mirror of
https://github.com/wgh136/flutter_qjs.git
synced 2025-09-27 05:27:23 +00:00
0.2.0
This commit is contained in:
47
lib/ffi.dart
47
lib/ffi.dart
@@ -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,
|
||||
|
@@ -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));
|
||||
|
@@ -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'];
|
||||
}
|
||||
|
@@ -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)
|
||||
|
Reference in New Issue
Block a user