From 66eb46b1a40c088b1096ba2f294796404b1a530c Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Wed, 14 Aug 2019 16:38:14 +0200 Subject: messaging and module loading --- library/src/main/cpp/CMakeLists.txt | 8 +- library/src/main/cpp/akono-jni.cpp | 400 +++++++++++++++++++++++++++++--- library/src/main/java/akono/AkoniJni.kt | 39 ---- library/src/main/java/akono/AkonoJni.kt | 208 +++++++++++++++++ 4 files changed, 577 insertions(+), 78 deletions(-) delete mode 100644 library/src/main/java/akono/AkoniJni.kt create mode 100644 library/src/main/java/akono/AkonoJni.kt (limited to 'library/src/main') diff --git a/library/src/main/cpp/CMakeLists.txt b/library/src/main/cpp/CMakeLists.txt index 72259215..10d6396f 100644 --- a/library/src/main/cpp/CMakeLists.txt +++ b/library/src/main/cpp/CMakeLists.txt @@ -11,12 +11,12 @@ if(NOT EXISTS ${deps_dir}) message( FATAL_ERROR "Dependency directory does not exist") endif() -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++11") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++14") include_directories( - ${deps_dir}/node/src - ${deps_dir}/node/deps/v8/include - ${deps_dir}/node/deps/uv/include + ${deps_dir}/android-node-v8/src + ${deps_dir}/android-node-v8/deps/v8/include + ${deps_dir}/android-node-v8/deps/uv/include ) add_library(node SHARED IMPORTED) diff --git a/library/src/main/cpp/akono-jni.cpp b/library/src/main/cpp/akono-jni.cpp index f355362b..b9e71012 100644 --- a/library/src/main/cpp/akono-jni.cpp +++ b/library/src/main/cpp/akono-jni.cpp @@ -2,12 +2,35 @@ #include #include #include + #define NODE_WANT_INTERNALS 1 + #include + #include #include #include +#include +#include + + + +// Provide stubs so that libnode.so links properly +namespace node { + namespace native_module { + const bool has_code_cache = false; + + void NativeModuleEnv::InitializeCodeCache() {} + } + v8::StartupData *NodeMainInstance::GetEmbeddedSnapshotBlob() { + return nullptr; + } + + const std::vector *NodeMainInstance::GetIsolateDataIndexes() { + return nullptr; + } +} static int pfd[2]; @@ -15,25 +38,22 @@ static pthread_t thr; static const char *tag = "myapp"; -static void *thread_func(void*) -{ +static void *thread_func(void *) { ssize_t rdsz; char buf[128]; - while((rdsz = read(pfd[0], buf, sizeof buf - 1)) > 0) { - if(buf[rdsz - 1] == '\n') --rdsz; + while ((rdsz = read(pfd[0], buf, sizeof buf - 1)) > 0) { + if (buf[rdsz - 1] == '\n') --rdsz; buf[rdsz] = 0; /* add null-terminator */ __android_log_write(ANDROID_LOG_DEBUG, tag, buf); } return 0; } -static void mylog(const char *msg) -{ +static void mylog(const char *msg) { __android_log_write(ANDROID_LOG_DEBUG, tag, msg); } -int start_logger(const char *app_name) -{ +int start_logger(const char *app_name) { tag = app_name; /* make stdout line-buffered and stderr unbuffered */ @@ -46,14 +66,13 @@ int start_logger(const char *app_name) dup2(pfd[1], 2); /* spawn the logging thread */ - if(pthread_create(&thr, 0, thread_func, 0) == -1) + if (pthread_create(&thr, 0, thread_func, 0) == -1) return -1; pthread_detach(thr); return 0; } - /** * Helper class to manage conversion from a JNI string to a C string. */ @@ -61,14 +80,16 @@ class JStringValue { private: jstring m_jstr; const char *m_cstr; - JNIEnv* m_env; + JNIEnv *m_env; public: - JStringValue(JNIEnv* env, jstring s) : m_env(env), m_jstr(s) { + JStringValue(JNIEnv *env, jstring s) : m_env(env), m_jstr(s) { m_cstr = env->GetStringUTFChars(s, NULL); } + ~JStringValue() { m_env->ReleaseStringUTFChars(m_jstr, m_cstr); } + const char *operator*() { return m_cstr; } @@ -86,6 +107,58 @@ static void InitNode(std::vector argv) { node::Init(&ret_argc, &argv[0], &ret_exec_argc, &ret_exec_argv); } +// Forward declarations +void notifyCb(uv_async_t *async); + +static void sendMessageCallback(const v8::FunctionCallbackInfo &args); +static void loadModuleCallback(const v8::FunctionCallbackInfo &args); +static void getDataCallback(const v8::FunctionCallbackInfo &args); + +static const char *main_code = "global.__akono_run = (x) => {" + " console.log('running code', x);" + " global.eval(x);" + "};" + "" + "global.__akono_onMessage = (x) => {" + " console.log('got __akono_onMessage', x);" + "};" + "" + "mod = require('module');" + "mod._saved_findPath = mod._findPath;" + "mod._akonoMods = {};" + "mod._findPath = (request, paths, isMain) => {" + " console.log('in _findPath');" + " const res = mod._saved_findPath(request, paths, isMain);" + " if (res !== false) return res;" + " const args = JSON.stringify({ request, paths});" + " const loadResult = JSON.parse(global.__akono_loadModule(args));" + " console.log('got loadModule result', loadResult);" + " if (!loadResult) return false;" + " mod._akonoMods[loadResult.path] = loadResult;" + " console.log('returning path', loadResult.path);" + " return loadResult.path;" + "};" + "" + "function stripBOM(content) {" + " if (content.charCodeAt(0) === 0xFEFF) {" + " content = content.slice(1);" + " }" + " return content;" + "}" + "" + "mod._saved_js_extension = mod._extensions[\".js\"];" + "mod._extensions[\".js\"] = (module, filename) => {" + " console.log('handling js extension', [module, filename]);" + " if (mod._akonoMods.hasOwnProperty(filename)) {" + " const akmod = mod._akonoMods[filename];" + " console.log('found mod', akmod);" + " const content = akmod.content;" + " return module._compile(stripBOM(content), filename);" + " }" + " console.log('falling back');" + " return mod._saved_js_extension(module, filename);" + "};"; + class NativeAkonoInstance { private: @@ -97,8 +170,16 @@ public: v8::Isolate *isolate; node::Environment *environment; v8::Persistent globalContext; + uv_async_t async_notify; + uv_loop_t *loop; + bool breakRequested = false; + JNIEnv *currentJniEnv = nullptr; + jobject currentJniThiz = nullptr; NativeAkonoInstance() : globalContext() { + loop = uv_default_loop(); + uv_async_init(loop, &async_notify, notifyCb); + async_notify.data = this; if (!logInitialized) { start_logger("myapp"); @@ -112,14 +193,13 @@ public: // Here, only the arguments used to initialize the global node/v8 platform // are relevant, the others are skipped. - InitNode(std::vector{"node", "--trace-events-enabled"}); + InitNode(std::vector{"node", "-e", main_code}); platform = node::InitializeV8Platform(10); v8::V8::Initialize(); v8Initialized = true; } - node::ArrayBufferAllocator *allocator = node::CreateArrayBufferAllocator(); this->isolate = node::NewIsolate(allocator, uv_default_loop()); @@ -128,18 +208,20 @@ public: v8::HandleScope handle_scope(isolate); node::IsolateData *isolateData = node::CreateIsolateData( - this->isolate, - uv_default_loop(), - platform, - allocator); + this->isolate, + uv_default_loop(), + platform, + allocator); - globalContext.Reset(isolate, v8::Context::New(isolate)); + globalContext.Reset(isolate, node::NewContext(isolate)); + + v8::Local context = globalContext.Get(isolate); // Arguments for node itself - std::vector nodeArgv{"node", "-e", "console.log('hello world');"}; + std::vector nodeArgv{"node", "-e", "console.log('hello world');"}; // Arguments for the script run by node - std::vector nodeExecArgv{}; + std::vector nodeExecArgv{}; mylog("entering global scopt"); @@ -155,25 +237,86 @@ public: nodeExecArgv.size(), &nodeExecArgv[0]); + mylog("loading environment"); node::LoadEnvironment(environment); - mylog("finished environment"); + mylog("finished loading environment"); + + v8::Local dataTemplate = v8::ObjectTemplate::New(isolate); + dataTemplate->SetInternalFieldCount(1); + v8::Local dataObject = dataTemplate->NewInstance(context).ToLocalChecked(); + dataObject->SetAlignedPointerInInternalField(0, this); + + v8::Local sendMessageFunction = v8::Function::New(context, + sendMessageCallback, + dataObject).ToLocalChecked(); + + v8::Local loadModuleFunction = v8::Function::New(context, + loadModuleCallback, + dataObject).ToLocalChecked(); + + v8::Local getDataFunction = v8::Function::New(context, + getDataCallback, + dataObject).ToLocalChecked(); + + v8::Local global = context->Global(); + + global->Set(v8::String::NewFromUtf8(isolate, "__akono_sendMessage", + v8::NewStringType::kNormal).ToLocalChecked(), + sendMessageFunction); + + global->Set(v8::String::NewFromUtf8(isolate, "__akono_loadModule", + v8::NewStringType::kNormal).ToLocalChecked(), + loadModuleFunction); + + // Get data synchronously (!) from the embedder + global->Set(v8::String::NewFromUtf8(isolate, "__akono_getData", + v8::NewStringType::kNormal).ToLocalChecked(), + getDataFunction); - //v8::Isolate::CreateParams create_params; - //create_params.array_buffer_allocator = v8::ArrayBuffer::Allocator::NewDefaultAllocator(); - //this->isolate = v8::Isolate::New(create_params); -// -// node::IsolateData *isolateData = node::CreateIsolateData() -// node::Environment *environment = node::CreateEnvironment(isolateData); + } + + /** + * Process the node message loop until a break has been requested. + * + * @param env JNI env of the thread we're running in. + */ + void runNode() { + this->breakRequested = false; + while (1) { + uv_run(uv_default_loop(), UV_RUN_ONCE); + if (this->breakRequested) + break; + } + } + + /** + * Inject code into the running node instance. + * + * Must not be called from a different thread. + */ + void makeCallback(const char *code) { + mylog("in makeCallback"); + v8::Isolate::Scope isolate_scope(isolate); + v8::HandleScope handle_scope(isolate); + v8::Local context = globalContext.Get(isolate); + v8::Context::Scope context_scope(context); + v8::Local global = context->Global(); + v8::Local argv[] = { + v8::String::NewFromUtf8(isolate, code, + v8::NewStringType::kNormal).ToLocalChecked() + }; + mylog("calling node::MakeCallback"); + node::MakeCallback(isolate, global, "__akono_run", 1, argv, {0, 0}); } ~NativeAkonoInstance() { //this->isolate->Dispose(); } - jstring evalJs(JNIEnv* env, jstring sourceString) { + jstring evalJs(JNIEnv *env, jstring sourceString) { mylog("begin evalJs"); JStringValue jsv(env, sourceString); @@ -234,25 +377,212 @@ bool NativeAkonoInstance::logInitialized = false; node::MultiIsolatePlatform *NativeAkonoInstance::platform = nullptr; +void notifyCb(uv_async_t *async) { + NativeAkonoInstance *akono = (NativeAkonoInstance *) async->data; + mylog("async notifyCb called!"); + akono->breakRequested = true; +} + +static void sendMessageCallback(const v8::FunctionCallbackInfo &args) { + if (args.Length() < 1) return; + v8::Isolate *isolate = args.GetIsolate(); + v8::HandleScope scope(isolate); + v8::Local arg = args[0]; + v8::String::Utf8Value value(isolate, arg); + mylog("sendMessageCallback called, yay!"); + + v8::Local data = v8::Local::Cast(args.Data()); + + mylog("getting instance"); + NativeAkonoInstance *myInstance = (NativeAkonoInstance *) data->GetAlignedPointerFromInternalField(0); + + JNIEnv *env = myInstance->currentJniEnv; + + if (env == nullptr) { + mylog("FATAL: JNI env is nullptr"); + return; + } + + mylog("finding class"); + jclass clazz = env->FindClass("akono/AkonoJni"); + + if (clazz == nullptr) { + mylog("FATAL: class not found"); + return; + } + + mylog("creating strings"); + jstring jstr1 = env->NewStringUTF("message"); + jstring jstr2 = env->NewStringUTF(*value); + + mylog("getting method"); + + jmethodID meth = env->GetMethodID(clazz, "internalOnNotify", "(Ljava/lang/String;Ljava/lang/String;)V"); + + if (meth == nullptr) { + mylog("FATAL: method not found"); + return; + } + + mylog("calling method"); + + env->CallVoidMethod(myInstance->currentJniThiz, meth, jstr1, jstr2); +} + + +static void loadModuleCallback(const v8::FunctionCallbackInfo &args) { + if (args.Length() < 1) return; + v8::Isolate *isolate = args.GetIsolate(); + v8::HandleScope scope(isolate); + v8::Local arg = args[0]; + v8::String::Utf8Value value(isolate, arg); + mylog("sendMessageCallback called, yay!"); + + v8::Local data = v8::Local::Cast(args.Data()); + + mylog("getting instance"); + NativeAkonoInstance *myInstance = (NativeAkonoInstance *) data->GetAlignedPointerFromInternalField(0); + + JNIEnv *env = myInstance->currentJniEnv; + + if (env == nullptr) { + mylog("FATAL: JNI env is nullptr"); + return; + } + + mylog("finding class"); + jclass clazz = env->FindClass("akono/AkonoJni"); + + if (clazz == nullptr) { + mylog("FATAL: class not found"); + return; + } + + mylog("creating strings"); + jstring jstr1 = env->NewStringUTF(*value); + + mylog("getting method"); + + jmethodID meth = env->GetMethodID(clazz, "internalOnModuleLoad", "(Ljava/lang/String;)Ljava/lang/String;"); + + if (meth == nullptr) { + mylog("FATAL: method not found"); + return; + } + + mylog("calling method"); + + jstring jresult = (jstring) env->CallObjectMethod(myInstance->currentJniThiz, meth, jstr1); + + JStringValue resultStringValue(env, jresult); + + printf("before creating string, res %s\n", *resultStringValue); + + // Create a string containing the JavaScript source code. + v8::Local rs = + v8::String::NewFromUtf8(isolate, *resultStringValue, + v8::NewStringType::kNormal) + .ToLocalChecked(); + + args.GetReturnValue().Set(rs); +} + + +static void getDataCallback(const v8::FunctionCallbackInfo &args) { + if (args.Length() < 1) return; + v8::Isolate *isolate = args.GetIsolate(); + v8::HandleScope scope(isolate); + v8::Local arg = args[0]; + v8::String::Utf8Value value(isolate, arg); + mylog("getDataCallback called"); + + v8::Local data = v8::Local::Cast(args.Data()); + mylog("getting instance"); + NativeAkonoInstance *myInstance = (NativeAkonoInstance *) data->GetAlignedPointerFromInternalField(0); + + JNIEnv *env = myInstance->currentJniEnv; + if (env == nullptr) { + mylog("FATAL: JNI env is nullptr"); + return; + } + + mylog("finding class"); + jclass clazz = env->FindClass("akono/AkonoJni"); + + if (clazz == nullptr) { + mylog("FATAL: class not found"); + return; + } + + mylog("creating strings"); + jstring jstr1 = env->NewStringUTF(*value); + + mylog("getting method"); + + jmethodID meth = env->GetMethodID(clazz, "internalOnGetData", "(Ljava/lang/String;)Ljava/lang/String;"); + + if (meth == nullptr) { + mylog("FATAL: method not found"); + return; + } + + mylog("calling method"); + + jstring jresult = (jstring) env->CallObjectMethod(myInstance->currentJniThiz, meth, jstr1); + + JStringValue resultStringValue(env, jresult); + + printf("before creating string, res %s\n", *resultStringValue); + + // Create a string containing the JavaScript source code. + v8::Local rs = + v8::String::NewFromUtf8(isolate, *resultStringValue, + v8::NewStringType::kNormal) + .ToLocalChecked(); + + args.GetReturnValue().Set(rs); +} + + extern "C" JNIEXPORT jobject JNICALL -Java_akono_AkonoJni_initNative(JNIEnv* env, jobject thiz) -{ +Java_akono_AkonoJni_initNative(JNIEnv *env, jobject thiz) { NativeAkonoInstance *myInstance = new NativeAkonoInstance(); return env->NewDirectByteBuffer(myInstance, 0); } extern "C" JNIEXPORT void JNICALL -Java_akono_AkonoJni_destroyNative(JNIEnv* env, jobject thiz, jobject buf) -{ +Java_akono_AkonoJni_destroyNative(JNIEnv *env, jobject thiz, jobject buf) { NativeAkonoInstance *myInstance = (NativeAkonoInstance *) env->GetDirectBufferAddress(buf); delete myInstance; } extern "C" JNIEXPORT jstring JNICALL -Java_akono_AkonoJni_evalJs(JNIEnv* env, jobject thiz, jstring sourceStr, jobject buf) -{ +Java_akono_AkonoJni_evalJs(JNIEnv *env, jobject thiz, jstring sourceStr, jobject buf) { NativeAkonoInstance *myInstance = (NativeAkonoInstance *) env->GetDirectBufferAddress(buf); return myInstance->evalJs(env, sourceStr); } + +extern "C" JNIEXPORT void JNICALL +Java_akono_AkonoJni_notifyNative(JNIEnv *env, jobject thiz, jobject buf) { + NativeAkonoInstance *myInstance = (NativeAkonoInstance *) env->GetDirectBufferAddress(buf); + uv_async_send(&myInstance->async_notify); +} + +extern "C" JNIEXPORT void JNICALL +Java_akono_AkonoJni_runNode(JNIEnv *env, jobject thiz, jobject buf) { + NativeAkonoInstance *myInstance = (NativeAkonoInstance *) env->GetDirectBufferAddress(buf); + myInstance->currentJniEnv = env; + myInstance->currentJniThiz = thiz; + myInstance->runNode(); +} + +extern "C" JNIEXPORT void JNICALL +Java_akono_AkonoJni_makeCallbackNative(JNIEnv *env, jobject thiz, jstring sourceStr, jobject buf) { + JStringValue jsv(env, sourceStr); + NativeAkonoInstance *myInstance = (NativeAkonoInstance *) env->GetDirectBufferAddress(buf); + myInstance->currentJniEnv = env; + myInstance->currentJniThiz = thiz; + return myInstance->makeCallback(*jsv); +} diff --git a/library/src/main/java/akono/AkoniJni.kt b/library/src/main/java/akono/AkoniJni.kt deleted file mode 100644 index 2210a7ef..00000000 --- a/library/src/main/java/akono/AkoniJni.kt +++ /dev/null @@ -1,39 +0,0 @@ -package akono - -import java.nio.ByteBuffer - -typealias AkonoNativePointer = ByteBuffer - -class AkonoJni { - external fun stringFromJNI(): String - - private external fun evalJs(source: String, p: AkonoNativePointer): String - - private external fun destroyNative(b: AkonoNativePointer) - private external fun initNative(nodeArgv: Array): AkonoNativePointer - - private external fun runNodeLoop(b: AkonoNativePointer) - - private external fun postMessageToNode(message: String, b: AkonoNativePointer) - - private external fun waitForMessageFromNode(b: AkonoNativePointer): String - - private var internalNativePointer: AkonoNativePointer - - fun evalJs(source: String): String = evalJs(source, internalNativePointer) - - @Override - protected fun finalize() { - destroyNative(internalNativePointer) - } - - constructor(vararg nodeArgv: String) { - internalNativePointer = initNative(nodeArgv) - } - - companion object { - init { - System.loadLibrary("akono-jni") - } - } -} \ No newline at end of file diff --git a/library/src/main/java/akono/AkonoJni.kt b/library/src/main/java/akono/AkonoJni.kt new file mode 100644 index 00000000..4a89a3f6 --- /dev/null +++ b/library/src/main/java/akono/AkonoJni.kt @@ -0,0 +1,208 @@ +package akono + +import android.util.Base64 +import android.util.Log +import org.json.JSONObject +import java.lang.Exception +import java.nio.ByteBuffer +import java.util.concurrent.CountDownLatch +import java.util.concurrent.LinkedBlockingDeque +import kotlin.concurrent.thread + +typealias AkonoNativePointer = ByteBuffer + +data class ModuleResult(val path: String, val contents: String) + +class AkonoJni(vararg nodeArgv: String) { + private var getDataHandler: GetDataHandler? = null + private var messageHandler: MessageHandler? = null + private var loadModuleHandler: LoadModuleHandler? = null + private val initializedLatch = CountDownLatch(1) + + private val workQueue = LinkedBlockingDeque<() -> Unit>() + + private external fun evalJs(source: String, p: AkonoNativePointer): String + private external fun runNode(p: AkonoNativePointer) + + private external fun makeCallbackNative(source: String, p: AkonoNativePointer) + + private external fun destroyNative(b: AkonoNativePointer) + private external fun initNative(nodeArgv: Array): AkonoNativePointer + private external fun notifyNative(b: AkonoNativePointer) + + private lateinit var internalNativePointer: AkonoNativePointer + + private val jniThread: Thread + + private var stopped = false + + /** + * Schedule a block do be executed in the node thread. + */ + private fun scheduleNodeThread(b: () -> Unit) { + initializedLatch.await() + workQueue.put(b) + notifyNative() + } + + /** + * Called by node/v8 from its thread. + */ + @Suppress("unused") + private fun internalOnNotify(type: String, payload: String) { + Log.i("myapp", "internalOnNotify called") + Log.i("myapp", "type: $type") + Log.i("myapp", "payload: $payload") + messageHandler?.handleMessage(payload) + } + + /** + * Called by node/v8 from its thread. + */ + @Suppress("unused") + private fun internalOnModuleLoad(loadInfoStr: String): String { + Log.i("myapp", "internalOnModuleLoad called") + Log.i("myapp", "loadInfoStr is $loadInfoStr") + try { + val loadInfo = JSONObject(loadInfoStr) + val request: String = loadInfo.getString("request") + Log.i("myapp", "request is $request") + val handler = loadModuleHandler + if (handler != null) { + val modResult = handler.loadModule(request, arrayOf()) ?: return "null" + val result = JSONObject() + result.put("path", modResult.path) + result.put("content", modResult.contents) + return result.toString() + } else { + Log.v("myapp", "no module load handler registered") + return "null" + } + } catch (e: Exception) { + Log.v("myapp", "exception during internalOnModuleLoad: $e") + return "null" + } + } + + /** + * Called by node/v8 from its thread. + */ + @Suppress("unused") + private fun internalOnGetData(what: String): String? { + Log.i("myapp", "internalOnGetData called for $what") + val data = getDataHandler?.handleGetData(what) ?: return null + return Base64.encodeToString(data, Base64.NO_WRAP) + } + + + fun notifyNative() { + initializedLatch.await() + notifyNative(internalNativePointer) + } + + /** + * Schedule Node.JS to be run. + */ + fun evalSimpleJs(source: String): String { + val latch = CountDownLatch(1) + var result: String? = null + scheduleNodeThread { + result = evalJs(source, internalNativePointer) + latch.countDown() + } + latch.await() + return result ?: throw Exception("invariant failed") + } + + fun evalNodeCode(source: String) { + scheduleNodeThread { + makeCallbackNative(source, internalNativePointer) + } + } + + /** + * Send a message to node, calling global.__akono_onMessage. + */ + fun sendMessage(message: String) { + val encoded = Base64.encodeToString(message.toByteArray(), Base64.NO_WRAP) + val source = """ + if (global.__akono_onMessage) { + const msg = (new Buffer('$encoded', 'base64')).toString('ascii'); + global.__akono_onMessage(msg); + } else { + console.log("WARN: no __akono_onMessage defined"); + } + """.trimIndent() + evalNodeCode(source) + } + + /** + * + */ + fun waitStopped(): Unit { + Log.i("myapp", "waiting for stop") + scheduleNodeThread { + stopped = true + } + jniThread.join() + return + } + + /** + * Register a message handler that is called when the JavaScript code + * running in [runNodeJs] calls __akono_sendMessage + * + * Does not block. + */ + fun setMessageHandler(handler: MessageHandler) { + this.messageHandler = handler + } + + fun setLoadModuleHandler(handler: LoadModuleHandler) { + this.loadModuleHandler = handler + } + + fun setGetDataHandler(handler: GetDataHandler) { + this.getDataHandler = handler + } + + @Override + protected fun finalize() { + destroyNative(internalNativePointer) + } + + init { + jniThread = thread { + internalNativePointer = initNative(nodeArgv) + initializedLatch.countDown() + while (true) { + runNode(internalNativePointer) + while (true) { + val w = workQueue.poll() ?: break + w() + } + if (stopped) { + break + } + } + } + } + + companion object { + init { + System.loadLibrary("akono-jni") + } + } + + interface MessageHandler { + fun handleMessage(message: String) + } + + interface LoadModuleHandler { + fun loadModule(name: String, paths: Array): ModuleResult? + } + + interface GetDataHandler { + fun handleGetData(what: String): ByteArray? + } +} \ No newline at end of file -- cgit v1.2.3