mirror of
https://github.com/wgh136/flutter_qjs.git
synced 2025-09-27 05:27:23 +00:00
ffi android
This commit is contained in:
14
cxx/ffi.cpp
14
cxx/ffi.cpp
@@ -3,7 +3,7 @@
|
||||
* @Author: ekibun
|
||||
* @Date: 2020-09-06 18:32:45
|
||||
* @LastEditors: ekibun
|
||||
* @LastEditTime: 2020-09-21 01:41:39
|
||||
* @LastEditTime: 2020-09-21 14:09:25
|
||||
*/
|
||||
#include "quickjs/quickjs.h"
|
||||
#include <functional>
|
||||
@@ -19,6 +19,11 @@ extern "C"
|
||||
{
|
||||
typedef void *JSChannel(JSContext *ctx, const char *method, void *argv);
|
||||
|
||||
DLLEXPORT JSValue *jsThrowInternalError(JSContext *ctx, char *message)
|
||||
{
|
||||
return new JSValue{JS_ThrowInternalError(ctx, "%s", message)};
|
||||
}
|
||||
|
||||
DLLEXPORT JSValue *jsEXCEPTION()
|
||||
{
|
||||
return new JSValue{JS_EXCEPTION};
|
||||
@@ -42,10 +47,7 @@ extern "C"
|
||||
JSChannel *channel = (JSChannel *)JS_GetRuntimeOpaque(rt);
|
||||
const char *str = (char *)channel(ctx, (char *)0, (void *)module_name);
|
||||
if (str == 0)
|
||||
{
|
||||
JS_ThrowReferenceError(ctx, "Module Not Found");
|
||||
return NULL;
|
||||
}
|
||||
JSValue func_val = JS_Eval(ctx, str, strlen(str), module_name, JS_EVAL_TYPE_MODULE | JS_EVAL_FLAG_COMPILE_ONLY);
|
||||
if (JS_IsException(func_val))
|
||||
return NULL;
|
||||
@@ -70,7 +72,7 @@ extern "C"
|
||||
DLLEXPORT JSRuntime *jsNewRuntime(JSChannel channel)
|
||||
{
|
||||
JSRuntime *rt = JS_NewRuntime();
|
||||
JS_SetRuntimeOpaque(rt, channel);
|
||||
JS_SetRuntimeOpaque(rt, (void *)channel);
|
||||
JS_SetModuleLoaderFunc(rt, nullptr, js_module_loader, nullptr);
|
||||
return rt;
|
||||
}
|
||||
@@ -266,7 +268,7 @@ extern "C"
|
||||
|
||||
DLLEXPORT uint32_t sizeOfJSValue()
|
||||
{
|
||||
return sizeof JSValue;
|
||||
return sizeof (JSValue);
|
||||
}
|
||||
|
||||
DLLEXPORT void setJSValueList(JSValue *list, uint32_t i, JSValue *val)
|
||||
|
@@ -1,266 +0,0 @@
|
||||
/*
|
||||
* @Description:
|
||||
* @Author: ekibun
|
||||
* @Date: 2020-08-07 13:55:52
|
||||
* @LastEditors: ekibun
|
||||
* @LastEditTime: 2020-09-16 00:29:57
|
||||
*/
|
||||
#pragma once
|
||||
#include "quickjspp.hpp"
|
||||
#include <future>
|
||||
#include <string.h>
|
||||
|
||||
namespace std
|
||||
{
|
||||
template <>
|
||||
struct hash<qjs::Value>
|
||||
{
|
||||
size_t operator()(const qjs::Value &key) const
|
||||
{
|
||||
return (size_t)JS_VALUE_GET_PTR(key.v);
|
||||
}
|
||||
};
|
||||
} // namespace std
|
||||
|
||||
namespace qjs
|
||||
{
|
||||
#include "quickjs/list.h"
|
||||
|
||||
static JSClassID js_dart_promise_class_id;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
int count;
|
||||
JSValue *argv;
|
||||
} JSOSFutureArgv;
|
||||
|
||||
using JSFutureReturn = std::function<JSOSFutureArgv(JSContext *)>;
|
||||
using DartChannel = std::function<std::promise<JSFutureReturn> *(std::string, Value)>;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
struct list_head link;
|
||||
std::promise<JSFutureReturn> *promise;
|
||||
std::shared_future<JSFutureReturn> future;
|
||||
JSValue resolve;
|
||||
JSValue reject;
|
||||
} JSOSFuture;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
struct list_head link;
|
||||
JSValue ref;
|
||||
} JSOSRef;
|
||||
|
||||
typedef struct JSThreadState
|
||||
{
|
||||
struct list_head os_future;
|
||||
struct list_head os_ref;
|
||||
DartChannel channel;
|
||||
} JSThreadState;
|
||||
|
||||
JSModuleDef *js_module_loader(
|
||||
JSContext *ctx,
|
||||
const char *module_name, void *opaque)
|
||||
{
|
||||
JSRuntime *rt = JS_GetRuntime(ctx);
|
||||
JSThreadState *ts = (JSThreadState *)JS_GetRuntimeOpaque(rt);
|
||||
auto promise = ts->channel("__dart_load_module__", Value{ctx, JS_NewString(ctx, module_name)});
|
||||
JSOSFutureArgv argv = promise->get_future().get()(ctx);
|
||||
if (argv.count > 0)
|
||||
{
|
||||
const char *str = JS_ToCString(ctx, argv.argv[0]);
|
||||
JSValue func_val = JS_Eval(ctx, str, strlen(str), module_name, JS_EVAL_TYPE_MODULE | JS_EVAL_FLAG_COMPILE_ONLY);
|
||||
JS_FreeCString(ctx, str);
|
||||
JS_FreeValue(ctx, argv.argv[0]);
|
||||
if (JS_IsException(func_val))
|
||||
return NULL;
|
||||
/* the module is already referenced, so we must free it */
|
||||
JSModuleDef *m = (JSModuleDef *)JS_VALUE_GET_PTR(func_val);
|
||||
JS_FreeValue(ctx, func_val);
|
||||
return m;
|
||||
}
|
||||
else
|
||||
{
|
||||
JS_Throw(ctx, argv.argv[0]);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
JSValue js_add_ref(Value val)
|
||||
{
|
||||
JSRuntime *rt = JS_GetRuntime(val.ctx);
|
||||
JSThreadState *ts = (JSThreadState *)JS_GetRuntimeOpaque(rt);
|
||||
JSOSRef *th;
|
||||
th = (JSOSRef *)js_mallocz(val.ctx, sizeof(*th));
|
||||
th->ref = JS_DupValue(val.ctx, val.v);
|
||||
list_add_tail(&th->link, &ts->os_ref);
|
||||
return th->ref;
|
||||
}
|
||||
|
||||
static JSValue js_add_future(Value resolve, Value reject, std::promise<JSFutureReturn> *promise)
|
||||
{
|
||||
JSRuntime *rt = JS_GetRuntime(resolve.ctx);
|
||||
JSThreadState *ts = (JSThreadState *)JS_GetRuntimeOpaque(rt);
|
||||
JSValueConst jsResolve, jsReject;
|
||||
JSOSFuture *th;
|
||||
JSValue obj;
|
||||
|
||||
jsResolve = resolve.v;
|
||||
if (!JS_IsFunction(resolve.ctx, jsResolve))
|
||||
return JS_ThrowTypeError(resolve.ctx, "resolve not a function");
|
||||
jsReject = reject.v;
|
||||
if (!JS_IsFunction(reject.ctx, jsReject))
|
||||
return JS_ThrowTypeError(reject.ctx, "reject not a function");
|
||||
obj = JS_NewObjectClass(resolve.ctx, js_dart_promise_class_id);
|
||||
if (JS_IsException(obj))
|
||||
return obj;
|
||||
th = (JSOSFuture *)js_mallocz(resolve.ctx, sizeof(*th));
|
||||
if (!th)
|
||||
{
|
||||
JS_FreeValue(resolve.ctx, obj);
|
||||
return JS_EXCEPTION;
|
||||
}
|
||||
th->promise = promise;
|
||||
th->future = promise->get_future();
|
||||
th->resolve = JS_DupValue(resolve.ctx, jsResolve);
|
||||
th->reject = JS_DupValue(reject.ctx, jsReject);
|
||||
list_add_tail(&th->link, &ts->os_future);
|
||||
JS_SetOpaque(obj, th);
|
||||
return obj;
|
||||
}
|
||||
|
||||
JSValue js_dart_future(Value resolve, Value reject, std::string name, Value args)
|
||||
{
|
||||
JSRuntime *rt = JS_GetRuntime(resolve.ctx);
|
||||
JSThreadState *ts = (JSThreadState *)JS_GetRuntimeOpaque(rt);
|
||||
return js_add_future(resolve, reject, ts->channel(name, args));
|
||||
}
|
||||
|
||||
static void unlink_ref(JSRuntime *rt, JSOSRef *th)
|
||||
{
|
||||
if (th->link.prev)
|
||||
{
|
||||
list_del(&th->link);
|
||||
th->link.prev = th->link.next = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static void free_ref(JSRuntime *rt, JSOSRef *th)
|
||||
{
|
||||
JS_FreeValueRT(rt, th->ref);
|
||||
js_free_rt(rt, th);
|
||||
}
|
||||
|
||||
static void unlink_future(JSRuntime *rt, JSOSFuture *th)
|
||||
{
|
||||
if (th->link.prev)
|
||||
{
|
||||
list_del(&th->link);
|
||||
th->link.prev = th->link.next = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static void free_future(JSRuntime *rt, JSOSFuture *th)
|
||||
{
|
||||
JS_FreeValueRT(rt, th->resolve);
|
||||
JS_FreeValueRT(rt, th->reject);
|
||||
js_free_rt(rt, th);
|
||||
}
|
||||
|
||||
void js_init_handlers(JSRuntime *rt, DartChannel channel)
|
||||
{
|
||||
JSThreadState *ts = (JSThreadState *)malloc(sizeof(*ts));
|
||||
if (!ts)
|
||||
{
|
||||
fprintf(stderr, "Could not allocate memory for the worker");
|
||||
exit(1);
|
||||
}
|
||||
memset(ts, 0, sizeof(*ts));
|
||||
init_list_head(&ts->os_future);
|
||||
init_list_head(&ts->os_ref);
|
||||
ts->channel = channel;
|
||||
|
||||
JS_SetRuntimeOpaque(rt, ts);
|
||||
}
|
||||
|
||||
void js_free_handlers(JSRuntime *rt)
|
||||
{
|
||||
JSThreadState *ts = (JSThreadState *)JS_GetRuntimeOpaque(rt);
|
||||
struct list_head *el, *el1;
|
||||
|
||||
list_for_each_safe(el, el1, &ts->os_future)
|
||||
{
|
||||
JSOSFuture *th = list_entry(el, JSOSFuture, link);
|
||||
th->future.get();
|
||||
delete th->promise;
|
||||
unlink_future(rt, th);
|
||||
free_future(rt, th);
|
||||
}
|
||||
list_for_each_safe(el, el1, &ts->os_ref)
|
||||
{
|
||||
JSOSRef *th = list_entry(el, JSOSRef, link);
|
||||
unlink_ref(rt, th);
|
||||
free_ref(rt, th);
|
||||
}
|
||||
ts->channel = nullptr;
|
||||
free(ts);
|
||||
JS_SetRuntimeOpaque(rt, NULL); /* fail safe */
|
||||
}
|
||||
|
||||
static JSValue call_handler(JSContext *ctx, JSValueConst func, int count, JSValue *argv)
|
||||
{
|
||||
JSValue ret, func1;
|
||||
/* 'func' might be destroyed when calling itself (if it frees the
|
||||
handler), so must take extra care */
|
||||
func1 = JS_DupValue(ctx, func);
|
||||
ret = JS_Call(ctx, func1, JS_UNDEFINED, count, argv);
|
||||
JS_FreeValue(ctx, func1);
|
||||
for (int i = 0; i < count; ++i)
|
||||
JS_FreeValue(ctx, argv[i]);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int js_dart_poll(JSContext *ctx)
|
||||
{
|
||||
JSRuntime *rt = JS_GetRuntime(ctx);
|
||||
JSThreadState *ts = (JSThreadState *)JS_GetRuntimeOpaque(rt);
|
||||
struct list_head *el;
|
||||
|
||||
/* XXX: handle signals if useful */
|
||||
|
||||
if (list_empty(&ts->os_future))
|
||||
return -1; /* no more events */
|
||||
|
||||
/* XXX: only timers and basic console input are supported */
|
||||
if (!list_empty(&ts->os_future))
|
||||
{
|
||||
list_for_each(el, &ts->os_future)
|
||||
{
|
||||
JSOSFuture *th = list_entry(el, JSOSFuture, link);
|
||||
auto status = th->future.wait_for(std::chrono::seconds(0));
|
||||
if (status == std::future_status::ready)
|
||||
{
|
||||
JSOSFutureArgv argv = th->future.get()(ctx);
|
||||
JSValue resolve, reject;
|
||||
/* the timer expired */
|
||||
resolve = th->resolve;
|
||||
th->resolve = JS_UNDEFINED;
|
||||
reject = th->reject;
|
||||
th->reject = JS_UNDEFINED;
|
||||
unlink_future(rt, th);
|
||||
free_future(rt, th);
|
||||
JSValue ret = call_handler(ctx, argv.count < 0 ? reject : resolve, abs(argv.count), argv.argv);
|
||||
if (qjs::JS_IsException(ret))
|
||||
throw qjs::exception{};
|
||||
JS_FreeValue(ctx, ret);
|
||||
JS_FreeValue(ctx, resolve);
|
||||
JS_FreeValue(ctx, reject);
|
||||
delete argv.argv;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
} // namespace qjs
|
@@ -1,196 +0,0 @@
|
||||
/*
|
||||
* @Description:
|
||||
* @Author: ekibun
|
||||
* @Date: 2020-08-08 10:30:59
|
||||
* @LastEditors: ekibun
|
||||
* @LastEditTime: 2020-08-27 18:55:57
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <queue>
|
||||
#include <thread>
|
||||
#include <atomic>
|
||||
#include <future>
|
||||
#include <iostream>
|
||||
|
||||
#include "js_dart_promise.hpp"
|
||||
|
||||
namespace qjs
|
||||
{
|
||||
struct EngineTask
|
||||
{
|
||||
std::function<Value(Context&)> invoke;
|
||||
std::function<void(Value)> resolve;
|
||||
std::function<void(Value)> reject;
|
||||
};
|
||||
|
||||
struct EngineTaskResolver
|
||||
{
|
||||
Value result;
|
||||
std::function<void(Value)> resolve;
|
||||
std::function<void(Value)> reject;
|
||||
};
|
||||
|
||||
std::string getStackTrack(Value exc)
|
||||
{
|
||||
std::string err = (std::string)exc;
|
||||
if ((bool)exc["stack"])
|
||||
err += "\n" + (std::string)exc["stack"];
|
||||
return err;
|
||||
}
|
||||
|
||||
class Engine
|
||||
{
|
||||
// 引擎线程
|
||||
std::thread thread;
|
||||
// 任务队列
|
||||
std::queue<EngineTask> tasks;
|
||||
// 同步
|
||||
std::mutex m_lock;
|
||||
// 是否关闭提交
|
||||
std::atomic<bool> stoped;
|
||||
|
||||
void handleException(qjs::Value exc)
|
||||
{
|
||||
printf("%s", getStackTrack(exc).c_str());
|
||||
std::cout << std::endl;
|
||||
}
|
||||
|
||||
public:
|
||||
inline Engine(std::function<std::promise<JSFutureReturn> *(std::string, Value, Engine *)> channel) : stoped{false}
|
||||
{
|
||||
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);
|
||||
Context ctx(rt);
|
||||
auto &module = ctx.addModule("__DartImpl");
|
||||
module.function<&js_dart_future>("__invoke");
|
||||
ctx.eval(
|
||||
R"xxx(
|
||||
import * as __DartImpl from "__DartImpl";
|
||||
globalThis.dart = (method, ...args) => new Promise((res, rej) =>
|
||||
__DartImpl.__invoke(res, rej, method, args));
|
||||
)xxx",
|
||||
"<dart>", JS_EVAL_TYPE_MODULE);
|
||||
JS_SetModuleLoaderFunc(rt.rt, nullptr, js_module_loader, nullptr);
|
||||
std::vector<EngineTaskResolver> unresolvedTask;
|
||||
Value promiseWrapper = ctx.eval(
|
||||
R"xxx(
|
||||
(value) => {
|
||||
const __ret = Promise.resolve(value)
|
||||
.then(v => {
|
||||
__ret.__value = v;
|
||||
__ret.__resolved = true;
|
||||
}).catch(e => {
|
||||
__ret.__error = e;
|
||||
__ret.__rejected = true;
|
||||
});
|
||||
return __ret;
|
||||
}
|
||||
)xxx",
|
||||
"<PromiseWrapper>", JS_EVAL_TYPE_GLOBAL);
|
||||
|
||||
// 循环
|
||||
while (!this->stoped)
|
||||
{
|
||||
// 获取待执行的task
|
||||
EngineTask task;
|
||||
{ // 获取一个待执行的 task
|
||||
std::unique_lock<std::mutex> lock{this->m_lock}; // unique_lock 相比 lock_guard 的好处是:可以随时 unlock() 和 lock()
|
||||
if (!this->tasks.empty())
|
||||
{
|
||||
task = this->tasks.front(); // 取一个 task
|
||||
this->tasks.pop();
|
||||
}
|
||||
}
|
||||
// 执行task
|
||||
if (task.resolve)
|
||||
try
|
||||
{
|
||||
Value val = task.invoke(ctx);
|
||||
Value ret = Value{ctx.ctx, JS_Call(ctx.ctx, promiseWrapper.v, ctx.global().v, 1, &(val.v))};
|
||||
unresolvedTask.emplace_back(EngineTaskResolver{ret, std::move(task.resolve), std::move(task.reject)});
|
||||
}
|
||||
catch (exception)
|
||||
{
|
||||
task.reject(ctx.getException());
|
||||
}
|
||||
// 执行microtask
|
||||
JSContext *pctx;
|
||||
for (;;)
|
||||
{
|
||||
int err = JS_ExecutePendingJob(rt.rt, &pctx);
|
||||
if (err <= 0)
|
||||
{
|
||||
if (err < 0)
|
||||
handleException(ctx.getException());
|
||||
break;
|
||||
}
|
||||
}
|
||||
// TODO 检查promise状态
|
||||
for (auto it = unresolvedTask.begin(); it != unresolvedTask.end();)
|
||||
{
|
||||
bool finished = false;
|
||||
if (it->result["__resolved"])
|
||||
{
|
||||
it->resolve(it->result["__value"]);
|
||||
finished = true;
|
||||
};
|
||||
if (it->result["__rejected"])
|
||||
{
|
||||
it->reject(it->result["__error"]);
|
||||
finished = true;
|
||||
};
|
||||
if (finished)
|
||||
it = unresolvedTask.erase(it);
|
||||
else
|
||||
++it;
|
||||
}
|
||||
// 检查dart交互
|
||||
bool idle = true;
|
||||
try
|
||||
{
|
||||
idle = js_dart_poll(ctx.ctx);
|
||||
}
|
||||
catch (exception)
|
||||
{
|
||||
handleException(ctx.getException());
|
||||
}
|
||||
// 空闲时reject所有task
|
||||
if (idle && !JS_IsJobPending(rt.rt) && !unresolvedTask.empty())
|
||||
{
|
||||
for (EngineTaskResolver &_task : unresolvedTask)
|
||||
{
|
||||
_task.reject(ctx.newValue("Promise cannot resolve"));
|
||||
}
|
||||
unresolvedTask.clear();
|
||||
}
|
||||
}
|
||||
js_free_handlers(rt.rt);
|
||||
});
|
||||
}
|
||||
inline ~Engine()
|
||||
{
|
||||
stoped.store(true);
|
||||
if (thread.joinable())
|
||||
thread.join(); // 等待任务结束, 前提:线程一定会执行完
|
||||
}
|
||||
|
||||
public:
|
||||
// 提交一个任务
|
||||
void commit(EngineTask task)
|
||||
{
|
||||
if (stoped.load()) // stop == true ??
|
||||
throw std::runtime_error("commit on stopped engine.");
|
||||
{ // 添加任务到队列
|
||||
std::lock_guard<std::mutex> lock{m_lock}; //对当前块的语句加锁 lock_guard 是 mutex 的 stack 封装类,构造的时候 lock(),析构的时候 unlock()
|
||||
tasks.emplace(task);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace qjs
|
Submodule cxx/quickjs updated: b994795091...56cbb57a2e
1294
cxx/quickjspp.hpp
1294
cxx/quickjspp.hpp
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user