From a2f1dd6ca3eafbe6c392fac33c1b0eab9e6e9d88 Mon Sep 17 00:00:00 2001 From: Krzysztof Magiera Date: Fri, 2 Dec 2022 14:13:48 +0100 Subject: [PATCH 01/23] Stashing work --- .../NativeModules/NativeReanimatedModule.cpp | 22 ++-- .../NativeModules/NativeReanimatedModule.h | 4 +- .../NativeReanimatedModuleSpec.h | 4 +- .../ReanimatedHermesRuntime.cpp | 19 ++++ .../ReanimatedHermesRuntime.h | 2 +- Common/cpp/SharedItems/Shareables.h | 3 +- Common/cpp/Tools/RuntimeDecorator.cpp | 6 +- Common/cpp/Tools/RuntimeDecorator.h | 2 +- plugin.js | 22 +++- .../NativeReanimated/NativeReanimated.ts | 13 ++- src/reanimated2/core.ts | 61 +--------- src/reanimated2/initializers.ts | 106 ++++++++++++++++++ 12 files changed, 181 insertions(+), 83 deletions(-) create mode 100644 src/reanimated2/initializers.ts diff --git a/Common/cpp/NativeModules/NativeReanimatedModule.cpp b/Common/cpp/NativeModules/NativeReanimatedModule.cpp index b9a31a82fe5a..13d47b7791cd 100644 --- a/Common/cpp/NativeModules/NativeReanimatedModule.cpp +++ b/Common/cpp/NativeModules/NativeReanimatedModule.cpp @@ -58,8 +58,12 @@ NativeReanimatedModule::NativeReanimatedModule( platformDepMethodsHolder.configurePropsFunction) #endif { - auto requestAnimationFrame = [=](FrameCallback callback) { - frameCallbacks.push_back(callback); + auto requestAnimationFrame = [=](jsi::Runtime &rt, const jsi::Value &fn) { + auto jsFunction = std::make_shared(rt, fn); + frameCallbacks.push_back([=](double timestamp) { + jsi::Runtime &rt = *runtimeHelper->uiRuntime(); + runtimeHelper->callGuard->call(rt, *jsFunction, jsi::Value(timestamp)); + }); maybeRequestRender(); }; @@ -174,20 +178,23 @@ NativeReanimatedModule::NativeReanimatedModule( void NativeReanimatedModule::installCoreFunctions( jsi::Runtime &rt, - const jsi::Value &valueUnpacker, - const jsi::Value &layoutAnimationStartFunction) { + const jsi::Value &callGuard, + const jsi::Value &valueUnpacker) { if (!runtimeHelper) { // initialize runtimeHelper here if not already present. We expect only one // instace of the helper to exists. runtimeHelper = std::make_shared(&rt, this->runtime.get(), scheduler); } + runtimeHelper->callGuard = + std::make_unique(runtimeHelper.get(), callGuard); runtimeHelper->valueUnpacker = - std::make_shared(runtimeHelper.get(), valueUnpacker); + std::make_unique(runtimeHelper.get(), valueUnpacker); } NativeReanimatedModule::~NativeReanimatedModule() { if (runtimeHelper) { + runtimeHelper->callGuard = nullptr; runtimeHelper->valueUnpacker = nullptr; // event handler registry stores some JSI values from UI runtime, so it has // to go away before we tear down the runtime @@ -205,11 +212,10 @@ void NativeReanimatedModule::scheduleOnUI( assert( shareableWorklet->valueType() == Shareable::WorkletType && "only worklets can be scheduled to run on UI"); - auto uiRuntime = runtimeHelper->uiRuntime(); frameCallbacks.push_back([=](double timestamp) { - jsi::Runtime &rt = *uiRuntime; + jsi::Runtime &rt = *runtimeHelper->uiRuntime(); auto workletValue = shareableWorklet->getJSValue(rt); - workletValue.asObject(rt).asFunction(rt).call(rt); + runtimeHelper->callGuard->call(rt, workletValue); }); maybeRequestRender(); } diff --git a/Common/cpp/NativeModules/NativeReanimatedModule.h b/Common/cpp/NativeModules/NativeReanimatedModule.h index 1ae695b9c95e..c16d6077493e 100644 --- a/Common/cpp/NativeModules/NativeReanimatedModule.h +++ b/Common/cpp/NativeModules/NativeReanimatedModule.h @@ -49,8 +49,8 @@ class NativeReanimatedModule : public NativeReanimatedModuleSpec, void installCoreFunctions( jsi::Runtime &rt, - const jsi::Value &workletMaker, - const jsi::Value &layoutAnimationStartFunction) override; + const jsi::Value &callGuard, + const jsi::Value &workletMaker) override; jsi::Value makeShareableClone(jsi::Runtime &rt, const jsi::Value &value) override; diff --git a/Common/cpp/NativeModules/NativeReanimatedModuleSpec.h b/Common/cpp/NativeModules/NativeReanimatedModuleSpec.h index 3e7393c90223..387004d050c0 100644 --- a/Common/cpp/NativeModules/NativeReanimatedModuleSpec.h +++ b/Common/cpp/NativeModules/NativeReanimatedModuleSpec.h @@ -24,8 +24,8 @@ class JSI_EXPORT NativeReanimatedModuleSpec : public TurboModule { public: virtual void installCoreFunctions( jsi::Runtime &rt, - const jsi::Value &valueUnpacker, - const jsi::Value &layoutAnimationStartFunction) = 0; + const jsi::Value &callGuard, + const jsi::Value &valueUnpacker) = 0; // SharedValue virtual jsi::Value makeShareableClone( diff --git a/Common/cpp/ReanimatedRuntime/ReanimatedHermesRuntime.cpp b/Common/cpp/ReanimatedRuntime/ReanimatedHermesRuntime.cpp index bc9f1e5795c2..334ce032307a 100644 --- a/Common/cpp/ReanimatedRuntime/ReanimatedHermesRuntime.cpp +++ b/Common/cpp/ReanimatedRuntime/ReanimatedHermesRuntime.cpp @@ -79,6 +79,25 @@ ReanimatedHermesRuntime::ReanimatedHermesRuntime( // that the thread was indeed `quit` before jsQueue->quitSynchronous(); #endif + + jsi::Value evalWithSourceMap = jsi::Function::createFromHostFunction( + *runtime_, + jsi::PropNameID::forAscii(*runtime_, "evalWithSourceMap"), + 3, + [](jsi::Runtime &rt, + const jsi::Value &thisValue, + const jsi::Value *args, + size_t count) -> jsi::Value { + auto codeBuffer = std::make_shared( + args[0].asString(rt).utf8(rt)); + auto sourceMapJSONBuffer = std::make_shared( + args[1].asString(rt).utf8(rt)); + return dynamic_cast(rt) + .evaluateJavaScriptWithSourceMap( + codeBuffer, sourceMapJSONBuffer, args[2].asString(rt).utf8(rt)); + }); + runtime_->global().setProperty( + *runtime_, "evalWithSourceMap", evalWithSourceMap); } ReanimatedHermesRuntime::~ReanimatedHermesRuntime() { diff --git a/Common/cpp/ReanimatedRuntime/ReanimatedHermesRuntime.h b/Common/cpp/ReanimatedRuntime/ReanimatedHermesRuntime.h index 526cc1ab690f..aac9dec16404 100644 --- a/Common/cpp/ReanimatedRuntime/ReanimatedHermesRuntime.h +++ b/Common/cpp/ReanimatedRuntime/ReanimatedHermesRuntime.h @@ -116,7 +116,7 @@ class ReanimatedHermesRuntime ~ReanimatedHermesRuntime(); private: - std::shared_ptr runtime_; + std::unique_ptr runtime_; ReanimatedReentrancyCheck reentrancyCheck_; }; diff --git a/Common/cpp/SharedItems/Shareables.h b/Common/cpp/SharedItems/Shareables.h index 0b9ff0e671a8..75ec4408b463 100644 --- a/Common/cpp/SharedItems/Shareables.h +++ b/Common/cpp/SharedItems/Shareables.h @@ -36,7 +36,8 @@ class JSRuntimeHelper { : rnRuntime_(rnRuntime), uiRuntime_(uiRuntime), scheduler_(scheduler) {} volatile bool uiRuntimeDestroyed; - std::shared_ptr valueUnpacker; + std::unique_ptr callGuard; + std::unique_ptr valueUnpacker; inline jsi::Runtime *uiRuntime() const { return uiRuntime_; diff --git a/Common/cpp/Tools/RuntimeDecorator.cpp b/Common/cpp/Tools/RuntimeDecorator.cpp index 1ed79a0a26cd..9177a314e2cf 100644 --- a/Common/cpp/Tools/RuntimeDecorator.cpp +++ b/Common/cpp/Tools/RuntimeDecorator.cpp @@ -226,11 +226,7 @@ void RuntimeDecorator::decorateUIRuntime( const jsi::Value &thisValue, const jsi::Value *args, const size_t count) -> jsi::Value { - auto fun = - std::make_shared(args[0].asObject(rt).asFunction(rt)); - requestFrame([&rt, fun](double timestampMs) { - fun->call(rt, jsi::Value(timestampMs)); - }); + requestFrame(rt, std::move(args[0])); return jsi::Value::undefined(); }; jsi::Value requestAnimationFrame = jsi::Function::createFromHostFunction( diff --git a/Common/cpp/Tools/RuntimeDecorator.h b/Common/cpp/Tools/RuntimeDecorator.h index 5b0f9b2ac2a3..38f41d89abe4 100644 --- a/Common/cpp/Tools/RuntimeDecorator.h +++ b/Common/cpp/Tools/RuntimeDecorator.h @@ -11,7 +11,7 @@ using namespace facebook; namespace reanimated { -using RequestFrameFunction = std::function)>; +using RequestFrameFunction = std::function; using ScheduleOnJSFunction = std::function; using MakeShareableCloneFunction = diff --git a/plugin.js b/plugin.js index b4626f361af9..ea2e37a0a2c4 100644 --- a/plugin.js +++ b/plugin.js @@ -4,6 +4,7 @@ const hash = require('string-hash-64'); const traverse = require('@babel/traverse').default; const { transformSync } = require('@babel/core'); const fs = require('fs'); +const convertSourceMap = require('convert-source-map'); /** * 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) */ @@ -72,11 +73,13 @@ const globals = new Set([ '_makeShareableClone', '_updateDataSynchronously', 'eval', + 'evalWithSourceMap', '_updatePropsPaper', '_updatePropsFabric', '_removeShadowNodeFromRegistry', 'RegExp', 'Error', + 'ErrorUtils', 'global', '_measure', '_scrollTo', @@ -384,7 +387,7 @@ function buildWorkletString(t, fun, closureVariables, name, inputMap) { const transformed = transformSync(code, { plugins: [prependClosureVariablesIfNecessary()], compact: !shouldGenerateSourceMap(), - sourceMaps: shouldGenerateSourceMap() ? 'inline' : false, + sourceMaps: shouldGenerateSourceMap() ? true : false, inputSourceMap: inputMap, ast: false, babelrc: false, @@ -392,7 +395,9 @@ function buildWorkletString(t, fun, closureVariables, name, inputMap) { comments: false, }); - return transformed.code; + const sourceMapString = convertSourceMap.fromObject(transformed.map).toJSON(); + + return [transformed.code, sourceMapString]; } function makeWorkletName(t, fun) { @@ -514,7 +519,7 @@ function makeWorklet(t, fun, state) { funExpression = clone; } - const funString = buildWorkletString( + const [funString, sourceMapString] = buildWorkletString( t, transformed.ast, variables, @@ -566,6 +571,17 @@ function makeWorklet(t, fun, state) { t.numericLiteral(workletHash) ) ), + t.expressionStatement( + t.assignmentExpression( + '=', + t.memberExpression( + privateFunctionId, + t.identifier('__sourceMap'), + false + ), + t.stringLiteral(sourceMapString) + ) + ), t.expressionStatement( t.assignmentExpression( '=', diff --git a/src/reanimated2/NativeReanimated/NativeReanimated.ts b/src/reanimated2/NativeReanimated/NativeReanimated.ts index 660fa6cc90f5..049523849604 100644 --- a/src/reanimated2/NativeReanimated/NativeReanimated.ts +++ b/src/reanimated2/NativeReanimated/NativeReanimated.ts @@ -47,8 +47,17 @@ export class NativeReanimated { throw new Error('stub implementation, used on the web only'); } - installCoreFunctions(valueUnpacker: (value: T) => T): void { - return this.InnerNativeModule.installCoreFunctions(valueUnpacker); + installCoreFunctions( + callGuard: , U>( + fn: (...args: T) => U, + ...args: T + ) => void, + valueUnpacker: (value: T) => T + ): void { + return this.InnerNativeModule.installCoreFunctions( + callGuard, + valueUnpacker + ); } makeShareableClone(value: T): ShareableRef { diff --git a/src/reanimated2/core.ts b/src/reanimated2/core.ts index 68bace96231a..20e288aab581 100644 --- a/src/reanimated2/core.ts +++ b/src/reanimated2/core.ts @@ -6,13 +6,13 @@ import { makeShareableCloneRecursive, makeShareable as makeShareableUnwrapped, } from './shareables'; -import { runOnUI, runOnJS } from './threads'; import { startMapper as startMapperUnwrapped } from './mappers'; import { makeMutable as makeMutableUnwrapped, makeRemote as makeRemoteUnwrapped, } from './mutables'; import { LayoutAnimationFunction } from './layoutReanimation'; +import { initializeUIRuntime } from './initializers'; export { stopMapper } from './mappers'; export { runOnJS, runOnUI } from './threads'; @@ -114,49 +114,6 @@ export function getViewProp(viewTag: string, propName: string): Promise { }); } -function valueUnpacker(objectToUnpack: any, category?: string): any { - 'worklet'; - let workletsCache = global.__workletsCache; - let handleCache = global.__handleCache; - if (workletsCache === undefined) { - // init - workletsCache = global.__workletsCache = new Map(); - handleCache = global.__handleCache = new WeakMap(); - } - if (objectToUnpack.__workletHash) { - let workletFun = workletsCache.get(objectToUnpack.__workletHash); - if (workletFun === undefined) { - // eslint-disable-next-line no-eval - workletFun = eval('(' + objectToUnpack.asString + '\n)') as ( - ...args: any[] - ) => any; - workletsCache.set(objectToUnpack.__workletHash, workletFun); - } - const functionInstance = workletFun.bind(objectToUnpack); - objectToUnpack._recur = functionInstance; - return functionInstance; - } else if (objectToUnpack.__init) { - let value = handleCache!.get(objectToUnpack); - if (value === undefined) { - value = objectToUnpack.__init(); - handleCache!.set(objectToUnpack, value); - } - return value; - } else if (category === 'RemoteFunction') { - const fun = () => { - throw new Error(`Tried to synchronously call a non-worklet function on the UI thread. - -Possible solutions are: - a) If you want to synchronously execute this method, mark it as a worklet - b) If you want to execute this function on the JS thread, wrap it using \`runOnJS\``); - }; - fun.__remoteFunction = objectToUnpack; - return fun; - } else { - throw new Error('data type not recognized by unpack method'); - } -} - export function registerEventHandler( eventHash: string, eventHandler: (event: T) => void @@ -199,21 +156,9 @@ export function unregisterSensor(listenerId: number): void { return NativeReanimatedModule.unregisterSensor(listenerId); } -NativeReanimatedModule.installCoreFunctions(valueUnpacker); - +// initialize UI runtime if applicable if (!isWeb() && isConfigured()) { - const capturableConsole = console; - runOnUI(() => { - 'worklet'; - const console = { - debug: runOnJS(capturableConsole.debug), - log: runOnJS(capturableConsole.log), - warn: runOnJS(capturableConsole.warn), - error: runOnJS(capturableConsole.error), - info: runOnJS(capturableConsole.info), - }; - _setGlobalConsole(console); - })(); + initializeUIRuntime(); } type FeaturesConfig = { diff --git a/src/reanimated2/initializers.ts b/src/reanimated2/initializers.ts new file mode 100644 index 000000000000..4651e7a0627a --- /dev/null +++ b/src/reanimated2/initializers.ts @@ -0,0 +1,106 @@ +import NativeReanimatedModule from './NativeReanimated'; +import { runOnUI, runOnJS } from './threads'; + +function callGuard, U>( + fn: (...args: T) => U, + ...args: T +): void { + 'worklet'; + try { + fn(...args); + } catch (e) { + if (global.ErrorUtils) { + global.ErrorUtils.reportFatalError(e); + } else { + throw e; + } + } +} + +function valueUnpacker(objectToUnpack: any, category?: string): any { + 'worklet'; + let workletsCache = global.__workletsCache; + let handleCache = global.__handleCache; + if (workletsCache === undefined) { + // init + workletsCache = global.__workletsCache = new Map(); + handleCache = global.__handleCache = new WeakMap(); + } + if (objectToUnpack.__workletHash) { + let workletFun = workletsCache.get(objectToUnpack.__workletHash); + if (workletFun === undefined) { + // eslint-disable-next-line no-eval + workletFun = evalWithSourceMap( + '(' + objectToUnpack.asString + '\n)', + objectToUnpack.__sourceMap, + objectToUnpack.__location + ) as (...args: any[]) => any; + workletsCache.set(objectToUnpack.__workletHash, workletFun); + } + const functionInstance = workletFun.bind(objectToUnpack); + objectToUnpack._recur = functionInstance; + return functionInstance; + } else if (objectToUnpack.__init) { + let value = handleCache!.get(objectToUnpack); + if (value === undefined) { + value = objectToUnpack.__init(); + handleCache!.set(objectToUnpack, value); + } + return value; + } else if (category === 'RemoteFunction') { + const fun = () => { + throw new Error(`Tried to synchronously call a non-worklet function on the UI thread. + +Possible solutions are: + a) If you want to synchronously execute this method, mark it as a worklet + b) If you want to execute this function on the JS thread, wrap it using \`runOnJS\``); + }; + fun.__remoteFunction = objectToUnpack; + return fun; + } else { + throw new Error('data type not recognized by unpack method'); + } +} + +function reportFatalErrorOnJS({ + message, + stack, +}: { + message: string; + stack?: string; +}) { + const error = new Error(); + error.message = message; + error.stack = stack; + error.name = 'ReanimatedError'; + error.jsEngine = 'reanimated'; + ErrorUtils.reportFatalError(error); +} + +export function initializeUIRuntime() { + NativeReanimatedModule.installCoreFunctions(callGuard, valueUnpacker); + + const capturableConsole = console; + runOnUI(() => { + 'worklet'; + // setup error handler + global.ErrorUtils = { + reportFatalError: (error: Error) => { + runOnJS(reportFatalErrorOnJS)({ + message: error.message, + stack: error.stack, + }); + }, + }; + + // setup console + const console = { + debug: runOnJS(capturableConsole.debug), + log: runOnJS(capturableConsole.log), + warn: runOnJS(capturableConsole.warn), + error: runOnJS(capturableConsole.error), + info: runOnJS(capturableConsole.info), + }; + _setGlobalConsole(console); + })(); +} From c6651dbd4b239900aad3f68fb4dd4863287f8f58 Mon Sep 17 00:00:00 2001 From: Krzysztof Magiera Date: Sat, 3 Dec 2022 01:15:15 +0100 Subject: [PATCH 02/23] Some updates --- plugin.js | 13 +++++++++---- src/reanimated2/core.ts | 1 - src/reanimated2/globals.d.ts | 16 ++++++++++++++++ src/reanimated2/initializers.ts | 3 ++- src/reanimated2/js-reanimated/JSReanimated.ts | 12 ++++++++---- 5 files changed, 35 insertions(+), 10 deletions(-) diff --git a/plugin.js b/plugin.js index ea2e37a0a2c4..c4d4d6024a71 100644 --- a/plugin.js +++ b/plugin.js @@ -387,7 +387,7 @@ function buildWorkletString(t, fun, closureVariables, name, inputMap) { const transformed = transformSync(code, { plugins: [prependClosureVariablesIfNecessary()], compact: !shouldGenerateSourceMap(), - sourceMaps: shouldGenerateSourceMap() ? true : false, + sourceMaps: shouldGenerateSourceMap(), inputSourceMap: inputMap, ast: false, babelrc: false, @@ -395,9 +395,14 @@ function buildWorkletString(t, fun, closureVariables, name, inputMap) { comments: false, }); - const sourceMapString = convertSourceMap.fromObject(transformed.map).toJSON(); + const sourceMap = convertSourceMap.fromObject(transformed.map).toObject(); + // sourcesContent field contains a full source code of the file which contains the worklet + // and is not needed by the source map interpreter in order to symbolicate a stack trace. + // Therefore, we remove it to reduce the bandwith and avoid sending it potentially multiple times + // in files that contain multiple worklets. + delete sourceMap.sourcesContent; - return [transformed.code, sourceMapString]; + return [transformed.code, JSON.stringify(sourceMap)]; } function makeWorkletName(t, fun) { @@ -410,7 +415,7 @@ function makeWorkletName(t, fun) { if (t.isFunctionExpression(fun) && t.isIdentifier(fun.node.id)) { return fun.node.id.name; } - return '_f'; // fallback for ArrowFunctionExpression and unnamed FunctionExpression + return 'anonymous'; // fallback for ArrowFunctionExpression and unnamed FunctionExpression } function makeWorklet(t, fun, state) { diff --git a/src/reanimated2/core.ts b/src/reanimated2/core.ts index 20e288aab581..e671b97efc98 100644 --- a/src/reanimated2/core.ts +++ b/src/reanimated2/core.ts @@ -1,4 +1,3 @@ -/* global _setGlobalConsole */ import NativeReanimatedModule from './NativeReanimated'; import { nativeShouldBeMock, shouldBeUseWeb, isWeb } from './PlatformChecker'; import { BasicWorkletFunction, Value3D, ValueRotation } from './commonTypes'; diff --git a/src/reanimated2/globals.d.ts b/src/reanimated2/globals.d.ts index f89a69c1ccff..387d0ce8bdc2 100644 --- a/src/reanimated2/globals.d.ts +++ b/src/reanimated2/globals.d.ts @@ -20,6 +20,11 @@ declare global { const _eventTimestamp: number; const __reanimatedModuleProxy: NativeReanimated; const _setGlobalConsole: (console?: ReanimatedConsole) => void; + const evalWithSourceMap: ( + js: string, + sourceMap: string, + sourceURL: string + ) => any; const _log: (s: string) => void; const _getCurrentTime: () => number; const _getTimestamp: () => number; @@ -65,6 +70,9 @@ declare global { const ReanimatedDataMock: { now: () => number; }; + const ErrorUtils: { + reportFatalError: (error: Error) => void; + }; const _frameCallbackRegistry: FrameCallbackRegistryUI; namespace NodeJS { @@ -76,6 +84,11 @@ declare global { _eventTimestamp: number; __reanimatedModuleProxy: NativeReanimated; _setGlobalConsole: (console?: ReanimatedConsole) => void; + evalWithSourceMap: ( + js: string, + sourceMap: string, + sourceURL: string + ) => any; _log: (s: string) => void; _getCurrentTime: () => number; _getTimestamp: () => number; @@ -118,6 +131,9 @@ declare global { ReanimatedDataMock: { now: () => number; }; + ErrorUtils: { + reportFatalError: (error: Error) => void; + }; _frameCallbackRegistry: FrameCallbackRegistryUI; __workletsCache?: Map any>; __handleCache?: WeakMap; diff --git a/src/reanimated2/initializers.ts b/src/reanimated2/initializers.ts index 4651e7a0627a..fe8d5bdc37da 100644 --- a/src/reanimated2/initializers.ts +++ b/src/reanimated2/initializers.ts @@ -73,8 +73,9 @@ function reportFatalErrorOnJS({ error.message = message; error.stack = stack; error.name = 'ReanimatedError'; + // @ts-ignore React Native's ErrorUtils implementation extends the Error type with jsEngine field error.jsEngine = 'reanimated'; - ErrorUtils.reportFatalError(error); + global.ErrorUtils.reportFatalError(error); } export function initializeUIRuntime() { diff --git a/src/reanimated2/js-reanimated/JSReanimated.ts b/src/reanimated2/js-reanimated/JSReanimated.ts index 977dc5a2c0cb..04ee23bf217d 100644 --- a/src/reanimated2/js-reanimated/JSReanimated.ts +++ b/src/reanimated2/js-reanimated/JSReanimated.ts @@ -3,8 +3,6 @@ import { ShareableRef } from '../commonTypes'; import { isJest } from '../PlatformChecker'; export default class JSReanimated extends NativeReanimated { - _valueUnpacker?: (value: T) => void = undefined; - constructor() { super(false); if (isJest()) { @@ -21,8 +19,14 @@ export default class JSReanimated extends NativeReanimated { return { __hostObjectShareableJSRef: value }; } - installCoreFunctions(valueUnpacker: (value: T) => T): void { - this._valueUnpacker = valueUnpacker; + installCoreFunctions( + _callGuard: , U>( + fn: (...args: T) => U, + ...args: T + ) => void, + _valueUnpacker: (value: T) => T + ): void { + // noop } scheduleOnUI(worklet: ShareableRef) { From 9e1537fa32e1b07da9e365f37f6cfe21037036b0 Mon Sep 17 00:00:00 2001 From: Krzysztof Magiera Date: Tue, 6 Dec 2022 16:03:03 +0100 Subject: [PATCH 03/23] New approach that supports JSC --- .../ReanimatedHermesRuntime.cpp | 16 ++++++--- Common/cpp/Tools/RuntimeDecorator.cpp | 27 +++++++++++--- plugin.js | 33 +++++++++++++++++ src/reanimated2/globals.d.ts | 8 ++--- src/reanimated2/initializers.ts | 35 ++++++++++++++++--- src/reanimated2/shareables.ts | 8 +++++ 6 files changed, 110 insertions(+), 17 deletions(-) diff --git a/Common/cpp/ReanimatedRuntime/ReanimatedHermesRuntime.cpp b/Common/cpp/ReanimatedRuntime/ReanimatedHermesRuntime.cpp index 334ce032307a..67f6323d6133 100644 --- a/Common/cpp/ReanimatedRuntime/ReanimatedHermesRuntime.cpp +++ b/Common/cpp/ReanimatedRuntime/ReanimatedHermesRuntime.cpp @@ -88,13 +88,19 @@ ReanimatedHermesRuntime::ReanimatedHermesRuntime( const jsi::Value &thisValue, const jsi::Value *args, size_t count) -> jsi::Value { - auto codeBuffer = std::make_shared( + auto code = std::make_shared( args[0].asString(rt).utf8(rt)); - auto sourceMapJSONBuffer = std::make_shared( - args[1].asString(rt).utf8(rt)); + std::string sourceURL; + if (count > 1 && args[1].isString()) { + sourceURL = args[1].asString(rt).utf8(rt); + } + std::shared_ptr sourceMap; + if (count > 2 && args[2].isString()) { + sourceMap = std::make_shared( + args[2].asString(rt).utf8(rt)); + } return dynamic_cast(rt) - .evaluateJavaScriptWithSourceMap( - codeBuffer, sourceMapJSONBuffer, args[2].asString(rt).utf8(rt)); + .evaluateJavaScriptWithSourceMap(code, sourceMap, sourceURL); }); runtime_->global().setProperty( *runtime_, "evalWithSourceMap", evalWithSourceMap); diff --git a/Common/cpp/Tools/RuntimeDecorator.cpp b/Common/cpp/Tools/RuntimeDecorator.cpp index 9177a314e2cf..3a9db1471044 100644 --- a/Common/cpp/Tools/RuntimeDecorator.cpp +++ b/Common/cpp/Tools/RuntimeDecorator.cpp @@ -30,11 +30,30 @@ void RuntimeDecorator::decorateRuntime( rt.global().setProperty( rt, "_LABEL", jsi::String::createFromAscii(rt, label)); - jsi::Object dummyGlobal(rt); - dummyGlobal.setProperty(rt, "gc", rt.global().getProperty(rt, "gc")); - rt.global().setProperty(rt, "global", dummyGlobal); + rt.global().setProperty(rt, "global", rt.global()); - rt.global().setProperty(rt, "jsThis", jsi::Value::undefined()); + auto evalWithSourceUrl = [](jsi::Runtime &rt, + const jsi::Value &thisValue, + const jsi::Value *args, + size_t count) -> jsi::Value { + auto code = std::make_shared( + args[0].asString(rt).utf8(rt)); + std::string url; + if (count > 1 && args[1].isString()) { + url = args[1].asString(rt).utf8(rt); + } + + return rt.evaluateJavaScript(code, url); + }; + + rt.global().setProperty( + rt, + "evalWithSourceUrl", + jsi::Function::createFromHostFunction( + rt, + jsi::PropNameID::forAscii(rt, "evalWithSourceUrl"), + 1, + evalWithSourceUrl)); auto callback = [](jsi::Runtime &rt, const jsi::Value &thisValue, diff --git a/plugin.js b/plugin.js index c4d4d6024a71..23f09f8de9e0 100644 --- a/plugin.js +++ b/plugin.js @@ -547,6 +547,11 @@ function makeWorklet(t, fun, state) { } } + let lineOffset = 1; + if (closure.size > 0) { + lineOffset -= closure.size + 2; + } + const statements = [ t.variableDeclaration('const', [ t.variableDeclarator(privateFunctionId, funExpression), @@ -600,6 +605,34 @@ function makeWorklet(t, fun, state) { ), ]; + if (!isRelease()) { + statements.unshift( + t.variableDeclaration('const', [ + t.variableDeclarator( + t.identifier('_e'), + t.arrayExpression([ + t.newExpression(t.identifier('Error'), []), + t.numericLiteral(lineOffset), + t.numericLiteral(-25), + ]) + ), + ]) + ); + statements.push( + t.expressionStatement( + t.assignmentExpression( + '=', + t.memberExpression( + privateFunctionId, + t.identifier('__stackDetails'), + false + ), + t.identifier('_e') + ) + ) + ); + } + if (options && options.optFlags) { statements.push( t.expressionStatement( diff --git a/src/reanimated2/globals.d.ts b/src/reanimated2/globals.d.ts index 387d0ce8bdc2..e731909d7489 100644 --- a/src/reanimated2/globals.d.ts +++ b/src/reanimated2/globals.d.ts @@ -22,8 +22,8 @@ declare global { const _setGlobalConsole: (console?: ReanimatedConsole) => void; const evalWithSourceMap: ( js: string, - sourceMap: string, - sourceURL: string + sourceURL: string, + sourceMap: string ) => any; const _log: (s: string) => void; const _getCurrentTime: () => number; @@ -86,8 +86,8 @@ declare global { _setGlobalConsole: (console?: ReanimatedConsole) => void; evalWithSourceMap: ( js: string, - sourceMap: string, - sourceURL: string + sourceURL: string, + sourceMap: string ) => any; _log: (s: string) => void; _getCurrentTime: () => number; diff --git a/src/reanimated2/initializers.ts b/src/reanimated2/initializers.ts index fe8d5bdc37da..0051ec7be5f3 100644 --- a/src/reanimated2/initializers.ts +++ b/src/reanimated2/initializers.ts @@ -30,10 +30,12 @@ function valueUnpacker(objectToUnpack: any, category?: string): any { let workletFun = workletsCache.get(objectToUnpack.__workletHash); if (workletFun === undefined) { // eslint-disable-next-line no-eval - workletFun = evalWithSourceMap( + const evalFn = + global.evalWithSourceMap || global.evalWithSourceUrl || eval; + workletFun = evalFn( '(' + objectToUnpack.asString + '\n)', - objectToUnpack.__sourceMap, - objectToUnpack.__location + objectToUnpack.__sourceURL || `worklet_${objectToUnpack.__workletHash}`, + objectToUnpack.__sourceMap ) as (...args: any[]) => any; workletsCache.set(objectToUnpack.__workletHash, workletFun); } @@ -62,6 +64,31 @@ Possible solutions are: } } +function getBundleOffset(error: Error) { + const frame = error.stack.split('\n')[0]; + const [, file, line, col] = /@(.*):(\d+):(\d+)/.exec(frame); + return [file, Number(line), Number(col)]; +} + +function processStack(stack: string): string { + const workletStackEntries = stack.match(/worklet_(\d+):(\d+):(\d+)/g); + let result = stack; + workletStackEntries?.forEach((match) => { + const [_, hash, origLine, origCol] = match.split(/:|_/).map(Number); + if (!global.__workletStackDetails.has(hash)) { + return; + } + const [error, lineOffset, colOffset] = + global.__workletStackDetails.get(hash); + const [bundleFile, bundleLine, bundleCol] = getBundleOffset(error); + const line = origLine + bundleLine + lineOffset; + const col = origCol + bundleCol + colOffset; + + result = result.replace(match, `${bundleFile}:${line}:${col}`); + }); + return result; +} + function reportFatalErrorOnJS({ message, stack, @@ -71,7 +98,7 @@ function reportFatalErrorOnJS({ }) { const error = new Error(); error.message = message; - error.stack = stack; + error.stack = processStack(stack); error.name = 'ReanimatedError'; // @ts-ignore React Native's ErrorUtils implementation extends the Error type with jsEngine field error.jsEngine = 'reanimated'; diff --git a/src/reanimated2/shareables.ts b/src/reanimated2/shareables.ts index 84aedc65643e..dab4fbe733e1 100644 --- a/src/reanimated2/shareables.ts +++ b/src/reanimated2/shareables.ts @@ -16,6 +16,8 @@ const _shareableCache = new WeakMap< // this is used to allow for a converted shareable to be passed to makeShareableClone const _shareableFlag = Symbol('shareable flag'); +global.__workletStackDetails = new Map(); + export function registerShareableMapping( shareable: any, shareableRef?: ShareableRef @@ -53,6 +55,12 @@ export function makeShareableCloneRecursive(value: any): ShareableRef { // this is a remote function toAdapt = value; } else { + if (__DEV__ && value.__workletHash !== undefined) { + global.__workletStackDetails.set( + value.__workletHash, + value.__stackDetails + ); + } toAdapt = {}; for (const [key, element] of Object.entries(value)) { toAdapt[key] = makeShareableCloneRecursive(element); From b8f2105ee718ca0fea31d723339505ad18a296c1 Mon Sep 17 00:00:00 2001 From: Krzysztof Magiera Date: Wed, 7 Dec 2022 00:49:18 +0100 Subject: [PATCH 04/23] Use different technique to support other JS engines --- Common/cpp/SharedItems/Shareables.cpp | 4 ++- plugin.js | 31 +++------------------ src/reanimated2/globals.d.ts | 2 ++ src/reanimated2/initializers.ts | 40 +++++++++++++++++++-------- src/reanimated2/shareables.ts | 5 ++-- 5 files changed, 39 insertions(+), 43 deletions(-) diff --git a/Common/cpp/SharedItems/Shareables.cpp b/Common/cpp/SharedItems/Shareables.cpp index 1cb4bbc3b9e0..bb17b40aac6e 100644 --- a/Common/cpp/SharedItems/Shareables.cpp +++ b/Common/cpp/SharedItems/Shareables.cpp @@ -13,7 +13,9 @@ CoreFunction::CoreFunction( rnFunction_ = std::make_unique(workletObject.asFunction(rt)); functionBody_ = workletObject.getProperty(rt, "asString").asString(rt).utf8(rt); - location_ = workletObject.getProperty(rt, "__location").asString(rt).utf8(rt); + location_ = "worklet_" + + std::to_string( + workletObject.getProperty(rt, "__workletHash").getNumber()); } std::unique_ptr &CoreFunction::getFunction(jsi::Runtime &rt) { diff --git a/plugin.js b/plugin.js index 23f09f8de9e0..bc178f6e6f93 100644 --- a/plugin.js +++ b/plugin.js @@ -399,8 +399,10 @@ function buildWorkletString(t, fun, closureVariables, name, inputMap) { // sourcesContent field contains a full source code of the file which contains the worklet // and is not needed by the source map interpreter in order to symbolicate a stack trace. // Therefore, we remove it to reduce the bandwith and avoid sending it potentially multiple times - // in files that contain multiple worklets. + // in files that contain multiple worklets. Along with sourcesContent, we also remove sources field + // as it isn't necessary for the stack trace symbolication. delete sourceMap.sourcesContent; + delete sourceMap.sources; return [transformed.code, JSON.stringify(sourceMap)]; } @@ -533,20 +535,6 @@ function makeWorklet(t, fun, state) { ); const workletHash = hash(funString); - let location = state.file.opts.filename; - if (state.opts && state.opts.relativeSourceLocation) { - const path = require('path'); - location = path.relative(state.cwd, location); - } - - const loc = fun && fun.node && fun.node.loc && fun.node.loc.start; - if (loc) { - const { line, column } = loc; - if (typeof line === 'number' && typeof column === 'number') { - location = `${location} (${line}:${column})`; - } - } - let lineOffset = 1; if (closure.size > 0) { lineOffset -= closure.size + 2; @@ -592,17 +580,6 @@ function makeWorklet(t, fun, state) { t.stringLiteral(sourceMapString) ) ), - t.expressionStatement( - t.assignmentExpression( - '=', - t.memberExpression( - privateFunctionId, - t.identifier('__location'), - false - ), - t.stringLiteral(location) - ) - ), ]; if (!isRelease()) { @@ -613,7 +590,7 @@ function makeWorklet(t, fun, state) { t.arrayExpression([ t.newExpression(t.identifier('Error'), []), t.numericLiteral(lineOffset), - t.numericLiteral(-25), + t.numericLiteral(-20), // the placement of opening bracket after Exeption in line that defined '_e' variable ]) ), ]) diff --git a/src/reanimated2/globals.d.ts b/src/reanimated2/globals.d.ts index e731909d7489..687894da8666 100644 --- a/src/reanimated2/globals.d.ts +++ b/src/reanimated2/globals.d.ts @@ -25,6 +25,7 @@ declare global { sourceURL: string, sourceMap: string ) => any; + const evalWithSourceUrl: (js: string, sourceURL: string) => any; const _log: (s: string) => void; const _getCurrentTime: () => number; const _getTimestamp: () => number; @@ -89,6 +90,7 @@ declare global { sourceURL: string, sourceMap: string ) => any; + evalWithSourceUrl: (js: string, sourceURL: string) => any; _log: (s: string) => void; _getCurrentTime: () => number; _getTimestamp: () => number; diff --git a/src/reanimated2/initializers.ts b/src/reanimated2/initializers.ts index 0051ec7be5f3..c27047b962f8 100644 --- a/src/reanimated2/initializers.ts +++ b/src/reanimated2/initializers.ts @@ -29,12 +29,11 @@ function valueUnpacker(objectToUnpack: any, category?: string): any { if (objectToUnpack.__workletHash) { let workletFun = workletsCache.get(objectToUnpack.__workletHash); if (workletFun === undefined) { - // eslint-disable-next-line no-eval const evalFn = - global.evalWithSourceMap || global.evalWithSourceUrl || eval; + global.evalWithSourceMap || global.evalWithSourceUrl || eval; // eslint-disable-line no-eval workletFun = evalFn( '(' + objectToUnpack.asString + '\n)', - objectToUnpack.__sourceURL || `worklet_${objectToUnpack.__workletHash}`, + `worklet_${objectToUnpack.__workletHash}`, objectToUnpack.__sourceMap ) as (...args: any[]) => any; workletsCache.set(objectToUnpack.__workletHash, workletFun); @@ -64,22 +63,39 @@ Possible solutions are: } } -function getBundleOffset(error: Error) { - const frame = error.stack.split('\n')[0]; - const [, file, line, col] = /@(.*):(\d+):(\d+)/.exec(frame); - return [file, Number(line), Number(col)]; +type StackDetails = [Error, number, number]; + +const _workletStackDetails = new Map(); + +export function registerWorkletStackDetails( + hash: number, + stackDetails: StackDetails +) { + _workletStackDetails.set(hash, stackDetails); +} + +function getBundleOffset(error: Error): [string, number, number] { + const frame = error.stack?.split('\n')?.[0]; + if (frame) { + const parsedFrame = /@(.*):(\d+):(\d+)/.exec(frame); + if (parsedFrame) { + const [, file, line, col] = parsedFrame; + return [file, Number(line), Number(col)]; + } + } + return ['unknown', 0, 0]; } function processStack(stack: string): string { const workletStackEntries = stack.match(/worklet_(\d+):(\d+):(\d+)/g); let result = stack; workletStackEntries?.forEach((match) => { - const [_, hash, origLine, origCol] = match.split(/:|_/).map(Number); - if (!global.__workletStackDetails.has(hash)) { + const [, hash, origLine, origCol] = match.split(/:|_/).map(Number); + const errorDetails = _workletStackDetails.get(hash); + if (!errorDetails) { return; } - const [error, lineOffset, colOffset] = - global.__workletStackDetails.get(hash); + const [error, lineOffset, colOffset] = errorDetails; const [bundleFile, bundleLine, bundleCol] = getBundleOffset(error); const line = origLine + bundleLine + lineOffset; const col = origCol + bundleCol + colOffset; @@ -98,7 +114,7 @@ function reportFatalErrorOnJS({ }) { const error = new Error(); error.message = message; - error.stack = processStack(stack); + error.stack = stack ? processStack(stack) : undefined; error.name = 'ReanimatedError'; // @ts-ignore React Native's ErrorUtils implementation extends the Error type with jsEngine field error.jsEngine = 'reanimated'; diff --git a/src/reanimated2/shareables.ts b/src/reanimated2/shareables.ts index dab4fbe733e1..459eaa6f94cb 100644 --- a/src/reanimated2/shareables.ts +++ b/src/reanimated2/shareables.ts @@ -1,6 +1,7 @@ import NativeReanimatedModule from './NativeReanimated'; import { ShareableRef } from './commonTypes'; import { shouldBeUseWeb } from './PlatformChecker'; +import { registerWorkletStackDetails } from './initializers'; // for web/chrome debugger/jest environments this file provides a stub implementation // where no shareable references are used. Instead, the objects themselves are used @@ -16,8 +17,6 @@ const _shareableCache = new WeakMap< // this is used to allow for a converted shareable to be passed to makeShareableClone const _shareableFlag = Symbol('shareable flag'); -global.__workletStackDetails = new Map(); - export function registerShareableMapping( shareable: any, shareableRef?: ShareableRef @@ -56,7 +55,7 @@ export function makeShareableCloneRecursive(value: any): ShareableRef { toAdapt = value; } else { if (__DEV__ && value.__workletHash !== undefined) { - global.__workletStackDetails.set( + registerWorkletStackDetails( value.__workletHash, value.__stackDetails ); From 8f363cfa1ab8d65e1f6ff9eb49a38ebf1e73021c Mon Sep 17 00:00:00 2001 From: Krzysztof Magiera Date: Thu, 8 Dec 2022 23:01:52 +0100 Subject: [PATCH 05/23] Moar updates --- Common/cpp/ReanimatedRuntime/ReanimatedHermesRuntime.cpp | 2 ++ Common/cpp/SharedItems/Shareables.cpp | 5 +++-- Common/cpp/Tools/RuntimeDecorator.cpp | 2 ++ Common/cpp/Tools/RuntimeDecorator.h | 3 ++- plugin.js | 4 +--- 5 files changed, 10 insertions(+), 6 deletions(-) diff --git a/Common/cpp/ReanimatedRuntime/ReanimatedHermesRuntime.cpp b/Common/cpp/ReanimatedRuntime/ReanimatedHermesRuntime.cpp index 67f6323d6133..1187aa5b4c2c 100644 --- a/Common/cpp/ReanimatedRuntime/ReanimatedHermesRuntime.cpp +++ b/Common/cpp/ReanimatedRuntime/ReanimatedHermesRuntime.cpp @@ -80,6 +80,7 @@ ReanimatedHermesRuntime::ReanimatedHermesRuntime( jsQueue->quitSynchronous(); #endif +#ifndef NDEBUG jsi::Value evalWithSourceMap = jsi::Function::createFromHostFunction( *runtime_, jsi::PropNameID::forAscii(*runtime_, "evalWithSourceMap"), @@ -104,6 +105,7 @@ ReanimatedHermesRuntime::ReanimatedHermesRuntime( }); runtime_->global().setProperty( *runtime_, "evalWithSourceMap", evalWithSourceMap); +#endif } ReanimatedHermesRuntime::~ReanimatedHermesRuntime() { diff --git a/Common/cpp/SharedItems/Shareables.cpp b/Common/cpp/SharedItems/Shareables.cpp index bb17b40aac6e..9451e508769b 100644 --- a/Common/cpp/SharedItems/Shareables.cpp +++ b/Common/cpp/SharedItems/Shareables.cpp @@ -13,9 +13,10 @@ CoreFunction::CoreFunction( rnFunction_ = std::make_unique(workletObject.asFunction(rt)); functionBody_ = workletObject.getProperty(rt, "asString").asString(rt).utf8(rt); - location_ = "worklet_" + + location_ = + "worklet_" + std::to_string( - workletObject.getProperty(rt, "__workletHash").getNumber()); + (int)workletObject.getProperty(rt, "__workletHash").getNumber()); } std::unique_ptr &CoreFunction::getFunction(jsi::Runtime &rt) { diff --git a/Common/cpp/Tools/RuntimeDecorator.cpp b/Common/cpp/Tools/RuntimeDecorator.cpp index 3a9db1471044..16f62744b6a9 100644 --- a/Common/cpp/Tools/RuntimeDecorator.cpp +++ b/Common/cpp/Tools/RuntimeDecorator.cpp @@ -32,6 +32,7 @@ void RuntimeDecorator::decorateRuntime( rt.global().setProperty(rt, "global", rt.global()); +#ifndef NDEBUG auto evalWithSourceUrl = [](jsi::Runtime &rt, const jsi::Value &thisValue, const jsi::Value *args, @@ -54,6 +55,7 @@ void RuntimeDecorator::decorateRuntime( jsi::PropNameID::forAscii(rt, "evalWithSourceUrl"), 1, evalWithSourceUrl)); +#endif auto callback = [](jsi::Runtime &rt, const jsi::Value &thisValue, diff --git a/Common/cpp/Tools/RuntimeDecorator.h b/Common/cpp/Tools/RuntimeDecorator.h index 38f41d89abe4..f1d8cddb2c12 100644 --- a/Common/cpp/Tools/RuntimeDecorator.h +++ b/Common/cpp/Tools/RuntimeDecorator.h @@ -11,7 +11,8 @@ using namespace facebook; namespace reanimated { -using RequestFrameFunction = std::function; +using RequestFrameFunction = + std::function; using ScheduleOnJSFunction = std::function; using MakeShareableCloneFunction = diff --git a/plugin.js b/plugin.js index bc178f6e6f93..e8a1ca4defde 100644 --- a/plugin.js +++ b/plugin.js @@ -399,10 +399,8 @@ function buildWorkletString(t, fun, closureVariables, name, inputMap) { // sourcesContent field contains a full source code of the file which contains the worklet // and is not needed by the source map interpreter in order to symbolicate a stack trace. // Therefore, we remove it to reduce the bandwith and avoid sending it potentially multiple times - // in files that contain multiple worklets. Along with sourcesContent, we also remove sources field - // as it isn't necessary for the stack trace symbolication. + // in files that contain multiple worklets. Along with sourcesContent. delete sourceMap.sourcesContent; - delete sourceMap.sources; return [transformed.code, JSON.stringify(sourceMap)]; } From 71257e347169a5154a8d830d26c71e0fc94bff9b Mon Sep 17 00:00:00 2001 From: Krzysztof Magiera Date: Tue, 13 Dec 2022 09:41:31 +0100 Subject: [PATCH 06/23] Update plugin.js Co-authored-by: Tomek Zawadzki --- plugin.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin.js b/plugin.js index e8a1ca4defde..7642ab1f6798 100644 --- a/plugin.js +++ b/plugin.js @@ -588,7 +588,7 @@ function makeWorklet(t, fun, state) { t.arrayExpression([ t.newExpression(t.identifier('Error'), []), t.numericLiteral(lineOffset), - t.numericLiteral(-20), // the placement of opening bracket after Exeption in line that defined '_e' variable + t.numericLiteral(-20), // the placement of opening bracket after Exception in line that defined '_e' variable ]) ), ]) From b346425ab7239d9bb097c0ce479d1af8243d93d9 Mon Sep 17 00:00:00 2001 From: Krzysztof Magiera Date: Tue, 13 Dec 2022 09:50:09 +0100 Subject: [PATCH 07/23] Responding to comments --- Common/cpp/ReanimatedRuntime/ReanimatedHermesRuntime.cpp | 4 ++-- Common/cpp/SharedItems/Shareables.cpp | 6 +++--- Common/cpp/Tools/RuntimeDecorator.cpp | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Common/cpp/ReanimatedRuntime/ReanimatedHermesRuntime.cpp b/Common/cpp/ReanimatedRuntime/ReanimatedHermesRuntime.cpp index 1187aa5b4c2c..ab8324f03860 100644 --- a/Common/cpp/ReanimatedRuntime/ReanimatedHermesRuntime.cpp +++ b/Common/cpp/ReanimatedRuntime/ReanimatedHermesRuntime.cpp @@ -80,7 +80,7 @@ ReanimatedHermesRuntime::ReanimatedHermesRuntime( jsQueue->quitSynchronous(); #endif -#ifndef NDEBUG +#ifdef DEBUG jsi::Value evalWithSourceMap = jsi::Function::createFromHostFunction( *runtime_, jsi::PropNameID::forAscii(*runtime_, "evalWithSourceMap"), @@ -105,7 +105,7 @@ ReanimatedHermesRuntime::ReanimatedHermesRuntime( }); runtime_->global().setProperty( *runtime_, "evalWithSourceMap", evalWithSourceMap); -#endif +#endif // DEBUG } ReanimatedHermesRuntime::~ReanimatedHermesRuntime() { diff --git a/Common/cpp/SharedItems/Shareables.cpp b/Common/cpp/SharedItems/Shareables.cpp index 9451e508769b..1b3035a8e059 100644 --- a/Common/cpp/SharedItems/Shareables.cpp +++ b/Common/cpp/SharedItems/Shareables.cpp @@ -13,10 +13,10 @@ CoreFunction::CoreFunction( rnFunction_ = std::make_unique(workletObject.asFunction(rt)); functionBody_ = workletObject.getProperty(rt, "asString").asString(rt).utf8(rt); - location_ = - "worklet_" + + location_ = "worklet_" + std::to_string( - (int)workletObject.getProperty(rt, "__workletHash").getNumber()); + (unsigned int)workletObject.getProperty(rt, "__workletHash") + .getNumber()); } std::unique_ptr &CoreFunction::getFunction(jsi::Runtime &rt) { diff --git a/Common/cpp/Tools/RuntimeDecorator.cpp b/Common/cpp/Tools/RuntimeDecorator.cpp index 16f62744b6a9..a8e7fcb91912 100644 --- a/Common/cpp/Tools/RuntimeDecorator.cpp +++ b/Common/cpp/Tools/RuntimeDecorator.cpp @@ -32,7 +32,7 @@ void RuntimeDecorator::decorateRuntime( rt.global().setProperty(rt, "global", rt.global()); -#ifndef NDEBUG +#ifdef DEBUG auto evalWithSourceUrl = [](jsi::Runtime &rt, const jsi::Value &thisValue, const jsi::Value *args, @@ -55,7 +55,7 @@ void RuntimeDecorator::decorateRuntime( jsi::PropNameID::forAscii(rt, "evalWithSourceUrl"), 1, evalWithSourceUrl)); -#endif +#endif // DEBUG auto callback = [](jsi::Runtime &rt, const jsi::Value &thisValue, From 625a3b248c623f70a8fc8377273ddf9c1a2c9f52 Mon Sep 17 00:00:00 2001 From: Krzysztof Piaskowy Date: Thu, 1 Dec 2022 14:55:31 +0100 Subject: [PATCH 08/23] Release 3.0.0-rc.8 (#3819) --- Example/ios/Podfile.lock | 4 ++-- FabricExample/ios/Podfile.lock | 4 ++-- FabricExample/yarn.lock | 5 ----- TVOSExample/ios/Podfile.lock | 4 ++-- package.json | 2 +- 5 files changed, 7 insertions(+), 12 deletions(-) diff --git a/Example/ios/Podfile.lock b/Example/ios/Podfile.lock index 5b516210edae..70f9c0fca7df 100644 --- a/Example/ios/Podfile.lock +++ b/Example/ios/Podfile.lock @@ -384,7 +384,7 @@ PODS: - React-Core - RNGestureHandler (2.7.1): - React-Core - - RNReanimated (3.0.0-rc.7): + - RNReanimated (3.0.0-rc.8): - DoubleConversion - FBLazyVector - FBReactNativeSpec @@ -650,7 +650,7 @@ SPEC CHECKSUMS: RNCMaskedView: 5a8ec07677aa885546a0d98da336457e2bea557f RNCPicker: 914b557e20b3b8317b084aca9ff4b4edb95f61e4 RNGestureHandler: b7a872907ee289ada902127f2554fa1d2c076122 - RNReanimated: a1cf5a56c16b1aa400c7d8d4cb45e0f9773d25b0 + RNReanimated: e6c9dc235b60a291ad6f2f6c029bc4333fdc7dd7 RNScreens: 34cc502acf1b916c582c60003dc3089fa01dc66d RNSVG: 07dbd870b0dcdecc99b3a202fa37c8ca163caec2 SocketRocket: fccef3f9c5cedea1353a9ef6ada904fde10d6608 diff --git a/FabricExample/ios/Podfile.lock b/FabricExample/ios/Podfile.lock index e57d22666f6c..bda8b9dc73e9 100644 --- a/FabricExample/ios/Podfile.lock +++ b/FabricExample/ios/Podfile.lock @@ -728,7 +728,7 @@ PODS: - React-Codegen - React-RCTFabric - ReactCommon/turbomodule/core - - RNReanimated (3.0.0-rc.7): + - RNReanimated (3.0.0-rc.8): - DoubleConversion - FBLazyVector - FBReactNativeSpec @@ -1018,7 +1018,7 @@ SPEC CHECKSUMS: React-runtimeexecutor: 7401c4a40f8728fd89df4a56104541b760876117 ReactCommon: c9246996e73bf75a2c6c3ff15f1e16707cdc2da9 RNGestureHandler: 8e5218fe0fde045c6f318202fc51ce3638deee54 - RNReanimated: 0e6700bdca5bc9b5cabd9defb551378cefca21cc + RNReanimated: 5fe97ed1710f9d0c90bd97b275c4ba6b58fa9b45 RNScreens: 208223c783496e6d0aa92ffdf307f61d58756fc1 RNSVG: 8ef4c60d9378eab6996a3f006dfb5784e6dab302 SocketRocket: fccef3f9c5cedea1353a9ef6ada904fde10d6608 diff --git a/FabricExample/yarn.lock b/FabricExample/yarn.lock index 1aff2e8a6e7f..aabab5107d8a 100644 --- a/FabricExample/yarn.lock +++ b/FabricExample/yarn.lock @@ -1774,11 +1774,6 @@ resolved "https://registry.yarnpkg.com/@types/hammerjs/-/hammerjs-2.0.41.tgz#f6ecf57d1b12d2befcce00e928a6a097c22980aa" integrity sha512-ewXv/ceBaJprikMcxCmWU1FKyMAQ2X7a9Gtmzw8fcg2kIePI1crERDM818W+XYrxqdBBOdlf2rm137bU+BltCA== -"@types/invariant@^2.2.35": - version "2.2.35" - resolved "https://registry.yarnpkg.com/@types/invariant/-/invariant-2.2.35.tgz#cd3ebf581a6557452735688d8daba6cf0bd5a3be" - integrity sha512-DxX1V9P8zdJPYQat1gHyY0xj3efl8gnMVjiM9iCY6y27lj+PoQWkgjt8jDqmovPqULkKVpKRg8J36iQiA+EtEg== - "@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1": version "2.0.4" resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz#8467d4b3c087805d63580480890791277ce35c44" diff --git a/TVOSExample/ios/Podfile.lock b/TVOSExample/ios/Podfile.lock index 7bb2b2e76638..1f530f76d084 100644 --- a/TVOSExample/ios/Podfile.lock +++ b/TVOSExample/ios/Podfile.lock @@ -322,7 +322,7 @@ PODS: - React-jsi (= 0.69.5-2) - React-logger (= 0.69.5-2) - React-perflogger (= 0.69.5-2) - - RNReanimated (3.0.0-rc.7): + - RNReanimated (3.0.0-rc.8): - DoubleConversion - FBLazyVector - FBReactNativeSpec @@ -538,7 +538,7 @@ SPEC CHECKSUMS: React-RCTText: 16122ecd9be53be613b2206a13e5cf987835c8cf React-runtimeexecutor: 44fe73dca7d31245dfc031971a2ce14085c8d5fe ReactCommon: 3f6173ad12133f7e032f4c8d061dba181115c1c0 - RNReanimated: 79d2d414d251e8dd8e280d0d5b32224eab311909 + RNReanimated: 3fe548cade4cee739282b4d36af15b7233ecfe4e SocketRocket: fccef3f9c5cedea1353a9ef6ada904fde10d6608 Yoga: 5f6b76dc63952163378af6afc502d8bab96643a1 YogaKit: 1e22bf2228b3a5ac8cc88965153061ae92c494b5 diff --git a/package.json b/package.json index af9ab186369b..fc6dd8ef2f91 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-native-reanimated", - "version": "3.0.0-rc.7", + "version": "3.0.0-rc.8", "description": "More powerful alternative to Animated library for React Native.", "scripts": { "test": "yarn run format:js && yarn run lint:js && yarn run test:unit", From f5405c59eb11f248f9b19a09221a7e67172a4749 Mon Sep 17 00:00:00 2001 From: Tomek Zawadzki Date: Thu, 8 Dec 2022 20:47:54 +0100 Subject: [PATCH 09/23] Fix new implementation iOS crash on reload while animation is running (#3837) ## Summary This PR fixes an iOS crash with Hermes on app reload that occurs only if some animation is still running: crash ## Test plan 1. Build and launch Example app on iOS simulator 2. Open Animated Style Update Example 3. Increase duration to 5000 ms 4. Click "Toggle" button 5. Press r in simulator 6. Make sure the app reloads correctly --- Common/cpp/NativeModules/NativeReanimatedModule.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Common/cpp/NativeModules/NativeReanimatedModule.cpp b/Common/cpp/NativeModules/NativeReanimatedModule.cpp index 13d47b7791cd..e0cebbe30509 100644 --- a/Common/cpp/NativeModules/NativeReanimatedModule.cpp +++ b/Common/cpp/NativeModules/NativeReanimatedModule.cpp @@ -196,9 +196,10 @@ NativeReanimatedModule::~NativeReanimatedModule() { if (runtimeHelper) { runtimeHelper->callGuard = nullptr; runtimeHelper->valueUnpacker = nullptr; - // event handler registry stores some JSI values from UI runtime, so it has - // to go away before we tear down the runtime + // event handler registry and frame callbacks store some JSI values from UI + // runtime, so they have to go away before we tear down the runtime eventHandlerRegistry.reset(); + frameCallbacks.clear(); runtime.reset(); // make sure uiRuntimeDestroyed is set after the runtime is deallocated runtimeHelper->uiRuntimeDestroyed = true; From bdd6c7be262955909bbbab0f6b401938865715d4 Mon Sep 17 00:00:00 2001 From: Tomek Zawadzki Date: Thu, 8 Dec 2022 20:49:02 +0100 Subject: [PATCH 10/23] Use weak object for shareables on V8 (#3839) ## Summary This PR enables weak object implementation for shareables when using V8 runtime. As @Kudo mentioned in https://github.com/software-mansion/react-native-reanimated/pull/3722#discussion_r1040966675, react-native-v8 exposes weak objects API via JSI (see [here](https://github.com/Kudo/react-native-v8/blob/96a1a2c8a35349967a3ad7219106a3475b85433a/src/v8runtime/V8Runtime.cpp#L982-L997)). ## Test plan Run Example app with react-native-v8 and check if everything works. --- Common/cpp/SharedItems/Shareables.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Common/cpp/SharedItems/Shareables.h b/Common/cpp/SharedItems/Shareables.h index 75ec4408b463..62478d863083 100644 --- a/Common/cpp/SharedItems/Shareables.h +++ b/Common/cpp/SharedItems/Shareables.h @@ -14,7 +14,7 @@ #include "RuntimeManager.h" #include "Scheduler.h" -#define HAS_JS_WEAK_OBJECTS JS_RUNTIME_HERMES +#define HAS_JS_WEAK_OBJECTS (JS_RUNTIME_HERMES || JS_RUNTIME_V8) using namespace facebook; From 2010e5b74a68d99bbb43d489b5e9b8cbd7be8e5e Mon Sep 17 00:00:00 2001 From: Juliusz Wajgelt <49338439+jwajgelt@users.noreply.github.com> Date: Fri, 9 Dec 2022 13:30:42 +0100 Subject: [PATCH 11/23] [iOS][Layout Animations] Remove `exiting` views from UIManager's view registry (#3824) ## Summary Currently, when views with an `exiting` animation are unmounted, any deleted in that transaction aren't removed from the UIManager's view registry (the mapping of react tags to native views). This can cause memory leaks of removed views. This PR restores the original behaviour of UIManager when removing views, allowing it to clean them up properly, and reattaches the views with `exiting` animations after the transaction removing the views has completed. ## Test plan - In example app, go to "Entering and Exiting with Layout" - Set a breakpoint somewhere in `RCTUIManager`, for example `_manageChildren`. - Press "Toggle" to create the animated views, and note the number of entries in `_viewRegistry`. - Disable the breakpoint, resume the app and start spamming the toggle button. - Enable the breakpoint again, and press toggle one more time. - The number of entries in `_viewRegistry` should be the same as before spamming toggle. --- ios/LayoutReanimation/REAAnimationsManager.h | 4 +- ios/LayoutReanimation/REAAnimationsManager.m | 52 ++++++++---- ios/LayoutReanimation/REAUIManager.mm | 84 +++++-------------- .../layoutReanimation/animationsManager.ts | 3 - 4 files changed, 62 insertions(+), 81 deletions(-) diff --git a/ios/LayoutReanimation/REAAnimationsManager.h b/ios/LayoutReanimation/REAAnimationsManager.h index e1a0a7a26f44..5ef6b3a310d1 100644 --- a/ios/LayoutReanimation/REAAnimationsManager.h +++ b/ios/LayoutReanimation/REAAnimationsManager.h @@ -33,7 +33,9 @@ typedef void (^REAAnimationRemovingBlock)(NSNumber *_Nonnull tag); - (REASnapshot *)prepareSnapshotBeforeMountForView:(UIView *)view; - (BOOL)wantsHandleRemovalOfView:(UIView *)view; - (void)removeAnimationsFromSubtree:(UIView *)view; -- (void)removeChildren:(NSArray *)children fromContainer:(UIView *)container; +- (void)reattachAnimatedChildren:(NSArray> *)children + toContainer:(id)container + atIndices:(NSArray *)indices; - (void)onViewCreate:(UIView *)view after:(REASnapshot *)after; - (void)onViewUpdate:(UIView *)view before:(REASnapshot *)before after:(REASnapshot *)after; diff --git a/ios/LayoutReanimation/REAAnimationsManager.m b/ios/LayoutReanimation/REAAnimationsManager.m index 920ecc194cc7..e30b0ea58469 100644 --- a/ios/LayoutReanimation/REAAnimationsManager.m +++ b/ios/LayoutReanimation/REAAnimationsManager.m @@ -279,20 +279,22 @@ - (void)maybeDropAncestors:(UIView *)child } } -- (BOOL)removeRecursive:(UIView *)view fromContainer:(UIView *)container withoutAnimation:(BOOL)removeImmediately; +- (BOOL)startAnimationsRecursive:(UIView *)view + shouldRemoveSubviewsWithoutAnimations:(BOOL)shouldRemoveSubviewsWithoutAnimations; { if (!view.reactTag) { return NO; } BOOL hasExitAnimation = _hasAnimationForTag(view.reactTag, @"exiting") || [_exitingViews objectForKey:view.reactTag]; BOOL hasAnimatedChildren = NO; - removeImmediately = removeImmediately && !hasExitAnimation; + shouldRemoveSubviewsWithoutAnimations = shouldRemoveSubviewsWithoutAnimations && !hasExitAnimation; NSMutableArray *toBeRemoved = [[NSMutableArray alloc] init]; for (UIView *subview in [view.reactSubviews copy]) { - if ([self removeRecursive:subview fromContainer:view withoutAnimation:removeImmediately]) { + if ([self startAnimationsRecursive:subview + shouldRemoveSubviewsWithoutAnimations:shouldRemoveSubviewsWithoutAnimations]) { hasAnimatedChildren = YES; - } else if (removeImmediately) { + } else if (shouldRemoveSubviewsWithoutAnimations) { [toBeRemoved addObject:subview]; } } @@ -307,14 +309,8 @@ - (BOOL)removeRecursive:(UIView *)view fromContainer:(UIView *)container without if (hasExitAnimation) { before = [[REASnapshot alloc] init:view]; } - // start exit animation - UIView *originalSuperview = view.superview; - NSUInteger originalIndex = [originalSuperview.subviews indexOfObjectIdenticalTo:view]; - [container removeReactSubview:view]; - // we don't want user interaction on exiting views - view.userInteractionEnabled = NO; - [originalSuperview insertSubview:view atIndex:originalIndex]; + // start exit animation if (hasExitAnimation && ![_exitingViews objectForKey:view.reactTag]) { NSDictionary *preparedValues = [self prepareDataForAnimatingWorklet:before.values frameConfig:ExitingFrame]; [_exitingViews setObject:view forKey:view.reactTag]; @@ -335,14 +331,40 @@ - (BOOL)removeRecursive:(UIView *)view fromContainer:(UIView *)container without // start new animations for it, and might as well remove // the layout animation config now _clearAnimationConfigForTag(view.reactTag); + + // we don't want user interaction on exiting views + view.userInteractionEnabled = NO; + return YES; } -- (void)removeChildren:(NSArray *)children fromContainer:(UIView *)container +- (void)reattachAnimatedChildren:(NSArray> *)children + toContainer:(id)container + atIndices:(NSArray *)indices { - for (UIView *removedChild in children) { - if (![self removeRecursive:removedChild fromContainer:container withoutAnimation:true]) { - [removedChild removeFromSuperview]; + if (![container isKindOfClass:[UIView class]]) { + return; + } + + // since we reattach only some of the views, + // we count the views we DIDN'T reattach + // and shift later views' indices by that number + // to make sure they appear at correct relative posisitons + // in the `subviews` array + int skippedViewsCount = 0; + + for (int i = 0; i < children.count; i++) { + id child = children[i]; + if (![child isKindOfClass:[UIView class]]) { + skippedViewsCount++; + continue; + } + UIView *childView = (UIView *)child; + NSNumber *originalIndex = indices[i]; + if ([self startAnimationsRecursive:childView shouldRemoveSubviewsWithoutAnimations:YES]) { + [(UIView *)container insertSubview:childView atIndex:[originalIndex intValue] - skippedViewsCount]; + } else { + skippedViewsCount++; } } } diff --git a/ios/LayoutReanimation/REAUIManager.mm b/ios/LayoutReanimation/REAUIManager.mm index eca019a99d67..47dfda2aa4cd 100644 --- a/ios/LayoutReanimation/REAUIManager.mm +++ b/ios/LayoutReanimation/REAUIManager.mm @@ -24,12 +24,6 @@ - (void)_manageChildren:(NSNumber *)containerTag removeAtIndices:(NSArray *)removeAtIndices registry:(NSMutableDictionary> *)registry; -- (void)_removeChildren:(NSArray *)children fromContainer:(UIView *)container; - -- (void)_removeChildren:(NSArray *)children - fromContainer:(UIView *)container - withAnimation:(RCTLayoutAnimationGroup *)animation; - - (RCTViewManagerUIBlock)uiBlockWithLayoutUpdateForRootView:(RCTRootShadowView *)rootShadowView; - (NSArray> *)_childrenToRemoveFromContainer:(id)container @@ -37,7 +31,6 @@ - (RCTViewManagerUIBlock)uiBlockWithLayoutUpdateForRootView:(RCTRootShadowView * @end @implementation REAUIManager { - RCTLayoutAnimationGroup *_reactLayoutAnimationGroup; NSMutableDictionary> *> *_toBeRemovedRegister; NSMutableDictionary *_parentMapper; REAAnimationsManager *_animationsManager; @@ -51,13 +44,6 @@ + (NSString *)moduleName - (void)setBridge:(RCTBridge *)bridge { - // setting a layout animation group with a deleting animation in order to - // allows us to call a different method in RCTUIManager for cleaning up exiting views - RCTLayoutAnimation *deletingAnimation = [[RCTLayoutAnimation alloc] initWithDuration:0 config:@{}]; - _reactLayoutAnimationGroup = [[RCTLayoutAnimationGroup alloc] initWithCreatingLayoutAnimation:nil - updatingLayoutAnimation:nil - deletingLayoutAnimation:deletingAnimation - callback:nil]; if (!_blockSetter) { _blockSetter = true; @@ -79,21 +65,6 @@ - (void)setBridge:(RCTBridge *)bridge } } -- (void)_removeChildren:(NSArray *)children - fromContainer:(UIView *)container - withAnimation:(RCTLayoutAnimationGroup *)animation -{ - if (animation == _reactLayoutAnimationGroup) { - // if a removed view in this batch has an `exiting` animation, - // let REAAnimationsManager handle the removal - [_animationsManager removeChildren:children fromContainer:container]; - } else { - // otherwise, if there's a layout animation group set, - // delegate to the React Native implementation for layout animations - [super _removeChildren:children fromContainer:container withAnimation:animation]; - } -} - - (void)_manageChildren:(NSNumber *)containerTag moveFromIndices:(NSArray *)moveFromIndices moveToIndices:(NSArray *)moveToIndices @@ -102,41 +73,14 @@ - (void)_manageChildren:(NSNumber *)containerTag removeAtIndices:(NSArray *)removeAtIndices registry:(NSMutableDictionary> *)registry { - if (!reanimated::FeaturesConfig::isLayoutAnimationEnabled()) { - [super _manageChildren:containerTag - moveFromIndices:moveFromIndices - moveToIndices:moveToIndices - addChildReactTags:addChildReactTags - addAtIndices:addAtIndices - removeAtIndices:removeAtIndices - registry:registry]; - return; + bool isLayoutAnimationEnabled = reanimated::FeaturesConfig::isLayoutAnimationEnabled(); + id container; + NSArray> *permanentlyRemovedChildren; + if (isLayoutAnimationEnabled) { + container = registry[containerTag]; + permanentlyRemovedChildren = [self _childrenToRemoveFromContainer:container atIndices:removeAtIndices]; } - // Reanimated changes /start - BOOL isUIViewRegistry = ((id)registry == (id)[self valueForKey:@"_viewRegistry"]); - if (isUIViewRegistry) { - BOOL wasProxyRemovalSet = [self valueForKey:@"_layoutAnimationGroup"] == _reactLayoutAnimationGroup; - BOOL wantProxyRemoval = NO; - if (!wasProxyRemovalSet) { - id container = registry[containerTag]; - NSArray> *permanentlyRemovedChildren = [self _childrenToRemoveFromContainer:container - atIndices:removeAtIndices]; - for (UIView *removedChild in permanentlyRemovedChildren) { - if ([_animationsManager wantsHandleRemovalOfView:removedChild]) { - wantProxyRemoval = YES; - break; - } - [_animationsManager removeAnimationsFromSubtree:removedChild]; - } - if (wantProxyRemoval) { - // set layout animation group - [super setNextLayoutAnimationGroup:_reactLayoutAnimationGroup]; - } - } - } - // Reanimated changes /end - [super _manageChildren:containerTag moveFromIndices:moveFromIndices moveToIndices:moveToIndices @@ -144,6 +88,22 @@ - (void)_manageChildren:(NSNumber *)containerTag addAtIndices:addAtIndices removeAtIndices:removeAtIndices registry:registry]; + + if (isLayoutAnimationEnabled) { + // we sort the (index, view) pairs to make sure we insert views back in order + NSMutableArray *> *removedViewsWithIndices = [NSMutableArray new]; + for (int i = 0; i < removeAtIndices.count; i++) { + removedViewsWithIndices[i] = @[ removeAtIndices[i], permanentlyRemovedChildren[i] ]; + } + [removedViewsWithIndices + sortUsingComparator:^NSComparisonResult(NSArray *_Nonnull obj1, NSArray *_Nonnull obj2) { + return [(NSNumber *)obj1[0] compare:(NSNumber *)obj2[0]]; + }]; + + [_animationsManager reattachAnimatedChildren:permanentlyRemovedChildren + toContainer:container + atIndices:removeAtIndices]; + } } - (void)callAnimationForTree:(UIView *)view parentTag:(NSNumber *)parentTag diff --git a/src/reanimated2/layoutReanimation/animationsManager.ts b/src/reanimated2/layoutReanimation/animationsManager.ts index 33de28d2fcb3..bd173fae5833 100644 --- a/src/reanimated2/layoutReanimation/animationsManager.ts +++ b/src/reanimated2/layoutReanimation/animationsManager.ts @@ -1,6 +1,5 @@ import { runOnUI } from '../core'; import { withStyleAnimation } from '../animation/styleAnimation'; -import { LogBox } from 'react-native'; import { SharedValue } from '../commonTypes'; import { makeUIMutable } from '../mutables'; import { @@ -8,8 +7,6 @@ import { LayoutAnimationsValues, } from './animationBuilder'; -LogBox.ignoreLogs(['Overriding previous layout animation with']); - const TAG_OFFSET = 1e9; function startObservingProgress( From f590030c27b2c63b7a231320cc3eb57d4df91c65 Mon Sep 17 00:00:00 2001 From: Krzysztof Magiera Date: Tue, 13 Dec 2022 09:40:39 +0100 Subject: [PATCH 12/23] Remove _setGloalConsole JSI method (#3853) After #3838 we can now access the main global object via `global` variable. This eliminates a need for _setGlobalConsole JSI method that we'd install in the UI runtime only so that we can set the console object as global on the UI runtime. In this PR we change the call to _setGlobalConsole with just reassigning global.console. Add some logs in worklets, run the app, see the logs appear on the output, also that console.warn displays in the log box. Co-authored-by: Tomek Zawadzki --- Common/cpp/Tools/RuntimeDecorator.cpp | 16 ---------------- plugin.js | 1 - src/reanimated2/__mocks__/NativeReanimated.ts | 4 ---- src/reanimated2/core.ts | 7 ------- src/reanimated2/globals.d.ts | 5 ++--- src/reanimated2/js-reanimated/global.ts | 11 ++++------- 6 files changed, 6 insertions(+), 38 deletions(-) diff --git a/Common/cpp/Tools/RuntimeDecorator.cpp b/Common/cpp/Tools/RuntimeDecorator.cpp index a8e7fcb91912..daa3adc449bf 100644 --- a/Common/cpp/Tools/RuntimeDecorator.cpp +++ b/Common/cpp/Tools/RuntimeDecorator.cpp @@ -77,22 +77,6 @@ void RuntimeDecorator::decorateRuntime( rt, jsi::PropNameID::forAscii(rt, "_log"), 1, callback); rt.global().setProperty(rt, "_log", log); - auto setGlobalConsole = [](jsi::Runtime &rt, - const jsi::Value &thisValue, - const jsi::Value *args, - size_t count) -> jsi::Value { - rt.global().setProperty(rt, "console", args[0]); - return jsi::Value::undefined(); - }; - rt.global().setProperty( - rt, - "_setGlobalConsole", - jsi::Function::createFromHostFunction( - rt, - jsi::PropNameID::forAscii(rt, "_setGlobalConsole"), - 1, - setGlobalConsole)); - auto chronoNow = [](jsi::Runtime &rt, const jsi::Value &thisValue, const jsi::Value *args, diff --git a/plugin.js b/plugin.js index 7642ab1f6798..9745c7c02736 100644 --- a/plugin.js +++ b/plugin.js @@ -33,7 +33,6 @@ const globals = new Set([ 'this', 'console', 'performance', - '_setGlobalConsole', '_chronoNow', 'Date', 'Array', diff --git a/src/reanimated2/__mocks__/NativeReanimated.ts b/src/reanimated2/__mocks__/NativeReanimated.ts index 0e4d51fb3c0f..e5f72a81be55 100644 --- a/src/reanimated2/__mocks__/NativeReanimated.ts +++ b/src/reanimated2/__mocks__/NativeReanimated.ts @@ -2,10 +2,6 @@ // @ts-nocheck import MutableValue from './MutableValue'; -global._setGlobalConsole = (_val) => { - // noop -}; - const NOOP = () => { // noop }; diff --git a/src/reanimated2/core.ts b/src/reanimated2/core.ts index e671b97efc98..f1a19e08cee0 100644 --- a/src/reanimated2/core.ts +++ b/src/reanimated2/core.ts @@ -17,13 +17,6 @@ export { stopMapper } from './mappers'; export { runOnJS, runOnUI } from './threads'; export { getTimestamp } from './time'; -if (global._setGlobalConsole === undefined) { - // it can happen when Reanimated plugin wasn't added, but the user uses the only API from version 1 - global._setGlobalConsole = () => { - // noop - }; -} - export type ReanimatedConsole = Pick< Console, 'debug' | 'log' | 'warn' | 'info' | 'error' diff --git a/src/reanimated2/globals.d.ts b/src/reanimated2/globals.d.ts index 687894da8666..0af77e01543b 100644 --- a/src/reanimated2/globals.d.ts +++ b/src/reanimated2/globals.d.ts @@ -6,7 +6,6 @@ import type { ShareableRef, ShareableSyncDataHolderRef, } from './commonTypes'; -import type { ReanimatedConsole } from './core'; import type { FrameCallbackRegistryUI } from './frameCallback/FrameCallbackRegistryUI'; import type { ShadowNodeWrapper } from './hook/commonTypes'; import { LayoutAnimationStartFunction } from './layoutReanimation'; @@ -19,7 +18,6 @@ declare global { const _frameTimestamp: number | null; const _eventTimestamp: number; const __reanimatedModuleProxy: NativeReanimated; - const _setGlobalConsole: (console?: ReanimatedConsole) => void; const evalWithSourceMap: ( js: string, sourceURL: string, @@ -75,6 +73,7 @@ declare global { reportFatalError: (error: Error) => void; }; const _frameCallbackRegistry: FrameCallbackRegistryUI; + const console: Console; namespace NodeJS { interface Global { @@ -84,7 +83,6 @@ declare global { _frameTimestamp: number | null; _eventTimestamp: number; __reanimatedModuleProxy: NativeReanimated; - _setGlobalConsole: (console?: ReanimatedConsole) => void; evalWithSourceMap: ( js: string, sourceURL: string, @@ -140,6 +138,7 @@ declare global { __workletsCache?: Map any>; __handleCache?: WeakMap; __mapperRegistry?: MapperRegistry; + console: Console; } } } diff --git a/src/reanimated2/js-reanimated/global.ts b/src/reanimated2/js-reanimated/global.ts index f3be0df6573d..4c1d640fb89a 100644 --- a/src/reanimated2/js-reanimated/global.ts +++ b/src/reanimated2/js-reanimated/global.ts @@ -4,9 +4,6 @@ import { shouldBeUseWeb } from '../PlatformChecker'; const initializeGlobalsForWeb = () => { if (shouldBeUseWeb()) { global._frameTimestamp = null; - global._setGlobalConsole = (_val) => { - // noop - }; global._measure = () => { console.warn( "[Reanimated] You can't use `measure` with Chrome Debugger or with web version" @@ -40,10 +37,10 @@ const initializeGlobalsForWeb = () => { }; /* - If a file doesn't export anything, tree shaking doesn't pack - it into the JS bundle. In effect, the code inside of this file - will never execute. That is why we wrapped initialization code - into a function, and we call this one during creating + If a file doesn't export anything, tree shaking doesn't pack + it into the JS bundle. In effect, the code inside of this file + will never execute. That is why we wrapped initialization code + into a function, and we call this one during creating the module export object. */ From 595c28641d72f29fb4b6509ef6ffc1dd02a27be1 Mon Sep 17 00:00:00 2001 From: Krzysztof Magiera Date: Tue, 13 Dec 2022 09:54:21 +0100 Subject: [PATCH 13/23] Fix bad merge --- src/reanimated2/initializers.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/reanimated2/initializers.ts b/src/reanimated2/initializers.ts index c27047b962f8..9038e39e268a 100644 --- a/src/reanimated2/initializers.ts +++ b/src/reanimated2/initializers.ts @@ -138,13 +138,13 @@ export function initializeUIRuntime() { }; // setup console - const console = { + // @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 + global.console = { debug: runOnJS(capturableConsole.debug), log: runOnJS(capturableConsole.log), warn: runOnJS(capturableConsole.warn), error: runOnJS(capturableConsole.error), info: runOnJS(capturableConsole.info), }; - _setGlobalConsole(console); })(); } From 2a772d45855da29f4d9c1c508f8bd3cb9532c22a Mon Sep 17 00:00:00 2001 From: Krzysztof Magiera Date: Tue, 13 Dec 2022 11:25:22 +0100 Subject: [PATCH 14/23] Responding to comments --- .../NativeModules/NativeReanimatedModule.h | 2 +- Common/cpp/SharedItems/Shareables.cpp | 6 +++--- package.json | 3 ++- plugin.js | 5 +++++ src/reanimated2/initializers.ts | 20 +++++++++++++++---- 5 files changed, 27 insertions(+), 9 deletions(-) diff --git a/Common/cpp/NativeModules/NativeReanimatedModule.h b/Common/cpp/NativeModules/NativeReanimatedModule.h index c16d6077493e..d15f196ebbb2 100644 --- a/Common/cpp/NativeModules/NativeReanimatedModule.h +++ b/Common/cpp/NativeModules/NativeReanimatedModule.h @@ -50,7 +50,7 @@ class NativeReanimatedModule : public NativeReanimatedModuleSpec, void installCoreFunctions( jsi::Runtime &rt, const jsi::Value &callGuard, - const jsi::Value &workletMaker) override; + const jsi::Value &valueUnpacker) override; jsi::Value makeShareableClone(jsi::Runtime &rt, const jsi::Value &value) override; diff --git a/Common/cpp/SharedItems/Shareables.cpp b/Common/cpp/SharedItems/Shareables.cpp index 1b3035a8e059..aaf8ddec9e44 100644 --- a/Common/cpp/SharedItems/Shareables.cpp +++ b/Common/cpp/SharedItems/Shareables.cpp @@ -14,9 +14,9 @@ CoreFunction::CoreFunction( functionBody_ = workletObject.getProperty(rt, "asString").asString(rt).utf8(rt); location_ = "worklet_" + - std::to_string( - (unsigned int)workletObject.getProperty(rt, "__workletHash") - .getNumber()); + std::to_string((unsigned long long)workletObject + .getProperty(rt, "__workletHash") + .getNumber()); } std::unique_ptr &CoreFunction::getFunction(jsi::Runtime &rt) { diff --git a/package.json b/package.json index fc6dd8ef2f91..77a3b4b50777 100644 --- a/package.json +++ b/package.json @@ -79,7 +79,8 @@ "invariant": "^2.2.4", "lodash.isequal": "^4.5.0", "setimmediate": "^1.0.5", - "string-hash-64": "^1.0.3" + "string-hash-64": "^1.0.3", + "convert-source-map": "^1.7.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0", diff --git a/plugin.js b/plugin.js index 9745c7c02736..e777536d4ca5 100644 --- a/plugin.js +++ b/plugin.js @@ -534,6 +534,11 @@ function makeWorklet(t, fun, state) { let lineOffset = 1; if (closure.size > 0) { + // When worklet captures some variables, we append closure destructing at + // the beginning of the function body. This effectively results in line + // numbers shifting by the number of captured variables (size of the + // closure) + 2 (for the opening and closing bracets of the destruct + // statement) lineOffset -= closure.size + 2; } diff --git a/src/reanimated2/initializers.ts b/src/reanimated2/initializers.ts index 9038e39e268a..f55f945b7ae3 100644 --- a/src/reanimated2/initializers.ts +++ b/src/reanimated2/initializers.ts @@ -1,7 +1,7 @@ import NativeReanimatedModule from './NativeReanimated'; import { runOnUI, runOnJS } from './threads'; -function callGuard, U>( +function callGuardDEV, U>( fn: (...args: T) => U, ...args: T ): void { @@ -10,13 +10,21 @@ function callGuard, U>( fn(...args); } catch (e) { if (global.ErrorUtils) { - global.ErrorUtils.reportFatalError(e); + global.ErrorUtils.reportFatalError(e as Error); } else { throw e; } } } +function callGuard, U>( + fn: (...args: T) => U, + ...args: T +): void { + 'worklet'; + fn(...args); +} + function valueUnpacker(objectToUnpack: any, category?: string): any { 'worklet'; let workletsCache = global.__workletsCache; @@ -77,7 +85,8 @@ export function registerWorkletStackDetails( function getBundleOffset(error: Error): [string, number, number] { const frame = error.stack?.split('\n')?.[0]; if (frame) { - const parsedFrame = /@(.*):(\d+):(\d+)/.exec(frame); + console.log('FRAME: ', frame); + const parsedFrame = /@([^@]+):(\d+):(\d+)/.exec(frame); if (parsedFrame) { const [, file, line, col] = parsedFrame; return [file, Number(line), Number(col)]; @@ -122,7 +131,10 @@ function reportFatalErrorOnJS({ } export function initializeUIRuntime() { - NativeReanimatedModule.installCoreFunctions(callGuard, valueUnpacker); + NativeReanimatedModule.installCoreFunctions( + __DEV__ ? callGuardDEV : callGuard, + valueUnpacker + ); const capturableConsole = console; runOnUI(() => { From 29fa80c81345bd18185fe2a6922792e330220b4d Mon Sep 17 00:00:00 2001 From: Krzysztof Magiera Date: Tue, 13 Dec 2022 13:10:13 +0100 Subject: [PATCH 15/23] Avoid RTTI and fix prod builds --- .../ReanimatedHermesRuntime.cpp | 15 +++--- plugin.js | 48 +++++++++++-------- 2 files changed, 38 insertions(+), 25 deletions(-) diff --git a/Common/cpp/ReanimatedRuntime/ReanimatedHermesRuntime.cpp b/Common/cpp/ReanimatedRuntime/ReanimatedHermesRuntime.cpp index ab8324f03860..0c772b5364be 100644 --- a/Common/cpp/ReanimatedRuntime/ReanimatedHermesRuntime.cpp +++ b/Common/cpp/ReanimatedRuntime/ReanimatedHermesRuntime.cpp @@ -9,6 +9,7 @@ #include #include +#include #include #if __has_include() @@ -81,14 +82,16 @@ ReanimatedHermesRuntime::ReanimatedHermesRuntime( #endif #ifdef DEBUG + facebook::hermes::HermesRuntime *wrappedRuntime = runtime_.get(); jsi::Value evalWithSourceMap = jsi::Function::createFromHostFunction( *runtime_, jsi::PropNameID::forAscii(*runtime_, "evalWithSourceMap"), 3, - [](jsi::Runtime &rt, - const jsi::Value &thisValue, - const jsi::Value *args, - size_t count) -> jsi::Value { + [wrappedRuntime]( + jsi::Runtime &rt, + const jsi::Value &thisValue, + const jsi::Value *args, + size_t count) -> jsi::Value { auto code = std::make_shared( args[0].asString(rt).utf8(rt)); std::string sourceURL; @@ -100,8 +103,8 @@ ReanimatedHermesRuntime::ReanimatedHermesRuntime( sourceMap = std::make_shared( args[2].asString(rt).utf8(rt)); } - return dynamic_cast(rt) - .evaluateJavaScriptWithSourceMap(code, sourceMap, sourceURL); + return wrappedRuntime->evaluateJavaScriptWithSourceMap( + code, sourceMap, sourceURL); }); runtime_->global().setProperty( *runtime_, "evalWithSourceMap", evalWithSourceMap); diff --git a/plugin.js b/plugin.js index e777536d4ca5..eb9236320b1f 100644 --- a/plugin.js +++ b/plugin.js @@ -383,10 +383,12 @@ function buildWorkletString(t, fun, closureVariables, name, inputMap) { } } + const includeSourceMap = shouldGenerateSourceMap(); + const transformed = transformSync(code, { plugins: [prependClosureVariablesIfNecessary()], - compact: !shouldGenerateSourceMap(), - sourceMaps: shouldGenerateSourceMap(), + compact: !includeSourceMap, + sourceMaps: includeSourceMap, inputSourceMap: inputMap, ast: false, babelrc: false, @@ -394,12 +396,15 @@ function buildWorkletString(t, fun, closureVariables, name, inputMap) { comments: false, }); - const sourceMap = convertSourceMap.fromObject(transformed.map).toObject(); - // sourcesContent field contains a full source code of the file which contains the worklet - // and is not needed by the source map interpreter in order to symbolicate a stack trace. - // Therefore, we remove it to reduce the bandwith and avoid sending it potentially multiple times - // in files that contain multiple worklets. Along with sourcesContent. - delete sourceMap.sourcesContent; + let sourceMap; + if (includeSourceMap) { + sourceMap = convertSourceMap.fromObject(transformed.map).toObject(); + // sourcesContent field contains a full source code of the file which contains the worklet + // and is not needed by the source map interpreter in order to symbolicate a stack trace. + // Therefore, we remove it to reduce the bandwith and avoid sending it potentially multiple times + // in files that contain multiple worklets. Along with sourcesContent. + delete sourceMap.sourcesContent; + } return [transformed.code, JSON.stringify(sourceMap)]; } @@ -571,19 +576,24 @@ function makeWorklet(t, fun, state) { t.numericLiteral(workletHash) ) ), - t.expressionStatement( - t.assignmentExpression( - '=', - t.memberExpression( - privateFunctionId, - t.identifier('__sourceMap'), - false - ), - t.stringLiteral(sourceMapString) - ) - ), ]; + if (sourceMapString) { + statements.push( + t.expressionStatement( + t.assignmentExpression( + '=', + t.memberExpression( + privateFunctionId, + t.identifier('__sourceMap'), + false + ), + t.stringLiteral(sourceMapString) + ) + ) + ); + } + if (!isRelease()) { statements.unshift( t.variableDeclaration('const', [ From 7e6f035a474ae9da3fb600ef2052a01fdbed55b3 Mon Sep 17 00:00:00 2001 From: Krzysztof Magiera Date: Tue, 13 Dec 2022 13:21:21 +0100 Subject: [PATCH 16/23] Sort out cyclic dependencies --- src/reanimated2/errors.ts | 58 +++++++++++++++++++++++++++++++ src/reanimated2/initializers.ts | 60 +-------------------------------- src/reanimated2/shareables.ts | 2 +- 3 files changed, 60 insertions(+), 60 deletions(-) create mode 100644 src/reanimated2/errors.ts diff --git a/src/reanimated2/errors.ts b/src/reanimated2/errors.ts new file mode 100644 index 000000000000..e2bd8074c6d0 --- /dev/null +++ b/src/reanimated2/errors.ts @@ -0,0 +1,58 @@ +type StackDetails = [Error, number, number]; + +const _workletStackDetails = new Map(); + +export function registerWorkletStackDetails( + hash: number, + stackDetails: StackDetails +) { + _workletStackDetails.set(hash, stackDetails); +} + +function getBundleOffset(error: Error): [string, number, number] { + const frame = error.stack?.split('\n')?.[0]; + if (frame) { + console.log('FRAME: ', frame); + const parsedFrame = /@([^@]+):(\d+):(\d+)/.exec(frame); + if (parsedFrame) { + const [, file, line, col] = parsedFrame; + return [file, Number(line), Number(col)]; + } + } + return ['unknown', 0, 0]; +} + +function processStack(stack: string): string { + const workletStackEntries = stack.match(/worklet_(\d+):(\d+):(\d+)/g); + let result = stack; + workletStackEntries?.forEach((match) => { + const [, hash, origLine, origCol] = match.split(/:|_/).map(Number); + const errorDetails = _workletStackDetails.get(hash); + if (!errorDetails) { + return; + } + const [error, lineOffset, colOffset] = errorDetails; + const [bundleFile, bundleLine, bundleCol] = getBundleOffset(error); + const line = origLine + bundleLine + lineOffset; + const col = origCol + bundleCol + colOffset; + + result = result.replace(match, `${bundleFile}:${line}:${col}`); + }); + return result; +} + +export function reportFatalErrorOnJS({ + message, + stack, +}: { + message: string; + stack?: string; +}) { + const error = new Error(); + error.message = message; + error.stack = stack ? processStack(stack) : undefined; + error.name = 'ReanimatedError'; + // @ts-ignore React Native's ErrorUtils implementation extends the Error type with jsEngine field + error.jsEngine = 'reanimated'; + global.ErrorUtils.reportFatalError(error); +} diff --git a/src/reanimated2/initializers.ts b/src/reanimated2/initializers.ts index f55f945b7ae3..80da7f762482 100644 --- a/src/reanimated2/initializers.ts +++ b/src/reanimated2/initializers.ts @@ -1,3 +1,4 @@ +import { reportFatalErrorOnJS } from './errors'; import NativeReanimatedModule from './NativeReanimated'; import { runOnUI, runOnJS } from './threads'; @@ -71,65 +72,6 @@ Possible solutions are: } } -type StackDetails = [Error, number, number]; - -const _workletStackDetails = new Map(); - -export function registerWorkletStackDetails( - hash: number, - stackDetails: StackDetails -) { - _workletStackDetails.set(hash, stackDetails); -} - -function getBundleOffset(error: Error): [string, number, number] { - const frame = error.stack?.split('\n')?.[0]; - if (frame) { - console.log('FRAME: ', frame); - const parsedFrame = /@([^@]+):(\d+):(\d+)/.exec(frame); - if (parsedFrame) { - const [, file, line, col] = parsedFrame; - return [file, Number(line), Number(col)]; - } - } - return ['unknown', 0, 0]; -} - -function processStack(stack: string): string { - const workletStackEntries = stack.match(/worklet_(\d+):(\d+):(\d+)/g); - let result = stack; - workletStackEntries?.forEach((match) => { - const [, hash, origLine, origCol] = match.split(/:|_/).map(Number); - const errorDetails = _workletStackDetails.get(hash); - if (!errorDetails) { - return; - } - const [error, lineOffset, colOffset] = errorDetails; - const [bundleFile, bundleLine, bundleCol] = getBundleOffset(error); - const line = origLine + bundleLine + lineOffset; - const col = origCol + bundleCol + colOffset; - - result = result.replace(match, `${bundleFile}:${line}:${col}`); - }); - return result; -} - -function reportFatalErrorOnJS({ - message, - stack, -}: { - message: string; - stack?: string; -}) { - const error = new Error(); - error.message = message; - error.stack = stack ? processStack(stack) : undefined; - error.name = 'ReanimatedError'; - // @ts-ignore React Native's ErrorUtils implementation extends the Error type with jsEngine field - error.jsEngine = 'reanimated'; - global.ErrorUtils.reportFatalError(error); -} - export function initializeUIRuntime() { NativeReanimatedModule.installCoreFunctions( __DEV__ ? callGuardDEV : callGuard, diff --git a/src/reanimated2/shareables.ts b/src/reanimated2/shareables.ts index 459eaa6f94cb..dc7e77c309b3 100644 --- a/src/reanimated2/shareables.ts +++ b/src/reanimated2/shareables.ts @@ -1,7 +1,7 @@ import NativeReanimatedModule from './NativeReanimated'; import { ShareableRef } from './commonTypes'; import { shouldBeUseWeb } from './PlatformChecker'; -import { registerWorkletStackDetails } from './initializers'; +import { registerWorkletStackDetails } from './errors'; // for web/chrome debugger/jest environments this file provides a stub implementation // where no shareable references are used. Instead, the objects themselves are used From 27bb97d21d3ff91ee334549977bfd27e073d85a7 Mon Sep 17 00:00:00 2001 From: Krzysztof Magiera Date: Tue, 13 Dec 2022 13:45:40 +0100 Subject: [PATCH 17/23] Update snapshots to reflect plugin changes --- __tests__/__snapshots__/plugin.test.js.snap | 144 +++++++++++++------- 1 file changed, 98 insertions(+), 46 deletions(-) diff --git a/__tests__/__snapshots__/plugin.test.js.snap b/__tests__/__snapshots__/plugin.test.js.snap index 4e5e827a76e5..255ee4b75aab 100644 --- a/__tests__/__snapshots__/plugin.test.js.snap +++ b/__tests__/__snapshots__/plugin.test.js.snap @@ -7,6 +7,8 @@ var objX = { }; var f = function () { + var _e = [new Error(), -3, -20]; + var _f = function _f() { return { res: x + objX.x @@ -21,13 +23,15 @@ var f = function () { }; _f.asString = \\"function f(){const{x,objX}=this._closure;return{res:x+objX.x};}\\"; _f.__workletHash = 5359970077727; - _f.__location = \\"${ process.cwd() }/jest tests fixture (6:6)\\"; + _f.__stackDetails = _e; return _f; }();" `; exports[`babel plugin doesn't capture globals 1`] = ` "var f = function () { + var _e = [new Error(), 1, -20]; + var _f = function _f() { console.log('test'); }; @@ -35,7 +39,7 @@ exports[`babel plugin doesn't capture globals 1`] = ` _f._closure = {}; _f.asString = \\"function f(){console.log('test');}\\"; _f.__workletHash = 13298016111221; - _f.__location = \\"${ process.cwd() }/jest tests fixture (2:6)\\"; + _f.__stackDetails = _e; return _f; }();" `; @@ -48,6 +52,8 @@ exports[`babel plugin doesn't transform standard callback functions 1`] = ` exports[`babel plugin doesn't transform string literals 1`] = ` "var foo = function () { + var _e = [new Error(), 1, -20]; + var _f = function _f(x) { var bar = 'worklet'; var baz = \\"worklet\\"; @@ -56,7 +62,7 @@ exports[`babel plugin doesn't transform string literals 1`] = ` _f._closure = {}; _f.asString = \\"function foo(x){const bar='worklet';const baz=\\\\\\"worklet\\\\\\";}\\"; _f.__workletHash = 9810417751380; - _f.__location = \\"${ process.cwd() }/jest tests fixture (2:6)\\"; + _f.__stackDetails = _e; return _f; }();" `; @@ -65,6 +71,8 @@ exports[`babel plugin supports recursive calls 1`] = ` "var a = 1; var foo = function () { + var _e = [new Error(), -2, -20]; + var _f = function _f(t) { if (t > 0) { return a + foo(t - 1); @@ -76,7 +84,7 @@ var foo = function () { }; _f.asString = \\"function foo(t){const foo=this._recur;const{a}=this._closure;if(t>0){return a+foo(t-1);}}\\"; _f.__workletHash = 2022702330805; - _f.__location = \\"${ process.cwd() }/jest tests fixture (3:6)\\"; + _f.__stackDetails = _e; return _f; }();" `; @@ -91,6 +99,8 @@ function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && function Box() { var offset = (0, _reactNativeReanimated.useSharedValue)(0); var animatedStyles = (0, _reactNativeReanimated.useAnimatedStyle)(function () { + var _e = [new Error(), -2, -20]; + var _f = function _f() { return { transform: [{ @@ -102,9 +112,9 @@ function Box() { _f._closure = { offset: offset }; - _f.asString = \\"function _f(){const{offset}=this._closure;return{transform:[{translateX:offset.value*255}]};}\\"; - _f.__workletHash = 361788175040; - _f.__location = \\"${ process.cwd() }/jest tests fixture (10:48)\\"; + _f.asString = \\"function anonymous(){const{offset}=this._closure;return{transform:[{translateX:offset.value*255}]};}\\"; + _f.__workletHash = 16669311443114; + _f.__stackDetails = _e; _f.__optimalization = 3; return _f; }()); @@ -121,6 +131,8 @@ function Box() { exports[`babel plugin transforms spread operator in worklets for arrays 1`] = ` "var foo = function () { + var _e = [new Error(), 1, -20]; + var _f = function _f() { var bar = [4, 5]; var baz = [1].concat([2, 3], bar); @@ -129,13 +141,15 @@ exports[`babel plugin transforms spread operator in worklets for arrays 1`] = ` _f._closure = {}; _f.asString = \\"function foo(){const bar=[4,5];const baz=[1,...[2,3],...bar];}\\"; _f.__workletHash = 3161057533258; - _f.__location = \\"${ process.cwd() }/jest tests fixture (2:6)\\"; + _f.__stackDetails = _e; return _f; }();" `; exports[`babel plugin transforms spread operator in worklets for function arguments 1`] = ` "var foo = function () { + var _e = [new Error(), 1, -20]; + var _f = function _f() { for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { args[_key] = arguments[_key]; @@ -147,7 +161,7 @@ exports[`babel plugin transforms spread operator in worklets for function argume _f._closure = {}; _f.asString = \\"function foo(...args){console.log(args);}\\"; _f.__workletHash = 9866931756941; - _f.__location = \\"${ process.cwd() }/jest tests fixture (2:6)\\"; + _f.__stackDetails = _e; return _f; }();" `; @@ -158,6 +172,8 @@ exports[`babel plugin transforms spread operator in worklets for function calls var _toConsumableArray2 = _interopRequireDefault(require(\\"@babel/runtime/helpers/toConsumableArray\\")); var foo = function () { + var _e = [new Error(), 1, -20]; + var _f = function _f(arg) { var _console; @@ -167,13 +183,15 @@ var foo = function () { _f._closure = {}; _f.asString = \\"function foo(arg){console.log(...arg);}\\"; _f.__workletHash = 2015887751437; - _f.__location = \\"${ process.cwd() }/jest tests fixture (2:6)\\"; + _f.__stackDetails = _e; return _f; }();" `; exports[`babel plugin transforms spread operator in worklets for objects 1`] = ` "var foo = function () { + var _e = [new Error(), 1, -20]; + var _f = function _f() { var bar = { d: 4, @@ -190,27 +208,31 @@ exports[`babel plugin transforms spread operator in worklets for objects 1`] = ` _f._closure = {}; _f.asString = \\"function foo(){const bar={d:4,e:5};const baz={a:1,...{b:2,c:3},...bar};}\\"; _f.__workletHash = 792186851025; - _f.__location = \\"${ process.cwd() }/jest tests fixture (2:6)\\"; + _f.__stackDetails = _e; return _f; }();" `; exports[`babel plugin workletizes ArrowFunctionExpression 1`] = ` "var foo = function () { + var _e = [new Error(), 1, -20]; + var _f = function _f(x) { return x + 2; }; _f._closure = {}; - _f.asString = \\"function _f(x){return x+2;}\\"; - _f.__workletHash = 11411090164019; - _f.__location = \\"${ process.cwd() }/jest tests fixture (2:18)\\"; + _f.asString = \\"function anonymous(x){return x+2;}\\"; + _f.__workletHash = 16347365292089; + _f.__stackDetails = _e; return _f; }();" `; exports[`babel plugin workletizes FunctionDeclaration 1`] = ` "var foo = function () { + var _e = [new Error(), 1, -20]; + var _f = function _f(x) { return x + 2; }; @@ -218,7 +240,7 @@ exports[`babel plugin workletizes FunctionDeclaration 1`] = ` _f._closure = {}; _f.asString = \\"function foo(x){return x+2;}\\"; _f.__workletHash = 4679479961836; - _f.__location = \\"${ process.cwd() }/jest tests fixture (2:6)\\"; + _f.__stackDetails = _e; return _f; }();" `; @@ -238,6 +260,8 @@ var Foo = function () { (0, _createClass2.default)(Foo, [{ key: \\"bar\\", get: function () { + var _e = [new Error(), -2, -20]; + var _f = function _f() { return x + 2; }; @@ -247,7 +271,7 @@ var Foo = function () { }; _f.asString = \\"function get(){const{x}=this._closure;return x+2;}\\"; _f.__workletHash = 10436985806815; - _f.__location = \\"${ process.cwd() }/jest tests fixture\\"; + _f.__stackDetails = _e; return _f; }() }]); @@ -257,6 +281,8 @@ var Foo = function () { exports[`babel plugin workletizes hook wrapped ArrowFunctionExpression automatically 1`] = ` "var animatedStyle = useAnimatedStyle(function () { + var _e = [new Error(), 1, -20]; + var _f = function _f() { return { width: 50 @@ -264,9 +290,9 @@ exports[`babel plugin workletizes hook wrapped ArrowFunctionExpression automatic }; _f._closure = {}; - _f.asString = \\"function _f(){return{width:50};}\\"; - _f.__workletHash = 9756190407413; - _f.__location = \\"${ process.cwd() }/jest tests fixture (2:45)\\"; + _f.asString = \\"function anonymous(){return{width:50};}\\"; + _f.__workletHash = 9645206935615; + _f.__stackDetails = _e; _f.__optimalization = 3; return _f; }());" @@ -274,6 +300,8 @@ exports[`babel plugin workletizes hook wrapped ArrowFunctionExpression automatic exports[`babel plugin workletizes hook wrapped named FunctionExpression automatically 1`] = ` "var animatedStyle = useAnimatedStyle(function () { + var _e = [new Error(), 1, -20]; + var _f = function _f() { return { width: 50 @@ -283,7 +311,7 @@ exports[`babel plugin workletizes hook wrapped named FunctionExpression automati _f._closure = {}; _f.asString = \\"function foo(){return{width:50};}\\"; _f.__workletHash = 6275510763626; - _f.__location = \\"${ process.cwd() }/jest tests fixture (2:45)\\"; + _f.__stackDetails = _e; _f.__optimalization = 3; return _f; }());" @@ -291,6 +319,8 @@ exports[`babel plugin workletizes hook wrapped named FunctionExpression automati exports[`babel plugin workletizes hook wrapped unnamed FunctionExpression automatically 1`] = ` "var animatedStyle = useAnimatedStyle(function () { + var _e = [new Error(), 1, -20]; + var _f = function _f() { return { width: 50 @@ -298,9 +328,9 @@ exports[`babel plugin workletizes hook wrapped unnamed FunctionExpression automa }; _f._closure = {}; - _f.asString = \\"function _f(){return{width:50};}\\"; - _f.__workletHash = 9756190407413; - _f.__location = \\"${ process.cwd() }/jest tests fixture (2:45)\\"; + _f.asString = \\"function anonymous(){return{width:50};}\\"; + _f.__workletHash = 9645206935615; + _f.__stackDetails = _e; _f.__optimalization = 3; return _f; }());" @@ -321,6 +351,8 @@ var Foo = function () { (0, _createClass2.default)(Foo, [{ key: \\"bar\\", value: function () { + var _e = [new Error(), 1, -20]; + var _f = function _f(x) { return x + 2; }; @@ -328,7 +360,7 @@ var Foo = function () { _f._closure = {}; _f.asString = \\"function bar(x){return x+2;}\\"; _f.__workletHash = 16974800582491; - _f.__location = \\"${ process.cwd() }/jest tests fixture\\"; + _f.__stackDetails = _e; return _f; }() }]); @@ -338,6 +370,8 @@ var Foo = function () { exports[`babel plugin workletizes named FunctionExpression 1`] = ` "var foo = function () { + var _e = [new Error(), 1, -20]; + var _f = function _f(x) { return x + 2; }; @@ -345,7 +379,7 @@ exports[`babel plugin workletizes named FunctionExpression 1`] = ` _f._closure = {}; _f.asString = \\"function foo(x){return x+2;}\\"; _f.__workletHash = 4679479961836; - _f.__location = \\"${ process.cwd() }/jest tests fixture (2:18)\\"; + _f.__stackDetails = _e; return _f; }();" `; @@ -353,14 +387,16 @@ exports[`babel plugin workletizes named FunctionExpression 1`] = ` exports[`babel plugin workletizes object hook wrapped ArrowFunctionExpression automatically 1`] = ` "useAnimatedGestureHandler({ onStart: function () { + var _e = [new Error(), 1, -20]; + var _f = function _f(event) { console.log(event); }; _f._closure = {}; - _f.asString = \\"function _f(event){console.log(event);}\\"; - _f.__workletHash = 2164830539996; - _f.__location = \\"${ process.cwd() }/jest tests fixture (3:17)\\"; + _f.asString = \\"function anonymous(event){console.log(event);}\\"; + _f.__workletHash = 1022605193782; + _f.__stackDetails = _e; return _f; }() });" @@ -369,6 +405,8 @@ exports[`babel plugin workletizes object hook wrapped ArrowFunctionExpression au exports[`babel plugin workletizes object hook wrapped ObjectMethod automatically 1`] = ` "useAnimatedGestureHandler({ onStart: function () { + var _e = [new Error(), 1, -20]; + var _f = function _f(event) { console.log(event); }; @@ -376,7 +414,7 @@ exports[`babel plugin workletizes object hook wrapped ObjectMethod automatically _f._closure = {}; _f.asString = \\"function onStart(event){console.log(event);}\\"; _f.__workletHash = 338158776260; - _f.__location = \\"${ process.cwd() }/jest tests fixture (3:8)\\"; + _f.__stackDetails = _e; return _f; }() });" @@ -385,6 +423,8 @@ exports[`babel plugin workletizes object hook wrapped ObjectMethod automatically exports[`babel plugin workletizes object hook wrapped named FunctionExpression automatically 1`] = ` "useAnimatedGestureHandler({ onStart: function () { + var _e = [new Error(), 1, -20]; + var _f = function _f(event) { console.log(event); }; @@ -392,7 +432,7 @@ exports[`babel plugin workletizes object hook wrapped named FunctionExpression a _f._closure = {}; _f.asString = \\"function onStart(event){console.log(event);}\\"; _f.__workletHash = 338158776260; - _f.__location = \\"${ process.cwd() }/jest tests fixture (3:17)\\"; + _f.__stackDetails = _e; return _f; }() });" @@ -401,14 +441,16 @@ exports[`babel plugin workletizes object hook wrapped named FunctionExpression a exports[`babel plugin workletizes object hook wrapped unnamed FunctionExpression automatically 1`] = ` "useAnimatedGestureHandler({ onStart: function () { + var _e = [new Error(), 1, -20]; + var _f = function _f(event) { console.log(event); }; _f._closure = {}; - _f.asString = \\"function _f(event){console.log(event);}\\"; - _f.__workletHash = 2164830539996; - _f.__location = \\"${ process.cwd() }/jest tests fixture (3:17)\\"; + _f.asString = \\"function anonymous(event){console.log(event);}\\"; + _f.__workletHash = 1022605193782; + _f.__stackDetails = _e; return _f; }() });" @@ -418,34 +460,40 @@ exports[`babel plugin workletizes possibly chained gesture object callback funct "var _reactNativeGestureHandler = require(\\"react-native-gesture-handler\\"); var foo = _reactNativeGestureHandler.Gesture.Tap().numberOfTaps(2).onBegin(function () { + var _e = [new Error(), 1, -20]; + var _f = function _f() { console.log('onBegin'); }; _f._closure = {}; - _f.asString = \\"function _f(){console.log('onBegin');}\\"; - _f.__workletHash = 13662490049850; - _f.__location = \\"${ process.cwd() }/jest tests fixture (6:17)\\"; + _f.asString = \\"function anonymous(){console.log('onBegin');}\\"; + _f.__workletHash = 15393478329680; + _f.__stackDetails = _e; return _f; }()).onStart(function () { + var _e = [new Error(), 1, -20]; + var _f = function _f(_event) { console.log('onStart'); }; _f._closure = {}; - _f.asString = \\"function _f(_event){console.log('onStart');}\\"; - _f.__workletHash = 16334902412526; - _f.__location = \\"${ process.cwd() }/jest tests fixture (9:17)\\"; + _f.asString = \\"function anonymous(_event){console.log('onStart');}\\"; + _f.__workletHash = 12748187344900; + _f.__stackDetails = _e; return _f; }()).onEnd(function () { + var _e = [new Error(), 1, -20]; + var _f = function _f(_event, _success) { console.log('onEnd'); }; _f._closure = {}; - _f.asString = \\"function _f(_event,_success){console.log('onEnd');}\\"; - _f.__workletHash = 4053780716017; - _f.__location = \\"${ process.cwd() }/jest tests fixture (12:15)\\"; + _f.asString = \\"function anonymous(_event,_success){console.log('onEnd');}\\"; + _f.__workletHash = 232586479291; + _f.__stackDetails = _e; return _f; }());" `; @@ -465,6 +513,8 @@ var Foo = function () { (0, _createClass2.default)(Foo, null, [{ key: \\"bar\\", value: function () { + var _e = [new Error(), 1, -20]; + var _f = function _f(x) { return x + 2; }; @@ -472,7 +522,7 @@ var Foo = function () { _f._closure = {}; _f.asString = \\"function bar(x){return x+2;}\\"; _f.__workletHash = 16974800582491; - _f.__location = \\"${ process.cwd() }/jest tests fixture\\"; + _f.__stackDetails = _e; return _f; }() }]); @@ -482,14 +532,16 @@ var Foo = function () { exports[`babel plugin workletizes unnamed FunctionExpression 1`] = ` "var foo = function () { + var _e = [new Error(), 1, -20]; + var _f = function _f(x) { return x + 2; }; _f._closure = {}; - _f.asString = \\"function _f(x){return x+2;}\\"; - _f.__workletHash = 11411090164019; - _f.__location = \\"${ process.cwd() }/jest tests fixture (2:18)\\"; + _f.asString = \\"function anonymous(x){return x+2;}\\"; + _f.__workletHash = 16347365292089; + _f.__stackDetails = _e; return _f; }();" `; From 9137e84d79bb2301ac949659f53ed48b69df1836 Mon Sep 17 00:00:00 2001 From: Krzysztof Magiera Date: Tue, 13 Dec 2022 13:46:25 +0100 Subject: [PATCH 18/23] =?UTF-8?q?Remove=20console=20=F0=9F=A4=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/reanimated2/errors.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/reanimated2/errors.ts b/src/reanimated2/errors.ts index e2bd8074c6d0..287214296e95 100644 --- a/src/reanimated2/errors.ts +++ b/src/reanimated2/errors.ts @@ -12,7 +12,6 @@ export function registerWorkletStackDetails( function getBundleOffset(error: Error): [string, number, number] { const frame = error.stack?.split('\n')?.[0]; if (frame) { - console.log('FRAME: ', frame); const parsedFrame = /@([^@]+):(\d+):(\d+)/.exec(frame); if (parsedFrame) { const [, file, line, col] = parsedFrame; From f63cf7d0cdc08dc4c95a88eecc19b3f9b852de4c Mon Sep 17 00:00:00 2001 From: Krzysztof Magiera Date: Wed, 14 Dec 2022 11:19:40 +0100 Subject: [PATCH 19/23] Responding to review comments --- .../NativeModules/NativeReanimatedModule.cpp | 5 +- Common/cpp/SharedItems/Shareables.cpp | 5 +- Common/cpp/SharedItems/Shareables.h | 55 ++++++++++++------- plugin.js | 1 - src/reanimated2/initializers.ts | 14 +---- 5 files changed, 41 insertions(+), 39 deletions(-) diff --git a/Common/cpp/NativeModules/NativeReanimatedModule.cpp b/Common/cpp/NativeModules/NativeReanimatedModule.cpp index e0cebbe30509..4330e4fdca4f 100644 --- a/Common/cpp/NativeModules/NativeReanimatedModule.cpp +++ b/Common/cpp/NativeModules/NativeReanimatedModule.cpp @@ -61,8 +61,7 @@ NativeReanimatedModule::NativeReanimatedModule( auto requestAnimationFrame = [=](jsi::Runtime &rt, const jsi::Value &fn) { auto jsFunction = std::make_shared(rt, fn); frameCallbacks.push_back([=](double timestamp) { - jsi::Runtime &rt = *runtimeHelper->uiRuntime(); - runtimeHelper->callGuard->call(rt, *jsFunction, jsi::Value(timestamp)); + runtimeHelper->runOnUIGuarded(*jsFunction, jsi::Value(timestamp)); }); maybeRequestRender(); }; @@ -216,7 +215,7 @@ void NativeReanimatedModule::scheduleOnUI( frameCallbacks.push_back([=](double timestamp) { jsi::Runtime &rt = *runtimeHelper->uiRuntime(); auto workletValue = shareableWorklet->getJSValue(rt); - runtimeHelper->callGuard->call(rt, workletValue); + runtimeHelper->runOnUIGuarded(workletValue); }); maybeRequestRender(); } diff --git a/Common/cpp/SharedItems/Shareables.cpp b/Common/cpp/SharedItems/Shareables.cpp index aaf8ddec9e44..8708b0405a1a 100644 --- a/Common/cpp/SharedItems/Shareables.cpp +++ b/Common/cpp/SharedItems/Shareables.cpp @@ -14,9 +14,8 @@ CoreFunction::CoreFunction( functionBody_ = workletObject.getProperty(rt, "asString").asString(rt).utf8(rt); location_ = "worklet_" + - std::to_string((unsigned long long)workletObject - .getProperty(rt, "__workletHash") - .getNumber()); + std::to_string(static_cast( + workletObject.getProperty(rt, "__workletHash").getNumber())); } std::unique_ptr &CoreFunction::getFunction(jsi::Runtime &rt) { diff --git a/Common/cpp/SharedItems/Shareables.h b/Common/cpp/SharedItems/Shareables.h index 62478d863083..105485f7408a 100644 --- a/Common/cpp/SharedItems/Shareables.h +++ b/Common/cpp/SharedItems/Shareables.h @@ -20,7 +20,29 @@ using namespace facebook; namespace reanimated { -class CoreFunction; +class JSRuntimeHelper; + +// Core functions are not allowed to capture outside variables, otherwise they'd +// try to access _closure variable which is something we want to avoid for +// simplicity reasons. +class CoreFunction { + private: + std::unique_ptr rnFunction_; + std::unique_ptr uiFunction_; + std::string functionBody_; + std::string location_; + JSRuntimeHelper + *runtimeHelper_; // runtime helper holds core function references, so we + // use normal pointer here to avoid ref cycles. + std::unique_ptr &getFunction(jsi::Runtime &rt); + + public: + CoreFunction(JSRuntimeHelper *runtimeHelper, const jsi::Value &workletObject); + template + jsi::Value call(jsi::Runtime &rt, Args &&...args) { + return getFunction(rt)->call(rt, args...); + } +}; class JSRuntimeHelper { private: @@ -62,27 +84,20 @@ class JSRuntimeHelper { void scheduleOnJS(std::function job) { scheduler_->scheduleOnJS(job); } -}; - -// Core functions are not allowed to capture outside variables, otherwise they'd -// try to access _closure variable which is something we want to avoid for -// simplicity reasons. -class CoreFunction { - private: - std::unique_ptr rnFunction_; - std::unique_ptr uiFunction_; - std::string functionBody_; - std::string location_; - JSRuntimeHelper - *runtimeHelper_; // runtime helper holds core function references, so we - // use normal pointer here to avoid ref cycles. - std::unique_ptr &getFunction(jsi::Runtime &rt); - public: - CoreFunction(JSRuntimeHelper *runtimeHelper, const jsi::Value &workletObject); template - jsi::Value call(jsi::Runtime &rt, Args &&...args) { - return getFunction(rt)->call(rt, args...); + inline void runOnUIGuarded(const jsi::Value &function, Args &&...args) { + // We only use callGuard in debug mode, otherwise we call the provided + // function directly. CallGuard provides a way of capturing expeptions in + // Javascript and propagating them to the main React Native thread such that + // they can be presented using RN's LogBox. +#ifdef DEBUG + callGuard->call(*uiRuntime_, function, args...); +#else + function.asObject(*uiRuntime_) + .asFunction(*uiRuntime_) + .call(*uiRuntime_, args...); +#endif } }; diff --git a/plugin.js b/plugin.js index eb9236320b1f..c06d071a9e1f 100644 --- a/plugin.js +++ b/plugin.js @@ -72,7 +72,6 @@ const globals = new Set([ '_makeShareableClone', '_updateDataSynchronously', 'eval', - 'evalWithSourceMap', '_updatePropsPaper', '_updatePropsFabric', '_removeShadowNodeFromRegistry', diff --git a/src/reanimated2/initializers.ts b/src/reanimated2/initializers.ts index 80da7f762482..0cbdf39d2c79 100644 --- a/src/reanimated2/initializers.ts +++ b/src/reanimated2/initializers.ts @@ -2,6 +2,7 @@ import { reportFatalErrorOnJS } from './errors'; import NativeReanimatedModule from './NativeReanimated'; import { runOnUI, runOnJS } from './threads'; +// callGuard is only used with debug builds function callGuardDEV, U>( fn: (...args: T) => U, ...args: T @@ -18,14 +19,6 @@ function callGuardDEV, U>( } } -function callGuard, U>( - fn: (...args: T) => U, - ...args: T -): void { - 'worklet'; - fn(...args); -} - function valueUnpacker(objectToUnpack: any, category?: string): any { 'worklet'; let workletsCache = global.__workletsCache; @@ -73,10 +66,7 @@ Possible solutions are: } export function initializeUIRuntime() { - NativeReanimatedModule.installCoreFunctions( - __DEV__ ? callGuardDEV : callGuard, - valueUnpacker - ); + NativeReanimatedModule.installCoreFunctions(callGuardDEV, valueUnpacker); const capturableConsole = console; runOnUI(() => { From 9f37efd5059210250ddddcd71ac657ed94d55fce Mon Sep 17 00:00:00 2001 From: Krzysztof Magiera Date: Wed, 14 Dec 2022 12:05:30 +0100 Subject: [PATCH 20/23] Update Common/cpp/SharedItems/Shareables.h Co-authored-by: Tomek Zawadzki --- Common/cpp/SharedItems/Shareables.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Common/cpp/SharedItems/Shareables.h b/Common/cpp/SharedItems/Shareables.h index 105485f7408a..4d1c337d84df 100644 --- a/Common/cpp/SharedItems/Shareables.h +++ b/Common/cpp/SharedItems/Shareables.h @@ -88,8 +88,8 @@ class JSRuntimeHelper { template inline void runOnUIGuarded(const jsi::Value &function, Args &&...args) { // We only use callGuard in debug mode, otherwise we call the provided - // function directly. CallGuard provides a way of capturing expeptions in - // Javascript and propagating them to the main React Native thread such that + // function directly. CallGuard provides a way of capturing exceptions in + // JavaScript and propagating them to the main React Native thread such that // they can be presented using RN's LogBox. #ifdef DEBUG callGuard->call(*uiRuntime_, function, args...); From fbb31104f2ecccb8b7e351273481c56b8e1815aa Mon Sep 17 00:00:00 2001 From: Krzysztof Magiera Date: Wed, 14 Dec 2022 12:07:07 +0100 Subject: [PATCH 21/23] Use rt ref --- Common/cpp/SharedItems/Shareables.h | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Common/cpp/SharedItems/Shareables.h b/Common/cpp/SharedItems/Shareables.h index 4d1c337d84df..748414100c0a 100644 --- a/Common/cpp/SharedItems/Shareables.h +++ b/Common/cpp/SharedItems/Shareables.h @@ -91,12 +91,11 @@ class JSRuntimeHelper { // function directly. CallGuard provides a way of capturing exceptions in // JavaScript and propagating them to the main React Native thread such that // they can be presented using RN's LogBox. + jsi::Runtime &rt = *uiRuntime_; #ifdef DEBUG - callGuard->call(*uiRuntime_, function, args...); + callGuard->call(rt, function, args...); #else - function.asObject(*uiRuntime_) - .asFunction(*uiRuntime_) - .call(*uiRuntime_, args...); + function.asObject(rt).asFunction(rt).call(rt, args...); #endif } }; From 31d6b7ba65cda48b5ee088e8e4649c7bd752e4e7 Mon Sep 17 00:00:00 2001 From: Krzysztof Magiera Date: Wed, 14 Dec 2022 23:55:50 +0100 Subject: [PATCH 22/23] Make debugging via chrome:inspect work --- __tests__/__snapshots__/plugin.test.js.snap | 27 ++++++++++++++++- plugin.js | 17 +++++++++++ src/reanimated2/initializers.ts | 33 ++++++++++++++++----- 3 files changed, 69 insertions(+), 8 deletions(-) diff --git a/__tests__/__snapshots__/plugin.test.js.snap b/__tests__/__snapshots__/plugin.test.js.snap index 255ee4b75aab..babaa71a1306 100644 --- a/__tests__/__snapshots__/plugin.test.js.snap +++ b/__tests__/__snapshots__/plugin.test.js.snap @@ -23,11 +23,11 @@ var f = function () { }; _f.asString = \\"function f(){const{x,objX}=this._closure;return{res:x+objX.x};}\\"; _f.__workletHash = 5359970077727; + _f.__location = \\"${ process.cwd() }/jest tests fixture\\"; _f.__stackDetails = _e; return _f; }();" `; - exports[`babel plugin doesn't capture globals 1`] = ` "var f = function () { var _e = [new Error(), 1, -20]; @@ -39,6 +39,7 @@ exports[`babel plugin doesn't capture globals 1`] = ` _f._closure = {}; _f.asString = \\"function f(){console.log('test');}\\"; _f.__workletHash = 13298016111221; + _f.__location = \\"${ process.cwd() }/jest tests fixture\\"; _f.__stackDetails = _e; return _f; }();" @@ -62,6 +63,7 @@ exports[`babel plugin doesn't transform string literals 1`] = ` _f._closure = {}; _f.asString = \\"function foo(x){const bar='worklet';const baz=\\\\\\"worklet\\\\\\";}\\"; _f.__workletHash = 9810417751380; + _f.__location = \\"${ process.cwd() }/jest tests fixture\\"; _f.__stackDetails = _e; return _f; }();" @@ -84,6 +86,7 @@ var foo = function () { }; _f.asString = \\"function foo(t){const foo=this._recur;const{a}=this._closure;if(t>0){return a+foo(t-1);}}\\"; _f.__workletHash = 2022702330805; + _f.__location = \\"${ process.cwd() }/jest tests fixture\\"; _f.__stackDetails = _e; return _f; }();" @@ -114,6 +117,7 @@ function Box() { }; _f.asString = \\"function anonymous(){const{offset}=this._closure;return{transform:[{translateX:offset.value*255}]};}\\"; _f.__workletHash = 16669311443114; + _f.__location = \\"${ process.cwd() }/jest tests fixture\\"; _f.__stackDetails = _e; _f.__optimalization = 3; return _f; @@ -141,6 +145,7 @@ exports[`babel plugin transforms spread operator in worklets for arrays 1`] = ` _f._closure = {}; _f.asString = \\"function foo(){const bar=[4,5];const baz=[1,...[2,3],...bar];}\\"; _f.__workletHash = 3161057533258; + _f.__location = \\"${ process.cwd() }/jest tests fixture\\"; _f.__stackDetails = _e; return _f; }();" @@ -161,6 +166,7 @@ exports[`babel plugin transforms spread operator in worklets for function argume _f._closure = {}; _f.asString = \\"function foo(...args){console.log(args);}\\"; _f.__workletHash = 9866931756941; + _f.__location = \\"${ process.cwd() }/jest tests fixture\\"; _f.__stackDetails = _e; return _f; }();" @@ -183,6 +189,7 @@ var foo = function () { _f._closure = {}; _f.asString = \\"function foo(arg){console.log(...arg);}\\"; _f.__workletHash = 2015887751437; + _f.__location = \\"${ process.cwd() }/jest tests fixture\\"; _f.__stackDetails = _e; return _f; }();" @@ -208,6 +215,7 @@ exports[`babel plugin transforms spread operator in worklets for objects 1`] = ` _f._closure = {}; _f.asString = \\"function foo(){const bar={d:4,e:5};const baz={a:1,...{b:2,c:3},...bar};}\\"; _f.__workletHash = 792186851025; + _f.__location = \\"${ process.cwd() }/jest tests fixture\\"; _f.__stackDetails = _e; return _f; }();" @@ -224,6 +232,7 @@ exports[`babel plugin workletizes ArrowFunctionExpression 1`] = ` _f._closure = {}; _f.asString = \\"function anonymous(x){return x+2;}\\"; _f.__workletHash = 16347365292089; + _f.__location = \\"${ process.cwd() }/jest tests fixture\\"; _f.__stackDetails = _e; return _f; }();" @@ -240,6 +249,7 @@ exports[`babel plugin workletizes FunctionDeclaration 1`] = ` _f._closure = {}; _f.asString = \\"function foo(x){return x+2;}\\"; _f.__workletHash = 4679479961836; + _f.__location = \\"${ process.cwd() }/jest tests fixture\\"; _f.__stackDetails = _e; return _f; }();" @@ -271,6 +281,7 @@ var Foo = function () { }; _f.asString = \\"function get(){const{x}=this._closure;return x+2;}\\"; _f.__workletHash = 10436985806815; + _f.__location = \\"${ process.cwd() }/jest tests fixture\\"; _f.__stackDetails = _e; return _f; }() @@ -292,6 +303,7 @@ exports[`babel plugin workletizes hook wrapped ArrowFunctionExpression automatic _f._closure = {}; _f.asString = \\"function anonymous(){return{width:50};}\\"; _f.__workletHash = 9645206935615; + _f.__location = \\"${ process.cwd() }/jest tests fixture\\"; _f.__stackDetails = _e; _f.__optimalization = 3; return _f; @@ -311,6 +323,7 @@ exports[`babel plugin workletizes hook wrapped named FunctionExpression automati _f._closure = {}; _f.asString = \\"function foo(){return{width:50};}\\"; _f.__workletHash = 6275510763626; + _f.__location = \\"${ process.cwd() }/jest tests fixture\\"; _f.__stackDetails = _e; _f.__optimalization = 3; return _f; @@ -330,6 +343,7 @@ exports[`babel plugin workletizes hook wrapped unnamed FunctionExpression automa _f._closure = {}; _f.asString = \\"function anonymous(){return{width:50};}\\"; _f.__workletHash = 9645206935615; + _f.__location = \\"${ process.cwd() }/jest tests fixture\\"; _f.__stackDetails = _e; _f.__optimalization = 3; return _f; @@ -360,6 +374,7 @@ var Foo = function () { _f._closure = {}; _f.asString = \\"function bar(x){return x+2;}\\"; _f.__workletHash = 16974800582491; + _f.__location = \\"${ process.cwd() }/jest tests fixture\\"; _f.__stackDetails = _e; return _f; }() @@ -379,6 +394,7 @@ exports[`babel plugin workletizes named FunctionExpression 1`] = ` _f._closure = {}; _f.asString = \\"function foo(x){return x+2;}\\"; _f.__workletHash = 4679479961836; + _f.__location = \\"${ process.cwd() }/jest tests fixture\\"; _f.__stackDetails = _e; return _f; }();" @@ -396,6 +412,7 @@ exports[`babel plugin workletizes object hook wrapped ArrowFunctionExpression au _f._closure = {}; _f.asString = \\"function anonymous(event){console.log(event);}\\"; _f.__workletHash = 1022605193782; + _f.__location = \\"${ process.cwd() }/jest tests fixture\\"; _f.__stackDetails = _e; return _f; }() @@ -414,6 +431,7 @@ exports[`babel plugin workletizes object hook wrapped ObjectMethod automatically _f._closure = {}; _f.asString = \\"function onStart(event){console.log(event);}\\"; _f.__workletHash = 338158776260; + _f.__location = \\"${ process.cwd() }/jest tests fixture\\"; _f.__stackDetails = _e; return _f; }() @@ -432,6 +450,7 @@ exports[`babel plugin workletizes object hook wrapped named FunctionExpression a _f._closure = {}; _f.asString = \\"function onStart(event){console.log(event);}\\"; _f.__workletHash = 338158776260; + _f.__location = \\"${ process.cwd() }/jest tests fixture\\"; _f.__stackDetails = _e; return _f; }() @@ -450,6 +469,7 @@ exports[`babel plugin workletizes object hook wrapped unnamed FunctionExpression _f._closure = {}; _f.asString = \\"function anonymous(event){console.log(event);}\\"; _f.__workletHash = 1022605193782; + _f.__location = \\"${ process.cwd() }/jest tests fixture\\"; _f.__stackDetails = _e; return _f; }() @@ -469,6 +489,7 @@ var foo = _reactNativeGestureHandler.Gesture.Tap().numberOfTaps(2).onBegin(funct _f._closure = {}; _f.asString = \\"function anonymous(){console.log('onBegin');}\\"; _f.__workletHash = 15393478329680; + _f.__location = \\"${ process.cwd() }/jest tests fixture\\"; _f.__stackDetails = _e; return _f; }()).onStart(function () { @@ -481,6 +502,7 @@ var foo = _reactNativeGestureHandler.Gesture.Tap().numberOfTaps(2).onBegin(funct _f._closure = {}; _f.asString = \\"function anonymous(_event){console.log('onStart');}\\"; _f.__workletHash = 12748187344900; + _f.__location = \\"${ process.cwd() }/jest tests fixture\\"; _f.__stackDetails = _e; return _f; }()).onEnd(function () { @@ -493,6 +515,7 @@ var foo = _reactNativeGestureHandler.Gesture.Tap().numberOfTaps(2).onBegin(funct _f._closure = {}; _f.asString = \\"function anonymous(_event,_success){console.log('onEnd');}\\"; _f.__workletHash = 232586479291; + _f.__location = \\"${ process.cwd() }/jest tests fixture\\"; _f.__stackDetails = _e; return _f; }());" @@ -522,6 +545,7 @@ var Foo = function () { _f._closure = {}; _f.asString = \\"function bar(x){return x+2;}\\"; _f.__workletHash = 16974800582491; + _f.__location = \\"${ process.cwd() }/jest tests fixture\\"; _f.__stackDetails = _e; return _f; }() @@ -541,6 +565,7 @@ exports[`babel plugin workletizes unnamed FunctionExpression 1`] = ` _f._closure = {}; _f.asString = \\"function anonymous(x){return x+2;}\\"; _f.__workletHash = 16347365292089; + _f.__location = \\"${ process.cwd() }/jest tests fixture\\"; _f.__stackDetails = _e; return _f; }();" diff --git a/plugin.js b/plugin.js index c06d071a9e1f..7435f4939aae 100644 --- a/plugin.js +++ b/plugin.js @@ -536,6 +536,12 @@ function makeWorklet(t, fun, state) { ); const workletHash = hash(funString); + let location = state.file.opts.filename; + if (state.opts && state.opts.relativeSourceLocation) { + const path = require('path'); + location = path.relative(state.cwd, location); + } + let lineOffset = 1; if (closure.size > 0) { // When worklet captures some variables, we append closure destructing at @@ -575,6 +581,17 @@ function makeWorklet(t, fun, state) { t.numericLiteral(workletHash) ) ), + t.expressionStatement( + t.assignmentExpression( + '=', + t.memberExpression( + privateFunctionId, + t.identifier('__location'), + false + ), + t.stringLiteral(location) + ) + ), ]; if (sourceMapString) { diff --git a/src/reanimated2/initializers.ts b/src/reanimated2/initializers.ts index 0cbdf39d2c79..52ad713b201e 100644 --- a/src/reanimated2/initializers.ts +++ b/src/reanimated2/initializers.ts @@ -31,13 +31,32 @@ function valueUnpacker(objectToUnpack: any, category?: string): any { if (objectToUnpack.__workletHash) { let workletFun = workletsCache.get(objectToUnpack.__workletHash); if (workletFun === undefined) { - const evalFn = - global.evalWithSourceMap || global.evalWithSourceUrl || eval; // eslint-disable-line no-eval - workletFun = evalFn( - '(' + objectToUnpack.asString + '\n)', - `worklet_${objectToUnpack.__workletHash}`, - objectToUnpack.__sourceMap - ) as (...args: any[]) => any; + if (global.evalWithSourceMap) { + // if the runtime (hermes only for now) supports loading source maps + // we want to use the proper filename for the location as that guarantees + // that debugger understands and loads the source code of the file where + // the worklet is defined. + workletFun = global.evalWithSourceMap( + '(' + objectToUnpack.asString + '\n)', + objectToUnpack.__location, + objectToUnpack.__sourceMap + ) as (...args: any[]) => any; + } else if (global.evalWithSourceUrl) { + // if the runtime doesn't support loading source maps, in dev mode we + // can pass source url when evaluating the worklet. Now, instead of using + // the actual file location we use worklet hash, as it the allows us to + // properly symbolicate traces (see errors.ts for details) + workletFun = global.evalWithSourceUrl( + '(' + objectToUnpack.asString + '\n)', + `worklet_${objectToUnpack.__workletHash}` + ) as (...args: any[]) => any; + } else { + // in release we use the regular eval to save on JSI calls + // eslint-disable-next-line no-eval + workletFun = eval('(' + objectToUnpack.asString + '\n)') as ( + ...args: any[] + ) => any; + } workletsCache.set(objectToUnpack.__workletHash, workletFun); } const functionInstance = workletFun.bind(objectToUnpack); From 294c26d8edee46a934ef232e5fe52311635047bf Mon Sep 17 00:00:00 2001 From: Krzysztof Magiera Date: Thu, 15 Dec 2022 10:37:12 +0100 Subject: [PATCH 23/23] Fix typos --- plugin.js | 2 +- src/reanimated2/initializers.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/plugin.js b/plugin.js index 7435f4939aae..73bbda0122b6 100644 --- a/plugin.js +++ b/plugin.js @@ -547,7 +547,7 @@ function makeWorklet(t, fun, state) { // When worklet captures some variables, we append closure destructing at // the beginning of the function body. This effectively results in line // numbers shifting by the number of captured variables (size of the - // closure) + 2 (for the opening and closing bracets of the destruct + // closure) + 2 (for the opening and closing brackets of the destruct // statement) lineOffset -= closure.size + 2; } diff --git a/src/reanimated2/initializers.ts b/src/reanimated2/initializers.ts index 52ad713b201e..b7d85487bfe2 100644 --- a/src/reanimated2/initializers.ts +++ b/src/reanimated2/initializers.ts @@ -33,7 +33,7 @@ function valueUnpacker(objectToUnpack: any, category?: string): any { if (workletFun === undefined) { if (global.evalWithSourceMap) { // if the runtime (hermes only for now) supports loading source maps - // we want to use the proper filename for the location as that guarantees + // we want to use the proper filename for the location as it guarantees // that debugger understands and loads the source code of the file where // the worklet is defined. workletFun = global.evalWithSourceMap(