diff --git a/apps/common-app/src/apps/reanimated/examples/EmptyExample.tsx b/apps/common-app/src/apps/reanimated/examples/EmptyExample.tsx index 7f3a1ac26ce..fc46beb5ebe 100644 --- a/apps/common-app/src/apps/reanimated/examples/EmptyExample.tsx +++ b/apps/common-app/src/apps/reanimated/examples/EmptyExample.tsx @@ -1,5 +1,17 @@ import React from 'react'; import { StyleSheet, Text, View } from 'react-native'; +import { createShareable } from 'react-native-worklets'; + +// const shareable = createShareable('UI', 2137); + +// if (shareable.isHost) { +// console.log('isHost shareable', true); +// shareable.value += 1; +// } else { +// console.log('isHost shareable', false); +// const value = shareable.getSync(); +// console.log('obtained value', value); +// } export default function EmptyExample() { return ( diff --git a/packages/react-native-reanimated/src/mutables.ts b/packages/react-native-reanimated/src/mutables.ts index f13ddf18662..a13ab97232e 100644 --- a/packages/react-native-reanimated/src/mutables.ts +++ b/packages/react-native-reanimated/src/mutables.ts @@ -3,6 +3,7 @@ import type { Synchronizable } from 'react-native-worklets'; import { createSerializable, + createShareable, createSynchronizable, runOnUISync, scheduleOnUI, @@ -209,12 +210,20 @@ const USE_SYNCHRONIZABLE_FOR_MUTABLES = getStaticFeatureFlag( function experimental_makeMutableNative(initial: Value): Mutable { let latest = initial; const dirtyFlag = createSynchronizable(false); - const handle = createSerializable({ - __init: () => { + const shareable = createShareable( + 'UI', + () => { 'worklet'; return experimental_makeMutableUI(initial, dirtyFlag); }, - }); + { inline: true } + ); + // const handle = createSerializable({ + // __init: () => { + // 'worklet'; + // return experimental_makeMutableUI(initial, dirtyFlag); + // }, + // }); const mutable: PartialMutable = { get value(): Value { @@ -223,6 +232,8 @@ function experimental_makeMutableNative(initial: Value): Mutable { const uiValueGetter = (svArg: Mutable) => runOnUISync((sv) => { sv.setDirty!(false); + // console.log('shareable value.value', shareable.value.value); + // console.log('shareable value.value'); return sv.value; }, svArg); latest = uiValueGetter(mutable as Mutable); @@ -269,7 +280,7 @@ function experimental_makeMutableNative(initial: Value): Mutable { hideInternalValueProp(mutable); addCompilerSafeGetAndSet(mutable); - serializableMappingCache.set(mutable, handle); + serializableMappingCache.set(mutable, createSerializable(shareable)); return mutable as Mutable; } diff --git a/packages/react-native-worklets/Common/cpp/worklets/NativeModules/JSIWorkletsModuleProxy.cpp b/packages/react-native-worklets/Common/cpp/worklets/NativeModules/JSIWorkletsModuleProxy.cpp index a7cb25ddf37..d94e1ff2b15 100644 --- a/packages/react-native-worklets/Common/cpp/worklets/NativeModules/JSIWorkletsModuleProxy.cpp +++ b/packages/react-native-worklets/Common/cpp/worklets/NativeModules/JSIWorkletsModuleProxy.cpp @@ -1,9 +1,11 @@ +#include #include #include #include #include #include +#include #include #include #include @@ -54,10 +56,8 @@ inline void scheduleOnUI( }); } -inline jsi::Value executeOnUIRuntimeSync( - const std::weak_ptr &weakUIWorkletRuntime, - jsi::Runtime &rt, - const jsi::Value &worklet) { +inline jsi::Value +runOnUISync(const std::weak_ptr &weakUIWorkletRuntime, jsi::Runtime &rt, const jsi::Value &worklet) { if (auto uiWorkletRuntime = weakUIWorkletRuntime.lock()) { auto serializableWorklet = extractSerializableOrThrow( rt, worklet, "[Worklets] Only worklets can be executed on UI runtime."); @@ -182,6 +182,8 @@ JSIWorkletsModuleProxy::~JSIWorkletsModuleProxy() = default; std::vector JSIWorkletsModuleProxy::getPropertyNames(jsi::Runtime &rt) { std::vector propertyNames; + /* #region Serializable */ + propertyNames.emplace_back(jsi::PropNameID::forAscii(rt, "createSerializable")); propertyNames.emplace_back(jsi::PropNameID::forAscii(rt, "createSerializableBigInt")); propertyNames.emplace_back(jsi::PropNameID::forAscii(rt, "createSerializableBoolean")); @@ -202,15 +204,25 @@ std::vector JSIWorkletsModuleProxy::getPropertyNames(jsi::Runti propertyNames.emplace_back(jsi::PropNameID::forAscii(rt, "createCustomSerializable")); propertyNames.emplace_back(jsi::PropNameID::forAscii(rt, "registerCustomSerializable")); + /* #endregion Serializable */ + /* #region Worklet Runtime */ + + propertyNames.emplace_back(jsi::PropNameID::forAscii(rt, "getUIWorkletRuntime")); propertyNames.emplace_back(jsi::PropNameID::forAscii(rt, "scheduleOnUI")); - propertyNames.emplace_back(jsi::PropNameID::forAscii(rt, "executeOnUIRuntimeSync")); + propertyNames.emplace_back(jsi::PropNameID::forAscii(rt, "runOnUISync")); propertyNames.emplace_back(jsi::PropNameID::forAscii(rt, "createWorkletRuntime")); propertyNames.emplace_back(jsi::PropNameID::forAscii(rt, "scheduleOnRuntime")); propertyNames.emplace_back(jsi::PropNameID::forAscii(rt, "reportFatalErrorOnJS")); + /* #endregion Worklet Runtime */ + /* #region Feature Flags */ + propertyNames.emplace_back(jsi::PropNameID::forAscii(rt, "getStaticFeatureFlag")); propertyNames.emplace_back(jsi::PropNameID::forAscii(rt, "setDynamicFeatureFlag")); + /* #endregion Feature Flags */ + /* #region Synchronizable */ + propertyNames.emplace_back(jsi::PropNameID::forAscii(rt, "createSynchronizable")); propertyNames.emplace_back(jsi::PropNameID::forAscii(rt, "synchronizableGetDirty")); propertyNames.emplace_back(jsi::PropNameID::forAscii(rt, "synchronizableGetBlocking")); @@ -218,16 +230,32 @@ std::vector JSIWorkletsModuleProxy::getPropertyNames(jsi::Runti propertyNames.emplace_back(jsi::PropNameID::forAscii(rt, "synchronizableLock")); propertyNames.emplace_back(jsi::PropNameID::forAscii(rt, "synchronizableUnlock")); + /* #endregion Synchronizable */ + /* #region Shareable */ + + propertyNames.emplace_back(jsi::PropNameID::forAscii(rt, "createShareable")); + propertyNames.emplace_back(jsi::PropNameID::forAscii(rt, "shareableGetAsync")); + propertyNames.emplace_back(jsi::PropNameID::forAscii(rt, "shareableGetSync")); + propertyNames.emplace_back(jsi::PropNameID::forAscii(rt, "shareableSetAsync")); + propertyNames.emplace_back(jsi::PropNameID::forAscii(rt, "shareableSetSync")); + + /* #endregion Shareable */ + /* #region Bundle Mode */ + #ifdef WORKLETS_BUNDLE_MODE propertyNames.emplace_back(jsi::PropNameID::forAscii(rt, "propagateModuleUpdate")); #endif // WORKLETS_BUNDLE_MODE + /* #endregion Bundle Mode */ + return propertyNames; } jsi::Value JSIWorkletsModuleProxy::get(jsi::Runtime &rt, const jsi::PropNameID &propName) { const auto name = propName.utf8(rt); + /* #region Serializable */ + if (name == "createSerializable") { return jsi::Function::createFromHostFunction( rt, propName, 3, [](jsi::Runtime &rt, const jsi::Value &thisValue, const jsi::Value *args, size_t count) { @@ -373,6 +401,24 @@ jsi::Value JSIWorkletsModuleProxy::get(jsi::Runtime &rt, const jsi::PropNameID & }); } + /* #endregion Serializable */ + /* #region Worklet Runtime */ + + if (name == "getUIWorkletRuntime") { + return jsi::Function::createFromHostFunction( + rt, + propName, + 0, + [weakUIWorkletRuntime = uiWorkletRuntime_]( + jsi::Runtime &rt, const jsi::Value &thisValue, const jsi::Value *args, size_t count) -> jsi::Value { + auto strongUIWorkletRuntime = weakUIWorkletRuntime.lock(); + if (!strongUIWorkletRuntime) { + return jsi::Value::undefined(); + } + return jsi::Object::createFromHostObject(rt, strongUIWorkletRuntime); + }); + } + if (name == "scheduleOnUI") { return jsi::Function::createFromHostFunction( rt, @@ -385,14 +431,14 @@ jsi::Value JSIWorkletsModuleProxy::get(jsi::Runtime &rt, const jsi::PropNameID & }); } - if (name == "executeOnUIRuntimeSync") { + if (name == "runOnUISync") { return jsi::Function::createFromHostFunction( rt, propName, 1, [uiWorkletRuntime = uiWorkletRuntime_]( jsi::Runtime &rt, const jsi::Value &thisValue, const jsi::Value *args, size_t count) { - return executeOnUIRuntimeSync(uiWorkletRuntime, rt, args[0]); + return runOnUISync(uiWorkletRuntime, rt, args[0]); }); } @@ -453,6 +499,30 @@ jsi::Value JSIWorkletsModuleProxy::get(jsi::Runtime &rt, const jsi::PropNameID & }); } + /* #endregion Worklet Runtime */ + /* #region Feature Flags */ + + if (name == "getStaticFeatureFlag") { + return jsi::Function::createFromHostFunction( + rt, propName, 2, [](jsi::Runtime &rt, const jsi::Value &thisValue, const jsi::Value *args, size_t count) { + return worklets::StaticFeatureFlags::getFlag( + /* name */ args[0].asString(rt).utf8(rt)); + }); + } + + if (name == "setDynamicFeatureFlag") { + return jsi::Function::createFromHostFunction( + rt, propName, 2, [](jsi::Runtime &rt, const jsi::Value &thisValue, const jsi::Value *args, size_t count) { + worklets::DynamicFeatureFlags::setFlag( + /* name */ args[0].asString(rt).utf8(rt), + /* value */ args[1].asBool()); + return jsi::Value::undefined(); + }); + } + + /* #endregion Feature Flags */ + /* #region Synchronizable */ + if (name == "createSynchronizable") { return jsi::Function::createFromHostFunction( rt, propName, 1, [](jsi::Runtime &rt, const jsi::Value &thisValue, const jsi::Value *args, size_t count) { @@ -506,6 +576,62 @@ jsi::Value JSIWorkletsModuleProxy::get(jsi::Runtime &rt, const jsi::PropNameID & }); } + /* #endregion Synchronizable */ + /* #region Shareable */ + + if (name == "createShareable") { + return jsi::Function::createFromHostFunction( + rt, + propName, + 3, + [](jsi::Runtime &rt, const jsi::Value &thisValue, const jsi::Value *args, size_t count) -> jsi::Value { + auto runtime = args[0].asObject(rt).getHostObject(rt); + auto serializable = extractSerializableOrThrow(rt, args[1], "[Worklets] Value must be a Serializable."); + auto isInline = args[2].asBool(); + auto shareable = std::make_shared(serializable, runtime, isInline); + return SerializableJSRef::newNativeStateObject(rt, shareable); + }); + } + if (name == "shareableGetAsync") { + return jsi::Function::createFromHostFunction( + rt, propName, 1, [](jsi::Runtime &rt, const jsi::Value &thisValue, const jsi::Value *args, size_t count) { + return jsi::Value::undefined(); + // auto shareable = extractShareableOrThrow(rt, args[0]); + // return shareable->getAsync()->toJSValue(rt); + }); + } + if (name == "shareableGetSync") { + return jsi::Function::createFromHostFunction( + rt, propName, 1, [](jsi::Runtime &rt, const jsi::Value &thisValue, const jsi::Value *args, size_t count) { + return jsi::Value::undefined(); + // auto shareable = extractShareableOrThrow(rt, args[0]); + // return shareable->getSync()->toJSValue(rt); + }); + } + if (name == "shareableSetAsync") { + return jsi::Function::createFromHostFunction( + rt, propName, 2, [](jsi::Runtime &rt, const jsi::Value &thisValue, const jsi::Value *args, size_t count) { + return jsi::Value::undefined(); + // auto shareable = extractShareableOrThrow(rt, args[0]); + // auto newValue = extractSerializableOrThrow(rt, args[1], "[Worklets] Value must be a Serializable."); + // shareable->setAsync(newValue); + // return jsi::Value::undefined(); + }); + } + if (name == "shareableSetSync") { + return jsi::Function::createFromHostFunction( + rt, propName, 2, [](jsi::Runtime &rt, const jsi::Value &thisValue, const jsi::Value *args, size_t count) { + return jsi::Value::undefined(); + // auto shareable = extractShareableOrThrow(rt, args[0]); + // auto newValue = extractSerializableOrThrow(rt, args[1], "[Worklets] Value must be a Serializable."); + // shareable->setSync(newValue); + // return jsi::Value::undefined(); + }); + } + + /* #endregion Shareable */ + /* #region Bundle Mode */ + #ifdef WORKLETS_BUNDLE_MODE if (name == "propagateModuleUpdate") { return jsi::Function::createFromHostFunction( @@ -522,23 +648,7 @@ jsi::Value JSIWorkletsModuleProxy::get(jsi::Runtime &rt, const jsi::PropNameID & } #endif // WORKLETS_BUNDLE_MODE - if (name == "getStaticFeatureFlag") { - return jsi::Function::createFromHostFunction( - rt, propName, 2, [](jsi::Runtime &rt, const jsi::Value &thisValue, const jsi::Value *args, size_t count) { - return worklets::StaticFeatureFlags::getFlag( - /* name */ args[0].asString(rt).utf8(rt)); - }); - } - - if (name == "setDynamicFeatureFlag") { - return jsi::Function::createFromHostFunction( - rt, propName, 2, [](jsi::Runtime &rt, const jsi::Value &thisValue, const jsi::Value *args, size_t count) { - worklets::DynamicFeatureFlags::setFlag( - /* name */ args[0].asString(rt).utf8(rt), - /* value */ args[1].asBool()); - return jsi::Value::undefined(); - }); - } + /* #endregion Bundle Mode */ return jsi::Value::undefined(); } diff --git a/packages/react-native-worklets/Common/cpp/worklets/NativeModules/JSIWorkletsModuleProxy.h b/packages/react-native-worklets/Common/cpp/worklets/NativeModules/JSIWorkletsModuleProxy.h index b42529ee54e..648cc1f13ba 100644 --- a/packages/react-native-worklets/Common/cpp/worklets/NativeModules/JSIWorkletsModuleProxy.h +++ b/packages/react-native-worklets/Common/cpp/worklets/NativeModules/JSIWorkletsModuleProxy.h @@ -1,4 +1,3 @@ - #pragma once #include diff --git a/packages/react-native-worklets/Common/cpp/worklets/Resources/ShareableUnpacker.cpp b/packages/react-native-worklets/Common/cpp/worklets/Resources/ShareableUnpacker.cpp new file mode 100644 index 00000000000..f29bd8f6d4c --- /dev/null +++ b/packages/react-native-worklets/Common/cpp/worklets/Resources/ShareableUnpacker.cpp @@ -0,0 +1,192 @@ +// This file was generated with +// `packages/react-native-worklets/scripts/export-unpackers.js`. +// Please do not modify it directly. + +#include + +namespace worklets { + +const char ShareableUnpackerCode[] = + R"DELIMITER__((function () { + var serializer = globalThis.__RUNTIME_KIND === 1 || globalThis._WORKLETS_BUNDLE_MODE ? function (value, _) { + return createSerializable(value); + } : globalThis._createSerializable; + var runOnUISync; + var scheduleOnUI; + var runOnUIAsync; + var memoize; + if (globalThis.__RUNTIME_KIND === 1) { + runOnUISync = RNRuntimeRunOnUISync; + scheduleOnUI = RNRuntimeScheduleOnUI; + runOnUIAsync = RNRuntimeRunOnUIAsync; + memoize = function memoize(unpacked, serialized) { + serializableMappingCache.set(unpacked, serialized); + }; + } else { + var proxy = globalThis.__workletsModuleProxy; + runOnUISync = function runOnUISync(worklet) { + for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { + args[_key - 1] = arguments[_key]; + } + return proxy.runOnUISync(serializer(function shareableUnpackerNativeTs1Factory(_ref) { + var _worklet_10367856098025_init_data = _ref._worklet_10367856098025_init_data, + worklet = _ref.worklet, + args = _ref.args; + var _e = [new global.Error(), -3, -27]; + var shareableUnpackerNativeTs1 = function shareableUnpackerNativeTs1() { + return worklet.apply(void 0, _toConsumableArray(args)); + }; + shareableUnpackerNativeTs1.__closure = { + worklet: worklet, + args: args + }; + shareableUnpackerNativeTs1.__workletHash = 10367856098025; + shareableUnpackerNativeTs1.__pluginVersion = "0.8.0-main"; + shareableUnpackerNativeTs1.__initData = _worklet_10367856098025_init_data; + shareableUnpackerNativeTs1.__stackDetails = _e; + return shareableUnpackerNativeTs1; + }({ + _worklet_10367856098025_init_data: _worklet_10367856098025_init_data, + worklet: worklet, + args: args + }))); + }; + scheduleOnUI = function scheduleOnUI(worklet) { + for (var _len2 = arguments.length, args = new Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) { + args[_key2 - 1] = arguments[_key2]; + } + proxy.scheduleOnUI(serializer(function shareableUnpackerNativeTs2Factory(_ref2) { + var _worklet_16822325880736_init_data = _ref2._worklet_16822325880736_init_data, + worklet = _ref2.worklet, + args = _ref2.args; + var _e = [new global.Error(), -3, -27]; + var shareableUnpackerNativeTs2 = function shareableUnpackerNativeTs2() { + worklet.apply(void 0, _toConsumableArray(args)); + }; + shareableUnpackerNativeTs2.__closure = { + worklet: worklet, + args: args + }; + shareableUnpackerNativeTs2.__workletHash = 16822325880736; + shareableUnpackerNativeTs2.__pluginVersion = "0.8.0-main"; + shareableUnpackerNativeTs2.__initData = _worklet_16822325880736_init_data; + shareableUnpackerNativeTs2.__stackDetails = _e; + return shareableUnpackerNativeTs2; + }({ + _worklet_16822325880736_init_data: _worklet_16822325880736_init_data, + worklet: worklet, + args: args + }))); + }; + runOnUIAsync = function runOnUIAsync() { + throw new WorkletsError('runOnUIAsync is not supported on Worklet Runtimes yet'); + }; + memoize = function memoize() {}; + } + function shareableUnpacker(shareableRef, isHost, initial, inline) { + var shareable; + if (isHost) { + initial = typeof initial === 'function' ? initial() : initial; + if (inline) { + var inlineShareable = initial; + inlineShareable.isHost = true; + inlineShareable.__shareableRef = true; + return inlineShareable; + } else { + return { + isHost: true, + __shareableRef: true, + value: initial + }; + } + } else { + var get = function shareableUnpackerNativeTs3Factory(_ref3) { + var _worklet_13730600479565_init_data = _ref3._worklet_13730600479565_init_data, + shareableRef = _ref3.shareableRef; + var _e = [new global.Error(), -2, -27]; + var shareableUnpackerNativeTs3 = function shareableUnpackerNativeTs3() { + return shareableRef.value; + }; + shareableUnpackerNativeTs3.__closure = { + shareableRef: shareableRef + }; + shareableUnpackerNativeTs3.__workletHash = 13730600479565; + shareableUnpackerNativeTs3.__pluginVersion = "0.8.0-main"; + shareableUnpackerNativeTs3.__initData = _worklet_13730600479565_init_data; + shareableUnpackerNativeTs3.__stackDetails = _e; + return shareableUnpackerNativeTs3; + }({ + _worklet_13730600479565_init_data: _worklet_13730600479565_init_data, + shareableRef: shareableRef + }); + var setWithValue = function shareableUnpackerNativeTs4Factory(_ref4) { + var _worklet_9086612025725_init_data = _ref4._worklet_9086612025725_init_data, + shareableRef = _ref4.shareableRef; + var _e = [new global.Error(), -2, -27]; + var shareableUnpackerNativeTs4 = function shareableUnpackerNativeTs4(value) { + shareableRef.value = value; + }; + shareableUnpackerNativeTs4.__closure = { + shareableRef: shareableRef + }; + shareableUnpackerNativeTs4.__workletHash = 9086612025725; + shareableUnpackerNativeTs4.__pluginVersion = "0.8.0-main"; + shareableUnpackerNativeTs4.__initData = _worklet_9086612025725_init_data; + shareableUnpackerNativeTs4.__stackDetails = _e; + return shareableUnpackerNativeTs4; + }({ + _worklet_9086612025725_init_data: _worklet_9086612025725_init_data, + shareableRef: shareableRef + }); + var setWithSetter = function shareableUnpackerNativeTs5Factory(_ref5) { + var _worklet_13165553193070_init_data = _ref5._worklet_13165553193070_init_data, + shareableRef = _ref5.shareableRef; + var _e = [new global.Error(), -2, -27]; + var shareableUnpackerNativeTs5 = function shareableUnpackerNativeTs5(setter) { + var currentValue = shareableRef.value; + var newValue = setter(currentValue); + shareableRef.value = newValue; + }; + shareableUnpackerNativeTs5.__closure = { + shareableRef: shareableRef + }; + shareableUnpackerNativeTs5.__workletHash = 13165553193070; + shareableUnpackerNativeTs5.__pluginVersion = "0.8.0-main"; + shareableUnpackerNativeTs5.__initData = _worklet_13165553193070_init_data; + shareableUnpackerNativeTs5.__stackDetails = _e; + return shareableUnpackerNativeTs5; + }({ + _worklet_13165553193070_init_data: _worklet_13165553193070_init_data, + shareableRef: shareableRef + }); + shareable = { + getAsync: function getAsync() { + return runOnUIAsync(get); + }, + getSync: function getSync() { + return runOnUISync(get); + }, + setAsync: function setAsync(value) { + if (typeof value === 'function') { + scheduleOnUI(setWithSetter, value); + } else { + scheduleOnUI(setWithValue, value); + } + }, + setSync: function setSync(value) { + if (typeof value === 'function') { + runOnUISync(setWithSetter, value); + } else { + runOnUISync(setWithValue, value); + } + }, + isHost: false, + __shareableRef: true + }; + } + memoize(shareable, shareableRef); + return shareable; + } + globalThis.__shareableUnpacker = shareableUnpacker; +})();)DELIMITER__"; +} // namespace worklets diff --git a/packages/react-native-worklets/Common/cpp/worklets/Resources/SynchronizableUnpacker.cpp b/packages/react-native-worklets/Common/cpp/worklets/Resources/SynchronizableUnpacker.cpp index 9805b38b98f..97a50fbb6de 100644 --- a/packages/react-native-worklets/Common/cpp/worklets/Resources/SynchronizableUnpacker.cpp +++ b/packages/react-native-worklets/Common/cpp/worklets/Resources/SynchronizableUnpacker.cpp @@ -8,8 +8,8 @@ namespace worklets { const char SynchronizableUnpackerCode[] = R"DELIMITER__((function () { - var serializer = !globalThis._WORKLET || globalThis._WORKLETS_BUNDLE_MODE ? function (value, _) { - return (0, _serializable.createSerializable)(value); + var serializer = globalThis.__RUNTIME_KIND === 1 || globalThis._WORKLETS_BUNDLE_MODE ? function (value, _) { + return createSerializable(value); } : globalThis._createSerializable; function synchronizableUnpacker(synchronizableRef) { var synchronizable = synchronizableRef; diff --git a/packages/react-native-worklets/Common/cpp/worklets/Resources/Unpackers.h b/packages/react-native-worklets/Common/cpp/worklets/Resources/Unpackers.h index 503833edecd..6dc52dfb572 100644 --- a/packages/react-native-worklets/Common/cpp/worklets/Resources/Unpackers.h +++ b/packages/react-native-worklets/Common/cpp/worklets/Resources/Unpackers.h @@ -4,4 +4,5 @@ namespace worklets { extern const char ValueUnpackerCode[]; extern const char SynchronizableUnpackerCode[]; extern const char CustomSerializableUnpackerCode[]; +extern const char ShareableUnpackerCode[]; } // namespace worklets diff --git a/packages/react-native-worklets/Common/cpp/worklets/SharedItems/Serializable.h b/packages/react-native-worklets/Common/cpp/worklets/SharedItems/Serializable.h index 771b6be96cd..34b754f1ee2 100644 --- a/packages/react-native-worklets/Common/cpp/worklets/SharedItems/Serializable.h +++ b/packages/react-native-worklets/Common/cpp/worklets/SharedItems/Serializable.h @@ -65,6 +65,7 @@ class Serializable { ArrayBufferType, TurboModuleLikeType, ImportType, + ShareableType, SynchronizableType, CustomType, }; diff --git a/packages/react-native-worklets/Common/cpp/worklets/SharedItems/Shareable.cpp b/packages/react-native-worklets/Common/cpp/worklets/SharedItems/Shareable.cpp new file mode 100644 index 00000000000..b22a5f1649f --- /dev/null +++ b/packages/react-native-worklets/Common/cpp/worklets/SharedItems/Shareable.cpp @@ -0,0 +1,44 @@ +#include +#include +#include +#include + +#include + +namespace worklets { + +jsi::Function Shareable::getShareableUnpacker(jsi::Runtime &rt) { + return rt.global().getPropertyAsFunction(rt, "__shareableUnpacker"); +} + +Shareable::Shareable( + const std::shared_ptr &initial, + const std::weak_ptr &hostRuntime, + const bool isInline) + : Serializable(ValueType::ShareableType) { + isInline_ = isInline; + const auto strongHostRuntime = hostRuntime.lock(); // TODO: handle null + strongHostRuntime->runSync([this, &initial](jsi::Runtime &rt) { + try { + const auto shareableUnpacker = getShareableUnpacker(rt); + const auto shareable = + shareableUnpacker.call(rt, jsi::Value::undefined(), true, initial->toJSValue(rt), jsi::Value(rt, isInline_)); + value_ = std::make_shared(rt, std::move(shareable)); + } catch (jsi::JSError &e) { + e; + } + }); + hostRuntime_ = hostRuntime; +} + +jsi::Value Shareable::toJSValue(jsi::Runtime &rt) { + if (auto hostRuntime = hostRuntime_.lock()) { + return jsi::Value(hostRuntime->getJSIRuntime(), *value_); + } else { + const auto shareableUnpacker = getShareableUnpacker(rt); + const auto ref = SerializableJSRef::newNativeStateObject(rt, shared_from_this()); + return shareableUnpacker.call(rt, false, ref); + } +} + +} // namespace worklets diff --git a/packages/react-native-worklets/Common/cpp/worklets/SharedItems/Shareable.h b/packages/react-native-worklets/Common/cpp/worklets/SharedItems/Shareable.h new file mode 100644 index 00000000000..f90d536c319 --- /dev/null +++ b/packages/react-native-worklets/Common/cpp/worklets/SharedItems/Shareable.h @@ -0,0 +1,28 @@ +#pragma once + +#include +#include +#include + +#include + +namespace worklets { + +class Shareable : public Serializable, public std::enable_shared_from_this { + public: + static jsi::Function getShareableUnpacker(jsi::Runtime &rt); + + Shareable( + const std::shared_ptr &initial, + const std::weak_ptr &hostRuntime, + bool isInline); + + jsi::Value toJSValue(jsi::Runtime &rt) override; + + private: + std::shared_ptr value_; + std::weak_ptr hostRuntime_; + bool isInline_; +}; + +} // namespace worklets diff --git a/packages/react-native-worklets/Common/cpp/worklets/WorkletRuntime/WorkletRuntime.cpp b/packages/react-native-worklets/Common/cpp/worklets/WorkletRuntime/WorkletRuntime.cpp index f9ded2885b8..131cbb9515f 100644 --- a/packages/react-native-worklets/Common/cpp/worklets/WorkletRuntime/WorkletRuntime.cpp +++ b/packages/react-native-worklets/Common/cpp/worklets/WorkletRuntime/WorkletRuntime.cpp @@ -134,6 +134,9 @@ void WorkletRuntime::init(std::shared_ptr jsiWorkletsMod auto synchronizableUnpackerBuffer = std::make_shared(SynchronizableUnpackerCode); rt.evaluateJavaScript(synchronizableUnpackerBuffer, "synchronizableUnpacker"); + auto shareableUnpackerBuffer = std::make_shared(ShareableUnpackerCode); + rt.evaluateJavaScript(shareableUnpackerBuffer, "shareableUnpacker"); + auto customSerializableUnpackerBuffer = std::make_shared(CustomSerializableUnpackerCode); rt.evaluateJavaScript(customSerializableUnpackerBuffer, "customSerializableUnpacker"); #endif // WORKLETS_BUNDLE_MODE diff --git a/packages/react-native-worklets/Common/cpp/worklets/WorkletRuntime/WorkletRuntime.h b/packages/react-native-worklets/Common/cpp/worklets/WorkletRuntime/WorkletRuntime.h index 715c7009fa4..a209ee3eeb9 100644 --- a/packages/react-native-worklets/Common/cpp/worklets/WorkletRuntime/WorkletRuntime.h +++ b/packages/react-native-worklets/Common/cpp/worklets/WorkletRuntime/WorkletRuntime.h @@ -185,6 +185,12 @@ class WorkletRuntime : public jsi::HostObject, public std::enable_shared_from_th // Android std::shared_ptr extractWorkletRuntime(jsi::Runtime &rt, const jsi::Value &value); +struct WeakWorkletRuntimeHolder : jsi::NativeState { + explicit WeakWorkletRuntimeHolder(const std::weak_ptr &weakWorkletRuntime) + : weakWorkletRuntime(weakWorkletRuntime) {} + const std::weak_ptr weakWorkletRuntime; +}; + void scheduleOnRuntime( jsi::Runtime &rt, const jsi::Value &workletRuntimeValue, diff --git a/packages/react-native-worklets/scripts/export-unpackers.js b/packages/react-native-worklets/scripts/export-unpackers.js index c6fda91932c..486d6c33faa 100644 --- a/packages/react-native-worklets/scripts/export-unpackers.js +++ b/packages/react-native-worklets/scripts/export-unpackers.js @@ -9,6 +9,7 @@ const generate = require('@babel/generator').default; const path = require('path'); const fs = require('fs'); const assert = require('assert').strict; +const workletsBabelPlugin = require('../plugin'); exportToCpp('valueUnpacker.native.ts', 'ValueUnpacker'); exportToCpp('synchronizableUnpacker.native.ts', 'SynchronizableUnpacker'); @@ -16,6 +17,7 @@ exportToCpp( 'customSerializableUnpacker.native.ts', 'CustomSerializableUnpacker' ); +exportToCpp('shareableUnpacker.native.ts', 'ShareableUnpacker'); /** * @param {string} sourceFilePath - The path to the TypeScript source file to @@ -31,10 +33,12 @@ function exportToCpp(sourceFilePath, outputFilename) { ['@babel/preset-env', { modules: false }], '@babel/preset-typescript', ], + plugins: [workletsBabelPlugin], sourceType: 'unambiguous', code: false, ast: true, comments: false, + configFile: false, } ); diff --git a/packages/react-native-worklets/src/WorkletsModule/NativeWorklets.native.ts b/packages/react-native-worklets/src/WorkletsModule/NativeWorklets.native.ts index 9a12fada577..7bcc7d1f037 100644 --- a/packages/react-native-worklets/src/WorkletsModule/NativeWorklets.native.ts +++ b/packages/react-native-worklets/src/WorkletsModule/NativeWorklets.native.ts @@ -173,14 +173,28 @@ See https://docs.swmansion.com/react-native-worklets/docs/guides/troubleshooting ); } + createShareable( + hostRuntime: WorkletRuntime, + initialValue: SerializableRef, + inline: boolean + ): SerializableRef { + return this.#workletsModuleProxy.createShareable( + hostRuntime, + initialValue, + inline + ); + } + + getUIWorkletRuntime(): WorkletRuntime { + return this.#workletsModuleProxy.getUIWorkletRuntime(); + } + scheduleOnUI(serializable: SerializableRef) { return this.#workletsModuleProxy.scheduleOnUI(serializable); } - executeOnUIRuntimeSync( - serializable: SerializableRef - ): TReturn { - return this.#workletsModuleProxy.executeOnUIRuntimeSync(serializable); + runOnUISync(serializable: SerializableRef): TReturn { + return this.#workletsModuleProxy.runOnUISync(serializable); } createWorkletRuntime( diff --git a/packages/react-native-worklets/src/WorkletsModule/workletsModuleProxy.ts b/packages/react-native-worklets/src/WorkletsModule/workletsModuleProxy.ts index b7d9fb2f110..3e7a275102b 100644 --- a/packages/react-native-worklets/src/WorkletsModule/workletsModuleProxy.ts +++ b/packages/react-native-worklets/src/WorkletsModule/workletsModuleProxy.ts @@ -81,11 +81,17 @@ export interface WorkletsModuleProxy { typeId: number ): void; + createShareable( + hostRuntime: WorkletRuntime, + initialValue: SerializableRef, + inline: boolean + ): SerializableRef; + + getUIWorkletRuntime(): WorkletRuntime; + scheduleOnUI(serializable: SerializableRef): void; - executeOnUIRuntimeSync( - serializable: SerializableRef - ): TReturn; + runOnUISync(serializable: SerializableRef): TReturn; createWorkletRuntime( name: string, diff --git a/packages/react-native-worklets/src/index.ts b/packages/react-native-worklets/src/index.ts index 7ce17d8ba5b..71d6f04cc54 100644 --- a/packages/react-native-worklets/src/index.ts +++ b/packages/react-native-worklets/src/index.ts @@ -33,10 +33,12 @@ export { registerCustomSerializable, } from './memory/serializable'; export { serializableMappingCache } from './memory/serializableMappingCache'; +export { createShareable } from './memory/shareable'; export { createSynchronizable } from './memory/synchronizable'; export type { RegistrationData, SerializableRef, + Shareable, Synchronizable, SynchronizableRef, } from './memory/types'; diff --git a/packages/react-native-worklets/src/initializers/initializers.native.ts b/packages/react-native-worklets/src/initializers/initializers.native.ts index 41b58868d23..000e8549c3d 100644 --- a/packages/react-native-worklets/src/initializers/initializers.native.ts +++ b/packages/react-native-worklets/src/initializers/initializers.native.ts @@ -5,6 +5,7 @@ import { registerReportFatalRemoteError } from '../debug/errors'; import { registerWorkletsError, WorkletsError } from '../debug/WorkletsError'; import { bundleValueUnpacker } from '../memory/bundleUnpacker'; import { __installUnpacker as installCustomSerializableUnpacker } from '../memory/customSerializableUnpacker'; +import { __installUnpacker as installShareableUnpacker } from '../memory/shareableUnpacker'; import { __installUnpacker as installSynchronizableUnpacker } from '../memory/synchronizableUnpacker'; import { setupSetImmediate } from '../runLoop/common/setImmediatePolyfill'; import { setupSetInterval } from '../runLoop/common/setIntervalPolyfill'; @@ -17,7 +18,7 @@ import { isWorkletFunction } from '../workletFunction'; import { WorkletsModule } from '../WorkletsModule/NativeWorklets'; if (globalThis.__RUNTIME_KIND === undefined) { - // The only runtime that doesn't have `__RUNTIME_KIND` preconfigured + // The only runtime that doesn't have `__RUNTIME_KIND` pre-configured // is the RN Runtime. We must set it as soon as possible. globalThis.__RUNTIME_KIND = RuntimeKind.ReactNative; } @@ -73,8 +74,21 @@ export function setupConsole(boundCapturableConsole: typeof console) { 'worklet'; // @ts-ignore TypeScript doesn't like that there are missing methods in console object, but we don't provide all the methods for the UI runtime console version globalThis.console = { - assert: (...args) => scheduleOnRN(boundCapturableConsole.assert, ...args), - debug: (...args) => scheduleOnRN(boundCapturableConsole.debug, ...args), + assert: (...args) => + scheduleOnRN( + boundCapturableConsole.assert, + args.map((arg) => String(arg)) + ), + debug: (...args) => + scheduleOnRN( + boundCapturableConsole.debug, + args.map((arg) => String(arg)) + ), + // log: (...args) => + // scheduleOnRN( + // boundCapturableConsole.log, + // args.map((arg) => String(arg)) + // ), log: (...args) => scheduleOnRN(boundCapturableConsole.log, ...args), warn: (...args) => scheduleOnRN(boundCapturableConsole.warn, ...args), error: (...args) => scheduleOnRN(boundCapturableConsole.error, ...args), @@ -107,6 +121,7 @@ function initializeRuntime() { } installSynchronizableUnpacker(); installCustomSerializableUnpacker(); + installShareableUnpacker(); } /** A function that should be run only on React Native runtime. */ diff --git a/packages/react-native-worklets/src/memory/shareable.native.ts b/packages/react-native-worklets/src/memory/shareable.native.ts new file mode 100644 index 00000000000..64cf8881fdf --- /dev/null +++ b/packages/react-native-worklets/src/memory/shareable.native.ts @@ -0,0 +1,48 @@ +'use strict'; + +import { WorkletsError } from '../debug/WorkletsError'; +import { getUIWorkletRuntime } from '../runtimes'; +import type { WorkletRuntime } from '../types'; +import { WorkletsModule } from '../WorkletsModule/NativeWorklets'; +import { createSerializable } from './serializable'; +import type { SerializableRef, Shareable } from './types'; + +/** + * @deprecated Only UI host runtime is supported now. Use 'UI' as the + * hostRuntime argument. + */ +export function createShareable( + hostRuntime: WorkletRuntime, + initialValue: SerializableRef +): Shareable; + +export function createShareable( + hostRuntime: 'UI', + initialValue: TValue +): Shareable; + +export function createShareable( + hostRuntime: WorkletRuntime | 'UI', + initialValue: TValue, + config?: { inline: true } +): Shareable { + let actualHostRuntime: WorkletRuntime; + if (hostRuntime === 'UI') { + actualHostRuntime = getUIWorkletRuntime(); + } else { + throw new WorkletsError('Only UI host runtime is supported currently'); + } + + const shareableRef = WorkletsModule.createShareable( + actualHostRuntime, + createSerializable(initialValue), + !!config?.inline + ); + + console.log('shareableRef created', shareableRef); + + return globalThis.__shareableUnpacker( + shareableRef, + false + ) as unknown as Shareable; +} diff --git a/packages/react-native-worklets/src/memory/shareableUnpacker.native.ts b/packages/react-native-worklets/src/memory/shareableUnpacker.native.ts new file mode 100644 index 00000000000..3ed88df1e2d --- /dev/null +++ b/packages/react-native-worklets/src/memory/shareableUnpacker.native.ts @@ -0,0 +1,160 @@ +'use strict'; + +import { WorkletsError } from '../debug/WorkletsError'; +import { + runOnUIAsync as RNRuntimeRunOnUIAsync, + runOnUISync as RNRuntimeRunOnUISync, + scheduleOnUI as RNRuntimeScheduleOnUI, +} from '../threads'; +import type { WorkletFunction } from '../types'; +import { createSerializable } from './serializable'; +import { serializableMappingCache } from './serializableMappingCache'; +import type { SerializableRef, Shareable, ShareableHost } from './types'; + +export function __installUnpacker() { + const serializer = + globalThis.__RUNTIME_KIND === 1 || globalThis._WORKLETS_BUNDLE_MODE + ? (value: unknown, _: unknown) => createSerializable(value) + : globalThis._createSerializable; + + let runOnUISync: typeof RNRuntimeRunOnUISync; + let scheduleOnUI: typeof RNRuntimeScheduleOnUI; + let runOnUIAsync: typeof RNRuntimeRunOnUIAsync; + let memoize: ( + unpacked: Shareable, + serialized: SerializableRef + ) => void; + + if (globalThis.__RUNTIME_KIND === 1 /* RuntimeKind.ReactNative */) { + runOnUISync = RNRuntimeRunOnUISync; + scheduleOnUI = RNRuntimeScheduleOnUI; + runOnUIAsync = RNRuntimeRunOnUIAsync; + memoize = (unpacked, serialized) => { + serializableMappingCache.set(unpacked, serialized); + }; + } else { + const proxy = globalThis.__workletsModuleProxy; + runOnUISync = ((worklet: WorkletFunction, ...args: unknown[]) => { + return proxy.runOnUISync( + serializer(() => { + 'worklet'; + return worklet(...args); + }) + ); + }) as typeof RNRuntimeRunOnUISync; + + scheduleOnUI = ((worklet: WorkletFunction, ...args: unknown[]) => { + proxy.scheduleOnUI( + serializer(() => { + 'worklet'; + worklet(...args); + }) + ); + }) as typeof RNRuntimeScheduleOnUI; + + runOnUIAsync = () => { + throw new WorkletsError( + 'runOnUIAsync is not supported on Worklet Runtimes yet' + ); + }; + + memoize = () => { + // No-op on Worklet Runtimes. + }; + } + + /** + * @param shareableRef - Is of type {@link SerializableRef} on the Ref Runtime + * side and of type {@link Shareable} on the Host Runtime side. + * @param isHost - Whether the unpacker is running on the Host Runtime side. + * @param initial - Initial value to use when running on the Host Runtime + * side. Undefined on the Ref Runtime side. + */ + function shareableUnpacker( + shareableRef: SerializableRef | Shareable, + isHost: boolean, + initial?: TValue, + inline?: boolean + ): Shareable { + type HostRuntimeType = ShareableHost; + type RefRuntimeType = SerializableRef; + + let shareable: Shareable; + + if (isHost) { + // console.log('initial on host', initial); + + initial = + typeof initial === 'function' ? (initial as () => TValue)() : initial; + + if (inline) { + const inlineShareable = initial as Shareable; + inlineShareable.isHost = true; + inlineShareable.__shareableRef = true; + return inlineShareable; + } else { + return { + isHost: true, + __shareableRef: true, + value: initial, + } as Shareable; + } + } else { + const get = () => { + 'worklet'; + return (shareableRef as HostRuntimeType).value; + }; + + const setWithValue = (value: TValue) => { + 'worklet'; + (shareableRef as HostRuntimeType).value = value; + }; + + const setWithSetter = (setter: (prev: TValue) => TValue) => { + 'worklet'; + const currentValue = (shareableRef as HostRuntimeType).value; + const newValue = setter(currentValue); + (shareableRef as HostRuntimeType).value = newValue; + }; + + shareable = { + getAsync() { + return runOnUIAsync(get); + }, + + getSync() { + return runOnUISync(get); + }, + + setAsync(value: TValue | ((prev: TValue) => TValue)) { + if (typeof value === 'function') { + scheduleOnUI(setWithSetter, value as (prev: TValue) => TValue); + } else { + scheduleOnUI(setWithValue, value); + } + }, + + setSync(value: TValue | ((prev: TValue) => TValue)) { + if (typeof value === 'function') { + runOnUISync(setWithSetter, value as (prev: TValue) => TValue); + } else { + runOnUISync(setWithValue, value); + } + }, + + isHost: false, + __shareableRef: true, + } as const; + } + + memoize(shareable, shareableRef as RefRuntimeType); + return shareable; + } + + globalThis.__shareableUnpacker = shareableUnpacker; +} + +export type ShareableUnpacker = ( + shareableRef: SerializableRef, + isHost: boolean +) => Shareable; diff --git a/packages/react-native-worklets/src/memory/synchronizableUnpacker.native.ts b/packages/react-native-worklets/src/memory/synchronizableUnpacker.native.ts index 523be9419d8..2cb66d5d5d1 100644 --- a/packages/react-native-worklets/src/memory/synchronizableUnpacker.native.ts +++ b/packages/react-native-worklets/src/memory/synchronizableUnpacker.native.ts @@ -6,7 +6,7 @@ import { type Synchronizable, type SynchronizableRef } from './types'; export function __installUnpacker() { // TODO: Add cache for synchronizables. const serializer = - !globalThis._WORKLET || globalThis._WORKLETS_BUNDLE_MODE + globalThis.__RUNTIME_KIND === 1 || globalThis._WORKLETS_BUNDLE_MODE ? (value: unknown, _: unknown) => createSerializable(value) : globalThis._createSerializable; @@ -15,7 +15,7 @@ export function __installUnpacker() { ): Synchronizable { const synchronizable = synchronizableRef as unknown as Synchronizable; - const proxy = globalThis.__workletsModuleProxy!; + const proxy = globalThis.__workletsModuleProxy; synchronizable.__synchronizableRef = true; synchronizable.getDirty = () => { diff --git a/packages/react-native-worklets/src/memory/types.ts b/packages/react-native-worklets/src/memory/types.ts index 488125e3e32..0ad451301e7 100644 --- a/packages/react-native-worklets/src/memory/types.ts +++ b/packages/react-native-worklets/src/memory/types.ts @@ -77,3 +77,30 @@ export type SerializationData = Omit< }; export type CustomSerializationRegistry = SerializationData[]; + +export type ShareableHostProps = { + value: TValue; +}; + +export type ShareableBorrowProps = { + getAsync(): Promise; + getSync(): TValue; + setAsync(value: TValue | ((prev: TValue) => TValue)): void; + setSync(value: TValue | ((prev: TValue) => TValue)): void; +}; + +export type ShareableHost = { + isHost: true; + __shareableRef: true; +} & ShareableHostProps & + Partial>; + +export type ShareableBorrow = { + isHost: false; + __shareableRef: true; +} & ShareableBorrowProps & + Partial>; + +export type Shareable = + | ShareableHost + | ShareableBorrow; diff --git a/packages/react-native-worklets/src/privateGlobals.d.ts b/packages/react-native-worklets/src/privateGlobals.d.ts index 7082c0f3f80..865f39a04e6 100644 --- a/packages/react-native-worklets/src/privateGlobals.d.ts +++ b/packages/react-native-worklets/src/privateGlobals.d.ts @@ -5,6 +5,7 @@ import type { callGuardDEV } from './callGuard'; import type { reportFatalRemoteError } from './debug/errors'; import type { CustomSerializableUnpacker } from './memory/customSerializableUnpacker'; +import type { ShareableUnpacker } from './memory/shareableUnpacker'; import type { SynchronizableUnpacker } from './memory/synchronizableUnpacker'; import type { CustomSerializationRegistry } from './memory/types'; import type { Queue } from './runLoop/workletRuntime/taskQueue'; @@ -17,7 +18,7 @@ declare global { Record; var _toString: (value: unknown) => string; - var __workletsModuleProxy: WorkletsModuleProxy | undefined; + var __workletsModuleProxy: WorkletsModuleProxy; var _WORKLETS_BUNDLE_MODE: boolean | undefined; var _WORKLETS_VERSION_CPP: string | undefined; var _WORKLETS_VERSION_JS: string | undefined; @@ -81,6 +82,7 @@ declare global { var __hasNativeState: (value: object) => boolean; /** Only in Debug builds. */ var __isHostObject: (value: object) => boolean; + var __shareableUnpacker: ShareableUnpacker; interface NodeRequire { resolveWeak(id: string): number; getModules(): Map; diff --git a/packages/react-native-worklets/src/runtimes.native.ts b/packages/react-native-worklets/src/runtimes.native.ts index bca57ac5c15..1c01b076f35 100644 --- a/packages/react-native-worklets/src/runtimes.native.ts +++ b/packages/react-native-worklets/src/runtimes.native.ts @@ -196,3 +196,7 @@ export function runOnRuntime( type WorkletRuntimeConfigInternal = WorkletRuntimeConfig & { initializer?: WorkletFunction<[], void>; }; + +export function getUIWorkletRuntime(): WorkletRuntime { + return WorkletsModule.getUIWorkletRuntime(); +} diff --git a/packages/react-native-worklets/src/runtimes.ts b/packages/react-native-worklets/src/runtimes.ts index 428b5e07772..f51a7a6414d 100644 --- a/packages/react-native-worklets/src/runtimes.ts +++ b/packages/react-native-worklets/src/runtimes.ts @@ -38,3 +38,9 @@ export function scheduleOnRuntime( export function scheduleOnRuntime(): never { throw new WorkletsError('`scheduleOnRuntime` is not supported on web.'); } + +export function getUIWorkletRuntime(): WorkletRuntime; + +export function getUIWorkletRuntime(): never { + throw new WorkletsError('`getUIWorkletRuntime` is not supported on web.'); +} diff --git a/packages/react-native-worklets/src/threads.native.ts b/packages/react-native-worklets/src/threads.native.ts index 5e59a65f667..3a9a667c6b8 100644 --- a/packages/react-native-worklets/src/threads.native.ts +++ b/packages/react-native-worklets/src/threads.native.ts @@ -187,7 +187,7 @@ export function runOnUISync( worklet: WorkletFunction, ...args: Args ): ReturnValue { - return WorkletsModule.executeOnUIRuntimeSync( + return WorkletsModule.runOnUISync( createSerializable(() => { 'worklet'; const result = worklet(...args);