LCOV - code coverage report
Current view: top level - src - isolate.dart (source / functions) Hit Total Coverage
Test: lcov.info Lines: 118 142 83.1 %
Date: 2021-03-31 22:28:15 Functions: 0 0 -

          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             : }

Generated by: LCOV version 1.14