From ecf719ab4d73705744a7cbed6067b16d503d4af7 Mon Sep 17 00:00:00 2001 From: Krzysztof Magiera Date: Wed, 30 Nov 2022 17:18:40 +0100 Subject: [PATCH] Better error message when trying to call non-worklet from UI thread (#3821) ## Summary After #3722 the error message presented after attempting to synchronously call React runtime function from the UI runtime has changed and became less descriptive than before. This PR adds a way for us to throw a more descriptive error in such scenario. ## Test plan Use the following code: ``` function rnMethod() { console.log('from RN'); } function uiMethod() { 'worklet'; rnMethod(); // this should throw } runOnUI(uiMethod)(); ``` Before this change you'd get a generic "Object is not a function" thrown at the line where we call `rnMethod`, now the error message says "Tried to synchronously call a non-worklet function" and presents possible solution (see the diff for the whole error message) Co-authored-by: Tomek Zawadzki --- Common/cpp/SharedItems/Shareables.h | 7 +++++++ src/reanimated2/commonTypes.ts | 1 + src/reanimated2/core.ts | 12 +++++++++++- src/reanimated2/threads.ts | 7 +++++++ 4 files changed, 26 insertions(+), 1 deletion(-) diff --git a/Common/cpp/SharedItems/Shareables.h b/Common/cpp/SharedItems/Shareables.h index 4ceddd8f617..0b9ff0e671a 100644 --- a/Common/cpp/SharedItems/Shareables.h +++ b/Common/cpp/SharedItems/Shareables.h @@ -285,7 +285,14 @@ class ShareableRemoteFunction function_(std::move(function)) {} jsi::Value toJSValue(jsi::Runtime &rt) override { if (runtimeHelper_->isUIRuntime(rt)) { +#ifdef DEBUG + return runtimeHelper_->valueUnpacker->call( + rt, + ShareableJSRef::newHostObject(rt, shared_from_this()), + jsi::String::createFromAscii(rt, "RemoteFunction")); +#else return ShareableJSRef::newHostObject(rt, shared_from_this()); +#endif } else { return jsi::Value(rt, function_); } diff --git a/src/reanimated2/commonTypes.ts b/src/reanimated2/commonTypes.ts index 96b0e2af241..891e43cec2e 100644 --- a/src/reanimated2/commonTypes.ts +++ b/src/reanimated2/commonTypes.ts @@ -96,6 +96,7 @@ export interface NativeEvent { export interface ComplexWorkletFunction extends WorkletFunction { (...args: A): R; + __remoteFunction?: (...args: A) => R; } export interface NestedObject { diff --git a/src/reanimated2/core.ts b/src/reanimated2/core.ts index da75c4490bd..68bace96231 100644 --- a/src/reanimated2/core.ts +++ b/src/reanimated2/core.ts @@ -114,7 +114,7 @@ export function getViewProp(viewTag: string, propName: string): Promise { }); } -function valueUnpacker(objectToUnpack: any): any { +function valueUnpacker(objectToUnpack: any, category?: string): any { 'worklet'; let workletsCache = global.__workletsCache; let handleCache = global.__handleCache; @@ -142,6 +142,16 @@ function valueUnpacker(objectToUnpack: any): any { 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'); } diff --git a/src/reanimated2/threads.ts b/src/reanimated2/threads.ts index e4f97f50dbe..006f47ba307 100644 --- a/src/reanimated2/threads.ts +++ b/src/reanimated2/threads.ts @@ -27,6 +27,13 @@ export function runOnJS( fun: ComplexWorkletFunction ): (...args: A) => void { 'worklet'; + if (fun.__remoteFunction) { + // in development mode the function provided as `fun` throws an error message + // such that when someone accidently calls it directly on the UI runtime, they + // see that they should use `runOnJS` instead. To facilitate that we purt the + // reference to the original remote function in the `__remoteFunction` property. + fun = fun.__remoteFunction; + } if (!_WORKLET) { return fun; }