diff --git a/Common/cpp/AnimatedSensor/AnimatedSensorModule.cpp b/Common/cpp/AnimatedSensor/AnimatedSensorModule.cpp index 83948c91314..94515621c27 100644 --- a/Common/cpp/AnimatedSensor/AnimatedSensorModule.cpp +++ b/Common/cpp/AnimatedSensor/AnimatedSensorModule.cpp @@ -1,19 +1,17 @@ #include "AnimatedSensorModule.h" -#include "MutableValue.h" -#include "ValueWrapper.h" #include #include +#include "Shareables.h" + namespace reanimated { AnimatedSensorModule::AnimatedSensorModule( - const PlatformDepMethodsHolder &platformDepMethodsHolder, - RuntimeManager *runtimeManager) + const PlatformDepMethodsHolder &platformDepMethodsHolder) : platformRegisterSensorFunction_(platformDepMethodsHolder.registerSensor), platformUnregisterSensorFunction_( - platformDepMethodsHolder.unregisterSensor), - runtimeManager_(runtimeManager) {} + platformDepMethodsHolder.unregisterSensor) {} AnimatedSensorModule::~AnimatedSensorModule() { // It is called during app reload because app reload doesn't call hooks @@ -25,41 +23,38 @@ AnimatedSensorModule::~AnimatedSensorModule() { jsi::Value AnimatedSensorModule::registerSensor( jsi::Runtime &rt, - const jsi::Value &sensorType, + const std::shared_ptr &runtimeHelper, + const jsi::Value &sensorTypeValue, const jsi::Value &interval, - const jsi::Value &sensorDataContainer) { - std::shared_ptr sensorsData = ShareableValue::adapt( - rt, sensorDataContainer.getObject(rt), runtimeManager_); - auto &mutableObject = - ValueWrapper::asMutableValue(sensorsData->valueContainer); + const jsi::Value &sensorDataHandler) { + SensorType sensorType = static_cast(sensorTypeValue.asNumber()); - std::function setter; - if (sensorType.asNumber() == SensorType::ROTATION_VECTOR) { - setter = [&, mutableObject](double newValues[]) { - jsi::Runtime &runtime = *runtimeManager_->runtime.get(); - jsi::Object value(runtime); - value.setProperty(runtime, "qx", newValues[0]); - value.setProperty(runtime, "qy", newValues[1]); - value.setProperty(runtime, "qz", newValues[2]); - value.setProperty(runtime, "qw", newValues[3]); - value.setProperty(runtime, "yaw", newValues[4]); - value.setProperty(runtime, "pitch", newValues[5]); - value.setProperty(runtime, "roll", newValues[6]); - mutableObject->setValue(runtime, std::move(value)); - }; - } else { - setter = [&, mutableObject](double newValues[]) { - jsi::Runtime &runtime = *runtimeManager_->runtime.get(); - jsi::Object value(runtime); - value.setProperty(runtime, "x", newValues[0]); - value.setProperty(runtime, "y", newValues[1]); - value.setProperty(runtime, "z", newValues[2]); - mutableObject->setValue(runtime, std::move(value)); - }; - } + auto shareableHandler = extractShareableOrThrow(rt, sensorDataHandler); + auto uiRuntime = runtimeHelper->uiRuntime(); int sensorId = platformRegisterSensorFunction_( - sensorType.asNumber(), interval.asNumber(), setter); + sensorType, interval.asNumber(), [=](double newValues[]) { + jsi::Runtime &rt = *uiRuntime; + auto handler = + shareableHandler->getJSValue(rt).asObject(rt).asFunction(rt); + if (sensorType == SensorType::ROTATION_VECTOR) { + jsi::Object value(rt); + value.setProperty(rt, "qx", newValues[0]); + value.setProperty(rt, "qy", newValues[1]); + value.setProperty(rt, "qz", newValues[2]); + value.setProperty(rt, "qw", newValues[3]); + value.setProperty(rt, "yaw", newValues[4]); + value.setProperty(rt, "pitch", newValues[5]); + value.setProperty(rt, "roll", newValues[6]); + handler.call(rt, value); + } else { + jsi::Object value(rt); + value.setProperty(rt, "x", newValues[0]); + value.setProperty(rt, "y", newValues[1]); + value.setProperty(rt, "z", newValues[2]); + handler.call(rt, value); + } + }); if (sensorId != -1) { sensorsIds_.insert(sensorId); } diff --git a/Common/cpp/AnimatedSensor/AnimatedSensorModule.h b/Common/cpp/AnimatedSensor/AnimatedSensorModule.h index 76b652bd636..6e43e385d2a 100644 --- a/Common/cpp/AnimatedSensor/AnimatedSensorModule.h +++ b/Common/cpp/AnimatedSensor/AnimatedSensorModule.h @@ -1,10 +1,12 @@ #pragma once #include +#include #include #include "PlatformDepMethodsHolder.h" #include "RuntimeManager.h" +#include "Shareables.h" namespace reanimated { @@ -22,16 +24,15 @@ class AnimatedSensorModule { std::unordered_set sensorsIds_; RegisterSensorFunction platformRegisterSensorFunction_; UnregisterSensorFunction platformUnregisterSensorFunction_; - RuntimeManager *runtimeManager_; public: AnimatedSensorModule( - const PlatformDepMethodsHolder &platformDepMethodsHolder, - RuntimeManager *runtimeManager); + const PlatformDepMethodsHolder &platformDepMethodsHolder); ~AnimatedSensorModule(); jsi::Value registerSensor( jsi::Runtime &rt, + const std::shared_ptr &runtimeHelper, const jsi::Value &sensorType, const jsi::Value &interval, const jsi::Value &sensorDataContainer); diff --git a/Common/cpp/LayoutAnimations/LayoutAnimationsManager.cpp b/Common/cpp/LayoutAnimations/LayoutAnimationsManager.cpp new file mode 100644 index 00000000000..804625f333c --- /dev/null +++ b/Common/cpp/LayoutAnimations/LayoutAnimationsManager.cpp @@ -0,0 +1,76 @@ +#include "LayoutAnimationsManager.h" +#include "Shareables.h" + +#include + +namespace reanimated { + +void LayoutAnimationsManager::configureAnimation( + int tag, + const std::string &type, + std::shared_ptr config) { + auto lock = std::unique_lock(animationsMutex_); + if (type == "entering") { + enteringAnimations_[tag] = config; + } else if (type == "exiting") { + exitingAnimations_[tag] = config; + } else if (type == "layout") { + layoutAnimations_[tag] = config; + } +} + +bool LayoutAnimationsManager::hasLayoutAnimation( + int tag, + const std::string &type) { + auto lock = std::unique_lock(animationsMutex_); + if (type == "entering") { + return enteringAnimations_.find(tag) != enteringAnimations_.end(); + } else if (type == "exiting") { + return exitingAnimations_.find(tag) != exitingAnimations_.end(); + } else if (type == "layout") { + return layoutAnimations_.find(tag) != layoutAnimations_.end(); + } + return false; +} + +void LayoutAnimationsManager::clearLayoutAnimationConfig(int tag) { + auto lock = std::unique_lock(animationsMutex_); + enteringAnimations_.erase(tag); + exitingAnimations_.erase(tag); + layoutAnimations_.erase(tag); +} + +void LayoutAnimationsManager::startLayoutAnimation( + jsi::Runtime &rt, + int tag, + const std::string &type, + const jsi::Object &values) { + std::shared_ptr config, viewShareable; + { + auto lock = std::unique_lock(animationsMutex_); + if (type == "entering") { + config = enteringAnimations_[tag]; + } else if (type == "exiting") { + config = exitingAnimations_[tag]; + } else if (type == "layout") { + config = layoutAnimations_[tag]; + } + } + + // TODO: cache the following!! + jsi::Value layoutAnimationRepositoryAsValue = + rt.global() + .getPropertyAsObject(rt, "global") + .getProperty(rt, "LayoutAnimationsManager"); + jsi::Function startAnimationForTag = + layoutAnimationRepositoryAsValue.getObject(rt).getPropertyAsFunction( + rt, "start"); + startAnimationForTag.call( + rt, + jsi::Value(tag), + jsi::String::createFromAscii(rt, type), + values, + config->getJSValue(rt)); +} + +} // namespace reanimated diff --git a/Common/cpp/LayoutAnimations/LayoutAnimationsManager.h b/Common/cpp/LayoutAnimations/LayoutAnimationsManager.h new file mode 100644 index 00000000000..b0e4f766351 --- /dev/null +++ b/Common/cpp/LayoutAnimations/LayoutAnimationsManager.h @@ -0,0 +1,41 @@ +#pragma once + +#include "ErrorHandler.h" +#include "Shareables.h" + +#include +#include +#include +#include +#include +#include +#include + +namespace reanimated { + +using namespace facebook; + +class LayoutAnimationsManager { + public: + void configureAnimation( + int tag, + const std::string &type, + std::shared_ptr config); + bool hasLayoutAnimation(int tag, const std::string &type); + void startLayoutAnimation( + jsi::Runtime &rt, + int tag, + const std::string &type, + const jsi::Object &values); + void clearLayoutAnimationConfig(int tag); + + private: + std::unordered_map> enteringAnimations_; + std::unordered_map> exitingAnimations_; + std::unordered_map> layoutAnimations_; + mutable std::mutex + animationsMutex_; // Protects `enteringAnimations_`, `exitingAnimations_`, + // `layoutAnimations_` and `viewSharedValues_`. +}; + +} // namespace reanimated diff --git a/Common/cpp/LayoutAnimations/LayoutAnimationsProxy.cpp b/Common/cpp/LayoutAnimations/LayoutAnimationsProxy.cpp deleted file mode 100644 index 17bdec77da5..00000000000 --- a/Common/cpp/LayoutAnimations/LayoutAnimationsProxy.cpp +++ /dev/null @@ -1,127 +0,0 @@ -#include "LayoutAnimationsProxy.h" -#include "FrozenObject.h" -#include "MutableValue.h" -#include "ShareableValue.h" -#include "ValueWrapper.h" - -#include - -namespace reanimated { - -const long long idOffset = 1e9; - -LayoutAnimationsProxy::LayoutAnimationsProxy( - std::function progressHandler, - std::function endHandler, - std::weak_ptr weakErrorHandler) - : progressHandler_(std::move(progressHandler)), - endHandler_(std::move(endHandler)), - weakErrorHandler_(weakErrorHandler) {} - -void LayoutAnimationsProxy::startObserving( - int tag, - std::shared_ptr sv, - jsi::Runtime &rt) { - observedValues_[tag] = sv; - this->progressHandler_(tag, sv->value->toJSValue(rt).asObject(rt)); - sv->addListener(tag + idOffset, [sv, tag, this, &rt]() { - this->progressHandler_(tag, sv->value->toJSValue(rt).asObject(rt)); - }); -} - -void LayoutAnimationsProxy::stopObserving( - int tag, - bool finished, - bool removeView) { - if (observedValues_.count(tag) == 0) { - return; - } - std::shared_ptr sv = observedValues_[tag]; - sv->removeListener(tag + idOffset); - observedValues_.erase(tag); - this->endHandler_(tag, !finished, removeView); -} - -void LayoutAnimationsProxy::configureAnimation( - int tag, - const std::string &type, - std::shared_ptr config, - std::shared_ptr viewSharedValue) { - auto lock = std::unique_lock(animationsMutex_); - if (type == "entering") { - enteringAnimations_[tag] = config; - } else if (type == "exiting") { - exitingAnimations_[tag] = config; - } else if (type == "layout") { - layoutAnimations_[tag] = config; - } - viewSharedValues_[tag] = viewSharedValue; -} - -bool LayoutAnimationsProxy::hasLayoutAnimation( - int tag, - const std::string &type) { - auto lock = std::unique_lock(animationsMutex_); - if (type == "entering") { - return enteringAnimations_.find(tag) != enteringAnimations_.end(); - } else if (type == "exiting") { - return exitingAnimations_.find(tag) != exitingAnimations_.end(); - } else if (type == "layout") { - return layoutAnimations_.find(tag) != layoutAnimations_.end(); - } - return false; -} - -void LayoutAnimationsProxy::clearLayoutAnimationConfig(int tag) { - auto lock = std::unique_lock(animationsMutex_); - enteringAnimations_.erase(tag); - exitingAnimations_.erase(tag); - layoutAnimations_.erase(tag); - viewSharedValues_.erase(tag); -} - -void LayoutAnimationsProxy::startLayoutAnimation( - jsi::Runtime &rt, - int tag, - const std::string &type, - const jsi::Object &values) { - std::shared_ptr config; - std::shared_ptr viewSharedValue; - { - auto lock = std::unique_lock(animationsMutex_); - if (type == "entering") { - config = enteringAnimations_[tag]; - } else if (type == "exiting") { - config = exitingAnimations_[tag]; - } else if (type == "layout") { - config = layoutAnimations_[tag]; - } - viewSharedValue = viewSharedValues_[tag]; - } - - jsi::Value layoutAnimationRepositoryAsValue = - rt.global() - .getPropertyAsObject(rt, "global") - .getProperty(rt, "LayoutAnimationRepository"); - if (layoutAnimationRepositoryAsValue.isUndefined()) { - auto errorHandler = weakErrorHandler_.lock(); - if (errorHandler != nullptr) { - errorHandler->setError( - "startLayoutAnimation called before initializing LayoutAnimationRepository"); - errorHandler->raise(); - } - return; - } - jsi::Function startAnimationForTag = - layoutAnimationRepositoryAsValue.getObject(rt).getPropertyAsFunction( - rt, "startAnimationForTag"); - startAnimationForTag.call( - rt, - jsi::Value(tag), - jsi::String::createFromAscii(rt, type), - values, - config->toJSValue(rt), - viewSharedValue->toJSValue(rt)); -} - -} // namespace reanimated diff --git a/Common/cpp/LayoutAnimations/LayoutAnimationsProxy.h b/Common/cpp/LayoutAnimations/LayoutAnimationsProxy.h deleted file mode 100644 index 5965ed5f4fb..00000000000 --- a/Common/cpp/LayoutAnimations/LayoutAnimationsProxy.h +++ /dev/null @@ -1,56 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include - -namespace reanimated { - -using namespace facebook; - -class MutableValue; -class ShareableValue; - -class LayoutAnimationsProxy { - public: - LayoutAnimationsProxy( - std::function progressHandler, - std::function endHandler, - std::weak_ptr weakErrorHandler); - - void - startObserving(int tag, std::shared_ptr sv, jsi::Runtime &rt); - void stopObserving(int tag, bool finished, bool removeView); - void configureAnimation( - int tag, - const std::string &type, - std::shared_ptr config, - std::shared_ptr viewSharedValue); - bool hasLayoutAnimation(int tag, const std::string &type); - void startLayoutAnimation( - jsi::Runtime &rt, - int tag, - const std::string &type, - const jsi::Object &values); - void clearLayoutAnimationConfig(int tag); - - private: - std::function progressHandler_; - std::function endHandler_; - std::weak_ptr weakErrorHandler_; - std::unordered_map> observedValues_; - std::unordered_map> viewSharedValues_; - std::unordered_map> enteringAnimations_; - std::unordered_map> exitingAnimations_; - std::unordered_map> layoutAnimations_; - mutable std::mutex - animationsMutex_; // Protects `enteringAnimations_`, `exitingAnimations_`, - // `layoutAnimations_` and `viewSharedValues_`. -}; - -} // namespace reanimated diff --git a/Common/cpp/NativeModules/NativeReanimatedModule.cpp b/Common/cpp/NativeModules/NativeReanimatedModule.cpp index 143b1a6a232..b9a31a82fe5 100644 --- a/Common/cpp/NativeModules/NativeReanimatedModule.cpp +++ b/Common/cpp/NativeModules/NativeReanimatedModule.cpp @@ -19,84 +19,37 @@ #include "EventHandlerRegistry.h" #include "FeaturesConfig.h" -#include "FrozenObject.h" -#include "JSIStoreValueUser.h" -#include "Mapper.h" -#include "MapperRegistry.h" -#include "MutableValue.h" #include "ReanimatedHiddenHeaders.h" #include "RuntimeDecorator.h" -#include "ShareableValue.h" +#include "Shareables.h" #include "WorkletEventHandler.h" using namespace facebook; namespace reanimated { -void extractMutables( - jsi::Runtime &rt, - std::shared_ptr sv, - std::vector> &res) { - switch (sv->type) { - case ValueType::MutableValueType: { - auto &mutableValue = ValueWrapper::asMutableValue(sv->valueContainer); - res.push_back(mutableValue); - break; - } - case ValueType::FrozenArrayType: - for (auto &it : ValueWrapper::asFrozenArray(sv->valueContainer)) { - extractMutables(rt, it, res); - } - break; - case ValueType::RemoteObjectType: - case ValueType::FrozenObjectType: - for (auto &it : ValueWrapper::asFrozenObject(sv->valueContainer)->map) { - extractMutables(rt, it.second, res); - } - break; - default: - break; - } -} - -std::vector> extractMutablesFromArray( - jsi::Runtime &rt, - const jsi::Array &array, - NativeReanimatedModule *module) { - std::vector> res; - for (size_t i = 0, size = array.size(rt); i < size; i++) { - auto shareable = - ShareableValue::adapt(rt, array.getValueAtIndex(rt, i), module); - extractMutables(rt, shareable, res); - } - return res; -} - NativeReanimatedModule::NativeReanimatedModule( - std::shared_ptr jsInvoker, - std::shared_ptr scheduler, - std::shared_ptr rt, - std::shared_ptr errorHandler, + const std::shared_ptr &jsInvoker, + const std::shared_ptr &scheduler, + const std::shared_ptr &rt, + const std::shared_ptr &errorHandler, #ifdef RCT_NEW_ARCH_ENABLED // nothing #else std::function propObtainer, #endif - std::shared_ptr layoutAnimationsProxy, PlatformDepMethodsHolder platformDepMethodsHolder) : NativeReanimatedModuleSpec(jsInvoker), RuntimeManager(rt, errorHandler, scheduler, RuntimeType::UI), - layoutAnimationsProxy_(layoutAnimationsProxy), - mapperRegistry(std::make_shared()), - eventHandlerRegistry(std::make_shared()), + eventHandlerRegistry(std::make_unique()), requestRender(platformDepMethodsHolder.requestRender), #ifdef RCT_NEW_ARCH_ENABLED // nothing #else propObtainer(propObtainer), #endif - animatedSensorModule(platformDepMethodsHolder, this), + animatedSensorModule(platformDepMethodsHolder), #ifdef RCT_NEW_ARCH_ENABLED synchronouslyUpdateUIPropsFunction( platformDepMethodsHolder.synchronouslyUpdateUIPropsFunction) @@ -110,7 +63,50 @@ NativeReanimatedModule::NativeReanimatedModule( maybeRequestRender(); }; - this->layoutAnimationsProxy_ = layoutAnimationsProxy; + auto scheduleOnJS = [this]( + jsi::Runtime &rt, + const jsi::Value &remoteFun, + const jsi::Value &argsValue) { + auto shareableRemoteFun = extractShareableOrThrow( + rt, + remoteFun, + "Incompatible object passed to scheduleOnJS. It is only allowed to schedule functions defined on the React Native JS runtime this way."); + auto shareableArgs = argsValue.isUndefined() + ? nullptr + : extractShareableOrThrow(rt, argsValue); + auto jsRuntime = this->runtimeHelper->rnRuntime(); + this->scheduler->scheduleOnJS([=] { + jsi::Runtime &rt = *jsRuntime; + auto remoteFun = shareableRemoteFun->getJSValue(rt); + if (shareableArgs == nullptr) { + // fast path for remote function w/o arguments + remoteFun.asObject(rt).asFunction(rt).call(rt); + } else { + auto argsArray = shareableArgs->getJSValue(rt).asObject(rt).asArray(rt); + auto argsSize = argsArray.size(rt); + // number of arguments is typically relatively small so it is ok to + // to use VLAs here, hence disabling the lint rule + jsi::Value args[argsSize]; // NOLINT(runtime/arrays) + for (size_t i = 0; i < argsSize; i++) { + args[i] = argsArray.getValueAtIndex(rt, i); + } + remoteFun.asObject(rt).asFunction(rt).call(rt, args, argsSize); + } + }); + }; + + auto makeShareableClone = [this](jsi::Runtime &rt, const jsi::Value &value) { + return this->makeShareableClone(rt, value); + }; + + auto updateDataSynchronously = + [this]( + jsi::Runtime &rt, + const jsi::Value &synchronizedDataHolderRef, + const jsi::Value &newData) { + return this->updateDataSynchronously( + rt, synchronizedDataHolderRef, newData); + }; #ifdef RCT_NEW_ARCH_ENABLED auto updateProps = [this]( @@ -151,11 +147,15 @@ NativeReanimatedModule::NativeReanimatedModule( platformDepMethodsHolder.scrollToFunction, #endif requestAnimationFrame, + scheduleOnJS, + makeShareableClone, + updateDataSynchronously, platformDepMethodsHolder.getCurrentTime, platformDepMethodsHolder.registerSensor, platformDepMethodsHolder.unregisterSensor, platformDepMethodsHolder.setGestureStateFunction, - layoutAnimationsProxy); + platformDepMethodsHolder.progressLayoutAnimation, + platformDepMethodsHolder.endLayoutAnimation); onRenderCallback = [this](double timestampMs) { this->renderRequested = false; this->onRender(timestampMs); @@ -174,87 +174,109 @@ NativeReanimatedModule::NativeReanimatedModule( void NativeReanimatedModule::installCoreFunctions( jsi::Runtime &rt, - const jsi::Value &valueSetter) { - this->valueSetter = ShareableValue::adapt(rt, valueSetter, this); + const jsi::Value &valueUnpacker, + const jsi::Value &layoutAnimationStartFunction) { + if (!runtimeHelper) { + // initialize runtimeHelper here if not already present. We expect only one + // instace of the helper to exists. + runtimeHelper = + std::make_shared(&rt, this->runtime.get(), scheduler); + } + runtimeHelper->valueUnpacker = + std::make_shared(runtimeHelper.get(), valueUnpacker); +} + +NativeReanimatedModule::~NativeReanimatedModule() { + if (runtimeHelper) { + runtimeHelper->valueUnpacker = nullptr; + // event handler registry stores some JSI values from UI runtime, so it has + // to go away before we tear down the runtime + eventHandlerRegistry.reset(); + runtime.reset(); + // make sure uiRuntimeDestroyed is set after the runtime is deallocated + runtimeHelper->uiRuntimeDestroyed = true; + } } -jsi::Value NativeReanimatedModule::makeShareable( +void NativeReanimatedModule::scheduleOnUI( jsi::Runtime &rt, - const jsi::Value &value) { - return ShareableValue::adapt(rt, value, this)->getValue(rt); + const jsi::Value &worklet) { + auto shareableWorklet = extractShareableOrThrow(rt, worklet); + assert( + shareableWorklet->valueType() == Shareable::WorkletType && + "only worklets can be scheduled to run on UI"); + auto uiRuntime = runtimeHelper->uiRuntime(); + frameCallbacks.push_back([=](double timestamp) { + jsi::Runtime &rt = *uiRuntime; + auto workletValue = shareableWorklet->getJSValue(rt); + workletValue.asObject(rt).asFunction(rt).call(rt); + }); + maybeRequestRender(); } -jsi::Value NativeReanimatedModule::makeMutable( +jsi::Value NativeReanimatedModule::makeSynchronizedDataHolder( jsi::Runtime &rt, - const jsi::Value &value) { - return ShareableValue::adapt(rt, value, this, ValueType::MutableValueType) - ->getValue(rt); + const jsi::Value &initialShareable) { + auto dataHolder = std::make_shared( + runtimeHelper, rt, initialShareable); + return dataHolder->getJSValue(rt); } -jsi::Value NativeReanimatedModule::makeRemote( +void NativeReanimatedModule::updateDataSynchronously( jsi::Runtime &rt, - const jsi::Value &value) { - return ShareableValue::adapt(rt, value, this, ValueType::RemoteObjectType) - ->getValue(rt); + const jsi::Value &synchronizedDataHolderRef, + const jsi::Value &newData) { + auto dataHolder = extractShareableOrThrow( + rt, synchronizedDataHolderRef); + dataHolder->set(rt, newData); } -jsi::Value NativeReanimatedModule::startMapper( +jsi::Value NativeReanimatedModule::getDataSynchronously( jsi::Runtime &rt, - const jsi::Value &worklet, - const jsi::Value &inputs, - const jsi::Value &outputs, - const jsi::Value &updater, - const jsi::Value &viewDescriptors) { - static unsigned long MAPPER_ID = 1; - - unsigned long newMapperId = MAPPER_ID++; - auto mapperShareable = ShareableValue::adapt(rt, worklet, this); - auto inputMutables = - extractMutablesFromArray(rt, inputs.asObject(rt).asArray(rt), this); - auto outputMutables = - extractMutablesFromArray(rt, outputs.asObject(rt).asArray(rt), this); - - int optimalizationLvl = 0; - auto optimalization = - updater.asObject(rt).getProperty(rt, "__optimalization"); - if (optimalization.isNumber()) { - optimalizationLvl = optimalization.asNumber(); - } - auto updaterSV = ShareableValue::adapt(rt, updater, this); - auto viewDescriptorsSV = ShareableValue::adapt(rt, viewDescriptors, this); - - scheduler->scheduleOnUI([=] { - auto mapperFunction = - mapperShareable->getValue(*runtime).asObject(*runtime).asFunction( - *runtime); - std::shared_ptr mapperFunctionPointer = - std::make_shared(std::move(mapperFunction)); - - std::shared_ptr mapperPointer = std::make_shared( - this, - newMapperId, - mapperFunctionPointer, - inputMutables, - outputMutables); - if (optimalizationLvl > 0) { - mapperPointer->enableFastMode( - optimalizationLvl, updaterSV, viewDescriptorsSV); - } - mapperRegistry->startMapper(mapperPointer); - maybeRequestRender(); - }); - - return jsi::Value(static_cast(newMapperId)); + const jsi::Value &synchronizedDataHolderRef) { + auto dataHolder = extractShareableOrThrow( + rt, synchronizedDataHolderRef); + return dataHolder->get(rt); } -void NativeReanimatedModule::stopMapper( +jsi::Value NativeReanimatedModule::makeShareableClone( jsi::Runtime &rt, - const jsi::Value &mapperId) { - unsigned long id = mapperId.asNumber(); - scheduler->scheduleOnUI([=] { - mapperRegistry->stopMapper(id); - maybeRequestRender(); - }); + const jsi::Value &value) { + std::shared_ptr shareable; + if (value.isObject()) { + auto object = value.asObject(rt); + if (!object.getProperty(rt, "__workletHash").isUndefined()) { + shareable = std::make_shared(runtimeHelper, rt, object); + } else if (!object.getProperty(rt, "__init").isUndefined()) { + shareable = std::make_shared(runtimeHelper, rt, object); + } else if (object.isFunction(rt)) { + shareable = std::make_shared( + runtimeHelper, rt, object.asFunction(rt)); + } else if (object.isArray(rt)) { + shareable = std::make_shared( + runtimeHelper, rt, object.asArray(rt)); +#ifdef RCT_NEW_ARCH_ENABLED + } else if (object.isHostObject(rt)) { + shareable = std::make_shared( + runtimeHelper, rt, object); +#endif + } else { + shareable = std::make_shared(runtimeHelper, rt, object); + } + } else if (value.isString()) { + shareable = std::make_shared(rt, value.asString(rt)); + } else if (value.isUndefined()) { + shareable = std::make_shared(); + } else if (value.isNull()) { + shareable = std::make_shared(nullptr); + } else if (value.isBool()) { + shareable = std::make_shared(value.getBool()); + } else if (value.isNumber()) { + shareable = std::make_shared(value.getNumber()); + } else { + throw std::runtime_error("attempted to convert an unsupported value type"); + } + return ShareableJSRef::newHostObject(rt, shareable); } jsi::Value NativeReanimatedModule::registerEventHandler( @@ -265,12 +287,12 @@ jsi::Value NativeReanimatedModule::registerEventHandler( unsigned long newRegistrationId = EVENT_HANDLER_ID++; auto eventName = eventHash.asString(rt).utf8(rt); - auto handlerShareable = ShareableValue::adapt(rt, worklet, this); + auto handlerShareable = extractShareableOrThrow(rt, worklet); scheduler->scheduleOnUI([=] { + jsi::Runtime &rt = *runtimeHelper->uiRuntime(); auto handlerFunction = - handlerShareable->getValue(*runtime).asObject(*runtime).asFunction( - *runtime); + handlerShareable->getJSValue(rt).asObject(rt).asFunction(rt); auto handler = std::make_shared( newRegistrationId, eventName, std::move(handlerFunction)); eventHandlerRegistry->registerEventHandler(handler); @@ -342,13 +364,11 @@ jsi::Value NativeReanimatedModule::configureLayoutAnimation( jsi::Runtime &rt, const jsi::Value &viewTag, const jsi::Value &type, - const jsi::Value &config, - const jsi::Value &viewSharedValue) { - layoutAnimationsProxy_->configureAnimation( + const jsi::Value &config) { + layoutAnimationsManager_.configureAnimation( viewTag.asNumber(), type.asString(rt).utf8(rt), - ShareableValue::adapt(rt, config, this), - ShareableValue::adapt(rt, viewSharedValue, this)); + extractShareableOrThrow(rt, config)); return jsi::Value::undefined(); } @@ -366,10 +386,6 @@ void NativeReanimatedModule::onEvent( #else eventHandlerRegistry->processEvent(*runtime, eventName, eventAsString); #endif - mapperRegistry->execute(*runtime); - if (mapperRegistry->needRunOnRender()) { - maybeRequestRender(); - } } catch (std::exception &e) { std::string str = e.what(); this->errorHandler->setError(str); @@ -400,11 +416,6 @@ void NativeReanimatedModule::onRender(double timestampMs) { for (auto &callback : callbacks) { callback(timestampMs); } - mapperRegistry->execute(*runtime); - - if (mapperRegistry->needRunOnRender()) { - maybeRequestRender(); - } } catch (std::exception &e) { std::string str = e.what(); this->errorHandler->setError(str); @@ -420,9 +431,9 @@ jsi::Value NativeReanimatedModule::registerSensor( jsi::Runtime &rt, const jsi::Value &sensorType, const jsi::Value &interval, - const jsi::Value &sensorDataContainer) { + const jsi::Value &sensorDataHandler) { return animatedSensorModule.registerSensor( - rt, sensorType, interval, sensorDataContainer); + rt, runtimeHelper, sensorType, interval, sensorDataHandler); } void NativeReanimatedModule::unregisterSensor( @@ -645,30 +656,15 @@ void NativeReanimatedModule::setNewestShadowNodesRegistry( jsi::Value NativeReanimatedModule::subscribeForKeyboardEvents( jsi::Runtime &rt, - const jsi::Value &keyboardEventContainer) { - jsi::Object keyboardEventObj = keyboardEventContainer.getObject(rt); - std::unordered_map> - sharedProperties; - std::shared_ptr keyboardStateShared = ShareableValue::adapt( - rt, keyboardEventObj.getProperty(rt, "state"), this); - std::shared_ptr heightShared = ShareableValue::adapt( - rt, keyboardEventObj.getProperty(rt, "height"), this); - - auto keyboardEventDataUpdater = - [this, &rt, keyboardStateShared, heightShared]( - int keyboardState, int height) { - auto &keyboardStateValue = - ValueWrapper::asMutableValue(keyboardStateShared->valueContainer); - keyboardStateValue->setValue(rt, jsi::Value(keyboardState)); - - auto &heightMutableValue = - ValueWrapper::asMutableValue(heightShared->valueContainer); - heightMutableValue->setValue(rt, jsi::Value(height)); - - this->mapperRegistry->execute(*this->runtime); - }; - - return subscribeForKeyboardEventsFunction(keyboardEventDataUpdater); + const jsi::Value &handlerWorklet) { + auto shareableHandler = extractShareableOrThrow(rt, handlerWorklet); + auto uiRuntime = runtimeHelper->uiRuntime(); + return subscribeForKeyboardEventsFunction([=](int keyboardState, int height) { + jsi::Runtime &rt = *uiRuntime; + auto handler = shareableHandler->getJSValue(rt); + handler.asObject(rt).asFunction(rt).call( + rt, jsi::Value(keyboardState), jsi::Value(height)); + }); } void NativeReanimatedModule::unsubscribeFromKeyboardEvents( diff --git a/Common/cpp/NativeModules/NativeReanimatedModule.h b/Common/cpp/NativeModules/NativeReanimatedModule.h index 5e26100912a..1ae695b9c95 100644 --- a/Common/cpp/NativeModules/NativeReanimatedModule.h +++ b/Common/cpp/NativeModules/NativeReanimatedModule.h @@ -13,7 +13,7 @@ #include "AnimatedSensorModule.h" #include "ErrorHandler.h" -#include "LayoutAnimationsProxy.h" +#include "LayoutAnimationsManager.h" #include "NativeReanimatedModuleSpec.h" #include "PlatformDepMethodsHolder.h" #include "RuntimeDecorator.h" @@ -25,46 +25,48 @@ namespace reanimated { using FrameCallback = std::function; -class ShareableValue; -class MutableValue; -class MapperRegistry; class EventHandlerRegistry; class NativeReanimatedModule : public NativeReanimatedModuleSpec, public RuntimeManager { - friend ShareableValue; - friend MutableValue; - public: NativeReanimatedModule( - std::shared_ptr jsInvoker, - std::shared_ptr scheduler, - std::shared_ptr rt, - std::shared_ptr errorHandler, + const std::shared_ptr &jsInvoker, + const std::shared_ptr &scheduler, + const std::shared_ptr &rt, + const std::shared_ptr &errorHandler, #ifdef RCT_NEW_ARCH_ENABLED // nothing #else std::function propObtainer, #endif - std::shared_ptr layoutAnimationsProxy, PlatformDepMethodsHolder platformDepMethodsHolder); - void installCoreFunctions(jsi::Runtime &rt, const jsi::Value &valueSetter) - override; + ~NativeReanimatedModule(); - jsi::Value makeShareable(jsi::Runtime &rt, const jsi::Value &value) override; - jsi::Value makeMutable(jsi::Runtime &rt, const jsi::Value &value) override; - jsi::Value makeRemote(jsi::Runtime &rt, const jsi::Value &value) override; + std::shared_ptr runtimeHelper; - jsi::Value startMapper( + void installCoreFunctions( jsi::Runtime &rt, - const jsi::Value &worklet, - const jsi::Value &inputs, - const jsi::Value &outputs, - const jsi::Value &updater, - const jsi::Value &viewDescriptors) override; - void stopMapper(jsi::Runtime &rt, const jsi::Value &mapperId) override; + const jsi::Value &workletMaker, + const jsi::Value &layoutAnimationStartFunction) override; + + jsi::Value makeShareableClone(jsi::Runtime &rt, const jsi::Value &value) + override; + + jsi::Value makeSynchronizedDataHolder( + jsi::Runtime &rt, + const jsi::Value &initialShareable) override; + jsi::Value getDataSynchronously( + jsi::Runtime &rt, + const jsi::Value &synchronizedDataHolderRef) override; + void updateDataSynchronously( + jsi::Runtime &rt, + const jsi::Value &synchronizedDataHolderRef, + const jsi::Value &newData); + + void scheduleOnUI(jsi::Runtime &rt, const jsi::Value &worklet) override; jsi::Value registerEventHandler( jsi::Runtime &rt, @@ -86,6 +88,11 @@ class NativeReanimatedModule : public NativeReanimatedModuleSpec, jsi::Runtime &rt, const jsi::Value &uiProps, const jsi::Value &nativeProps) override; + jsi::Value configureLayoutAnimation( + jsi::Runtime &rt, + const jsi::Value &viewTag, + const jsi::Value &type, + const jsi::Value &config) override; void onRender(double timestampMs); #ifdef RCT_NEW_ARCH_ENABLED @@ -144,23 +151,17 @@ class NativeReanimatedModule : public NativeReanimatedModuleSpec, jsi::Runtime &rt, const jsi::Value &listenerId) override; - jsi::Value configureLayoutAnimation( - jsi::Runtime &rt, - const jsi::Value &viewTag, - const jsi::Value &type, - const jsi::Value &config, - const jsi::Value &viewSharedValue) override; + inline LayoutAnimationsManager &layoutAnimationsManager() { + return layoutAnimationsManager_; + } private: #ifdef RCT_NEW_ARCH_ENABLED bool isThereAnyLayoutProp(jsi::Runtime &rt, const jsi::Value &props); #endif // RCT_NEW_ARCH_ENABLED - std::shared_ptr layoutAnimationsProxy_; - std::shared_ptr mapperRegistry; - std::shared_ptr eventHandlerRegistry; + std::unique_ptr eventHandlerRegistry; std::function requestRender; - std::shared_ptr dummyEvent; std::vector frameCallbacks; bool renderRequested = false; std::function @@ -187,6 +188,7 @@ class NativeReanimatedModule : public NativeReanimatedModuleSpec, #endif std::unordered_set nativePropNames_; // filled by configureProps + LayoutAnimationsManager layoutAnimationsManager_; KeyboardEventSubscribeFunction subscribeForKeyboardEventsFunction; KeyboardEventUnsubscribeFunction unsubscribeFromKeyboardEventsFunction; diff --git a/Common/cpp/NativeModules/NativeReanimatedModuleSpec.cpp b/Common/cpp/NativeModules/NativeReanimatedModuleSpec.cpp index afae89a093e..5489f559b40 100644 --- a/Common/cpp/NativeModules/NativeReanimatedModuleSpec.cpp +++ b/Common/cpp/NativeModules/NativeReanimatedModuleSpec.cpp @@ -12,61 +12,50 @@ static jsi::Value SPEC_PREFIX(installCoreFunctions)( const jsi::Value *args, size_t count) { static_cast(&turboModule) - ->installCoreFunctions(rt, std::move(args[0])); + ->installCoreFunctions(rt, std::move(args[0]), std::move(args[1])); return jsi::Value::undefined(); } // SharedValue -static jsi::Value SPEC_PREFIX(makeShareable)( +static jsi::Value SPEC_PREFIX(makeShareableClone)( jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value *args, size_t count) { return static_cast(&turboModule) - ->makeShareable(rt, std::move(args[0])); + ->makeShareableClone(rt, std::move(args[0])); } -static jsi::Value SPEC_PREFIX(makeMutable)( - jsi::Runtime &rt, - TurboModule &turboModule, - const jsi::Value *args, - size_t count) { - return static_cast(&turboModule) - ->makeMutable(rt, std::move(args[0])); -} +// Sync methods -static jsi::Value SPEC_PREFIX(makeRemote)( +static jsi::Value SPEC_PREFIX(makeSynchronizedDataHolder)( jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value *args, size_t count) { return static_cast(&turboModule) - ->makeRemote(rt, std::move(args[0])); + ->makeSynchronizedDataHolder(rt, std::move(args[0])); } -static jsi::Value SPEC_PREFIX(startMapper)( +static jsi::Value SPEC_PREFIX(getDataSynchronously)( jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value *args, size_t count) { return static_cast(&turboModule) - ->startMapper( - rt, - std::move(args[0]), - std::move(args[1]), - std::move(args[2]), - std::move(args[3]), - std::move(args[4])); + ->getDataSynchronously(rt, std::move(args[0])); } -static jsi::Value SPEC_PREFIX(stopMapper)( +// scheduler + +static jsi::Value SPEC_PREFIX(scheduleOnUI)( jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value *args, size_t count) { static_cast(&turboModule) - ->stopMapper(rt, std::move(args[0])); + ->scheduleOnUI(rt, std::move(args[0])); return jsi::Value::undefined(); } @@ -166,25 +155,24 @@ static jsi::Value SPEC_PREFIX(configureLayoutAnimation)( size_t count) { return static_cast(&turboModule) ->configureLayoutAnimation( - rt, - std::move(args[0]), - std::move(args[1]), - std::move(args[2]), - std::move(args[3])); + rt, std::move(args[0]), std::move(args[1]), std::move(args[2])); } NativeReanimatedModuleSpec::NativeReanimatedModuleSpec( std::shared_ptr jsInvoker) : TurboModule("NativeReanimated", jsInvoker) { methodMap_["installCoreFunctions"] = - MethodMetadata{1, SPEC_PREFIX(installCoreFunctions)}; + MethodMetadata{2, SPEC_PREFIX(installCoreFunctions)}; + + methodMap_["makeShareableClone"] = + MethodMetadata{1, SPEC_PREFIX(makeShareableClone)}; - methodMap_["makeShareable"] = MethodMetadata{1, SPEC_PREFIX(makeShareable)}; - methodMap_["makeMutable"] = MethodMetadata{1, SPEC_PREFIX(makeMutable)}; - methodMap_["makeRemote"] = MethodMetadata{1, SPEC_PREFIX(makeRemote)}; + methodMap_["makeSynchronizedDataHolder"] = + MethodMetadata{1, SPEC_PREFIX(makeSynchronizedDataHolder)}; + methodMap_["getDataSynchronously"] = + MethodMetadata{1, SPEC_PREFIX(getDataSynchronously)}; - methodMap_["startMapper"] = MethodMetadata{5, SPEC_PREFIX(startMapper)}; - methodMap_["stopMapper"] = MethodMetadata{1, SPEC_PREFIX(stopMapper)}; + methodMap_["scheduleOnUI"] = MethodMetadata{1, SPEC_PREFIX(scheduleOnUI)}; methodMap_["registerEventHandler"] = MethodMetadata{2, SPEC_PREFIX(registerEventHandler)}; diff --git a/Common/cpp/NativeModules/NativeReanimatedModuleSpec.h b/Common/cpp/NativeModules/NativeReanimatedModuleSpec.h index b365b2b52a7..3e7393c9022 100644 --- a/Common/cpp/NativeModules/NativeReanimatedModuleSpec.h +++ b/Common/cpp/NativeModules/NativeReanimatedModuleSpec.h @@ -24,24 +24,24 @@ class JSI_EXPORT NativeReanimatedModuleSpec : public TurboModule { public: virtual void installCoreFunctions( jsi::Runtime &rt, - const jsi::Value &valueSetter) = 0; + const jsi::Value &valueUnpacker, + const jsi::Value &layoutAnimationStartFunction) = 0; // SharedValue - virtual jsi::Value makeShareable( + virtual jsi::Value makeShareableClone( jsi::Runtime &rt, const jsi::Value &value) = 0; - virtual jsi::Value makeMutable(jsi::Runtime &rt, const jsi::Value &value) = 0; - virtual jsi::Value makeRemote(jsi::Runtime &rt, const jsi::Value &value) = 0; - // mappers - virtual jsi::Value startMapper( + // Synchronized data objects + virtual jsi::Value makeSynchronizedDataHolder( jsi::Runtime &rt, - const jsi::Value &worklet, - const jsi::Value &inputs, - const jsi::Value &outputs, - const jsi::Value &updater, - const jsi::Value &viewDescriptors) = 0; - virtual void stopMapper(jsi::Runtime &rt, const jsi::Value &mapperId) = 0; + const jsi::Value &initialShareable) = 0; + virtual jsi::Value getDataSynchronously( + jsi::Runtime &rt, + const jsi::Value &synchronizedDataHolderRef) = 0; + + // Scheduling + virtual void scheduleOnUI(jsi::Runtime &rt, const jsi::Value &worklet) = 0; // events virtual jsi::Value registerEventHandler( @@ -91,8 +91,7 @@ class JSI_EXPORT NativeReanimatedModuleSpec : public TurboModule { jsi::Runtime &rt, const jsi::Value &viewTag, const jsi::Value &type, - const jsi::Value &config, - const jsi::Value &viewSharedValue) = 0; + const jsi::Value &config) = 0; }; } // namespace reanimated diff --git a/Common/cpp/Registries/MapperRegistry.cpp b/Common/cpp/Registries/MapperRegistry.cpp deleted file mode 100644 index 3d764d78499..00000000000 --- a/Common/cpp/Registries/MapperRegistry.cpp +++ /dev/null @@ -1,147 +0,0 @@ -#include "MapperRegistry.h" -#include "Mapper.h" - -#include -#include -#include -#include - -namespace reanimated { - -void MapperRegistry::startMapper(std::shared_ptr mapper) { - mappers[mapper->id] = mapper; - updatedSinceLastExecute = true; -} - -void MapperRegistry::stopMapper(unsigned long id) { - mappers.erase(id); - updatedSinceLastExecute = true; -} - -void MapperRegistry::execute(jsi::Runtime &rt) { - if (updatedSinceLastExecute) { - updateOrder(); - updatedSinceLastExecute = false; - } - for (auto &mapper : sortedMappers) { - if (mapper->dirty) { - mapper->execute(rt); - } - } -} - -bool MapperRegistry::needRunOnRender() { - return updatedSinceLastExecute; // TODO: also run if input nodes are dirty -} - -void MapperRegistry::updateOrder() { // Topological sorting - sortedMappers.clear(); - - struct NodeID { - std::shared_ptr mapper; - std::shared_ptr mutableValue; - - explicit NodeID(std::shared_ptr mapper) { - if (mapper == nullptr) { - throw std::runtime_error( - "Graph couldn't be sorted (Mapper cannot be nullptr)"); - } - this->mapper = mapper; - } - - explicit NodeID(std::shared_ptr mutableValue) { - if (mutableValue == nullptr) { - throw std::runtime_error( - "Graph couldn't be sorted (Mutable cannot be nullptr)"); - } - this->mutableValue = mutableValue; - } - - bool isMutable() const { - return mutableValue != nullptr; - } - - bool operator<(const NodeID &other) const { - if (isMutable() != other.isMutable()) - return isMutable() < other.isMutable(); - - if (isMutable()) { - return mutableValue < other.mutableValue; - } - - return mapper < other.mapper; - } - }; - - std::map deg; - - std::map, std::vector>> - mappersThatUseSharedValue; - - std::set> nodes; - - std::function update = [&](NodeID id) { - auto entry = std::make_pair(deg[id], id); - if (nodes.find(entry) == nodes.end()) - return; - nodes.erase(entry); - entry.first--; - deg[id]--; - nodes.insert(entry); - }; - - for (auto &entry : mappers) { - auto id = NodeID(entry.second); - auto &mapper = entry.second; - deg[id] = mapper->inputs.size(); - nodes.insert(std::make_pair(deg[id], id)); - - for (auto sharedValue : mapper->inputs) { - auto sharedValueID = NodeID(sharedValue); - mappersThatUseSharedValue[sharedValue].push_back(mapper); - if (deg.count(sharedValueID) == 0) { - deg[sharedValueID] = 0; - } - } - - for (auto sharedValue : mapper->outputs) { - deg[NodeID(sharedValue)]++; - } - } - - for (auto &entry : deg) { - auto id = entry.first; - if (id.isMutable()) { - nodes.insert(std::make_pair(entry.second, id)); - } - } - - while (nodes.size() > 0 && nodes.begin()->first == 0) { - auto entry = *nodes.begin(); - nodes.erase(entry); - - auto id = entry.second; - std::vector toUpdate; - - if (id.isMutable()) { - for (auto id : mappersThatUseSharedValue[id.mutableValue]) { - toUpdate.push_back(NodeID(id)); - } - } else { - for (auto sharedValue : id.mapper->outputs) { - toUpdate.push_back(NodeID(sharedValue)); - } - - sortedMappers.push_back(id.mapper); - } - - for (auto &id : toUpdate) - update(id); - } - - if (nodes.size() > 0) { - throw std::runtime_error("Cycle in mappers graph!"); - } -} - -} // namespace reanimated diff --git a/Common/cpp/Registries/MapperRegistry.h b/Common/cpp/Registries/MapperRegistry.h deleted file mode 100644 index 08ac47c9e4d..00000000000 --- a/Common/cpp/Registries/MapperRegistry.h +++ /dev/null @@ -1,29 +0,0 @@ -#pragma once - -#include -#include -#include -#include - -using namespace facebook; - -namespace reanimated { - -class Mapper; - -class MapperRegistry { - std::unordered_map> mappers; - std::vector> sortedMappers; - void updateOrder(); - bool updatedSinceLastExecute = false; - - public: - void startMapper(std::shared_ptr mapper); - void stopMapper(unsigned long id); - - void execute(jsi::Runtime &rt); - - bool needRunOnRender(); -}; - -} // namespace reanimated diff --git a/Common/cpp/Registries/WorkletsCache.cpp b/Common/cpp/Registries/WorkletsCache.cpp deleted file mode 100644 index 869543abb59..00000000000 --- a/Common/cpp/Registries/WorkletsCache.cpp +++ /dev/null @@ -1,45 +0,0 @@ -#include "WorkletsCache.h" -#include "FrozenObject.h" -#include "ShareableValue.h" - -#include -#include - -using namespace facebook; - -namespace reanimated { - -jsi::Value eval(jsi::Runtime &rt, const char *code) { - return rt.global().getPropertyAsFunction(rt, "eval").call(rt, code); -} - -jsi::Function function(jsi::Runtime &rt, const std::string &code) { - return eval(rt, ("(" + code + ")").c_str()).getObject(rt).getFunction(rt); -} - -std::shared_ptr WorkletsCache::getFunction( - jsi::Runtime &rt, - std::shared_ptr frozenObj) { - long long workletHash = - ValueWrapper::asNumber(frozenObj->map["__workletHash"]->valueContainer); - if (worklets.count(workletHash) == 0) { - // We need to add a newline before the closing bracket, because in debug - // builds the last line will be a source map, which is a comment and that - // would make the bracket part of the comment and cause an error due to a - // missing closing bracket. - auto codeBuffer = std::make_shared( - "(" + - ValueWrapper::asString(frozenObj->map["asString"]->valueContainer) + - "\n)"); - auto func = rt.evaluateJavaScript( - codeBuffer, - ValueWrapper::asString( - frozenObj->map["__location"]->valueContainer)) - .asObject(rt) - .asFunction(rt); - worklets[workletHash] = std::make_shared(std::move(func)); - } - return worklets[workletHash]; -} - -} // namespace reanimated diff --git a/Common/cpp/Registries/WorkletsCache.h b/Common/cpp/Registries/WorkletsCache.h deleted file mode 100644 index b1b0e48957a..00000000000 --- a/Common/cpp/Registries/WorkletsCache.h +++ /dev/null @@ -1,24 +0,0 @@ -#pragma once - -#include -#include -#include -#include - -namespace reanimated { - -using namespace facebook; - -class FrozenObject; - -class WorkletsCache { - private: - std::unordered_map> worklets; - - public: - std::shared_ptr getFunction( - jsi::Runtime &rt, - std::shared_ptr frozenObj); -}; - -} // namespace reanimated diff --git a/Common/cpp/SharedItems/FrozenObject.cpp b/Common/cpp/SharedItems/FrozenObject.cpp deleted file mode 100644 index 69c0a4ac8fb..00000000000 --- a/Common/cpp/SharedItems/FrozenObject.cpp +++ /dev/null @@ -1,35 +0,0 @@ -#include "FrozenObject.h" -#include "RuntimeManager.h" -#include "ShareableValue.h" -#include "SharedParent.h" - -namespace reanimated { - -FrozenObject::FrozenObject( - jsi::Runtime &rt, - const jsi::Object &object, - RuntimeManager *runtimeManager) { - auto propertyNames = object.getPropertyNames(rt); - const size_t count = propertyNames.size(rt); - namesOrder.reserve(count); - for (size_t i = 0; i < count; i++) { - auto propertyName = propertyNames.getValueAtIndex(rt, i).asString(rt); - namesOrder.push_back(propertyName.utf8(rt)); - std::string nameStr = propertyName.utf8(rt); - map[nameStr] = ShareableValue::adapt( - rt, object.getProperty(rt, propertyName), runtimeManager); - this->containsHostFunction |= map[nameStr]->containsHostFunction; - } -} - -jsi::Object FrozenObject::shallowClone(jsi::Runtime &rt) { - jsi::Object object(rt); - for (auto propName : namesOrder) { - auto value = map[propName]; - object.setProperty( - rt, jsi::String::createFromUtf8(rt, propName), value->getValue(rt)); - } - return object; -} - -} // namespace reanimated diff --git a/Common/cpp/SharedItems/FrozenObject.h b/Common/cpp/SharedItems/FrozenObject.h deleted file mode 100644 index a5cf9e2cb40..00000000000 --- a/Common/cpp/SharedItems/FrozenObject.h +++ /dev/null @@ -1,36 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include "RuntimeManager.h" -#include "SharedParent.h" -#include "WorkletsCache.h" - -using namespace facebook; - -namespace reanimated { - -class FrozenObject : public jsi::HostObject { - friend WorkletsCache; - friend void extractMutables( - jsi::Runtime &rt, - std::shared_ptr sv, - std::vector> &res); - - private: - std::unordered_map> map; - std::vector namesOrder; - - public: - FrozenObject( - jsi::Runtime &rt, - const jsi::Object &object, - RuntimeManager *runtimeManager); - jsi::Object shallowClone(jsi::Runtime &rt); - bool containsHostFunction = false; -}; - -} // namespace reanimated diff --git a/Common/cpp/SharedItems/HostFunctionHandler.h b/Common/cpp/SharedItems/HostFunctionHandler.h deleted file mode 100644 index 9096d42ec1e..00000000000 --- a/Common/cpp/SharedItems/HostFunctionHandler.h +++ /dev/null @@ -1,28 +0,0 @@ -#pragma once - -#include -#include -#include - -using namespace facebook; - -namespace reanimated { - -struct HostFunctionHandler : jsi::HostObject { - std::shared_ptr pureFunction; - std::string functionName; - jsi::Runtime *hostRuntime; - jsi::HostObject a; - - HostFunctionHandler(std::shared_ptr f, jsi::Runtime &rt) { - pureFunction = f; - functionName = f->getProperty(rt, "name").asString(rt).utf8(rt); - hostRuntime = &rt; - } - - std::shared_ptr getPureFunction() { - return pureFunction; - } -}; - -} // namespace reanimated diff --git a/Common/cpp/SharedItems/MutableValue.cpp b/Common/cpp/SharedItems/MutableValue.cpp deleted file mode 100644 index eebbb7753e0..00000000000 --- a/Common/cpp/SharedItems/MutableValue.cpp +++ /dev/null @@ -1,134 +0,0 @@ -#include "MutableValue.h" -#include "RuntimeDecorator.h" -#include "RuntimeManager.h" -#include "ShareableValue.h" -#include "SharedParent.h" - -#include - -namespace reanimated { - -void MutableValue::setValue(jsi::Runtime &rt, const jsi::Value &newValue) { - std::lock_guard lock(readWriteMutex); - value = ShareableValue::adapt(rt, newValue, runtimeManager); - - std::shared_ptr thiz = shared_from_this(); - auto notifyListeners = [thiz]() { - for (auto listener : thiz->listeners) { - listener.second(); - } - }; - if (RuntimeDecorator::isUIRuntime(rt)) { - notifyListeners(); - } else { - runtimeManager->scheduler->scheduleOnUI( - [notifyListeners] { notifyListeners(); }); - } -} - -jsi::Value MutableValue::getValue(jsi::Runtime &rt) { - std::lock_guard lock(readWriteMutex); - return value->getValue(rt); -} - -void MutableValue::set( - jsi::Runtime &rt, - const jsi::PropNameID &name, - const jsi::Value &newValue) { - auto propName = name.utf8(rt); - if (!runtimeManager->valueSetter) { - throw jsi::JSError( - rt, - "Value-Setter is not yet configured! Make sure the core-functions are installed."); - } - - if (RuntimeDecorator::isUIRuntime(rt)) { - // UI thread - if (propName == "value") { - auto setterProxy = jsi::Object::createFromHostObject( - rt, std::make_shared(shared_from_this())); - runtimeManager->valueSetter->getValue(rt) - .asObject(rt) - .asFunction(rt) - .callWithThis(rt, setterProxy, newValue); - } else if (propName == "_animation") { - // TODO: assert to allow animation to be set from UI only - if (animation.expired()) { - animation = getWeakRef(rt); - } - *animation.lock() = jsi::Value(rt, newValue); - } else if (propName == "_value") { - auto setter = - std::make_shared(shared_from_this()); - setter->set(rt, jsi::PropNameID::forAscii(rt, "_value"), newValue); - } - } else { - // React-JS Thread or another threaded Runtime. - if (propName == "value") { - auto shareable = ShareableValue::adapt(rt, newValue, runtimeManager); - runtimeManager->scheduler->scheduleOnUI([this, shareable] { - jsi::Runtime &rt = *this->runtimeManager->runtime.get(); - auto setterProxy = jsi::Object::createFromHostObject( - rt, std::make_shared(shared_from_this())); - jsi::Value newValue = shareable->getValue(rt); - runtimeManager->valueSetter->getValue(rt) - .asObject(rt) - .asFunction(rt) - .callWithThis(rt, setterProxy, newValue); - }); - } - } -} - -jsi::Value MutableValue::get(jsi::Runtime &rt, const jsi::PropNameID &name) { - auto propName = name.utf8(rt); - - if (propName == "value") { - return getValue(rt); - } - - if (RuntimeDecorator::isUIRuntime(rt)) { - // _value and _animation should be accessed from UI only - if (propName == "_value") { - return getValue(rt); - } else if (propName == "_animation") { - // TODO: assert to allow animation to be read from UI only - if (animation.expired()) { - animation = getWeakRef(rt); - } - return jsi::Value(rt, *(animation.lock())); - } - } - - return jsi::Value::undefined(); -} - -std::vector MutableValue::getPropertyNames(jsi::Runtime &rt) { - std::vector result; - result.push_back(jsi::PropNameID::forUtf8(rt, std::string("value"))); - return result; -} - -MutableValue::MutableValue( - jsi::Runtime &rt, - const jsi::Value &initial, - RuntimeManager *runtimeManager, - std::shared_ptr s) - : StoreUser(s, *runtimeManager), - runtimeManager(runtimeManager), - value(ShareableValue::adapt(rt, initial, runtimeManager)) {} - -unsigned long int MutableValue::addListener( - unsigned long id, - std::function listener) { - listeners[id] = listener; - return id; -} - -void MutableValue::removeListener(unsigned long listenerId) { - if (listeners.count(listenerId) > 0) { - listeners.erase(listenerId); - } -} - -} // namespace reanimated diff --git a/Common/cpp/SharedItems/MutableValue.h b/Common/cpp/SharedItems/MutableValue.h deleted file mode 100644 index b00f4eed478..00000000000 --- a/Common/cpp/SharedItems/MutableValue.h +++ /dev/null @@ -1,54 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include "JSIStoreValueUser.h" -#include "LayoutAnimationsProxy.h" -#include "MutableValueSetterProxy.h" -#include "RuntimeManager.h" -#include "SharedParent.h" - -using namespace facebook; - -namespace reanimated { - -class MutableValue : public jsi::HostObject, - public std::enable_shared_from_this, - public StoreUser { - private: - friend MutableValueSetterProxy; - friend LayoutAnimationsProxy; - - private: - RuntimeManager *runtimeManager; - std::mutex readWriteMutex; - std::shared_ptr value; - std::weak_ptr animation; - std::map> listeners; - - public: - void setValue(jsi::Runtime &rt, const jsi::Value &newValue); - jsi::Value getValue(jsi::Runtime &rt); - - public: - MutableValue( - jsi::Runtime &rt, - const jsi::Value &initial, - RuntimeManager *runtimeManager, - std::shared_ptr s); - - public: - void - set(jsi::Runtime &rt, const jsi::PropNameID &name, const jsi::Value &value); - jsi::Value get(jsi::Runtime &rt, const jsi::PropNameID &name); - std::vector getPropertyNames(jsi::Runtime &rt); - unsigned long addListener( - unsigned long listenerId, - std::function listener); - void removeListener(unsigned long listenerId); -}; - -} // namespace reanimated diff --git a/Common/cpp/SharedItems/MutableValueSetterProxy.cpp b/Common/cpp/SharedItems/MutableValueSetterProxy.cpp deleted file mode 100644 index 87336dd8255..00000000000 --- a/Common/cpp/SharedItems/MutableValueSetterProxy.cpp +++ /dev/null @@ -1,47 +0,0 @@ -#include "MutableValueSetterProxy.h" -#include -#include "MutableValue.h" -#include "SharedParent.h" - -using namespace facebook; - -namespace reanimated { - -void MutableValueSetterProxy::set( - jsi::Runtime &rt, - const jsi::PropNameID &name, - const jsi::Value &newValue) { - auto propName = name.utf8(rt); - if (propName == "_value") { - mutableValue->setValue(rt, newValue); - } else if (propName == "_animation") { - // TODO: assert to allow animation to be set from UI only - if (mutableValue->animation.expired()) { - mutableValue->animation = mutableValue->getWeakRef(rt); - } - *mutableValue->animation.lock() = jsi::Value(rt, newValue); - } else if (propName == "value") { - // you call `this.value` inside of value setter, we should throw - } -} - -jsi::Value MutableValueSetterProxy::get( - jsi::Runtime &rt, - const jsi::PropNameID &name) { - auto propName = name.utf8(rt); - - if (propName == "value") { - return mutableValue->getValue(rt); - } else if (propName == "_value") { - return mutableValue->getValue(rt); - } else if (propName == "_animation") { - if (mutableValue->animation.expired()) { - mutableValue->animation = mutableValue->getWeakRef(rt); - } - return jsi::Value(rt, *mutableValue->animation.lock()); - } - - return jsi::Value::undefined(); -} - -} // namespace reanimated diff --git a/Common/cpp/SharedItems/MutableValueSetterProxy.h b/Common/cpp/SharedItems/MutableValueSetterProxy.h deleted file mode 100644 index 423f0fcb80a..00000000000 --- a/Common/cpp/SharedItems/MutableValueSetterProxy.h +++ /dev/null @@ -1,25 +0,0 @@ -#pragma once - -#include -#include -#include -#include "SharedParent.h" - -using namespace facebook; - -namespace reanimated { - -class MutableValueSetterProxy : public jsi::HostObject { - private: - friend MutableValue; - std::shared_ptr mutableValue; - - public: - explicit MutableValueSetterProxy(std::shared_ptr mutableValue) - : mutableValue(std::move(mutableValue)) {} - void - set(jsi::Runtime &rt, const jsi::PropNameID &name, const jsi::Value &value); - jsi::Value get(jsi::Runtime &rt, const jsi::PropNameID &name); -}; - -} // namespace reanimated diff --git a/Common/cpp/SharedItems/RemoteObject.cpp b/Common/cpp/SharedItems/RemoteObject.cpp deleted file mode 100644 index 0795add9202..00000000000 --- a/Common/cpp/SharedItems/RemoteObject.cpp +++ /dev/null @@ -1,45 +0,0 @@ -#include "RemoteObject.h" -#include -#include "RuntimeDecorator.h" -#include "SharedParent.h" - -using namespace facebook; - -namespace reanimated { - -void RemoteObject::maybeInitializeOnWorkletRuntime(jsi::Runtime &rt) { - if (initializer.get() != nullptr) { - backing = getWeakRef(rt); - *backing.lock() = initializer->shallowClone(rt); - initializer = nullptr; - } -} - -jsi::Value RemoteObject::get(jsi::Runtime &rt, const jsi::PropNameID &name) { - if (RuntimeDecorator::isWorkletRuntime(rt)) { - return backing.lock()->getObject(rt).getProperty(rt, name); - } - return jsi::Value::undefined(); -} - -void RemoteObject::set( - jsi::Runtime &rt, - const jsi::PropNameID &name, - const jsi::Value &value) { - if (RuntimeDecorator::isWorkletRuntime(rt)) { - backing.lock()->getObject(rt).setProperty(rt, name, value); - } - // TODO: we should throw if trying to update remote from host runtime -} - -std::vector RemoteObject::getPropertyNames(jsi::Runtime &rt) { - std::vector res; - auto propertyNames = backing.lock()->getObject(rt).getPropertyNames(rt); - for (size_t i = 0, size = propertyNames.size(rt); i < size; i++) { - res.push_back(jsi::PropNameID::forString( - rt, propertyNames.getValueAtIndex(rt, i).asString(rt))); - } - return res; -} - -} // namespace reanimated diff --git a/Common/cpp/SharedItems/RemoteObject.h b/Common/cpp/SharedItems/RemoteObject.h deleted file mode 100644 index 46a437d6b3a..00000000000 --- a/Common/cpp/SharedItems/RemoteObject.h +++ /dev/null @@ -1,35 +0,0 @@ -#pragma once - -#include -#include - -#include "FrozenObject.h" -#include "JSIStoreValueUser.h" -#include "SharedParent.h" - -using namespace facebook; - -namespace reanimated { - -class RemoteObject : public jsi::HostObject, public StoreUser { - private: - std::weak_ptr backing; - std::unique_ptr initializer; - - public: - void maybeInitializeOnWorkletRuntime(jsi::Runtime &rt); - RemoteObject( - jsi::Runtime &rt, - const jsi::Object &object, - RuntimeManager *runtimeManager, - std::shared_ptr s) - : StoreUser(s, *runtimeManager), - initializer( - std::make_unique(rt, object, runtimeManager)) {} - void - set(jsi::Runtime &rt, const jsi::PropNameID &name, const jsi::Value &value); - jsi::Value get(jsi::Runtime &rt, const jsi::PropNameID &name); - std::vector getPropertyNames(jsi::Runtime &rt); -}; - -} // namespace reanimated diff --git a/Common/cpp/SharedItems/RuntimeManager.h b/Common/cpp/SharedItems/RuntimeManager.h index b0cf203e19b..7a0e546fec0 100644 --- a/Common/cpp/SharedItems/RuntimeManager.h +++ b/Common/cpp/SharedItems/RuntimeManager.h @@ -3,11 +3,8 @@ #include #include #include "ErrorHandler.h" -#include "JSIStoreValueUser.h" #include "RuntimeDecorator.h" #include "Scheduler.h" -#include "ShareableValue.h" -#include "WorkletsCache.h" namespace reanimated { @@ -23,24 +20,10 @@ class RuntimeManager { std::shared_ptr errorHandler, std::shared_ptr scheduler, RuntimeType runtimeType = RuntimeType::Worklet) - : runtime(runtime), - errorHandler(errorHandler), - scheduler(scheduler), - workletsCache(std::make_unique()), - storeUserData(std::make_shared()) { + : runtime(runtime), errorHandler(errorHandler), scheduler(scheduler) { RuntimeDecorator::registerRuntime(this->runtime.get(), runtimeType); } - virtual ~RuntimeManager() { - clearStore(); - } - - public: - /** - Holds the jsi::Function worklet that is responsible for updating values in - JS. Can be null. - */ - std::shared_ptr valueSetter; /** Holds the jsi::Runtime this RuntimeManager is managing. */ @@ -54,22 +37,6 @@ class RuntimeManager { React-JS Thread. */ std::shared_ptr scheduler; - /** - Holds a list of adapted Worklets which are cached to avoid unneccessary - recreation. - */ - std::unique_ptr workletsCache; - /** - Holds the JSI-Value Store where JSI::Values are cached on a - per-RuntimeManager basis. - */ - std::shared_ptr storeUserData; - - private: - void clearStore() { - const std::lock_guard lock(storeUserData->storeMutex); - storeUserData->store.clear(); - } }; } // namespace reanimated diff --git a/Common/cpp/SharedItems/ShareableValue.cpp b/Common/cpp/SharedItems/ShareableValue.cpp deleted file mode 100644 index a1c77e9e08c..00000000000 --- a/Common/cpp/SharedItems/ShareableValue.cpp +++ /dev/null @@ -1,529 +0,0 @@ -#include -#include - -#ifdef RCT_NEW_ARCH_ENABLED -#include -#endif - -#include "FrozenObject.h" -#include "MutableValue.h" -#include "MutableValueSetterProxy.h" -#include "RemoteObject.h" -#include "RuntimeDecorator.h" -#include "RuntimeManager.h" -#include "ShareableValue.h" -#include "SharedParent.h" - -namespace reanimated { -class ShareableValue; -const char *HIDDEN_HOST_OBJECT_PROP = "__reanimatedHostObjectRef"; -const char *ALREADY_CONVERTED = "__alreadyConverted"; -const char *CALL_ASYNC = "__callAsync"; -const char *PRIMAL_FUNCTION = "__primalFunction"; -const char *CALLBACK_ERROR_SUFFIX = - "\n\nPossible solutions are:\n" - "a) If you want to synchronously execute this method, mark it as a Worklet\n" - "b) If you want to execute this method on the JS thread, wrap it using runOnJS"; - -void addHiddenProperty( - jsi::Runtime &rt, - jsi::Value &&value, - const jsi::Object &obj, - const char *name) { - jsi::Object globalObject = rt.global().getPropertyAsObject(rt, "Object"); - jsi::Function defineProperty = - globalObject.getPropertyAsFunction(rt, "defineProperty"); - jsi::String internalPropName = jsi::String::createFromUtf8(rt, name); - jsi::Object paramForDefineProperty(rt); - paramForDefineProperty.setProperty(rt, "enumerable", false); - paramForDefineProperty.setProperty(rt, "value", value); - defineProperty.call(rt, obj, internalPropName, paramForDefineProperty); -} - -void freeze(jsi::Runtime &rt, const jsi::Object &obj) { - jsi::Object globalObject = rt.global().getPropertyAsObject(rt, "Object"); - jsi::Function freeze = globalObject.getPropertyAsFunction(rt, "freeze"); - freeze.call(rt, obj); -} - -void ShareableValue::adaptCache(jsi::Runtime &rt, const jsi::Value &value) { - // when adapting from host object we can assign cached value immediately such - // that we avoid running `toJSValue` in the future when given object is - // accessed - if (RuntimeDecorator::isWorkletRuntime(rt)) { - if (remoteValue.expired()) { - remoteValue = getWeakRef(rt); - } - (*remoteValue.lock()) = jsi::Value(rt, value); - } else { - hostValue = std::make_unique(rt, value); - } -} - -void ShareableValue::adapt( - jsi::Runtime &rt, - const jsi::Value &value, - ValueType objectType) { - if (value.isObject()) { - jsi::Object object = value.asObject(rt); - jsi::Value hiddenValue = object.getProperty(rt, HIDDEN_HOST_OBJECT_PROP); - if (!(hiddenValue.isUndefined())) { - jsi::Object hiddenProperty = hiddenValue.asObject(rt); - if (hiddenProperty.isHostObject(rt)) { - type = ValueType::FrozenObjectType; - if (object.hasProperty(rt, "__workletHash") && object.isFunction(rt)) { - type = ValueType::WorkletFunctionType; - } - valueContainer = std::make_unique( - hiddenProperty.getHostObject(rt)); - if (object.hasProperty(rt, ALREADY_CONVERTED)) { - adaptCache(rt, value); - } - return; - } - } - } - - if (objectType == ValueType::MutableValueType) { - type = ValueType::MutableValueType; - valueContainer = - std::make_unique(std::make_shared( - rt, value, runtimeManager, runtimeManager->scheduler)); - } else if (value.isUndefined()) { - type = ValueType::UndefinedType; - } else if (value.isNull()) { - type = ValueType::NullType; - } else if (value.isBool()) { - type = ValueType::BoolType; - valueContainer = std::make_unique(value.getBool()); - } else if (value.isNumber()) { - type = ValueType::NumberType; - valueContainer = std::make_unique(value.asNumber()); - } else if (value.isString()) { - type = ValueType::StringType; - valueContainer = - std::make_unique(value.asString(rt).utf8(rt)); - } else if (value.isObject()) { - auto object = value.asObject(rt); - if (object.isFunction(rt)) { - if (object.getProperty(rt, "__workletHash").isUndefined()) { - // not a worklet, we treat this as a host function - type = ValueType::HostFunctionType; - containsHostFunction = true; - - // Check if it's a hostFunction wrapper - jsi::Value primalFunction = object.getProperty(rt, PRIMAL_FUNCTION); - if (!primalFunction.isUndefined()) { - jsi::Object handlerAsObject = primalFunction.asObject(rt); - std::shared_ptr handler = - handlerAsObject.getHostObject(rt); - valueContainer = std::make_unique(handler); - } else { - valueContainer = std::make_unique( - std::make_shared( - std::make_shared(object.asFunction(rt)), rt)); - } - - } else { - // a worklet - type = ValueType::WorkletFunctionType; - valueContainer = std::make_unique( - std::make_shared(rt, object, runtimeManager)); - auto &frozenObject = ValueWrapper::asFrozenObject(valueContainer); - containsHostFunction |= frozenObject->containsHostFunction; - if (RuntimeDecorator::isReactRuntime(rt) && !containsHostFunction) { - addHiddenProperty( - rt, - createHost(rt, frozenObject), - object, - HIDDEN_HOST_OBJECT_PROP); - } - } - } else if (object.isArray(rt)) { - type = ValueType::FrozenArrayType; - auto array = object.asArray(rt); - valueContainer = std::make_unique(); - auto &frozenArray = ValueWrapper::asFrozenArray(valueContainer); - for (size_t i = 0, size = array.size(rt); i < size; i++) { - auto sv = adapt(rt, array.getValueAtIndex(rt, i), runtimeManager); - containsHostFunction |= sv->containsHostFunction; - frozenArray.push_back(sv); - } - } else if (object.isHostObject(rt)) { - type = ValueType::MutableValueType; - valueContainer = std::make_unique( - object.getHostObject(rt)); - adaptCache(rt, value); - } else if (object.isHostObject(rt)) { - type = ValueType::RemoteObjectType; - valueContainer = std::make_unique( - object.getHostObject(rt)); - adaptCache(rt, value); - } else if (objectType == ValueType::RemoteObjectType) { - type = ValueType::RemoteObjectType; - valueContainer = - std::make_unique(std::make_shared( - rt, object, runtimeManager, runtimeManager->scheduler)); -#ifdef RCT_NEW_ARCH_ENABLED - } else if (object.isHostObject(rt)) { - type = ValueType::ShadowNodeType; - auto shadowNode = object.getHostObject(rt)->shadowNode; - valueContainer = std::make_unique(shadowNode); - adaptCache(rt, value); -#endif - } else { - // create frozen object based on a copy of a given object - type = ValueType::FrozenObjectType; - valueContainer = std::make_unique( - std::make_shared(rt, object, runtimeManager)); - auto &frozenObject = ValueWrapper::asFrozenObject(valueContainer); - containsHostFunction |= frozenObject->containsHostFunction; - if (RuntimeDecorator::isReactRuntime(rt)) { - if (!containsHostFunction) { - addHiddenProperty( - rt, - createHost(rt, frozenObject), - object, - HIDDEN_HOST_OBJECT_PROP); - } - freeze(rt, object); - } - } - } else if (value.isSymbol()) { - type = ValueType::StringType; - valueContainer = - std::make_unique(value.asSymbol(rt).toString(rt)); - } else { - throw "Invalid value type"; - } -} - -std::shared_ptr ShareableValue::adapt( - jsi::Runtime &rt, - const jsi::Value &value, - RuntimeManager *runtimeManager, - ValueType valueType) { - auto sv = std::shared_ptr( - new ShareableValue(runtimeManager, runtimeManager->scheduler)); - sv->adapt(rt, value, valueType); - return sv; -} - -jsi::Value ShareableValue::getValue(jsi::Runtime &rt) { - // TODO: maybe we can cache toJSValue results on a per-runtime basis, need to - // avoid ref loops - if (&rt == runtimeManager->runtime.get()) { - // Getting value on the same runtime where it was created, prepare - // remoteValue - if (remoteValue.expired()) { - remoteValue = getWeakRef(rt); - } - - if (remoteValue.lock()->isUndefined()) { - (*remoteValue.lock()) = toJSValue(rt); - } - return jsi::Value(rt, *remoteValue.lock()); - } else { - // Getting value on a different runtime than where it was created from, - // prepare hostValue - if (hostValue.get() == nullptr) { - hostValue = std::make_unique(toJSValue(rt)); - } - return jsi::Value(rt, *hostValue); - } -} - -jsi::Object ShareableValue::createHost( - jsi::Runtime &rt, - std::shared_ptr host) { - return jsi::Object::createFromHostObject(rt, host); -} - -jsi::Value createFrozenWrapper( - jsi::Runtime &rt, - std::shared_ptr frozenObject) { - jsi::Object __reanimatedHiddenHost = - jsi::Object::createFromHostObject(rt, frozenObject); - jsi::Object obj = frozenObject->shallowClone(rt); - jsi::Object globalObject = rt.global().getPropertyAsObject(rt, "Object"); - jsi::Function freeze = globalObject.getPropertyAsFunction(rt, "freeze"); - if (!frozenObject->containsHostFunction) { - addHiddenProperty( - rt, std::move(__reanimatedHiddenHost), obj, HIDDEN_HOST_OBJECT_PROP); - addHiddenProperty(rt, true, obj, ALREADY_CONVERTED); - } - return freeze.call(rt, obj); -} - -jsi::Value ShareableValue::toJSValue(jsi::Runtime &rt) { - switch (type) { - case ValueType::UndefinedType: - return jsi::Value::undefined(); - case ValueType::NullType: - return jsi::Value::null(); - case ValueType::BoolType: - return jsi::Value(ValueWrapper::asBoolean(valueContainer)); - case ValueType::NumberType: - return jsi::Value(ValueWrapper::asNumber(valueContainer)); - case ValueType::StringType: { - auto &stringValue = ValueWrapper::asString(valueContainer); - return jsi::Value(rt, jsi::String::createFromUtf8(rt, stringValue)); - } - case ValueType::FrozenObjectType: { - auto &frozenObject = ValueWrapper::asFrozenObject(valueContainer); - return createFrozenWrapper(rt, frozenObject); - } - case ValueType::FrozenArrayType: { - auto &frozenArray = ValueWrapper::asFrozenArray(valueContainer); - jsi::Array array(rt, frozenArray.size()); - for (size_t i = 0; i < frozenArray.size(); i++) { - array.setValueAtIndex(rt, i, frozenArray[i]->toJSValue(rt)); - } - return array; - } - case ValueType::RemoteObjectType: { - auto &remoteObject = ValueWrapper::asRemoteObject(valueContainer); - if (RuntimeDecorator::isWorkletRuntime(rt)) { - remoteObject->maybeInitializeOnWorkletRuntime(rt); - } - return createHost(rt, remoteObject); - } - case ValueType::MutableValueType: { - auto &mutableObject = ValueWrapper::asMutableValue(valueContainer); - return createHost(rt, mutableObject); - } -#ifdef RCT_NEW_ARCH_ENABLED - case ValueType::ShadowNodeType: { - auto &shadowNode = ValueWrapper::asShadowNode(valueContainer); - return createHost(rt, std::make_shared(shadowNode)); - } -#endif - case ValueType::HostFunctionType: { - auto hostFunctionWrapper = - ValueWrapper::asHostFunctionWrapper(valueContainer); - auto &hostRuntime = hostFunctionWrapper->value->hostRuntime; - if (hostRuntime == &rt) { - // function is accessed from the same runtime it was crated, we just - // return same function obj - return jsi::Value( - rt, *hostFunctionWrapper->value->getPureFunction().get()); - } else { - // function is accessed from a different runtime, we wrap function in - // host func that'd enqueue call on an appropriate thread - - auto runtimeManager = this->runtimeManager; - auto hostFunction = hostFunctionWrapper->value; - - auto warnFunction = [runtimeManager, hostFunction]( - jsi::Runtime &rt, - const jsi::Value &thisValue, - const jsi::Value *args, - size_t count) -> jsi::Value { - jsi::Value jsThis = rt.global().getProperty(rt, "jsThis"); - std::string workletLocation = jsThis.asObject(rt) - .getProperty(rt, "__location") - .toString(rt) - .utf8(rt); - std::string exceptionMessage = "Tried to synchronously call "; - if (hostFunction->functionName.empty()) { - exceptionMessage += "anonymous function"; - } else { - exceptionMessage += "function {" + hostFunction->functionName + "}"; - } - exceptionMessage += - " from a different thread.\n\nOccurred in worklet location: "; - exceptionMessage += workletLocation; - exceptionMessage += CALLBACK_ERROR_SUFFIX; - runtimeManager->errorHandler->setError(exceptionMessage); - runtimeManager->errorHandler->raise(); - - return jsi::Value::undefined(); - }; - - auto clb = [runtimeManager, hostFunction, hostRuntime]( - jsi::Runtime &rt, - const jsi::Value &thisValue, - const jsi::Value *args, - size_t count) -> jsi::Value { - // TODO: we should find thread based on runtime such that we could - // also call UI methods from RN and not only RN methods from UI - - std::vector> params; - for (int i = 0; i < count; ++i) { - params.push_back( - ShareableValue::adapt(rt, args[i], runtimeManager)); - } - - std::function job = [hostFunction, hostRuntime, params] { - jsi::Value *args = new jsi::Value[params.size()]; - for (int i = 0; i < params.size(); ++i) { - args[i] = params[i]->getValue(*hostRuntime); - } - jsi::Value returnedValue = - hostFunction->getPureFunction().get()->call( - *hostRuntime, - static_cast(args), - static_cast(params.size())); - - delete[] args; - // ToDo use returned value to return promise - }; - - runtimeManager->scheduler->scheduleOnJS(job); - return jsi::Value::undefined(); - }; - jsi::Function wrapperFunction = jsi::Function::createFromHostFunction( - rt, jsi::PropNameID::forAscii(rt, "hostFunction"), 0, warnFunction); - jsi::Function res = jsi::Function::createFromHostFunction( - rt, jsi::PropNameID::forAscii(rt, "hostFunction"), 0, clb); - addHiddenProperty(rt, std::move(res), wrapperFunction, CALL_ASYNC); - jsi::Object functionHandler = - createHost(rt, hostFunctionWrapper->value); - addHiddenProperty( - rt, std::move(functionHandler), wrapperFunction, PRIMAL_FUNCTION); - return wrapperFunction; - } - } - case ValueType::WorkletFunctionType: { - auto runtimeManager = this->runtimeManager; - auto &frozenObject = ValueWrapper::asFrozenObject(this->valueContainer); - if (RuntimeDecorator::isWorkletRuntime(rt)) { - // when running on worklet thread we prep a function - - auto jsThis = std::make_shared( - frozenObject->shallowClone(*runtimeManager->runtime)); - std::shared_ptr funPtr( - runtimeManager->workletsCache->getFunction(rt, frozenObject)); - auto name = funPtr->getProperty(rt, "name").asString(rt).utf8(rt); - - auto clb = [=](jsi::Runtime &rt, - const jsi::Value &thisValue, - const jsi::Value *args, - size_t count) mutable -> jsi::Value { - const jsi::String jsThisName = - jsi::String::createFromAscii(rt, "jsThis"); - jsi::Object global = rt.global(); - jsi::Value oldJSThis = global.getProperty(rt, jsThisName); - global.setProperty(rt, jsThisName, *jsThis); // set jsThis - - jsi::Value res = jsi::Value::undefined(); - try { - if (thisValue.isObject()) { - res = - funPtr->callWithThis(rt, thisValue.asObject(rt), args, count); - } else { - res = funPtr->call(rt, args, count); - } - } catch (std::exception &e) { - std::string str = e.what(); - runtimeManager->errorHandler->setError(str); - runtimeManager->errorHandler->raise(); - } catch (...) { - if (demangleExceptionName( - abi::__cxa_current_exception_type()->name()) == - "facebook::jsi::JSError") { - throw jsi::JSError(rt, "Javascript worklet error"); - } - // TODO find out a way to get the error's message on hermes - jsi::Value location = jsThis->getProperty(rt, "__location"); - std::string str = "Javascript worklet error"; - if (location.isString()) { - str += "\nIn file: " + location.asString(rt).utf8(rt); - } - runtimeManager->errorHandler->setError(str); - runtimeManager->errorHandler->raise(); - } - global.setProperty(rt, jsThisName, oldJSThis); // clean jsThis - return res; - }; - return jsi::Function::createFromHostFunction( - rt, jsi::PropNameID::forAscii(rt, name.c_str()), 0, clb); - } else { - // when run outside of UI thread we enqueue a call on the UI thread - auto clb = [=](jsi::Runtime &rt, - const jsi::Value &thisValue, - const jsi::Value *args, - size_t count) -> jsi::Value { - // TODO: we should find thread based on runtime such that we could - // also call UI methods from RN and not only RN methods from UI - - std::vector> params; - for (int i = 0; i < count; ++i) { - params.push_back( - ShareableValue::adapt(rt, args[i], runtimeManager)); - } - - runtimeManager->scheduler->scheduleOnUI([=] { - jsi::Runtime &rt = *runtimeManager->runtime.get(); - auto jsThis = createFrozenWrapper(rt, frozenObject).getObject(rt); - auto code = - jsThis.getProperty(rt, "asString").asString(rt).utf8(rt); - std::shared_ptr funPtr( - runtimeManager->workletsCache->getFunction(rt, frozenObject)); - - jsi::Value *args = new jsi::Value[params.size()]; - for (int i = 0; i < params.size(); ++i) { - args[i] = params[i]->getValue(rt); - } - - jsi::Value returnedValue; - const jsi::String jsThisName = - jsi::String::createFromAscii(rt, "jsThis"); - jsi::Object global = rt.global(); - jsi::Value oldJSThis = global.getProperty(rt, jsThisName); - global.setProperty(rt, jsThisName, jsThis); // set jsThis - try { - returnedValue = funPtr->call( - rt, - static_cast(args), - static_cast(params.size())); - } catch (std::exception &e) { - std::string str = e.what(); - runtimeManager->errorHandler->setError(str); - runtimeManager->errorHandler->raise(); - } catch (...) { - if (demangleExceptionName( - abi::__cxa_current_exception_type()->name()) == - "facebook::jsi::JSError") { - throw jsi::JSError(rt, "Javascript worklet error"); - } - // TODO find out a way to get the error's message on hermes - jsi::Value location = jsThis.getProperty(rt, "__location"); - std::string str = "Javascript worklet error"; - if (location.isString()) { - str += "\nIn file: " + location.asString(rt).utf8(rt); - } - runtimeManager->errorHandler->setError(str); - runtimeManager->errorHandler->raise(); - } - global.setProperty(rt, jsThisName, oldJSThis); // clean jsThis - - delete[] args; - // ToDo use returned value to return promise - }); - return jsi::Value::undefined(); - }; - return jsi::Function::createFromHostFunction( - rt, jsi::PropNameID::forAscii(rt, "_workletFunction"), 0, clb); - } - } - default: { - throw "Unable to find conversion method for this type"; - } - } - throw "convert error"; -} - -std::string ShareableValue::demangleExceptionName(std::string toDemangle) { - int status = 0; - char *buff = - __cxxabiv1::__cxa_demangle(toDemangle.c_str(), nullptr, nullptr, &status); - if (!buff) { - return toDemangle; - } - std::string demangled = buff; - std::free(buff); - return demangled; -} - -} // namespace reanimated diff --git a/Common/cpp/SharedItems/ShareableValue.h b/Common/cpp/SharedItems/ShareableValue.h deleted file mode 100644 index 9d54763051d..00000000000 --- a/Common/cpp/SharedItems/ShareableValue.h +++ /dev/null @@ -1,63 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include -#include "AnimatedSensorModule.h" -#include "HostFunctionHandler.h" -#include "JSIStoreValueUser.h" -#include "LayoutAnimationsProxy.h" -#include "RuntimeManager.h" -#include "Scheduler.h" -#include "SharedParent.h" -#include "ValueWrapper.h" -#include "WorkletsCache.h" - -using namespace facebook; - -namespace reanimated { - -class ShareableValue : public std::enable_shared_from_this, - public StoreUser { - friend WorkletsCache; - friend FrozenObject; - friend LayoutAnimationsProxy; - friend NativeReanimatedModule; - friend AnimatedSensorModule; - friend void extractMutables( - jsi::Runtime &rt, - std::shared_ptr sv, - std::vector> &res); - - private: - RuntimeManager *runtimeManager; - std::unique_ptr valueContainer; - std::unique_ptr hostValue; - std::weak_ptr remoteValue; - bool containsHostFunction = false; - - ShareableValue(RuntimeManager *runtimeManager, std::shared_ptr s) - : StoreUser(s, *runtimeManager), runtimeManager(runtimeManager) {} - - jsi::Value toJSValue(jsi::Runtime &rt); - jsi::Object createHost( - jsi::Runtime &rt, - std::shared_ptr host); - void adapt(jsi::Runtime &rt, const jsi::Value &value, ValueType objectType); - void adaptCache(jsi::Runtime &rt, const jsi::Value &value); - std::string demangleExceptionName(std::string toDemangle); - - public: - ValueType type = ValueType::UndefinedType; - static std::shared_ptr adapt( - jsi::Runtime &rt, - const jsi::Value &value, - RuntimeManager *runtimeManager, - ValueType objectType = ValueType::UndefinedType); - jsi::Value getValue(jsi::Runtime &rt); -}; - -} // namespace reanimated diff --git a/Common/cpp/SharedItems/Shareables.cpp b/Common/cpp/SharedItems/Shareables.cpp new file mode 100644 index 00000000000..1cb4bbc3b9e --- /dev/null +++ b/Common/cpp/SharedItems/Shareables.cpp @@ -0,0 +1,142 @@ +#include "Shareables.h" + +using namespace facebook; + +namespace reanimated { + +CoreFunction::CoreFunction( + JSRuntimeHelper *runtimeHelper, + const jsi::Value &workletValue) + : runtimeHelper_(runtimeHelper) { + jsi::Runtime &rt = *runtimeHelper->rnRuntime(); + auto workletObject = workletValue.asObject(rt); + rnFunction_ = std::make_unique(workletObject.asFunction(rt)); + functionBody_ = + workletObject.getProperty(rt, "asString").asString(rt).utf8(rt); + location_ = workletObject.getProperty(rt, "__location").asString(rt).utf8(rt); +} + +std::unique_ptr &CoreFunction::getFunction(jsi::Runtime &rt) { + if (runtimeHelper_->isUIRuntime(rt)) { + if (uiFunction_ == nullptr) { + // maybe need to initialize UI Function + // the newline before closing paren is needed because the last line can be + // an inline comment (specifically this happens when we attach source maps + // at the end) in which case the paren won't be parsed + auto codeBuffer = std::make_shared( + "(" + functionBody_ + "\n)"); + uiFunction_ = std::make_unique( + rt.evaluateJavaScript(codeBuffer, location_) + .asObject(rt) + .asFunction(rt)); + } + return uiFunction_; + } else { + // running on the main RN runtime + return rnFunction_; + } +} + +std::shared_ptr extractShareableOrThrow( + jsi::Runtime &rt, + const jsi::Value &maybeShareableValue, + const char *errorMessage) { + if (maybeShareableValue.isObject()) { + auto object = maybeShareableValue.asObject(rt); + if (object.isHostObject(rt)) { + return object.getHostObject(rt)->value(); + } + } else if (maybeShareableValue.isUndefined()) { + return Shareable::undefined(); + } + throw std::runtime_error( + errorMessage != nullptr + ? errorMessage + : "expecting the object to be of type ShareableJSRef"); +} + +Shareable::~Shareable() {} + +#if HAS_JS_WEAK_OBJECTS + +jsi::Value RetainingShareable::getJSValue(jsi::Runtime &rt) { + jsi::Value value; + if (runtimeHelper_->isRNRuntime(rt)) { + // TODO: it is suboptimal to generate new object every time getJS is called + // on host runtime – the objects we are generating already exists and we + // should possibly just grab a hold of such object and use it here instead + // of creating a new JS representation. As far as I understand the only + // case where it can be realistically called this way is when a shared + // value is created and then accessed on the same runtime + return toJSValue(rt); + } else if (remoteValue_ == nullptr) { + value = toJSValue(rt); + remoteValue_ = std::make_unique(rt, value.asObject(rt)); + } else { + value = remoteValue_->lock(rt); + if (value.isUndefined()) { + value = toJSValue(rt); + remoteValue_ = std::make_unique(rt, value.asObject(rt)); + } + } + return value; +} + +RetainingShareable::~RetainingShareable() { + if (runtimeHelper_->uiRuntimeDestroyed) { + // The below use of unique_ptr.release prevents the smart pointer from + // calling the destructor of the kept object. This effectively results in + // leaking some memory. We do this on purpose, as sometimes we would keep + // references to JSI objects past the lifetime of its runtime (e.g., shared + // values references from the RN VM holds reference to JSI objects on the + // UI runtime). When the UI runtime is terminated, the orphaned JSI objects + // would crash the app when their destructors are called, because they + // call into a memory that's managed by the terminated runtime. We accept + // the tradeoff of leaking memory here, as it has a limited impact. This + // scenario can only occur when the React instance is torn down which + // happens in development mode during app reloads, or in production when the + // app is being shut down gracefully by the system. An alternative solution + // would require us to keep track of all JSI values that are in use which + // would require additional data structure and compute spent on bookkeeping + // that only for the sake of destroying the values in time before the + // runtime is terminated. Note that the underlying memory that jsi::Value + // refers to is managed by the VM and gets freed along with the runtime. + remoteValue_.release(); + } +} + +#endif // HAS_JS_WEAK_OBJECTS + +ShareableArray::ShareableArray( + const std::shared_ptr &runtimeHelper, + jsi::Runtime &rt, + const jsi::Array &array) + : RetainingShareable(runtimeHelper, rt, ArrayType) { + auto size = array.size(rt); + data_.reserve(size); + for (size_t i = 0; i < size; i++) { + data_.push_back(extractShareableOrThrow(rt, array.getValueAtIndex(rt, i))); + } +} + +ShareableObject::ShareableObject( + const std::shared_ptr &runtimeHelper, + jsi::Runtime &rt, + const jsi::Object &object) + : RetainingShareable(runtimeHelper, rt, ObjectType) { + auto propertyNames = object.getPropertyNames(rt); + auto size = propertyNames.size(rt); + data_.reserve(size); + for (size_t i = 0; i < size; i++) { + auto key = propertyNames.getValueAtIndex(rt, i).asString(rt); + auto value = extractShareableOrThrow(rt, object.getProperty(rt, key)); + data_.emplace_back(key.utf8(rt), value); + } +} + +std::shared_ptr Shareable::undefined() { + static auto undefined = std::make_shared(); + return undefined; +} + +} /* namespace reanimated */ diff --git a/Common/cpp/SharedItems/Shareables.h b/Common/cpp/SharedItems/Shareables.h new file mode 100644 index 00000000000..4ceddd8f617 --- /dev/null +++ b/Common/cpp/SharedItems/Shareables.h @@ -0,0 +1,448 @@ +#pragma once + +#include +#include +#include +#include +#include + +#ifdef RCT_NEW_ARCH_ENABLED +#include +#endif + +#include "ReanimatedRuntime.h" +#include "RuntimeManager.h" +#include "Scheduler.h" + +#define HAS_JS_WEAK_OBJECTS JS_RUNTIME_HERMES + +using namespace facebook; + +namespace reanimated { + +class CoreFunction; + +class JSRuntimeHelper { + private: + jsi::Runtime *rnRuntime_; // React-Native's main JS runtime + jsi::Runtime *uiRuntime_; // UI runtime created by Reanimated + std::shared_ptr scheduler_; + + public: + JSRuntimeHelper( + jsi::Runtime *rnRuntime, + jsi::Runtime *uiRuntime, + const std::shared_ptr &scheduler) + : rnRuntime_(rnRuntime), uiRuntime_(uiRuntime), scheduler_(scheduler) {} + + volatile bool uiRuntimeDestroyed; + std::shared_ptr valueUnpacker; + + inline jsi::Runtime *uiRuntime() const { + return uiRuntime_; + } + + inline jsi::Runtime *rnRuntime() const { + return rnRuntime_; + } + + inline bool isUIRuntime(const jsi::Runtime &rt) const { + return &rt == uiRuntime_; + } + + inline bool isRNRuntime(const jsi::Runtime &rt) const { + return &rt == rnRuntime_; + } + + void scheduleOnUI(std::function job) { + scheduler_->scheduleOnUI(job); + } + + void scheduleOnJS(std::function job) { + scheduler_->scheduleOnJS(job); + } +}; + +// Core functions are not allowed to capture outside variables, otherwise they'd +// try to access _closure variable which is something we want to avoid for +// simplicity reasons. +class CoreFunction { + private: + std::unique_ptr rnFunction_; + std::unique_ptr uiFunction_; + std::string functionBody_; + std::string location_; + JSRuntimeHelper + *runtimeHelper_; // runtime helper holds core function references, so we + // use normal pointer here to avoid ref cycles. + std::unique_ptr &getFunction(jsi::Runtime &rt); + + public: + CoreFunction(JSRuntimeHelper *runtimeHelper, const jsi::Value &workletObject); + template + jsi::Value call(jsi::Runtime &rt, Args &&...args) { + return getFunction(rt)->call(rt, args...); + } +}; + +class Shareable { + protected: + virtual jsi::Value toJSValue(jsi::Runtime &rt) = 0; + + public: + virtual ~Shareable(); + + enum ValueType { + UndefinedType, + NullType, + BooleanType, + NumberType, + // SymbolType, TODO + // BigIntType, TODO + StringType, + ObjectType, + ArrayType, + WorkletType, + RemoteFunctionType, + HandleType, + SynchronizedDataHolder, +#ifdef RCT_NEW_ARCH_ENABLED + ShadowNode, +#endif + }; + + explicit Shareable(ValueType valueType) : valueType_(valueType) {} + virtual jsi::Value getJSValue(jsi::Runtime &rt) { + return toJSValue(rt); + } + + inline ValueType valueType() const { + return valueType_; + } + + static std::shared_ptr undefined(); + + protected: + ValueType valueType_; +}; + +class RetainingShareable : public Shareable { + protected: + std::shared_ptr runtimeHelper_; + +#if HAS_JS_WEAK_OBJECTS + + private: + std::unique_ptr remoteValue_; + + public: + jsi::Value getJSValue(jsi::Runtime &rt) final; + + RetainingShareable( + const std::shared_ptr &runtimeHelper, + jsi::Runtime &rt, + ValueType valueType) + : Shareable(valueType), runtimeHelper_(runtimeHelper) {} + + ~RetainingShareable(); +#else + + public: + RetainingShareable( + const std::shared_ptr &runtimeHelper, + jsi::Runtime &rt, + ValueType valueType) + : Shareable(valueType), runtimeHelper_(runtimeHelper) {} +#endif +}; + +class ShareableJSRef : public jsi::HostObject { + private: + std::shared_ptr value_; + + public: + explicit ShareableJSRef(std::shared_ptr value) : value_(value) {} + std::shared_ptr value() const { + return value_; + } + + static jsi::Object newHostObject( + jsi::Runtime &rt, + const std::shared_ptr &value) { + return jsi::Object::createFromHostObject( + rt, std::make_shared(value)); + } +}; + +std::shared_ptr extractShareableOrThrow( + jsi::Runtime &rt, + const jsi::Value &maybeShareableValue, + const char *errorMessage = nullptr); + +template +std::shared_ptr extractShareableOrThrow( + jsi::Runtime &rt, + const jsi::Value &shareableRef, + const char *errorMessage = nullptr) { + auto res = std::dynamic_pointer_cast( + extractShareableOrThrow(rt, shareableRef, errorMessage)); + if (!res) { + throw new std::runtime_error( + errorMessage != nullptr + ? errorMessage + : "provided shareable object is of an incompatible type"); + } + return res; +} + +class ShareableArray : public RetainingShareable { + public: + ShareableArray( + const std::shared_ptr &runtimeHelper, + jsi::Runtime &rt, + const jsi::Array &array); + + jsi::Value toJSValue(jsi::Runtime &rt) override { + auto size = data_.size(); + auto ary = jsi::Array(rt, size); + for (size_t i = 0; i < size; i++) { + ary.setValueAtIndex(rt, i, data_[i]->getJSValue(rt)); + } + return ary; + } + + protected: + std::vector> data_; +}; + +class ShareableObject : public RetainingShareable { + public: + ShareableObject( + const std::shared_ptr &runtimeHelper, + jsi::Runtime &rt, + const jsi::Object &object); + jsi::Value toJSValue(jsi::Runtime &rt) override { + auto obj = jsi::Object(rt); + for (size_t i = 0, size = data_.size(); i < size; i++) { + obj.setProperty( + rt, data_[i].first.c_str(), data_[i].second->getJSValue(rt)); + } + return obj; + } + + protected: + std::vector>> data_; +}; + +#ifdef RCT_NEW_ARCH_ENABLED +class ShareableShadowNodeWrapper : public Shareable { + private: + react::ShadowNode::Shared shadowNode_; + + public: + ShareableShadowNodeWrapper( + const std::shared_ptr &runtimeHelper, + jsi::Runtime &rt, + const jsi::Object &wrapperObject) + : Shareable(ShadowNode) { + shadowNode_ = + wrapperObject.getHostObject(rt)->shadowNode; + } + jsi::Value toJSValue(jsi::Runtime &rt) override { + return jsi::Object::createFromHostObject( + rt, std::make_shared(shadowNode_)); + } +}; +#endif + +class ShareableWorklet : public ShareableObject { + public: + ShareableWorklet( + const std::shared_ptr &runtimeHelper, + jsi::Runtime &rt, + const jsi::Object &worklet) + : ShareableObject(runtimeHelper, rt, worklet) { + valueType_ = WorkletType; + } + jsi::Value toJSValue(jsi::Runtime &rt) override { + jsi::Value obj = ShareableObject::toJSValue(rt); + return runtimeHelper_->valueUnpacker->call(rt, obj); + } +}; + +class ShareableRemoteFunction + : public RetainingShareable, + public std::enable_shared_from_this { + private: + jsi::Function function_; + + public: + ShareableRemoteFunction( + const std::shared_ptr &runtimeHelper, + jsi::Runtime &rt, + jsi::Function &&function) + : RetainingShareable(runtimeHelper, rt, RemoteFunctionType), + function_(std::move(function)) {} + jsi::Value toJSValue(jsi::Runtime &rt) override { + if (runtimeHelper_->isUIRuntime(rt)) { + return ShareableJSRef::newHostObject(rt, shared_from_this()); + } else { + return jsi::Value(rt, function_); + } + } +}; + +class ShareableHandle : public Shareable { + private: + std::shared_ptr runtimeHelper_; + std::unique_ptr initializer_; + std::unique_ptr remoteValue_; + + public: + ShareableHandle( + const std::shared_ptr runtimeHelper, + jsi::Runtime &rt, + const jsi::Object &initializerObject) + : Shareable(HandleType), runtimeHelper_(runtimeHelper) { + initializer_ = + std::make_unique(runtimeHelper, rt, initializerObject); + } + ~ShareableHandle() { + if (runtimeHelper_->uiRuntimeDestroyed) { + // The below use of unique_ptr.release prevents the smart pointer from + // calling the destructor of the kept object. This effectively results in + // leaking some memory. We do this on purpose, as sometimes we would keep + // references to JSI objects past the lifetime of its runtime (e.g., + // shared values references from the RN VM holds reference to JSI objects + // on the UI runtime). When the UI runtime is terminated, the orphaned JSI + // objects would crash the app when their destructors are called, because + // they call into a memory that's managed by the terminated runtime. We + // accept the tradeoff of leaking memory here, as it has a limited impact. + // This scenario can only occur when the React instance is torn down which + // happens in development mode during app reloads, or in production when + // the app is being shut down gracefully by the system. An alternative + // solution would require us to keep track of all JSI values that are in + // use which would require additional data structure and compute spent on + // bookkeeping that only for the sake of destroying the values in time + // before the runtime is terminated. Note that the underlying memory that + // jsi::Value refers to is managed by the VM and gets freed along with the + // runtime. + remoteValue_.release(); + } + } + jsi::Value toJSValue(jsi::Runtime &rt) override { + if (initializer_ != nullptr) { + auto initObj = initializer_->getJSValue(rt); + remoteValue_ = std::make_unique( + runtimeHelper_->valueUnpacker->call(rt, initObj)); + initializer_ = nullptr; // we can release ref to initializer as this + // method should be called at most once + } + return jsi::Value(rt, *remoteValue_); + } +}; + +class ShareableSynchronizedDataHolder + : public Shareable, + public std::enable_shared_from_this { + private: + std::shared_ptr runtimeHelper_; + std::shared_ptr data_; + std::shared_ptr uiValue_; + std::shared_ptr rnValue_; + std::mutex dataAccessLock_; + + public: + ShareableSynchronizedDataHolder( + std::shared_ptr runtimeHelper, + jsi::Runtime &rt, + const jsi::Value &initialValue) + : Shareable(SynchronizedDataHolder), + runtimeHelper_(runtimeHelper), + data_(extractShareableOrThrow(rt, initialValue)) {} + + jsi::Value get(jsi::Runtime &rt) { + std::unique_lock read_lock(dataAccessLock_); + if (runtimeHelper_->isUIRuntime(rt)) { + if (uiValue_ == nullptr) { + auto value = data_->getJSValue(rt); + uiValue_ = std::make_shared(rt, value); + return value; + } else { + return jsi::Value(rt, *uiValue_); + } + } else { + if (rnValue_ == nullptr) { + auto value = data_->getJSValue(rt); + rnValue_ = std::make_shared(rt, value); + return value; + } else { + return jsi::Value(rt, *rnValue_); + } + } + } + + void set(jsi::Runtime &rt, const jsi::Value &data) { + std::unique_lock write_lock(dataAccessLock_); + data_ = extractShareableOrThrow(rt, data); + uiValue_.reset(); + rnValue_.reset(); + } + + jsi::Value toJSValue(jsi::Runtime &rt) override { + return ShareableJSRef::newHostObject(rt, shared_from_this()); + }; +}; + +class ShareableString : public Shareable { + public: + ShareableString(jsi::Runtime &rt, const jsi::String &string) + : Shareable(StringType) { + data = string.utf8(rt); + } + jsi::Value toJSValue(jsi::Runtime &rt) override { + return jsi::String::createFromUtf8(rt, data); + } + + protected: + std::string data; +}; + +class ShareableScalar : public Shareable { + public: + explicit ShareableScalar(double number) : Shareable(NumberType) { + data_.number = number; + } + explicit ShareableScalar(bool boolean) : Shareable(BooleanType) { + data_.boolean = boolean; + } + ShareableScalar() : Shareable(UndefinedType) {} + explicit ShareableScalar(std::nullptr_t) : Shareable(NullType) {} + + jsi::Value toJSValue(jsi::Runtime &rt) override { + switch (valueType_) { + case Shareable::UndefinedType: + return jsi::Value(); + case Shareable::NullType: + return jsi::Value(nullptr); + case Shareable::BooleanType: + return jsi::Value(data_.boolean); + case Shareable::NumberType: + return jsi::Value(data_.number); + default: + throw std::runtime_error( + "attempted to convert object that's not of a scalar type"); + } + } + + protected: + union Data { + bool boolean; + double number; + }; + + private: + Data data_; +}; + +} // namespace reanimated diff --git a/Common/cpp/SharedItems/ValueWrapper.h b/Common/cpp/SharedItems/ValueWrapper.h deleted file mode 100644 index d751cdaab5d..00000000000 --- a/Common/cpp/SharedItems/ValueWrapper.h +++ /dev/null @@ -1,183 +0,0 @@ -#pragma once - -#include -#ifdef RCT_NEW_ARCH_ENABLED -#include -#endif -#include -#include -#include -#include "HostFunctionHandler.h" -#include "JSIStoreValueUser.h" -#include "SharedParent.h" -#include "WorkletsCache.h" - -using namespace facebook::react; - -namespace reanimated { - -class HostFunctionWrapper; -class AnimatedSensorModule; - -class ValueWrapper { - friend AnimatedSensorModule; - - public: - ValueWrapper() {} - explicit ValueWrapper(ValueType _type) : type(_type) {} - ValueType getType() const { - return type; - } - - virtual ~ValueWrapper() {} - - static inline bool asBoolean( - const std::unique_ptr &valueContainer); - static inline double asNumber( - const std::unique_ptr &valueContainer); - static inline const std::string &asString( - const std::unique_ptr &valueContainer); - static inline const std::shared_ptr &asHostFunction( - const std::unique_ptr &valueContainer); - static inline const std::shared_ptr &asFrozenObject( - const std::unique_ptr &valueContainer); - static inline const std::shared_ptr &asRemoteObject( - const std::unique_ptr &valueContainer); - static inline std::vector> &asFrozenArray( - const std::unique_ptr &valueContainer); - static inline const std::shared_ptr &asMutableValue( - const std::unique_ptr &valueContainer); -#ifdef RCT_NEW_ARCH_ENABLED - static inline const ShadowNode::Shared &asShadowNode( - const std::unique_ptr &valueContainer); -#endif - - static const HostFunctionWrapper *asHostFunctionWrapper( - const std::unique_ptr &valueContainer); - - protected: - ValueType type; -}; - -class BooleanValueWrapper : public ValueWrapper { - public: - explicit BooleanValueWrapper(const bool _value) - : ValueWrapper(ValueType::BoolType), value(_value) {} - bool value; -}; - -class NumberValueWrapper : public ValueWrapper { - public: - explicit NumberValueWrapper(const double _value) - : ValueWrapper(ValueType::NumberType), value(_value) {} - double value; -}; - -class StringValueWrapper : public ValueWrapper { - public: - explicit StringValueWrapper(const std::string &_value) - : ValueWrapper(ValueType::StringType), value(_value) {} - std::string value; -}; - -class HostFunctionWrapper : public ValueWrapper { - public: - explicit HostFunctionWrapper( - const std::shared_ptr &_value) - : ValueWrapper(ValueType::HostFunctionType), value(_value) {} - std::shared_ptr value; -}; - -class FrozenObjectWrapper : public ValueWrapper { - public: - explicit FrozenObjectWrapper(const std::shared_ptr &_value) - : ValueWrapper(ValueType::FrozenObjectType), value(_value) {} - std::shared_ptr value; -}; - -class RemoteObjectWrapper : public ValueWrapper { - public: - explicit RemoteObjectWrapper(const std::shared_ptr &_value) - : ValueWrapper(ValueType::RemoteObjectType), value(_value) {} - std::shared_ptr value; -}; - -class FrozenArrayWrapper : public ValueWrapper { - public: - FrozenArrayWrapper() : ValueWrapper(ValueType::FrozenArrayType) {} - explicit FrozenArrayWrapper( - const std::vector> &_value) - : ValueWrapper(ValueType::FrozenArrayType), value(_value) {} - std::vector> value; -}; - -class MutableValueWrapper : public ValueWrapper { - public: - explicit MutableValueWrapper(const std::shared_ptr &_value) - : ValueWrapper(ValueType::MutableValueType), value(_value) {} - std::shared_ptr value; -}; - -#ifdef RCT_NEW_ARCH_ENABLED -class ShadowNodeValueWrapper : public ValueWrapper { - public: - explicit ShadowNodeValueWrapper(const ShadowNode::Shared &_value) - : ValueWrapper(ValueType::ShadowNodeType), value(_value) {} - ShadowNode::Shared value; -}; -#endif - -inline bool ValueWrapper::asBoolean( - const std::unique_ptr &valueContainer) { - return static_cast(valueContainer.get())->value; -} - -inline double ValueWrapper::asNumber( - const std::unique_ptr &valueContainer) { - return static_cast(valueContainer.get())->value; -} - -inline const std::string &ValueWrapper::asString( - const std::unique_ptr &valueContainer) { - return static_cast(valueContainer.get())->value; -} - -inline const std::shared_ptr &ValueWrapper::asHostFunction( - const std::unique_ptr &valueContainer) { - return static_cast(valueContainer.get())->value; -} - -inline const std::shared_ptr &ValueWrapper::asFrozenObject( - const std::unique_ptr &valueContainer) { - return static_cast(valueContainer.get())->value; -} - -inline const std::shared_ptr &ValueWrapper::asRemoteObject( - const std::unique_ptr &valueContainer) { - return static_cast(valueContainer.get())->value; -} - -inline std::vector> - &ValueWrapper::asFrozenArray( - const std::unique_ptr &valueContainer) { - return static_cast(valueContainer.get())->value; -} - -inline const std::shared_ptr &ValueWrapper::asMutableValue( - const std::unique_ptr &valueContainer) { - return static_cast(valueContainer.get())->value; -} - -#ifdef RCT_NEW_ARCH_ENABLED -inline const ShadowNode::Shared &ValueWrapper::asShadowNode( - const std::unique_ptr &valueContainer) { - return static_cast(valueContainer.get())->value; -} -#endif - -inline const HostFunctionWrapper *ValueWrapper::asHostFunctionWrapper( - const std::unique_ptr &valueContainer) { - return static_cast(valueContainer.get()); -} - -} // namespace reanimated diff --git a/Common/cpp/Tools/JSIStoreValueUser.cpp b/Common/cpp/Tools/JSIStoreValueUser.cpp deleted file mode 100644 index 546a85f8cdb..00000000000 --- a/Common/cpp/Tools/JSIStoreValueUser.cpp +++ /dev/null @@ -1,55 +0,0 @@ -#include "JSIStoreValueUser.h" -#include "RuntimeManager.h" -#ifdef ANDROID -#include -#endif - -namespace reanimated { - -std::weak_ptr StoreUser::getWeakRef(jsi::Runtime &rt) { - const std::lock_guard lock(storeUserData->storeMutex); - if (storeUserData->store.count(identifier) == 0) { - storeUserData->store[identifier] = - std::vector>(); - } - std::shared_ptr sv = - std::make_shared(rt, jsi::Value::undefined()); - storeUserData->store[identifier].push_back(sv); - - return sv; -} - -StoreUser::StoreUser( - std::shared_ptr s, - const RuntimeManager &runtimeManager) - : scheduler(s) { - storeUserData = runtimeManager.storeUserData; - identifier = storeUserData->ctr++; -} - -StoreUser::~StoreUser() { - int id = identifier; - std::shared_ptr strongScheduler = scheduler.lock(); - if (strongScheduler != nullptr) { - std::shared_ptr sud = storeUserData; -#ifdef ANDROID - jni::ThreadScope::WithClassLoader([&] { - strongScheduler->scheduleOnUI([id, sud]() { - const std::lock_guard lock(sud->storeMutex); - if (sud->store.count(id) > 0) { - sud->store.erase(id); - } - }); - }); -#else - strongScheduler->scheduleOnUI([id, sud]() { - const std::lock_guard lock(sud->storeMutex); - if (sud->store.count(id) > 0) { - sud->store.erase(id); - } - }); -#endif - } -} - -} // namespace reanimated diff --git a/Common/cpp/Tools/JSIStoreValueUser.h b/Common/cpp/Tools/JSIStoreValueUser.h deleted file mode 100644 index 9424abdd10c..00000000000 --- a/Common/cpp/Tools/JSIStoreValueUser.h +++ /dev/null @@ -1,37 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include -#include "Scheduler.h" - -using namespace facebook; - -namespace reanimated { - -class RuntimeManager; - -struct StaticStoreUser { - std::atomic ctr; - std::unordered_map>> store; - std::recursive_mutex storeMutex; -}; - -class StoreUser { - int identifier = 0; - std::weak_ptr scheduler; - std::shared_ptr storeUserData; - - public: - StoreUser(std::shared_ptr s, const RuntimeManager &runtimeManager); - - std::weak_ptr getWeakRef(jsi::Runtime &rt); - void removeRefs(); - - virtual ~StoreUser(); -}; - -} // namespace reanimated diff --git a/Common/cpp/Tools/Mapper.cpp b/Common/cpp/Tools/Mapper.cpp deleted file mode 100644 index fcf161a624a..00000000000 --- a/Common/cpp/Tools/Mapper.cpp +++ /dev/null @@ -1,86 +0,0 @@ -#include "Mapper.h" -#include "MutableValue.h" -#include "SharedParent.h" - -namespace reanimated { - -Mapper::Mapper( - NativeReanimatedModule *module, - unsigned long id, - std::shared_ptr mapper, - std::vector> inputs, - std::vector> outputs) - : id(id), module(module), mapper(mapper), inputs(inputs), outputs(outputs) { - auto markDirty = [this, module]() { - this->dirty = true; - module->maybeRequestRender(); - }; - for (auto input : inputs) { - input->addListener(id, markDirty); - } -} - -void Mapper::execute(jsi::Runtime &rt) { - dirty = false; - if (optimalizationLvl == 0) { - mapper->callWithThis(rt, *mapper); // call styleUpdater - } else { -#ifdef RCT_NEW_ARCH_ENABLED - jsi::Value newStyle = userUpdater->call(rt).asObject(rt); -#else - jsi::Object newStyle = userUpdater->call(rt).asObject(rt); -#endif - auto jsViewDescriptorArray = viewDescriptors->getValue(rt) - .getObject(rt) - .getProperty(rt, "value") - .asObject(rt) - .getArray(rt); - for (int i = 0; i < jsViewDescriptorArray.length(rt); ++i) { - auto jsViewDescriptor = - jsViewDescriptorArray.getValueAtIndex(rt, i).getObject(rt); -#ifdef RCT_NEW_ARCH_ENABLED - updateProps( - rt, jsViewDescriptor.getProperty(rt, "shadowNodeWrapper"), newStyle); -#else - updateProps( - rt, - static_cast(jsViewDescriptor.getProperty(rt, "tag").asNumber()), - jsViewDescriptor.getProperty(rt, "name"), - newStyle); -#endif - } - } -} - -void Mapper::enableFastMode( - const int optimalizationLvl, - const std::shared_ptr &updater, - const std::shared_ptr &jsViewDescriptors) { - if (optimalizationLvl == 0) { - return; - } - viewDescriptors = jsViewDescriptors; - this->optimalizationLvl = optimalizationLvl; -#ifdef RCT_NEW_ARCH_ENABLED - updateProps = [this]( - jsi::Runtime &rt, - const jsi::Value &shadowNodeValue, - const jsi::Value &props) { - this->module->updateProps(rt, shadowNodeValue, props); - }; -#else - // TODO: don't get public field, instead call this->module->updateProps - updateProps = module->updatePropsFunction; -#endif - jsi::Runtime &rt = *module->runtime; - userUpdater = std::make_shared( - updater->getValue(rt).asObject(rt).asFunction(rt)); -} - -Mapper::~Mapper() { - for (auto input : inputs) { - input->removeListener(id); - } -} - -} // namespace reanimated diff --git a/Common/cpp/Tools/Mapper.h b/Common/cpp/Tools/Mapper.h deleted file mode 100644 index 9b53025a7ad..00000000000 --- a/Common/cpp/Tools/Mapper.h +++ /dev/null @@ -1,51 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include "NativeReanimatedModule.h" -#include "ShareableValue.h" - -using namespace facebook; - -namespace reanimated { - -class MapperRegistry; - -struct ViewDescriptor { - int tag; - jsi::Value name; -}; - -class Mapper : public std::enable_shared_from_this { - friend MapperRegistry; - - private: - unsigned long id; - NativeReanimatedModule *module; - std::shared_ptr mapper; - std::vector> inputs; - std::vector> outputs; - bool dirty = true; - std::shared_ptr userUpdater; - UpdatePropsFunction updateProps; - int optimalizationLvl = 0; - std::shared_ptr viewDescriptors; - - public: - Mapper( - NativeReanimatedModule *module, - unsigned long id, - std::shared_ptr mapper, - std::vector> inputs, - std::vector> outputs); - void execute(jsi::Runtime &rt); - void enableFastMode( - const int optimalizationLvl, - const std::shared_ptr &updater, - const std::shared_ptr &jsViewDescriptors); - virtual ~Mapper(); -}; - -} // namespace reanimated diff --git a/Common/cpp/Tools/PlatformDepMethodsHolder.h b/Common/cpp/Tools/PlatformDepMethodsHolder.h index 3defea29b85..cc91187a2c6 100644 --- a/Common/cpp/Tools/PlatformDepMethodsHolder.h +++ b/Common/cpp/Tools/PlatformDepMethodsHolder.h @@ -53,6 +53,10 @@ using RequestRender = std::function, jsi::Runtime &rt)>; using TimeProviderFunction = std::function; +using ProgressLayoutAnimationFunction = + std::function; +using EndLayoutAnimationFunction = std::function; + using RegisterSensorFunction = std::function)>; using UnregisterSensorFunction = std::function; @@ -76,6 +80,8 @@ struct PlatformDepMethodsHolder { ConfigurePropsFunction configurePropsFunction; #endif TimeProviderFunction getCurrentTime; + ProgressLayoutAnimationFunction progressLayoutAnimation; + EndLayoutAnimationFunction endLayoutAnimation; RegisterSensorFunction registerSensor; UnregisterSensorFunction unregisterSensor; SetGestureStateFunction setGestureStateFunction; diff --git a/Common/cpp/Tools/RuntimeDecorator.cpp b/Common/cpp/Tools/RuntimeDecorator.cpp index 3cc4977ca2f..841827d0fb8 100644 --- a/Common/cpp/Tools/RuntimeDecorator.cpp +++ b/Common/cpp/Tools/RuntimeDecorator.cpp @@ -1,9 +1,9 @@ #include "RuntimeDecorator.h" +#include #include #include #include -#include "LayoutAnimationsProxy.h" -#include "MutableValue.h" +#include #include "ReanimatedHiddenHeaders.h" namespace reanimated { @@ -31,6 +31,7 @@ void RuntimeDecorator::decorateRuntime( rt, "_LABEL", jsi::String::createFromAscii(rt, label)); jsi::Object dummyGlobal(rt); + dummyGlobal.setProperty(rt, "gc", rt.global().getProperty(rt, "gc")); rt.global().setProperty(rt, "global", dummyGlobal); rt.global().setProperty(rt, "jsThis", jsi::Value::undefined()); @@ -105,11 +106,15 @@ void RuntimeDecorator::decorateUIRuntime( const ScrollToFunction scrollTo, #endif const RequestFrameFunction requestFrame, + const ScheduleOnJSFunction scheduleOnJS, + const MakeShareableCloneFunction makeShareableClone, + const UpdateDataSynchronouslyFunction updateDataSynchronously, const TimeProviderFunction getCurrentTime, const RegisterSensorFunction registerSensor, const UnregisterSensorFunction unregisterSensor, const SetGestureStateFunction setGestureState, - std::shared_ptr layoutAnimationsProxy) { + const ProgressLayoutAnimationFunction progressLayoutAnimationFunction, + const EndLayoutAnimationFunction endLayoutAnimationFunction) { RuntimeDecorator::decorateRuntime(rt, "UI"); rt.global().setProperty(rt, "_UI", jsi::Value(true)); @@ -232,6 +237,42 @@ void RuntimeDecorator::decorateUIRuntime( rt, jsi::PropNameID::forAscii(rt, "requestAnimationFrame"), 1, clb2); rt.global().setProperty(rt, "requestAnimationFrame", requestAnimationFrame); + auto clb4 = [scheduleOnJS]( + jsi::Runtime &rt, + const jsi::Value &thisValue, + const jsi::Value *args, + const size_t count) -> jsi::Value { + scheduleOnJS(rt, args[0], args[1]); + return jsi::Value::undefined(); + }; + jsi::Value scheduleOnJSFun = jsi::Function::createFromHostFunction( + rt, jsi::PropNameID::forAscii(rt, "_scheduleOnJS"), 2, clb4); + rt.global().setProperty(rt, "_scheduleOnJS", scheduleOnJSFun); + + auto clb5 = [makeShareableClone]( + jsi::Runtime &rt, + const jsi::Value &thisValue, + const jsi::Value *args, + const size_t count) -> jsi::Value { + return makeShareableClone(rt, std::move(args[0])); + }; + jsi::Value makeShareableCloneFun = jsi::Function::createFromHostFunction( + rt, jsi::PropNameID::forAscii(rt, "_makeShareableClone"), 1, clb5); + rt.global().setProperty(rt, "_makeShareableClone", makeShareableCloneFun); + + auto clb51 = [updateDataSynchronously]( + jsi::Runtime &rt, + const jsi::Value &thisValue, + const jsi::Value *args, + const size_t count) -> jsi::Value { + updateDataSynchronously(rt, std::move(args[0]), std::move(args[1])); + return jsi::Value::undefined(); + }; + jsi::Value updateDataSynchronouslyFun = jsi::Function::createFromHostFunction( + rt, jsi::PropNameID::forAscii(rt, "_updateDataSynchronously"), 1, clb51); + rt.global().setProperty( + rt, "_updateDataSynchronously", updateDataSynchronouslyFun); + auto clb6 = [getCurrentTime]( jsi::Runtime &rt, const jsi::Value &thisValue, @@ -247,43 +288,30 @@ void RuntimeDecorator::decorateUIRuntime( rt.global().setProperty(rt, "_eventTimestamp", jsi::Value::undefined()); // layout animation - std::weak_ptr layoutProxy = layoutAnimationsProxy; - auto clb7 = [layoutProxy]( + auto clb7 = [progressLayoutAnimationFunction]( jsi::Runtime &rt, const jsi::Value &thisValue, const jsi::Value *args, size_t count) -> jsi::Value { - std::shared_ptr proxy = layoutProxy.lock(); - if (layoutProxy.expired()) { - return jsi::Value::undefined(); - } - proxy->startObserving( - args[0].asNumber(), - args[1].asObject(rt).getHostObject(rt), - rt); + progressLayoutAnimationFunction(args[0].asNumber(), args[1].asObject(rt)); return jsi::Value::undefined(); }; - jsi::Value _startObservingProgress = jsi::Function::createFromHostFunction( - rt, jsi::PropNameID::forAscii(rt, "_startObservingProgress"), 0, clb7); - rt.global().setProperty( - rt, "_startObservingProgress", _startObservingProgress); + jsi::Value _notifyAboutProgress = jsi::Function::createFromHostFunction( + rt, jsi::PropNameID::forAscii(rt, "_notifyAboutProgress"), 2, clb7); + rt.global().setProperty(rt, "_notifyAboutProgress", _notifyAboutProgress); - auto clb8 = [layoutProxy]( + auto clb8 = [endLayoutAnimationFunction]( jsi::Runtime &rt, const jsi::Value &thisValue, const jsi::Value *args, size_t count) -> jsi::Value { - std::shared_ptr proxy = layoutProxy.lock(); - if (layoutProxy.expired()) { - return jsi::Value::undefined(); - } - proxy->stopObserving( - args[0].asNumber(), args[1].getBool(), args[2].getBool()); + endLayoutAnimationFunction( + args[0].asNumber(), args[1].asBool(), args[2].asBool()); return jsi::Value::undefined(); }; - jsi::Value _stopObservingProgress = jsi::Function::createFromHostFunction( - rt, jsi::PropNameID::forAscii(rt, "_stopObservingProgress"), 0, clb8); - rt.global().setProperty(rt, "_stopObservingProgress", _stopObservingProgress); + jsi::Value _notifyAboutEnd = jsi::Function::createFromHostFunction( + rt, jsi::PropNameID::forAscii(rt, "_notifyAboutEnd"), 2, clb8); + rt.global().setProperty(rt, "_notifyAboutEnd", _notifyAboutEnd); auto clb9 = [setGestureState]( jsi::Runtime &rt, diff --git a/Common/cpp/Tools/RuntimeDecorator.h b/Common/cpp/Tools/RuntimeDecorator.h index e3aa21c75e4..5b0f9b2ac2a 100644 --- a/Common/cpp/Tools/RuntimeDecorator.h +++ b/Common/cpp/Tools/RuntimeDecorator.h @@ -5,7 +5,6 @@ #include #include #include -#include "LayoutAnimationsProxy.h" #include "PlatformDepMethodsHolder.h" using namespace facebook; @@ -13,6 +12,12 @@ using namespace facebook; namespace reanimated { using RequestFrameFunction = std::function)>; +using ScheduleOnJSFunction = + std::function; +using MakeShareableCloneFunction = + std::function; +using UpdateDataSynchronouslyFunction = + std::function; enum RuntimeType { /** @@ -40,11 +45,15 @@ class RuntimeDecorator { const ScrollToFunction scrollTo, #endif const RequestFrameFunction requestFrame, + const ScheduleOnJSFunction scheduleOnJS, + const MakeShareableCloneFunction makeShareableClone, + const UpdateDataSynchronouslyFunction updateDataSynchronously, const TimeProviderFunction getCurrentTime, const RegisterSensorFunction registerSensor, const UnregisterSensorFunction unregisterSensor, const SetGestureStateFunction setGestureState, - std::shared_ptr layoutAnimationsProxy); + const ProgressLayoutAnimationFunction progressLayoutAnimationFunction, + const EndLayoutAnimationFunction endLayoutAnimationFunction); /** Returns true if the given Runtime is the Reanimated UI-Thread Runtime. diff --git a/Common/cpp/Tools/Scheduler.cpp b/Common/cpp/Tools/Scheduler.cpp index 50942107cf8..fc429ba4a12 100644 --- a/Common/cpp/Tools/Scheduler.cpp +++ b/Common/cpp/Tools/Scheduler.cpp @@ -3,6 +3,8 @@ #else #include "Scheduler.h" #endif +#include "ReanimatedRuntime.h" +#include "RuntimeManager.h" namespace reanimated { @@ -16,6 +18,14 @@ void Scheduler::scheduleOnJS(std::function job) { void Scheduler::triggerUI() { scheduledOnUI = false; +#if JS_RUNTIME_HERMES + // JSI's scope defined here allows for JSI-objects to be cleared up after + // each runtime loop. Within these loops we typically create some temporary + // JSI objects and hence it allows for such objects to be garbage collected + // much sooner. + // Apparently the scope API is only supported on Hermes at the moment. + auto scope = jsi::Scope(*runtimeManager.lock()->runtime); +#endif while (uiJobs.getSize()) { auto job = uiJobs.pop(); job(); diff --git a/Common/cpp/Tools/SingleInstanceChecker.h b/Common/cpp/Tools/SingleInstanceChecker.h index 797afd829ca..3fff2f406e9 100644 --- a/Common/cpp/Tools/SingleInstanceChecker.h +++ b/Common/cpp/Tools/SingleInstanceChecker.h @@ -29,7 +29,7 @@ class SingleInstanceChecker { // A static field will exist separately for every class template. // This has to be inline for automatic initialization. - inline static int instanceCount_; + inline static volatile int instanceCount_; }; template diff --git a/__tests__/Animation.test.js b/__tests__/Animation.test.js index 5039ba100b4..5859c1c13ef 100644 --- a/__tests__/Animation.test.js +++ b/__tests__/Animation.test.js @@ -7,12 +7,7 @@ import Animated, { useAnimatedStyle, withTiming, } from '../src/'; -import { - withReanimatedTimer, - advanceAnimationByTime, - advanceAnimationByFrame, - getAnimatedStyle, -} from '../src/reanimated2/jestUtils'; +import { getAnimatedStyle } from '../src/reanimated2/jestUtils'; const AnimatedSharedValueComponent = (props) => { const widthSV = props.sharedValue; @@ -55,113 +50,97 @@ const getDefaultStyle = () => ({ }); describe('Tests of animations', () => { - test('withTiming animation', async () => { - withReanimatedTimer(() => { - const style = getDefaultStyle(); - - const { getByTestId } = render(); - const view = getByTestId('view'); - const button = getByTestId('button'); - - expect(view.props.style.width).toBe(0); - expect(view).toHaveAnimatedStyle(style); - fireEvent.press(button); - advanceAnimationByTime(600); - style.width = 100; - expect(view).toHaveAnimatedStyle(style); - }); + beforeEach(() => { + jest.useFakeTimers(); }); - test('withTiming animation, get animated style', async () => { - withReanimatedTimer(() => { - const { getByTestId } = render(); - const view = getByTestId('view'); - const button = getByTestId('button'); - fireEvent.press(button); - advanceAnimationByTime(600); - const style = getAnimatedStyle(view); - expect(style.width).toBe(100); - }); + afterEach(() => { + jest.runOnlyPendingTimers(); + jest.useRealTimers(); }); - test('withTiming animation, width in a middle of animation', () => { - withReanimatedTimer(() => { - const style = getDefaultStyle(); + test('withTiming animation', () => { + const style = getDefaultStyle(); + + const { getByTestId } = render(); + const view = getByTestId('view'); + const button = getByTestId('button'); + expect(view.props.style.width).toBe(0); + expect(view).toHaveAnimatedStyle(style); + fireEvent.press(button); + jest.advanceTimersByTime(600); + style.width = 100; + expect(view).toHaveAnimatedStyle(style); + }); - const { getByTestId } = render(); - const view = getByTestId('view'); - const button = getByTestId('button'); + test('withTiming animation, get animated style', () => { + const { getByTestId } = render(); + const view = getByTestId('view'); + const button = getByTestId('button'); + fireEvent.press(button); + jest.advanceTimersByTime(600); + const style = getAnimatedStyle(view); + expect(style.width).toBe(100); + }); - expect(view.props.style.width).toBe(0); - expect(view).toHaveAnimatedStyle(style); + test('withTiming animation, width in a middle of animation', () => { + const style = getDefaultStyle(); - fireEvent.press(button); - advanceAnimationByTime(150); - style.width = 18.7272; // value of component width after 150ms of animation - expect(view).toHaveAnimatedStyle(style); - }); - }); + const { getByTestId } = render(); + const view = getByTestId('view'); + const button = getByTestId('button'); - test('withTiming animation, use animation timer and advance by 10 frames of animation', () => { - withReanimatedTimer(() => { - const { getByTestId } = render(); - const view = getByTestId('view'); - const button = getByTestId('button'); + expect(view.props.style.width).toBe(0); + expect(view).toHaveAnimatedStyle(style); - fireEvent.press(button); - advanceAnimationByFrame(13); - // value of component width after 13 frames of animation - expect(view).toHaveAnimatedStyle({ width: 39.0728 }); - }); + fireEvent.press(button); + jest.advanceTimersByTime(250); + jest.runOnlyPendingTimers(); // timers scheduled for the exact 250ms won't run without this additional call + style.width = 50; // value of component width after 150ms of animation + expect(view).toHaveAnimatedStyle(style); }); test('withTiming animation, compare all styles', () => { - withReanimatedTimer(() => { - const style = getDefaultStyle(); + const style = getDefaultStyle(); - const { getByTestId } = render(); - const view = getByTestId('view'); - const button = getByTestId('button'); + const { getByTestId } = render(); + const view = getByTestId('view'); + const button = getByTestId('button'); - fireEvent.press(button); - advanceAnimationByTime(150); - style.width = 18.7272; // value of component width after 150ms of animation - expect(view).toHaveAnimatedStyle(style, true); - }); + fireEvent.press(button); + jest.advanceTimersByTime(250); + jest.runOnlyPendingTimers(); + style.width = 50; // value of component width after 250ms of animation + expect(view).toHaveAnimatedStyle(style, true); }); test('withTiming animation, define shared value outside component', () => { - withReanimatedTimer(() => { - let sharedValue; - renderHook(() => { - sharedValue = useSharedValue(0); - }); - const { getByTestId } = render( - - ); - const view = getByTestId('view'); - const button = getByTestId('button'); - - fireEvent.press(button); - advanceAnimationByTime(150); - // value of component width after 150ms of animation - expect(view).toHaveAnimatedStyle({ width: 18.7272 }); + let sharedValue; + renderHook(() => { + sharedValue = useSharedValue(0); }); + const { getByTestId } = render( + + ); + const view = getByTestId('view'); + const button = getByTestId('button'); + + fireEvent.press(button); + jest.advanceTimersByTime(600); + expect(view).toHaveAnimatedStyle({ width: 100 }); }); test('withTiming animation, change shared value outside component', () => { - withReanimatedTimer(() => { - let sharedValue; - renderHook(() => { - sharedValue = useSharedValue(0); - }); - const { getByTestId } = render( - - ); - const view = getByTestId('view'); - sharedValue.value = 50; - advanceAnimationByTime(600); - expect(view).toHaveAnimatedStyle({ width: 50 }); + let sharedValue; + renderHook(() => { + sharedValue = useSharedValue(0); }); + const { getByTestId } = render( + + ); + const view = getByTestId('view'); + sharedValue.value = 50; + jest.advanceTimersByTime(600); + expect(view).toHaveAnimatedStyle({ width: 50 }); }); }); diff --git a/__tests__/__snapshots__/plugin.test.js.snap b/__tests__/__snapshots__/plugin.test.js.snap index 0a4d9b253d3..4e5e827a76e 100644 --- a/__tests__/__snapshots__/plugin.test.js.snap +++ b/__tests__/__snapshots__/plugin.test.js.snap @@ -19,8 +19,8 @@ var f = function () { x: objX.x } }; - _f.asString = \\"function f(){const{x,objX}=jsThis._closure;return{res:x+objX.x};}\\"; - _f.__workletHash = 4922892741734; + _f.asString = \\"function f(){const{x,objX}=this._closure;return{res:x+objX.x};}\\"; + _f.__workletHash = 5359970077727; _f.__location = \\"${ process.cwd() }/jest tests fixture (6:6)\\"; return _f; }();" @@ -61,6 +61,26 @@ exports[`babel plugin doesn't transform string literals 1`] = ` }();" `; +exports[`babel plugin supports recursive calls 1`] = ` +"var a = 1; + +var foo = function () { + var _f = function _f(t) { + if (t > 0) { + return a + foo(t - 1); + } + }; + + _f._closure = { + a: a + }; + _f.asString = \\"function foo(t){const foo=this._recur;const{a}=this._closure;if(t>0){return a+foo(t-1);}}\\"; + _f.__workletHash = 2022702330805; + _f.__location = \\"${ process.cwd() }/jest tests fixture (3:6)\\"; + return _f; +}();" +`; + exports[`babel plugin transforms 1`] = ` "var _reactNativeReanimated = _interopRequireWildcard(require(\\"react-native-reanimated\\")); @@ -82,8 +102,8 @@ function Box() { _f._closure = { offset: offset }; - _f.asString = \\"function _f(){const{offset}=jsThis._closure;return{transform:[{translateX:offset.value*255}]};}\\"; - _f.__workletHash = 8545597539225; + _f.asString = \\"function _f(){const{offset}=this._closure;return{transform:[{translateX:offset.value*255}]};}\\"; + _f.__workletHash = 361788175040; _f.__location = \\"${ process.cwd() }/jest tests fixture (10:48)\\"; _f.__optimalization = 3; return _f; @@ -225,8 +245,8 @@ var Foo = function () { _f._closure = { x: x }; - _f.asString = \\"function get(){const{x}=jsThis._closure;return x+2;}\\"; - _f.__workletHash = 923830736710; + _f.asString = \\"function get(){const{x}=this._closure;return x+2;}\\"; + _f.__workletHash = 10436985806815; _f.__location = \\"${ process.cwd() }/jest tests fixture\\"; return _f; }() diff --git a/__tests__/plugin.test.js b/__tests__/plugin.test.js index 170afec502b..4d5d7f75310 100644 --- a/__tests__/plugin.test.js +++ b/__tests__/plugin.test.js @@ -22,16 +22,16 @@ describe('babel plugin', () => { useAnimatedStyle, useSharedValue, } from 'react-native-reanimated'; - + function Box() { const offset = useSharedValue(0); - + const animatedStyles = useAnimatedStyle(() => { return { transform: [{ translateX: offset.value * 255 }], }; }); - + return ( <> @@ -51,13 +51,13 @@ describe('babel plugin', () => { function Box() { const offset = Reanimated.useSharedValue(0); - + const animatedStyles = Reanimated.useAnimatedStyle(() => { return { transform: [{ translateX: offset.value * 255 }], }; }); - + return ( <> @@ -141,7 +141,7 @@ describe('babel plugin', () => { const x = 5; const objX = { x }; - + function f() { 'worklet'; return { res: x + objX.x }; @@ -488,4 +488,19 @@ describe('babel plugin', () => { const { code } = runPlugin(input); expect(code).toMatchSnapshot(); }); + + it('supports recursive calls', () => { + const input = ` + const a = 1; + function foo(t) { + 'worklet'; + if (t > 0) { + return a + foo(t-1); + } + } + `; + + const { code } = runPlugin(input); + expect(code).toMatchSnapshot(); + }); }); diff --git a/android/src/main/cpp/LayoutAnimations.cpp b/android/src/main/cpp/LayoutAnimations.cpp index 444c9e2798a..c7846bef575 100644 --- a/android/src/main/cpp/LayoutAnimations.cpp +++ b/android/src/main/cpp/LayoutAnimations.cpp @@ -13,10 +13,6 @@ jni::local_ref LayoutAnimations::initHybrid( return makeCxxInstance(jThis); } -void LayoutAnimations::setWeakUIRuntime(std::weak_ptr wrt) { - this->weakUIRuntime = wrt; -} - void LayoutAnimations::setAnimationStartingBlock( AnimationStartingBlock animationStartingBlock) { this->animationStartingBlock_ = animationStartingBlock; @@ -31,17 +27,12 @@ void LayoutAnimations::startAnimationForTag( void LayoutAnimations::progressLayoutAnimation( int tag, - const jsi::Value &progress) { - if (auto rt = this->weakUIRuntime.lock()) { - static const auto method = - javaPart_->getClass() - ->getMethod::javaobject)>( - "progressLayoutAnimation"); - method( - javaPart_.get(), - tag, - JNIHelper::ConvertToPropsMap(*rt, progress.asObject(*rt)).get()); - } + const jni::local_ref &updates) { + static const auto method = + javaPart_->getClass() + ->getMethod::javaobject)>( + "progressLayoutAnimation"); + method(javaPart_.get(), tag, updates.get()); } void LayoutAnimations::endLayoutAnimation( @@ -91,5 +82,4 @@ void LayoutAnimations::registerNatives() { LayoutAnimations::isLayoutAnimationEnabled), }); } - }; // namespace reanimated diff --git a/android/src/main/cpp/LayoutAnimations.h b/android/src/main/cpp/LayoutAnimations.h index e380b66f51f..c28ae242b1e 100644 --- a/android/src/main/cpp/LayoutAnimations.h +++ b/android/src/main/cpp/LayoutAnimations.h @@ -14,7 +14,7 @@ using namespace facebook; class LayoutAnimations : public jni::HybridClass { using AnimationStartingBlock = std::function< void(int, alias_ref, alias_ref>)>; - using HasAnimationBlock = std::function; + using HasAnimationBlock = std::function; using ClearAnimationConfigBlock = std::function; public: @@ -31,20 +31,20 @@ class LayoutAnimations : public jni::HybridClass { bool hasAnimationForTag(int tag, std::string type); bool isLayoutAnimationEnabled(); - void setWeakUIRuntime(std::weak_ptr wrt); void setAnimationStartingBlock(AnimationStartingBlock animationStartingBlock); void setHasAnimationBlock(HasAnimationBlock hasAnimationBlock); void setClearAnimationConfigBlock( ClearAnimationConfigBlock clearAnimationConfigBlock); - void progressLayoutAnimation(int tag, const jsi::Value &progress); + void progressLayoutAnimation( + int tag, + const jni::local_ref &updates); void endLayoutAnimation(int tag, bool cancelled, bool removeView); void clearAnimationConfigForTag(int tag); private: friend HybridBase; jni::global_ref javaPart_; - std::weak_ptr weakUIRuntime; AnimationStartingBlock animationStartingBlock_; HasAnimationBlock hasAnimationBlock_; ClearAnimationConfigBlock clearAnimationConfigBlock_; diff --git a/android/src/main/cpp/NativeProxy.cpp b/android/src/main/cpp/NativeProxy.cpp index 9fdbc80f271..2ee44045db5 100644 --- a/android/src/main/cpp/NativeProxy.cpp +++ b/android/src/main/cpp/NativeProxy.cpp @@ -11,7 +11,7 @@ #include "AndroidErrorHandler.h" #include "AndroidScheduler.h" -#include "LayoutAnimationsProxy.h" +#include "LayoutAnimationsManager.h" #include "NativeProxy.h" #include "PlatformDepMethodsHolder.h" #include "ReanimatedRuntime.h" @@ -237,11 +237,12 @@ void NativeProxy::installJSIBindings( std::shared_ptr errorHandler = std::make_shared(scheduler_); + std::weak_ptr wrt = animatedRuntime; - // Layout Animations Start - - auto progressLayoutAnimation = [this](int tag, jsi::Value progress) { - this->layoutAnimations->cthis()->progressLayoutAnimation(tag, progress); + auto progressLayoutAnimation = [this, wrt]( + int tag, const jsi::Object &newProps) { + auto newPropsJNI = JNIHelper::ConvertToPropsMap(*wrt.lock(), newProps); + this->layoutAnimations->cthis()->progressLayoutAnimation(tag, newPropsJNI); }; auto endLayoutAnimation = [this](int tag, bool isCancelled, bool removeView) { @@ -249,62 +250,6 @@ void NativeProxy::installJSIBindings( tag, isCancelled, removeView); }; - std::shared_ptr layoutAnimationsProxy = - std::make_shared( - progressLayoutAnimation, endLayoutAnimation, errorHandler); - std::weak_ptr wrt = animatedRuntime; - std::weak_ptr weakLayoutAnimationsProxy = - layoutAnimationsProxy; - layoutAnimations->cthis()->setWeakUIRuntime(wrt); - std::weak_ptr weakErrorHandler = errorHandler; - - layoutAnimations->cthis()->setAnimationStartingBlock( - [wrt, weakLayoutAnimationsProxy, weakErrorHandler]( - int tag, - alias_ref type, - alias_ref> values) { - auto runtime = wrt.lock(); - auto layoutAnimationsProxy = weakLayoutAnimationsProxy.lock(); - if (!runtime || !layoutAnimationsProxy) { - return; - } - auto &rt = *runtime; - jsi::Object yogaValues(rt); - for (const auto &entry : *values) { - try { - auto key = - jsi::String::createFromAscii(rt, entry.first->toStdString()); - auto value = stod(entry.second->toStdString()); - yogaValues.setProperty(rt, key, value); - } catch (std::invalid_argument e) { - if (auto errorHandler = weakErrorHandler.lock()) { - errorHandler->setError("Failed to convert value to number"); - errorHandler->raise(); - } - } - } - - layoutAnimationsProxy->startLayoutAnimation( - *runtime, tag, type->toStdString(), yogaValues); - }); - - layoutAnimations->cthis()->setHasAnimationBlock( - [weakLayoutAnimationsProxy](int tag, std::string type) { - auto layoutAnimationsProxy = weakLayoutAnimationsProxy.lock(); - return layoutAnimationsProxy && - layoutAnimationsProxy->hasLayoutAnimation(tag, type); - }); - - layoutAnimations->cthis()->setClearAnimationConfigBlock( - [weakLayoutAnimationsProxy](int tag) { - auto layoutAnimationsProxy = weakLayoutAnimationsProxy.lock(); - if (layoutAnimationsProxy) { - layoutAnimationsProxy->clearLayoutAnimationConfig(tag); - } - }); - - // Layout Animations End - PlatformDepMethodsHolder platformDepMethodsHolder = { requestRender, #ifdef RCT_NEW_ARCH_ENABLED @@ -316,6 +261,8 @@ void NativeProxy::installJSIBindings( configurePropsFunction, #endif getCurrentTime, + progressLayoutAnimation, + endLayoutAnimation, registerSensorFunction, unregisterSensorFunction, setGestureStateFunction, @@ -333,12 +280,13 @@ void NativeProxy::installJSIBindings( #else propObtainer, #endif - layoutAnimationsProxy, platformDepMethodsHolder); - _nativeReanimatedModule = module; + scheduler_->setRuntimeManager(module); + _nativeReanimatedModule = module; std::weak_ptr weakModule = module; + #ifdef RCT_NEW_ARCH_ENABLED this->registerEventHandler([weakModule, getCurrentTime]( std::string eventName, @@ -390,6 +338,45 @@ void NativeProxy::installJSIBindings( // reactScheduler_ = binding->getScheduler(); // reactScheduler_->addEventListener(eventListener_); + std::weak_ptr weakErrorHandler = errorHandler; + + layoutAnimations->cthis()->setAnimationStartingBlock( + [wrt, weakModule, weakErrorHandler]( + int tag, + alias_ref type, + alias_ref> values) { + auto &rt = *wrt.lock(); + jsi::Object yogaValues(rt); + for (const auto &entry : *values) { + try { + auto key = + jsi::String::createFromAscii(rt, entry.first->toStdString()); + auto value = stod(entry.second->toStdString()); + yogaValues.setProperty(rt, key, value); + } catch (std::invalid_argument e) { + if (auto errorHandler = weakErrorHandler.lock()) { + errorHandler->setError("Failed to convert value to number"); + errorHandler->raise(); + } + } + } + + weakModule.lock()->layoutAnimationsManager().startLayoutAnimation( + rt, tag, type->toStdString(), yogaValues); + }); + + layoutAnimations->cthis()->setHasAnimationBlock( + [weakModule](int tag, const std::string &type) { + return weakModule.lock()->layoutAnimationsManager().hasLayoutAnimation( + tag, type); + }); + + layoutAnimations->cthis()->setClearAnimationConfigBlock( + [weakModule](int tag) { + weakModule.lock()->layoutAnimationsManager().clearLayoutAnimationConfig( + tag); + }); + runtime_->global().setProperty( *runtime_, jsi::PropNameID::forAscii(*runtime_, "__reanimatedModuleProxy"), diff --git a/ios/native/NativeProxy.mm b/ios/native/NativeProxy.mm index 1ad8e83d1a2..6a136616762 100644 --- a/ios/native/NativeProxy.mm +++ b/ios/native/NativeProxy.mm @@ -1,4 +1,3 @@ -#import #import #import #import @@ -229,8 +228,14 @@ static id convertJSIValueToObjCObject(jsi::Runtime &runtime, const jsi::Value &v [nodesManager synchronouslyUpdateViewOnUIThread:viewTag props:uiProps]; }; - auto layoutAnimationsProxy = std::make_shared( - [](int tag, jsi::Object newStyle) {}, [](int tag, bool isCancelled, bool removeView) {}, errorHandler); + auto progressLayoutAnimation = [=](int tag, const jsi::Object &newStyle) { + // noop + }; + + auto endLayoutAnimation = [=](int tag, bool isCancelled, bool removeView) { + // noop + }; + #else // Layout Animations start __block std::weak_ptr weakScheduler = scheduler; @@ -247,19 +252,15 @@ static id convertJSIValueToObjCObject(jsi::Runtime &runtime, const jsi::Value &v [reaUiManagerNoCast setUp:animationsManager]; __weak REAAnimationsManager *weakAnimationsManager = animationsManager; - auto progressLayoutAnimation = [weakAnimationsManager, animatedRuntime](int tag, jsi::Object newStyle) { - REAAnimationsManager *animationsManager = weakAnimationsManager; - if (animationsManager) { - NSDictionary *propsDict = convertJSIObjectToNSDictionary(*animatedRuntime, newStyle); - [animationsManager progressLayoutAnimationWithStyle:propsDict forTag:@(tag)]; - } + std::weak_ptr wrt = animatedRuntime; + + auto progressLayoutAnimation = [=](int tag, const jsi::Object &newStyle) { + NSDictionary *propsDict = convertJSIObjectToNSDictionary(*wrt.lock(), newStyle); + [weakAnimationsManager progressLayoutAnimationWithStyle:propsDict forTag:@(tag)]; }; - auto endLayoutAnimation = [weakAnimationsManager](int tag, bool isCancelled, bool removeView) { - REAAnimationsManager *animationsManager = weakAnimationsManager; - if (animationsManager) { - [animationsManager endLayoutAnimationForTag:@(tag) cancelled:isCancelled removeView:removeView]; - } + auto endLayoutAnimation = [=](int tag, bool isCancelled, bool removeView) { + [weakAnimationsManager endLayoutAnimationForTag:@(tag) cancelled:isCancelled removeView:removeView]; }; auto configurePropsFunction = [reanimatedModule]( @@ -269,11 +270,6 @@ static id convertJSIValueToObjCObject(jsi::Runtime &runtime, const jsi::Value &v [reanimatedModule.nodesManager configureUiProps:uiPropsSet andNativeProps:nativePropsSet]; }; - auto layoutAnimationsProxy = - std::make_shared(progressLayoutAnimation, endLayoutAnimation, errorHandler); - auto weakLayoutAnimationsProxy = std::weak_ptr(layoutAnimationsProxy); - std::weak_ptr wrt = animatedRuntime; - // Layout Animations end #endif @@ -318,6 +314,8 @@ static id convertJSIValueToObjCObject(jsi::Runtime &runtime, const jsi::Value &v configurePropsFunction, #endif getCurrentTime, + progressLayoutAnimation, + endLayoutAnimation, registerSensorFunction, unregisterSensorFunction, setGestureStateFunction, @@ -335,7 +333,6 @@ static id convertJSIValueToObjCObject(jsi::Runtime &runtime, const jsi::Value &v #else propObtainer, #endif - layoutAnimationsProxy, platformDepMethodsHolder); scheduler->setRuntimeManager(module); @@ -372,8 +369,8 @@ static id convertJSIValueToObjCObject(jsi::Runtime &runtime, const jsi::Value &v }]; #endif -#ifdef RCT_NEW_ARCH_ENABLED std::weak_ptr weakModule = module; // to avoid retain cycle +#ifdef RCT_NEW_ARCH_ENABLED [reanimatedModule.nodesManager registerPerformOperations:^() { if (auto module = weakModule.lock()) { module->performOperations(); @@ -383,37 +380,24 @@ static id convertJSIValueToObjCObject(jsi::Runtime &runtime, const jsi::Value &v // Layout Animation callbacks setup [animationsManager setAnimationStartingBlock:^( NSNumber *_Nonnull tag, NSString *type, NSDictionary *_Nonnull values, NSNumber *depth) { - std::shared_ptr runtime = wrt.lock(); - if (runtime == nullptr) { - return; - } - auto layoutAnimationsProxy = weakLayoutAnimationsProxy.lock(); - if (layoutAnimationsProxy == nullptr) { - return; - } - jsi::Object yogaValues(*runtime); + jsi::Runtime &rt = *wrt.lock(); + jsi::Object yogaValues(rt); for (NSString *key in values.allKeys) { NSNumber *value = values[key]; - yogaValues.setProperty(*runtime, [key UTF8String], [value doubleValue]); + yogaValues.setProperty(rt, [key UTF8String], [value doubleValue]); } - layoutAnimationsProxy->startLayoutAnimation(*runtime, [tag intValue], std::string([type UTF8String]), yogaValues); + weakModule.lock()->layoutAnimationsManager().startLayoutAnimation( + rt, [tag intValue], std::string([type UTF8String]), yogaValues); }]; [animationsManager setHasAnimationBlock:^(NSNumber *_Nonnull tag, NSString *_Nonnull type) { - auto layoutAnimationsProxy = weakLayoutAnimationsProxy.lock(); - if (layoutAnimationsProxy == nullptr) { - return false; - } - return layoutAnimationsProxy->hasLayoutAnimation([tag intValue], std::string([type UTF8String])); + return weakModule.lock()->layoutAnimationsManager().hasLayoutAnimation( + [tag intValue], std::string([type UTF8String])); }]; [animationsManager setAnimationRemovingBlock:^(NSNumber *_Nonnull tag) { - auto layoutAnimationsProxy = weakLayoutAnimationsProxy.lock(); - if (layoutAnimationsProxy == nullptr) { - return; - } - layoutAnimationsProxy->clearLayoutAnimationConfig([tag intValue]); + weakModule.lock()->layoutAnimationsManager().clearLayoutAnimationConfig([tag intValue]); }]; #endif diff --git a/plugin.js b/plugin.js index 50cd9f47f7e..b4626f361af 100644 --- a/plugin.js +++ b/plugin.js @@ -64,8 +64,14 @@ const globals = new Set([ 'parseInt', 'parseFloat', 'Map', + 'WeakMap', + 'WeakRef', 'Set', '_log', + '_scheduleOnJS', + '_makeShareableClone', + '_updateDataSynchronously', + 'eval', '_updatePropsPaper', '_updatePropsFabric', '_removeShadowNodeFromRegistry', @@ -81,8 +87,8 @@ const globals = new Set([ '_frameTimestamp', 'isNaN', 'LayoutAnimationRepository', - '_stopObservingProgress', - '_startObservingProgress', + '_notifyAboutProgress', + '_notifyAboutEnd', ]); // leaving way to avoid deep capturing by adding 'stopCapturing' to the blacklist @@ -310,7 +316,7 @@ function buildWorkletString(t, fun, closureVariables, name, inputMap) { ) ) ), - t.memberExpression(t.identifier('jsThis'), t.identifier('_closure')) + t.memberExpression(t.thisExpression(), t.identifier('_closure')) ), ]); @@ -322,20 +328,30 @@ function buildWorkletString(t, fun, closureVariables, name, inputMap) { path.node.body.body.unshift(closureDeclaration); } + function prepandRecursiveDeclaration(path) { + if (path.parent.type === 'Program' && path.node.id && path.scope.parent) { + const hasRecursiveCalls = + path.scope.parent.bindings[path.node.id.name]?.references > 0; + if (hasRecursiveCalls) { + path.node.body.body.unshift( + t.variableDeclaration('const', [ + t.variableDeclarator( + t.identifier(path.node.id.name), + t.memberExpression(t.thisExpression(), t.identifier('_recur')) + ), + ]) + ); + } + } + } + return { visitor: { - FunctionDeclaration(path) { - prependClosure(path); - }, - FunctionExpression(path) { - prependClosure(path); - }, - ArrowFunctionExpression(path) { - prependClosure(path); - }, - ObjectMethod(path) { - prependClosure(path); - }, + 'FunctionDeclaration|FunctionExpression|ArrowFunctionExpression|ObjectMethod': + (path) => { + prependClosure(path); + prepandRecursiveDeclaration(path); + }, }, }; } diff --git a/src/createAnimatedComponent.tsx b/src/createAnimatedComponent.tsx index e0593ed4160..8a2b41f650f 100644 --- a/src/createAnimatedComponent.tsx +++ b/src/createAnimatedComponent.tsx @@ -2,12 +2,11 @@ import React, { Component, ComponentType, MutableRefObject, Ref } from 'react'; import { findNodeHandle, Platform, StyleSheet } from 'react-native'; import WorkletEventHandler from './reanimated2/WorkletEventHandler'; import setAndForwardRef from './setAndForwardRef'; -import './reanimated2/layoutReanimation/LayoutAnimationRepository'; +import './reanimated2/layoutReanimation/animationsManager'; import invariant from 'invariant'; import { adaptViewConfig } from './ConfigHelper'; import { RNRenderer } from './reanimated2/platform-specific/RNRenderer'; import { - makeMutable, configureLayoutAnimations, enableLayoutAnimations, runOnUI, @@ -34,6 +33,7 @@ import { ViewRefSet, } from './reanimated2/ViewDescriptorsSet'; import { getShadowNodeWrapperFromRef } from './reanimated2/fabricUtils'; +import { makeShareableShadowNodeWrapper } from './reanimated2/shareables'; function dummyListener() { // empty listener we use to assign to listener properties for which animated @@ -169,7 +169,6 @@ export default function createAnimatedComponent( _isFirstRender = true; animatedStyle: { value: StyleProps } = { value: {} }; initialStyle = {}; - sv: SharedValue> | null; _component: ComponentRef | null = null; static displayName: string; @@ -178,13 +177,11 @@ export default function createAnimatedComponent( if (isJest()) { this.animatedStyle = { value: {} }; } - this.sv = makeMutable({}); } componentWillUnmount() { this._detachNativeEvents(); this._detachStyles(); - this.sv = null; } componentDidMount() { @@ -332,7 +329,9 @@ export default function createAnimatedComponent( } if (global._IS_FABRIC) { - shadowNodeWrapper = getShadowNodeWrapperFromRef(this); + shadowNodeWrapper = makeShareableShadowNodeWrapper( + getShadowNodeWrapperFromRef(this) + ); } } this._viewTag = viewTag as number; @@ -421,28 +420,13 @@ export default function createAnimatedComponent( enableLayoutAnimations(true, false); } if (layout) { - configureLayoutAnimations( - tag, - 'layout', - maybeBuild(layout), - this.sv - ); + configureLayoutAnimations(tag, 'layout', maybeBuild(layout)); } if (entering) { - configureLayoutAnimations( - tag, - 'entering', - maybeBuild(entering), - this.sv - ); + configureLayoutAnimations(tag, 'entering', maybeBuild(entering)); } if (exiting) { - configureLayoutAnimations( - tag, - 'exiting', - maybeBuild(exiting), - this.sv - ); + configureLayoutAnimations(tag, 'exiting', maybeBuild(exiting)); } } diff --git a/src/reanimated2/NativeReanimated/NativeReanimated.ts b/src/reanimated2/NativeReanimated/NativeReanimated.ts index e128a9d54c7..660fa6cc90f 100644 --- a/src/reanimated2/NativeReanimated/NativeReanimated.ts +++ b/src/reanimated2/NativeReanimated/NativeReanimated.ts @@ -1,11 +1,5 @@ import { NativeModules } from 'react-native'; -import { - SharedValue, - SensorValue3D, - SensorValueRotation, - AnimatedKeyboardInfo, -} from '../commonTypes'; -import { Descriptor } from '../hook/commonTypes'; +import { ShareableRef, ShareableSyncDataHolderRef } from '../commonTypes'; import { LayoutAnimationFunction } from '../layoutReanimation'; import { version as jsVersion } from '../../../package.json'; @@ -49,61 +43,54 @@ export class NativeReanimated { } } - installCoreFunctions(valueSetter: (value: T) => void): void { - return this.InnerNativeModule.installCoreFunctions(valueSetter); + getTimestamp(): number { + throw new Error('stub implementation, used on the web only'); } - makeShareable(value: T): T { - return this.InnerNativeModule.makeShareable(value); + installCoreFunctions(valueUnpacker: (value: T) => T): void { + return this.InnerNativeModule.installCoreFunctions(valueUnpacker); } - makeMutable(value: T): SharedValue { - return this.InnerNativeModule.makeMutable(value); + makeShareableClone(value: T): ShareableRef { + return this.InnerNativeModule.makeShareableClone(value); } - makeRemote(object = {}): T { - return this.InnerNativeModule.makeRemote(object); + makeSynchronizedDataHolder( + valueRef: ShareableRef + ): ShareableSyncDataHolderRef { + return this.InnerNativeModule.makeSynchronizedDataHolder(valueRef); } - registerSensor( + getDataSynchronously(ref: ShareableSyncDataHolderRef): T { + return this.InnerNativeModule.getDataSynchronously(ref); + } + + updateDataSynchronously( + ref: ShareableSyncDataHolderRef, + value: T + ): void { + this.InnerNativeModule.updateDataSynchronously(ref, value); + } + + scheduleOnUI(shareable: ShareableRef) { + return this.InnerNativeModule.scheduleOnUI(shareable); + } + + registerSensor( sensorType: number, interval: number, - sensorData: SensorValue3D | SensorValueRotation + handler: ShareableRef ) { - return this.InnerNativeModule.registerSensor( - sensorType, - interval, - sensorData - ); + return this.InnerNativeModule.registerSensor(sensorType, interval, handler); } unregisterSensor(sensorId: number) { return this.InnerNativeModule.unregisterSensor(sensorId); } - startMapper( - mapper: () => void, - inputs: any[] = [], - outputs: any[] = [], - updater: () => void, - viewDescriptors: Descriptor[] | SharedValue - ): number { - return this.InnerNativeModule.startMapper( - mapper, - inputs, - outputs, - updater, - viewDescriptors - ); - } - - stopMapper(mapperId: number): void { - return this.InnerNativeModule.stopMapper(mapperId); - } - registerEventHandler( eventHash: string, - eventHandler: (event: T) => void + eventHandler: ShareableRef ): string { return this.InnerNativeModule.registerEventHandler(eventHash, eventHandler); } @@ -123,15 +110,9 @@ export class NativeReanimated { configureLayoutAnimation( viewTag: number, type: string, - config: Keyframe | LayoutAnimationFunction, - viewSharedValue: SharedValue> | null + config: ShareableRef ) { - this.InnerNativeModule.configureLayoutAnimation( - viewTag, - type, - config, - viewSharedValue - ); + this.InnerNativeModule.configureLayoutAnimation(viewTag, type, config); } enableLayoutAnimations(flag: boolean): void { @@ -142,8 +123,8 @@ export class NativeReanimated { this.InnerNativeModule.configureProps(uiProps, nativeProps); } - subscribeForKeyboardEvents(keyboardEventData: AnimatedKeyboardInfo): number { - return this.InnerNativeModule.subscribeForKeyboardEvents(keyboardEventData); + subscribeForKeyboardEvents(handler: ShareableRef): number { + return this.InnerNativeModule.subscribeForKeyboardEvents(handler); } unsubscribeFromKeyboardEvents(listenerId: number): void { diff --git a/src/reanimated2/ViewDescriptorsSet.ts b/src/reanimated2/ViewDescriptorsSet.ts index 87869e0d8d8..6ee794e822a 100644 --- a/src/reanimated2/ViewDescriptorsSet.ts +++ b/src/reanimated2/ViewDescriptorsSet.ts @@ -2,7 +2,6 @@ import { useRef } from 'react'; import { makeMutable } from './core'; import { SharedValue } from './commonTypes'; import { Descriptor } from './hook/commonTypes'; -import { shouldBeUseWeb } from './PlatformChecker'; export interface ViewRefSet { items: Set; @@ -11,82 +10,44 @@ export interface ViewRefSet { } export interface ViewDescriptorsSet { - batchToRemove: Set; - tags: Set; - waitForInsertSync: boolean; - waitForRemoveSync: boolean; sharableViewDescriptors: SharedValue; - items: Descriptor[]; add: (item: Descriptor) => void; remove: (viewTag: number) => void; - rebuildsharableViewDescriptors: ( - sharableViewDescriptor: SharedValue - ) => void; } -const scheduleUpdates = shouldBeUseWeb() ? requestAnimationFrame : setImmediate; - export function makeViewDescriptorsSet(): ViewDescriptorsSet { - const ref = useRef(null); - if (ref.current === null) { - const data: ViewDescriptorsSet = { - batchToRemove: new Set(), - tags: new Set(), - waitForInsertSync: false, - waitForRemoveSync: false, - sharableViewDescriptors: makeMutable([]), - items: [], - - add: (item: Descriptor) => { - if (data.tags.has(item.tag)) { - return; + const sharableViewDescriptors = makeMutable([]); + const data: ViewDescriptorsSet = { + sharableViewDescriptors, + add: (item: Descriptor) => { + sharableViewDescriptors.modify((descriptors: Descriptor[]) => { + 'worklet'; + const index = descriptors.findIndex( + (descriptor) => descriptor.tag === item.tag + ); + if (index !== -1) { + descriptors[index] = item; + } else { + descriptors.push(item); } - data.tags.add(item.tag); - data.items.push(item); - - if (!data.waitForInsertSync) { - data.waitForInsertSync = true; - - scheduleUpdates(() => { - data.sharableViewDescriptors.value = data.items; - data.waitForInsertSync = false; - }); - } - }, - - remove: (viewTag: number) => { - data.batchToRemove.add(viewTag); - - if (!data.waitForRemoveSync) { - data.waitForRemoveSync = true; - - scheduleUpdates(() => { - const items = []; - for (const item of data.items) { - if (data.batchToRemove.has(item.tag)) { - data.tags.delete(item.tag); - } else { - items.push(item); - } - } - data.items = items; - data.sharableViewDescriptors.value = items; - data.batchToRemove = new Set(); - data.waitForRemoveSync = false; - }); + return descriptors; + }); + }, + + remove: (viewTag: number) => { + sharableViewDescriptors.modify((descriptors: Descriptor[]) => { + 'worklet'; + const index = descriptors.findIndex( + (descriptor) => descriptor.tag === viewTag + ); + if (index !== -1) { + descriptors.splice(index, 1); } - }, - - rebuildsharableViewDescriptors: ( - sharableViewDescriptors: SharedValue - ) => { - data.sharableViewDescriptors = sharableViewDescriptors; - }, - }; - ref.current = data; - } - - return ref.current; + return descriptors; + }); + }, + }; + return data; } export function makeViewsRefSet(): ViewRefSet { diff --git a/src/reanimated2/WorkletEventHandler.ts b/src/reanimated2/WorkletEventHandler.ts index 671a19af3ec..0adf0beca80 100644 --- a/src/reanimated2/WorkletEventHandler.ts +++ b/src/reanimated2/WorkletEventHandler.ts @@ -1,5 +1,6 @@ import { NativeEvent } from './commonTypes'; import NativeReanimatedModule from './NativeReanimated'; +import { registerEventHandler, unregisterEventHandler } from './core'; function jsListener>( eventName: string, @@ -44,25 +45,17 @@ export default class WorkletEventHandler> { registerForEvents(viewTag: number, fallbackEventName?: string): void { this.viewTag = viewTag; this.registrations = this.eventNames.map((eventName) => - NativeReanimatedModule.registerEventHandler( - viewTag + eventName, - this.worklet - ) + registerEventHandler(viewTag + eventName, this.worklet) ); if (this.registrations.length === 0 && fallbackEventName) { this.registrations.push( - NativeReanimatedModule.registerEventHandler( - viewTag + fallbackEventName, - this.worklet - ) + registerEventHandler(viewTag + fallbackEventName, this.worklet) ); } } unregisterFromEvents(): void { - this.registrations.forEach((id) => - NativeReanimatedModule.unregisterEventHandler(id) - ); + this.registrations.forEach((id) => unregisterEventHandler(id)); this.registrations = []; } } diff --git a/src/reanimated2/animation/util.ts b/src/reanimated2/animation/util.ts index 79b0e5f1c74..d33342df447 100644 --- a/src/reanimated2/animation/util.ts +++ b/src/reanimated2/animation/util.ts @@ -29,6 +29,7 @@ export function initialUpdaterRun(updater: () => T): T { IN_STYLE_UPDATER = false; return result; } + interface RecognizedPrefixSuffix { prefix?: string; suffix?: string; diff --git a/src/reanimated2/commonTypes.ts b/src/reanimated2/commonTypes.ts index 69e98524dce..96b0e2af241 100644 --- a/src/reanimated2/commonTypes.ts +++ b/src/reanimated2/commonTypes.ts @@ -44,8 +44,36 @@ export interface AnimatedStyle } export interface SharedValue { value: T; + addListener: (listenerID: number, listener: (value: T) => void) => void; + removeListener: (listenerID: number) => void; + modify: (modifier: (value: T) => T) => void; } +// The below type is used for HostObjects retured by the JSI API that don't have +// any accessable fields or methods but can carry data that is accessed from the +// c++ side. We add a field to the type to make it possible for typescript to recognize +// which JSI methods accept those types as arguments and to be able to correctly type +// check other methods that may use them. However, this field is not actually defined +// nor should be used for anything else as assigning any data to those objects will +// throw an error. +export type ShareableRef = { + __hostObjectShareableJSRef: T; +}; + +export type ShareableSyncDataHolderRef = { + __hostObjectShareableJSRefSyncDataHolder: T; +}; + +export type MapperRegistry = { + start: ( + mapperID: number, + worklet: () => void, + inputs: SharedValue[], + outputs?: SharedValue[] + ) => void; + stop: (mapperID: number) => void; +}; + export type Context = Record; export interface WorkletFunction { @@ -133,8 +161,6 @@ export type Value3D = { z: number; }; -export type SensorValue3D = SharedValue; - export type ValueRotation = { qw: number; qx: number; @@ -145,8 +171,6 @@ export type ValueRotation = { roll: number; }; -export type SensorValueRotation = SharedValue; - export type ShadowNodeWrapper = object; export enum KeyboardState { diff --git a/src/reanimated2/core.ts b/src/reanimated2/core.ts index 79fef3634ca..da75c4490bd 100644 --- a/src/reanimated2/core.ts +++ b/src/reanimated2/core.ts @@ -1,19 +1,23 @@ -/* global _WORKLET _getCurrentTime _frameTimestamp _eventTimestamp _setGlobalConsole */ +/* global _setGlobalConsole */ import NativeReanimatedModule from './NativeReanimated'; -import { Platform } from 'react-native'; import { nativeShouldBeMock, shouldBeUseWeb, isWeb } from './PlatformChecker'; +import { BasicWorkletFunction, Value3D, ValueRotation } from './commonTypes'; import { - BasicWorkletFunction, - ComplexWorkletFunction, - SharedValue, - AnimationObject, - AnimatableValue, - Timestamp, -} from './commonTypes'; -import { Descriptor } from './hook/commonTypes'; -import JSReanimated from './js-reanimated/JSReanimated'; + makeShareableCloneRecursive, + makeShareable as makeShareableUnwrapped, +} from './shareables'; +import { runOnUI, runOnJS } from './threads'; +import { startMapper as startMapperUnwrapped } from './mappers'; +import { + makeMutable as makeMutableUnwrapped, + makeRemote as makeRemoteUnwrapped, +} from './mutables'; import { LayoutAnimationFunction } from './layoutReanimation'; +export { stopMapper } from './mappers'; +export { runOnJS, runOnUI } from './threads'; +export { getTimestamp } from './time'; + if (global._setGlobalConsole === undefined) { // it can happen when Reanimated plugin wasn't added, but the user uses the only API from version 1 global._setGlobalConsole = () => { @@ -26,18 +30,6 @@ export type ReanimatedConsole = Pick< 'debug' | 'log' | 'warn' | 'info' | 'error' >; -export type WorkletValue = - | (() => AnimationObject) - | AnimationObject - | AnimatableValue - | Descriptor; -interface WorkletValueSetterContext { - _animation?: AnimationObject | null; - _value?: AnimatableValue | Descriptor; - value?: AnimatableValue; - _setValue?: (val: AnimatableValue | Descriptor) => void; -} - const testWorklet: BasicWorkletFunction = () => { 'worklet'; }; @@ -70,35 +62,36 @@ export const isConfiguredCheck: () => void = () => { checkPluginState(true); }; -function pushFrame(frame: (timestamp: Timestamp) => void): void { - (NativeReanimatedModule as JSReanimated).pushFrame(frame); -} +const configurationCheckWrapper = __DEV__ + ? , U>(fn: (...args: T) => U) => { + return (...args: T): U => { + isConfigured(true); + return fn(...args); + }; + } + : , U>(fn: (...args: T) => U) => fn; -export function requestFrame(frame: (timestamp: Timestamp) => void): void { - 'worklet'; - if (NativeReanimatedModule.native) { - requestAnimationFrame(frame); - } else { - pushFrame(frame); - } -} +export const startMapper = __DEV__ + ? configurationCheckWrapper(startMapperUnwrapped) + : startMapperUnwrapped; + +export const makeShareable = __DEV__ + ? configurationCheckWrapper(makeShareableUnwrapped) + : makeShareableUnwrapped; + +export const makeMutable = __DEV__ + ? configurationCheckWrapper(makeMutableUnwrapped) + : makeMutableUnwrapped; + +export const makeRemote = __DEV__ + ? configurationCheckWrapper(makeRemoteUnwrapped) + : makeRemoteUnwrapped; global._WORKLET = false; global._log = function (s: string) { console.log(s); }; -export function runOnUI( - worklet: ComplexWorkletFunction -): (...args: A) => void { - return makeShareable(worklet); -} - -export function makeShareable(value: T): T { - isConfiguredCheck(); - return NativeReanimatedModule.makeShareable(value); -} - export function getViewProp(viewTag: string, propName: string): Promise { if (global._IS_FABRIC) { throw new Error( @@ -121,206 +114,82 @@ export function getViewProp(viewTag: string, propName: string): Promise { }); } -let _getTimestamp: () => number; -if (nativeShouldBeMock()) { - _getTimestamp = () => { - return (NativeReanimatedModule as JSReanimated).getTimestamp(); - }; -} else { - _getTimestamp = () => { - 'worklet'; - if (_frameTimestamp) { - return _frameTimestamp; - } - if (_eventTimestamp) { - return _eventTimestamp; - } - return _getCurrentTime(); - }; -} - -export function getTimestamp(): number { +function valueUnpacker(objectToUnpack: any): any { 'worklet'; - if (Platform.OS === 'web') { - return (NativeReanimatedModule as JSReanimated).getTimestamp(); + let workletsCache = global.__workletsCache; + let handleCache = global.__handleCache; + if (workletsCache === undefined) { + // init + workletsCache = global.__workletsCache = new Map(); + handleCache = global.__handleCache = new WeakMap(); } - return _getTimestamp(); -} - -function workletValueSetter( - this: WorkletValueSetterContext, - value: T -): void { - 'worklet'; - const previousAnimation = this._animation; - if (previousAnimation) { - previousAnimation.cancelled = true; - this._animation = null; - } - if ( - typeof value === 'function' || - (value !== null && - typeof value === 'object' && - (value as AnimationObject).onFrame !== undefined) - ) { - const animation: AnimationObject = - typeof value === 'function' - ? (value as () => AnimationObject)() - : (value as AnimationObject); - // prevent setting again to the same value - // and triggering the mappers that treat this value as an input - // this happens when the animation's target value(stored in animation.current until animation.onStart is called) is set to the same value as a current one(this._value) - // built in animations that are not higher order(withTiming, withSpring) hold target value in .current - if (this._value === animation.current && !animation.isHigherOrder) { - animation.callback && animation.callback(true); - return; + if (objectToUnpack.__workletHash) { + let workletFun = workletsCache.get(objectToUnpack.__workletHash); + if (workletFun === undefined) { + // eslint-disable-next-line no-eval + workletFun = eval('(' + objectToUnpack.asString + '\n)') as ( + ...args: any[] + ) => any; + workletsCache.set(objectToUnpack.__workletHash, workletFun); } - // animated set - const initializeAnimation = (timestamp: number) => { - animation.onStart(animation, this.value, timestamp, previousAnimation); - }; - const currentTimestamp = getTimestamp(); - initializeAnimation(currentTimestamp); - const step = (timestamp: number) => { - if (animation.cancelled) { - animation.callback && animation.callback(false /* finished */); - return; - } - const finished = animation.onFrame(animation, timestamp); - animation.finished = true; - animation.timestamp = timestamp; - this._value = animation.current; - if (finished) { - animation.callback && animation.callback(true /* finished */); - } else { - requestAnimationFrame(step); - } - }; - - this._animation = animation; - step(currentTimestamp); - } else { - // prevent setting again to the same value - // and triggering the mappers that treat this value as an input - if (this._value === value) { - return; + const functionInstance = workletFun.bind(objectToUnpack); + objectToUnpack._recur = functionInstance; + return functionInstance; + } else if (objectToUnpack.__init) { + let value = handleCache!.get(objectToUnpack); + if (value === undefined) { + value = objectToUnpack.__init(); + handleCache!.set(objectToUnpack, value); } - this._value = value as Descriptor | AnimatableValue; - } -} - -// We cannot use pushFrame -// so we use own implementation for js -function workletValueSetterJS( - this: WorkletValueSetterContext, - value: T -): void { - const previousAnimation = this._animation; - if (previousAnimation) { - previousAnimation.cancelled = true; - this._animation = null; - } - if ( - typeof value === 'function' || - (value !== null && - typeof value === 'object' && - (value as AnimationObject).onFrame) - ) { - // animated set - const animation: AnimationObject = - typeof value === 'function' - ? (value as () => AnimationObject)() - : (value as AnimationObject); - let initializeAnimation: ((timestamp: number) => void) | null = ( - timestamp: number - ) => { - animation.onStart(animation, this.value, timestamp, previousAnimation); - }; - const step = (timestamp: number) => { - if (animation.cancelled) { - animation.callback && animation.callback(false /* finished */); - return; - } - if (initializeAnimation) { - initializeAnimation(timestamp); - initializeAnimation = null; // prevent closure from keeping ref to previous animation - } - const finished = animation.onFrame(animation, timestamp); - animation.timestamp = timestamp; - this._setValue && this._setValue(animation.current as AnimatableValue); - if (finished) { - animation.callback && animation.callback(true /* finished */); - } else { - requestFrame(step); - } - }; - - this._animation = animation; - step(getTimestamp()); + return value; } else { - this._setValue && this._setValue(value as AnimatableValue | Descriptor); + throw new Error('data type not recognized by unpack method'); } } -export function makeMutable(value: T): SharedValue { - isConfiguredCheck(); - return NativeReanimatedModule.makeMutable(value); +export function registerEventHandler( + eventHash: string, + eventHandler: (event: T) => void +): string { + return NativeReanimatedModule.registerEventHandler( + eventHash, + makeShareableCloneRecursive(eventHandler) + ); } -export function makeRemote(object = {}): T { - isConfiguredCheck(); - return NativeReanimatedModule.makeRemote(object); +export function unregisterEventHandler(id: string): void { + return NativeReanimatedModule.unregisterEventHandler(id); } -export function startMapper( - mapper: () => void, - inputs: any[] = [], - outputs: any[] = [], - updater: () => void = () => { - // noop - }, - viewDescriptors: Descriptor[] | SharedValue = [] +export function subscribeForKeyboardEvents( + eventHandler: (state: number, height: number) => void ): number { - isConfiguredCheck(); - return NativeReanimatedModule.startMapper( - mapper, - inputs, - outputs, - updater, - viewDescriptors + return NativeReanimatedModule.subscribeForKeyboardEvents( + makeShareableCloneRecursive(eventHandler) ); } -export function stopMapper(mapperId: number): void { - NativeReanimatedModule.stopMapper(mapperId); +export function unsubscribeFromKeyboardEvents(listenerId: number): void { + return NativeReanimatedModule.unsubscribeFromKeyboardEvents(listenerId); } -export interface RunOnJSFunction { - __callAsync?: (...args: A) => void; - (...args: A): R; +export function registerSensor( + sensorType: number, + interval: number, + eventHandler: (data: Value3D | ValueRotation) => void +): number { + return NativeReanimatedModule.registerSensor( + sensorType, + interval, + makeShareableCloneRecursive(eventHandler) + ); } -export function runOnJS( - fun: RunOnJSFunction -): () => void { - 'worklet'; - if (!_WORKLET) { - return fun; - } - if (!fun.__callAsync) { - throw new Error( - "Attempting to call runOnJS with an object that is not a host function. Using runOnJS is only possible with methods that are defined on the main React-Native Javascript thread and that aren't marked as worklets" - ); - } else { - return fun.__callAsync; - } +export function unregisterSensor(listenerId: number): void { + return NativeReanimatedModule.unregisterSensor(listenerId); } -NativeReanimatedModule.installCoreFunctions( - NativeReanimatedModule.native - ? (workletValueSetter as (value: T) => void) - : (workletValueSetterJS as (value: T) => void) -); +NativeReanimatedModule.installCoreFunctions(valueUnpacker); if (!isWeb() && isConfigured()) { const capturableConsole = console; @@ -369,14 +238,12 @@ export function enableLayoutAnimations( export function configureLayoutAnimations( viewTag: number, type: string, - config: LayoutAnimationFunction | Keyframe, - viewSharedValue: SharedValue> | null + config: LayoutAnimationFunction | Keyframe ): void { NativeReanimatedModule.configureLayoutAnimation( viewTag, type, - config, - viewSharedValue + makeShareableCloneRecursive(config) ); } @@ -385,7 +252,3 @@ export function configureProps(uiProps: string[], nativeProps: string[]): void { NativeReanimatedModule.configureProps(uiProps, nativeProps); } } - -export function jestResetJsReanimatedModule() { - (NativeReanimatedModule as JSReanimated).jestResetModule(); -} diff --git a/src/reanimated2/globals.d.ts b/src/reanimated2/globals.d.ts index 8845a4d2f17..f89a69c1ccf 100644 --- a/src/reanimated2/globals.d.ts +++ b/src/reanimated2/globals.d.ts @@ -2,10 +2,14 @@ import type { AnimatedStyle, StyleProps, MeasuredDimensions, + MapperRegistry, + ShareableRef, + ShareableSyncDataHolderRef, } from './commonTypes'; import type { ReanimatedConsole } from './core'; import type { FrameCallbackRegistryUI } from './frameCallback/FrameCallbackRegistryUI'; import type { ShadowNodeWrapper } from './hook/commonTypes'; +import { LayoutAnimationStartFunction } from './layoutReanimation'; import type { NativeReanimated } from './NativeReanimated/NativeReanimated'; declare global { @@ -18,17 +22,20 @@ declare global { const _setGlobalConsole: (console?: ReanimatedConsole) => void; const _log: (s: string) => void; const _getCurrentTime: () => number; - const _stopObservingProgress: ( + const _getTimestamp: () => number; + const _notifyAboutProgress: (tag: number, value: number) => void; + const _notifyAboutEnd: ( tag: number, - cancelled: boolean, + finished: boolean, removeView: boolean ) => void; - const _startObservingProgress: ( - tag: number, - viewSharedValue: { value: unknown; _value: unknown }, - type: string - ) => void; const _setGestureState: (handlerTag: number, newState: number) => void; + const _makeShareableClone: (value: any) => any; + const _updateDataSynchronously: ( + dataHolder: ShareableSyncDataHolderRef, + data: ShareableRef + ) => void; + const _scheduleOnJS: (fun: ShareableRef, args?: ShareableRef) => void; const _updatePropsPaper: ( tag: number, name: string, @@ -55,12 +62,6 @@ declare global { ) => void; const _chronoNow: () => number; const performance: { now: () => number }; - const LayoutAnimationRepository: { - configs: Record; - registerConfig(tag: number, config: Record): void; - removeConfig(tag: number): void; - startAnimationForTag(tag: number, type: string, yogaValues: unknown): void; - }; const ReanimatedDataMock: { now: () => number; }; @@ -77,12 +78,14 @@ declare global { _setGlobalConsole: (console?: ReanimatedConsole) => void; _log: (s: string) => void; _getCurrentTime: () => number; - _stopObservingProgress: (tag: number, flag: boolean) => void; - _startObservingProgress: ( - tag: number, - flag: { value: boolean; _value: boolean } - ) => void; + _getTimestamp: () => number; _setGestureState: (handlerTag: number, newState: number) => void; + _makeShareableClone: (value: any) => any; + _updateDataSynchronously: ( + ShareableSyncDataHolderRef, + ShareableRef + ) => void; + _scheduleOnJS: (fun: ShareableRef, args?: ShareableRef) => void; _updatePropsPaper: ( tag: number, name: string, @@ -109,19 +112,16 @@ declare global { ) => void; _chronoNow: () => number; performance: { now: () => number }; - LayoutAnimationRepository: { - startAnimationForTag( - tag: number, - type: string, - yogaValues: Record, - config: LayoutAnimationFunction | Keyframe, - viewSharedValue: { value: unknown; _value: unknown } - ): void; + LayoutAnimationsManager: { + start: LayoutAnimationStartFunction; }; ReanimatedDataMock: { now: () => number; }; _frameCallbackRegistry: FrameCallbackRegistryUI; + __workletsCache?: Map any>; + __handleCache?: WeakMap; + __mapperRegistry?: MapperRegistry; } } } diff --git a/src/reanimated2/hook/useAnimatedKeyboard.ts b/src/reanimated2/hook/useAnimatedKeyboard.ts index c162ac95718..fffba0c6dfc 100644 --- a/src/reanimated2/hook/useAnimatedKeyboard.ts +++ b/src/reanimated2/hook/useAnimatedKeyboard.ts @@ -1,6 +1,9 @@ import { useEffect, useRef } from 'react'; -import NativeReanimated from '../NativeReanimated'; -import { makeMutable } from '../core'; +import { + makeMutable, + subscribeForKeyboardEvents, + unsubscribeFromKeyboardEvents, +} from '../core'; import { AnimatedKeyboardInfo, KeyboardState } from '../commonTypes'; export function useAnimatedKeyboard(): AnimatedKeyboardInfo { @@ -10,24 +13,30 @@ export function useAnimatedKeyboard(): AnimatedKeyboardInfo { if (ref.current === null) { const keyboardEventData: AnimatedKeyboardInfo = { - state: makeMutable(KeyboardState.UNKNOWN), + state: makeMutable(KeyboardState.UNKNOWN), height: makeMutable(0), }; - listenerId.current = - NativeReanimated.subscribeForKeyboardEvents(keyboardEventData); + listenerId.current = subscribeForKeyboardEvents((state, height) => { + 'worklet'; + keyboardEventData.state.value = state; + keyboardEventData.height.value = height; + }); ref.current = keyboardEventData; isSubscribed.current = true; } useEffect(() => { if (isSubscribed.current === false && ref.current !== null) { + const keyboardEventData = ref.current; // subscribe again after Fast Refresh - listenerId.current = NativeReanimated.subscribeForKeyboardEvents( - ref.current - ); + listenerId.current = subscribeForKeyboardEvents((state, height) => { + 'worklet'; + keyboardEventData.state.value = state; + keyboardEventData.height.value = height; + }); isSubscribed.current = true; } return () => { - NativeReanimated.unsubscribeFromKeyboardEvents(listenerId.current); + unsubscribeFromKeyboardEvents(listenerId.current); isSubscribed.current = false; }; }, []); diff --git a/src/reanimated2/hook/useAnimatedRef.ts b/src/reanimated2/hook/useAnimatedRef.ts index 1b272360d37..f76dc01ec7f 100644 --- a/src/reanimated2/hook/useAnimatedRef.ts +++ b/src/reanimated2/hook/useAnimatedRef.ts @@ -4,30 +4,39 @@ import { RefObjectFunction } from './commonTypes'; import { ShadowNodeWrapper } from '../commonTypes'; import { getTag } from '../NativeMethods'; import { getShadowNodeWrapperFromHostInstance } from '../fabricUtils'; +import { + makeShareableCloneRecursive, + registerShareableMapping, +} from '../shareables'; + +const getTagValueFunction = global._IS_FABRIC + ? getShadowNodeWrapperFromHostInstance + : getTag; export function useAnimatedRef(): RefObjectFunction { const tag = useSharedValue(-1); const ref = useRef>(); - const isFabric = global._IS_FABRIC; if (!ref.current) { const fun: RefObjectFunction = >((component) => { - 'worklet'; // enters when ref is set by attaching to a component if (component) { - tag.value = isFabric - ? getShadowNodeWrapperFromHostInstance(component) - : getTag(component); + tag.value = getTagValueFunction(component); fun.current = component; } return tag.value; }); - Object.defineProperty(fun, 'current', { - value: null, - writable: true, - enumerable: false, + fun.current = null; + + const remoteRef = makeShareableCloneRecursive({ + __init: () => { + 'worklet'; + return () => tag.value; + }, }); + registerShareableMapping(fun, remoteRef); + ref.current = fun; } diff --git a/src/reanimated2/hook/useAnimatedSensor.ts b/src/reanimated2/hook/useAnimatedSensor.ts index 79be3530745..04ad72a4d27 100644 --- a/src/reanimated2/hook/useAnimatedSensor.ts +++ b/src/reanimated2/hook/useAnimatedSensor.ts @@ -1,7 +1,6 @@ import { useEffect, useRef } from 'react'; -import { makeMutable } from '../core'; -import NativeReanimated from '../NativeReanimated'; -import { SensorValue3D, SensorValueRotation } from '../commonTypes'; +import { makeMutable, registerSensor, unregisterSensor } from '../core'; +import { SharedValue, Value3D, ValueRotation } from '../commonTypes'; export enum SensorType { ACCELEROMETER = 1, @@ -16,18 +15,40 @@ export type SensorConfig = { }; export type AnimatedSensor = { - sensor: SensorValue3D | SensorValueRotation | null; + sensor: SharedValue; unregister: () => void; isAvailable: boolean; config: SensorConfig; }; +function initSensorData( + sensorType: SensorType +): SharedValue { + if (sensorType === SensorType.ROTATION) { + return makeMutable({ + qw: 0, + qx: 0, + qy: 0, + qz: 0, + yaw: 0, + pitch: 0, + roll: 0, + }); + } else { + return makeMutable({ + x: 0, + y: 0, + z: 0, + }); + } +} + export function useAnimatedSensor( sensorType: SensorType, userConfig?: SensorConfig ): AnimatedSensor { const ref = useRef({ - sensor: null, + sensor: initSensorData(sensorType), unregister: () => { // NOOP }, @@ -37,40 +58,21 @@ export function useAnimatedSensor( }, }); - if (ref.current.sensor === null) { - ref.current.config = { interval: 'auto', ...userConfig }; - let sensorData; - if (sensorType === SensorType.ROTATION) { - sensorData = { - qw: 0, - qx: 0, - qy: 0, - qz: 0, - yaw: 0, - pitch: 0, - roll: 0, - }; - } else { - sensorData = { - x: 0, - y: 0, - z: 0, - }; - } - ref.current.sensor = makeMutable(sensorData) as any; - } - useEffect(() => { ref.current.config = { interval: 'auto', ...userConfig }; - const id = NativeReanimated.registerSensor( + const sensorData = ref.current.sensor!; + const id = registerSensor( sensorType, ref.current.config.interval === 'auto' ? -1 : ref.current.config.interval, - ref.current.sensor as any + (data) => { + 'worklet'; + sensorData.value = data; + } ); if (id !== -1) { // if sensor is available - ref.current.unregister = () => NativeReanimated.unregisterSensor(id); + ref.current.unregister = () => unregisterSensor(id); ref.current.isAvailable = true; } else { // if sensor is unavailable diff --git a/src/reanimated2/hook/useAnimatedStyle.ts b/src/reanimated2/hook/useAnimatedStyle.ts index cdb0ec39737..37902a1c900 100644 --- a/src/reanimated2/hook/useAnimatedStyle.ts +++ b/src/reanimated2/hook/useAnimatedStyle.ts @@ -1,23 +1,14 @@ /* global _frameTimestamp */ import { MutableRefObject, useEffect, useRef } from 'react'; -import { - startMapper, - stopMapper, - makeRemote, - requestFrame, - getTimestamp, - makeMutable, -} from '../core'; +import { startMapper, stopMapper, makeRemote, getTimestamp } from '../core'; import updateProps, { updatePropsJestWrapper } from '../UpdateProps'; import { initialUpdaterRun } from '../animation'; import NativeReanimatedModule from '../NativeReanimated'; import { useSharedValue } from './useSharedValue'; import { buildWorkletsHash, - canApplyOptimalisation, getStyleWithoutAnimations, - hasColorProps, isAnimated, parseColors, styleDiff, @@ -61,7 +52,7 @@ interface AnimationRef { updater: () => AnimatedStyle; }; remoteState: AnimatedState; - sharableViewDescriptors: SharedValue; + viewDescriptors: ViewDescriptorsSet; } function prepareAnimation( @@ -71,13 +62,13 @@ function prepareAnimation( ): void { 'worklet'; if (Array.isArray(animatedProp)) { - animatedProp.forEach((prop, index) => + animatedProp.forEach((prop, index) => { prepareAnimation( prop, lastAnimation && lastAnimation[index], lastValue && lastValue[index] - ) - ); + ); + }); // return animatedProp; } if (typeof animatedProp === 'object' && animatedProp.onFrame) { @@ -207,8 +198,9 @@ function styleUpdater( } if (hasAnimations) { - const frame = (timestamp: Timestamp) => { + const frame = (_timestamp?: Timestamp) => { const { animations, last, isAnimationCancelled } = state; + const timestamp = _timestamp ?? getTimestamp(); if (isAnimationCancelled) { state.isAnimationRunning = false; return; @@ -237,7 +229,7 @@ function styleUpdater( } if (!allFinished) { - requestFrame(frame); + requestAnimationFrame(frame); } else { state.isAnimationRunning = false; } @@ -250,7 +242,7 @@ function styleUpdater( if (_frameTimestamp) { frame(_frameTimestamp); } else { - requestFrame(frame); + requestAnimationFrame(frame); } } state.last = Object.assign({}, oldValues, newValues); @@ -301,8 +293,9 @@ function jestStyleUpdater( } }); - function frame(timestamp: Timestamp) { + function frame(_timestamp?: Timestamp) { const { animations, last, isAnimationCancelled } = state; + const timestamp = _timestamp ?? getTimestamp(); if (isAnimationCancelled) { state.isAnimationRunning = false; return; @@ -337,7 +330,7 @@ function jestStyleUpdater( } if (!allFinished) { - requestFrame(frame); + requestAnimationFrame(frame); } else { state.isAnimationRunning = false; } @@ -351,7 +344,7 @@ function jestStyleUpdater( if (_frameTimestamp) { frame(_frameTimestamp); } else { - requestFrame(frame); + requestAnimationFrame(frame); } } } else { @@ -407,7 +400,6 @@ export function useAnimatedStyle( adapters?: AdapterWorkletFunction | AdapterWorkletFunction[] ): AnimatedStyleResult { const viewsRef: ViewRefSet = makeViewsRefSet(); - const viewDescriptors: ViewDescriptorsSet = makeViewDescriptorsSet(); const initRef = useRef(); const inputs = Object.values(updater._closure ?? {}); const adaptersArray: AdapterWorkletFunction[] = adapters @@ -437,24 +429,26 @@ export function useAnimatedStyle( value: initialStyle, updater: updater, }, - remoteState: makeRemote({ last: initialStyle }), - sharableViewDescriptors: makeMutable([]), + remoteState: makeRemote({ + last: initialStyle, + animations: {}, + isAnimationCancelled: false, + isAnimationRunning: false, + }), + viewDescriptors: makeViewDescriptorsSet(), }; - viewDescriptors.rebuildsharableViewDescriptors( - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - initRef.current!.sharableViewDescriptors - ); } - dependencies.push(initRef.current?.sharableViewDescriptors.value); // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - const { initial, remoteState, sharableViewDescriptors } = initRef.current!; + const { initial, remoteState, viewDescriptors } = initRef.current!; + const sharableViewDescriptors = viewDescriptors.sharableViewDescriptors; const maybeViewRef = NativeReanimatedModule.native ? undefined : viewsRef; + dependencies.push(sharableViewDescriptors); + useEffect(() => { let fun; let updaterFn = updater as BasicWorkletFunctionOptional; - let optimalization = updater.__optimalization; if (adapters) { updaterFn = () => { 'worklet'; @@ -466,29 +460,7 @@ export function useAnimatedStyle( }; } - if (canApplyOptimalisation(updaterFn) && !shouldBeUseWeb()) { - if (hasColorProps(updaterFn())) { - updaterFn = () => { - 'worklet'; - const newValues = updaterFn(); - const oldValues = remoteState.last; - const diff = styleDiff(oldValues, newValues); - remoteState.last = Object.assign({}, oldValues, newValues); - parseColors(diff); - return diff; - }; - } else { - updaterFn = () => { - 'worklet'; - const newValues = updaterFn(); - const oldValues = remoteState.last; - const diff = styleDiff(oldValues, newValues); - remoteState.last = Object.assign({}, oldValues, newValues); - return diff; - }; - } - } else if (!shouldBeUseWeb()) { - optimalization = 0; + if (!shouldBeUseWeb()) { updaterFn = () => { 'worklet'; const style = updaterFn(); @@ -496,9 +468,6 @@ export function useAnimatedStyle( return style; }; } - if (typeof updater.__optimalization !== undefined) { - updaterFn.__optimalization = optimalization; - } if (isJest()) { fun = () => { @@ -525,14 +494,7 @@ export function useAnimatedStyle( ); }; } - const mapperId = startMapper( - fun, - inputs, - [], - updaterFn, - // TODO fix this - sharableViewDescriptors - ); + const mapperId = startMapper(fun, inputs); return () => { stopMapper(mapperId); }; diff --git a/src/reanimated2/hook/useSharedValue.ts b/src/reanimated2/hook/useSharedValue.ts index ce0978619c8..6b4bae20b9a 100644 --- a/src/reanimated2/hook/useSharedValue.ts +++ b/src/reanimated2/hook/useSharedValue.ts @@ -3,11 +3,14 @@ import { cancelAnimation } from '../animation'; import { SharedValue } from '../commonTypes'; import { makeMutable } from '../core'; -export function useSharedValue(init: T): SharedValue { - const ref = useRef>(makeMutable(init)); +export function useSharedValue( + init: T, + oneWayReadsOnly = false +): SharedValue { + const ref = useRef>(makeMutable(init, oneWayReadsOnly)); if (ref.current === null) { - ref.current = makeMutable(init); + ref.current = makeMutable(init, oneWayReadsOnly); } useEffect(() => { diff --git a/src/reanimated2/hook/utils.ts b/src/reanimated2/hook/utils.ts index 763de8164bf..667ba24047f 100644 --- a/src/reanimated2/hook/utils.ts +++ b/src/reanimated2/hook/utils.ts @@ -51,7 +51,7 @@ export function useHandler( const initRef = useRef | null>(null); if (initRef.current === null) { initRef.current = { - context: makeRemote({}), + context: makeRemote({} as TContext), savedDependencies: [], }; } diff --git a/src/reanimated2/interpolateColor.ts b/src/reanimated2/interpolateColor.ts index 70eae8ccd91..f3355240ea6 100644 --- a/src/reanimated2/interpolateColor.ts +++ b/src/reanimated2/interpolateColor.ts @@ -141,11 +141,11 @@ export function useInterpolateConfig( outputRange: readonly (string | number)[], colorSpace = ColorSpace.RGB ): SharedValue { - return useSharedValue({ + return useSharedValue({ inputRange, outputRange, colorSpace, - cache: makeMutable(null), + cache: makeMutable(null), }); } diff --git a/src/reanimated2/jestUtils.ts b/src/reanimated2/jestUtils.ts index 1e3c5882b4f..48a0d3341ca 100644 --- a/src/reanimated2/jestUtils.ts +++ b/src/reanimated2/jestUtils.ts @@ -1,7 +1,5 @@ // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-nocheck -import { jestResetJsReanimatedModule } from './core'; - declare global { // eslint-disable-next-line @typescript-eslint/no-namespace namespace jest { @@ -139,52 +137,39 @@ const compareStyle = (received, expectedStyle, config) => { }; let frameTime = 1000 / config.fps; -let requestAnimationFrameCopy; -let currentTimestamp = 0; - -const requestAnimationFrame = (callback) => { - setTimeout(callback, frameTime); -}; const beforeTest = () => { - jestResetJsReanimatedModule(); - requestAnimationFrameCopy = global.requestAnimationFrame; - global.requestAnimationFrame = requestAnimationFrame; - global.ReanimatedDataMock = { - now: () => currentTimestamp, - }; - currentTimestamp = 0; jest.useFakeTimers(); }; const afterTest = () => { + jest.runOnlyPendingTimers(); jest.useRealTimers(); - global.requestAnimationFrame = requestAnimationFrameCopy; -}; - -const tickTravel = () => { - currentTimestamp += frameTime; - jest.advanceTimersByTime(frameTime); }; export const withReanimatedTimer = (animationTest) => { + console.warn( + 'This method is deprecated, you shoulddefine your own before and after test hooks to enable jest.useFakeTimers(). Check out the documentation for details on testing' + ); beforeTest(); animationTest(); afterTest(); }; export const advanceAnimationByTime = (time = frameTime) => { - for (let i = 0; i <= Math.ceil(time / frameTime); i++) { - tickTravel(); - } - jest.advanceTimersByTime(frameTime); + console.warn( + 'This method is deprecated, use jest.advanceTimersByTime directly' + ); + jest.advanceTimersByTime(time); + jest.runOnlyPendingTimers(); }; export const advanceAnimationByFrame = (count) => { - for (let i = 0; i <= count; i++) { - tickTravel(); - } - jest.advanceTimersByTime(frameTime); + console.warn( + 'This method is deprecated, use jest.advanceTimersByTime directly' + ); + jest.advanceTimersByTime(count * frameTime); + jest.runOnlyPendingTimers(); }; export const setUpTests = (userConfig = {}) => { diff --git a/src/reanimated2/js-reanimated/JSReanimated.ts b/src/reanimated2/js-reanimated/JSReanimated.ts index 3628beee68b..977dc5a2c0c 100644 --- a/src/reanimated2/js-reanimated/JSReanimated.ts +++ b/src/reanimated2/js-reanimated/JSReanimated.ts @@ -1,100 +1,39 @@ -import MapperRegistry from './MapperRegistry'; -import Mapper from './Mapper'; -import MutableValue from './MutableValue'; import { NativeReanimated } from '../NativeReanimated/NativeReanimated'; -import { - Timestamp, - NestedObjectValues, - AnimatedKeyboardInfo, -} from '../commonTypes'; +import { ShareableRef } from '../commonTypes'; import { isJest } from '../PlatformChecker'; export default class JSReanimated extends NativeReanimated { - _valueSetter?: (value: T) => void = undefined; - - _renderRequested = false; - _mapperRegistry: MapperRegistry = new MapperRegistry(this); - _frames: ((timestamp: Timestamp) => void)[] = []; - timeProvider: { now: () => number }; + _valueUnpacker?: (value: T) => void = undefined; constructor() { super(false); if (isJest()) { - this.timeProvider = { now: () => global.ReanimatedDataMock.now() }; + // on node < 16 jest have problems mocking performance.now method which + // results in the tests failing, since date precision isn't that important + // for tests, we use Date.now instead + this.getTimestamp = () => Date.now(); } else { - this.timeProvider = { now: () => window.performance.now() }; - } - } - - pushFrame(frame: (timestamp: Timestamp) => void): void { - this._frames.push(frame); - this.maybeRequestRender(); - } - - getTimestamp(): number { - return this.timeProvider.now(); - } - - maybeRequestRender(): void { - if (!this._renderRequested) { - this._renderRequested = true; - - requestAnimationFrame((_timestampMs) => { - this._renderRequested = false; - - this._onRender(this.getTimestamp()); - }); - } - } - - _onRender(timestampMs: number): void { - this._mapperRegistry.execute(); - - const frames = [...this._frames]; - this._frames = []; - - for (let i = 0, len = frames.length; i < len; ++i) { - frames[i](timestampMs); - } - - if (this._mapperRegistry.needRunOnRender) { - this._mapperRegistry.execute(); + this.getTimestamp = () => window.performance.now(); } } - installCoreFunctions(valueSetter: (value: T) => void): void { - this._valueSetter = valueSetter; - } - - makeShareable(value: T): T { - return value; - } - - makeMutable(value: T): MutableValue { - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - return new MutableValue(value, this._valueSetter!); + makeShareableClone(value: T): ShareableRef { + return { __hostObjectShareableJSRef: value }; } - makeRemote(object = {}): T { - return object as T; + installCoreFunctions(valueUnpacker: (value: T) => T): void { + this._valueUnpacker = valueUnpacker; } - startMapper( - mapper: () => void, - inputs: NestedObjectValues>[] = [], - outputs: NestedObjectValues>[] = [] - ): number { - const instance = new Mapper(this, mapper, inputs, outputs); - const mapperId = this._mapperRegistry.startMapper(instance); - this.maybeRequestRender(); - return mapperId; + scheduleOnUI(worklet: ShareableRef) { + // @ts-ignore web implementation has still not been updated after the rewrite, this will be addressed once the web implementation updates are ready + requestAnimationFrame(worklet); } - stopMapper(mapperId: number): void { - this._mapperRegistry.stopMapper(mapperId); - } - - registerEventHandler(_: string, __: (event: T) => void): string { + registerEventHandler( + _eventHash: string, + _eventHandler: ShareableRef + ): string { // noop return ''; } @@ -118,20 +57,7 @@ export default class JSReanimated extends NativeReanimated { // noop } - jestResetModule() { - if (isJest()) { - /** - * If someone used timers to stop animation before the end, - * then _renderRequested was set as true - * and any new update from another test wasn't applied. - */ - this._renderRequested = false; - } else { - throw Error('This method can be only use in Jest testing.'); - } - } - - subscribeForKeyboardEvents(_: AnimatedKeyboardInfo): number { + subscribeForKeyboardEvents(_: ShareableRef): number { console.warn( '[Reanimated] useAnimatedKeyboard is not available on web yet.' ); diff --git a/src/reanimated2/js-reanimated/index.ts b/src/reanimated2/js-reanimated/index.ts index 631b20a6943..52a9c18adad 100644 --- a/src/reanimated2/js-reanimated/index.ts +++ b/src/reanimated2/js-reanimated/index.ts @@ -3,6 +3,10 @@ import { AnimatedStyle, StyleProps } from '../commonTypes'; const reanimatedJS = new JSReanimated(); +global._makeShareableClone = (c) => c; +global._scheduleOnJS = setImmediate; +global._getTimestamp = reanimatedJS.getTimestamp.bind(reanimatedJS); + interface JSReanimatedComponent { previousStyle: StyleProps; setNativeProps: (style: StyleProps) => void; diff --git a/src/reanimated2/layoutReanimation/LayoutAnimationRepository.ts b/src/reanimated2/layoutReanimation/LayoutAnimationRepository.ts deleted file mode 100644 index 3c799063d64..00000000000 --- a/src/reanimated2/layoutReanimation/LayoutAnimationRepository.ts +++ /dev/null @@ -1,49 +0,0 @@ -/* global _stopObservingProgress, _startObservingProgress */ -import { runOnUI } from '../core'; -import { withStyleAnimation } from '../animation/styleAnimation'; -import { LogBox } from 'react-native'; - -LogBox.ignoreLogs(['Overriding previous layout animation with']); - -runOnUI(() => { - 'worklet'; - - const enteringAnimationForTag: Record = {}; - - global.LayoutAnimationRepository = { - startAnimationForTag(tag, type, yogaValues, config, viewSharedValue) { - const style = config(yogaValues); - let currentAnimation = style.animations; - - if (type === 'entering') { - enteringAnimationForTag[tag] = currentAnimation; - } else if (type === 'layout') { - // When layout animation is requested, but entering is still running, we merge - // new layout animation targets into the ongoing animation - const enteringAnimation = enteringAnimationForTag[tag]; - if (enteringAnimation) { - currentAnimation = { ...enteringAnimation, ...style.animations }; - } - } - _stopObservingProgress(tag, false, false); - viewSharedValue._value = Object.assign( - {}, - viewSharedValue._value, - style.initialValues - ); - const animation = withStyleAnimation(currentAnimation); - - animation.callback = (finished?: boolean) => { - if (finished) { - delete enteringAnimationForTag[tag]; - const shouldRemoveView = type === 'exiting'; - _stopObservingProgress(tag, finished, shouldRemoveView); - } - style.callback && style.callback(finished); - }; - - viewSharedValue.value = animation; - _startObservingProgress(tag, viewSharedValue, type); - }, - }; -})(); diff --git a/src/reanimated2/layoutReanimation/animationBuilder/commonTypes.ts b/src/reanimated2/layoutReanimation/animationBuilder/commonTypes.ts index fe3c372a850..8173201b2d8 100644 --- a/src/reanimated2/layoutReanimation/animationBuilder/commonTypes.ts +++ b/src/reanimated2/layoutReanimation/animationBuilder/commonTypes.ts @@ -59,6 +59,13 @@ export type LayoutAnimationFunction = ( targetValues: LayoutAnimationsValues ) => LayoutAnimation; +export type LayoutAnimationStartFunction = ( + tag: number, + type: string, + yogaValues: LayoutAnimationsValues, + config: LayoutAnimationFunction +) => void; + export interface ILayoutAnimationBuilder { build: () => LayoutAnimationFunction; } diff --git a/src/reanimated2/layoutReanimation/animationBuilder/index.ts b/src/reanimated2/layoutReanimation/animationBuilder/index.ts index 09181f724b4..44258292d78 100644 --- a/src/reanimated2/layoutReanimation/animationBuilder/index.ts +++ b/src/reanimated2/layoutReanimation/animationBuilder/index.ts @@ -12,6 +12,7 @@ export type { IExitAnimationBuilder, LayoutAnimationsValues, LayoutAnimationFunction, + LayoutAnimationStartFunction, ILayoutAnimationBuilder, BaseLayoutAnimationConfig, BaseBuilderAnimationConfig, diff --git a/src/reanimated2/layoutReanimation/animationsManager.ts b/src/reanimated2/layoutReanimation/animationsManager.ts new file mode 100644 index 00000000000..33de28d2fcb --- /dev/null +++ b/src/reanimated2/layoutReanimation/animationsManager.ts @@ -0,0 +1,94 @@ +import { runOnUI } from '../core'; +import { withStyleAnimation } from '../animation/styleAnimation'; +import { LogBox } from 'react-native'; +import { SharedValue } from '../commonTypes'; +import { makeUIMutable } from '../mutables'; +import { + LayoutAnimationFunction, + LayoutAnimationsValues, +} from './animationBuilder'; + +LogBox.ignoreLogs(['Overriding previous layout animation with']); + +const TAG_OFFSET = 1e9; + +function startObservingProgress( + tag: number, + sharedValue: SharedValue +): void { + 'worklet'; + sharedValue.addListener(tag + TAG_OFFSET, () => { + _notifyAboutProgress(tag, sharedValue.value); + }); +} + +function stopObservingProgress( + tag: number, + sharedValue: SharedValue, + cancelled: boolean, + removeView: boolean +): void { + 'worklet'; + sharedValue.removeListener(tag + TAG_OFFSET); + _notifyAboutEnd(tag, cancelled, removeView); +} + +function createLayoutAnimationManager() { + 'worklet'; + const enteringAnimationForTag = new Map(); + const mutableValuesForTag = new Map(); + + return { + start( + tag: number, + type: string, + yogaValues: LayoutAnimationsValues, + config: LayoutAnimationFunction + ) { + const style = config(yogaValues); + let currentAnimation = style.animations; + + if (type === 'entering') { + enteringAnimationForTag.set(tag, currentAnimation); + } else if (type === 'layout') { + // When layout animation is requested, but entering is still running, we merge + // new layout animation targets into the ongoing animation + const enteringAnimation = enteringAnimationForTag.get(tag); + if (enteringAnimation) { + currentAnimation = { ...enteringAnimation, ...style.animations }; + } + } + + let value = mutableValuesForTag.get(tag); + if (value === undefined) { + value = makeUIMutable(style.initialValues); + mutableValuesForTag.set(tag, value); + } else { + stopObservingProgress(tag, value, false, false); + value._value = style.initialValues; + } + + // @ts-ignore The line below started failing because I added types to the method – don't have time to fix it right now + const animation = withStyleAnimation(currentAnimation); + + animation.callback = (finished?: boolean) => { + if (finished) { + enteringAnimationForTag.delete(tag); + mutableValuesForTag.delete(tag); + const shouldRemoveView = type === 'exiting'; + stopObservingProgress(tag, value, finished, shouldRemoveView); + } + style.callback && + style.callback(finished === undefined ? false : finished); + }; + + startObservingProgress(tag, value); + value.value = animation; + }, + }; +} + +runOnUI(() => { + 'worklet'; + global.LayoutAnimationsManager = createLayoutAnimationManager(); +})(); diff --git a/src/reanimated2/layoutReanimation/index.ts b/src/reanimated2/layoutReanimation/index.ts index a1fad7728dc..e05331a295f 100644 --- a/src/reanimated2/layoutReanimation/index.ts +++ b/src/reanimated2/layoutReanimation/index.ts @@ -1,4 +1,4 @@ -import './LayoutAnimationRepository'; +import './animationsManager'; export * from './animationBuilder'; export * from './defaultAnimations'; export * from './defaultTransitions'; diff --git a/src/reanimated2/mappers.ts b/src/reanimated2/mappers.ts new file mode 100644 index 00000000000..4bb300ee3fb --- /dev/null +++ b/src/reanimated2/mappers.ts @@ -0,0 +1,179 @@ +import { SharedValue } from './commonTypes'; +import { runOnUI } from './threads'; + +export type Mapper = { + id: number; + dirty: boolean; + worklet: () => void; + inputs: SharedValue[]; + outputs?: SharedValue[]; +}; + +export function createMapperRegistry() { + 'worklet'; + const mappers = new Map(); + let sortedMappers: Mapper[] = []; + + let frameRequested = false; + + function updateMappersOrder() { + // sort mappers topologically + // the algorithm here takes adventage of a fact that the topological order + // of a transposed graph is a reverse topological order of the original graph + // The graph in our case consists of mappers and an edge between two mappers + // A and B exists if there is a shared value that's on A's output lists and on + // B's input list. + // + // We don't need however to calculate that graph as it is easier to work with + // the transposed version of it that can be calculated ad-hoc. For the transposed + // version to be traversed we use "pre" map that maps share value to mappers that + // output that shared value. Then we can infer all the outgoing edges for a given + // mapper simply by scanning it's input list and checking if any of the shared values + // from that list exists in the "pre" map. If they do, then we have an edge between + // that mapper and the mappers from the "pre" list for the given shared value. + // + // For topological sorting we use a dfs-based approach that requires the graph to + // be traversed in dfs order and each node after being processed lands at the + // beginning of the topological order list. Since we traverse a transposed graph, + // instead of reversing that order we can use a normal array and push processed + // mappers to the end. There is no need to reverse that array after we are done. + const pre = new Map(); // map from sv -> mapper that outputs that sv + mappers.forEach((mapper) => { + if (mapper.outputs) { + for (const output of mapper.outputs) { + const preMappers = pre.get(output); + if (preMappers === undefined) { + pre.set(output, [mapper]); + } else { + preMappers.push(mapper); + } + } + } + }); + const visited = new Set(); + const newOrder: Mapper[] = []; + function dfs(mapper: Mapper) { + visited.add(mapper); + for (const input of mapper.inputs) { + const preMappers = pre.get(input); + if (preMappers) { + for (const preMapper of preMappers) { + if (!visited.has(preMapper)) { + dfs(preMapper); + } + } + } + } + newOrder.push(mapper); + } + mappers.forEach((mapper) => { + if (!visited.has(mapper)) { + dfs(mapper); + } + }); + sortedMappers = newOrder; + } + + function mapperFrame() { + frameRequested = false; + if (mappers.size !== sortedMappers.length) { + updateMappersOrder(); + } + for (const mapper of sortedMappers) { + if (mapper.dirty) { + mapper.dirty = false; + mapper.worklet(); + } + } + } + + function maybeRequestUpdates() { + if (!frameRequested) { + requestAnimationFrame(mapperFrame); + frameRequested = true; + } + } + + function extractInputs( + inputs: any, + resultArray: SharedValue[] + ): SharedValue[] { + if (Array.isArray(inputs)) { + for (const input of inputs) { + input && extractInputs(input, resultArray); + } + } else if (inputs.addListener) { + resultArray.push(inputs); + } else if (typeof inputs === 'object') { + for (const element of Object.values(inputs)) { + element && extractInputs(element, resultArray); + } + } + return resultArray; + } + + return { + start: ( + mapperID: number, + worklet: () => void, + inputs: SharedValue[], + outputs?: SharedValue[] + ) => { + const mapper = { + id: mapperID, + dirty: true, + worklet, + inputs: extractInputs(inputs, []), + outputs, + }; + mappers.set(mapper.id, mapper); + sortedMappers = []; + for (const sv of mapper.inputs) { + sv.addListener(mapper.id, () => { + mapper.dirty = true; + maybeRequestUpdates(); + }); + } + maybeRequestUpdates(); + }, + stop: (mapperID: number) => { + const mapper = mappers.get(mapperID); + if (mapper) { + mappers.delete(mapper.id); + sortedMappers = []; + for (const sv of mapper.inputs) { + sv.removeListener(mapper.id); + } + } + }, + }; +} + +let MAPPER_ID = 9999; + +export function startMapper( + worklet: () => void, + inputs: any[] = [], + outputs: any[] = [] +): number { + const mapperID = (MAPPER_ID += 1); + + runOnUI(() => { + 'worklet'; + let mapperRegistry = global.__mapperRegistry; + if (mapperRegistry === undefined) { + mapperRegistry = global.__mapperRegistry = createMapperRegistry(); + } + mapperRegistry.start(mapperID, worklet, inputs, outputs); + })(); + + return mapperID; +} + +export function stopMapper(mapperID: number): void { + runOnUI(() => { + 'worklet'; + const mapperRegistry = global.__mapperRegistry; + mapperRegistry?.stop(mapperID); + })(); +} diff --git a/src/reanimated2/mutables.ts b/src/reanimated2/mutables.ts new file mode 100644 index 00000000000..71a5d9658d6 --- /dev/null +++ b/src/reanimated2/mutables.ts @@ -0,0 +1,151 @@ +import NativeReanimatedModule from './NativeReanimated'; +import { SharedValue, ShareableSyncDataHolderRef } from './commonTypes'; +import { + makeShareableCloneOnUIRecursive, + makeShareableCloneRecursive, + registerShareableMapping, +} from './shareables'; +import { runOnUI } from './threads'; +import { valueSetter } from './valueSetter'; +export { stopMapper } from './mappers'; + +export function makeUIMutable( + initial: T, + syncDataHolder?: ShareableSyncDataHolderRef +) { + 'worklet'; + + const listeners = new Map(); + let value = initial; + + const self = { + set value(newValue) { + valueSetter(self, newValue); + }, + get value() { + return value; + }, + /** + * _value prop should only be accessed by the valueSetter implementation + * which may make the decision about updating the mutable value depending + * on the provided new value. All other places should only attempt to modify + * the mutable by assigning to value prop directly. + */ + set _value(newValue: T) { + value = newValue; + if (syncDataHolder) { + _updateDataSynchronously( + syncDataHolder, + makeShareableCloneOnUIRecursive(newValue) + ); + } + listeners.forEach((listener) => { + listener(newValue); + }); + }, + get _value(): T { + return value; + }, + addListener: (id: number, listener: (newValue: T) => void) => { + listeners.set(id, listener); + }, + removeListener: (id: number) => { + listeners.delete(id); + }, + _animation: null, + }; + return self; +} + +export function makeMutable( + initial: T, + oneWayReadsOnly = false +): SharedValue { + let value: T = initial; + let syncDataHolder: ShareableSyncDataHolderRef | undefined; + if (!oneWayReadsOnly && NativeReanimatedModule.native) { + // updates are always synchronous when running on web or in Jest environment + syncDataHolder = NativeReanimatedModule.makeSynchronizedDataHolder( + makeShareableCloneRecursive(value) + ); + registerShareableMapping(syncDataHolder); + } + const handle = makeShareableCloneRecursive({ + __init: () => { + 'worklet'; + return makeUIMutable(initial, syncDataHolder); + }, + }); + // listeners can only work on JS thread on Web and jest environments + const listeners = NativeReanimatedModule.native ? undefined : new Map(); + const mutable = { + set value(newValue) { + if (NativeReanimatedModule.native) { + runOnUI(() => { + 'worklet'; + mutable.value = newValue; + })(); + } else { + valueSetter(mutable, newValue); + } + }, + get value() { + if (syncDataHolder) { + return NativeReanimatedModule.getDataSynchronously(syncDataHolder); + } + return value; + }, + set _value(newValue: T) { + if (NativeReanimatedModule.native) { + throw new Error( + 'Setting `_value` directly is only possible on the UI runtime' + ); + } + value = newValue; + listeners!.forEach((listener) => { + listener(newValue); + }); + }, + get _value(): T { + if (NativeReanimatedModule.native) { + throw new Error( + 'Reading from `_value` directly is only possible on the UI runtime' + ); + } + return value; + }, + modify: (modifier: (value: T) => T) => { + runOnUI(() => { + 'worklet'; + mutable.value = modifier(mutable.value); + })(); + }, + addListener: (id: number, listener: (value: T) => void) => { + if (NativeReanimatedModule.native) { + throw new Error('adding listeners is only possible on the UI runtime'); + } + listeners!.set(id, listener); + }, + removeListener: (id: number) => { + if (NativeReanimatedModule.native) { + throw new Error( + 'removing listeners is only possible on the UI runtime' + ); + } + listeners!.delete(id); + }, + }; + registerShareableMapping(mutable, handle); + return mutable; +} + +export function makeRemote(initial: T = {} as T): T { + const handle = makeShareableCloneRecursive({ + __init: () => { + 'worklet'; + return initial; + }, + }); + registerShareableMapping(initial, handle); + return initial; +} diff --git a/src/reanimated2/shareables.ts b/src/reanimated2/shareables.ts new file mode 100644 index 00000000000..84aedc65643 --- /dev/null +++ b/src/reanimated2/shareables.ts @@ -0,0 +1,121 @@ +import NativeReanimatedModule from './NativeReanimated'; +import { ShareableRef } from './commonTypes'; +import { shouldBeUseWeb } from './PlatformChecker'; + +// for web/chrome debugger/jest environments this file provides a stub implementation +// where no shareable references are used. Instead, the objects themselves are used +// instead of shareable references, because of the fact that we don't have to deal with +// runnning the code on separate VMs. +const USE_STUB_IMPLEMENTATION = shouldBeUseWeb(); + +const _shareableCache = new WeakMap< + Record, + ShareableRef | symbol +>(); +// the below symbol is used to represent a mapping from the value to itself +// this is used to allow for a converted shareable to be passed to makeShareableClone +const _shareableFlag = Symbol('shareable flag'); + +export function registerShareableMapping( + shareable: any, + shareableRef?: ShareableRef +): void { + if (USE_STUB_IMPLEMENTATION) { + return; + } + _shareableCache.set(shareable, shareableRef || _shareableFlag); +} + +export function makeShareableShadowNodeWrapper(shadowNodeWrapper: T): T { + const adoptedSNW = + NativeReanimatedModule.makeShareableClone(shadowNodeWrapper); + registerShareableMapping(shadowNodeWrapper, adoptedSNW); + return shadowNodeWrapper; +} + +export function makeShareableCloneRecursive(value: any): ShareableRef { + if (USE_STUB_IMPLEMENTATION) { + return value; + } + // This one actually may be worth to be moved to c++, we also need similar logic to run on the UI thread + const type = typeof value; + if ((type === 'object' || type === 'function') && value !== null) { + const cached = _shareableCache.get(value); + if (cached === _shareableFlag) { + return value; + } else if (cached !== undefined) { + return cached as ShareableRef; + } else { + let toAdapt: any; + if (Array.isArray(value)) { + toAdapt = value.map((element) => makeShareableCloneRecursive(element)); + } else if (type === 'function' && value.__workletHash === undefined) { + // this is a remote function + toAdapt = value; + } else { + toAdapt = {}; + for (const [key, element] of Object.entries(value)) { + toAdapt[key] = makeShareableCloneRecursive(element); + } + } + if (__DEV__) { + // we freeze objects that are transformed to shareable. This should help + // detect issues when someone modifies data after it's been converted to + // shareable. Meaning that they may be doing a faulty assumption in their + // code expecting that the updates are going to automatically populate to + // the object sent to the UI thread. If the user really wants some objects + // to be mutable they should use share values instead. + Object.freeze(value); + } + const adopted = NativeReanimatedModule.makeShareableClone(toAdapt); + _shareableCache.set(value, adopted); + _shareableCache.set(adopted, _shareableFlag); + return adopted; + } + } + return NativeReanimatedModule.makeShareableClone(value); +} + +export function makeShareableCloneOnUIRecursive(value: T): ShareableRef { + 'worklet'; + if (USE_STUB_IMPLEMENTATION) { + // @ts-ignore web is an interesting place where we don't run a secondary VM on the UI thread + // see more details in the comment where USE_STUB_IMPLEMENTATION is defined. + return value; + } + function cloneRecursive(value: T): ShareableRef { + const type = typeof value; + if ((type === 'object' || type === 'function') && value !== null) { + let toAdapt: any; + if (Array.isArray(value)) { + toAdapt = value.map((element) => cloneRecursive(element)); + } else { + toAdapt = {}; + for (const [key, element] of Object.entries(value)) { + toAdapt[key] = cloneRecursive(element); + } + } + if (__DEV__) { + // See the reasoning behind freezing in the other comment above. + Object.freeze(value); + } + return _makeShareableClone(toAdapt); + } + return _makeShareableClone(value); + } + return cloneRecursive(value); +} + +export function makeShareable(value: T): T { + if (USE_STUB_IMPLEMENTATION) { + return value; + } + const handle = makeShareableCloneRecursive({ + __init: () => { + 'worklet'; + return value; + }, + }); + registerShareableMapping(value, handle); + return value; +} diff --git a/src/reanimated2/threads.ts b/src/reanimated2/threads.ts new file mode 100644 index 00000000000..e4f97f50dbe --- /dev/null +++ b/src/reanimated2/threads.ts @@ -0,0 +1,39 @@ +import NativeReanimatedModule from './NativeReanimated'; +import { ComplexWorkletFunction } from './commonTypes'; +import { + makeShareableCloneOnUIRecursive, + makeShareableCloneRecursive, +} from './shareables'; + +export function runOnUI( + worklet: ComplexWorkletFunction +): (...args: A) => void { + if (__DEV__) { + if (worklet.__workletHash === undefined) { + throw new Error('runOnUI() can only be used on worklets'); + } + } + return (...args) => { + NativeReanimatedModule.scheduleOnUI( + makeShareableCloneRecursive(() => { + 'worklet'; + return worklet(...args); + }) + ); + }; +} + +export function runOnJS( + fun: ComplexWorkletFunction +): (...args: A) => void { + 'worklet'; + if (!_WORKLET) { + return fun; + } + return (...args) => { + _scheduleOnJS( + fun, + args.length > 0 ? makeShareableCloneOnUIRecursive(args) : undefined + ); + }; +} diff --git a/src/reanimated2/time.ts b/src/reanimated2/time.ts new file mode 100644 index 00000000000..74e347edbe2 --- /dev/null +++ b/src/reanimated2/time.ts @@ -0,0 +1,30 @@ +import NativeReanimatedModule from './NativeReanimated'; +import { Platform } from 'react-native'; +import { nativeShouldBeMock } from './PlatformChecker'; +export { stopMapper } from './mappers'; + +let _getTimestamp: () => number; +if (nativeShouldBeMock()) { + _getTimestamp = () => { + return NativeReanimatedModule.getTimestamp(); + }; +} else { + _getTimestamp = () => { + 'worklet'; + if (_frameTimestamp) { + return _frameTimestamp; + } + if (_eventTimestamp) { + return _eventTimestamp; + } + return _getCurrentTime(); + }; +} + +export function getTimestamp(): number { + 'worklet'; + if (Platform.OS === 'web') { + return NativeReanimatedModule.getTimestamp(); + } + return _getTimestamp(); +} diff --git a/src/reanimated2/valueSetter.ts b/src/reanimated2/valueSetter.ts new file mode 100644 index 00000000000..88a79fcc37a --- /dev/null +++ b/src/reanimated2/valueSetter.ts @@ -0,0 +1,64 @@ +import { AnimationObject, AnimatableValue } from './commonTypes'; +import { Descriptor } from './hook/commonTypes'; +import { getTimestamp } from './time'; +export { stopMapper } from './mappers'; + +export function valueSetter(sv: any, value: any): void { + 'worklet'; + const previousAnimation = sv._animation; + if (previousAnimation) { + previousAnimation.cancelled = true; + sv._animation = null; + } + if ( + typeof value === 'function' || + (value !== null && + typeof value === 'object' && + (value as AnimationObject).onFrame !== undefined) + ) { + const animation: AnimationObject = + typeof value === 'function' + ? (value as () => AnimationObject)() + : (value as AnimationObject); + // prevent setting again to the same value + // and triggering the mappers that treat this value as an input + // this happens when the animation's target value(stored in animation.current until animation.onStart is called) is set to the same value as a current one(this._value) + // built in animations that are not higher order(withTiming, withSpring) hold target value in .current + if (sv._value === animation.current && !animation.isHigherOrder) { + animation.callback && animation.callback(true); + return; + } + // animated set + const initializeAnimation = (timestamp: number) => { + animation.onStart(animation, sv.value, timestamp, previousAnimation); + }; + const currentTimestamp = getTimestamp(); + initializeAnimation(currentTimestamp); + const step = (timestamp: number) => { + if (animation.cancelled) { + animation.callback && animation.callback(false /* finished */); + return; + } + const finished = animation.onFrame(animation, timestamp); + animation.finished = true; + animation.timestamp = timestamp; + sv._value = animation.current; + if (finished) { + animation.callback && animation.callback(true /* finished */); + } else { + requestAnimationFrame(step); + } + }; + + sv._animation = animation; + + step(currentTimestamp); + } else { + // prevent setting again to the same value + // and triggering the mappers that treat this value as an input + if (sv._value === value) { + return; + } + sv._value = value as Descriptor | AnimatableValue; + } +}