From d51290b2ea271a29ab2cba5e24fb1ae097c905bc Mon Sep 17 00:00:00 2001 From: ekibun Date: Mon, 18 Jan 2021 13:25:16 +0800 Subject: [PATCH] remove dart object when jsfree. --- CHANGELOG.md | 4 +++ cxx/ffi.cpp | 46 +++++++++++++++++++++++++++++++++- cxx/ffi.h | 11 +++++++++ cxx/quickjs | 2 +- example/pubspec.lock | 2 +- lib/ffi.dart | 59 ++++++++++++++++++++++++++++++++++++++++++-- lib/flutter_qjs.dart | 12 +++++++-- lib/wrapper.dart | 56 ++++++++++++++++------------------------- pubspec.yaml | 2 +- 9 files changed, 151 insertions(+), 43 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 142246e..74ba117 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ * @LastEditTime: 2020-12-02 11:36:40 --> +## 0.2.5 + +* remove dart object when jsfree. + ## 0.2.4 * wrap dart object to js. diff --git a/cxx/ffi.cpp b/cxx/ffi.cpp index c9843de..227a61b 100644 --- a/cxx/ffi.cpp +++ b/cxx/ffi.cpp @@ -39,7 +39,7 @@ extern "C" { JSRuntime *rt = JS_GetRuntime(ctx); JSChannel *channel = (JSChannel *)JS_GetRuntimeOpaque(rt); - const char *str = (char *)channel(ctx, (char *)0, (void *)module_name); + const char *str = (char *)channel(ctx, module_name, nullptr); if (str == 0) return NULL; JSValue func_val = JS_Eval(ctx, str, strlen(str), module_name, JS_EVAL_TYPE_MODULE | JS_EVAL_FLAG_COMPILE_ONLY); @@ -71,6 +71,50 @@ extern "C" return rt; } + DLLEXPORT uint32_t jsNewClass(JSContext *ctx, const char *name) + { + JSClassID QJSClassId = 0; + JS_NewClassID(&QJSClassId); + JSRuntime *rt = JS_GetRuntime(ctx); + if (!JS_IsRegisteredClass(rt, QJSClassId)) + { + JSClassDef def{ + name, + // destructor + [](JSRuntime *rt, JSValue obj) noexcept { + JSClassID classid = JS_GetClassID(obj); + ObjectOpaque *opaque = (ObjectOpaque *)JS_GetOpaque(obj, classid); + JSChannel *channel = (JSChannel *)JS_GetRuntimeOpaque(rt); + channel((JSContext *)rt, nullptr, (void *)opaque->opaque); + delete opaque; + }}; + int e = JS_NewClass(rt, QJSClassId, &def); + if (e < 0) + { + JS_ThrowInternalError(ctx, "Cant register class %s", name); + return 0; + } + } + return QJSClassId; + } + + DLLEXPORT void *jsGetObjectOpaque(JSValue *obj, uint32_t classid) + { + ObjectOpaque *opaque = (ObjectOpaque *)JS_GetOpaque(*obj, classid); + if(opaque == nullptr) return nullptr; + return opaque->opaque; + } + + DLLEXPORT JSValue *jsNewObjectClass(JSContext *ctx, uint32_t QJSClassId, void *opaque) + { + auto jsobj = new JSValue(JS_NewObjectClass(ctx, QJSClassId)); + if (JS_IsException(*jsobj)) + return jsobj; + ObjectOpaque *objOpaque = new ObjectOpaque{ctx, opaque}; + JS_SetOpaque(*jsobj, objOpaque); + return jsobj; + } + DLLEXPORT void jsSetMaxStackSize(JSRuntime *rt, size_t stack_size) { JS_SetMaxStackSize(rt, stack_size); diff --git a/cxx/ffi.h b/cxx/ffi.h index 399cc97..e732b15 100644 --- a/cxx/ffi.h +++ b/cxx/ffi.h @@ -8,6 +8,11 @@ extern "C" { + struct ObjectOpaque + { + JSContext *ctx; + void *opaque; + }; typedef void *JSChannel(JSContext *ctx, const char *method, void *argv); @@ -21,6 +26,12 @@ extern "C" DLLEXPORT JSRuntime *jsNewRuntime(JSChannel channel); + DLLEXPORT uint32_t jsNewClass(JSContext *ctx, const char *name); + + DLLEXPORT void *jsGetObjectOpaque(JSValue *obj, uint32_t classid); + + DLLEXPORT JSValue *jsNewObjectClass(JSContext *ctx, uint32_t QJSClassId, void *opaque); + DLLEXPORT void jsSetMaxStackSize(JSRuntime *rt, size_t stack_size); DLLEXPORT void jsFreeRuntime(JSRuntime *rt); diff --git a/cxx/quickjs b/cxx/quickjs index 9ac8741..7daa190 160000 --- a/cxx/quickjs +++ b/cxx/quickjs @@ -1 +1 @@ -Subproject commit 9ac874168b360f760459d3af51d33946279cce9b +Subproject commit 7daa190ca7930bab95b1b778c515ff05b1584ab8 diff --git a/example/pubspec.lock b/example/pubspec.lock index 6752578..d9ad585 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -82,7 +82,7 @@ packages: path: ".." relative: true source: path - version: "0.2.4" + version: "0.2.5" flutter_test: dependency: "direct dev" description: flutter diff --git a/lib/ffi.dart b/lib/ffi.dart index bedb673..c0db83d 100644 --- a/lib/ffi.dart +++ b/lib/ffi.dart @@ -113,13 +113,14 @@ class RuntimeOpaque { List ref = []; ReceivePort port; Future Function(Pointer) promiseToFuture; - Pointer Function(Object) objectWrapper; + int dartObjectClassId; } final Map runtimeOpaques = Map(); Pointer channelDispacher(Pointer ctx, Pointer method, Pointer argv) { - return runtimeOpaques[jsGetRuntime(ctx)].channel(ctx, method, argv); + Pointer rt = method.address == 0 ? ctx : jsGetRuntime(ctx); + return runtimeOpaques[rt]?.channel(ctx, method, argv); } Pointer jsNewRuntime( @@ -518,6 +519,60 @@ String jsToCString( return str; } +/// DLLEXPORT uint32_t jsNewClass(JSContext *ctx, const char *name) +final int Function( + Pointer ctx, + Pointer name, +) _jsNewClass = qjsLib + .lookup< + NativeFunction< + Uint32 Function( + Pointer, + Pointer, + )>>("jsNewClass") + .asFunction(); + +int jsNewClass( + Pointer ctx, + String name, +) { + var utf8name = Utf8.toUtf8(name); + var val = _jsNewClass( + ctx, + utf8name, + ); + free(utf8name); + return val; +} + +/// DLLEXPORT JSValue *jsNewObjectClass(JSContext *ctx, uint32_t QJSClassId, void *opaque) +final Pointer Function( + Pointer ctx, + int classId, + int opaque, +) jsNewObjectClass = qjsLib + .lookup< + NativeFunction< + Pointer Function( + Pointer, + Uint32, + IntPtr, + )>>("jsNewObjectClass") + .asFunction(); + +/// DLLEXPORT void *jsGetObjectOpaque(JSValue *obj, uint32_t classid) +final int Function( + Pointer obj, + int classid, +) jsGetObjectOpaque = qjsLib + .lookup< + NativeFunction< + IntPtr Function( + Pointer, + Uint32, + )>>("jsGetObjectOpaque") + .asFunction(); + /// uint8_t *jsGetArrayBuffer(JSContext *ctx, size_t *psize, JSValueConst *obj) final Pointer Function( Pointer ctx, diff --git a/lib/flutter_qjs.dart b/lib/flutter_qjs.dart index c75dd40..9700903 100644 --- a/lib/flutter_qjs.dart +++ b/lib/flutter_qjs.dart @@ -44,7 +44,14 @@ class FlutterQjs { if (_rt != null) return; _rt = jsNewRuntime((ctx, method, argv) { try { - if (method.address != 0) { + if (method.address == 0) { + Pointer rt = ctx; + DartObject obj = DartObject.fromAddress(rt, argv.address); + obj?.release(); + runtimeOpaques[rt]?.ref?.remove(obj); + return Pointer.fromAddress(0); + } + if (argv.address != 0 && method.address != 0) { if (methodHandler == null) throw Exception("No MethodHandler"); var argvs = jsToDart(ctx, argv); return dartToJs( @@ -55,7 +62,8 @@ class FlutterQjs { )); } if (moduleHandler == null) throw Exception("No ModuleHandler"); - var ret = Utf8.toUtf8(moduleHandler(Utf8.fromUtf8(argv.cast()))); + var ret = + Utf8.toUtf8(moduleHandler(Utf8.fromUtf8(method.cast()))); Future.microtask(() { free(ret); }); diff --git a/lib/wrapper.dart b/lib/wrapper.dart index ba8e054..68a8436 100644 --- a/lib/wrapper.dart +++ b/lib/wrapper.dart @@ -44,15 +44,16 @@ class DartObject implements JSRef { runtimeOpaques[jsGetRuntime(ctx)]?.ref?.add(this); } - static DartObject fromAddress(Pointer ctx, int val) { - return runtimeOpaques[jsGetRuntime(ctx)]?.ref?.firstWhere( + static DartObject fromAddress(Pointer rt, int val) { + return runtimeOpaques[rt]?.ref?.firstWhere( (e) => identityHashCode(e) == val, - orElse: null, + orElse: () => null, ); } @override void release() { + obj = null; ctx = null; } } @@ -216,7 +217,14 @@ Pointer dartToJs(Pointer ctx, dynamic val, {Map cache}) { } return ret; } - return runtimeOpaques[jsGetRuntime(ctx)]?.objectWrapper(val) ?? jsUNDEFINED(); + int dartObjectClassId = + runtimeOpaques[jsGetRuntime(ctx)]?.dartObjectClassId ?? 0; + if (dartObjectClassId == 0) return jsUNDEFINED(); + return jsNewObjectClass( + ctx, + dartObjectClassId, + identityHashCode(DartObject(ctx, val)), + ); } dynamic jsToDart(Pointer ctx, Pointer val, {Map cache}) { @@ -233,6 +241,13 @@ dynamic jsToDart(Pointer ctx, Pointer val, {Map cache}) { case JSTag.STRING: return jsToCString(ctx, val); case JSTag.OBJECT: + final rt = jsGetRuntime(ctx); + final dartObjectClassId = runtimeOpaques[rt].dartObjectClassId; + if (dartObjectClassId != 0) { + final dartObject = DartObject.fromAddress( + rt, jsGetObjectOpaque(val, dartObjectClassId)); + if (dartObject != null) return dartObject.obj; + } Pointer psize = allocate(); Pointer buf = jsGetArrayBuffer(ctx, psize, val); int size = psize.value; @@ -247,7 +262,7 @@ dynamic jsToDart(Pointer ctx, Pointer val, {Map cache}) { if (jsIsFunction(ctx, val) != 0) { return JSFunction(ctx, val); } else if (jsIsPromise(ctx, val) != 0) { - return runtimeOpaques[jsGetRuntime(ctx)]?.promiseToFuture(val); + return runtimeOpaques[rt]?.promiseToFuture(val); } else if (jsIsArray(ctx, val) != 0) { Pointer jslength = jsGetPropertyStr(ctx, val, "length"); int length = jsToInt64(ctx, jslength); @@ -283,10 +298,6 @@ dynamic jsToDart(Pointer ctx, Pointer val, {Map cache}) { } jsFree(ctx, ptab.value); free(ptab); - final objHash = ret["__dart_obj_hash__"]; - if (objHash is int) { - return DartObject.fromAddress(ctx, objHash)?.obj ?? ret; - } return ret; } break; @@ -318,23 +329,9 @@ Pointer jsNewContextWithPromsieWrapper(Pointer rt) { """, "", JSEvalFlag.GLOBAL); - var jsObjectWrapper = jsEval( - ctx, - """ - (objHash, type) => { - const ret = { - "__dart_obj_hash__": objHash, - }; - ret.__proto__.toString = ()=> "" + type; - return ret; - }; - """, - "", - JSEvalFlag.GLOBAL); + runtimeOpaque.dartObjectClassId = jsNewClass(ctx, "DartObject"); final promiseWrapper = JSRefValue(ctx, jsPromiseWrapper); jsFreeValue(ctx, jsPromiseWrapper); - final objectWrapper = JSRefValue(ctx, jsObjectWrapper); - jsFreeValue(ctx, jsObjectWrapper); runtimeOpaque.promiseToFuture = (promise) { var completer = Completer(); var wrapper = promiseWrapper.val; @@ -345,16 +342,5 @@ Pointer jsNewContextWithPromsieWrapper(Pointer rt) { jsFreeValue(ctx, jsPromise); return wrapPromise.completer.future; }; - runtimeOpaque.objectWrapper = (obj) { - var jsObjHash = jsNewInt64(ctx, identityHashCode(DartObject(ctx, obj))); - var jsTypeString = jsNewString(ctx, obj.toString()); - var wrapper = objectWrapper.val; - if (wrapper == null) throw Exception("Runtime has been released!"); - var ret = jsCall(ctx, wrapper, null, [jsObjHash, jsTypeString]); - jsFreeValue(ctx, jsObjHash); - jsFreeValue(ctx, jsTypeString); - return ret; - }; - return ctx; } diff --git a/pubspec.yaml b/pubspec.yaml index cc92630..2f9390f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_qjs description: This plugin is a simple js engine for flutter using the `quickjs` project. Plugin currently supports all the platforms except web! -version: 0.2.4 +version: 0.2.5 homepage: https://github.com/ekibun/flutter_qjs environment: