From af4e366312a2335535f80109e6bf0b3a977444f0 Mon Sep 17 00:00:00 2001 From: Marc Rousavy Date: Tue, 21 Mar 2023 16:10:09 +0100 Subject: [PATCH] fix: Fix `runAtTargetFps` for multiple invocations per FP --- src/Camera.tsx | 3 ++- src/FrameProcessorPlugins.ts | 31 ++++++++++++++++++++++--------- src/JSIHelper.ts | 2 ++ 3 files changed, 26 insertions(+), 10 deletions(-) diff --git a/src/Camera.tsx b/src/Camera.tsx index 07427e2..6dffae0 100644 --- a/src/Camera.tsx +++ b/src/Camera.tsx @@ -6,7 +6,7 @@ import type { ErrorWithCause } from './CameraError'; import { CameraCaptureError, CameraRuntimeError, tryParseNativeCameraError, isErrorWithCause } from './CameraError'; import type { CameraProps } from './CameraProps'; import type { Frame } from './Frame'; -import { assertFrameProcessorsAvailable } from './JSIHelper'; +import { assertFrameProcessorsAvailable, assertJSIAvailable } from './JSIHelper'; import { CameraModule } from './NativeCameraModule'; import type { PhotoFile, TakePhotoOptions } from './PhotoFile'; import type { Point } from './Point'; @@ -310,6 +310,7 @@ export class Camera extends React.PureComponent { * Install JSI Bindings for Frame Processors */ public static installFrameProcessorBindings(): void { + assertJSIAvailable(); const result = CameraModule.installFrameProcessorBindings() as unknown; if (result !== true) throw new Error('Failed to install Frame Processor JSI bindings!'); } diff --git a/src/FrameProcessorPlugins.ts b/src/FrameProcessorPlugins.ts index e30f19c..b5afad0 100644 --- a/src/FrameProcessorPlugins.ts +++ b/src/FrameProcessorPlugins.ts @@ -1,13 +1,15 @@ import type { Frame, FrameInternal } from './Frame'; import { Camera } from './Camera'; import { Worklets } from 'react-native-worklets/src'; -import { assertJSIAvailable } from './JSIHelper'; - -assertJSIAvailable(); // Install VisionCamera Frame Processor JSI Bindings and Plugins Camera.installFrameProcessorBindings(); +declare global { + // eslint-disable-next-line no-var + var __frameProcessorRunAtTargetFpsMap: Record | undefined; +} + type BasicParameterType = string | number | boolean | undefined; type ParameterType = BasicParameterType | BasicParameterType[] | Record; type FrameProcessor = (frame: Frame, parameters?: Record) => unknown; @@ -19,7 +21,15 @@ type TFrameProcessorPlugins = Record; // @ts-expect-error The global JSI Proxy object is not typed. export const FrameProcessorPlugins = global.FrameProcessorPlugins as TFrameProcessorPlugins; -const lastFrameProcessorCall = Worklets.createSharedValue(performance.now()); +function getLastFrameProcessorCall(frameProcessorFuncId: string): number { + 'worklet'; + return global.__frameProcessorRunAtTargetFpsMap?.[frameProcessorFuncId] ?? 0; +} +function setLastFrameProcessorCall(frameProcessorFuncId: string, value: number): void { + 'worklet'; + if (global.__frameProcessorRunAtTargetFpsMap == null) global.__frameProcessorRunAtTargetFpsMap = {}; + global.__frameProcessorRunAtTargetFpsMap[frameProcessorFuncId] = value; +} /** * Runs the given function at the given target FPS rate. @@ -37,22 +47,25 @@ const lastFrameProcessorCall = Worklets.createSharedValue(performance.now()); * const frameProcessor = useFrameProcessor((frame) => { * 'worklet' * console.log('New Frame') - * const face = runAtTargetFps(5, () => { + * runAtTargetFps(5, () => { * 'worklet' * const faces = detectFaces(frame) - * return faces[0] + * console.log(`Detected a new face: ${faces[0]}`) * }) - * if (face != null) console.log(`Detected a new face: ${face}`) * }) * ``` */ export function runAtTargetFps(fps: number, func: () => T): T | undefined { 'worklet'; + // @ts-expect-error + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const funcId = func.__workletHash ?? '1'; + const targetIntervalMs = 1000 / fps; // <-- 60 FPS => 16,6667ms interval const now = performance.now(); - const diffToLastCall = now - lastFrameProcessorCall.value; + const diffToLastCall = now - getLastFrameProcessorCall(funcId); if (diffToLastCall >= targetIntervalMs) { - lastFrameProcessorCall.value = now; + setLastFrameProcessorCall(funcId, now); // Last Frame Processor call is already so long ago that we want to make a new call return func(); } diff --git a/src/JSIHelper.ts b/src/JSIHelper.ts index 1634c2b..83a08ce 100644 --- a/src/JSIHelper.ts +++ b/src/JSIHelper.ts @@ -12,6 +12,8 @@ export function assertJSIAvailable(): void { } export function assertFrameProcessorsAvailable(): void { + assertJSIAvailable(); + // @ts-expect-error JSI functions aren't typed if (global.setFrameProcessor == null || global.unsetFrameProcessor == null) { throw new CameraRuntimeError(