Line data Source code
1 : import 'dart:async';
2 : import 'dart:isolate';
3 :
4 : import 'package:combine/src/combine_isolate/combine_isolate.dart';
5 : import 'package:combine/src/combine_singleton.dart';
6 : import 'package:combine/src/combine_worker/combine_worker_impl.dart';
7 : import 'package:combine/src/combine_worker/tasks.dart';
8 : import 'package:flutter/foundation.dart';
9 :
10 : const defaultTasksPerIsolate = 2;
11 : const defaultIsolatePrefix = 'combine-worker';
12 :
13 : /// {@template combine_worker_singleton}
14 : /// Combine Worker is a pool of [CombineIsolate]s that efficiently executes
15 : /// tasks in them. It is primarily used to reduce the number of isolates
16 : /// and provide more performant and easy way to execute tasks.
17 : ///
18 : /// In comparison to Fluter's [compute] method which creates an isolate each time
19 : /// it's called, Combine Worker creates a pool of isolates and efficiently
20 : /// reuses them. At the same time, it has all of the advantages of [CombineIsolate],
21 : /// allowing you to work with platform channels in an isolate.
22 : ///
23 : /// If you want to create a single [CombineIsolate], take a look at [Combine].
24 : /// {@endtemplate}
25 : abstract class CombineWorker {
26 : /// {@macro combine_worker_singleton}
27 2 : factory CombineWorker() => _instance;
28 :
29 : /// Creates a new instance of the [CombineWorker].
30 2 : factory CombineWorker.newInstance() => CombineWorkerImpl();
31 :
32 3 : static late final _instance = CombineWorker.newInstance();
33 :
34 : /// {@template combine_worker_initialize}
35 : /// Initializes worker.
36 : ///
37 : /// Worker manager can be lazily initialized on the first execution,
38 : /// so you can omit calling `initialize`.
39 : ///
40 : /// To initialize worker with a custom amount of isolates, use the
41 : /// [isolatesCount] parameter.
42 : /// Default value is calculated by the following formula:
43 : /// `max(1, (numberOfProcessors / 2).floor())`
44 : /// Please keep in mind that Flutter already uses 3 threads:
45 : /// Dart main, native main and GPU. So your [isolatesCount] should be less
46 : /// than `numberOfProcessors - 3`.
47 : ///
48 : /// Each isolate can execute one or more tasks asynchronously
49 : /// (thanks to async IO and event loop).
50 : /// [tasksPerIsolate] parameter is used to set maximum number of tasks that
51 : /// one isolate can perform asynchronously.
52 : ///
53 : /// [initializer] is a function that will be executed in the each worker
54 : /// isolate. It can be used to initialize something in the worker isolate.
55 : ///
56 : /// [isolatesPrefix] will be used to set isolates debug name. Debug name will
57 : /// be visible in the debugger.
58 : /// {@endtemplate}
59 : Future<void> initialize({
60 : int? isolatesCount,
61 : int tasksPerIsolate = defaultTasksPerIsolate,
62 : WorkerInitializer? initializer,
63 : String isolatesPrefix = defaultIsolatePrefix,
64 : });
65 :
66 : /// {@template combine_worker_execute}
67 : /// Executes given [task] in combine isolate.
68 : /// {@endtemplate}
69 : ///
70 : /// {@template combine_worker_execute_exception}
71 : /// This future may complete with:
72 : /// - [CombineWorkerClosedException] if you [close] worker
73 : /// with `waitForRemainingTasks` flag set to `false`.
74 : /// - [UnsupportedIsolateArgumentError] if you send to/from isolate
75 : /// some unsupported object like [ReceivePort].
76 : /// - an original exception thrown by the [task].
77 : /// {@endtemplate}
78 : Future<T> execute<T>(Task<T> task);
79 :
80 : /// {@template combine_worker_execute_with_arg}
81 : /// Executes given [task] with given [argument] in combine isolate.
82 : /// {@endtemplate}
83 : ///
84 : /// {@macro combine_worker_execute_exception}
85 : Future<T> executeWithArg<T, Q>(Task1<T, Q> task, Q argument);
86 :
87 : /// {@template combine_worker_execute_with_2_args}
88 : /// Executes given [task] with given [argument] and [argument2] in combine isolate.
89 : /// {@endtemplate}
90 : ///
91 : /// {@macro combine_worker_execute_exception}
92 : Future<T> executeWith2Args<T, Q, C>(
93 : Task2<T, Q, C> task,
94 : Q argument,
95 : C argument2,
96 : );
97 :
98 : /// {@template combine_worker_execute_with_multiple_args}
99 : /// Executes given [task] with given arguments in combine isolate.
100 : /// {@endtemplate}
101 : ///
102 : /// {@macro combine_worker_execute_exception}
103 : Future<T> executeWith3Args<T, Q, C, A>(
104 : Task3<T, Q, C, A> task,
105 : Q argument,
106 : C argument2,
107 : A argument3,
108 : );
109 :
110 : /// {@template combine_worker_execute_with_multiple_args}
111 : /// Executes given [task] with given arguments in combine isolate.
112 : /// {@endtemplate}
113 : ///
114 : /// {@macro combine_worker_execute_exception}
115 : Future<T> executeWith4Args<T, Q, C, A, B>(
116 : Task4<T, Q, C, A, B> task,
117 : Q argument,
118 : C argument2,
119 : A argument3,
120 : B argument4,
121 : );
122 :
123 : /// {@template combine_worker_execute_with_multiple_args}
124 : /// Executes given [task] with given arguments in combine isolate.
125 : /// {@endtemplate}
126 : ///
127 : /// {@macro combine_worker_execute_exception}
128 : Future<T> executeWith5Args<T, Q, C, A, B, D>(
129 : Task5<T, Q, C, A, B, D> task,
130 : Q argument,
131 : C argument2,
132 : A argument3,
133 : B argument4,
134 : D argument5,
135 : );
136 :
137 : /// {@template combine_worker_execute_with_multiple_args}
138 : /// Dynamically execute [task] with the specified arguments in combine isolate
139 : /// It works like [Function.apply].
140 : ///
141 : /// Acts the same as calling function with positional arguments
142 : /// corresponding to the elements of [positionalArguments] and
143 : /// named arguments corresponding to the elements of [namedArguments].
144 : ///
145 : /// This includes giving the same errors if [task] isn't callable or
146 : /// if it expects different parameters.
147 : ///
148 : /// Don't use this method while you can use [executeWith5Args],
149 : /// [executeWith4Args], [executeWith3Args] etc.
150 : /// These methods are typesafe unlike [executeWithApply].
151 : /// {@endtemplate}
152 : ///
153 : /// {@macro combine_worker_execute_exception}
154 : Future<T> executeWithApply<T>(
155 : TaskApply task,
156 : List<dynamic> positionalArguments, [
157 : Map<Symbol, dynamic>? namedArguments,
158 : ]);
159 :
160 : /// {@template combine_worker_close}
161 : /// Closes the current Worker.
162 : /// [CombineWorker] is a singleton but under the hood it uses a worker manager instance
163 : /// which can be closed and recreated. It may be useful if you want to cancel
164 : /// all running and awaiting tasks (i. e. on user logout).
165 : ///
166 : /// If `waitForRemainingTasks` flag is set to `true` then
167 : /// worker will be marked as closed but will finish all its tasks.
168 : /// Otherwise all remaining tasks will complete with [CombineWorkerClosedException].
169 : ///
170 : /// You can call [execute] or [initialize] methods without awaiting for this future.
171 : /// In that case new isolates' pool will be created.
172 : /// {@endtemplate}
173 : Future<void> close({bool waitForRemainingTasks = false});
174 : }
175 :
176 : /// Typedef for the function that will be executed in the each worker isolate.
177 : typedef WorkerInitializer = FutureOr<void> Function();
178 :
179 : class CombineWorkerClosedException implements Exception {
180 1 : @override
181 : String toString() {
182 : return "The CombineWorker has been `close`d with `waitForRemainingTasks: false`. "
183 : "So task can't be finished.\n"
184 : "If you want to close the Worker and wait for remaining tasks call the "
185 : "`close` method with the `waitForRemainingTasks: false` parameter.";
186 : }
187 : }
188 :
189 : /// This exception is thrown when when you send to/from isolate some unsupported data like [ReceivePort].
190 : class UnsupportedIsolateArgumentError extends ArgumentError {
191 1 : UnsupportedIsolateArgumentError(this.originalError);
192 :
193 : final ArgumentError originalError;
194 :
195 : /// Name of the invalid argument, if available.
196 1 : @override
197 2 : String? get name => originalError.name;
198 :
199 : /// Message describing the problem.
200 1 : @override
201 2 : dynamic get message => originalError.message;
202 : }
|