fix: Fix runAtTargetFps for multiple invocations per FP

This commit is contained in:
Marc Rousavy 2023-03-21 16:10:09 +01:00
parent e1973b9b8d
commit af4e366312
3 changed files with 26 additions and 10 deletions

View File

@ -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<CameraProps> {
* 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!');
}

View File

@ -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<string, number | undefined> | undefined;
}
type BasicParameterType = string | number | boolean | undefined;
type ParameterType = BasicParameterType | BasicParameterType[] | Record<string, BasicParameterType | undefined>;
type FrameProcessor = (frame: Frame, parameters?: Record<string, ParameterType | undefined>) => unknown;
@ -19,7 +21,15 @@ type TFrameProcessorPlugins = Record<string, FrameProcessor>;
// @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<T>(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();
}

View File

@ -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(