diff --git a/examples/pxScene2d/src/CMakeLists.txt b/examples/pxScene2d/src/CMakeLists.txt index 9c43aac5a4..e5aeb6f080 100644 --- a/examples/pxScene2d/src/CMakeLists.txt +++ b/examples/pxScene2d/src/CMakeLists.txt @@ -462,6 +462,7 @@ if (BUILD_PXSCENE_APP) install(TARGETS pxscene_app RUNTIME DESTINATION . COMPONENT pxscene) install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/browser DESTINATION . COMPONENT pxscene) install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/node_modules DESTINATION . COMPONENT pxscene) + install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/v8_modules DESTINATION . COMPONENT pxscene) if (SUPPORT_DUKTAPE) install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/rcvrcore/" DESTINATION rcvrcore COMPONENT pxscene) install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/duk_modules/" DESTINATION duk_modules COMPONENT pxscene) diff --git a/examples/pxScene2d/src/test_module_loading.js b/examples/pxScene2d/src/test_module_loading.js new file mode 100644 index 0000000000..765c316b8a --- /dev/null +++ b/examples/pxScene2d/src/test_module_loading.js @@ -0,0 +1,4 @@ +var mod = require("test_module"); +mod.info("hi"); +var mod2 = require("test_module"); +mod2.info("hi2"); diff --git a/examples/pxScene2d/src/test_promises.js b/examples/pxScene2d/src/test_promises.js new file mode 100644 index 0000000000..082005d904 --- /dev/null +++ b/examples/pxScene2d/src/test_promises.js @@ -0,0 +1,43 @@ + + +var promise1 = Promise.resolve(3); +var promise2 = _testPromiseResolvedReturnFunc(); +var promise3 = _testPromiseReturnFunc(); + +Promise.all([promise1, promise2, promise3]).then(function (values) { + print("OK"); +}); + +var promise4 = _testPromiseRejectedReturnFunc(); + +promise4.then(function (val) { + print("resolved2"); +}).catch(function (val) { + print("rejected2"); +}); + +var promise5 = _testPromiseReturnRejectFunc(); + +promise5.then(function (val) { + print("resolved3"); +}).catch(function (val) { + print("rejected3"); +}); + +var promise6 = _testPromiseReturnFunc(); + +new Promise((resolve, reject) => { + promise6.then(function (val) { + resolve(val); + }).catch(function (val) { + reject(val); + }); +}).then(function (val) { + print("resolved4"); +}, +function (val) { + print("rejected4"); +} +); + + diff --git a/examples/pxScene2d/src/v8_modules/test_module.js b/examples/pxScene2d/src/v8_modules/test_module.js new file mode 100644 index 0000000000..c036f7a9fc --- /dev/null +++ b/examples/pxScene2d/src/v8_modules/test_module.js @@ -0,0 +1,8 @@ + +function info(msg) { + print(msg); +} + +module.exports = { + info: info, +} diff --git a/src/rtScriptV8/rtScriptV8.cpp b/src/rtScriptV8/rtScriptV8.cpp index 61676e1a8f..1964f123d2 100755 --- a/src/rtScriptV8/rtScriptV8.cpp +++ b/src/rtScriptV8/rtScriptV8.cpp @@ -16,6 +16,12 @@ */ +#define RT_V8_TEST_BINDINGS + +#ifdef RT_V8_TEST_BINDINGS +#pragma optimize("", off) +#endif + #if defined WIN32 #include #include @@ -27,6 +33,8 @@ #include #include +#include + #include #include @@ -52,10 +60,10 @@ #include "rtValue.h" #include "rtAtomic.h" #include "rtScript.h" +#include "rtPromise.h" #include "rtFunctionWrapper.h" #include "rtObjectWrapper.h" - #include #include @@ -84,8 +92,7 @@ class V8ArrayBufferAllocator : public v8::ArrayBuffer::Allocator { virtual void* Allocate(size_t size) { void *ret = AllocateUninitialized(size); - if (!ret) - { + if (!ret) { return NULL; } memset(ret, 0, size); @@ -138,9 +145,9 @@ typedef rtRef rtV8ContextRef; class rtV8Context: rtIScriptContext // V8 { public: - rtV8Context(v8::Isolate *isolate, v8::Platform* platform); + rtV8Context(v8::Isolate *isolate, v8::Platform* platform, uv_loop_t *loop); #ifdef USE_CONTEXTIFY_CLONES - rtV8Context(v8::Isolate *isolate, rtV8ContextRef clone_me); + rtV8Context(v8::Isolate *isolate, rtV8ContextRef clone_me, uv_loop_t *loop); #endif virtual ~rtV8Context(); @@ -160,10 +167,20 @@ class rtV8Context: rtIScriptContext // V8 unsigned long Release(); + Local loadV8Module(const rtString &name); + +private: + void addMethod(const char *name, v8::FunctionCallback callback); + void setupModuleLoading(); + private: v8::Isolate *mIsolate; v8::Platform *mPlatform; v8::Persistent mContext; + uv_loop_t *mUvLoop; + + std::map *> mLoadedModuleCache; + int mRefCount; const char *js_file; @@ -214,6 +231,7 @@ class rtScriptV8: public rtIScript v8::Isolate *mIsolate; v8::Persistent mContext; v8::Platform *mPlatform; + uv_loop_t *mUvLoop; bool mV8Initialized; @@ -223,7 +241,6 @@ class rtScriptV8: public rtIScript using namespace v8; -#define RT_V8_TEST_BINDINGS #ifdef RT_V8_TEST_BINDINGS rtError rtTestArrayReturnBinding(int numArgs, const rtValue* args, rtValue* result, void* context); @@ -305,14 +322,91 @@ rtError rtTestObjectReturnBinding(int numArgs, const rtValue* args, rtValue* res return RT_OK; } +rtError rtTestPromiseReturnResolvedBinding(int numArgs, const rtValue* args, rtValue* result, void* context) +{ + rtPromise* ret = new rtPromise(); + + ret->resolve(2); + + *result = ret; + + return RT_OK; +} + +rtError rtTestPromiseReturnRejectedBinding(int numArgs, const rtValue* args, rtValue* result, void* context) +{ + rtPromise* ret = new rtPromise(); + + ret->reject(3); + + *result = ret; + + return RT_OK; +} + +static void testPromiseDoWork(uv_work_t* req) +{ + +} + +static void testPromiseDoResolveCallback(uv_work_t* req, int status) +{ + rtPromise *promise = (rtPromise *)req->data; + promise->resolve(3); + promise->Release(); +} + +static void testPromiseDoRejectCallback(uv_work_t* req, int status) +{ + rtPromise *promise = (rtPromise *)req->data; + promise->reject(promise); + promise->Release(); +} + +rtError rtTestPromiseReturnBinding(int numArgs, const rtValue* args, rtValue* result, void* context) +{ + rtPromise* ret = new rtPromise(); + + ret->AddRef(); + + uv_work_t *work = new uv_work_t(); + work->data = (void*)ret; + + uv_queue_work(uv_default_loop(), work, &testPromiseDoWork, &testPromiseDoResolveCallback); + + *result = ret; + + return RT_OK; +} + +rtError rtTestPromiseReturnRejectBinding(int numArgs, const rtValue* args, rtValue* result, void* context) +{ + rtPromise* ret = new rtPromise(); + + ret->AddRef(); + + uv_work_t *work = new uv_work_t(); + work->data = (void*)ret; + + uv_queue_work(uv_default_loop(), work, &testPromiseDoWork, &testPromiseDoRejectCallback); + + *result = ret; + + return RT_OK; +} + rtRef g_testArrayReturnFunc; rtRef g_testMapReturnFunc; rtRef g_testObjectReturnFunc; +rtRef g_testPromiseResolvedReturnFunc; +rtRef g_testPromiseReturnFunc; +rtRef g_testPromiseRejectedReturnFunc; +rtRef g_testPromiseReturnRejectFunc; #endif -rtV8Context::rtV8Context(Isolate *isolate, Platform *platform) : - mIsolate(isolate), mRefCount(0), mPlatform(platform) +rtV8Context::rtV8Context(Isolate *isolate, Platform *platform, uv_loop_t *loop) : + mIsolate(isolate), mRefCount(0), mPlatform(platform), mUvLoop(loop) { rtLogInfo(__FUNCTION__); Locker locker(mIsolate); @@ -331,19 +425,31 @@ rtV8Context::rtV8Context(Isolate *isolate, Platform *platform) : rtObjectWrapper::exportPrototype(mIsolate, global); rtFunctionWrapper::exportPrototype(mIsolate, global); + setupModuleLoading(); + v8::platform::PumpMessageLoop(mPlatform, mIsolate); +#ifdef RT_V8_TEST_BINDINGS g_testArrayReturnFunc = new rtFunctionCallback(rtTestArrayReturnBinding); g_testMapReturnFunc = new rtFunctionCallback(rtTestMapReturnBinding); g_testObjectReturnFunc = new rtFunctionCallback(rtTestObjectReturnBinding); + g_testPromiseResolvedReturnFunc = new rtFunctionCallback(rtTestPromiseReturnResolvedBinding); + g_testPromiseReturnFunc = new rtFunctionCallback(rtTestPromiseReturnBinding); + g_testPromiseRejectedReturnFunc = new rtFunctionCallback(rtTestPromiseReturnRejectedBinding); + g_testPromiseReturnRejectFunc = new rtFunctionCallback(rtTestPromiseReturnRejectBinding); add("_testArrayReturnFunc", g_testArrayReturnFunc.getPtr()); add("_testMapReturnFunc", g_testMapReturnFunc.getPtr()); add("_testObjectReturnFunc", g_testObjectReturnFunc.getPtr()); + add("_testPromiseResolvedReturnFunc", g_testPromiseResolvedReturnFunc.getPtr()); + add("_testPromiseReturnFunc", g_testPromiseReturnFunc.getPtr()); + add("_testPromiseRejectedReturnFunc", g_testPromiseRejectedReturnFunc.getPtr()); + add("_testPromiseReturnRejectFunc", g_testPromiseReturnRejectFunc.getPtr()); +#endif } #ifdef USE_CONTEXTIFY_CLONES -rtV8Context::rtV8Context(Isolate *isolate, rtV8ContextRef clone_me) +rtV8Context::rtV8Context(Isolate *isolate, rtV8ContextRef clone_me, uv_loop_t *loop) { assert(0); } @@ -351,23 +457,184 @@ rtV8Context::rtV8Context(Isolate *isolate, rtV8ContextRef clone_me) rtV8Context::~rtV8Context() { + if (!mLoadedModuleCache.empty()) { + for (auto it = mLoadedModuleCache.begin(); it != mLoadedModuleCache.end(); ++it) { + delete (*it).second; + } + } +} + + +static bool bloatFileFromPath(uv_loop_t *loop, const rtString &path, rtString &data) +{ + uv_fs_t req; + int fd = 0; + uint64_t size; + char* chunk; + uv_buf_t buf; + + if (uv_fs_open(loop, &req, path.cString(), 0, 0644, NULL) < 0) { + goto fail; + } + uv_fs_req_cleanup(&req); + fd = req.result; + if (uv_fs_fstat(loop, &req, fd, NULL) < 0) { + goto fail; + } + uv_fs_req_cleanup(&req); + size = req.statbuf.st_size; + chunk = (char*)malloc(size); + buf = uv_buf_init(chunk, static_cast(size)); + if (uv_fs_read(loop, &req, fd, &buf, 1, 0, NULL) < 0) { + free(chunk); + goto fail; + } + uv_fs_req_cleanup(&req); + data = rtString(chunk, size); + free(chunk); + return true; + +fail: + uv_fs_req_cleanup(&req); + if (fd) { + uv_fs_close(loop, &req, fd, NULL); + } + uv_fs_req_cleanup(&req); + return false; +} + +static rtString getTryCatchResult(Local context, const TryCatch &tryCatch) +{ + if (tryCatch.HasCaught()) { + MaybeLocal val = tryCatch.StackTrace(context); + if (val.IsEmpty()) { + return rtString(); + } + Local ret = val.ToLocalChecked(); + String::Utf8Value trace(ret); + return rtString(*trace); + } + return rtString(); +} + +Local rtV8Context::loadV8Module(const rtString &name) +{ + rtString path = name; + if (!name.endsWith(".js")) { + path.append(".js"); + } + + rtString contents; + if (!bloatFileFromPath(mUvLoop, path, contents)) { + rtString path1("v8_modules/"); + path1.append(path.cString()); + path = path1; + if (!bloatFileFromPath(mUvLoop, path, contents)) { + rtLogWarn("module '%s' not found", name.cString()); + return Local(); + } + } + + Locker locker(mIsolate); + Isolate::Scope isolate_scope(mIsolate); + HandleScope handle_scope(mIsolate); + + Local localContext = PersistentToLocal(mIsolate, mContext); + Context::Scope context_scope(localContext); + + // check in cache + if (mLoadedModuleCache.find(path) != mLoadedModuleCache.end()) { + Persistent *loadedModule = mLoadedModuleCache[path]; + return PersistentToLocal(mIsolate, *loadedModule); + } + + rtString contents1 = "(function(){var module=this,exports=this.exports;"; + contents1.append(contents.cString()); + contents1.append(" return this.exports; })"); + + v8::Local source = + v8::String::NewFromUtf8(mIsolate, contents1.cString(), v8::NewStringType::kNormal).ToLocalChecked(); + + TryCatch tryCatch(mIsolate); + + v8::MaybeLocal script = v8::Script::Compile(localContext, source); + + if (script.IsEmpty()) { + rtLogWarn("module '%s' compilation failed (%s)", name.cString(), getTryCatchResult(localContext, tryCatch).cString()); + return Local(); + } + + v8::MaybeLocal result = script.ToLocalChecked()->Run(localContext); + + if (result.IsEmpty() || !result.ToLocalChecked()->IsFunction()) { + rtLogWarn("module '%s' unexpected result (%s)", name.cString(), getTryCatchResult(localContext, tryCatch).cString()); + return Local(); + } + + v8::Function *func = v8::Function::Cast(*result.ToLocalChecked()); + MaybeLocal ret = func->Call(localContext, result.ToLocalChecked(), 0, NULL); + + if (ret.IsEmpty()) { + rtLogWarn("module '%s' unexpected call result (%s)", name.cString(), getTryCatchResult(localContext, tryCatch).cString()); + return Local(); + } + + Local toRet = ret.ToLocalChecked(); + Persistent *toRetPersistent = new Persistent(mIsolate, toRet); + + // store in cache + mLoadedModuleCache[path] = toRetPersistent; + + return toRet; +} + + +static void requireCallback(const v8::FunctionCallbackInfo& args) +{ + assert(args.Data()->isExternal()); + v8::External *val = v8::External::Cast(*args.Data()); + assert(val != NULL); + rtV8Context *ctx = (rtV8Context *)val->Value(); + assert(args.Length() == 1); + assert(args[0].IsString()); + + rtString moduleName = toString(args[0]->ToString()); + args.GetReturnValue().Set(ctx->loadV8Module(moduleName)); +} + + +void rtV8Context::addMethod(const char *name, v8::FunctionCallback callback) +{ + Locker locker(mIsolate); + Isolate::Scope isolate_scope(mIsolate); + HandleScope handle_scope(mIsolate); + + Local fTemplate = v8::FunctionTemplate::New(mIsolate, callback, v8::External::New(mIsolate, (void*)this)); + Local localContext = PersistentToLocal(mIsolate, mContext); + Context::Scope context_scope(localContext); + localContext->Global()->Set( + String::NewFromUtf8(mIsolate, name, NewStringType::kNormal).ToLocalChecked(), + fTemplate->GetFunction() + ); +} + +void rtV8Context::setupModuleLoading() +{ + addMethod("require", &requireCallback); } rtError rtV8Context::add(const char *name, const rtValue& val) { - if (name == NULL) - { + if (name == NULL) { rtLogDebug(" rtNodeContext::add() - no symbolic name for rtValue"); return RT_FAIL; } - else if (this->has(name)) - { + else if (this->has(name)) { rtLogDebug(" rtNodeContext::add() - ALREADY HAS '%s' ... over-writing.", name); // return; // Allow for "Null"-ing erasure. } - if (val.isEmpty()) - { + if (val.isEmpty()) { rtLogDebug(" rtNodeContext::add() - rtValue is empty"); return RT_FAIL; } @@ -387,8 +654,7 @@ rtError rtV8Context::add(const char *name, const rtValue& val) rtValue rtV8Context::get(const char *name) { - if (name == NULL) - { + if (name == NULL) { rtLogError(" rtNodeContext::get() - no symbolic name for rtValue"); return rtValue(); } @@ -406,13 +672,11 @@ rtValue rtV8Context::get(const char *name) // Get the object Local object = global->Get(String::NewFromUtf8(mIsolate, name)); - if (object->IsUndefined() || object->IsNull()) - { + if (object->IsUndefined() || object->IsNull()) { rtLogError("FATAL: '%s' is Undefined ", name); return rtValue(); } - else - { + else { rtWrapperError error; // TODO - handle error return v82rt(local_context, object, &error); } @@ -420,8 +684,7 @@ rtValue rtV8Context::get(const char *name) bool rtV8Context::has(const char *name) { - if (name == NULL) - { + if (name == NULL) { rtLogError(" rtNodeContext::has() - no symbolic name for rtValue"); return false; } @@ -439,8 +702,7 @@ bool rtV8Context::has(const char *name) TryCatch try_catch(mIsolate); Handle value = global->Get(String::NewFromUtf8(mIsolate, name)); - if (try_catch.HasCaught()) - { + if (try_catch.HasCaught()) { rtLogError("\n ## has() - HasCaught() ... ERROR"); return false; } @@ -454,8 +716,7 @@ bool rtV8Context::has(const char *name) rtError rtV8Context::runScript(const char *script, rtValue* retVal /*= NULL*/, const char *args /*= NULL*/) { rtLogInfo(__FUNCTION__); - if (!script || strlen(script) == 0) - { + if (!script || strlen(script) == 0) { rtLogError(" %s ... no script given.", __PRETTY_FUNCTION__); return RT_FAIL; @@ -479,16 +740,14 @@ rtError rtV8Context::runScript(const char *script, rtValue* retVal /*= NULL*/, c // Run the script to get the result. Local result = run_script->Run(); // !CLF TODO: TEST FOR MT - if (tryCatch.HasCaught()) - { + if (tryCatch.HasCaught()) { String::Utf8Value trace(tryCatch.StackTrace()); rtLogWarn("%s", *trace); return RT_FAIL; } - if (retVal) - { + if (retVal) { // Return val rtWrapperError error; *retVal = v82rt(local_context, result, &error); @@ -518,10 +777,8 @@ static std::string v8ReadFile(const char *file) rtError rtV8Context::runFile(const char *file, rtValue* retVal /*= NULL*/, const char *args /*= NULL*/) { - if (file == NULL) - { + if (file == NULL) { rtLogError(" %s ... no script given.", __PRETTY_FUNCTION__); - return RT_FAIL; } @@ -529,20 +786,24 @@ rtError rtV8Context::runFile(const char *file, rtValue* retVal /*= NULL*/, const js_file = file; js_script = v8ReadFile(file); - if (js_script.empty()) // load error - { + if (js_script.empty()) { // load error rtLogError(" %s ... load error / not found.", __PRETTY_FUNCTION__); - return RT_FAIL; } - return runScript(js_script.c_str(), retVal, args); + rtError ret = runScript(js_script.c_str(), retVal, args); + if (ret == RT_FAIL) { + rtLogError("runFile v8 script '%s' failed", js_file); + } + + return ret; } rtScriptV8::rtScriptV8():mRefCount(0), mV8Initialized(false) { mIsolate = NULL; mPlatform = NULL; + mUvLoop = NULL; init(); } @@ -554,8 +815,7 @@ rtScriptV8::~rtScriptV8() unsigned long rtScriptV8::Release() { long l = rtAtomicDec(&mRefCount); - if (l == 0) - { + if (l == 0) { delete this; } return l; @@ -565,9 +825,9 @@ rtError rtScriptV8::init() { rtLogInfo(__FUNCTION__); - if (mV8Initialized == false) - { + if (mV8Initialized == false) { V8::InitializeICU(); + mUvLoop = uv_default_loop(); Platform* platform = platform::CreateDefaultPlatform(); mPlatform = platform; V8::InitializePlatform(platform); @@ -597,11 +857,9 @@ rtError rtScriptV8::init() rtError rtScriptV8::term() { - if (mV8Initialized == true) - { + if (mV8Initialized == true) { V8::ShutdownPlatform(); - if (mPlatform) - { + if (mPlatform) { delete mPlatform; mPlatform = NULL; } @@ -622,7 +880,7 @@ rtError rtScriptV8::createContext(const char *lang, rtScriptContextRef& ctx) rtV8ContextRef rtScriptV8::createContext() { - return new rtV8Context(mIsolate, mPlatform); + return new rtV8Context(mIsolate, mPlatform, mUvLoop); } rtError rtScriptV8::pump() @@ -633,7 +891,7 @@ rtError rtScriptV8::pump() v8::platform::PumpMessageLoop(mPlatform, mIsolate); mIsolate->RunMicrotasks(); - uv_run(uv_default_loop(), UV_RUN_NOWAIT); + uv_run(mUvLoop, UV_RUN_NOWAIT); return RT_OK; } @@ -660,8 +918,7 @@ void* rtScriptV8::getParameter(rtString param) unsigned long rtV8Context::Release() { long l = rtAtomicDec(&mRefCount); - if (l == 0) - { + if (l == 0) { delete this; } return l;