feat: Re-throw error on JS side instead of just logging on native side (#2366)
* feat: Re-throw error on JS side instead of just logging on native side * fix: Fix proxy * fix: Fix app crash by only logging error * fix: Use `global.ErrorUtils` (from reanimated)
This commit is contained in:
parent
eb14aa1402
commit
34c5b11927
@ -36,22 +36,12 @@ void JFrameProcessor::callWithFrameHostObject(const std::shared_ptr<FrameHostObj
|
||||
// Call the Frame Processor on the Worklet Runtime
|
||||
jsi::Runtime& runtime = _workletContext->getWorkletRuntime();
|
||||
|
||||
try {
|
||||
// Wrap HostObject as JSI Value
|
||||
auto argument = jsi::Object::createFromHostObject(runtime, frameHostObject);
|
||||
jsi::Value jsValue(std::move(argument));
|
||||
// Wrap HostObject as JSI Value
|
||||
auto argument = jsi::Object::createFromHostObject(runtime, frameHostObject);
|
||||
jsi::Value jsValue(std::move(argument));
|
||||
|
||||
// Call the Worklet with the Frame JS Host Object as an argument
|
||||
_workletInvoker->call(runtime, jsi::Value::undefined(), &jsValue, 1);
|
||||
} catch (jsi::JSError& jsError) {
|
||||
// JS Error occured, print it to console.
|
||||
const std::string& message = jsError.getMessage();
|
||||
|
||||
_workletContext->invokeOnJsThread([message](jsi::Runtime& jsRuntime) {
|
||||
auto logFn = jsRuntime.global().getPropertyAsObject(jsRuntime, "console").getPropertyAsFunction(jsRuntime, "error");
|
||||
logFn.call(jsRuntime, jsi::String::createFromUtf8(jsRuntime, "Frame Processor threw an error: " + message));
|
||||
});
|
||||
}
|
||||
// Call the Worklet with the Frame JS Host Object as an argument
|
||||
_workletInvoker->call(runtime, jsi::Value::undefined(), &jsValue, 1);
|
||||
}
|
||||
|
||||
void JFrameProcessor::call(jni::alias_ref<JFrame::javaobject> frame) {
|
||||
|
@ -34,22 +34,12 @@ using namespace facebook;
|
||||
// Call the Frame Processor on the Worklet Runtime
|
||||
jsi::Runtime& runtime = _workletContext->getWorkletRuntime();
|
||||
|
||||
try {
|
||||
// Wrap HostObject as JSI Value
|
||||
auto argument = jsi::Object::createFromHostObject(runtime, frameHostObject);
|
||||
jsi::Value jsValue(std::move(argument));
|
||||
// Wrap HostObject as JSI Value
|
||||
auto argument = jsi::Object::createFromHostObject(runtime, frameHostObject);
|
||||
jsi::Value jsValue(std::move(argument));
|
||||
|
||||
// Call the Worklet with the Frame JS Host Object as an argument
|
||||
_workletInvoker->call(runtime, jsi::Value::undefined(), &jsValue, 1);
|
||||
} catch (jsi::JSError& jsError) {
|
||||
// JS Error occured, print it to console.
|
||||
auto message = jsError.getMessage();
|
||||
|
||||
_workletContext->invokeOnJsThread([message](jsi::Runtime& jsRuntime) {
|
||||
auto logFn = jsRuntime.global().getPropertyAsObject(jsRuntime, "console").getPropertyAsFunction(jsRuntime, "error");
|
||||
logFn.call(jsRuntime, jsi::String::createFromUtf8(jsRuntime, "Frame Processor threw an error: " + message));
|
||||
});
|
||||
}
|
||||
// Call the Worklet with the Frame JS Host Object as an argument
|
||||
_workletInvoker->call(runtime, jsi::Value::undefined(), &jsValue, 1);
|
||||
}
|
||||
|
||||
- (void)call:(Frame* _Nonnull)frame {
|
||||
|
@ -35,6 +35,10 @@ interface TVisionCameraProxy {
|
||||
* ```
|
||||
*/
|
||||
initFrameProcessorPlugin: (name: string, options?: Record<string, ParameterType>) => FrameProcessorPlugin | undefined
|
||||
/**
|
||||
* Throws the given error.
|
||||
*/
|
||||
throwJSError: (error: unknown) => void
|
||||
}
|
||||
|
||||
const errorMessage = 'Frame Processors are not available, react-native-worklets-core is not installed!'
|
||||
@ -44,6 +48,9 @@ let isAsyncContextBusy = { value: false }
|
||||
let runOnAsyncContext = (_frame: Frame, _func: () => void): void => {
|
||||
throw new CameraRuntimeError('system/frame-processors-unavailable', errorMessage)
|
||||
}
|
||||
let throwJSError = (error: unknown): void => {
|
||||
throw error
|
||||
}
|
||||
|
||||
try {
|
||||
assertJSIAvailable()
|
||||
@ -51,6 +58,24 @@ try {
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const { Worklets } = require('react-native-worklets-core') as typeof TWorklets
|
||||
|
||||
const throwErrorOnJS = Worklets.createRunInJsFn((message: string, stack: string | undefined) => {
|
||||
const error = new Error()
|
||||
error.message = message
|
||||
error.stack = stack
|
||||
error.name = 'Frame Processor Error'
|
||||
// @ts-expect-error this is react-native specific
|
||||
error.jsEngine = 'VisionCamera'
|
||||
// From react-native:
|
||||
// @ts-ignore the reportFatalError method is an internal method of ErrorUtils not exposed in the type definitions
|
||||
global.ErrorUtils.reportFatalError(error)
|
||||
})
|
||||
throwJSError = (error) => {
|
||||
'worklet'
|
||||
const safeError = error as Error | undefined
|
||||
const message = safeError != null && 'message' in safeError ? safeError.message : 'Frame Processor threw an error.'
|
||||
throwErrorOnJS(message, safeError?.stack)
|
||||
}
|
||||
|
||||
isAsyncContextBusy = Worklets.createSharedValue(false)
|
||||
const asyncContext = Worklets.createContext('VisionCamera.async')
|
||||
runOnAsyncContext = Worklets.createRunInContextFn((frame: Frame, func: () => void) => {
|
||||
@ -58,6 +83,9 @@ try {
|
||||
try {
|
||||
// Call long-running function
|
||||
func()
|
||||
} catch (e) {
|
||||
// Re-throw error on JS Thread
|
||||
throwJSError(e)
|
||||
} finally {
|
||||
// Potentially delete Frame if we were the last ref
|
||||
const internal = frame as FrameInternal
|
||||
@ -81,6 +109,7 @@ let proxy: TVisionCameraProxy = {
|
||||
setFrameProcessor: () => {
|
||||
throw new CameraRuntimeError('system/frame-processors-unavailable', errorMessage)
|
||||
},
|
||||
throwJSError: throwJSError,
|
||||
}
|
||||
if (hasWorklets) {
|
||||
// Install native Frame Processor Runtime Manager
|
||||
@ -103,6 +132,7 @@ export const VisionCameraProxy: TVisionCameraProxy = {
|
||||
initFrameProcessorPlugin: proxy.initFrameProcessorPlugin,
|
||||
removeFrameProcessor: proxy.removeFrameProcessor,
|
||||
setFrameProcessor: proxy.setFrameProcessor,
|
||||
throwJSError: throwJSError,
|
||||
// TODO: Remove this in the next version
|
||||
// @ts-expect-error
|
||||
getFrameProcessorPlugin: (name, options) => {
|
||||
@ -116,6 +146,12 @@ export const VisionCameraProxy: TVisionCameraProxy = {
|
||||
declare global {
|
||||
// eslint-disable-next-line no-var
|
||||
var __frameProcessorRunAtTargetFpsMap: Record<string, number | undefined> | undefined
|
||||
// eslint-disable-next-line no-var
|
||||
var __ErrorUtils:
|
||||
| {
|
||||
reportFatalError: (error: unknown) => void
|
||||
}
|
||||
| undefined
|
||||
}
|
||||
|
||||
function getLastFrameProcessorCall(frameProcessorFuncId: string): number {
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { DependencyList, useMemo } from 'react'
|
||||
import type { Frame, FrameInternal } from '../Frame'
|
||||
import { FrameProcessor } from '../CameraProps'
|
||||
import { VisionCameraProxy } from '../FrameProcessorPlugins'
|
||||
|
||||
/**
|
||||
* Create a new Frame Processor function which you can pass to the `<Camera>`.
|
||||
@ -20,6 +21,9 @@ export function createFrameProcessor(frameProcessor: FrameProcessor['frameProces
|
||||
try {
|
||||
// Call sync frame processor
|
||||
frameProcessor(frame)
|
||||
} catch (e) {
|
||||
// Re-throw error on JS Thread
|
||||
VisionCameraProxy.throwJSError(e)
|
||||
} finally {
|
||||
// Potentially delete Frame if we were the last ref (no runAsync)
|
||||
internal.decrementRefCount()
|
||||
|
Loading…
Reference in New Issue
Block a user