From 0ba5b045fa5abd1e4a3f4b8fc9194027d8442f9f Mon Sep 17 00:00:00 2001 From: ekibun Date: Sat, 15 Aug 2020 16:47:01 +0800 Subject: [PATCH] multi engine --- cxx/js_dart_promise.hpp | 2 +- cxx/js_engine.hpp | 24 +++++----- example/lib/test.dart | 85 ++++++++++++++++++---------------- lib/flutter_qjs.dart | 79 +++++++++++++++++++++---------- windows/dart_js_wrapper.hpp | 6 +-- windows/flutter_qjs_plugin.cpp | 36 +++++++------- 6 files changed, 136 insertions(+), 96 deletions(-) diff --git a/cxx/js_dart_promise.hpp b/cxx/js_dart_promise.hpp index 64d2a0e..6b99f37 100644 --- a/cxx/js_dart_promise.hpp +++ b/cxx/js_dart_promise.hpp @@ -3,7 +3,7 @@ * @Author: ekibun * @Date: 2020-08-07 13:55:52 * @LastEditors: ekibun - * @LastEditTime: 2020-08-15 14:45:00 + * @LastEditTime: 2020-08-15 16:28:38 */ #pragma once #include "quickjspp/quickjspp.hpp" diff --git a/cxx/js_engine.hpp b/cxx/js_engine.hpp index acf4c2a..436058d 100644 --- a/cxx/js_engine.hpp +++ b/cxx/js_engine.hpp @@ -3,7 +3,7 @@ * @Author: ekibun * @Date: 2020-08-08 10:30:59 * @LastEditors: ekibun - * @LastEditTime: 2020-08-15 13:13:43 + * @LastEditTime: 2020-08-15 16:31:14 */ #pragma once @@ -22,15 +22,15 @@ namespace qjs struct EngineTask { std::function invoke; - std::function resolve; - std::function reject; + std::function resolve; + std::function reject; }; struct EngineTaskResolver { Value result; - std::function resolve; - std::function reject; + std::function resolve; + std::function reject; }; std::string getStackTrack(Value exc) @@ -58,9 +58,11 @@ namespace qjs } public: - inline Engine(DartChannel channel) : stoped{false} + inline Engine(std::function *(std::string, Value, Engine *)> channel) : stoped{false} { - thread = std::thread([this, channel] { // 工作线程函数 + thread = std::thread([this, channel = [this, channel](std::string method, Value args){ + return channel(method, args, this); + }] { // 创建运行环境 Runtime rt; js_init_handlers(rt.rt, channel); @@ -114,7 +116,7 @@ namespace qjs } catch (exception) { - task.reject(getStackTrack(ctx.getException())); + task.reject(ctx.getException()); } // 执行microtask JSContext *pctx; @@ -134,12 +136,12 @@ namespace qjs bool finished = false; if (it->result["__resolved"]) { - it->resolve((std::string)it->result["__value"]); + it->resolve(it->result["__value"]); finished = true; }; if (it->result["__rejected"]) { - it->reject(getStackTrack(it->result["__error"])); + it->reject(it->result["__error"]); finished = true; }; if (finished) @@ -162,7 +164,7 @@ namespace qjs { for (EngineTaskResolver &_task : unresolvedTask) { - _task.reject("Promise cannot resolve"); + _task.reject(ctx.newValue("Promise cannot resolve")); } unresolvedTask.clear(); } diff --git a/example/lib/test.dart b/example/lib/test.dart index 4c0b063..308083c 100644 --- a/example/lib/test.dart +++ b/example/lib/test.dart @@ -3,8 +3,8 @@ * @Author: ekibun * @Date: 2020-07-18 23:28:55 * @LastEditors: ekibun - * @LastEditTime: 2020-08-15 14:01:09 - */ + * @LastEditTime: 2020-08-15 16:39:07 + */ import 'package:dio/dio.dart'; import 'package:flutter/material.dart'; import 'package:flutter_qjs/flutter_qjs.dart'; @@ -17,9 +17,8 @@ class TestPage extends StatefulWidget { } class _TestPageState extends State { - String code, resp; - int engine; + FlutterJs engine; @override Widget build(BuildContext context) { @@ -36,43 +35,47 @@ class _TestPageState extends State { scrollDirection: Axis.horizontal, child: Row( children: [ - FlatButton(child: Text("初始化引擎"), onPressed: () async { - if ((engine?? 0) != 0) return; - engine = await FlutterJs.initEngine(); - // dart 函数回调 - FlutterJs.methodHandler = (String method, List arg) async { - switch (method) { - case "delay": - await Future.delayed(Duration(milliseconds: arg[0])); + FlatButton( + child: Text("初始化引擎"), + onPressed: () async { + if (engine != null) return; + engine = FlutterJs(); + engine.setMethodHandler((String method, List arg) async { + switch (method) { + case "delay": + await Future.delayed(Duration(milliseconds: arg[0])); + return; + case "http": + Response response = await Dio() + .get(arg[0], options: Options(responseType: ResponseType.bytes)); + return response.data; + case "hello": + return await arg[0](["hello: "]); + default: + } + }); + }), + FlatButton( + child: Text("运行"), + onPressed: () async { + if (engine == null) { + print("请先初始化引擎"); return; - case "http": - Response response = await Dio().get(arg[0]); - return response.data; - case "hello": - return await arg[0](["hello: "]); - default: - } - }; - }), - FlatButton(child: Text("运行"), onPressed: () async { - if ((engine?? 0) == 0) { - print("请先初始化引擎"); - return; - } - try { - resp = await FlutterJs.evaluate(code ?? '', ""); - } catch(e) { - resp = e.toString(); - } - setState(() { - code = code; - }); - }), - FlatButton(child: Text("释放引擎"), onPressed: () async { - if ((engine?? 0) == 0) return; - await FlutterJs.close(); - engine = 0; - }), + } + try { + resp = "${await engine.evaluate(code ?? '', "")}"; + } catch (e) { + resp = e.toString(); + } + setState(() {}); + }), + FlatButton( + child: Text("释放引擎"), + onPressed: () async { + if (engine != null) return; + await engine.destroy(); + engine = null; + }), ], ), ), @@ -101,4 +104,4 @@ class _TestPageState extends State { ), ); } -} \ No newline at end of file +} diff --git a/lib/flutter_qjs.dart b/lib/flutter_qjs.dart index 5ff74ed..12ac8cb 100644 --- a/lib/flutter_qjs.dart +++ b/lib/flutter_qjs.dart @@ -3,27 +3,31 @@ * @Author: ekibun * @Date: 2020-08-08 08:29:09 * @LastEditors: ekibun - * @LastEditTime: 2020-08-15 13:58:11 + * @LastEditTime: 2020-08-15 16:21:56 */ import 'dart:async'; - import 'package:flutter/services.dart'; -class FlutterJs { - static const MethodChannel _channel = const MethodChannel('soko.ekibun.flutter_qjs'); +typedef JsMethodHandler = Future Function(String method, List args); - static Future Function(String method, List args) methodHandler; - - static Future initEngine() async { - final int engineId = await _channel.invokeMethod("initEngine"); +class _FlutterJs { + factory _FlutterJs() => _getInstance(); + static _FlutterJs get instance => _getInstance(); + static _FlutterJs _instance; + MethodChannel _channel = const MethodChannel('soko.ekibun.flutter_qjs'); + Map methodHandlers = Map(); + _FlutterJs._internal() { _channel.setMethodCallHandler((call) async { - if (methodHandler == null) return call.noSuchMethod(null); - return await methodHandler(call.method, _wrapFunctionArguments(call.arguments)); + print(call.arguments); + var engine = call.arguments["engine"]; + var args = call.arguments["args"]; + print(methodHandlers.entries); + print(methodHandlers[engine]); + if (methodHandlers[engine] == null) return call.noSuchMethod(null); + return await methodHandlers[engine](call.method, _wrapFunctionArguments(args)); }); - return engineId; } - - static dynamic _wrapFunctionArguments(dynamic val) { + dynamic _wrapFunctionArguments(dynamic val) { if (val is List) { for (var i = 0; i < val.length; ++i) { val[i] = _wrapFunctionArguments(val[i]); @@ -35,20 +39,47 @@ class FlutterJs { var arguments = {"function": functionId, "arguments": args}; return await _channel.invokeMethod("call", arguments); }; - }else for(var key in val.keys) { - val[key] = _wrapFunctionArguments(val[key]); - } + } else + for (var key in val.keys) { + val[key] = _wrapFunctionArguments(val[key]); + } } return val; } - static Future evaluate(String command, String name) async { - var arguments = {"script": command, "name": command}; - final String jsResult = await _channel.invokeMethod("evaluate", arguments); - return jsResult ?? "null"; - } - - static Future close() async { - return await _channel.invokeMethod("close"); + static _FlutterJs _getInstance() { + if (_instance == null) { + _instance = new _FlutterJs._internal(); + } + return _instance; + } +} + +class FlutterJs { + dynamic _engine; + + ensureEngine() async { + if(_engine == null){ + _engine = await _FlutterJs.instance._channel.invokeMethod("createEngine"); + } + } + + setMethodHandler(JsMethodHandler handler) async { + await ensureEngine(); + _FlutterJs.instance.methodHandlers[_engine] = handler; + } + + destroy() async { + if (_engine != null){ + await _FlutterJs.instance._channel.invokeMethod("close", {"engine": _engine}); + _engine = null; + } + } + + Future evaluate(String command, String name) async { + ensureEngine(); + var arguments = {"engine": _engine, "script": command, "name": command}; + return _FlutterJs.instance._wrapFunctionArguments( + await _FlutterJs.instance._channel.invokeMethod("evaluate", arguments)); } } diff --git a/windows/dart_js_wrapper.hpp b/windows/dart_js_wrapper.hpp index 6e58034..143888b 100644 --- a/windows/dart_js_wrapper.hpp +++ b/windows/dart_js_wrapper.hpp @@ -3,7 +3,7 @@ * @Author: ekibun * @Date: 2020-08-14 21:45:02 * @LastEditors: ekibun - * @LastEditTime: 2020-08-15 14:08:04 + * @LastEditTime: 2020-08-15 15:42:55 */ #include "../cxx/js_engine.hpp" #include @@ -47,7 +47,7 @@ namespace qjs return JS_UNDEFINED; } - JSValue dartToJs(JSContext *ctx, flutter::EncodableValue val, std::unordered_map cache) + JSValue dartToJs(JSContext *ctx, flutter::EncodableValue val, std::unordered_map cache = std::unordered_map()) { if (val.IsNull()) return JS_UNDEFINED; @@ -111,7 +111,7 @@ namespace qjs return JS_UNDEFINED; } - flutter::EncodableValue jsToDart(Value val, std::unordered_map cache) + flutter::EncodableValue jsToDart(Value val, std::unordered_map cache = std::unordered_map()) { if (cache.find(val) != cache.end()) return cache[val]; diff --git a/windows/flutter_qjs_plugin.cpp b/windows/flutter_qjs_plugin.cpp index aa0ef74..bda4428 100644 --- a/windows/flutter_qjs_plugin.cpp +++ b/windows/flutter_qjs_plugin.cpp @@ -30,17 +30,20 @@ namespace }; std::shared_ptr> channel; - std::promise *invokeChannelMethod(std::string name, qjs::Value args) + std::promise *invokeChannelMethod(std::string name, qjs::Value args, qjs::Engine *engine) { auto promise = new std::promise(); + auto map = new flutter::EncodableMap(); + (*map)[std::string("engine")] = (int64_t)engine; + (*map)[std::string("args")] = qjs::jsToDart(args, std::unordered_map()); channel->InvokeMethod( name, - std::make_unique(qjs::jsToDart(args, std::unordered_map())), + std::make_unique(*map), std::make_unique>( (flutter::ResultHandlerSuccess)[promise]( const flutter::EncodableValue *result) { promise->set_value((qjs::JSFutureReturn)[result = result ? *result : flutter::EncodableValue()](qjs::JSContext * ctx) { - qjs::JSValue *ret = new qjs::JSValue{qjs::dartToJs(ctx, result, std::unordered_map())}; + qjs::JSValue *ret = new qjs::JSValue{qjs::dartToJs(ctx, result)}; return qjs::JSOSFutureArgv{1, ret}; }); }, @@ -96,8 +99,6 @@ namespace return it->second; } - qjs::Engine *engine = nullptr; - void FlutterQjsPlugin::HandleMethodCall( const flutter::MethodCall &method_call, std::unique_ptr> result) @@ -108,15 +109,16 @@ namespace // and // https://github.com/flutter/engine/tree/master/shell/platform/glfw/client_wrapper/include/flutter // for the relevant Flutter APIs. - if (method_call.method_name().compare("initEngine") == 0) + if (method_call.method_name().compare("createEngine") == 0) { - engine = new qjs::Engine((qjs::DartChannel)invokeChannelMethod); + qjs::Engine *engine = new qjs::Engine(invokeChannelMethod); flutter::EncodableValue response = (int64_t)engine; result->Success(&response); } else if (method_call.method_name().compare("evaluate") == 0) { flutter::EncodableMap args = *std::get_if(method_call.arguments()); + qjs::Engine *engine = (qjs::Engine *)std::get(ValueOrNull(args, "engine")); std::string script = std::get(ValueOrNull(args, "script")); std::string name = std::get(ValueOrNull(args, "name")); auto presult = result.release(); @@ -124,17 +126,18 @@ namespace [script, name](qjs::Context &ctx) { return ctx.eval(script, name.c_str(), JS_EVAL_TYPE_GLOBAL); }, - [presult](std::string resolve) { - flutter::EncodableValue response = resolve; + [presult](qjs::Value resolve) { + flutter::EncodableValue response = qjs::jsToDart(resolve); presult->Success(&response); }, - [presult](std::string reject) { - presult->Error("FlutterJSException", reject); + [presult](qjs::Value reject) { + presult->Error("FlutterJSException", qjs::getStackTrack(reject)); }}); } else if (method_call.method_name().compare("call") == 0) { flutter::EncodableMap args = *std::get_if(method_call.arguments()); + qjs::Engine *engine = (qjs::Engine *)std::get(ValueOrNull(args, "engine")); qjs::JSValue *function = (qjs::JSValue *)std::get(ValueOrNull(args, "function")); flutter::EncodableList arguments = std::get(ValueOrNull(args, "arguments")); auto presult = result.release(); @@ -144,7 +147,7 @@ namespace qjs::JSValue *callargs = new qjs::JSValue[argscount]; for (size_t i = 0; i < argscount; i++) { - callargs[i] = qjs::dartToJs(ctx.ctx, arguments[i], std::unordered_map()); + callargs[i] = qjs::dartToJs(ctx.ctx, arguments[i]); } qjs::JSValue ret = JS_Call(ctx.ctx, *function, qjs::JSValue{qjs::JSValueUnion{0}, qjs::JS_TAG_UNDEFINED}, (int)argscount, callargs); qjs::JS_FreeValue(ctx.ctx, *function); @@ -152,16 +155,17 @@ namespace throw qjs::exception{}; return qjs::Value{ctx.ctx, ret}; }, - [presult](std::string resolve) { - flutter::EncodableValue response = resolve; + [presult](qjs::Value resolve) { + flutter::EncodableValue response = qjs::jsToDart(resolve); presult->Success(&response); }, - [presult](std::string reject) { - presult->Error("FlutterJSException", reject); + [presult](qjs::Value reject) { + presult->Error("FlutterJSException", qjs::getStackTrack(reject)); }}); } else if (method_call.method_name().compare("close") == 0) { + qjs::Engine *engine = (qjs::Engine *)*std::get_if(method_call.arguments()); delete engine; result->Success(); }