diff --git a/Common/cpp/NativeModules/NativeReanimatedModule.cpp b/Common/cpp/NativeModules/NativeReanimatedModule.cpp index 92bd8ab5c02..d0411da81b4 100644 --- a/Common/cpp/NativeModules/NativeReanimatedModule.cpp +++ b/Common/cpp/NativeModules/NativeReanimatedModule.cpp @@ -10,6 +10,7 @@ #include #include #include "JSIStoreValueUser.h" +#include "ReanimatedHiddenHeaders.h" using namespace facebook; @@ -62,8 +63,9 @@ NativeReanimatedModule::NativeReanimatedModule(std::shared_ptr jsIn std::shared_ptr errorHandler, std::function propObtainer, std::shared_ptr layoutAnimationsProxy, - PlatformDepMethodsHolder platformDepMethodsHolder) : NativeReanimatedModuleSpec(jsInvoker), - RuntimeManager(rt, errorHandler, scheduler), + PlatformDepMethodsHolder platformDepMethodsHolder) : + NativeReanimatedModuleSpec(jsInvoker), + RuntimeManager(rt, errorHandler, scheduler, RuntimeType::UI), mapperRegistry(std::make_shared()), eventHandlerRegistry(std::make_shared()), requestRender(platformDepMethodsHolder.requestRender), @@ -84,6 +86,11 @@ NativeReanimatedModule::NativeReanimatedModule(std::shared_ptr jsIn platformDepMethodsHolder.measuringFunction, platformDepMethodsHolder.getCurrentTime, layoutAnimationsProxy); + onRenderCallback = [this](double timestampMs) { + this->renderRequested = false; + this->onRender(timestampMs); + }; + updaterFunction = platformDepMethodsHolder.updaterFunction; } void NativeReanimatedModule::installCoreFunctions(jsi::Runtime &rt, const jsi::Value &valueSetter) @@ -106,7 +113,15 @@ jsi::Value NativeReanimatedModule::makeRemote(jsi::Runtime &rt, const jsi::Value return ShareableValue::adapt(rt, value, this, ValueType::RemoteObjectType)->getValue(rt); } -jsi::Value NativeReanimatedModule::startMapper(jsi::Runtime &rt, const jsi::Value &worklet, const jsi::Value &inputs, const jsi::Value &outputs) +jsi::Value NativeReanimatedModule::startMapper( + jsi::Runtime &rt, + const jsi::Value &worklet, + const jsi::Value &inputs, + const jsi::Value &outputs, + const jsi::Value &updater, + const jsi::Value &tag, + const jsi::Value &name + ) { static unsigned long MAPPER_ID = 1; @@ -114,11 +129,31 @@ jsi::Value NativeReanimatedModule::startMapper(jsi::Runtime &rt, const jsi::Valu 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); + const int tagInt = tag.asNumber(); + const std::string nameStr = name.asString(rt).utf8(rt); 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); + + std::shared_ptr mapperPointer = std::make_shared( + this, + newMapperId, + mapperFunctionPointer, + inputMutables, + outputMutables, + updaterSV, + tagInt, + nameStr, + optimalizationLvl + ); mapperRegistry->startMapper(mapperPointer); maybeRequestRender(); }); @@ -214,10 +249,7 @@ void NativeReanimatedModule::maybeRequestRender() if (!renderRequested) { renderRequested = true; - requestRender([this](double timestampMs) { - this->renderRequested = false; - this->onRender(timestampMs); - }, *this->runtime); + requestRender(onRenderCallback, *this->runtime); } } @@ -225,13 +257,13 @@ void NativeReanimatedModule::onRender(double timestampMs) { try { - std::vector callbacks = frameCallbacks; - frameCallbacks.clear(); - for (auto callback : callbacks) - { - callback(timestampMs); - } - mapperRegistry->execute(*runtime); + std::vector callbacks = frameCallbacks; + frameCallbacks.clear(); + for (auto& callback : callbacks) + { + callback(timestampMs); + } + mapperRegistry->execute(*runtime); if (mapperRegistry->needRunOnRender()) { diff --git a/Common/cpp/NativeModules/NativeReanimatedModuleSpec.cpp b/Common/cpp/NativeModules/NativeReanimatedModuleSpec.cpp index 22927a73074..a48844faaa4 100644 --- a/Common/cpp/NativeModules/NativeReanimatedModuleSpec.cpp +++ b/Common/cpp/NativeModules/NativeReanimatedModuleSpec.cpp @@ -47,7 +47,13 @@ static jsi::Value __hostFunction_NativeReanimatedModuleSpec_startMapper( 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])); + ->startMapper(rt, + std::move(args[0]), + std::move(args[1]), + std::move(args[2]), + std::move(args[3]), + std::move(args[4]), + std::move(args[5])); } static jsi::Value __hostFunction_NativeReanimatedModuleSpec_stopMapper( @@ -104,7 +110,7 @@ NativeReanimatedModuleSpec::NativeReanimatedModuleSpec(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) { - jsi::Function fun = function( - rt, - ValueWrapper::asString(frozenObj->map["asString"]->valueContainer) - ); - std::shared_ptr funPtr = std::make_shared(std::move(fun)); - worklets[workletHash] = funPtr; + auto codeBuffer = std::make_shared("(" + ValueWrapper::asString(frozenObj->map["asString"]->valueContainer) + ")"); + 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]; } diff --git a/Common/cpp/SharedItems/MutableValueSetterProxy.cpp b/Common/cpp/SharedItems/MutableValueSetterProxy.cpp index f282ed73d01..067a423bcf8 100644 --- a/Common/cpp/SharedItems/MutableValueSetterProxy.cpp +++ b/Common/cpp/SharedItems/MutableValueSetterProxy.cpp @@ -9,9 +9,7 @@ namespace reanimated { void MutableValueSetterProxy::set(jsi::Runtime &rt, const jsi::PropNameID &name, const jsi::Value &newValue) { auto propName = name.utf8(rt); - if (propName == "value") { - // you call `this.value` inside of value setter, we should throw - } else if (propName == "_value") { + if (propName == "_value") { mutableValue->setValue(rt, newValue); } else if (propName == "_animation") { // TODO: assert to allow animation to be set from UI only @@ -19,6 +17,8 @@ void MutableValueSetterProxy::set(jsi::Runtime &rt, const jsi::PropNameID &name, 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 } } diff --git a/Common/cpp/SharedItems/ShareableValue.cpp b/Common/cpp/SharedItems/ShareableValue.cpp index 193db0ff7d9..3c2ede8680b 100644 --- a/Common/cpp/SharedItems/ShareableValue.cpp +++ b/Common/cpp/SharedItems/ShareableValue.cpp @@ -52,7 +52,6 @@ void ShareableValue::adaptCache(jsi::Runtime &rt, const jsi::Value &value) { } void ShareableValue::adapt(jsi::Runtime &rt, const jsi::Value &value, ValueType objectType) { - bool isRNRuntime = RuntimeDecorator::isReactRuntime(rt); if (value.isObject()) { jsi::Object object = value.asObject(rt); jsi::Value hiddenValue = object.getProperty(rt, HIDDEN_HOST_OBJECT_PROP); @@ -117,7 +116,7 @@ void ShareableValue::adapt(jsi::Runtime &rt, const jsi::Value &value, ValueType valueContainer = std::make_unique(std::make_shared(rt, object, runtimeManager)); auto& frozenObject = ValueWrapper::asFrozenObject(valueContainer); containsHostFunction |= frozenObject->containsHostFunction; - if (isRNRuntime && !containsHostFunction) { + if (RuntimeDecorator::isReactRuntime(rt) && !containsHostFunction) { addHiddenProperty(rt, createHost(rt, frozenObject), object, HIDDEN_HOST_OBJECT_PROP); } } @@ -154,7 +153,7 @@ void ShareableValue::adapt(jsi::Runtime &rt, const jsi::Value &value, ValueType ); auto& frozenObject = ValueWrapper::asFrozenObject(valueContainer); containsHostFunction |= frozenObject->containsHostFunction; - if (isRNRuntime) { + if (RuntimeDecorator::isReactRuntime(rt)) { if (!containsHostFunction) { addHiddenProperty(rt, createHost(rt, frozenObject), object, HIDDEN_HOST_OBJECT_PROP); } @@ -179,17 +178,16 @@ 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 (RuntimeDecorator::isWorkletRuntime(rt)) { if (remoteValue.expired()) { - auto ref = getWeakRef(rt); - remoteValue = ref; + remoteValue = getWeakRef(rt); } if (remoteValue.lock()->isUndefined()) { - (*remoteValue.lock()) = jsi::Value(rt, toJSValue(rt)); + (*remoteValue.lock()) = toJSValue(rt); } return jsi::Value(rt, *remoteValue.lock()); } else { if (hostValue.get() == nullptr) { - hostValue = std::make_unique(rt, toJSValue(rt)); + hostValue = std::make_unique(toJSValue(rt)); } return jsi::Value(rt, *hostValue); } @@ -339,8 +337,10 @@ jsi::Value ShareableValue::toJSValue(jsi::Runtime &rt) { const jsi::Value *args, size_t count ) mutable -> jsi::Value { - jsi::Value oldJSThis = rt.global().getProperty(rt, "jsThis"); - rt.global().setProperty(rt, "jsThis", *jsThis); //set jsThis + 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 { @@ -361,8 +361,7 @@ jsi::Value ShareableValue::toJSValue(jsi::Runtime &rt) { runtimeManager->errorHandler->setError(str); runtimeManager->errorHandler->raise(); } - - rt.global().setProperty(rt, "jsThis", oldJSThis); //clean jsThis + global.setProperty(rt, jsThisName, oldJSThis); //clean jsThis return res; }; return jsi::Function::createFromHostFunction(rt, jsi::PropNameID::forAscii(rt, name.c_str()), 0, clb); @@ -394,9 +393,10 @@ jsi::Value ShareableValue::toJSValue(jsi::Runtime &rt) { } jsi::Value returnedValue; - - jsi::Value oldJSThis = rt.global().getProperty(rt, "jsThis"); - rt.global().setProperty(rt, "jsThis", jsThis); //set jsThis + 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), @@ -416,7 +416,7 @@ jsi::Value ShareableValue::toJSValue(jsi::Runtime &rt) { runtimeManager->errorHandler->setError(str); runtimeManager->errorHandler->raise(); } - rt.global().setProperty(rt, "jsThis", oldJSThis); //clean jsThis + global.setProperty(rt, jsThisName, oldJSThis); //clean jsThis delete [] args; // ToDo use returned value to return promise diff --git a/Common/cpp/Tools/Mapper.cpp b/Common/cpp/Tools/Mapper.cpp index 073fdac5ed1..6197e759c86 100644 --- a/Common/cpp/Tools/Mapper.cpp +++ b/Common/cpp/Tools/Mapper.cpp @@ -8,12 +8,25 @@ Mapper::Mapper(NativeReanimatedModule *module, unsigned long id, std::shared_ptr mapper, std::vector> inputs, - std::vector> outputs): + std::vector> outputs, + std::shared_ptr updater, + const int viewTag, + const std::string& viewName, + const int optimalizationLvl): id(id), module(module), mapper(mapper), inputs(inputs), -outputs(outputs) { +outputs(outputs), +viewTag(viewTag), +optimalizationLvl(optimalizationLvl) +{ + jsi::Runtime* rt = module->runtime.get(); + this->viewName = jsi::Value(*rt, jsi::String::createFromUtf8(*rt, viewName)); + updateProps = &module->updaterFunction; + userUpdater = std::make_shared(updater->getValue(*rt).asObject(*rt).asFunction(*rt)); + + auto markDirty = [this, module]() { this->dirty = true; module->maybeRequestRender(); @@ -25,7 +38,12 @@ outputs(outputs) { void Mapper::execute(jsi::Runtime &rt) { dirty = false; - mapper->callWithThis(rt, *mapper); + if(optimalizationLvl == 0) { + mapper->callWithThis(rt, *mapper);// call styleUpdater + } + else { + (*updateProps)(rt, viewTag, viewName, userUpdater->call(rt).asObject(rt)); + } } Mapper::~Mapper() { diff --git a/Common/cpp/Tools/RuntimeDecorator.cpp b/Common/cpp/Tools/RuntimeDecorator.cpp index 92780eaa025..defd8ad7f76 100644 --- a/Common/cpp/Tools/RuntimeDecorator.cpp +++ b/Common/cpp/Tools/RuntimeDecorator.cpp @@ -7,7 +7,12 @@ namespace reanimated { -void RuntimeDecorator::decorateRuntime(jsi::Runtime &rt, std::string label) { +std::unordered_map RuntimeDecorator::runtimeRegistry; +void RuntimeDecorator::registerRuntime(jsi::Runtime *runtime, RuntimeType runtimeType) { + runtimeRegistry.insert({ runtime, runtimeType }); +} + +void RuntimeDecorator::decorateRuntime(jsi::Runtime &rt, const std::string &label) { // This property will be used to find out if a runtime is a custom worklet runtime (e.g. UI, VisionCamera frame processor, ...) rt.global().setProperty(rt, "_WORKLET", jsi::Value(true)); // This property will be used for debugging @@ -63,11 +68,11 @@ void RuntimeDecorator::decorateRuntime(jsi::Runtime &rt, std::string label) { } void RuntimeDecorator::decorateUIRuntime(jsi::Runtime &rt, - UpdaterFunction updater, - RequestFrameFunction requestFrame, - ScrollToFunction scrollTo, - MeasuringFunction measure, - TimeProviderFunction getCurrentTime, + const UpdaterFunction updater, + const RequestFrameFunction requestFrame, + const ScrollToFunction scrollTo, + const MeasuringFunction measure, + const TimeProviderFunction getCurrentTime, std::shared_ptr layoutAnimationsProxy) { RuntimeDecorator::decorateRuntime(rt, "UI"); rt.global().setProperty(rt, "_UI", jsi::Value(true)); @@ -76,7 +81,7 @@ void RuntimeDecorator::decorateUIRuntime(jsi::Runtime &rt, jsi::Runtime &rt, const jsi::Value &thisValue, const jsi::Value *args, - size_t count + const size_t count ) -> jsi::Value { const auto viewTag = args[0].asNumber(); const jsi::Value* viewName = &args[1]; @@ -92,7 +97,7 @@ void RuntimeDecorator::decorateUIRuntime(jsi::Runtime &rt, jsi::Runtime &rt, const jsi::Value &thisValue, const jsi::Value *args, - size_t count + const size_t count ) -> jsi::Value { auto fun = std::make_shared(args[0].asObject(rt).asFunction(rt)); requestFrame([&rt, fun](double timestampMs) { @@ -107,7 +112,7 @@ void RuntimeDecorator::decorateUIRuntime(jsi::Runtime &rt, jsi::Runtime &rt, const jsi::Value &thisValue, const jsi::Value *args, - size_t count + const size_t count ) -> jsi::Value { int viewTag = (int)args[0].asNumber(); double x = args[1].asNumber(); @@ -123,7 +128,7 @@ void RuntimeDecorator::decorateUIRuntime(jsi::Runtime &rt, jsi::Runtime &rt, const jsi::Value &thisValue, const jsi::Value *args, - size_t count + const size_t count ) -> jsi::Value { int viewTag = (int)args[0].asNumber(); auto result = measure(viewTag); @@ -140,7 +145,7 @@ void RuntimeDecorator::decorateUIRuntime(jsi::Runtime &rt, jsi::Runtime &rt, const jsi::Value &thisValue, const jsi::Value *args, - size_t count + const size_t count ) -> jsi::Value { return getCurrentTime(); }; @@ -185,18 +190,4 @@ void RuntimeDecorator::decorateUIRuntime(jsi::Runtime &rt, rt.global().setProperty(rt, "_stopObservingProgress", _stopObservingProgress); } -bool RuntimeDecorator::isUIRuntime(jsi::Runtime& rt) { - auto isUi = rt.global().getProperty(rt, "_UI"); - return isUi.isBool() && isUi.getBool(); -} - -bool RuntimeDecorator::isWorkletRuntime(jsi::Runtime& rt) { - auto isWorklet = rt.global().getProperty(rt, "_WORKLET"); - return isWorklet.isBool() && isWorklet.getBool(); -} - -bool RuntimeDecorator::isReactRuntime(jsi::Runtime& rt) { - return !isWorkletRuntime(rt); -} - } diff --git a/Common/cpp/headers/NativeModules/NativeReanimatedModule.h b/Common/cpp/headers/NativeModules/NativeReanimatedModule.h index 50318cc9195..23c4c63fd31 100644 --- a/Common/cpp/headers/NativeModules/NativeReanimatedModule.h +++ b/Common/cpp/headers/NativeModules/NativeReanimatedModule.h @@ -43,7 +43,13 @@ class NativeReanimatedModule : public NativeReanimatedModuleSpec, public Runtime jsi::Value makeMutable(jsi::Runtime &rt, const jsi::Value &value) override; jsi::Value makeRemote(jsi::Runtime &rt, const jsi::Value &value) override; - jsi::Value startMapper(jsi::Runtime &rt, const jsi::Value &worklet, const jsi::Value &inputs, const jsi::Value &outputs) override; + jsi::Value startMapper(jsi::Runtime &rt, + const jsi::Value &worklet, + const jsi::Value &inputs, + const jsi::Value &outputs, + const jsi::Value &updater, + const jsi::Value &tag, + const jsi::Value &name) override; void stopMapper(jsi::Runtime &rt, const jsi::Value &mapperId) override; jsi::Value registerEventHandler(jsi::Runtime &rt, const jsi::Value &eventHash, const jsi::Value &worklet) override; @@ -56,14 +62,16 @@ class NativeReanimatedModule : public NativeReanimatedModuleSpec, public Runtime bool isAnyHandlerWaitingForEvent(std::string eventName); void maybeRequestRender(); + UpdaterFunction updaterFunction; private: std::shared_ptr mapperRegistry; std::shared_ptr eventHandlerRegistry; - std::function requestRender; + std::function requestRender; std::shared_ptr dummyEvent; std::vector frameCallbacks; bool renderRequested = false; std::function propObtainer; + std::function onRenderCallback; std::shared_ptr layoutAnimationsProxy; }; diff --git a/Common/cpp/headers/NativeModules/NativeReanimatedModuleSpec.h b/Common/cpp/headers/NativeModules/NativeReanimatedModuleSpec.h index aa6eb42f05a..74ca43b24e3 100644 --- a/Common/cpp/headers/NativeModules/NativeReanimatedModuleSpec.h +++ b/Common/cpp/headers/NativeModules/NativeReanimatedModuleSpec.h @@ -31,7 +31,13 @@ class JSI_EXPORT NativeReanimatedModuleSpec : public TurboModule { virtual jsi::Value makeRemote(jsi::Runtime &rt, const jsi::Value &value) = 0; // mappers - virtual jsi::Value startMapper(jsi::Runtime &rt, const jsi::Value &worklet, const jsi::Value &inputs, const jsi::Value &outputs) = 0; + virtual jsi::Value startMapper(jsi::Runtime &rt, + const jsi::Value &worklet, + const jsi::Value &inputs, + const jsi::Value &outputs, + const jsi::Value &updater, + const jsi::Value &tag, + const jsi::Value &name) = 0; virtual void stopMapper(jsi::Runtime &rt, const jsi::Value &mapperId) = 0; // events diff --git a/Common/cpp/headers/SharedItems/RuntimeManager.h b/Common/cpp/headers/SharedItems/RuntimeManager.h index b5735676586..7ebe50eeeff 100644 --- a/Common/cpp/headers/SharedItems/RuntimeManager.h +++ b/Common/cpp/headers/SharedItems/RuntimeManager.h @@ -4,6 +4,7 @@ #include "ErrorHandler.h" #include "Scheduler.h" #include "WorkletsCache.h" +#include "RuntimeDecorator.h" #include #include @@ -16,9 +17,19 @@ using namespace facebook; */ class RuntimeManager { public: - RuntimeManager(std::shared_ptr runtime, - std::shared_ptr errorHandler, - std::shared_ptr scheduler): runtime(runtime), errorHandler(errorHandler), scheduler(scheduler), workletsCache(std::make_unique()) { } + RuntimeManager( + std::shared_ptr runtime, + std::shared_ptr errorHandler, + std::shared_ptr scheduler, + RuntimeType runtimeType = RuntimeType::Worklet + ) : + runtime(runtime), + errorHandler(errorHandler), + scheduler(scheduler), + workletsCache(std::make_unique()) + { + RuntimeDecorator::registerRuntime(this->runtime.get(), runtimeType); + } public: /** Holds the jsi::Function worklet that is responsible for updating values in JS. diff --git a/Common/cpp/headers/Tools/Mapper.h b/Common/cpp/headers/Tools/Mapper.h index 539db83d0a0..95a50e4067c 100644 --- a/Common/cpp/headers/Tools/Mapper.h +++ b/Common/cpp/headers/Tools/Mapper.h @@ -20,13 +20,22 @@ class Mapper : public std::enable_shared_from_this { std::vector> inputs; std::vector> outputs; bool dirty = true; + std::shared_ptr userUpdater; + UpdaterFunction* updateProps; + jsi::Value viewName; + int viewTag; + int optimalizationLvl = 0; public: Mapper(NativeReanimatedModule *module, unsigned long id, std::shared_ptr mapper, std::vector> inputs, - std::vector> outputs); + std::vector> outputs, + std::shared_ptr updater, + const int viewTag, + const std::string& viewName, + const int optimalizationLvl); void execute(jsi::Runtime &rt); virtual ~Mapper(); }; diff --git a/Common/cpp/headers/Tools/RuntimeDecorator.h b/Common/cpp/headers/Tools/RuntimeDecorator.h index d19a1b9b77a..35d1e6de2ba 100644 --- a/Common/cpp/headers/Tools/RuntimeDecorator.h +++ b/Common/cpp/headers/Tools/RuntimeDecorator.h @@ -2,6 +2,7 @@ #include "PlatformDepMethodsHolder.h" #include +#include #include #include "LayoutAnimationsProxy.h" @@ -11,29 +12,67 @@ namespace reanimated { using RequestFrameFunction = std::function)>; +enum RuntimeType { + /** + Represents any runtime that supports the concept of workletization + */ + Worklet, + /** + Represents the Reanimated UI worklet runtime specifically + */ + UI +}; +typedef jsi::Runtime* RuntimePointer; + class RuntimeDecorator { public: - static void decorateRuntime(jsi::Runtime &rt, std::string label); + static void decorateRuntime(jsi::Runtime &rt, const std::string &label); static void decorateUIRuntime(jsi::Runtime &rt, - UpdaterFunction updater, - RequestFrameFunction requestFrame, - ScrollToFunction scrollTo, - MeasuringFunction measure, - TimeProviderFunction getCurrentTime, + const UpdaterFunction updater, + const RequestFrameFunction requestFrame, + const ScrollToFunction scrollTo, + const MeasuringFunction measure, + const TimeProviderFunction getCurrentTime, std::shared_ptr layoutAnimationsProxy); /** Returns true if the given Runtime is the Reanimated UI-Thread Runtime. */ - static bool isUIRuntime(jsi::Runtime &rt); + inline static bool isUIRuntime(jsi::Runtime &rt); /** - Returns true if the given Runtime is a Runtime that supports Workletization. (REA, Vision, ...) + Returns true if the given Runtime is a Runtime that supports the concept of Workletization. (REA, Vision, ...) */ - static bool isWorkletRuntime(jsi::Runtime &rt); + inline static bool isWorkletRuntime(jsi::Runtime &rt); /** Returns true if the given Runtime is the default React-JS Runtime. */ - static bool isReactRuntime(jsi::Runtime &rt); + inline static bool isReactRuntime(jsi::Runtime &rt); + /** + Register the given Runtime. This function is required for every RuntimeManager, otherwise future runtime checks will fail. + */ + static void registerRuntime(jsi::Runtime* runtime, RuntimeType runtimeType); + +private: + static std::unordered_map runtimeRegistry; }; +inline bool RuntimeDecorator::isUIRuntime(jsi::Runtime& rt) { + auto iterator = runtimeRegistry.find(&rt); + if (iterator == runtimeRegistry.end()) return false; + return iterator->second == RuntimeType::UI; +} + +inline bool RuntimeDecorator::isWorkletRuntime(jsi::Runtime& rt) { + auto iterator = runtimeRegistry.find(&rt); + if (iterator == runtimeRegistry.end()) return false; + auto type = iterator->second; + return type == RuntimeType::UI || type == RuntimeType::Worklet; +} + +inline bool RuntimeDecorator::isReactRuntime(jsi::Runtime& rt) { + auto iterator = runtimeRegistry.find(&rt); + if (iterator == runtimeRegistry.end()) return true; + return false; +} + } diff --git a/Common/cpp/hidden_headers/SpeedChecker.h b/Common/cpp/hidden_headers/SpeedChecker.h index 2d449c0720f..bc31b2776ab 100644 --- a/Common/cpp/hidden_headers/SpeedChecker.h +++ b/Common/cpp/hidden_headers/SpeedChecker.h @@ -1,11 +1,12 @@ #pragma once -#define CHECK_SPEED 0 +#define CHECK_SPEED 1 #include "./Logger.h" #include #include #include +#include namespace reanimated { diff --git a/Example/ios/Podfile.lock b/Example/ios/Podfile.lock index 086ee999df5..82d88e91f12 100644 --- a/Example/ios/Podfile.lock +++ b/Example/ios/Podfile.lock @@ -435,7 +435,7 @@ SPEC CHECKSUMS: boost-for-react-native: 39c7adb57c4e60d6c5479dd8623128eb5b3f0f2c DoubleConversion: cf9b38bf0b2d048436d9a82ad2abe1404f11e7de FBLazyVector: 49cbe4b43e445b06bf29199b6ad2057649e4c8f5 - FBReactNativeSpec: a231a5702f05925855ad8d6449930e1a024bf38b + FBReactNativeSpec: 6d46d7c05350e10b3e711c32af90bb188b792a56 glog: 73c2498ac6884b13ede40eda8228cb1eee9d9d62 RCT-Folly: ec7a233ccc97cc556cf7237f0db1ff65b986f27c RCTRequired: 2f8cb5b7533219bf4218a045f92768129cf7050a diff --git a/__tests__/Animation.test.js b/__tests__/Animation.test.js index 30bf0e0b4cd..5039ba100b4 100644 --- a/__tests__/Animation.test.js +++ b/__tests__/Animation.test.js @@ -56,32 +56,32 @@ const getDefaultStyle = () => ({ describe('Tests of animations', () => { test('withTiming animation', async () => { - jest.useFakeTimers(); - 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); + withReanimatedTimer(() => { + const style = getDefaultStyle(); - jest.runAllTimers(); + const { getByTestId } = render(); + const view = getByTestId('view'); + const button = getByTestId('button'); - style.width = 100; - expect(view).toHaveAnimatedStyle(style); + expect(view.props.style.width).toBe(0); + expect(view).toHaveAnimatedStyle(style); + fireEvent.press(button); + advanceAnimationByTime(600); + style.width = 100; + expect(view).toHaveAnimatedStyle(style); + }); }); test('withTiming animation, get animated style', async () => { - jest.useFakeTimers(); - const { getByTestId } = render(); - const view = getByTestId('view'); - const button = getByTestId('button'); - fireEvent.press(button); - jest.runAllTimers(); - const style = getAnimatedStyle(view); - expect(style.width).toBe(100); + 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); + }); }); test('withTiming animation, width in a middle of animation', () => { @@ -150,17 +150,18 @@ describe('Tests of animations', () => { }); test('withTiming animation, change shared value outside component', () => { - jest.useFakeTimers(); - let sharedValue; - renderHook(() => { - sharedValue = useSharedValue(0); + withReanimatedTimer(() => { + let sharedValue; + renderHook(() => { + sharedValue = useSharedValue(0); + }); + const { getByTestId } = render( + + ); + const view = getByTestId('view'); + sharedValue.value = 50; + advanceAnimationByTime(600); + expect(view).toHaveAnimatedStyle({ width: 50 }); }); - const { getByTestId } = render( - - ); - const view = getByTestId('view'); - sharedValue.value = 50; - jest.runAllTimers(); - expect(view).toHaveAnimatedStyle({ width: 50 }); }); }); diff --git a/__tests__/__snapshots__/plugin.test.js.snap b/__tests__/__snapshots__/plugin.test.js.snap index 46e71b41585..37fbed4b074 100644 --- a/__tests__/__snapshots__/plugin.test.js.snap +++ b/__tests__/__snapshots__/plugin.test.js.snap @@ -22,6 +22,7 @@ var f = function () { _f.asString = \\"function f(){const{x,objX}=jsThis._closure;{return{res:x+objX.x};}}\\"; _f.__workletHash = 10184269015616; _f.__location = \\"${ process.cwd() }/jest tests fixture (4:4)\\"; + _f.__optimalization = 3; global.__reanimatedWorkletInit(_f); @@ -39,6 +40,7 @@ exports[`babel plugin doesn't capture globals 1`] = ` _f.asString = \\"function f(){console.log(\\\\\\"test\\\\\\");}\\"; _f.__workletHash = 6265462104213; _f.__location = \\"${ process.cwd() }/jest tests fixture (2:6)\\"; + _f.__optimalization = 2; global.__reanimatedWorkletInit(_f); diff --git a/android/src/main/cpp/NativeProxy.cpp b/android/src/main/cpp/NativeProxy.cpp index 43b272d4aab..d19affa7d70 100644 --- a/android/src/main/cpp/NativeProxy.cpp +++ b/android/src/main/cpp/NativeProxy.cpp @@ -71,10 +71,12 @@ void NativeProxy::installJSIBindings() //as we use diffrent timer to better handle events. The lambda is translated to NodeManager.OnAnimationFrame //and treated just like reanimated 1 frame callbacks which make use of the timestamp. auto wrappedOnRender = [getCurrentTime, &rt, onRender](double doNotUse) { - double frameTimestamp = getCurrentTime(); - rt.global().setProperty(rt, "_frameTimestamp", frameTimestamp); - onRender(frameTimestamp); - rt.global().setProperty(rt, "_frameTimestamp", jsi::Value::undefined()); + jsi::Object global = rt.global(); + jsi::String frameTimestampName = jsi::String::createFromAscii(rt, "_frameTimestamp"); + double frameTimestamp = getCurrentTime(); + global.setProperty(rt, frameTimestampName, frameTimestamp); + onRender(frameTimestamp); + global.setProperty(rt, frameTimestampName, jsi::Value::undefined()); }; this->requestRender(std::move(wrappedOnRender)); }; @@ -140,9 +142,11 @@ void NativeProxy::installJSIBindings() _nativeReanimatedModule = module; this->registerEventHandler([module, getCurrentTime](std::string eventName, std::string eventAsString) { - module->runtime->global().setProperty(*module->runtime, "_eventTimestamp", getCurrentTime()); + jsi::Object global = module->runtime->global(); + jsi::String eventTimestampName = jsi::String::createFromAscii(*module->runtime, "_eventTimestamp"); + global.setProperty(*module->runtime, eventTimestampName, getCurrentTime()); module->onEvent(eventName, eventAsString); - module->runtime->global().setProperty(*module->runtime, "_eventTimestamp", jsi::Value::undefined()); + global.setProperty(*module->runtime, eventTimestampName, jsi::Value::undefined()); }); runtime_->global().setProperty( diff --git a/ios/native/NativeProxy.mm b/ios/native/NativeProxy.mm index 536880f87a8..cda52a7c81d 100644 --- a/ios/native/NativeProxy.mm +++ b/ios/native/NativeProxy.mm @@ -127,9 +127,11 @@ static id convertJSIValueToObjCObject(jsi::Runtime &runtime, const jsi::Value &v auto requestRender = [reanimatedModule, &module](std::function onRender, jsi::Runtime &rt) { [reanimatedModule.nodesManager postOnAnimation:^(CADisplayLink *displayLink) { double frameTimestamp = displayLink.targetTimestamp * 1000; - rt.global().setProperty(rt, "_frameTimestamp", frameTimestamp); + jsi::Object global = rt.global(); + jsi::String frameTimestampName = jsi::String::createFromAscii(rt, "_frameTimestamp"); + global.setProperty(rt, frameTimestampName, frameTimestamp); onRender(frameTimestamp); - rt.global().setProperty(rt, "_frameTimestamp", jsi::Value::undefined()); + global.setProperty(rt, frameTimestampName, jsi::Value::undefined()); }]; }; @@ -212,9 +214,11 @@ static id convertJSIValueToObjCObject(jsi::Runtime &runtime, const jsi::Value &v std::string eventAsString = folly::toJson(convertIdToFollyDynamic([event arguments][2])); eventAsString = "{ NativeMap:" + eventAsString + "}"; - module->runtime->global().setProperty(*module->runtime, "_eventTimestamp", CACurrentMediaTime() * 1000); + jsi::Object global = module->runtime->global(); + jsi::String eventTimestampName = jsi::String::createFromAscii(*module->runtime, "_eventTimestamp"); + global.setProperty(*module->runtime, eventTimestampName, CACurrentMediaTime() * 1000); module->onEvent(eventNameString, eventAsString); - module->runtime->global().setProperty(*module->runtime, "_eventTimestamp", jsi::Value::undefined()); + global.setProperty(*module->runtime, eventTimestampName, jsi::Value::undefined()); }]; return module; diff --git a/mock.js b/mock.js index 5000a2bb368..e4cae79eba1 100644 --- a/mock.js +++ b/mock.js @@ -19,7 +19,9 @@ const { } = require('react-native'); const ReanimatedV2 = require('./src/reanimated2/mock'); -function NOOP() {} +function NOOP() { + // noop +} function simulateCallbackFactory(...params) { return (callback) => { @@ -75,9 +77,13 @@ function createTransitioningComponent(Component) { Component.name || 'Component'}`; - setNativeProps() {} + setNativeProps() { + // noop + } - animateNextTransition() {} + animateNextTransition() { + // noop + } render() { return ; diff --git a/plugin.js b/plugin.js index f7218ed9e21..d23bc69d468 100644 --- a/plugin.js +++ b/plugin.js @@ -1,11 +1,9 @@ 'use strict'; - const generate = require('@babel/generator').default; const hash = require('string-hash-64'); const { visitors } = require('@babel/traverse'); const traverse = require('@babel/traverse').default; const parse = require('@babel/parser').parse; - /** * holds a map of function names as keys and array of argument indexes as values which should be automatically workletized(they have to be functions)(starting from 0) */ @@ -126,6 +124,8 @@ const blacklistedFunctions = new Set([ '__callAsync', ]); +const possibleOptFunction = new Set(['interpolate']); + class ClosureGenerator { constructor() { this.trie = [{}, false]; @@ -273,11 +273,10 @@ function buildWorkletString(t, fun, closureVariables, name) { return generate(workletFunction, { compact: true }).code; } -function processWorkletFunction(t, fun, fileName) { +function processWorkletFunction(t, fun, fileName, options = {}) { if (!t.isFunctionParent(fun)) { return; } - const functionName = fun.node.id ? fun.node.id.name : '_f'; const closure = new Map(); @@ -299,7 +298,8 @@ function processWorkletFunction(t, fun, fileName) { if ( parentNode.type === 'MemberExpression' && - (parentNode.property === path.node && !parentNode.computed) + parentNode.property === path.node && + !parentNode.computed ) { return; } @@ -346,10 +346,6 @@ function processWorkletFunction(t, fun, fileName) { const variables = Array.from(closure.values()); const privateFunctionId = t.identifier('_f'); - - // if we don't clone other modules won't process parts of newFun defined below - // this is weird but couldn't find a better way to force transform helper to - // process the function const clone = t.cloneNode(fun.node); const funExpression = t.functionExpression(null, clone.params, clone.body); @@ -364,69 +360,82 @@ function processWorkletFunction(t, fun, fileName) { } } - const newFun = t.functionExpression( - fun.id, - [], - t.blockStatement([ - t.variableDeclaration('const', [ - t.variableDeclarator(privateFunctionId, funExpression), - ]), - t.expressionStatement( - t.assignmentExpression( - '=', - t.memberExpression( - privateFunctionId, - t.identifier('_closure'), - false - ), - closureGenerator.generate(t, variables, closure.keys()) - ) - ), - t.expressionStatement( - t.assignmentExpression( - '=', - t.memberExpression( - privateFunctionId, - t.identifier('asString'), - false - ), - t.stringLiteral(funString) - ) - ), - t.expressionStatement( - t.assignmentExpression( - '=', - t.memberExpression( - privateFunctionId, - t.identifier('__workletHash'), - false - ), - t.numericLiteral(workletHash) - ) - ), + const steatmentas = [ + t.variableDeclaration('const', [ + t.variableDeclarator(privateFunctionId, funExpression), + ]), + t.expressionStatement( + t.assignmentExpression( + '=', + t.memberExpression(privateFunctionId, t.identifier('_closure'), false), + closureGenerator.generate(t, variables, closure.keys()) + ) + ), + t.expressionStatement( + t.assignmentExpression( + '=', + t.memberExpression(privateFunctionId, t.identifier('asString'), false), + t.stringLiteral(funString) + ) + ), + t.expressionStatement( + t.assignmentExpression( + '=', + t.memberExpression( + privateFunctionId, + t.identifier('__workletHash'), + false + ), + t.numericLiteral(workletHash) + ) + ), + t.expressionStatement( + t.assignmentExpression( + '=', + t.memberExpression( + privateFunctionId, + t.identifier('__location'), + false + ), + t.stringLiteral(fileName) + ) + ), + ]; + + if (options && options.optFlags) { + steatmentas.push( t.expressionStatement( t.assignmentExpression( '=', t.memberExpression( privateFunctionId, - t.identifier('__location'), + t.identifier('__optimalization'), false ), - t.stringLiteral(fileName) + t.numericLiteral(options.optFlags) ) - ), - t.expressionStatement( - t.callExpression( - t.memberExpression( - t.identifier('global'), - t.identifier('__reanimatedWorkletInit'), - false - ), - [privateFunctionId] - ) - ), - t.returnStatement(privateFunctionId), - ]) + ) + ); + } + + steatmentas.push( + t.expressionStatement( + t.callExpression( + t.memberExpression( + t.identifier('global'), + t.identifier('__reanimatedWorkletInit'), + false + ), + [privateFunctionId] + ) + ) + ); + steatmentas.push(t.returnStatement(privateFunctionId)); + + const newFun = t.functionExpression( + fun.id, + [], + t.blockStatement(steatmentas) ); const replacement = t.callExpression(newFun, []); @@ -464,7 +473,12 @@ function processIfWorkletNode(t, fun, fileName) { directive.value.value === 'worklet' ) ) { - processWorkletFunction(t, fun, fileName); + const flags = isPossibleOptimization(fun); + if (flags) { + processWorkletFunction(t, fun, fileName, { optFlags: flags }); + } else { + processWorkletFunction(t, fun, fileName); + } } } }, @@ -502,6 +516,32 @@ function processWorklets(t, path, fileName) { } } +const FUNCTIONLESS_FLAG = 0b00000001; +const STATEMENTLESS_FLAG = 0b00000010; + +function isPossibleOptimization(fun) { + let isFunctionCall = false; + let isSteatements = false; + fun.scope.path.traverse({ + CallExpression(path) { + if (!possibleOptFunction.has(path.node.callee.name)) { + isFunctionCall = true; + } + }, + IfStatement() { + isSteatements = true; + }, + }); + let flags = 0; + if (!isFunctionCall) { + flags = flags | FUNCTIONLESS_FLAG; + } + if (!isSteatements) { + flags = flags | STATEMENTLESS_FLAG; + } + return flags; +} + const PLUGIN_BLACKLIST_NAMES = ['@babel/plugin-transform-object-assign']; const PLUGIN_BLACKLIST = PLUGIN_BLACKLIST_NAMES.map((pluginName) => { @@ -555,7 +595,7 @@ function removePluginsFromBlacklist(plugins) { }); } -module.exports = function({ types: t }) { +module.exports = function ({ types: t }) { return { pre() { // allows adding custom globals such as host-functions @@ -579,7 +619,7 @@ module.exports = function({ types: t }) { }, // In this way we can modify babel options // https://github.com/babel/babel/blob/eea156b2cb8deecfcf82d52aa1b71ba4995c7d68/packages/babel-core/src/transformation/normalize-opts.js#L64 - manipulateOptions(opts, parserOpts) { + manipulateOptions(opts, _) { const plugins = opts.plugins; removePluginsFromBlacklist(plugins); }, diff --git a/src/reanimated2/Colors.ts b/src/reanimated2/Colors.ts index 2bc4191501f..d6e1fc85078 100644 --- a/src/reanimated2/Colors.ts +++ b/src/reanimated2/Colors.ts @@ -455,15 +455,15 @@ export const rgbaColor = ( if (Platform.OS === 'web' || !_WORKLET) { return `rgba(${r}, ${g}, ${b}, ${alpha})`; } - const a = Math.round(alpha * 255); + const c = - a * (1 << 24) + + Math.round(alpha * 255) * (1 << 24) + Math.round(r) * (1 << 16) + Math.round(g) * (1 << 8) + Math.round(b); if (Platform.OS === 'android') { // on Android color is represented as signed 32 bit int - return c < (1 << 31) >>> 0 ? c : c - Math.pow(2, 32); + return c < (1 << 31) >>> 0 ? c : c - 4294967296; // 4294967296 == Math.pow(2, 32); } return c; }; @@ -656,70 +656,116 @@ export function toRGBA(HSVA: [number, number, number, number]): string { const interpolateColorsHSV = ( value: number, inputRange: readonly number[], - colors: readonly number[] + colors: InterpolateCacheHSV ) => { 'worklet'; - const colorsAsHSV = colors.map((c) => RGBtoHSV(c as any)); - const h = interpolate( - value, - inputRange, - colorsAsHSV.map((c) => c.h), - Extrapolate.CLAMP - ); - const s = interpolate( - value, - inputRange, - colorsAsHSV.map((c) => c.s), - Extrapolate.CLAMP - ); - const v = interpolate( - value, - inputRange, - colorsAsHSV.map((c) => c.v), - Extrapolate.CLAMP - ); + const h = interpolate(value, inputRange, colors.h, Extrapolate.CLAMP); + const s = interpolate(value, inputRange, colors.s, Extrapolate.CLAMP); + const v = interpolate(value, inputRange, colors.v, Extrapolate.CLAMP); return hsvToColor(h, s, v); }; const interpolateColorsRGB = ( value: number, inputRange: readonly number[], - colors: readonly number[] + colors: InterpolateCacheRGBA ) => { 'worklet'; - const r = Math.round( - interpolate( - value, - inputRange, - colors.map((c) => red(c)), - Extrapolate.CLAMP - ) - ); - const g = Math.round( - interpolate( - value, - inputRange, - colors.map((c) => green(c)), - Extrapolate.CLAMP - ) - ); - const b = Math.round( - interpolate( - value, - inputRange, - colors.map((c) => blue(c)), - Extrapolate.CLAMP - ) - ); - const a = interpolate( - value, - inputRange, - colors.map((c) => opacity(c)), - Extrapolate.CLAMP - ); + const r = interpolate(value, inputRange, colors.r, Extrapolate.CLAMP); + const g = interpolate(value, inputRange, colors.g, Extrapolate.CLAMP); + const b = interpolate(value, inputRange, colors.b, Extrapolate.CLAMP); + const a = interpolate(value, inputRange, colors.a, Extrapolate.CLAMP); return rgbaColor(r, g, b, a); }; +interface InterpolateCacheRGBA { + r: number[]; + g: number[]; + b: number[]; + a: number[]; +} + +const BUFFER_SIZE = 200; +const hashOrderRGBA: any = new ArrayBuffer(BUFFER_SIZE); +let curentHashIndexRGBA = 0; +const interpolateCacheRGBA: { [name: string]: InterpolateCacheRGBA } = {}; + +const getInterpolateCacheRGBA = ( + colors: readonly (string | number)[] +): InterpolateCacheRGBA => { + 'worklet'; + const hash = colors.join(''); + const cache = interpolateCacheRGBA[hash]; + if (cache !== undefined) { + return cache; + } + + const r = []; + const g = []; + const b = []; + const a = []; + for (const color of colors) { + const proocessedColor = processColor(color); + if (proocessedColor) { + r.push(red(proocessedColor)); + g.push(green(proocessedColor)); + b.push(blue(proocessedColor)); + a.push(opacity(proocessedColor)); + } + } + const newCache = { r, g, b, a }; + const overrideHash = hashOrderRGBA[curentHashIndexRGBA]; + if (overrideHash) { + delete interpolateCacheRGBA[overrideHash]; + } + interpolateCacheRGBA[hash] = newCache; + hashOrderRGBA[curentHashIndexRGBA] = hash; + curentHashIndexRGBA = (curentHashIndexRGBA + 1) % BUFFER_SIZE; + return newCache; +}; + +interface InterpolateCacheHSV { + h: number[]; + s: number[]; + v: number[]; +} + +const hashOrderHSV: any = new ArrayBuffer(BUFFER_SIZE); +let curentHashIndexHSV = 0; +const interpolateCacheHSV: { [name: string]: InterpolateCacheHSV } = {}; + +const getInterpolateCacheHSV = ( + colors: readonly (string | number)[] +): InterpolateCacheHSV => { + 'worklet'; + const hash = colors.join(''); + const cache = interpolateCacheHSV[hash]; + if (cache !== undefined) { + return cache; + } + + const h = []; + const s = []; + const v = []; + for (const color of colors) { + const proocessedColor = RGBtoHSV(processColor(color) as any); + if (proocessedColor) { + h.push(proocessedColor.h); + s.push(proocessedColor.s); + v.push(proocessedColor.v); + } + } + const newCache = { h, s, v }; + const overrideHash = hashOrderHSV[curentHashIndexHSV]; + if (overrideHash) { + delete interpolateCacheHSV[overrideHash]; + } + interpolateCacheHSV[hash] = newCache; + hashOrderHSV[curentHashIndexHSV] = hash; + curentHashIndexHSV = (curentHashIndexHSV + 1) % BUFFER_SIZE; + return newCache; +}; + export const interpolateColor = ( value: number, inputRange: readonly number[], @@ -727,12 +773,18 @@ export const interpolateColor = ( colorSpace: 'RGB' | 'HSV' = 'RGB' ): string | number => { 'worklet'; - const processedOutputRange = outputRange.map((c) => processColor(c)!); if (colorSpace === 'HSV') { - return interpolateColorsHSV(value, inputRange, processedOutputRange); - } - if (colorSpace === 'RGB') { - return interpolateColorsRGB(value, inputRange, processedOutputRange); + return interpolateColorsHSV( + value, + inputRange, + getInterpolateCacheHSV(outputRange) + ); + } else if (colorSpace === 'RGB') { + return interpolateColorsRGB( + value, + inputRange, + getInterpolateCacheRGBA(outputRange) + ); } throw new Error( `invalid color space provided: ${colorSpace}. Supported values are: ['RGB', 'HSV']` diff --git a/src/reanimated2/Easing.ts b/src/reanimated2/Easing.ts index 6fa496d7cd1..801814ca821 100644 --- a/src/reanimated2/Easing.ts +++ b/src/reanimated2/Easing.ts @@ -209,9 +209,14 @@ function bezier( y1: number, x2: number, y2: number -): (x: number) => number { +): { factory: () => (x: number) => number } { 'worklet'; - return Bezier(x1, y1, x2, y2); + return { + factory: () => { + 'worklet'; + return Bezier(x1, y1, x2, y2); + }, + }; } /** diff --git a/src/reanimated2/Hooks.ts b/src/reanimated2/Hooks.ts index 9e2ba9cd02d..6c39290139f 100644 --- a/src/reanimated2/Hooks.ts +++ b/src/reanimated2/Hooks.ts @@ -12,11 +12,12 @@ import { requestFrame, getTimestamp, } from './core'; -import updateProps, { updatePropsJestWrapper } from './UpdateProps'; +import updateProps, { updatePropsJestWrapper, colorProps } from './UpdateProps'; import { initialUpdaterRun, cancelAnimation } from './animations'; import { getTag } from './NativeMethods'; import NativeReanimated from './NativeReanimated'; import { Platform } from 'react-native'; +import { processColor } from './Colors'; export function useSharedValue(init) { const ref = useRef(null); @@ -52,134 +53,125 @@ export function useEvent(handler, eventNames = [], rebuild = false) { function prepareAnimation(animatedProp, lastAnimation, lastValue) { 'worklet'; - function prepareAnimation(animatedProp, lastAnimation, lastValue) { - if (Array.isArray(animatedProp)) { - animatedProp.forEach((prop, index) => - prepareAnimation( - prop, - lastAnimation && lastAnimation[index], - lastValue && lastValue[index] - ) - ); - return animatedProp; - } - if (typeof animatedProp === 'object' && animatedProp.onFrame) { - const animation = animatedProp; - - let value = animation.current; - if (lastValue !== undefined) { - if (typeof lastValue === 'object') { - if (lastValue.value !== undefined) { - // previously it was a shared value - value = lastValue.value; - } else if (lastValue.onFrame !== undefined) { - if (lastAnimation?.current !== undefined) { - // it was an animation before, copy its state - value = lastAnimation.current; - } else if (lastValue?.current !== undefined) { - // it was initialized - value = lastValue.current; - } + if (Array.isArray(animatedProp)) { + animatedProp.forEach((prop, index) => + prepareAnimation( + prop, + lastAnimation && lastAnimation[index], + lastValue && lastValue[index] + ) + ); + return animatedProp; + } + if (typeof animatedProp === 'object' && animatedProp.onFrame) { + const animation = animatedProp; + + let value = animation.current; + if (lastValue !== undefined) { + if (typeof lastValue === 'object') { + if (lastValue.value !== undefined) { + // previously it was a shared value + value = lastValue.value; + } else if (lastValue.onFrame !== undefined) { + if (lastAnimation?.current !== undefined) { + // it was an animation before, copy its state + value = lastAnimation.current; + } else if (lastValue?.current !== undefined) { + // it was initialized + value = lastValue.current; } - } else { - // previously it was a plain value, just set it as starting point - value = lastValue; } + } else { + // previously it was a plain value, just set it as starting point + value = lastValue; } - - animation.callStart = (timestamp) => { - animation.onStart(animation, value, timestamp, lastAnimation); - }; - animation.callStart(getTimestamp()); - animation.callStart = null; - } else if (typeof animatedProp === 'object') { - // it is an object - Object.keys(animatedProp).forEach((key) => - prepareAnimation( - animatedProp[key], - lastAnimation && lastAnimation[key], - lastValue && lastValue[key] - ) - ); } + + animation.callStart = (timestamp) => { + animation.onStart(animation, value, timestamp, lastAnimation); + }; + animation.callStart(getTimestamp()); + animation.callStart = null; + } else if (typeof animatedProp === 'object') { + // it is an object + Object.keys(animatedProp).forEach((key) => + prepareAnimation( + animatedProp[key], + lastAnimation && lastAnimation[key], + lastValue && lastValue[key] + ) + ); } - return prepareAnimation(animatedProp, lastAnimation, lastValue); } function runAnimations(animation, timestamp, key, result, animationsActive) { 'worklet'; - function runAnimations(animation, timestamp, key, result, animationsActive) { - if (!animationsActive.value) { - return true; - } - if (Array.isArray(animation)) { - result[key] = []; - let allFinished = true; - animation.forEach((entry, index) => { - if ( - !runAnimations(entry, timestamp, index, result[key], animationsActive) - ) { - allFinished = false; - } - }); - return allFinished; - } else if (typeof animation === 'object' && animation.onFrame) { - let finished = true; - if (!animation.finished) { - if (animation.callStart) { - animation.callStart(timestamp); - animation.callStart = null; - } - finished = animation.onFrame(animation, timestamp); - animation.timestamp = timestamp; - if (finished) { - animation.finished = true; - animation.callback && animation.callback(true /* finished */); - } + if (!animationsActive.value) { + return true; + } + if (Array.isArray(animation)) { + result[key] = []; + let allFinished = true; + animation.forEach((entry, index) => { + if ( + !runAnimations(entry, timestamp, index, result[key], animationsActive) + ) { + allFinished = false; + } + }); + return allFinished; + } else if (typeof animation === 'object' && animation.onFrame) { + let finished = true; + if (!animation.finished) { + if (animation.callStart) { + animation.callStart(timestamp); + animation.callStart = null; + } + finished = animation.onFrame(animation, timestamp); + animation.timestamp = timestamp; + if (finished) { + animation.finished = true; + animation.callback && animation.callback(true /* finished */); } - result[key] = animation.current; - return finished; - } else if (typeof animation === 'object') { - result[key] = {}; - let allFinished = true; - Object.keys(animation).forEach((k) => { - if ( - !runAnimations( - animation[k], - timestamp, - k, - result[key], - animationsActive - ) - ) { - allFinished = false; - } - }); - return allFinished; - } else { - result[key] = animation; - return true; } + result[key] = animation.current; + return finished; + } else if (typeof animation === 'object') { + result[key] = {}; + let allFinished = true; + Object.keys(animation).forEach((k) => { + if ( + !runAnimations( + animation[k], + timestamp, + k, + result[key], + animationsActive + ) + ) { + allFinished = false; + } + }); + return allFinished; + } else { + result[key] = animation; + return true; } - return runAnimations(animation, timestamp, key, result, animationsActive); } -// TODO: recirsive worklets aren't supported yet function isAnimated(prop) { 'worklet'; - function isAnimated(prop) { - if (Array.isArray(prop)) { - return prop.some(isAnimated); - } - if (typeof prop === 'object') { - if (prop.onFrame) { - return true; + if (Array.isArray(prop)) { + for (const item of prop) { + for (const key in item) { + if (item[key].onFrame !== undefined) { + return true; + } } - return Object.keys(prop).some((key) => isAnimated(prop[key])); } return false; } - return isAnimated(prop); + return prop.onFrame !== undefined; } function styleDiff(oldStyle, newStyle) { @@ -209,6 +201,20 @@ function styleDiff(oldStyle, newStyle) { return diff; } +function getStyleWithoutAnimations(newStyle) { + 'worklet'; + const diff = {}; + + for (const key in newStyle) { + const value = newStyle[key]; + if (isAnimated(value)) { + continue; + } + diff[key] = value; + } + return diff; +} + const validateAnimatedStyles = (styles) => { 'worklet'; if (typeof styles !== 'object') { @@ -227,7 +233,6 @@ function styleUpdater( updater, state, maybeViewRef, - adapters, animationsActive ) { 'worklet'; @@ -235,60 +240,55 @@ function styleUpdater( const newValues = updater() || {}; const oldValues = state.last; - // extract animated props let hasAnimations = false; - Object.keys(animations).forEach((key) => { - const value = newValues[key]; - if (!isAnimated(value)) { - delete animations[key]; - } - }); - Object.keys(newValues).forEach((key) => { + for (const key in newValues) { const value = newValues[key]; if (isAnimated(value)) { prepareAnimation(value, animations[key], oldValues[key]); animations[key] = value; hasAnimations = true; + } else { + delete animations[key]; } - }); + } - function frame(timestamp) { - const { animations, last, isAnimationCancelled } = state; - if (isAnimationCancelled) { - state.isAnimationRunning = false; - return; - } + if (hasAnimations) { + const frame = (timestamp) => { + const { animations, last, isAnimationCancelled } = state; + if (isAnimationCancelled) { + state.isAnimationRunning = false; + return; + } - const updates = {}; - let allFinished = true; - Object.keys(animations).forEach((propName) => { - const finished = runAnimations( - animations[propName], - timestamp, - propName, - updates, - animationsActive - ); - if (finished) { - last[propName] = updates[propName]; - delete animations[propName]; - } else { - allFinished = false; + const updates = {}; + let allFinished = true; + for (const propName in animations) { + const finished = runAnimations( + animations[propName], + timestamp, + propName, + updates, + animationsActive + ); + if (finished) { + last[propName] = updates[propName]; + delete animations[propName]; + } else { + allFinished = false; + } } - }); - if (Object.keys(updates).length) { - updateProps(viewDescriptor, updates, maybeViewRef, adapters); - } + if (updates) { + updateProps(viewDescriptor, updates, maybeViewRef); + } - if (!allFinished) { - requestFrame(frame); - } else { - state.isAnimationRunning = false; - } - } + if (!allFinished) { + requestFrame(frame); + } else { + state.isAnimationRunning = false; + } + }; - if (hasAnimations) { state.animations = animations; if (!state.isAnimationRunning) { state.isAnimationCancelled = false; @@ -299,17 +299,15 @@ function styleUpdater( requestFrame(frame); } } + state.last = Object.assign({}, oldValues, newValues); + const style = getStyleWithoutAnimations(oldValues, newValues); + if (style) { + updateProps(viewDescriptor, style, maybeViewRef); + } } else { state.isAnimationCancelled = true; state.animations = {}; - } - - // calculate diff - const diff = styleDiff(oldValues, newValues); - state.last = Object.assign({}, oldValues, newValues); - - if (Object.keys(diff).length !== 0) { - updateProps(viewDescriptor, diff, maybeViewRef, adapters); + updateProps(viewDescriptor, newValues, maybeViewRef); } } @@ -318,9 +316,9 @@ function jestStyleUpdater( updater, state, maybeViewRef, - adapters, animationsActive, - animatedStyle + animatedStyle, + adapters = [] ) { 'worklet'; const animations = state.animations || {}; @@ -374,8 +372,8 @@ function jestStyleUpdater( viewDescriptor, updates, maybeViewRef, - adapters, - animatedStyle + animatedStyle, + adapters ); } @@ -411,11 +409,36 @@ function jestStyleUpdater( viewDescriptor, diff, maybeViewRef, - adapters, - animatedStyle + animatedStyle, + adapters ); } } +const colorPropsSet = new Set(colorProps); +const hasColorProps = (updates) => { + for (const key in updates) { + if (colorPropsSet.has(key)) { + return true; + } + } + return false; +}; + +const parseColors = (updates) => { + 'worklet'; + for (const key in updates) { + if (colorProps.indexOf(key) !== -1) { + updates[key] = processColor(updates[key]); + } + } +}; + +const canApplyOptimalisation = (upadterFn) => { + const FUNCTIONLESS_FLAG = 0b00000001; + const STATEMENTLESS_FLAG = 0b00000010; + const optimalization = upadterFn.__optimalization; + return (optimalization & FUNCTIONLESS_FLAG) && (optimalization & STATEMENTLESS_FLAG); +}; export function useAnimatedStyle(updater, dependencies, adapters) { const viewDescriptor = useSharedValue({ tag: -1, name: null }, false); @@ -452,6 +475,39 @@ export function useAnimatedStyle(updater, dependencies, adapters) { useEffect(() => { let fun; + let upadterFn = updater; + let optimalization = updater.__optimalization; + if (adapters) { + upadterFn = () => { + 'worklet'; + const newValues = updater(); + adapters.forEach((adapter) => { + adapter(newValues); + }); + return newValues; + }; + } + + if (canApplyOptimalisation(upadterFn)) { + if (hasColorProps(upadterFn())) { + upadterFn = () => { + 'worklet'; + const style = upadterFn(); + parseColors(style); + return style; + }; + } + } else { + optimalization = 0; + upadterFn = () => { + 'worklet'; + const style = upadterFn(); + parseColors(style); + return style; + }; + } + upadterFn.__optimalization = optimalization; + if (process.env.JEST_WORKER_ID) { fun = () => { 'worklet'; @@ -460,9 +516,9 @@ export function useAnimatedStyle(updater, dependencies, adapters) { updater, remoteState, maybeViewRef, - adapters, animationsActive, - animatedStyle + animatedStyle, + adapters ); }; } else { @@ -470,15 +526,21 @@ export function useAnimatedStyle(updater, dependencies, adapters) { 'worklet'; styleUpdater( viewDescriptor, - updater, + upadterFn, remoteState, maybeViewRef, - adapters, animationsActive ); }; } - const mapperId = startMapper(fun, inputs, []); + const mapperId = startMapper( + fun, + inputs, + [], + upadterFn, + viewDescriptor.value.tag, + viewDescriptor.value.name || 'RCTView' + ); return () => { stopMapper(mapperId); }; diff --git a/src/reanimated2/NativeReanimated.native.ts b/src/reanimated2/NativeReanimated.native.ts index 1c6220ca258..39b5756750e 100644 --- a/src/reanimated2/NativeReanimated.native.ts +++ b/src/reanimated2/NativeReanimated.native.ts @@ -24,8 +24,15 @@ const NativeReanimated = { return InnerNativeModule.makeRemote(object); }, - startMapper(mapper, inputs = [], outputs = []) { - return InnerNativeModule.startMapper(mapper, inputs, outputs); + startMapper(mapper, inputs = [], outputs = [], updater, tag, name) { + return InnerNativeModule.startMapper( + mapper, + inputs, + outputs, + updater, + tag, + name + ); }, stopMapper(mapperId) { diff --git a/src/reanimated2/UpdateProps.ts b/src/reanimated2/UpdateProps.ts index 942676fd13f..d7c3d6d02b4 100644 --- a/src/reanimated2/UpdateProps.ts +++ b/src/reanimated2/UpdateProps.ts @@ -7,7 +7,7 @@ import { Platform } from 'react-native'; import { _updatePropsJS } from './js-reanimated'; // copied from react-native/Libraries/Components/View/ReactNativeStyleAttributes -const colorProps = [ +export const colorProps = [ 'backgroundColor', 'borderBottomColor', 'borderColor', @@ -26,54 +26,48 @@ const colorProps = [ const ColorProperties = !isConfigured() ? [] : makeShareable(colorProps); -export const updateProps = ( - viewDescriptor, - updates, - maybeViewRef, - adapters -) => { - 'worklet'; - - const viewName = viewDescriptor.value.name || 'RCTView'; - - if (adapters) { - adapters.forEach((adapter) => { - adapter(updates); - }); - } +let updatePropsByPlatform; +if (Platform.OS === 'web' || process.env.JEST_WORKER_ID) { + updatePropsByPlatform = (viewDescriptor, updates, maybeViewRef) => { + 'worklet'; + _updatePropsJS(viewDescriptor.value.tag, null, updates, maybeViewRef); + }; +} else { + updatePropsByPlatform = (viewDescriptor, updates, _) => { + 'worklet'; - if (Platform.OS !== 'web') { - Object.keys(updates).forEach((key) => { + for (const key in updates) { if (ColorProperties.indexOf(key) !== -1) { updates[key] = processColor(updates[key]); } - }); - } + } - const updatePropsInternal = - typeof _updateProps === 'undefined' ? _updatePropsJS : _updateProps; + _updateProps( + viewDescriptor.value.tag, + viewDescriptor.value.name || 'RCTView', + updates + ); + }; +} - updatePropsInternal( - viewDescriptor.value.tag, - viewName, - updates, - maybeViewRef - ); -}; +export const updateProps = updatePropsByPlatform; export const updatePropsJestWrapper = ( viewDescriptor, updates, maybeViewRef, - adapters, - animatedStyle + animatedStyle, + adapters ) => { + adapters.forEach((adapter) => { + adapter(updates); + }); animatedStyle.current.value = { ...animatedStyle.current.value, ...updates, }; - updateProps(viewDescriptor, updates, maybeViewRef, adapters); + updateProps(viewDescriptor, updates, maybeViewRef); }; export default updateProps; diff --git a/src/reanimated2/animations.ts b/src/reanimated2/animations.ts index a5389ca4db0..5bd8af189b7 100644 --- a/src/reanimated2/animations.ts +++ b/src/reanimated2/animations.ts @@ -194,7 +194,6 @@ export function withTiming(toValue, userConfig, callback) { function timing(animation, now) { const { toValue, progress, startTime, current } = animation; - const runtime = now - startTime; if (runtime >= config.duration) { @@ -204,11 +203,9 @@ export function withTiming(toValue, userConfig, callback) { return true; } - const newProgress = config.easing(runtime / config.duration); - - const dist = + const newProgress = animation.easing(runtime / config.duration); + animation.current += ((toValue - current) * (newProgress - progress)) / (1 - progress); - animation.current += dist; animation.progress = newProgress; return false; } @@ -230,6 +227,11 @@ export function withTiming(toValue, userConfig, callback) { animation.progress = 0; } animation.current = value; + if (typeof config.easing === 'object') { + animation.easing = config.easing.factory(); + } else { + animation.easing = config.easing; + } } return { @@ -413,7 +415,6 @@ export function withStyleAnimation(styleAnimations) { }); } - export function withSpring(toValue, userConfig, callback) { 'worklet'; diff --git a/src/reanimated2/core.ts b/src/reanimated2/core.ts index 41c63f308d4..76ab6c9c2e6 100644 --- a/src/reanimated2/core.ts +++ b/src/reanimated2/core.ts @@ -259,9 +259,25 @@ export function makeRemote(object = {}) { return NativeReanimated.makeRemote(object); } -export function startMapper(mapper, inputs = [], outputs = []) { +export function startMapper( + mapper, + inputs = [], + outputs = [], + updater = () => { + // noop + }, + tag = 0, + name = '' +) { isConfiguredCheck(); - return NativeReanimated.startMapper(mapper, inputs, outputs); + return NativeReanimated.startMapper( + mapper, + inputs, + outputs, + updater, + tag, + name + ); } export function stopMapper(mapperId) { diff --git a/src/reanimated2/interpolation.ts b/src/reanimated2/interpolation.ts index 860af012260..bd06cd4909e 100644 --- a/src/reanimated2/interpolation.ts +++ b/src/reanimated2/interpolation.ts @@ -96,7 +96,7 @@ function internalInterpolate(x, l, r, ll, rr, type) { const config = { type, coef, val, ll, rr, x }; - validateType(type); + if (__DEV__) validateType(type); if (typeof type === 'object') { if (coef * val < coef * ll) {