├── analysis_options.yaml ├── example └── main.dart ├── .gitignore ├── test ├── _compute_caller.dart ├── _compute_caller_error.dart ├── _compute_caller_unsound_null_safety_error.dart ├── _compute_caller_invalid_message.dart ├── _compute_caller_invalid_error.dart ├── _compute_caller_unsound_null_safety.dart └── compute_test.dart ├── CHANGELOG.md ├── pubspec.yaml ├── lib ├── src │ ├── compute_web.dart │ ├── compute.dart │ └── compute_io.dart └── compute.dart ├── LICENSE ├── .github └── workflows │ └── continuous_integration.yml └── README.md /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:lints/recommended.yaml 2 | -------------------------------------------------------------------------------- /example/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:compute/compute.dart'; 2 | 3 | int square(int a) => a * a; 4 | 5 | Future main() async { 6 | final squared = await compute(square, 5); 7 | print('5^2=$squared'); 8 | } 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Files and directories created by pub. 2 | .dart_tool/ 3 | .packages 4 | 5 | # Conventional directory for build outputs. 6 | build/ 7 | 8 | # Omit committing pubspec.lock for library packages; see 9 | # https://dart.dev/guides/libraries/private-files#pubspeclock. 10 | pubspec.lock 11 | 12 | .idea/ 13 | 14 | test/.test_coverage.dart 15 | -------------------------------------------------------------------------------- /test/_compute_caller.dart: -------------------------------------------------------------------------------- 1 | // A test script that invokes compute() to start an isolate. 2 | 3 | import 'package:compute/compute.dart'; 4 | 5 | int getLength(String s) { 6 | return s.length; 7 | } 8 | 9 | Future main() async { 10 | const String s = 'hello world'; 11 | final int result = await compute(getLength, s); 12 | if (result != s.length) { 13 | throw Exception('compute returned bad result'); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /test/_compute_caller_error.dart: -------------------------------------------------------------------------------- 1 | // A test script that invokes compute() to start an isolate. 2 | 3 | import 'package:compute/compute.dart'; 4 | 5 | int getLength(String s) { 6 | throw 10; 7 | } 8 | 9 | Future main() async { 10 | const String s = 'hello world'; 11 | try { 12 | await compute(getLength, s); 13 | } catch (e) { 14 | if (e != 10) { 15 | throw Exception('compute threw bad result'); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 1.0.2 2 | 3 | - Small documentation and README improvements. 4 | 5 | ## 1.0.1 6 | 7 | - Sync with the status of the stable branch on August 14, 2022. No significant changes from upstream, only documentation changes. 8 | - Improve the `compute` package's README and documentation. 9 | - Clarify that this packages is synced with the `stable` branch. 10 | 11 | ## 1.0.0 12 | 13 | - Stable release. 14 | 15 | ## 0.1.0 16 | 17 | - Initial version. 18 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: compute 2 | description: Flutter's compute function made available for all non-Flutter Dart programs 3 | version: 1.0.2 4 | repository: https://github.com/dartsidedev/compute 5 | homepage: https://github.com/vincevargadev 6 | issue_tracker: https://github.com/dartsidedev/compute/issues 7 | environment: 8 | sdk: '>=2.15.1 <3.0.0' 9 | 10 | dependencies: 11 | meta: ^1.0.0 12 | 13 | dev_dependencies: 14 | lints: ^1.0.0 15 | test: ^1.16.0 16 | platform: ^3.1.0 17 | file: ^6.1.2 18 | -------------------------------------------------------------------------------- /test/_compute_caller_unsound_null_safety_error.dart: -------------------------------------------------------------------------------- 1 | // @dart=2.9 2 | // Running in unsound null-safety mode is intended to test for potential miscasts 3 | // or invalid assertions. 4 | 5 | import 'package:compute/compute.dart'; 6 | 7 | int throwNull(int arg) { 8 | throw null; 9 | } 10 | 11 | void main() async { 12 | try { 13 | await compute(throwNull, null); 14 | } catch (e) { 15 | if (e is! NullThrownError) { 16 | throw Exception('compute returned bad result'); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /test/_compute_caller_invalid_message.dart: -------------------------------------------------------------------------------- 1 | // A test script that invokes compute() to start an isolate. 2 | 3 | import 'dart:isolate'; 4 | 5 | import 'package:compute/compute.dart'; 6 | 7 | int getLength(ReceivePort s) { 8 | return 0; 9 | } 10 | 11 | Future main() async { 12 | final ReceivePort s = ReceivePort(); 13 | 14 | bool wasError = false; 15 | try { 16 | await compute(getLength, s); 17 | } on Object { 18 | wasError = true; 19 | } 20 | s.close(); 21 | 22 | assert(wasError); 23 | } 24 | -------------------------------------------------------------------------------- /test/_compute_caller_invalid_error.dart: -------------------------------------------------------------------------------- 1 | // A test script that invokes compute() to start an isolate. 2 | 3 | import 'dart:isolate'; 4 | 5 | import 'package:compute/compute.dart'; 6 | 7 | int getLength(String s) { 8 | final ReceivePort r = ReceivePort(); 9 | try { 10 | throw r; 11 | } finally { 12 | r.close(); 13 | } 14 | } 15 | 16 | Future main() async { 17 | const String s = 'hello world'; 18 | 19 | bool wasError = false; 20 | try { 21 | await compute(getLength, s); 22 | } on RemoteError { 23 | wasError = true; 24 | } 25 | if (!wasError) { 26 | throw Exception('compute threw bad result'); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /test/_compute_caller_unsound_null_safety.dart: -------------------------------------------------------------------------------- 1 | // @dart=2.9 2 | // Running in unsound null-safety mode is intended to test for potential miscasts 3 | // or invalid assertions. 4 | 5 | import 'package:compute/compute.dart'; 6 | 7 | int returnInt(int arg) { 8 | return arg; 9 | } 10 | 11 | Future returnIntAsync(int arg) { 12 | return Future.value(arg); 13 | } 14 | 15 | Future testCompute(ComputeCallback callback, T input) async { 16 | if (input != await compute(callback, input)) { 17 | throw Exception('compute returned bad result'); 18 | } 19 | } 20 | 21 | void main() async { 22 | await testCompute(returnInt, 10); 23 | await testCompute(returnInt, null); 24 | await testCompute(returnIntAsync, 10); 25 | await testCompute(returnIntAsync, null); 26 | } 27 | -------------------------------------------------------------------------------- /lib/src/compute_web.dart: -------------------------------------------------------------------------------- 1 | /// This file corresponds to Flutter's 2 | /// [`foundation/_isolates_web.dart`](https://github.com/flutter/flutter/blob/stable/packages/flutter/lib/src/foundation/_isolates_web.dart). 3 | /// 4 | /// Changes are only synced with the `stable` branch. 5 | /// 6 | /// Last synced commit: 7 | /// [978a2e7](https://github.com/flutter/flutter/commit/978a2e7bf6a2ed287130af8dbd94cef019fb7bef) 8 | /// 9 | /// The changes are currently manually synced. If you noticed that the Flutter's 10 | /// original `compute` function (and any of the related files) have changed 11 | /// on the `stable` branch and you would like to see those changes in the `compute` package 12 | /// please open an [issue](https://github.com/dartsidedev/compute/issues), 13 | /// and I'll try my best to "merge". 14 | /// 15 | /// The file is intentionally not refactored so that it is easier to keep the 16 | /// compute package up to date with Flutter's implementation. 17 | 18 | import 'compute.dart' as c; 19 | 20 | /// The dart:html implementation of [c.compute]. 21 | Future compute( 22 | c.ComputeCallback callback, 23 | Q message, { 24 | String? debugLabel, 25 | }) async { 26 | // To avoid blocking the UI immediately for an expensive function call, we 27 | // pump a single frame to allow the framework to complete the current set 28 | // of work. 29 | await null; 30 | return callback(message); 31 | } 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 2 | 3 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 4 | 5 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 6 | 7 | 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 8 | 9 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 10 | -------------------------------------------------------------------------------- /lib/compute.dart: -------------------------------------------------------------------------------- 1 | /// When working with isolates, wiring up the `SendPort`s, the `ReceivePort`s 2 | /// correctly is a lot of boilerplate code when all you want to do is spawn an 3 | /// isolate, compute something, and use the computed value. 4 | /// 5 | /// Flutter's `compute` function is a very useful abstraction over isolates that 6 | /// can be useful in all kinds of Dart apps. 7 | /// 8 | /// Unfortunately, Flutter's `compute` function is not available for a Dart 9 | /// package that doesn't use Flutter, for example command-line and server-side 10 | /// applications. 11 | /// 12 | /// This package addresses this issue. It extracts the `compute` function from 13 | /// Flutter and makes it available for all Dart projects, so if you wish to 14 | /// perform some computation on a separate isolate and use its return value, 15 | /// now you can! 16 | /// 17 | /// **Disclaimers** 18 | /// 19 | /// Do not assume that using `compute` will automatically speed up your code: 20 | /// you should benchmark your Dart applications 21 | /// *with and without the `compute` function*, and only switch to using 22 | /// `compute` if it really speeds up your application. 23 | /// 24 | /// Keep in mind, that by using `compute`, you lose some flexibility that 25 | /// working directly with isolates would enable you. 26 | /// 27 | /// The package is safe to be used on web, but there will be no real isolates 28 | /// spawned. 29 | /// 30 | /// Changes are synced with Flutter's stable branch only (and they are currently 31 | /// synced manually). 32 | export 'src/compute.dart'; 33 | -------------------------------------------------------------------------------- /.github/workflows/continuous_integration.yml: -------------------------------------------------------------------------------- 1 | name: Continuous Integration 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | format: 7 | runs-on: ubuntu-latest 8 | timeout-minutes: 5 9 | container: 10 | image: google/dart 11 | steps: 12 | - uses: actions/checkout@v2 13 | - name: Ensure idiomatic Dart source code formatting 14 | run: dart format --set-exit-if-changed . 15 | 16 | analyze: 17 | runs-on: ubuntu-latest 18 | timeout-minutes: 5 19 | container: 20 | image: google/dart 21 | steps: 22 | - uses: actions/checkout@v2 23 | - name: Get dependencies 24 | run: dart pub get 25 | - name: Ensure no analysis errors 26 | run: dart analyze --fatal-infos --fatal-warnings . 27 | 28 | test-ubuntu: 29 | runs-on: ubuntu-latest 30 | timeout-minutes: 5 31 | container: 32 | image: google/dart 33 | steps: 34 | - uses: actions/checkout@v2 35 | - name: Run tests 36 | run: dart test 37 | 38 | publish: 39 | runs-on: ubuntu-latest 40 | timeout-minutes: 5 41 | needs: [format, analyze, test-ubuntu] 42 | if: github.ref == 'refs/heads/main' 43 | # Continue on error doesn't seem to work? 44 | # Using || true for publishing 45 | # continue-on-error: true 46 | steps: 47 | - uses: actions/checkout@v2 48 | - name: Install Dart 49 | run: | 50 | sudo apt-get update 51 | sudo apt-get install apt-transport-https 52 | sudo sh -c 'wget -qO- https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add -' 53 | sudo sh -c 'wget -qO- https://storage.googleapis.com/download.dartlang.org/linux/debian/dart_stable.list > /etc/apt/sources.list.d/dart_stable.list' 54 | sudo apt-get update 55 | sudo apt-get install dart 56 | export PATH="$PATH:/usr/lib/dart/bin" 57 | - name: Setup credentials 58 | run: | 59 | mkdir -p ~/.pub-cache 60 | echo ${{ secrets.PUB_CREDENTIALS_JSON }} > ~/.pub-cache/credentials.json 61 | - name: Publish the package 62 | run: | 63 | export version=v$(grep "version: " pubspec.yaml | cut -c 10-) 64 | dart pub publish --force && (git tag $version && git push origin $version && gh release create $version) || true 65 | env: 66 | GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }} 67 | - name: Delete credentials 68 | run: rm ~/.pub-cache/credentials.json 69 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `compute` 2 | 3 | > Flutter's compute function made available for all non-Flutter Dart programs 4 | 5 | The `compute` package takes Flutter's `compute` function and makes it available for all Dart programs. 6 | 7 | [![Continuous Integration](https://github.com/dartsidedev/compute/workflows/Continuous%20Integration/badge.svg?branch=main)](https://github.com/dartsidedev/compute/actions) [![compute](https://img.shields.io/pub/v/compute?label=compute&logo=dart)](https://pub.dev/packages/compute 'See compute package info on pub.dev') [![Published by dartside.dev](https://img.shields.io/static/v1?label=Published%20by&message=dartside.dev&logo=dart&logoWidth=30&color=40C4FF&labelColor=1d599b&labelWidth=100)](https://pub.dev/publishers/dartside.dev/packages) [![GitHub Stars Count](https://img.shields.io/github/stars/dartsidedev/compute?logo=github)](https://github.com/dartsidedev/compute 'Star me on GitHub!') 8 | 9 | ## Important links 10 | 11 | * [Read the source code and **star the repo** on GitHub](https://github.com/dartsidedev/compute) 12 | * [Open an issue on GitHub](https://github.com/dartsidedev/compute/issues) 13 | * [See package on pub.dev](https://pub.dev/packages/compute) 14 | * [Read the docs on pub.dev](https://pub.dev/documentation/compute/latest/) 15 | * [Flutter `foundation`'s `compute` function](https://api.flutter.dev/flutter/foundation/compute-constant.html) 16 | 17 | **If you enjoy using this package, a thumbs-up on [pub.dev](https://pub.dev/packages/compute) would be highly appreciated! 👍💙** 18 | 19 | ## Motivation 20 | 21 | When working with isolates, wiring up the `SendPort`s, the `ReceivePort`s correctly 22 | is a lot of boilerplate code when all you want to do is spawn an isolate, compute something, and use the computed value. 23 | 24 | Flutter's `compute` function is a very useful abstraction over isolates that can be useful in all kinds of Dart apps. 25 | 26 | Unfortunately, Flutter's `compute` function is not available for a Dart package that doesn't use Flutter, 27 | for example command-line and server-side applications. 28 | 29 | This package addresses this issue. It extracts the `compute` function from Flutter and makes it available 30 | for all Dart projects, so if you wish to perform some computation on a separate isolate and use its return value, now you can! 31 | 32 | ## Disclaimers 33 | 34 | Do not assume that using `compute` will automatically speed up your code: 35 | you should benchmark your Dart applications *with and without the `compute` function*, 36 | and only switch to using `compute` if it really speeds up your application. 37 | 38 | Keep in mind, that by using `compute`, you lose some flexibility that working directly with isolates would enable you. 39 | 40 | The package is safe to be used on web, but there will be no real isolates spawned. 41 | 42 | Changes are synced with Flutter's stable branch only (and they are currently synced manually). 43 | 44 | ## Usage 45 | 46 | ### Dart 47 | 48 | This package works everywhere and doesn't have any Flutter-specific dependency. 49 | 50 | ```dart 51 | import 'package:compute/compute.dart'; 52 | 53 | int square(int a) => a * a; 54 | 55 | Future main() async { 56 | final squared = await compute(square, 5); 57 | print('5^2=$squared'); 58 | } 59 | ``` 60 | 61 | ### Flutter 62 | 63 | If you are on a Flutter project, you don't need this package. 64 | This package should only be used in environments where you cannot use Flutter's `compute` function. 65 | 66 | For your Flutter project, [use the `compute` function directly from Flutter's `foundation`](https://api.flutter.dev/flutter/foundation/compute-constant.html). 67 | -------------------------------------------------------------------------------- /lib/src/compute.dart: -------------------------------------------------------------------------------- 1 | /// This file corresponds to Flutter's 2 | /// [`foundation/isolates.dart`](https://github.com/flutter/flutter/blob/stable/packages/flutter/lib/src/foundation/isolates.dart). 3 | /// 4 | /// Changes are only synced with the `stable` branch. 5 | /// 6 | /// Last synced commit: 7 | /// [3d46ab9](https://github.com/flutter/flutter/commit/3d46ab920b47a2ecb250c6f890f3559ef913cb0b) 8 | /// 9 | /// The changes are currently manually synced. If you noticed that the Flutter's 10 | /// original `compute` function (and any of the related files) have changed 11 | /// on the `stable` branch and you would like to see those changes in the `compute` package 12 | /// please open an [issue](https://github.com/dartsidedev/compute/issues), 13 | /// and I'll try my best to "merge". 14 | /// 15 | /// The file is intentionally not refactored so that it is easier to keep the 16 | /// compute package up to date with Flutter's implementation. 17 | 18 | import 'dart:async'; 19 | 20 | import 'compute_io.dart' if (dart.library.html) 'compute_web.dart' as _c; 21 | 22 | /// Signature for the callback passed to [compute]. 23 | /// 24 | /// For more information, visit Flutter documentation for the equivalent 25 | /// [`ComputeCallback` type definition](https://api.flutter.dev/flutter/foundation/ComputeCallback.html) 26 | /// in Flutter. This documentation is taken directly from 27 | /// the Flutter source code. 28 | /// 29 | /// {@macro flutter.foundation.compute.types} 30 | /// 31 | /// Instances of [ComputeCallback] must be functions that can be sent to an 32 | /// isolate. 33 | /// {@macro flutter.foundation.compute.callback} 34 | /// 35 | /// {@macro flutter.foundation.compute.types} 36 | typedef ComputeCallback = FutureOr Function(Q message); 37 | 38 | /// The signature of [compute], which spawns an isolate, runs `callback` on 39 | /// that isolate, passes it `message`, and (eventually) returns the value 40 | /// returned by `callback`. 41 | /// 42 | /// For more information, visit Flutter documentation for the equivalent 43 | /// [`ComputeImpl` type definition](https://api.flutter.dev/flutter/foundation/ComputeImpl.html) 44 | /// in Flutter. This documentation is taken directly from 45 | /// the Flutter source code. 46 | /// 47 | /// {@macro flutter.foundation.compute.usecase} 48 | /// 49 | /// The function used as `callback` must be one that can be sent to an isolate. 50 | /// {@macro flutter.foundation.compute.callback} 51 | /// 52 | /// {@macro flutter.foundation.compute.types} 53 | /// 54 | /// The `debugLabel` argument can be specified to provide a name to add to the 55 | /// [Timeline]. This is useful when profiling an application. 56 | typedef ComputeImpl = Future Function( 57 | ComputeCallback callback, 58 | Q message, { 59 | String? debugLabel, 60 | }); 61 | 62 | /// A function that spawns an isolate and runs the provided `callback` on that 63 | /// isolate, passes it the provided `message`, and (eventually) returns the 64 | /// value returned by `callback`. 65 | /// 66 | /// For more information, visit Flutter documentation for the equivalent 67 | /// [`compute` function](https://pub.dev/documentation/compute/latest/compute/compute-constant.html) 68 | /// in Flutter. This documentation is taken directly from 69 | /// the Flutter source code. 70 | /// 71 | /// {@template flutter.foundation.compute.usecase} 72 | /// This is useful for operations that take longer than a few milliseconds, and 73 | /// which would therefore risk skipping frames. For tasks that will only take a 74 | /// few milliseconds, consider [SchedulerBinding.scheduleTask] instead. 75 | /// {@endtemplate} 76 | /// 77 | /// {@youtube 560 315 https://www.youtube.com/watch?v=5AxWC49ZMzs} 78 | /// 79 | /// The following code uses the [compute] function to check whether a given 80 | /// integer is a prime number. 81 | /// 82 | /// ```dart 83 | /// Future isPrime(int value) { 84 | /// return compute(_calculate, value); 85 | /// } 86 | /// 87 | /// bool _calculate(int value) { 88 | /// if (value == 1) { 89 | /// return false; 90 | /// } 91 | /// for (int i = 2; i < value; ++i) { 92 | /// if (value % i == 0) { 93 | /// return false; 94 | /// } 95 | /// } 96 | /// return true; 97 | /// } 98 | /// ``` 99 | /// 100 | /// The function used as `callback` must be one that can be sent to an isolate. 101 | /// {@template flutter.foundation.compute.callback} 102 | /// Qualifying functions include: 103 | /// 104 | /// * top-level functions 105 | /// * static methods 106 | /// * closures that only capture objects that can be sent to an isolate 107 | /// 108 | /// Using closures must be done with care. Due to 109 | /// [dart-lang/sdk#36983](https://github.com/dart-lang/sdk/issues/36983) a 110 | /// closure may captures objects that, while not directly used in the closure 111 | /// itself, may prevent it from being sent to an isolate. 112 | /// {@endtemplate} 113 | /// 114 | /// {@template flutter.foundation.compute.types} 115 | /// The [compute] method accepts the following parameters: 116 | /// 117 | /// * `Q` is the type of the message that kicks off the computation. 118 | /// * `R` is the type of the value returned. 119 | /// 120 | /// There are limitations on the values that can be sent and received to and 121 | /// from isolates. These limitations constrain the values of `Q` and `R` that 122 | /// are possible. See the discussion at [SendPort.send]. 123 | /// 124 | /// The same limitations apply to any errors generated by the computation. 125 | /// {@endtemplate} 126 | /// 127 | /// See also: 128 | /// 129 | /// * [ComputeImpl], for the [compute] function's signature. 130 | const ComputeImpl compute = _c.compute; 131 | -------------------------------------------------------------------------------- /lib/src/compute_io.dart: -------------------------------------------------------------------------------- 1 | /// This file corresponds to Flutter's 2 | /// [`foundation/_isolates_io.dart`](https://github.com/flutter/flutter/blob/stable/packages/flutter/lib/src/foundation/_isolates_io.dart). 3 | /// 4 | /// Changes are only synced with the `stable` branch. 5 | /// 6 | /// Last synced commit: 7 | /// [3420b9c](https://github.com/flutter/flutter/commit/3420b9c50ea19489dd74b024705bb010c5763d0a) 8 | /// 9 | /// The changes are currently manually synced. If you noticed that the Flutter's 10 | /// original `compute` function (and any of the related files) have changed 11 | /// on the `stable` branch and you would like to see those changes in the `compute` package 12 | /// please open an [issue](https://github.com/dartsidedev/compute/issues), 13 | /// and I'll try my best to "merge". 14 | /// 15 | /// The file is intentionally not refactored so that it is easier to keep the 16 | /// compute package up to date with Flutter's implementation. 17 | 18 | import 'dart:async'; 19 | import 'dart:developer'; 20 | import 'dart:isolate'; 21 | 22 | import 'package:meta/meta.dart'; 23 | 24 | import 'compute.dart' as c; 25 | 26 | const _kReleaseMode = bool.fromEnvironment('dart.vm.product'); 27 | 28 | /// The dart:io implementation of [c.compute]. 29 | Future compute( 30 | c.ComputeCallback callback, 31 | Q message, { 32 | String? debugLabel, 33 | }) async { 34 | debugLabel ??= _kReleaseMode ? 'compute' : callback.toString(); 35 | 36 | final Flow flow = Flow.begin(); 37 | Timeline.startSync('$debugLabel: start', flow: flow); 38 | final RawReceivePort port = RawReceivePort(); 39 | Timeline.finishSync(); 40 | 41 | void _timeEndAndCleanup() { 42 | Timeline.startSync('$debugLabel: end', flow: Flow.end(flow.id)); 43 | port.close(); 44 | Timeline.finishSync(); 45 | } 46 | 47 | final Completer completer = Completer(); 48 | port.handler = (dynamic msg) { 49 | _timeEndAndCleanup(); 50 | completer.complete(msg); 51 | }; 52 | 53 | try { 54 | await Isolate.spawn<_IsolateConfiguration>( 55 | _spawn, 56 | _IsolateConfiguration( 57 | callback, 58 | message, 59 | port.sendPort, 60 | debugLabel, 61 | flow.id, 62 | ), 63 | errorsAreFatal: true, 64 | onExit: port.sendPort, 65 | onError: port.sendPort, 66 | debugName: debugLabel, 67 | ); 68 | } on Object { 69 | _timeEndAndCleanup(); 70 | rethrow; 71 | } 72 | 73 | final dynamic response = await completer.future; 74 | if (response == null) { 75 | throw RemoteError('Isolate exited without result or error.', ''); 76 | } 77 | 78 | assert(response is List); 79 | response as List; 80 | 81 | final int type = response.length; 82 | assert(1 <= type && type <= 3); 83 | 84 | switch (type) { 85 | // success; see _buildSuccessResponse 86 | case 1: 87 | return response[0] as R; 88 | 89 | // native error; see Isolate.addErrorListener 90 | case 2: 91 | await Future.error(RemoteError( 92 | response[0] as String, 93 | response[1] as String, 94 | )); 95 | 96 | // caught error; see _buildErrorResponse 97 | case 3: 98 | default: 99 | assert(type == 3 && response[2] == null); 100 | 101 | await Future.error( 102 | response[0] as Object, 103 | response[1] as StackTrace, 104 | ); 105 | } 106 | } 107 | 108 | @immutable 109 | class _IsolateConfiguration { 110 | const _IsolateConfiguration( 111 | this.callback, 112 | this.message, 113 | this.resultPort, 114 | this.debugLabel, 115 | this.flowId, 116 | ); 117 | 118 | final c.ComputeCallback callback; 119 | final Q message; 120 | final SendPort resultPort; 121 | final String debugLabel; 122 | final int flowId; 123 | 124 | FutureOr applyAndTime() { 125 | return Timeline.timeSync( 126 | debugLabel, 127 | () => callback(message), 128 | flow: Flow.step(flowId), 129 | ); 130 | } 131 | } 132 | 133 | /// The spawn point MUST guarantee only one result event is sent through the 134 | /// [SendPort.send] be it directly or indirectly i.e. [Isolate.exit]. 135 | /// 136 | /// In case an [Error] or [Exception] are thrown AFTER the data 137 | /// is sent, they will NOT be handled or reported by the main [Isolate] because 138 | /// it stops listening after the first event is received. 139 | /// 140 | /// Also use the helpers [_buildSuccessResponse] and [_buildErrorResponse] to 141 | /// build the response 142 | Future _spawn(_IsolateConfiguration configuration) async { 143 | late final List computationResult; 144 | 145 | try { 146 | computationResult = 147 | _buildSuccessResponse(await configuration.applyAndTime()); 148 | } catch (e, s) { 149 | computationResult = _buildErrorResponse(e, s); 150 | } 151 | 152 | Isolate.exit(configuration.resultPort, computationResult); 153 | } 154 | 155 | /// Wrap in [List] to ensure our expectations in the main [Isolate] are met. 156 | /// 157 | /// We need to wrap a success result in a [List] because the user provided type 158 | /// [R] could also be a [List]. Meaning, a check `result is R` could return true 159 | /// for what was an error event. 160 | List _buildSuccessResponse(R result) { 161 | return List.filled(1, result); 162 | } 163 | 164 | /// Wrap in [List] to ensure our expectations in the main isolate are met. 165 | /// 166 | /// We wrap a caught error in a 3 element [List]. Where the last element is 167 | /// always null. We do this so we have a way to know if an error was one we 168 | /// caught or one thrown by the library code. 169 | List _buildErrorResponse(Object error, StackTrace stack) { 170 | return List.filled(3, null) 171 | ..[0] = error 172 | ..[1] = stack; 173 | } 174 | -------------------------------------------------------------------------------- /test/compute_test.dart: -------------------------------------------------------------------------------- 1 | /// This file corresponds to Flutter's 2 | /// [`test/foundation/isolates_test.dart`](https://github.com/flutter/flutter/blob/stable/packages/flutter/test/foundation/isolates_test.dart). 3 | /// 4 | /// Changes are only synced with the `stable` branch. 5 | /// 6 | /// Last synced commit: 7 | /// [3d46ab9](https://github.com/flutter/flutter/commit/3d46ab920b47a2ecb250c6f890f3559ef913cb0b) 8 | /// 9 | /// The changes are currently manually synced. If you noticed that the Flutter's 10 | /// original `compute` function (and any of the related files) have changed 11 | /// on the `stable` branch and you would like to see those changes in the `compute` package 12 | /// please open an [issue](https://github.com/dartsidedev/compute/issues), 13 | /// and I'll try my best to "merge". 14 | /// 15 | /// The file is intentionally not refactored so that it is easier to keep the 16 | /// compute package up to date with Flutter's implementation. 17 | 18 | import 'dart:io'; 19 | import 'dart:isolate'; 20 | 21 | import 'package:compute/compute.dart'; 22 | import 'package:file/file.dart'; 23 | import 'package:file/local.dart'; 24 | import 'package:test/test.dart'; 25 | 26 | final Matcher throwsRemoteError = throwsA(isA()); 27 | 28 | int test1(int value) { 29 | return value + 1; 30 | } 31 | 32 | int test2(int value) { 33 | throw 2; 34 | } 35 | 36 | int test3(int value) { 37 | Isolate.exit(); 38 | } 39 | 40 | int test4(int value) { 41 | Isolate.current.kill(); 42 | 43 | return value + 1; 44 | } 45 | 46 | int test5(int value) { 47 | Isolate.current.kill(priority: Isolate.immediate); 48 | 49 | return value + 1; 50 | } 51 | 52 | Future test1Async(int value) async { 53 | return value + 1; 54 | } 55 | 56 | Future test2Async(int value) async { 57 | throw 2; 58 | } 59 | 60 | Future test3Async(int value) async { 61 | Isolate.exit(); 62 | } 63 | 64 | Future test4Async(int value) async { 65 | Isolate.current.kill(); 66 | 67 | return value + 1; 68 | } 69 | 70 | Future test5Async(int value) async { 71 | Isolate.current.kill(priority: Isolate.immediate); 72 | 73 | return value + 1; 74 | } 75 | 76 | Future test1CallCompute(int value) { 77 | return compute(test1, value); 78 | } 79 | 80 | Future test2CallCompute(int value) { 81 | return compute(test2, value); 82 | } 83 | 84 | Future test3CallCompute(int value) { 85 | return compute(test3, value); 86 | } 87 | 88 | Future test4CallCompute(int value) { 89 | return compute(test4, value); 90 | } 91 | 92 | Future test5CallCompute(int value) { 93 | return compute(test5, value); 94 | } 95 | 96 | Future expectFileSuccessfullyCompletes(String filename) async { 97 | // Run a Dart script that calls compute(). 98 | // The Dart process will terminate only if the script exits cleanly with 99 | // all isolate ports closed. 100 | const FileSystem fs = LocalFileSystem(); 101 | // const Platform platform = LocalPlatform(); 102 | // final String flutterRoot = platform.environment['FLUTTER_ROOT']!; 103 | final String dartPath = 'dart'; 104 | // fs.path.join(flutterRoot, 'bin', 'cache', 'dart-sdk', 'bin', 'dart'); 105 | // final String packageRoot = fs.path.dirname(fs.path.fromUri(platform.script)); 106 | final String scriptPath = fs.path.join('test', filename); 107 | // fs.path.join(packageRoot, 'test', 'foundation', filename); 108 | 109 | // Enable asserts to also catch potentially invalid assertions. 110 | final ProcessResult result = await Process.run( 111 | dartPath, ['run', '--enable-asserts', scriptPath]); 112 | expect(result.exitCode, 0); 113 | } 114 | 115 | class ComputeTestSubject { 116 | ComputeTestSubject(this.base, [this.additional]); 117 | 118 | final int base; 119 | final dynamic additional; 120 | 121 | int method(int x) { 122 | return base * x; 123 | } 124 | 125 | static int staticMethod(int square) { 126 | return square * square; 127 | } 128 | } 129 | 130 | Future computeStaticMethod(int square) { 131 | return compute(ComputeTestSubject.staticMethod, square); 132 | } 133 | 134 | Future computeClosure(int square) { 135 | return compute((_) => square * square, null); 136 | } 137 | 138 | Future computeInvalidClosure(int square) { 139 | final ReceivePort r = ReceivePort(); 140 | 141 | return compute((_) { 142 | r.sendPort.send('Computing!'); 143 | 144 | return square * square; 145 | }, null); 146 | } 147 | 148 | Future computeInstanceMethod(int square) { 149 | final ComputeTestSubject subject = ComputeTestSubject(square); 150 | return compute(subject.method, square); 151 | } 152 | 153 | Future computeInvalidInstanceMethod(int square) { 154 | final ComputeTestSubject subject = ComputeTestSubject(square, ReceivePort()); 155 | return compute(subject.method, square); 156 | } 157 | 158 | dynamic testInvalidResponse(int square) { 159 | final ReceivePort r = ReceivePort(); 160 | try { 161 | return r; 162 | } finally { 163 | r.close(); 164 | } 165 | } 166 | 167 | dynamic testInvalidError(int square) { 168 | final ReceivePort r = ReceivePort(); 169 | try { 170 | throw r; 171 | } finally { 172 | r.close(); 173 | } 174 | } 175 | 176 | String? testDebugName(_) { 177 | return Isolate.current.debugName; 178 | } 179 | 180 | int? testReturnNull(_) { 181 | return null; 182 | } 183 | 184 | void main() { 185 | test('compute()', () async { 186 | expect(await compute(test1, 0), 1); 187 | expect(compute(test2, 0), throwsA(2)); 188 | expect(compute(test3, 0), throwsRemoteError); 189 | expect(await compute(test4, 0), 1); 190 | expect(compute(test5, 0), throwsRemoteError); 191 | 192 | expect(await compute(test1Async, 0), 1); 193 | expect(compute(test2Async, 0), throwsA(2)); 194 | expect(compute(test3Async, 0), throwsRemoteError); 195 | expect(await compute(test4Async, 0), 1); 196 | expect(compute(test5Async, 0), throwsRemoteError); 197 | 198 | expect(await compute(test1CallCompute, 0), 1); 199 | expect(compute(test2CallCompute, 0), throwsA(2)); 200 | expect(compute(test3CallCompute, 0), throwsRemoteError); 201 | expect(await compute(test4CallCompute, 0), 1); 202 | expect(compute(test5CallCompute, 0), throwsRemoteError); 203 | 204 | expect(compute(testInvalidResponse, 0), throwsRemoteError); 205 | expect(compute(testInvalidError, 0), throwsRemoteError); 206 | 207 | expect(await computeStaticMethod(10), 100); 208 | expect(await computeClosure(10), 100); 209 | expect(computeInvalidClosure(10), throwsArgumentError); 210 | expect(await computeInstanceMethod(10), 100); 211 | expect(computeInvalidInstanceMethod(10), throwsArgumentError); 212 | 213 | expect(await compute(testDebugName, null, debugLabel: 'debug_name'), 214 | 'debug_name'); 215 | expect(await compute(testReturnNull, null), null); 216 | }); 217 | 218 | group('compute() closes all ports', () { 219 | test('with valid message', () async { 220 | await expectFileSuccessfullyCompletes('_compute_caller.dart'); 221 | }); 222 | test('with invalid message', () async { 223 | await expectFileSuccessfullyCompletes( 224 | '_compute_caller_invalid_message.dart'); 225 | }); 226 | test('with valid error', () async { 227 | await expectFileSuccessfullyCompletes('_compute_caller.dart'); 228 | }); 229 | test('with invalid error', () async { 230 | await expectFileSuccessfullyCompletes( 231 | '_compute_caller_invalid_message.dart'); 232 | }); 233 | }); 234 | 235 | group('compute() works with unsound null safety caller', () { 236 | test('returning', () async { 237 | await expectFileSuccessfullyCompletes( 238 | '_compute_caller_unsound_null_safety.dart'); 239 | }); 240 | test('erroring', () async { 241 | await expectFileSuccessfullyCompletes( 242 | '_compute_caller_unsound_null_safety_error.dart'); 243 | }); 244 | }); 245 | } 246 | --------------------------------------------------------------------------------