This commit is contained in:
ekibun
2021-01-02 00:57:52 +08:00
parent d44af3d093
commit 045277dbe3
13 changed files with 278 additions and 185 deletions

View File

@@ -16,7 +16,7 @@ abstract class JSRef {
}
/// JS_Eval() flags
class JSEvalType {
class JSEvalFlag {
static const GLOBAL = 0 << 0;
static const MODULE = 1 << 0;
}
@@ -110,9 +110,9 @@ typedef JSChannel = Pointer Function(Pointer ctx, Pointer method, Pointer argv);
class RuntimeOpaque {
JSChannel channel;
List<JSRef> ref = List();
List<JSRef> ref = [];
ReceivePort port;
Future Function(Pointer) promsieToFuture;
Future Function(Pointer) promiseToFuture;
}
final Map<Pointer, RuntimeOpaque> runtimeOpaques = Map();
@@ -356,32 +356,52 @@ final Pointer Function(
)>>("jsNewObject")
.asFunction();
/// void jsFreeValue(JSContext *ctx, JSValue *val)
/// void jsFreeValue(JSContext *ctx, JSValue *val, int32_t free)
final void Function(
Pointer ctx,
Pointer val,
) jsFreeValue = qjsLib
int free,
) _jsFreeValue = qjsLib
.lookup<
NativeFunction<
Void Function(
Pointer,
Pointer,
Int32,
)>>("jsFreeValue")
.asFunction();
/// void jsFreeValueRT(JSRuntime *rt, JSValue *v)
void jsFreeValue(
Pointer ctx,
Pointer val, {
bool free = true,
}) {
_jsFreeValue(ctx, val, free ? 1 : 0);
}
/// void jsFreeValue(JSRuntime *rt, JSValue *val, int32_t free)
final void Function(
Pointer rt,
Pointer val,
) jsFreeValueRT = qjsLib
int free,
) _jsFreeValueRT = qjsLib
.lookup<
NativeFunction<
Void Function(
Pointer,
Pointer,
Int32,
)>>("jsFreeValueRT")
.asFunction();
void jsFreeValueRT(
Pointer rt,
Pointer val, {
bool free = true,
}) {
_jsFreeValueRT(rt, val, free ? 1 : 0);
}
/// JSValue *jsDupValue(JSContext *ctx, JSValueConst *v)
final Pointer Function(
Pointer ctx,
@@ -512,6 +532,19 @@ final int Function(
)>>("jsIsFunction")
.asFunction();
/// int32_t jsIsPromise(JSContext *ctx, JSValueConst *val)
final int Function(
Pointer ctx,
Pointer val,
) jsIsPromise = qjsLib
.lookup<
NativeFunction<
Int32 Function(
Pointer,
Pointer,
)>>("jsIsPromise")
.asFunction();
/// int32_t jsIsArray(JSContext *ctx, JSValueConst *val)
final int Function(
Pointer ctx,

View File

@@ -23,9 +23,15 @@ class FlutterQjs {
Pointer _rt;
Pointer _ctx;
ReceivePort port = ReceivePort();
/// Set a handler to manage js call with `channel(method, args)` function.
JsMethodHandler methodHandler;
/// Set a handler to manage js module.
JsModuleHandler moduleHandler;
FlutterQjs({this.methodHandler, this.moduleHandler});
_ensureEngine() {
if (_rt != null) return;
_rt = jsNewRuntime((ctx, method, argv) {
@@ -61,18 +67,8 @@ class FlutterQjs {
_ctx = jsNewContextWithPromsieWrapper(_rt);
}
/// Set a handler to manage js call with `channel(method, args)` function.
setMethodHandler(JsMethodHandler handler) {
methodHandler = handler;
}
/// Set a handler to manage js module.
setModuleHandler(JsModuleHandler handler) {
moduleHandler = handler;
}
/// Free Runtime and Context which can be recreate when evaluate again.
recreate() {
close() {
if (_rt != null) {
jsFreeContext(_ctx);
jsFreeRuntime(_rt);
@@ -81,18 +77,10 @@ class FlutterQjs {
_ctx = null;
}
/// Close ReceivePort.
close() {
if (port != null) {
port.close();
recreate();
}
port = null;
}
/// DispatchMessage
Future<void> dispatch() async {
await for (var _ in port) {
if (_rt == null) continue;
while (true) {
int err = jsExecutePendingJob(_rt);
if (err <= 0) {
@@ -116,24 +104,14 @@ class FlutterQjs {
}
/// Evaluate js script.
Future<dynamic> evaluate(String command, {String name, int evalFlags}) async {
dynamic evaluate(String command, {String name, int evalFlags}) {
_ensureEngine();
var jsval =
jsEval(_ctx, command, name ?? "<eval>", evalFlags ?? JSEvalType.GLOBAL);
if (jsIsException(jsval) != 0) {
jsFreeValue(_ctx, jsval);
throw Exception(parseJSException(_ctx));
}
var ret = runtimeOpaques[_rt]?.promsieToFuture(jsval);
jsFreeValue(_ctx, jsval);
return ret;
}
/// Evaluate js script (Sync).
dynamic evaluateSync(String command, {String name, int evalFlags}) {
_ensureEngine();
var jsval =
jsEval(_ctx, command, name ?? "<eval>", evalFlags ?? JSEvalType.GLOBAL);
var jsval = jsEval(
_ctx,
command,
name ?? "<eval>",
evalFlags ?? JSEvalFlag.GLOBAL,
);
if (jsIsException(jsval) != 0) {
jsFreeValue(_ctx, jsval);
throw Exception(parseJSException(_ctx));

View File

@@ -76,8 +76,20 @@ dynamic _encodeData(dynamic data, {Map<dynamic, dynamic> cache}) {
};
}
if (data is Future) {
// Not support
return {};
var futurePort = ReceivePort();
data.then((value) {
futurePort.first.then((port) => {
(port as SendPort).send({'data': _encodeData(value)})
});
}, onError: (e, stack) {
futurePort.first.then((port) => {
(port as SendPort)
.send({'error': e.toString() + "\n" + stack.toString()})
});
});
return {
'__js_future_port': futurePort.sendPort,
};
}
return data;
}
@@ -104,6 +116,20 @@ dynamic _decodeData(dynamic data, SendPort port,
return JSFunction.fromAddress(ctx, val);
}
}
if (data.containsKey('__js_future_port')) {
SendPort port = data['__js_future_port'];
var futurePort = ReceivePort();
port.send(futurePort.sendPort);
var futureCompleter = Completer();
futurePort.first.then((value) {
if (value['error'] != null) {
futureCompleter.completeError(value['error']);
} else {
futureCompleter.complete(value['data']);
}
});
return futureCompleter.future;
}
var ret = {};
cache[data] = ret;
for (var entry in data.entries) {
@@ -116,30 +142,31 @@ dynamic _decodeData(dynamic data, SendPort port,
}
void _runJsIsolate(Map spawnMessage) async {
var qjs = FlutterQjs();
SendPort sendPort = spawnMessage['port'];
JsMethodHandler methodHandler = spawnMessage['handler'];
ReceivePort port = ReceivePort();
sendPort.send(port.sendPort);
qjs.setMethodHandler(methodHandler);
qjs.setModuleHandler((name) {
var ptr = allocate<Pointer<Utf8>>();
ptr.value = Pointer.fromAddress(0);
sendPort.send({
'type': 'module',
'name': name,
'ptr': ptr.address,
});
while (ptr.value.address == 0) sleep(Duration.zero);
if (ptr.value.address == -1) throw Exception("Module Not found");
var ret = Utf8.fromUtf8(ptr.value);
sendPort.send({
'type': 'release',
'ptr': ptr.value.address,
});
free(ptr);
return ret;
});
var qjs = FlutterQjs(
methodHandler: methodHandler,
moduleHandler: (name) {
var ptr = allocate<Pointer<Utf8>>();
ptr.value = Pointer.fromAddress(0);
sendPort.send({
'type': 'module',
'name': name,
'ptr': ptr.address,
});
while (ptr.value.address == 0) sleep(Duration.zero);
if (ptr.value.address == -1) throw Exception("Module Not found");
var ret = Utf8.fromUtf8(ptr.value);
sendPort.send({
'type': 'release',
'ptr': ptr.value.address,
});
free(ptr);
return ret;
},
);
qjs.dispatch();
await for (var msg in port) {
var data;
@@ -160,6 +187,7 @@ void _runJsIsolate(Map spawnMessage) async {
).invoke(_decodeData(msg['args'], null));
break;
case 'close':
qjs.port.close();
qjs.close();
port.close();
break;
@@ -182,12 +210,15 @@ typedef JsIsolateSpawn = void Function(SendPort sendPort);
class IsolateQjs {
Future<SendPort> _sendPort;
JsMethodHandler _methodHandler;
JsAsyncModuleHandler _moduleHandler;
/// Set a handler to manage js call with `channel(method, args)` function.
/// The function must be a top-level function or a static method
IsolateQjs(this._methodHandler);
JsMethodHandler methodHandler;
/// Set a handler to manage js module.
JsAsyncModuleHandler moduleHandler;
IsolateQjs({this.methodHandler, this.moduleHandler});
_ensureEngine() {
if (_sendPort != null) return;
@@ -196,7 +227,7 @@ class IsolateQjs {
_runJsIsolate,
{
'port': port.sendPort,
'handler': _methodHandler,
'handler': methodHandler,
},
errorsAreFatal: true,
);
@@ -210,7 +241,7 @@ class IsolateQjs {
case 'module':
var ptr = Pointer<Pointer>.fromAddress(msg['ptr']);
try {
ptr.value = Utf8.toUtf8(await _moduleHandler(msg['name']));
ptr.value = Utf8.toUtf8(await moduleHandler(msg['name']));
} catch (e) {
ptr.value = Pointer.fromAddress(-1);
}
@@ -226,11 +257,6 @@ class IsolateQjs {
_sendPort = completer.future;
}
/// Set a handler to manage js module.
setModuleHandler(JsAsyncModuleHandler handler) {
_moduleHandler = handler;
}
close() {
if (_sendPort == null) return;
_sendPort.then((sendPort) {
@@ -253,8 +279,9 @@ class IsolateQjs {
'port': evaluatePort.sendPort,
});
var result = await evaluatePort.first;
if (result['error'] == null)
if (result['error'] == null){
return _decodeData(result['data'], sendPort);
}
else
throw result['error'];
}

View File

@@ -130,8 +130,8 @@ Pointer dartToJs(Pointer ctx, dynamic val, {Map<dynamic, dynamic> cache}) {
var ret = jsNewPromiseCapability(ctx, resolvingFunc);
var res = jsToDart(ctx, resolvingFunc);
var rej = jsToDart(ctx, resolvingFunc2);
jsFreeValue(ctx, resolvingFunc);
jsFreeValue(ctx, resolvingFunc2);
jsFreeValue(ctx, resolvingFunc, free: false);
jsFreeValue(ctx, resolvingFunc2, free: false);
free(resolvingFunc);
val.then((value) {
res(value);
@@ -225,10 +225,12 @@ dynamic jsToDart(Pointer ctx, Pointer val, {Map<int, dynamic> cache}) {
}
if (jsIsFunction(ctx, val) != 0) {
return JSFunction(ctx, val);
} else if (jsIsPromise(ctx, val) != 0) {
return runtimeOpaques[jsGetRuntime(ctx)]?.promiseToFuture(val);
} else if (jsIsArray(ctx, val) != 0) {
Pointer jslength = jsGetPropertyStr(ctx, val, "length");
int length = jsToInt64(ctx, jslength);
List<dynamic> ret = List();
List<dynamic> ret = [];
cache[valptr] = ret;
for (int i = 0; i < length; ++i) {
var jsAtomVal = jsNewInt64(ctx, i);
@@ -274,7 +276,8 @@ Pointer jsNewContextWithPromsieWrapper(Pointer rt) {
ctx,
"""
(value) => {
const __ret = Promise.resolve(value)
const __ret = {};
Promise.resolve(value)
.then(v => {
__ret.__value = v;
__ret.__resolved = true;
@@ -286,10 +289,10 @@ Pointer jsNewContextWithPromsieWrapper(Pointer rt) {
}
""",
"<future>",
JSEvalType.GLOBAL);
JSEvalFlag.GLOBAL);
var promiseWrapper = JSRefValue(ctx, jsPromiseWrapper);
jsFreeValue(ctx, jsPromiseWrapper);
runtimeOpaques[rt].promsieToFuture = (promise) {
runtimeOpaques[rt].promiseToFuture = (promise) {
var completer = Completer();
var wrapper = promiseWrapper.val;
if (wrapper == null)