Line data Source code
1 : /*
2 : * @Description: isolate
3 : * @Author: ekibun
4 : * @Date: 2020-10-02 13:49:03
5 : * @LastEditors: ekibun
6 : * @LastEditTime: 2020-10-03 22:21:31
7 : */
8 : part of '../flutter_qjs.dart';
9 :
10 : typedef dynamic _Decode(Map obj);
11 9 : List<_Decode> _decoders = [
12 : JSError._decode,
13 : IsolateFunction._decode,
14 : ];
15 :
16 : abstract class _IsolateEncodable {
17 : Map _encode();
18 : }
19 :
20 3 : dynamic _encodeData(dynamic data, {Map<dynamic, dynamic>? cache}) {
21 3 : if (cache == null) cache = Map();
22 5 : if (cache.containsKey(data)) return cache[data];
23 6 : if (data is _IsolateEncodable) return data._encode();
24 3 : if (data is List) {
25 3 : final ret = [];
26 3 : cache[data] = ret;
27 9 : for (int i = 0; i < data.length; ++i) {
28 9 : ret.add(_encodeData(data[i], cache: cache));
29 : }
30 : return ret;
31 : }
32 3 : if (data is Map) {
33 2 : final ret = {};
34 2 : cache[data] = ret;
35 4 : for (final entry in data.entries) {
36 6 : ret[_encodeData(entry.key, cache: cache)] =
37 4 : _encodeData(entry.value, cache: cache);
38 : }
39 : return ret;
40 : }
41 3 : if (data is Future) {
42 2 : final futurePort = ReceivePort();
43 4 : data.then((value) {
44 6 : futurePort.first.then((port) {
45 2 : futurePort.close();
46 4 : (port as SendPort).send(_encodeData(value));
47 : });
48 2 : }, onError: (e) {
49 6 : futurePort.first.then((port) {
50 2 : futurePort.close();
51 6 : (port as SendPort).send({#error: _encodeData(e)});
52 : });
53 : });
54 2 : return {
55 2 : #jsFuturePort: futurePort.sendPort,
56 : };
57 : }
58 : return data;
59 : }
60 :
61 3 : dynamic _decodeData(dynamic data, {Map<dynamic, dynamic>? cache}) {
62 3 : if (cache == null) cache = Map();
63 5 : if (cache.containsKey(data)) return cache[data];
64 3 : if (data is List) {
65 3 : final ret = [];
66 3 : cache[data] = ret;
67 9 : for (int i = 0; i < data.length; ++i) {
68 9 : ret.add(_decodeData(data[i], cache: cache));
69 : }
70 : return ret;
71 : }
72 3 : if (data is Map) {
73 6 : for (final decoder in _decoders) {
74 3 : final decodeObj = decoder(data);
75 : if (decodeObj != null) return decodeObj;
76 : }
77 2 : if (data.containsKey(#jsFuturePort)) {
78 2 : SendPort port = data[#jsFuturePort];
79 2 : final futurePort = ReceivePort();
80 4 : port.send(futurePort.sendPort);
81 2 : final futureCompleter = Completer();
82 6 : futureCompleter.future.catchError((e) {});
83 6 : futurePort.first.then((value) {
84 2 : futurePort.close();
85 4 : if (value is Map && value.containsKey(#error)) {
86 6 : futureCompleter.completeError(_decodeData(value[#error]));
87 : } else {
88 4 : futureCompleter.complete(_decodeData(value));
89 : }
90 : });
91 2 : return futureCompleter.future;
92 : }
93 2 : final ret = {};
94 2 : cache[data] = ret;
95 4 : for (final entry in data.entries) {
96 6 : ret[_decodeData(entry.key, cache: cache)] =
97 4 : _decodeData(entry.value, cache: cache);
98 : }
99 : return ret;
100 : }
101 : return data;
102 : }
103 :
104 2 : void _runJsIsolate(Map spawnMessage) async {
105 2 : SendPort sendPort = spawnMessage[#port];
106 2 : ReceivePort port = ReceivePort();
107 4 : sendPort.send(port.sendPort);
108 2 : final qjs = FlutterQjs(
109 2 : stackSize: spawnMessage[#stackSize],
110 1 : hostPromiseRejectionHandler: (reason) {
111 2 : sendPort.send({
112 : #type: #hostPromiseRejection,
113 1 : #reason: _encodeData(reason),
114 : });
115 : },
116 0 : moduleHandler: (name) {
117 : final ptr = calloc<Pointer<Utf8>>();
118 0 : ptr.value = Pointer.fromAddress(ptr.address);
119 0 : sendPort.send({
120 : #type: #module,
121 : #name: name,
122 0 : #ptr: ptr.address,
123 : });
124 0 : while (ptr.value.address == ptr.address) sleep(Duration(microseconds: 1));
125 0 : final ret = ptr.value;
126 0 : malloc.free(ptr);
127 0 : if (ret.address == -1) throw JSError('Module Not found');
128 0 : final retString = ret.toDartString();
129 0 : malloc.free(ret);
130 : return retString;
131 : },
132 : );
133 4 : port.listen((msg) async {
134 : var data;
135 2 : SendPort? msgPort = msg[#port];
136 : try {
137 2 : switch (msg[#type]) {
138 2 : case #evaluate:
139 4 : data = await qjs.evaluate(
140 2 : msg[#command],
141 2 : name: msg[#name],
142 2 : evalFlags: msg[#flag],
143 : );
144 : break;
145 2 : case #close:
146 : data = false;
147 4 : qjs.port.close();
148 2 : qjs.close();
149 2 : port.close();
150 : data = true;
151 : break;
152 : }
153 4 : if (msgPort != null) msgPort.send(_encodeData(data));
154 : } catch (e) {
155 : if (msgPort != null)
156 0 : msgPort.send({
157 0 : #error: _encodeData(e),
158 : });
159 : }
160 : });
161 4 : await qjs.dispatch();
162 : }
163 :
164 : typedef _JsAsyncModuleHandler = Future<String> Function(String name);
165 :
166 : class IsolateQjs {
167 : Future<SendPort>? _sendPort;
168 :
169 : /// Max stack size for quickjs.
170 : final int? stackSize;
171 :
172 : /// Asynchronously handler to manage js module.
173 : final _JsAsyncModuleHandler? moduleHandler;
174 :
175 : /// Handler function to manage js module.
176 : final _JsHostPromiseRejectionHandler? hostPromiseRejectionHandler;
177 :
178 : /// Quickjs engine runing on isolate thread.
179 : ///
180 : /// Pass handlers to implement js-dart interaction and resolving modules. The `methodHandler` is
181 : /// used in isolate, so **the handler function must be a top-level function or a static method**.
182 1 : IsolateQjs({
183 : this.moduleHandler,
184 : this.stackSize,
185 : this.hostPromiseRejectionHandler,
186 : });
187 :
188 1 : _ensureEngine() {
189 1 : if (_sendPort != null) return;
190 1 : ReceivePort port = ReceivePort();
191 1 : Isolate.spawn(
192 : _runJsIsolate,
193 1 : {
194 1 : #port: port.sendPort,
195 1 : #stackSize: stackSize,
196 : },
197 : errorsAreFatal: true,
198 : );
199 1 : final completer = Completer<SendPort>();
200 2 : port.listen((msg) async {
201 2 : if (msg is SendPort && !completer.isCompleted) {
202 1 : completer.complete(msg);
203 : return;
204 : }
205 1 : switch (msg[#type]) {
206 1 : case #hostPromiseRejection:
207 : try {
208 2 : final err = _decodeData(msg[#reason]);
209 1 : if (hostPromiseRejectionHandler != null) {
210 2 : hostPromiseRejectionHandler!(err);
211 : } else {
212 0 : print('unhandled promise rejection: $err');
213 : }
214 : } catch (e) {
215 0 : print('host Promise Rejection Handler error: $e');
216 : }
217 : break;
218 0 : case #module:
219 0 : final ptr = Pointer<Pointer>.fromAddress(msg[#ptr]);
220 : try {
221 0 : ptr.value = (await moduleHandler!(msg[#name])).toNativeUtf8();
222 : } catch (e) {
223 0 : ptr.value = Pointer.fromAddress(-1);
224 : }
225 : break;
226 : }
227 0 : }, onDone: () {
228 0 : close();
229 0 : if (!completer.isCompleted)
230 0 : completer.completeError(JSError('isolate close'));
231 : });
232 2 : _sendPort = completer.future;
233 : }
234 :
235 : /// Free Runtime and close isolate thread that can be recreate when evaluate again.
236 1 : close() {
237 1 : final sendPort = _sendPort;
238 1 : _sendPort = null;
239 : if (sendPort == null) return;
240 2 : final ret = sendPort.then((sendPort) async {
241 1 : final closePort = ReceivePort();
242 2 : sendPort.send({
243 : #type: #close,
244 1 : #port: closePort.sendPort,
245 : });
246 2 : final result = await closePort.first;
247 1 : closePort.close();
248 1 : if (result is Map && result.containsKey(#error))
249 0 : throw _decodeData(result[#error]);
250 1 : return _decodeData(result);
251 : });
252 : return ret;
253 : }
254 :
255 : /// Evaluate js script.
256 1 : Future<dynamic> evaluate(
257 : String command, {
258 : String? name,
259 : int? evalFlags,
260 : }) async {
261 1 : _ensureEngine();
262 1 : final evaluatePort = ReceivePort();
263 2 : final sendPort = await _sendPort!;
264 2 : sendPort.send({
265 : #type: #evaluate,
266 : #command: command,
267 : #name: name,
268 : #flag: evalFlags,
269 1 : #port: evaluatePort.sendPort,
270 : });
271 2 : final result = await evaluatePort.first;
272 1 : evaluatePort.close();
273 2 : if (result is Map && result.containsKey(#error))
274 0 : throw _decodeData(result[#error]);
275 1 : return _decodeData(result);
276 : }
277 : }
|