#include #include #include #include #include #include #include #ifdef RCT_NEW_ARCH_ENABLED #include #endif #include #include #include "AndroidUIScheduler.h" #include "LayoutAnimationsManager.h" #include "NativeProxy.h" #include "PlatformDepMethodsHolder.h" #include "RNRuntimeDecorator.h" #include "ReanimatedJSIUtils.h" #include "ReanimatedRuntime.h" #include "ReanimatedVersion.h" #include "WorkletRuntime.h" #include "WorkletRuntimeCollector.h" namespace reanimated { using namespace facebook; using namespace react; NativeProxy::NativeProxy( jni::alias_ref jThis, jsi::Runtime *rnRuntime, const std::shared_ptr &jsCallInvoker, const std::shared_ptr &uiScheduler, jni::global_ref layoutAnimations, jni::alias_ref messageQueueThread, #ifdef RCT_NEW_ARCH_ENABLED jni::alias_ref fabricUIManager, #endif const std::string &valueUnpackerCode) : javaPart_(jni::make_global(jThis)), rnRuntime_(rnRuntime), nativeReanimatedModule_(std::make_shared( *rnRuntime, jsCallInvoker, std::make_shared(messageQueueThread), uiScheduler, getPlatformDependentMethods(), valueUnpackerCode)), layoutAnimations_(std::move(layoutAnimations)) { #ifdef RCT_NEW_ARCH_ENABLED const auto &uiManager = fabricUIManager->getBinding()->getScheduler()->getUIManager(); nativeReanimatedModule_->initializeFabric(uiManager); // removed temporarily, event listener mechanism needs to be fixed on RN side // eventListener_ = std::make_shared( // [nativeReanimatedModule, // getAnimationTimestamp](const RawEvent &rawEvent) { // return nativeReanimatedModule->handleRawEvent( // rawEvent, getAnimationTimestamp()); // }); // reactScheduler_ = binding->getScheduler(); // reactScheduler_->addEventListener(eventListener_); #endif } NativeProxy::~NativeProxy() { // removed temporary, new event listener mechanism need fix on the RN side // reactScheduler_->removeEventListener(eventListener_); // cleanup all animated sensors here, since NativeProxy // has already been destroyed when AnimatedSensorModule's // destructor is ran nativeReanimatedModule_->cleanupSensors(); } jni::local_ref NativeProxy::initHybrid( jni::alias_ref jThis, jlong jsContext, jni::alias_ref jsCallInvokerHolder, jni::alias_ref androidUiScheduler, jni::alias_ref layoutAnimations, jni::alias_ref messageQueueThread, #ifdef RCT_NEW_ARCH_ENABLED jni::alias_ref fabricUIManager, #endif const std::string &valueUnpackerCode) { auto jsCallInvoker = jsCallInvokerHolder->cthis()->getCallInvoker(); auto uiScheduler = androidUiScheduler->cthis()->getUIScheduler(); return makeCxxInstance( jThis, (jsi::Runtime *)jsContext, jsCallInvoker, uiScheduler, make_global(layoutAnimations), messageQueueThread, #ifdef RCT_NEW_ARCH_ENABLED fabricUIManager, #endif valueUnpackerCode); } #ifndef NDEBUG void NativeProxy::checkJavaVersion(jsi::Runtime &rnRuntime) { std::string javaVersion; try { javaVersion = getJniMethod("getReanimatedJavaVersion")(javaPart_.get()) ->toStdString(); } catch (std::exception &) { throw std::runtime_error( std::string( "[Reanimated] C++ side failed to resolve Java code version.\n") + "See `https://docs.swmansion.com/react-native-reanimated/docs/guides/troubleshooting#c-side-failed-to-resolve-java-code-version` for more details."); } auto cppVersion = getReanimatedCppVersion(); if (cppVersion != javaVersion) { throw std::runtime_error( std::string( "[Reanimated] Mismatch between C++ code version and Java code version (") + cppVersion + " vs. " + javaVersion + " respectively).\n" + "See `https://docs.swmansion.com/react-native-reanimated/docs/guides/troubleshooting#mismatch-between-c-code-version-and-java-code-version` for more details."); } } void NativeProxy::injectCppVersion() { auto cppVersion = getReanimatedCppVersion(); try { static const auto method = getJniMethod)>("setCppVersion"); method(javaPart_.get(), make_jstring(cppVersion)); } catch (std::exception &) { throw std::runtime_error( std::string( "[Reanimated] C++ side failed to resolve Java code version (injection).\n") + "See `https://docs.swmansion.com/react-native-reanimated/docs/guides/troubleshooting#c-side-failed-to-resolve-java-code-version` for more details."); } } #endif // NDEBUG void NativeProxy::installJSIBindings() { jsi::Runtime &rnRuntime = *rnRuntime_; WorkletRuntimeCollector::install(rnRuntime); auto isReducedMotion = getIsReducedMotion(); RNRuntimeDecorator::decorate( rnRuntime, nativeReanimatedModule_, isReducedMotion); #ifndef NDEBUG checkJavaVersion(rnRuntime); injectCppVersion(); #endif // NDEBUG registerEventHandler(); setupLayoutAnimations(); } bool NativeProxy::isAnyHandlerWaitingForEvent( const std::string &eventName, const int emitterReactTag) { return nativeReanimatedModule_->isAnyHandlerWaitingForEvent( eventName, emitterReactTag); } void NativeProxy::performOperations() { #ifdef RCT_NEW_ARCH_ENABLED nativeReanimatedModule_->performOperations(); #endif } bool NativeProxy::getIsReducedMotion() { static const auto method = getJniMethod("getIsReducedMotion"); return method(javaPart_.get()); } void NativeProxy::registerNatives() { registerHybrid( {makeNativeMethod("initHybrid", NativeProxy::initHybrid), makeNativeMethod("installJSIBindings", NativeProxy::installJSIBindings), makeNativeMethod( "isAnyHandlerWaitingForEvent", NativeProxy::isAnyHandlerWaitingForEvent), makeNativeMethod("performOperations", NativeProxy::performOperations)}); } void NativeProxy::requestRender( std::function onRender, jsi::Runtime &) { static const auto method = getJniMethod("requestRender"); method( javaPart_.get(), AnimationFrameCallback::newObjectCxxArgs(std::move(onRender)).get()); } void NativeProxy::registerEventHandler() { auto eventHandler = bindThis(&NativeProxy::handleEvent); static const auto method = getJniMethod("registerEventHandler"); method( javaPart_.get(), EventHandler::newObjectCxxArgs(std::move(eventHandler)).get()); } void NativeProxy::maybeFlushUIUpdatesQueue() { static const auto method = getJniMethod("maybeFlushUIUpdatesQueue"); method(javaPart_.get()); } #ifdef RCT_NEW_ARCH_ENABLED // nothing #else jsi::Value NativeProxy::obtainProp( jsi::Runtime &rt, const int viewTag, const jsi::String &propName) { static const auto method = getJniMethod(int, jni::local_ref)>( "obtainProp"); local_ref propNameJStr = jni::make_jstring(propName.utf8(rt).c_str()); auto result = method(javaPart_.get(), viewTag, propNameJStr); std::string str = result->toStdString(); return jsi::Value(rt, jsi::String::createFromAscii(rt, str)); } void NativeProxy::configureProps( jsi::Runtime &rt, const jsi::Value &uiProps, const jsi::Value &nativeProps) { static const auto method = getJniMethod( "configureProps"); method( javaPart_.get(), ReadableNativeArray::newObjectCxxArgs(jsi::dynamicFromValue(rt, uiProps)) .get(), ReadableNativeArray::newObjectCxxArgs( jsi::dynamicFromValue(rt, nativeProps)) .get()); } void NativeProxy::updateProps(jsi::Runtime &rt, const jsi::Value &operations) { static const auto method = getJniMethod::javaobject)>( "updateProps"); auto array = operations.asObject(rt).asArray(rt); size_t length = array.size(rt); for (size_t i = 0; i < length; ++i) { auto item = array.getValueAtIndex(rt, i).asObject(rt); int viewTag = item.getProperty(rt, "tag").asNumber(); const jsi::Object &props = item.getProperty(rt, "updates").asObject(rt); method( javaPart_.get(), viewTag, JNIHelper::ConvertToPropsMap(rt, props).get()); } } void NativeProxy::scrollTo(int viewTag, double x, double y, bool animated) { static const auto method = getJniMethod("scrollTo"); method(javaPart_.get(), viewTag, x, y, animated); } inline jni::local_ref castReadableArray( jni::local_ref const &nativeArray) { return make_local( reinterpret_cast(nativeArray.get())); } void NativeProxy::dispatchCommand( jsi::Runtime &rt, const int viewTag, const jsi::Value &commandNameValue, const jsi::Value &argsValue) { static const auto method = getJniMethod, jni::local_ref)>( "dispatchCommand"); local_ref commandId = jni::make_jstring(commandNameValue.asString(rt).utf8(rt).c_str()); jni::local_ref commandArgs = castReadableArray(ReadableNativeArray::newObjectCxxArgs( jsi::dynamicFromValue(rt, argsValue))); method(javaPart_.get(), viewTag, commandId, commandArgs); } std::vector> NativeProxy::measure(int viewTag) { static const auto method = getJniMethod(int)>("measure"); local_ref output = method(javaPart_.get(), viewTag); size_t size = output->size(); auto elements = output->getRegion(0, size); return { {"x", elements[0]}, {"y", elements[1]}, {"pageX", elements[2]}, {"pageY", elements[3]}, {"width", elements[4]}, {"height", elements[5]}, }; } #endif // RCT_NEW_ARCH_ENABLED #ifdef RCT_NEW_ARCH_ENABLED inline jni::local_ref castReadableMap( jni::local_ref const &nativeMap) { return make_local(reinterpret_cast(nativeMap.get())); } void NativeProxy::synchronouslyUpdateUIProps( jsi::Runtime &rt, Tag tag, const jsi::Object &props) { static const auto method = getJniMethod)>( "synchronouslyUpdateUIProps"); jni::local_ref uiProps = castReadableMap(ReadableNativeMap::newObjectCxxArgs( jsi::dynamicFromValue(rt, jsi::Value(rt, props)))); method(javaPart_.get(), tag, uiProps); } #endif int NativeProxy::registerSensor( int sensorType, int interval, int, std::function setter) { static const auto method = getJniMethod("registerSensor"); return method( javaPart_.get(), sensorType, interval, SensorSetter::newObjectCxxArgs(std::move(setter)).get()); } void NativeProxy::unregisterSensor(int sensorId) { static const auto method = getJniMethod("unregisterSensor"); method(javaPart_.get(), sensorId); } void NativeProxy::setGestureState(int handlerTag, int newState) { static const auto method = getJniMethod("setGestureState"); method(javaPart_.get(), handlerTag, newState); } int NativeProxy::subscribeForKeyboardEvents( std::function keyboardEventDataUpdater, bool isStatusBarTranslucent) { static const auto method = getJniMethod( "subscribeForKeyboardEvents"); return method( javaPart_.get(), KeyboardEventDataUpdater::newObjectCxxArgs( std::move(keyboardEventDataUpdater)) .get(), isStatusBarTranslucent); } void NativeProxy::unsubscribeFromKeyboardEvents(int listenerId) { static const auto method = getJniMethod("unsubscribeFromKeyboardEvents"); method(javaPart_.get(), listenerId); } double NativeProxy::getAnimationTimestamp() { static const auto method = getJniMethod("getAnimationTimestamp"); jlong output = method(javaPart_.get()); return static_cast(output); } void NativeProxy::handleEvent( jni::alias_ref eventName, jint emitterReactTag, jni::alias_ref event) { // handles RCTEvents from RNGestureHandler if (event.get() == nullptr) { // Ignore events with null payload. return; } // TODO: convert event directly to jsi::Value without JSON serialization std::string eventAsString; try { eventAsString = event->toString(); } catch (std::exception &) { // Events from other libraries may contain NaN or INF values which // cannot be represented in JSON. See // https://github.com/software-mansion/react-native-reanimated/issues/1776 // for details. return; } #if REACT_NATIVE_MINOR_VERSION >= 72 std::string eventJSON = eventAsString; #else // remove "{ NativeMap: " and " }" std::string eventJSON = eventAsString.substr(13, eventAsString.length() - 15); #endif if (eventJSON == "null") { return; } jsi::Runtime &rt = nativeReanimatedModule_->getUIRuntime(); jsi::Value payload; try { payload = jsi::Value::createFromJsonUtf8( rt, reinterpret_cast(&eventJSON[0]), eventJSON.size()); } catch (std::exception &) { // Ignore events with malformed JSON payload. return; } nativeReanimatedModule_->handleEvent( eventName->toString(), emitterReactTag, payload, getAnimationTimestamp()); } void NativeProxy::progressLayoutAnimation( jsi::Runtime &rt, int tag, const jsi::Object &newProps, bool isSharedTransition) { auto newPropsJNI = JNIHelper::ConvertToPropsMap(rt, newProps); layoutAnimations_->cthis()->progressLayoutAnimation( tag, newPropsJNI, isSharedTransition); } PlatformDepMethodsHolder NativeProxy::getPlatformDependentMethods() { #ifdef RCT_NEW_ARCH_ENABLED // nothing #else auto updatePropsFunction = bindThis(&NativeProxy::updateProps); auto measureFunction = bindThis(&NativeProxy::measure); auto scrollToFunction = bindThis(&NativeProxy::scrollTo); auto dispatchCommandFunction = bindThis(&NativeProxy::dispatchCommand); auto obtainPropFunction = bindThis(&NativeProxy::obtainProp); #endif auto getAnimationTimestamp = bindThis(&NativeProxy::getAnimationTimestamp); auto requestRender = bindThis(&NativeProxy::requestRender); #ifdef RCT_NEW_ARCH_ENABLED auto synchronouslyUpdateUIPropsFunction = bindThis(&NativeProxy::synchronouslyUpdateUIProps); #else auto configurePropsFunction = bindThis(&NativeProxy::configureProps); #endif auto registerSensorFunction = bindThis(&NativeProxy::registerSensor); auto unregisterSensorFunction = bindThis(&NativeProxy::unregisterSensor); auto setGestureStateFunction = bindThis(&NativeProxy::setGestureState); auto subscribeForKeyboardEventsFunction = bindThis(&NativeProxy::subscribeForKeyboardEvents); auto unsubscribeFromKeyboardEventsFunction = bindThis(&NativeProxy::unsubscribeFromKeyboardEvents); auto progressLayoutAnimation = bindThis(&NativeProxy::progressLayoutAnimation); auto endLayoutAnimation = [this](int tag, bool removeView) { this->layoutAnimations_->cthis()->endLayoutAnimation(tag, removeView); }; auto maybeFlushUiUpdatesQueueFunction = bindThis(&NativeProxy::maybeFlushUIUpdatesQueue); return { requestRender, #ifdef RCT_NEW_ARCH_ENABLED synchronouslyUpdateUIPropsFunction, #else updatePropsFunction, scrollToFunction, dispatchCommandFunction, measureFunction, configurePropsFunction, obtainPropFunction, #endif getAnimationTimestamp, progressLayoutAnimation, endLayoutAnimation, registerSensorFunction, unregisterSensorFunction, setGestureStateFunction, subscribeForKeyboardEventsFunction, unsubscribeFromKeyboardEventsFunction, maybeFlushUiUpdatesQueueFunction, }; } void NativeProxy::setupLayoutAnimations() { auto weakNativeReanimatedModule = std::weak_ptr(nativeReanimatedModule_); layoutAnimations_->cthis()->setAnimationStartingBlock( [weakNativeReanimatedModule]( int tag, int type, alias_ref> values) { if (auto nativeReanimatedModule = weakNativeReanimatedModule.lock()) { jsi::Runtime &rt = nativeReanimatedModule->getUIRuntime(); jsi::Object yogaValues(rt); for (const auto &entry : *values) { try { std::string keyString = entry.first->toStdString(); std::string valueString = entry.second->toStdString(); auto key = jsi::String::createFromAscii(rt, keyString); if (keyString == "currentTransformMatrix" || keyString == "targetTransformMatrix") { jsi::Array matrix = jsi_utils::convertStringToArray(rt, valueString, 9); yogaValues.setProperty(rt, key, matrix); } else { auto value = stod(valueString); yogaValues.setProperty(rt, key, value); } } catch (std::invalid_argument e) { throw std::runtime_error( "[Reanimated] Failed to convert value to number."); } } nativeReanimatedModule->layoutAnimationsManager() .startLayoutAnimation( rt, tag, static_cast(type), yogaValues); } }); layoutAnimations_->cthis()->setHasAnimationBlock( [weakNativeReanimatedModule](int tag, int type) { if (auto nativeReanimatedModule = weakNativeReanimatedModule.lock()) { return nativeReanimatedModule->layoutAnimationsManager() .hasLayoutAnimation(tag, static_cast(type)); } return false; }); layoutAnimations_->cthis()->setShouldAnimateExitingBlock( [weakNativeReanimatedModule](int tag, bool shouldAnimate) { if (auto nativeReanimatedModule = weakNativeReanimatedModule.lock()) { return nativeReanimatedModule->layoutAnimationsManager() .shouldAnimateExiting(tag, shouldAnimate); } return false; }); #ifndef NDEBUG layoutAnimations_->cthis()->setCheckDuplicateSharedTag( [weakNativeReanimatedModule](int viewTag, int screenTag) { if (auto nativeReanimatedModule = weakNativeReanimatedModule.lock()) { nativeReanimatedModule->layoutAnimationsManager() .checkDuplicateSharedTag(viewTag, screenTag); } }); #endif layoutAnimations_->cthis()->setClearAnimationConfigBlock( [weakNativeReanimatedModule](int tag) { if (auto nativeReanimatedModule = weakNativeReanimatedModule.lock()) { nativeReanimatedModule->layoutAnimationsManager() .clearLayoutAnimationConfig(tag); } }); layoutAnimations_->cthis()->setCancelAnimationForTag( [weakNativeReanimatedModule](int tag) { if (auto nativeReanimatedModule = weakNativeReanimatedModule.lock()) { jsi::Runtime &rt = nativeReanimatedModule->getUIRuntime(); nativeReanimatedModule->layoutAnimationsManager() .cancelLayoutAnimation(rt, tag); } }); layoutAnimations_->cthis()->setFindPrecedingViewTagForTransition( [weakNativeReanimatedModule](int tag) { if (auto nativeReanimatedModule = weakNativeReanimatedModule.lock()) { return nativeReanimatedModule->layoutAnimationsManager() .findPrecedingViewTagForTransition(tag); } else { return -1; } }); } } // namespace reanimated