diff --git a/Libraries/Utilities/MessageQueue.js b/Libraries/Utilities/MessageQueue.js index 20cc0eeea..d34bda8b9 100644 --- a/Libraries/Utilities/MessageQueue.js +++ b/Libraries/Utilities/MessageQueue.js @@ -25,6 +25,7 @@ let stringifySafe = require('stringifySafe'); let MODULE_IDS = 0; let METHOD_IDS = 1; let PARAMS = 2; +let MIN_TIME_BETWEEN_FLUSHES_MS = 5; let SPY_MODE = false; @@ -53,6 +54,7 @@ class MessageQueue { this._methodTable = {}; this._callbacks = []; this._callbackID = 0; + this._lastFlush = 0; [ 'invokeCallbackAndReturnFlushedQueue', @@ -125,6 +127,14 @@ class MessageQueue { this._queue[MODULE_IDS].push(module); this._queue[METHOD_IDS].push(method); this._queue[PARAMS].push(params); + + var now = new Date().getTime(); + if (global.nativeFlushQueueImmediate && + now - this._lastFlush >= MIN_TIME_BETWEEN_FLUSHES_MS) { + global.nativeFlushQueueImmediate(this._queue); + this._queue = [[],[],[]]; + this._lastFlush = now; + } if (__DEV__ && SPY_MODE && isFinite(module)) { console.log('JS->N : ' + this._remoteModuleTable[module] + '.' + this._remoteMethodTable[module][method] + '(' + JSON.stringify(params) + ')'); @@ -133,6 +143,7 @@ class MessageQueue { __callFunction(module, method, args) { BridgeProfiling.profile(() => `${module}.${method}(${stringifySafe(args)})`); + this._lastFlush = new Date().getTime(); if (isFinite(module)) { method = this._methodTable[module][method]; module = this._moduleTable[module]; @@ -148,6 +159,7 @@ class MessageQueue { __invokeCallback(cbID, args) { BridgeProfiling.profile( () => `MessageQueue.invokeCallback(${cbID}, ${stringifySafe(args)})`); + this._lastFlush = new Date().getTime(); let callback = this._callbacks[cbID]; if (!callback || __DEV__) { let debug = this._debugInfo[cbID >> 1]; diff --git a/ReactAndroid/src/main/jni/react/Bridge.cpp b/ReactAndroid/src/main/jni/react/Bridge.cpp index ab868dea1..e3643e6cf 100644 --- a/ReactAndroid/src/main/jni/react/Bridge.cpp +++ b/ReactAndroid/src/main/jni/react/Bridge.cpp @@ -16,9 +16,12 @@ namespace react { class JSThreadState { public: JSThreadState(const RefPtr& jsExecutorFactory, Bridge::Callback&& callback) : - m_jsExecutor(jsExecutorFactory->createJSExecutor()), m_callback(callback) - {} + { + m_jsExecutor = jsExecutorFactory->createJSExecutor([this, callback] (std::string queueJSON) { + m_callback(parseMethodCalls(queueJSON), false /* = isEndOfBatch */); + }); + } void executeApplicationScript(const std::string& script, const std::string& sourceURL) { m_jsExecutor->executeApplicationScript(script, sourceURL); @@ -29,7 +32,7 @@ public: const std::string& methodName, const std::vector& arguments) { auto returnedJSON = m_jsExecutor->executeJSCall(moduleName, methodName, arguments); - m_callback(parseMethodCalls(returnedJSON)); + m_callback(parseMethodCalls(returnedJSON), true /* = isEndOfBatch */); } void setGlobalVariable(const std::string& propName, const std::string& jsonValue) { @@ -58,11 +61,11 @@ Bridge::Bridge(const RefPtr& jsExecutorFactory, Callback call m_destroyed(std::shared_ptr(new bool(false))) { auto destroyed = m_destroyed; - auto proxyCallback = [this, destroyed] (std::vector calls) { + auto proxyCallback = [this, destroyed] (std::vector calls, bool isEndOfBatch) { if (*destroyed) { return; } - m_callback(std::move(calls)); + m_callback(std::move(calls), isEndOfBatch); }; m_threadState.reset(new JSThreadState(jsExecutorFactory, std::move(proxyCallback))); } diff --git a/ReactAndroid/src/main/jni/react/Bridge.h b/ReactAndroid/src/main/jni/react/Bridge.h index 7a9f80d32..9f81f529d 100644 --- a/ReactAndroid/src/main/jni/react/Bridge.h +++ b/ReactAndroid/src/main/jni/react/Bridge.h @@ -24,7 +24,7 @@ namespace react { class JSThreadState; class Bridge : public Countable { public: - typedef std::function)> Callback; + typedef std::function, bool isEndOfBatch)> Callback; Bridge(const RefPtr& jsExecutorFactory, Callback callback); virtual ~Bridge(); diff --git a/ReactAndroid/src/main/jni/react/Executor.h b/ReactAndroid/src/main/jni/react/Executor.h index 428a3761d..1f4f54cc5 100644 --- a/ReactAndroid/src/main/jni/react/Executor.h +++ b/ReactAndroid/src/main/jni/react/Executor.h @@ -18,9 +18,11 @@ namespace react { class JSExecutor; +typedef std::function FlushImmediateCallback; + class JSExecutorFactory : public Countable { public: - virtual std::unique_ptr createJSExecutor() = 0; + virtual std::unique_ptr createJSExecutor(FlushImmediateCallback cb) = 0; virtual ~JSExecutorFactory() {}; }; diff --git a/ReactAndroid/src/main/jni/react/JSCExecutor.cpp b/ReactAndroid/src/main/jni/react/JSCExecutor.cpp index f6360f0a5..c58d42dbf 100644 --- a/ReactAndroid/src/main/jni/react/JSCExecutor.cpp +++ b/ReactAndroid/src/main/jni/react/JSCExecutor.cpp @@ -4,6 +4,7 @@ #include #include +#include #include #include #include @@ -26,12 +27,21 @@ using fbsystrace::FbSystraceSection; namespace facebook { namespace react { +static std::unordered_map s_globalContextRefToJSCExecutor; +static JSValueRef nativeFlushQueueImmediate( + JSContextRef ctx, + JSObjectRef function, + JSObjectRef thisObject, + size_t argumentCount, + const JSValueRef arguments[], + JSValueRef *exception); static JSValueRef nativeLoggingHook( JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, - const JSValueRef arguments[], JSValueRef *exception); + const JSValueRef arguments[], + JSValueRef *exception); static JSValueRef evaluateScriptWithJSC( JSGlobalContextRef ctx, @@ -47,12 +57,15 @@ static JSValueRef evaluateScriptWithJSC( return result; } -std::unique_ptr JSCExecutorFactory::createJSExecutor() { - return std::unique_ptr(new JSCExecutor()); +std::unique_ptr JSCExecutorFactory::createJSExecutor(FlushImmediateCallback cb) { + return std::unique_ptr(new JSCExecutor(cb)); } -JSCExecutor::JSCExecutor() { +JSCExecutor::JSCExecutor(FlushImmediateCallback cb) : + m_flushImmediateCallback(cb) { m_context = JSGlobalContextCreateInGroup(nullptr, nullptr); + s_globalContextRefToJSCExecutor[m_context] = this; + installGlobalFunction(m_context, "nativeFlushQueueImmediate", nativeFlushQueueImmediate); installGlobalFunction(m_context, "nativeLoggingHook", nativeLoggingHook); #ifdef WITH_JSC_EXTRA_TRACING addNativeTracingHooks(m_context); @@ -62,6 +75,7 @@ JSCExecutor::JSCExecutor() { } JSCExecutor::~JSCExecutor() { + s_globalContextRefToJSCExecutor.erase(m_context); JSGlobalContextRelease(m_context); } @@ -132,6 +146,42 @@ void JSCExecutor::stopProfiler(const std::string &titleString, const std::string #endif } +void JSCExecutor::flushQueueImmediate(std::string queueJSON) { + m_flushImmediateCallback(queueJSON); +} + +static JSValueRef createErrorString(JSContextRef ctx, const char *msg) { + return JSValueMakeString(ctx, String(msg)); +} + +static JSValueRef nativeFlushQueueImmediate( + JSContextRef ctx, + JSObjectRef function, + JSObjectRef thisObject, + size_t argumentCount, + const JSValueRef arguments[], + JSValueRef *exception) { + if (argumentCount != 1) { + *exception = createErrorString(ctx, "Got wrong number of args"); + return JSValueMakeUndefined(ctx); + } + + JSCExecutor *executor; + try { + executor = s_globalContextRefToJSCExecutor.at(JSContextGetGlobalContext(ctx)); + } catch (std::out_of_range& e) { + *exception = createErrorString(ctx, "Global JS context didn't map to a valid executor"); + return JSValueMakeUndefined(ctx); + } + + JSValueProtect(ctx, arguments[0]); + std::string resStr = Value(ctx, arguments[0]).toJSONString(); + + executor->flushQueueImmediate(resStr); + + return JSValueMakeUndefined(ctx); +} + static JSValueRef nativeLoggingHook( JSContextRef ctx, JSObjectRef function, diff --git a/ReactAndroid/src/main/jni/react/JSCExecutor.h b/ReactAndroid/src/main/jni/react/JSCExecutor.h index 9cb5a6a08..849148bfa 100644 --- a/ReactAndroid/src/main/jni/react/JSCExecutor.h +++ b/ReactAndroid/src/main/jni/react/JSCExecutor.h @@ -11,12 +11,12 @@ namespace react { class JSCExecutorFactory : public JSExecutorFactory { public: - virtual std::unique_ptr createJSExecutor() override; + virtual std::unique_ptr createJSExecutor(FlushImmediateCallback cb) override; }; class JSCExecutor : public JSExecutor { public: - JSCExecutor(); + explicit JSCExecutor(FlushImmediateCallback flushImmediateCallback); ~JSCExecutor() override; virtual void executeApplicationScript( const std::string& script, @@ -31,11 +31,13 @@ public: virtual bool supportsProfiling() override; virtual void startProfiler(const std::string &titleString) override; virtual void stopProfiler(const std::string &titleString, const std::string &filename) override; - + + void flushQueueImmediate(std::string queueJSON); void installNativeHook(const char *name, JSObjectCallAsFunctionCallback callback); private: JSGlobalContextRef m_context; + FlushImmediateCallback m_flushImmediateCallback; }; } } diff --git a/ReactAndroid/src/main/jni/react/jni/OnLoad.cpp b/ReactAndroid/src/main/jni/react/jni/OnLoad.cpp index 9e37dfe47..cd537c7dd 100644 --- a/ReactAndroid/src/main/jni/react/jni/OnLoad.cpp +++ b/ReactAndroid/src/main/jni/react/jni/OnLoad.cpp @@ -567,7 +567,8 @@ static void signalBatchComplete(JNIEnv* env, jobject callback) { static void dispatchCallbacksToJava(const RefPtr& weakCallback, const RefPtr& weakCallbackQueueThread, - std::vector&& calls) { + std::vector&& calls, + bool isEndOfBatch) { auto env = Environment::current(); if (env->ExceptionCheck()) { FBLOGW("Dropped calls because of pending exception"); @@ -580,7 +581,7 @@ static void dispatchCallbacksToJava(const RefPtr& weakCallback, return; } - auto runnableFunction = std::bind([weakCallback] (std::vector& calls) { + auto runnableFunction = std::bind([weakCallback, isEndOfBatch] (std::vector& calls) { auto env = Environment::current(); if (env->ExceptionCheck()) { FBLOGW("Dropped calls because of pending exception"); @@ -594,7 +595,9 @@ static void dispatchCallbacksToJava(const RefPtr& weakCallback, return; } } - signalBatchComplete(env, callback); + if (isEndOfBatch) { + signalBatchComplete(env, callback); + } } }, std::move(calls)); @@ -606,8 +609,8 @@ static void create(JNIEnv* env, jobject obj, jobject executor, jobject callback, jobject callbackQueueThread) { auto weakCallback = createNew(callback); auto weakCallbackQueueThread = createNew(callbackQueueThread); - auto bridgeCallback = [weakCallback, weakCallbackQueueThread] (std::vector calls) { - dispatchCallbacksToJava(weakCallback, weakCallbackQueueThread, std::move(calls)); + auto bridgeCallback = [weakCallback, weakCallbackQueueThread] (std::vector calls, bool isEndOfBatch) { + dispatchCallbacksToJava(weakCallback, weakCallbackQueueThread, std::move(calls), isEndOfBatch); }; auto nativeExecutorFactory = extractRefPtr(env, executor); auto bridge = createNew(nativeExecutorFactory, bridgeCallback); diff --git a/ReactAndroid/src/main/jni/react/jni/ProxyExecutor.cpp b/ReactAndroid/src/main/jni/react/jni/ProxyExecutor.cpp index 426b71d45..129d737fc 100644 --- a/ReactAndroid/src/main/jni/react/jni/ProxyExecutor.cpp +++ b/ReactAndroid/src/main/jni/react/jni/ProxyExecutor.cpp @@ -12,7 +12,7 @@ namespace react { const auto EXECUTOR_BASECLASS = "com/facebook/react/bridge/ProxyJavaScriptExecutor$JavaJSExecutor"; -std::unique_ptr ProxyExecutorOneTimeFactory::createJSExecutor() { +std::unique_ptr ProxyExecutorOneTimeFactory::createJSExecutor(FlushImmediateCallback ignoredCallback) { FBASSERTMSGF( m_executor.get() != nullptr, "Proxy instance should not be null. Did you attempt to call createJSExecutor() on this factory " diff --git a/ReactAndroid/src/main/jni/react/jni/ProxyExecutor.h b/ReactAndroid/src/main/jni/react/jni/ProxyExecutor.h index 4bdc74714..805eda532 100644 --- a/ReactAndroid/src/main/jni/react/jni/ProxyExecutor.h +++ b/ReactAndroid/src/main/jni/react/jni/ProxyExecutor.h @@ -18,7 +18,7 @@ class ProxyExecutorOneTimeFactory : public JSExecutorFactory { public: ProxyExecutorOneTimeFactory(jni::global_ref&& executorInstance) : m_executor(std::move(executorInstance)) {} - virtual std::unique_ptr createJSExecutor() override; + virtual std::unique_ptr createJSExecutor(FlushImmediateCallback ignoredCallback) override; private: jni::global_ref m_executor;