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
|
// Call the Frame Processor on the Worklet Runtime
|
||||||
jsi::Runtime& runtime = _workletContext->getWorkletRuntime();
|
jsi::Runtime& runtime = _workletContext->getWorkletRuntime();
|
||||||
|
|
||||||
try {
|
// Wrap HostObject as JSI Value
|
||||||
// Wrap HostObject as JSI Value
|
auto argument = jsi::Object::createFromHostObject(runtime, frameHostObject);
|
||||||
auto argument = jsi::Object::createFromHostObject(runtime, frameHostObject);
|
jsi::Value jsValue(std::move(argument));
|
||||||
jsi::Value jsValue(std::move(argument));
|
|
||||||
|
|
||||||
// Call the Worklet with the Frame JS Host Object as an argument
|
// Call the Worklet with the Frame JS Host Object as an argument
|
||||||
_workletInvoker->call(runtime, jsi::Value::undefined(), &jsValue, 1);
|
_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));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void JFrameProcessor::call(jni::alias_ref<JFrame::javaobject> frame) {
|
void JFrameProcessor::call(jni::alias_ref<JFrame::javaobject> frame) {
|
||||||
|
@ -34,22 +34,12 @@ using namespace facebook;
|
|||||||
// Call the Frame Processor on the Worklet Runtime
|
// Call the Frame Processor on the Worklet Runtime
|
||||||
jsi::Runtime& runtime = _workletContext->getWorkletRuntime();
|
jsi::Runtime& runtime = _workletContext->getWorkletRuntime();
|
||||||
|
|
||||||
try {
|
// Wrap HostObject as JSI Value
|
||||||
// Wrap HostObject as JSI Value
|
auto argument = jsi::Object::createFromHostObject(runtime, frameHostObject);
|
||||||
auto argument = jsi::Object::createFromHostObject(runtime, frameHostObject);
|
jsi::Value jsValue(std::move(argument));
|
||||||
jsi::Value jsValue(std::move(argument));
|
|
||||||
|
|
||||||
// Call the Worklet with the Frame JS Host Object as an argument
|
// Call the Worklet with the Frame JS Host Object as an argument
|
||||||
_workletInvoker->call(runtime, jsi::Value::undefined(), &jsValue, 1);
|
_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));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)call:(Frame* _Nonnull)frame {
|
- (void)call:(Frame* _Nonnull)frame {
|
||||||
|
@ -35,6 +35,10 @@ interface TVisionCameraProxy {
|
|||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
initFrameProcessorPlugin: (name: string, options?: Record<string, ParameterType>) => FrameProcessorPlugin | undefined
|
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!'
|
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 => {
|
let runOnAsyncContext = (_frame: Frame, _func: () => void): void => {
|
||||||
throw new CameraRuntimeError('system/frame-processors-unavailable', errorMessage)
|
throw new CameraRuntimeError('system/frame-processors-unavailable', errorMessage)
|
||||||
}
|
}
|
||||||
|
let throwJSError = (error: unknown): void => {
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
assertJSIAvailable()
|
assertJSIAvailable()
|
||||||
@ -51,6 +58,24 @@ try {
|
|||||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||||
const { Worklets } = require('react-native-worklets-core') as typeof TWorklets
|
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)
|
isAsyncContextBusy = Worklets.createSharedValue(false)
|
||||||
const asyncContext = Worklets.createContext('VisionCamera.async')
|
const asyncContext = Worklets.createContext('VisionCamera.async')
|
||||||
runOnAsyncContext = Worklets.createRunInContextFn((frame: Frame, func: () => void) => {
|
runOnAsyncContext = Worklets.createRunInContextFn((frame: Frame, func: () => void) => {
|
||||||
@ -58,6 +83,9 @@ try {
|
|||||||
try {
|
try {
|
||||||
// Call long-running function
|
// Call long-running function
|
||||||
func()
|
func()
|
||||||
|
} catch (e) {
|
||||||
|
// Re-throw error on JS Thread
|
||||||
|
throwJSError(e)
|
||||||
} finally {
|
} finally {
|
||||||
// Potentially delete Frame if we were the last ref
|
// Potentially delete Frame if we were the last ref
|
||||||
const internal = frame as FrameInternal
|
const internal = frame as FrameInternal
|
||||||
@ -81,6 +109,7 @@ let proxy: TVisionCameraProxy = {
|
|||||||
setFrameProcessor: () => {
|
setFrameProcessor: () => {
|
||||||
throw new CameraRuntimeError('system/frame-processors-unavailable', errorMessage)
|
throw new CameraRuntimeError('system/frame-processors-unavailable', errorMessage)
|
||||||
},
|
},
|
||||||
|
throwJSError: throwJSError,
|
||||||
}
|
}
|
||||||
if (hasWorklets) {
|
if (hasWorklets) {
|
||||||
// Install native Frame Processor Runtime Manager
|
// Install native Frame Processor Runtime Manager
|
||||||
@ -103,6 +132,7 @@ export const VisionCameraProxy: TVisionCameraProxy = {
|
|||||||
initFrameProcessorPlugin: proxy.initFrameProcessorPlugin,
|
initFrameProcessorPlugin: proxy.initFrameProcessorPlugin,
|
||||||
removeFrameProcessor: proxy.removeFrameProcessor,
|
removeFrameProcessor: proxy.removeFrameProcessor,
|
||||||
setFrameProcessor: proxy.setFrameProcessor,
|
setFrameProcessor: proxy.setFrameProcessor,
|
||||||
|
throwJSError: throwJSError,
|
||||||
// TODO: Remove this in the next version
|
// TODO: Remove this in the next version
|
||||||
// @ts-expect-error
|
// @ts-expect-error
|
||||||
getFrameProcessorPlugin: (name, options) => {
|
getFrameProcessorPlugin: (name, options) => {
|
||||||
@ -116,6 +146,12 @@ export const VisionCameraProxy: TVisionCameraProxy = {
|
|||||||
declare global {
|
declare global {
|
||||||
// eslint-disable-next-line no-var
|
// eslint-disable-next-line no-var
|
||||||
var __frameProcessorRunAtTargetFpsMap: Record<string, number | undefined> | undefined
|
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 {
|
function getLastFrameProcessorCall(frameProcessorFuncId: string): number {
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { DependencyList, useMemo } from 'react'
|
import { DependencyList, useMemo } from 'react'
|
||||||
import type { Frame, FrameInternal } from '../Frame'
|
import type { Frame, FrameInternal } from '../Frame'
|
||||||
import { FrameProcessor } from '../CameraProps'
|
import { FrameProcessor } from '../CameraProps'
|
||||||
|
import { VisionCameraProxy } from '../FrameProcessorPlugins'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new Frame Processor function which you can pass to the `<Camera>`.
|
* Create a new Frame Processor function which you can pass to the `<Camera>`.
|
||||||
@ -20,6 +21,9 @@ export function createFrameProcessor(frameProcessor: FrameProcessor['frameProces
|
|||||||
try {
|
try {
|
||||||
// Call sync frame processor
|
// Call sync frame processor
|
||||||
frameProcessor(frame)
|
frameProcessor(frame)
|
||||||
|
} catch (e) {
|
||||||
|
// Re-throw error on JS Thread
|
||||||
|
VisionCameraProxy.throwJSError(e)
|
||||||
} finally {
|
} finally {
|
||||||
// Potentially delete Frame if we were the last ref (no runAsync)
|
// Potentially delete Frame if we were the last ref (no runAsync)
|
||||||
internal.decrementRefCount()
|
internal.decrementRefCount()
|
||||||
|
Loading…
Reference in New Issue
Block a user