mirror of
https://github.com/wgh136/flutter_qjs.git
synced 2025-09-27 13:27:24 +00:00
dup free
This commit is contained in:
@@ -77,10 +77,10 @@ or use `invoke` method to pass list parameters:
|
|||||||
```
|
```
|
||||||
|
|
||||||
`JSInvokable` returned by evaluation may increase reference of JS object.
|
`JSInvokable` returned by evaluation may increase reference of JS object.
|
||||||
You should manually call `release` to release JS reference:
|
You should manually call `free` to release JS reference:
|
||||||
|
|
||||||
```dart
|
```dart
|
||||||
(func as JSInvokable).release();
|
(func as JSInvokable).free();
|
||||||
```
|
```
|
||||||
|
|
||||||
### Use modules
|
### Use modules
|
||||||
|
@@ -80,7 +80,7 @@ class FlutterQjs {
|
|||||||
case JSChannelType.FREE_OBJECT:
|
case JSChannelType.FREE_OBJECT:
|
||||||
Pointer rt = ctx;
|
Pointer rt = ctx;
|
||||||
_DartObject obj = _DartObject.fromAddress(rt, ptr.address);
|
_DartObject obj = _DartObject.fromAddress(rt, ptr.address);
|
||||||
obj?.release();
|
obj?.free();
|
||||||
return Pointer.fromAddress(0);
|
return Pointer.fromAddress(0);
|
||||||
}
|
}
|
||||||
throw JSError('call channel with wrong type');
|
throw JSError('call channel with wrong type');
|
||||||
|
@@ -11,10 +11,21 @@ import 'dart:isolate';
|
|||||||
import 'package:ffi/ffi.dart';
|
import 'package:ffi/ffi.dart';
|
||||||
|
|
||||||
abstract class JSRef {
|
abstract class JSRef {
|
||||||
bool leakable = false;
|
int _refCount = 0;
|
||||||
void release();
|
void dup() {
|
||||||
|
_refCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
void free() {
|
||||||
|
_refCount--;
|
||||||
|
if (_refCount < 0) destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
void destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
abstract class JSRefLeakable {}
|
||||||
|
|
||||||
class JSEvalFlag {
|
class JSEvalFlag {
|
||||||
static const GLOBAL = 0 << 0;
|
static const GLOBAL = 0 << 0;
|
||||||
static const MODULE = 1 << 0;
|
static const MODULE = 1 << 0;
|
||||||
@@ -170,22 +181,20 @@ void jsFreeRuntime(
|
|||||||
while (true) {
|
while (true) {
|
||||||
final ref = runtimeOpaques[rt]
|
final ref = runtimeOpaques[rt]
|
||||||
?._ref
|
?._ref
|
||||||
?.firstWhere((ref) => ref.leakable, orElse: () => null);
|
?.firstWhere((ref) => ref is JSRefLeakable, orElse: () => null);
|
||||||
if (ref == null) break;
|
if (ref == null) break;
|
||||||
ref.release();
|
ref.destroy();
|
||||||
runtimeOpaques[rt]?._ref?.remove(ref);
|
runtimeOpaques[rt]?._ref?.remove(ref);
|
||||||
}
|
}
|
||||||
while (0 < runtimeOpaques[rt]?._ref?.length ?? 0) {
|
while (0 < runtimeOpaques[rt]?._ref?.length ?? 0) {
|
||||||
final ref = runtimeOpaques[rt]?._ref?.first;
|
final ref = runtimeOpaques[rt]?._ref?.first;
|
||||||
assert(!ref.leakable);
|
|
||||||
referenceleak.add(
|
referenceleak.add(
|
||||||
" ${identityHashCode(ref)}\t${ref.runtimeType.toString()}\t${ref.toString().replaceAll('\n', '\\n')}");
|
" ${identityHashCode(ref)}\t${ref._refCount + 1}\t${ref.runtimeType.toString()}\t${ref.toString().replaceAll('\n', '\\n')}");
|
||||||
ref.release();
|
ref.destroy();
|
||||||
runtimeOpaques[rt]?._ref?.remove(ref);
|
|
||||||
}
|
}
|
||||||
_jsFreeRuntime(rt);
|
_jsFreeRuntime(rt);
|
||||||
if (referenceleak.length > 0) {
|
if (referenceleak.length > 0) {
|
||||||
throw ('reference leak:\n ADDR\t TYPE \t PROP\n' +
|
throw ('reference leak:\n ADDR\tREF\tTYPE\tPROP\n' +
|
||||||
referenceleak.join('\n'));
|
referenceleak.join('\n'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -7,22 +7,24 @@
|
|||||||
*/
|
*/
|
||||||
part of '../flutter_qjs.dart';
|
part of '../flutter_qjs.dart';
|
||||||
|
|
||||||
typedef dynamic _Decode(Map obj, SendPort port);
|
typedef dynamic _Decode(Map obj);
|
||||||
List<_Decode> _decoders = [
|
List<_Decode> _decoders = [
|
||||||
JSError._decode,
|
JSError._decode,
|
||||||
_IsolateJSFunction._decode,
|
IsolateFunction._decode,
|
||||||
_IsolateFunction._decode,
|
|
||||||
_JSFunction._decode,
|
|
||||||
];
|
];
|
||||||
|
|
||||||
abstract class _IsolateEncodable {
|
abstract class _IsolateEncodable {
|
||||||
Map _encode();
|
Map _encode();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final List _sendAllowType = [Null, String, int, double, bool, SendPort];
|
||||||
|
|
||||||
dynamic _encodeData(dynamic data, {Map<dynamic, dynamic> cache}) {
|
dynamic _encodeData(dynamic data, {Map<dynamic, dynamic> cache}) {
|
||||||
|
if (data is Function) return data;
|
||||||
|
if (_sendAllowType.contains(data.runtimeType)) return data;
|
||||||
if (cache == null) cache = Map();
|
if (cache == null) cache = Map();
|
||||||
if (data is _IsolateEncodable) return data._encode();
|
|
||||||
if (cache.containsKey(data)) return cache[data];
|
if (cache.containsKey(data)) return cache[data];
|
||||||
|
if (data is _IsolateEncodable) return data._encode();
|
||||||
if (data is List) {
|
if (data is List) {
|
||||||
final ret = [];
|
final ret = [];
|
||||||
cache[data] = ret;
|
cache[data] = ret;
|
||||||
@@ -57,24 +59,23 @@ dynamic _encodeData(dynamic data, {Map<dynamic, dynamic> cache}) {
|
|||||||
#jsFuturePort: futurePort.sendPort,
|
#jsFuturePort: futurePort.sendPort,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return data;
|
throw JSError('unsupport type: ${data.runtimeType}\n${data.toString()}');
|
||||||
}
|
}
|
||||||
|
|
||||||
dynamic _decodeData(dynamic data, SendPort port,
|
dynamic _decodeData(dynamic data, {Map<dynamic, dynamic> cache}) {
|
||||||
{Map<dynamic, dynamic> cache}) {
|
|
||||||
if (cache == null) cache = Map();
|
if (cache == null) cache = Map();
|
||||||
if (cache.containsKey(data)) return cache[data];
|
if (cache.containsKey(data)) return cache[data];
|
||||||
if (data is List) {
|
if (data is List) {
|
||||||
final ret = [];
|
final ret = [];
|
||||||
cache[data] = ret;
|
cache[data] = ret;
|
||||||
for (int i = 0; i < data.length; ++i) {
|
for (int i = 0; i < data.length; ++i) {
|
||||||
ret.add(_decodeData(data[i], port, cache: cache));
|
ret.add(_decodeData(data[i], cache: cache));
|
||||||
}
|
}
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
if (data is Map) {
|
if (data is Map) {
|
||||||
for (final decoder in _decoders) {
|
for (final decoder in _decoders) {
|
||||||
final decodeObj = decoder(data, port);
|
final decodeObj = decoder(data);
|
||||||
if (decodeObj != null) return decodeObj;
|
if (decodeObj != null) return decodeObj;
|
||||||
}
|
}
|
||||||
if (data.containsKey(#jsFuturePort)) {
|
if (data.containsKey(#jsFuturePort)) {
|
||||||
@@ -86,9 +87,9 @@ dynamic _decodeData(dynamic data, SendPort port,
|
|||||||
futurePort.first.then((value) {
|
futurePort.first.then((value) {
|
||||||
futurePort.close();
|
futurePort.close();
|
||||||
if (value is Map && value.containsKey(#error)) {
|
if (value is Map && value.containsKey(#error)) {
|
||||||
futureCompleter.completeError(_decodeData(value[#error], port));
|
futureCompleter.completeError(_decodeData(value[#error]));
|
||||||
} else {
|
} else {
|
||||||
futureCompleter.complete(_decodeData(value, port));
|
futureCompleter.complete(_decodeData(value));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return futureCompleter.future;
|
return futureCompleter.future;
|
||||||
@@ -96,8 +97,8 @@ dynamic _decodeData(dynamic data, SendPort port,
|
|||||||
final ret = {};
|
final ret = {};
|
||||||
cache[data] = ret;
|
cache[data] = ret;
|
||||||
for (final entry in data.entries) {
|
for (final entry in data.entries) {
|
||||||
ret[_decodeData(entry.key, port, cache: cache)] =
|
ret[_decodeData(entry.key, cache: cache)] =
|
||||||
_decodeData(entry.value, port, cache: cache);
|
_decodeData(entry.value, cache: cache);
|
||||||
}
|
}
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
@@ -147,21 +148,6 @@ void _runJsIsolate(Map spawnMessage) async {
|
|||||||
evalFlags: msg[#flag],
|
evalFlags: msg[#flag],
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
case #call:
|
|
||||||
data = await _JSFunction.fromAddress(
|
|
||||||
Pointer.fromAddress(msg[#ctx]),
|
|
||||||
Pointer.fromAddress(msg[#val]),
|
|
||||||
).invoke(
|
|
||||||
_decodeData(msg[#args], null),
|
|
||||||
_decodeData(msg[#thisVal], null),
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
case #closeFunction:
|
|
||||||
_JSFunction.fromAddress(
|
|
||||||
Pointer.fromAddress(msg[#ctx]),
|
|
||||||
Pointer.fromAddress(msg[#val]),
|
|
||||||
).release();
|
|
||||||
break;
|
|
||||||
case #close:
|
case #close:
|
||||||
data = false;
|
data = false;
|
||||||
qjs.port.close();
|
qjs.port.close();
|
||||||
@@ -225,7 +211,7 @@ class IsolateQjs {
|
|||||||
switch (msg[#type]) {
|
switch (msg[#type]) {
|
||||||
case #hostPromiseRejection:
|
case #hostPromiseRejection:
|
||||||
try {
|
try {
|
||||||
final err = _decodeData(msg[#reason], port.sendPort);
|
final err = _decodeData(msg[#reason]);
|
||||||
if (hostPromiseRejectionHandler != null) {
|
if (hostPromiseRejectionHandler != null) {
|
||||||
hostPromiseRejectionHandler(err);
|
hostPromiseRejectionHandler(err);
|
||||||
} else {
|
} else {
|
||||||
@@ -255,12 +241,6 @@ class IsolateQjs {
|
|||||||
_sendPort = completer.future;
|
_sendPort = completer.future;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create isolate function
|
|
||||||
Future<_IsolateFunction> bind(Function func) async {
|
|
||||||
_ensureEngine();
|
|
||||||
return _IsolateFunction._bind(func, await _sendPort);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Free Runtime and close isolate thread that can be recreate when evaluate again.
|
/// Free Runtime and close isolate thread that can be recreate when evaluate again.
|
||||||
close() {
|
close() {
|
||||||
if (_sendPort == null) return;
|
if (_sendPort == null) return;
|
||||||
@@ -273,8 +253,8 @@ class IsolateQjs {
|
|||||||
final result = await closePort.first;
|
final result = await closePort.first;
|
||||||
closePort.close();
|
closePort.close();
|
||||||
if (result is Map && result.containsKey(#error))
|
if (result is Map && result.containsKey(#error))
|
||||||
throw _decodeData(result[#error], sendPort);
|
throw _decodeData(result[#error]);
|
||||||
return _decodeData(result, sendPort);
|
return _decodeData(result);
|
||||||
});
|
});
|
||||||
_sendPort = null;
|
_sendPort = null;
|
||||||
return ret;
|
return ret;
|
||||||
@@ -295,7 +275,7 @@ class IsolateQjs {
|
|||||||
final result = await evaluatePort.first;
|
final result = await evaluatePort.first;
|
||||||
evaluatePort.close();
|
evaluatePort.close();
|
||||||
if (result is Map && result.containsKey(#error))
|
if (result is Map && result.containsKey(#error))
|
||||||
throw _decodeData(result[#error], sendPort);
|
throw _decodeData(result[#error]);
|
||||||
return _decodeData(result, sendPort);
|
return _decodeData(result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -8,7 +8,7 @@
|
|||||||
part of '../flutter_qjs.dart';
|
part of '../flutter_qjs.dart';
|
||||||
|
|
||||||
/// js invokable
|
/// js invokable
|
||||||
abstract class JSInvokable extends JSReleasable {
|
abstract class JSInvokable extends JSRef {
|
||||||
dynamic invoke(List args, [dynamic thisVal]);
|
dynamic invoke(List args, [dynamic thisVal]);
|
||||||
|
|
||||||
static dynamic _wrap(dynamic func) {
|
static dynamic _wrap(dynamic func) {
|
||||||
@@ -18,26 +18,39 @@ abstract class JSInvokable extends JSReleasable {
|
|||||||
? _DartFunction(func)
|
? _DartFunction(func)
|
||||||
: func;
|
: func;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
noSuchMethod(Invocation invocation) {
|
|
||||||
return invoke(
|
|
||||||
invocation.positionalArguments,
|
|
||||||
invocation.namedArguments[#thisVal],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class _DartFunction extends JSInvokable {
|
class _DartFunction extends JSInvokable {
|
||||||
final Function _func;
|
final Function _func;
|
||||||
_DartFunction(this._func);
|
_DartFunction(this._func);
|
||||||
|
|
||||||
|
void _freeRecursive(dynamic obj, [Set cache]) {
|
||||||
|
if (obj == null) return;
|
||||||
|
if (cache == null) cache = Set();
|
||||||
|
if (cache.contains(obj)) return;
|
||||||
|
if (obj is List) {
|
||||||
|
cache.add(obj);
|
||||||
|
obj.forEach((e) => _freeRecursive(e, cache));
|
||||||
|
}
|
||||||
|
if (obj is Map) {
|
||||||
|
cache.add(obj);
|
||||||
|
obj.values.forEach((e) => _freeRecursive(e, cache));
|
||||||
|
}
|
||||||
|
if (obj is JSRef) {
|
||||||
|
obj.free();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
invoke(List args, [thisVal]) {
|
invoke(List args, [thisVal]) {
|
||||||
/// wrap this into function
|
/// wrap this into function
|
||||||
final passThis =
|
final passThis =
|
||||||
RegExp('{.*thisVal.*}').hasMatch(_func.runtimeType.toString());
|
RegExp('{.*thisVal.*}').hasMatch(_func.runtimeType.toString());
|
||||||
return Function.apply(_func, args, passThis ? {#thisVal: thisVal} : null);
|
final ret =
|
||||||
|
Function.apply(_func, args, passThis ? {#thisVal: thisVal} : null);
|
||||||
|
_freeRecursive(args);
|
||||||
|
_freeRecursive(thisVal);
|
||||||
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -46,21 +59,18 @@ class _DartFunction extends JSInvokable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
release() {}
|
destroy() {}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// implement this to capture js object release.
|
/// implement this to capture js object release.
|
||||||
abstract class JSReleasable {
|
|
||||||
void release();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _DartObject extends JSRef {
|
|
||||||
@override
|
|
||||||
bool leakable = true;
|
|
||||||
|
|
||||||
|
class _DartObject extends JSRef implements JSRefLeakable {
|
||||||
Object _obj;
|
Object _obj;
|
||||||
Pointer _ctx;
|
Pointer _ctx;
|
||||||
_DartObject(this._ctx, this._obj) {
|
_DartObject(this._ctx, this._obj) {
|
||||||
|
if (_obj is JSRef) {
|
||||||
|
(_obj as JSRef).dup();
|
||||||
|
}
|
||||||
runtimeOpaques[jsGetRuntime(_ctx)]?.addRef(this);
|
runtimeOpaques[jsGetRuntime(_ctx)]?.addRef(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,17 +80,17 @@ class _DartObject extends JSRef {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
if (_ctx == null) return "DartObject(<released>)";
|
if (_ctx == null) return "DartObject(released)";
|
||||||
return _obj.toString();
|
return _obj.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void release() {
|
void destroy() {
|
||||||
if (_ctx == null) return;
|
if (_ctx == null) return;
|
||||||
runtimeOpaques[jsGetRuntime(_ctx)]?.removeRef(this);
|
runtimeOpaques[jsGetRuntime(_ctx)]?.removeRef(this);
|
||||||
_ctx = null;
|
_ctx = null;
|
||||||
if (_obj is JSReleasable) {
|
if (_obj is JSRef) {
|
||||||
(_obj as JSReleasable).release();
|
(_obj as JSRef).free();
|
||||||
}
|
}
|
||||||
_obj = null;
|
_obj = null;
|
||||||
}
|
}
|
||||||
@@ -105,7 +115,7 @@ class JSError extends _IsolateEncodable {
|
|||||||
return stack == null ? message.toString() : "$message\n$stack";
|
return stack == null ? message.toString() : "$message\n$stack";
|
||||||
}
|
}
|
||||||
|
|
||||||
static JSError _decode(Map obj, SendPort port) {
|
static JSError _decode(Map obj) {
|
||||||
if (obj.containsKey(#jsError))
|
if (obj.containsKey(#jsError))
|
||||||
return JSError(obj[#jsError], obj[#jsErrorStack]);
|
return JSError(obj[#jsError], obj[#jsErrorStack]);
|
||||||
return null;
|
return null;
|
||||||
@@ -133,16 +143,8 @@ class _JSObject extends JSRef {
|
|||||||
runtimeOpaques[rt]?.addRef(this);
|
runtimeOpaques[rt]?.addRef(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
static _JSObject fromAddress(Pointer ctx, Pointer val) {
|
|
||||||
Pointer rt = jsGetRuntime(ctx);
|
|
||||||
return runtimeOpaques[rt]?.getRef((e) =>
|
|
||||||
e is _JSObject &&
|
|
||||||
e._val.address == val.address &&
|
|
||||||
e._ctx.address == ctx.address);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void release() {
|
void destroy() {
|
||||||
if (_val == null) return;
|
if (_val == null) return;
|
||||||
Pointer rt = jsGetRuntime(_ctx);
|
Pointer rt = jsGetRuntime(_ctx);
|
||||||
runtimeOpaques[rt]?.removeRef(this);
|
runtimeOpaques[rt]?.removeRef(this);
|
||||||
@@ -162,10 +164,6 @@ class _JSObject extends JSRef {
|
|||||||
class _JSFunction extends _JSObject implements JSInvokable, _IsolateEncodable {
|
class _JSFunction extends _JSObject implements JSInvokable, _IsolateEncodable {
|
||||||
_JSFunction(Pointer ctx, Pointer val) : super(ctx, val);
|
_JSFunction(Pointer ctx, Pointer val) : super(ctx, val);
|
||||||
|
|
||||||
static _JSFunction fromAddress(Pointer ctx, Pointer val) {
|
|
||||||
return _JSObject.fromAddress(ctx, val);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
invoke(List<dynamic> arguments, [dynamic thisVal]) {
|
invoke(List<dynamic> arguments, [dynamic thisVal]) {
|
||||||
Pointer jsRet = _invoke(arguments, thisVal);
|
Pointer jsRet = _invoke(arguments, thisVal);
|
||||||
@@ -196,11 +194,124 @@ class _JSFunction extends _JSObject implements JSInvokable, _IsolateEncodable {
|
|||||||
return jsRet;
|
return jsRet;
|
||||||
}
|
}
|
||||||
|
|
||||||
static _JSFunction _decode(Map obj, SendPort port) {
|
@override
|
||||||
if (obj.containsKey(#jsFunction) && port == null)
|
Map _encode() {
|
||||||
return _JSFunction.fromAddress(
|
final func = IsolateFunction._new(this);
|
||||||
Pointer.fromAddress(obj[#jsFunctionCtx]),
|
final ret = func._encode();
|
||||||
Pointer.fromAddress(obj[#jsFunction]),
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class _IsolatePortHandler {
|
||||||
|
int _isolateId;
|
||||||
|
dynamic _handle(dynamic);
|
||||||
|
}
|
||||||
|
|
||||||
|
class _IsolatePort {
|
||||||
|
static ReceivePort _invokeHandler;
|
||||||
|
static Set<_IsolatePortHandler> _handlers = Set();
|
||||||
|
|
||||||
|
static get _port {
|
||||||
|
if (_invokeHandler == null) {
|
||||||
|
_invokeHandler = ReceivePort();
|
||||||
|
_invokeHandler.listen((msg) async {
|
||||||
|
final msgPort = msg[#port];
|
||||||
|
try {
|
||||||
|
final handler = _handlers.firstWhere(
|
||||||
|
(v) => identityHashCode(v) == msg[#handler],
|
||||||
|
orElse: () => null,
|
||||||
|
);
|
||||||
|
if (handler == null) throw JSError('handler released');
|
||||||
|
final ret = _encodeData(await handler._handle(msg[#msg]));
|
||||||
|
if (msgPort != null) msgPort.send(ret);
|
||||||
|
} catch (e) {
|
||||||
|
final err = _encodeData(e);
|
||||||
|
if (msgPort != null)
|
||||||
|
msgPort.send({
|
||||||
|
#error: err,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return _invokeHandler.sendPort;
|
||||||
|
}
|
||||||
|
|
||||||
|
static _send(SendPort isolate, _IsolatePortHandler handler, msg) async {
|
||||||
|
if (isolate == null) return handler._handle(msg);
|
||||||
|
final evaluatePort = ReceivePort();
|
||||||
|
isolate.send({
|
||||||
|
#handler: handler._isolateId,
|
||||||
|
#msg: msg,
|
||||||
|
#port: evaluatePort.sendPort,
|
||||||
|
});
|
||||||
|
final result = await evaluatePort.first;
|
||||||
|
if (result is Map && result.containsKey(#error))
|
||||||
|
throw _decodeData(result[#error]);
|
||||||
|
return _decodeData(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
static _add(_IsolatePortHandler sendport) => _handlers.add(sendport);
|
||||||
|
static _remove(_IsolatePortHandler sendport) => _handlers.remove(sendport);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Dart function wrapper for isolate
|
||||||
|
class IsolateFunction extends JSInvokable
|
||||||
|
implements _IsolateEncodable, _IsolatePortHandler {
|
||||||
|
@override
|
||||||
|
int _isolateId;
|
||||||
|
SendPort _port;
|
||||||
|
JSInvokable _invokable;
|
||||||
|
IsolateFunction._fromId(this._isolateId, this._port);
|
||||||
|
|
||||||
|
IsolateFunction._new(this._invokable) {
|
||||||
|
_IsolatePort._add(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
static IsolateFunction func(Function func) {
|
||||||
|
return IsolateFunction._new(_DartFunction(func));
|
||||||
|
}
|
||||||
|
|
||||||
|
_destroy() {
|
||||||
|
_IsolatePort._remove(this);
|
||||||
|
_invokable?.free();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
_handle(msg) async {
|
||||||
|
switch (msg) {
|
||||||
|
case #dup:
|
||||||
|
_refCount++;
|
||||||
|
return null;
|
||||||
|
case #free:
|
||||||
|
_refCount--;
|
||||||
|
print("${identityHashCode(this)} ref $_refCount");
|
||||||
|
if (_refCount < 0) _destroy();
|
||||||
|
return null;
|
||||||
|
case #destroy:
|
||||||
|
_destroy();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
List args = _decodeData(msg[#args]);
|
||||||
|
Map thisVal = _decodeData(msg[#thisVal]);
|
||||||
|
return _invokable.invoke(args, thisVal);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future invoke(List positionalArguments, [thisVal]) async {
|
||||||
|
List dArgs = _encodeData(positionalArguments);
|
||||||
|
Map dThisVal = _encodeData(thisVal);
|
||||||
|
return _IsolatePort._send(_port, this, {
|
||||||
|
#type: #invokeIsolate,
|
||||||
|
#args: dArgs,
|
||||||
|
#thisVal: dThisVal,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static IsolateFunction _decode(Map obj) {
|
||||||
|
if (obj.containsKey(#jsFunctionPort))
|
||||||
|
return IsolateFunction._fromId(
|
||||||
|
obj[#jsFunctionId],
|
||||||
|
obj[#jsFunctionPort],
|
||||||
);
|
);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -208,135 +319,25 @@ class _JSFunction extends _JSObject implements JSInvokable, _IsolateEncodable {
|
|||||||
@override
|
@override
|
||||||
Map _encode() {
|
Map _encode() {
|
||||||
return {
|
return {
|
||||||
#jsFunction: _val.address,
|
#jsFunctionId: _isolateId ?? identityHashCode(this),
|
||||||
#jsFunctionCtx: _ctx.address,
|
#jsFunctionPort: _port ?? _IsolatePort._port,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int _refCount = 0;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
noSuchMethod(Invocation invocation) {
|
dup() {
|
||||||
return invoke(
|
_IsolatePort._send(_port, this, #dup);
|
||||||
invocation.positionalArguments,
|
}
|
||||||
invocation.namedArguments[#thisVal],
|
|
||||||
);
|
@override
|
||||||
}
|
free() {
|
||||||
}
|
_IsolatePort._send(_port, this, #free);
|
||||||
|
}
|
||||||
/// JS function wrapper for isolate
|
|
||||||
class _IsolateJSFunction extends JSInvokable implements _IsolateEncodable {
|
@override
|
||||||
int _val;
|
void destroy() {
|
||||||
int _ctx;
|
_IsolatePort._send(_port, this, #destroy);
|
||||||
SendPort _port;
|
|
||||||
_IsolateJSFunction(this._ctx, this._val, this._port);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future invoke(List arguments, [thisVal]) async {
|
|
||||||
if (0 == _val ?? 0) return;
|
|
||||||
final evaluatePort = ReceivePort();
|
|
||||||
_port.send({
|
|
||||||
#type: #call,
|
|
||||||
#ctx: _ctx,
|
|
||||||
#val: _val,
|
|
||||||
#args: _encodeData(arguments),
|
|
||||||
#thisVal: _encodeData(thisVal),
|
|
||||||
#port: evaluatePort.sendPort,
|
|
||||||
});
|
|
||||||
final result = await evaluatePort.first;
|
|
||||||
evaluatePort.close();
|
|
||||||
if (result is Map && result.containsKey(#error))
|
|
||||||
throw _decodeData(result[#error], _port);
|
|
||||||
return _decodeData(result, _port);
|
|
||||||
}
|
|
||||||
|
|
||||||
static _IsolateJSFunction _decode(Map obj, SendPort port) {
|
|
||||||
if (obj.containsKey(#jsFunction) && port != null)
|
|
||||||
return _IsolateJSFunction(obj[#jsFunctionCtx], obj[#jsFunction], port);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Map _encode() {
|
|
||||||
return {
|
|
||||||
#jsFunction: _val,
|
|
||||||
#jsFunctionCtx: _ctx,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void release() {
|
|
||||||
if (_port == null) return;
|
|
||||||
_port.send({
|
|
||||||
#type: #closeFunction,
|
|
||||||
#ctx: _ctx,
|
|
||||||
#val: _val,
|
|
||||||
});
|
|
||||||
_port = null;
|
|
||||||
_val = null;
|
|
||||||
_ctx = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Dart function wrapper for isolate
|
|
||||||
class _IsolateFunction extends JSInvokable
|
|
||||||
implements JSReleasable, _IsolateEncodable {
|
|
||||||
SendPort _port;
|
|
||||||
SendPort _func;
|
|
||||||
_IsolateFunction(this._func, this._port);
|
|
||||||
|
|
||||||
static _IsolateFunction _bind(Function func, SendPort port) {
|
|
||||||
final JSInvokable invokable = JSInvokable._wrap(func);
|
|
||||||
final funcPort = ReceivePort();
|
|
||||||
funcPort.listen((msg) async {
|
|
||||||
if (msg == #close) return funcPort.close();
|
|
||||||
SendPort msgPort = msg[#port];
|
|
||||||
try {
|
|
||||||
List args = _decodeData(msg[#args], port);
|
|
||||||
Map thisVal = _decodeData(msg[#thisVal], port);
|
|
||||||
final data = await invokable.invoke(args, thisVal);
|
|
||||||
if (msgPort != null) msgPort.send(_encodeData(data));
|
|
||||||
} catch (e) {
|
|
||||||
if (msgPort != null)
|
|
||||||
msgPort.send({
|
|
||||||
#error: _encodeData(e),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return _IsolateFunction(funcPort.sendPort, port);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future invoke(List positionalArguments, [thisVal]) async {
|
|
||||||
if (_func == null) return;
|
|
||||||
final evaluatePort = ReceivePort();
|
|
||||||
_func.send({
|
|
||||||
#args: _encodeData(positionalArguments),
|
|
||||||
#thisVal: _encodeData(thisVal),
|
|
||||||
#port: evaluatePort.sendPort,
|
|
||||||
});
|
|
||||||
final result = await evaluatePort.first;
|
|
||||||
evaluatePort.close();
|
|
||||||
if (result is Map && result.containsKey(#error))
|
|
||||||
throw _decodeData(result[#error], _port);
|
|
||||||
return _decodeData(result, _port);
|
|
||||||
}
|
|
||||||
|
|
||||||
static _IsolateFunction _decode(Map obj, SendPort port) {
|
|
||||||
if (obj.containsKey(#jsFunctionPort))
|
|
||||||
return _IsolateFunction(obj[#jsFunctionPort], port);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Map _encode() {
|
|
||||||
return {
|
|
||||||
#jsFunctionPort: _func,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void release() {
|
|
||||||
if (_func == null) return;
|
|
||||||
_func.send(#close);
|
|
||||||
_func = null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -68,18 +68,22 @@ Pointer _dartToJs(Pointer ctx, dynamic val, {Map<dynamic, dynamic> cache}) {
|
|||||||
final resolvingFunc2 =
|
final resolvingFunc2 =
|
||||||
Pointer.fromAddress(resolvingFunc.address + sizeOfJSValue);
|
Pointer.fromAddress(resolvingFunc.address + sizeOfJSValue);
|
||||||
final ret = jsNewPromiseCapability(ctx, resolvingFunc);
|
final ret = jsNewPromiseCapability(ctx, resolvingFunc);
|
||||||
_JSFunction res = _jsToDart(ctx, resolvingFunc)..leakable = true;
|
_JSFunction res = _jsToDart(ctx, resolvingFunc);
|
||||||
_JSFunction rej = _jsToDart(ctx, resolvingFunc2)..leakable = true;
|
_JSFunction rej = _jsToDart(ctx, resolvingFunc2);
|
||||||
jsFreeValue(ctx, resolvingFunc, free: false);
|
jsFreeValue(ctx, resolvingFunc, free: false);
|
||||||
jsFreeValue(ctx, resolvingFunc2, free: false);
|
jsFreeValue(ctx, resolvingFunc2, free: false);
|
||||||
free(resolvingFunc);
|
free(resolvingFunc);
|
||||||
|
_DartObject refRes = _DartObject(ctx, res);
|
||||||
|
_DartObject refRej = _DartObject(ctx, rej);
|
||||||
|
res.free();
|
||||||
|
rej.free();
|
||||||
val.then((value) {
|
val.then((value) {
|
||||||
res.invoke([value]);
|
res.invoke([value]);
|
||||||
}, onError: (e) {
|
}, onError: (e) {
|
||||||
rej.invoke([e]);
|
rej.invoke([e]);
|
||||||
}).whenComplete(() {
|
}).whenComplete(() {
|
||||||
res.release();
|
refRes.free();
|
||||||
rej.release();
|
refRej.free();
|
||||||
});
|
});
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
@@ -189,8 +193,8 @@ dynamic _jsToDart(Pointer ctx, Pointer val, {Map<int, dynamic> cache}) {
|
|||||||
if (!completer.isCompleted) completer.completeError(e);
|
if (!completer.isCompleted) completer.completeError(e);
|
||||||
},
|
},
|
||||||
], jsPromise);
|
], jsPromise);
|
||||||
jsPromise.release();
|
jsPromise.free();
|
||||||
promiseThen.release();
|
promiseThen.free();
|
||||||
bool isException = jsIsException(jsRet) != 0;
|
bool isException = jsIsException(jsRet) != 0;
|
||||||
jsFreeValue(ctx, jsRet);
|
jsFreeValue(ctx, jsRet);
|
||||||
if (isException) throw _parseJSException(ctx);
|
if (isException) throw _parseJSException(ctx);
|
||||||
|
@@ -8,6 +8,7 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
import 'dart:isolate';
|
||||||
|
|
||||||
import 'package:flutter_qjs/flutter_qjs.dart';
|
import 'package:flutter_qjs/flutter_qjs.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
@@ -17,48 +18,42 @@ dynamic myFunction(String args, {thisVal}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future testEvaluate(qjs) async {
|
Future testEvaluate(qjs) async {
|
||||||
final testWrap = await qjs.evaluate(
|
JSInvokable wrapFunction = await qjs.evaluate(
|
||||||
'(a) => a',
|
'(a) => a',
|
||||||
name: '<testWrap>',
|
name: '<testWrap>',
|
||||||
);
|
);
|
||||||
final wrapNull = await testWrap(null);
|
dynamic testWrap = await wrapFunction.invoke([wrapFunction]);
|
||||||
|
final wrapNull = await testWrap.invoke([null]);
|
||||||
expect(wrapNull, null, reason: 'wrap null');
|
expect(wrapNull, null, reason: 'wrap null');
|
||||||
final primities = [0, 1, 0.1, true, false, 'str'];
|
final primities = [0, 1, 0.1, true, false, 'str'];
|
||||||
final wrapPrimities = await testWrap(primities);
|
final wrapPrimities = await testWrap.invoke([primities]);
|
||||||
for (int i = 0; i < primities.length; i++) {
|
for (int i = 0; i < primities.length; i++) {
|
||||||
expect(wrapPrimities[i], primities[i], reason: 'wrap primities');
|
expect(wrapPrimities[i], primities[i], reason: 'wrap primities');
|
||||||
}
|
}
|
||||||
final jsError = JSError('test Error');
|
final jsError = JSError('test Error');
|
||||||
final wrapJsError = await testWrap(jsError);
|
final wrapJsError = await testWrap.invoke([jsError]);
|
||||||
expect(jsError.message, (wrapJsError as JSError).message,
|
expect(jsError.message, (wrapJsError as JSError).message,
|
||||||
reason: 'wrap JSError');
|
reason: 'wrap JSError');
|
||||||
final wrapFunction = await testWrap(testWrap);
|
|
||||||
final testEqual = await qjs.evaluate(
|
|
||||||
'(a, b) => a === b',
|
|
||||||
name: '<testEqual>',
|
|
||||||
);
|
|
||||||
expect(await testEqual(wrapFunction, testWrap), true,
|
|
||||||
reason: 'wrap function');
|
|
||||||
wrapFunction.release();
|
|
||||||
testEqual.release();
|
|
||||||
|
|
||||||
expect(wrapNull, null, reason: 'wrap null');
|
expect(wrapNull, null, reason: 'wrap null');
|
||||||
final a = {};
|
final a = {};
|
||||||
a['a'] = a;
|
a['a'] = a;
|
||||||
final wrapA = await testWrap(a);
|
final wrapA = await testWrap.invoke([a]);
|
||||||
expect(wrapA['a'], wrapA, reason: 'recursive object');
|
expect(wrapA['a'], wrapA, reason: 'recursive object');
|
||||||
final testThis = await qjs.evaluate(
|
JSInvokable testThis = await qjs.evaluate(
|
||||||
'(function (func, arg) { return func.call(this, arg) })',
|
'(function (func, arg) { return func.call(this, arg) })',
|
||||||
name: '<testThis>',
|
name: '<testThis>',
|
||||||
);
|
);
|
||||||
final funcRet = await testThis(myFunction, 'arg', thisVal: {'name': 'this'});
|
final funcRet = await testThis.invoke([myFunction, 'arg'], {'name': 'this'});
|
||||||
testThis.release();
|
testThis.free();
|
||||||
expect(funcRet[0]['name'], 'this', reason: 'js function this');
|
expect(funcRet[0]['name'], 'this', reason: 'js function this');
|
||||||
expect(funcRet[1], 'arg', reason: 'js function argument');
|
expect(funcRet[1], 'arg', reason: 'js function argument');
|
||||||
final promises = await testWrap(await qjs.evaluate(
|
List promises = await testWrap.invoke([
|
||||||
'[Promise.reject("reject"), Promise.resolve("resolve"), new Promise(() => {})]',
|
await qjs.evaluate(
|
||||||
name: '<promises>',
|
'[Promise.reject("reject"), Promise.resolve("resolve"), new Promise(() => {})]',
|
||||||
));
|
name: '<promises>',
|
||||||
|
)
|
||||||
|
]);
|
||||||
for (final promise in promises)
|
for (final promise in promises)
|
||||||
expect(promise, isInstanceOf<Future>(), reason: 'promise object');
|
expect(promise, isInstanceOf<Future>(), reason: 'promise object');
|
||||||
try {
|
try {
|
||||||
@@ -68,10 +63,16 @@ Future testEvaluate(qjs) async {
|
|||||||
expect(e, 'reject', reason: 'promise object reject');
|
expect(e, 'reject', reason: 'promise object reject');
|
||||||
}
|
}
|
||||||
expect(await promises[1], 'resolve', reason: 'promise object resolve');
|
expect(await promises[1], 'resolve', reason: 'promise object resolve');
|
||||||
testWrap.release();
|
testWrap.free();
|
||||||
|
wrapFunction.free();
|
||||||
}
|
}
|
||||||
|
|
||||||
void main() async {
|
void main() async {
|
||||||
|
test('send', () async {
|
||||||
|
final rec = ReceivePort();
|
||||||
|
rec.close();
|
||||||
|
rec.sendPort.send("3232");
|
||||||
|
});
|
||||||
test('make', () async {
|
test('make', () async {
|
||||||
final utf8Encoding = Encoding.getByName('utf-8');
|
final utf8Encoding = Encoding.getByName('utf-8');
|
||||||
var cmakePath = 'cmake';
|
var cmakePath = 'cmake';
|
||||||
@@ -140,15 +141,23 @@ void main() async {
|
|||||||
});
|
});
|
||||||
test('isolate bind function', () async {
|
test('isolate bind function', () async {
|
||||||
final qjs = IsolateQjs();
|
final qjs = IsolateQjs();
|
||||||
var localVar;
|
final localVars = [];
|
||||||
final testFunc = await qjs.evaluate('(func)=>func("ret")', name: '<eval>');
|
JSInvokable testFunc =
|
||||||
final testFuncRet = await testFunc(await qjs.bind((args) {
|
await qjs.evaluate('(func)=>func(()=>"ret")', name: '<eval>');
|
||||||
localVar = 'test';
|
final func = IsolateFunction.func((args) {
|
||||||
return args;
|
localVars.add(args..dup());
|
||||||
}));
|
return args.invoke([]);
|
||||||
testFunc.release();
|
});
|
||||||
expect(localVar, 'test', reason: 'bind function');
|
final testFuncRet = await testFunc.invoke([func..dup()]);
|
||||||
|
final testFuncRet2 = await testFunc.invoke([func..dup()]);
|
||||||
|
func.free();
|
||||||
|
testFunc.free();
|
||||||
|
for (IsolateFunction vars in localVars) {
|
||||||
|
expect(await vars.invoke([]), 'ret', reason: 'bind function');
|
||||||
|
vars.free();
|
||||||
|
}
|
||||||
expect(testFuncRet, 'ret', reason: 'bind function args return');
|
expect(testFuncRet, 'ret', reason: 'bind function args return');
|
||||||
|
expect(testFuncRet2, testFuncRet, reason: 'bind function args return2');
|
||||||
await qjs.close();
|
await qjs.close();
|
||||||
});
|
});
|
||||||
test('reference leak', () async {
|
test('reference leak', () async {
|
||||||
|
Reference in New Issue
Block a user