2023-09-26 03:39:17 -06:00
|
|
|
import type { Frame, FrameInternal } from './Frame'
|
|
|
|
import type { FrameProcessor } from './CameraProps'
|
|
|
|
import { CameraRuntimeError } from './CameraError'
|
2023-07-21 09:52:30 -06:00
|
|
|
|
2023-08-23 04:42:38 -06:00
|
|
|
// only import typescript types
|
2023-09-26 03:39:17 -06:00
|
|
|
import type TWorklets from 'react-native-worklets-core'
|
|
|
|
import { CameraModule } from './NativeCameraModule'
|
|
|
|
import { assertJSIAvailable } from './JSIHelper'
|
2023-08-23 04:42:38 -06:00
|
|
|
|
2023-09-26 03:39:17 -06:00
|
|
|
type BasicParameterType = string | number | boolean | undefined
|
|
|
|
type ParameterType = BasicParameterType | BasicParameterType[] | Record<string, BasicParameterType | undefined>
|
2023-07-21 09:52:30 -06:00
|
|
|
|
|
|
|
interface FrameProcessorPlugin {
|
|
|
|
/**
|
|
|
|
* Call the native Frame Processor Plugin with the given Frame and options.
|
|
|
|
* @param frame The Frame from the Frame Processor.
|
|
|
|
* @param options (optional) Additional options. Options will be converted to a native dictionary
|
|
|
|
* @returns (optional) A value returned from the native Frame Processor Plugin (or undefined)
|
|
|
|
*/
|
2023-09-26 03:39:17 -06:00
|
|
|
call: (frame: Frame, options?: Record<string, ParameterType>) => ParameterType
|
2023-07-21 09:52:30 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
interface TVisionCameraProxy {
|
2023-09-26 03:39:17 -06:00
|
|
|
setFrameProcessor: (viewTag: number, frameProcessor: FrameProcessor) => void
|
|
|
|
removeFrameProcessor: (viewTag: number) => void
|
2023-07-21 09:52:30 -06:00
|
|
|
/**
|
2023-10-19 03:19:47 -06:00
|
|
|
* Creates a new instance of a native Frame Processor Plugin.
|
|
|
|
* The Plugin has to be registered on the native side, otherwise this returns `undefined`.
|
|
|
|
* @param name The name of the Frame Processor Plugin. This has to be the same name as on the native side.
|
|
|
|
* @param options (optional) Options, as a native dictionary, passed to the constructor/init-function of the native plugin.
|
|
|
|
* @example
|
|
|
|
* ```ts
|
|
|
|
* const plugin = VisionCameraProxy.initFrameProcessorPlugin('scanFaces', { model: 'fast' })
|
|
|
|
* if (plugin == null) throw new Error("Failed to load scanFaces plugin!")
|
|
|
|
* ```
|
2023-07-21 09:52:30 -06:00
|
|
|
*/
|
2023-10-19 03:19:47 -06:00
|
|
|
initFrameProcessorPlugin: (name: string, options?: Record<string, ParameterType>) => FrameProcessorPlugin | undefined
|
2024-01-11 09:23:38 -07:00
|
|
|
/**
|
|
|
|
* Throws the given error.
|
|
|
|
*/
|
|
|
|
throwJSError: (error: unknown) => void
|
2023-07-21 09:52:30 -06:00
|
|
|
}
|
2023-02-13 07:22:45 -07:00
|
|
|
|
2023-10-24 05:44:03 -06:00
|
|
|
const errorMessage = 'Frame Processors are not available, react-native-worklets-core is not installed!'
|
|
|
|
|
2023-09-26 03:39:17 -06:00
|
|
|
let hasWorklets = false
|
|
|
|
let isAsyncContextBusy = { value: false }
|
2023-08-23 04:42:38 -06:00
|
|
|
let runOnAsyncContext = (_frame: Frame, _func: () => void): void => {
|
2023-10-24 05:44:03 -06:00
|
|
|
throw new CameraRuntimeError('system/frame-processors-unavailable', errorMessage)
|
2023-09-26 03:39:17 -06:00
|
|
|
}
|
2024-01-11 09:23:38 -07:00
|
|
|
let throwJSError = (error: unknown): void => {
|
|
|
|
throw error
|
|
|
|
}
|
2023-08-23 04:42:38 -06:00
|
|
|
|
|
|
|
try {
|
2023-09-26 03:39:17 -06:00
|
|
|
assertJSIAvailable()
|
2023-08-24 02:55:57 -06:00
|
|
|
|
2023-08-23 04:42:38 -06:00
|
|
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
2023-09-26 03:39:17 -06:00
|
|
|
const { Worklets } = require('react-native-worklets-core') as typeof TWorklets
|
2023-08-23 04:42:38 -06:00
|
|
|
|
2024-01-11 09:23:38 -07:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
2023-09-26 03:39:17 -06:00
|
|
|
isAsyncContextBusy = Worklets.createSharedValue(false)
|
|
|
|
const asyncContext = Worklets.createContext('VisionCamera.async')
|
2023-08-23 04:42:38 -06:00
|
|
|
runOnAsyncContext = Worklets.createRunInContextFn((frame: Frame, func: () => void) => {
|
2023-09-26 03:39:17 -06:00
|
|
|
'worklet'
|
2023-08-23 04:42:38 -06:00
|
|
|
try {
|
|
|
|
// Call long-running function
|
2023-09-26 03:39:17 -06:00
|
|
|
func()
|
2024-01-11 09:23:38 -07:00
|
|
|
} catch (e) {
|
|
|
|
// Re-throw error on JS Thread
|
|
|
|
throwJSError(e)
|
2023-08-23 04:42:38 -06:00
|
|
|
} finally {
|
|
|
|
// Potentially delete Frame if we were the last ref
|
2023-09-26 03:39:17 -06:00
|
|
|
const internal = frame as FrameInternal
|
|
|
|
internal.decrementRefCount()
|
2023-08-23 04:42:38 -06:00
|
|
|
|
2023-09-26 03:39:17 -06:00
|
|
|
isAsyncContextBusy.value = false
|
2023-08-23 04:42:38 -06:00
|
|
|
}
|
2023-09-26 03:39:17 -06:00
|
|
|
}, asyncContext)
|
|
|
|
hasWorklets = true
|
2023-08-23 04:42:38 -06:00
|
|
|
} catch (e) {
|
|
|
|
// Worklets are not installed, so Frame Processors are disabled.
|
2023-07-21 09:52:30 -06:00
|
|
|
}
|
|
|
|
|
2023-08-24 08:28:16 -06:00
|
|
|
let proxy: TVisionCameraProxy = {
|
2023-10-19 03:19:47 -06:00
|
|
|
initFrameProcessorPlugin: () => {
|
2023-10-24 05:44:03 -06:00
|
|
|
throw new CameraRuntimeError('system/frame-processors-unavailable', errorMessage)
|
2023-08-24 08:28:16 -06:00
|
|
|
},
|
|
|
|
removeFrameProcessor: () => {
|
2023-10-24 05:44:03 -06:00
|
|
|
throw new CameraRuntimeError('system/frame-processors-unavailable', errorMessage)
|
2023-08-24 08:28:16 -06:00
|
|
|
},
|
|
|
|
setFrameProcessor: () => {
|
2023-10-24 05:44:03 -06:00
|
|
|
throw new CameraRuntimeError('system/frame-processors-unavailable', errorMessage)
|
2023-08-24 08:28:16 -06:00
|
|
|
},
|
2024-01-11 09:23:38 -07:00
|
|
|
throwJSError: throwJSError,
|
2023-09-26 03:39:17 -06:00
|
|
|
}
|
2023-08-24 08:28:16 -06:00
|
|
|
if (hasWorklets) {
|
|
|
|
// Install native Frame Processor Runtime Manager
|
2023-09-26 03:39:17 -06:00
|
|
|
const result = CameraModule.installFrameProcessorBindings() as unknown
|
2023-08-24 08:28:16 -06:00
|
|
|
if (result !== true)
|
2023-09-26 03:39:17 -06:00
|
|
|
throw new CameraRuntimeError('system/frame-processors-unavailable', 'Failed to install Frame Processor JSI bindings!')
|
2023-08-24 08:28:16 -06:00
|
|
|
|
|
|
|
// @ts-expect-error global is untyped, it's a C++ host-object
|
2023-09-26 03:39:17 -06:00
|
|
|
proxy = global.VisionCameraProxy as TVisionCameraProxy
|
2023-08-24 08:28:16 -06:00
|
|
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
|
|
if (proxy == null) {
|
|
|
|
throw new CameraRuntimeError(
|
|
|
|
'system/frame-processors-unavailable',
|
|
|
|
'Failed to install VisionCameraProxy. Are Frame Processors properly enabled?',
|
2023-09-26 03:39:17 -06:00
|
|
|
)
|
2023-08-24 08:28:16 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-10-19 03:35:02 -06:00
|
|
|
export const VisionCameraProxy: TVisionCameraProxy = {
|
|
|
|
initFrameProcessorPlugin: proxy.initFrameProcessorPlugin,
|
|
|
|
removeFrameProcessor: proxy.removeFrameProcessor,
|
|
|
|
setFrameProcessor: proxy.setFrameProcessor,
|
2024-01-11 09:23:38 -07:00
|
|
|
throwJSError: throwJSError,
|
2023-10-19 03:35:02 -06:00
|
|
|
// TODO: Remove this in the next version
|
|
|
|
// @ts-expect-error
|
|
|
|
getFrameProcessorPlugin: (name, options) => {
|
|
|
|
console.warn(
|
|
|
|
'"getFrameProcessorPlugin" has been renamed to "initFrameProcessorPlugin". This function will be removed in the next release.',
|
|
|
|
)
|
|
|
|
return proxy.initFrameProcessorPlugin(name, options)
|
|
|
|
},
|
|
|
|
}
|
2023-08-23 04:42:38 -06:00
|
|
|
|
2023-03-21 09:10:09 -06:00
|
|
|
declare global {
|
|
|
|
// eslint-disable-next-line no-var
|
2023-09-26 03:39:17 -06:00
|
|
|
var __frameProcessorRunAtTargetFpsMap: Record<string, number | undefined> | undefined
|
2024-01-11 09:23:38 -07:00
|
|
|
// eslint-disable-next-line no-var
|
|
|
|
var __ErrorUtils:
|
|
|
|
| {
|
|
|
|
reportFatalError: (error: unknown) => void
|
|
|
|
}
|
|
|
|
| undefined
|
2023-03-21 09:10:09 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
function getLastFrameProcessorCall(frameProcessorFuncId: string): number {
|
2023-09-26 03:39:17 -06:00
|
|
|
'worklet'
|
|
|
|
return global.__frameProcessorRunAtTargetFpsMap?.[frameProcessorFuncId] ?? 0
|
2023-03-21 09:10:09 -06:00
|
|
|
}
|
|
|
|
function setLastFrameProcessorCall(frameProcessorFuncId: string, value: number): void {
|
2023-09-26 03:39:17 -06:00
|
|
|
'worklet'
|
|
|
|
if (global.__frameProcessorRunAtTargetFpsMap == null) global.__frameProcessorRunAtTargetFpsMap = {}
|
|
|
|
global.__frameProcessorRunAtTargetFpsMap[frameProcessorFuncId] = value
|
2023-03-21 09:10:09 -06:00
|
|
|
}
|
feat: Sync Frame Processors (plus `runAsync` and `runAtTargetFps`) (#1472)
Before, Frame Processors ran on a separate Thread.
After, Frame Processors run fully synchronous and always at the same FPS as the Camera.
Two new functions have been introduced:
* `runAtTargetFps(fps: number, func: () => void)`: Runs the given code as often as the given `fps`, effectively throttling it's calls.
* `runAsync(frame: Frame, func: () => void)`: Runs the given function on a separate Thread for Frame Processing. A strong reference to the Frame is held as long as the function takes to execute.
You can use `runAtTargetFps` to throttle calls to a specific API (e.g. if your Camera is running at 60 FPS, but you only want to run face detection at ~25 FPS, use `runAtTargetFps(25, ...)`.)
You can use `runAsync` to run a heavy algorithm asynchronous, so that the Camera is not blocked while your algorithm runs. This is useful if your main sync processor draws something, and your async processor is doing some image analysis on the side.
You can also combine both functions.
Examples:
```js
const frameProcessor = useFrameProcessor((frame) => {
'worklet'
console.log("I'm running at 60 FPS!")
}, [])
```
```js
const frameProcessor = useFrameProcessor((frame) => {
'worklet'
console.log("I'm running at 60 FPS!")
runAtTargetFps(10, () => {
'worklet'
console.log("I'm running at 10 FPS!")
})
}, [])
```
```js
const frameProcessor = useFrameProcessor((frame) => {
'worklet'
console.log("I'm running at 60 FPS!")
runAsync(frame, () => {
'worklet'
console.log("I'm running on another Thread, I can block for longer!")
})
}, [])
```
```js
const frameProcessor = useFrameProcessor((frame) => {
'worklet'
console.log("I'm running at 60 FPS!")
runAtTargetFps(10, () => {
'worklet'
runAsync(frame, () => {
'worklet'
console.log("I'm running on another Thread at 10 FPS, I can block for longer!")
})
})
}, [])
```
2023-02-15 08:47:09 -07:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Runs the given function at the given target FPS rate.
|
|
|
|
*
|
|
|
|
* For example, if you want to run a heavy face detection algorithm
|
|
|
|
* only once per second, you can use `runAtTargetFps(1, ...)` to
|
|
|
|
* throttle it to 1 FPS.
|
|
|
|
*
|
|
|
|
* @param fps The target FPS rate at which the given function should be executed
|
|
|
|
* @param func The function to execute.
|
|
|
|
* @returns The result of the function if it was executed, or `undefined` otherwise.
|
|
|
|
* @example
|
|
|
|
*
|
|
|
|
* ```ts
|
|
|
|
* const frameProcessor = useFrameProcessor((frame) => {
|
|
|
|
* 'worklet'
|
|
|
|
* console.log('New Frame')
|
2023-03-21 09:10:09 -06:00
|
|
|
* runAtTargetFps(5, () => {
|
feat: Sync Frame Processors (plus `runAsync` and `runAtTargetFps`) (#1472)
Before, Frame Processors ran on a separate Thread.
After, Frame Processors run fully synchronous and always at the same FPS as the Camera.
Two new functions have been introduced:
* `runAtTargetFps(fps: number, func: () => void)`: Runs the given code as often as the given `fps`, effectively throttling it's calls.
* `runAsync(frame: Frame, func: () => void)`: Runs the given function on a separate Thread for Frame Processing. A strong reference to the Frame is held as long as the function takes to execute.
You can use `runAtTargetFps` to throttle calls to a specific API (e.g. if your Camera is running at 60 FPS, but you only want to run face detection at ~25 FPS, use `runAtTargetFps(25, ...)`.)
You can use `runAsync` to run a heavy algorithm asynchronous, so that the Camera is not blocked while your algorithm runs. This is useful if your main sync processor draws something, and your async processor is doing some image analysis on the side.
You can also combine both functions.
Examples:
```js
const frameProcessor = useFrameProcessor((frame) => {
'worklet'
console.log("I'm running at 60 FPS!")
}, [])
```
```js
const frameProcessor = useFrameProcessor((frame) => {
'worklet'
console.log("I'm running at 60 FPS!")
runAtTargetFps(10, () => {
'worklet'
console.log("I'm running at 10 FPS!")
})
}, [])
```
```js
const frameProcessor = useFrameProcessor((frame) => {
'worklet'
console.log("I'm running at 60 FPS!")
runAsync(frame, () => {
'worklet'
console.log("I'm running on another Thread, I can block for longer!")
})
}, [])
```
```js
const frameProcessor = useFrameProcessor((frame) => {
'worklet'
console.log("I'm running at 60 FPS!")
runAtTargetFps(10, () => {
'worklet'
runAsync(frame, () => {
'worklet'
console.log("I'm running on another Thread at 10 FPS, I can block for longer!")
})
})
}, [])
```
2023-02-15 08:47:09 -07:00
|
|
|
* 'worklet'
|
|
|
|
* const faces = detectFaces(frame)
|
2023-03-21 09:10:09 -06:00
|
|
|
* console.log(`Detected a new face: ${faces[0]}`)
|
feat: Sync Frame Processors (plus `runAsync` and `runAtTargetFps`) (#1472)
Before, Frame Processors ran on a separate Thread.
After, Frame Processors run fully synchronous and always at the same FPS as the Camera.
Two new functions have been introduced:
* `runAtTargetFps(fps: number, func: () => void)`: Runs the given code as often as the given `fps`, effectively throttling it's calls.
* `runAsync(frame: Frame, func: () => void)`: Runs the given function on a separate Thread for Frame Processing. A strong reference to the Frame is held as long as the function takes to execute.
You can use `runAtTargetFps` to throttle calls to a specific API (e.g. if your Camera is running at 60 FPS, but you only want to run face detection at ~25 FPS, use `runAtTargetFps(25, ...)`.)
You can use `runAsync` to run a heavy algorithm asynchronous, so that the Camera is not blocked while your algorithm runs. This is useful if your main sync processor draws something, and your async processor is doing some image analysis on the side.
You can also combine both functions.
Examples:
```js
const frameProcessor = useFrameProcessor((frame) => {
'worklet'
console.log("I'm running at 60 FPS!")
}, [])
```
```js
const frameProcessor = useFrameProcessor((frame) => {
'worklet'
console.log("I'm running at 60 FPS!")
runAtTargetFps(10, () => {
'worklet'
console.log("I'm running at 10 FPS!")
})
}, [])
```
```js
const frameProcessor = useFrameProcessor((frame) => {
'worklet'
console.log("I'm running at 60 FPS!")
runAsync(frame, () => {
'worklet'
console.log("I'm running on another Thread, I can block for longer!")
})
}, [])
```
```js
const frameProcessor = useFrameProcessor((frame) => {
'worklet'
console.log("I'm running at 60 FPS!")
runAtTargetFps(10, () => {
'worklet'
runAsync(frame, () => {
'worklet'
console.log("I'm running on another Thread at 10 FPS, I can block for longer!")
})
})
}, [])
```
2023-02-15 08:47:09 -07:00
|
|
|
* })
|
|
|
|
* })
|
|
|
|
* ```
|
|
|
|
*/
|
|
|
|
export function runAtTargetFps<T>(fps: number, func: () => T): T | undefined {
|
2023-09-26 03:39:17 -06:00
|
|
|
'worklet'
|
2023-03-21 09:10:09 -06:00
|
|
|
// @ts-expect-error
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
2023-09-26 03:39:17 -06:00
|
|
|
const funcId = func.__workletHash ?? '1'
|
2023-03-21 09:10:09 -06:00
|
|
|
|
2023-09-26 03:39:17 -06:00
|
|
|
const targetIntervalMs = 1000 / fps // <-- 60 FPS => 16,6667ms interval
|
|
|
|
const now = performance.now()
|
|
|
|
const diffToLastCall = now - getLastFrameProcessorCall(funcId)
|
feat: Sync Frame Processors (plus `runAsync` and `runAtTargetFps`) (#1472)
Before, Frame Processors ran on a separate Thread.
After, Frame Processors run fully synchronous and always at the same FPS as the Camera.
Two new functions have been introduced:
* `runAtTargetFps(fps: number, func: () => void)`: Runs the given code as often as the given `fps`, effectively throttling it's calls.
* `runAsync(frame: Frame, func: () => void)`: Runs the given function on a separate Thread for Frame Processing. A strong reference to the Frame is held as long as the function takes to execute.
You can use `runAtTargetFps` to throttle calls to a specific API (e.g. if your Camera is running at 60 FPS, but you only want to run face detection at ~25 FPS, use `runAtTargetFps(25, ...)`.)
You can use `runAsync` to run a heavy algorithm asynchronous, so that the Camera is not blocked while your algorithm runs. This is useful if your main sync processor draws something, and your async processor is doing some image analysis on the side.
You can also combine both functions.
Examples:
```js
const frameProcessor = useFrameProcessor((frame) => {
'worklet'
console.log("I'm running at 60 FPS!")
}, [])
```
```js
const frameProcessor = useFrameProcessor((frame) => {
'worklet'
console.log("I'm running at 60 FPS!")
runAtTargetFps(10, () => {
'worklet'
console.log("I'm running at 10 FPS!")
})
}, [])
```
```js
const frameProcessor = useFrameProcessor((frame) => {
'worklet'
console.log("I'm running at 60 FPS!")
runAsync(frame, () => {
'worklet'
console.log("I'm running on another Thread, I can block for longer!")
})
}, [])
```
```js
const frameProcessor = useFrameProcessor((frame) => {
'worklet'
console.log("I'm running at 60 FPS!")
runAtTargetFps(10, () => {
'worklet'
runAsync(frame, () => {
'worklet'
console.log("I'm running on another Thread at 10 FPS, I can block for longer!")
})
})
}, [])
```
2023-02-15 08:47:09 -07:00
|
|
|
if (diffToLastCall >= targetIntervalMs) {
|
2023-09-26 03:39:17 -06:00
|
|
|
setLastFrameProcessorCall(funcId, now)
|
feat: Sync Frame Processors (plus `runAsync` and `runAtTargetFps`) (#1472)
Before, Frame Processors ran on a separate Thread.
After, Frame Processors run fully synchronous and always at the same FPS as the Camera.
Two new functions have been introduced:
* `runAtTargetFps(fps: number, func: () => void)`: Runs the given code as often as the given `fps`, effectively throttling it's calls.
* `runAsync(frame: Frame, func: () => void)`: Runs the given function on a separate Thread for Frame Processing. A strong reference to the Frame is held as long as the function takes to execute.
You can use `runAtTargetFps` to throttle calls to a specific API (e.g. if your Camera is running at 60 FPS, but you only want to run face detection at ~25 FPS, use `runAtTargetFps(25, ...)`.)
You can use `runAsync` to run a heavy algorithm asynchronous, so that the Camera is not blocked while your algorithm runs. This is useful if your main sync processor draws something, and your async processor is doing some image analysis on the side.
You can also combine both functions.
Examples:
```js
const frameProcessor = useFrameProcessor((frame) => {
'worklet'
console.log("I'm running at 60 FPS!")
}, [])
```
```js
const frameProcessor = useFrameProcessor((frame) => {
'worklet'
console.log("I'm running at 60 FPS!")
runAtTargetFps(10, () => {
'worklet'
console.log("I'm running at 10 FPS!")
})
}, [])
```
```js
const frameProcessor = useFrameProcessor((frame) => {
'worklet'
console.log("I'm running at 60 FPS!")
runAsync(frame, () => {
'worklet'
console.log("I'm running on another Thread, I can block for longer!")
})
}, [])
```
```js
const frameProcessor = useFrameProcessor((frame) => {
'worklet'
console.log("I'm running at 60 FPS!")
runAtTargetFps(10, () => {
'worklet'
runAsync(frame, () => {
'worklet'
console.log("I'm running on another Thread at 10 FPS, I can block for longer!")
})
})
}, [])
```
2023-02-15 08:47:09 -07:00
|
|
|
// Last Frame Processor call is already so long ago that we want to make a new call
|
2023-09-26 03:39:17 -06:00
|
|
|
return func()
|
feat: Sync Frame Processors (plus `runAsync` and `runAtTargetFps`) (#1472)
Before, Frame Processors ran on a separate Thread.
After, Frame Processors run fully synchronous and always at the same FPS as the Camera.
Two new functions have been introduced:
* `runAtTargetFps(fps: number, func: () => void)`: Runs the given code as often as the given `fps`, effectively throttling it's calls.
* `runAsync(frame: Frame, func: () => void)`: Runs the given function on a separate Thread for Frame Processing. A strong reference to the Frame is held as long as the function takes to execute.
You can use `runAtTargetFps` to throttle calls to a specific API (e.g. if your Camera is running at 60 FPS, but you only want to run face detection at ~25 FPS, use `runAtTargetFps(25, ...)`.)
You can use `runAsync` to run a heavy algorithm asynchronous, so that the Camera is not blocked while your algorithm runs. This is useful if your main sync processor draws something, and your async processor is doing some image analysis on the side.
You can also combine both functions.
Examples:
```js
const frameProcessor = useFrameProcessor((frame) => {
'worklet'
console.log("I'm running at 60 FPS!")
}, [])
```
```js
const frameProcessor = useFrameProcessor((frame) => {
'worklet'
console.log("I'm running at 60 FPS!")
runAtTargetFps(10, () => {
'worklet'
console.log("I'm running at 10 FPS!")
})
}, [])
```
```js
const frameProcessor = useFrameProcessor((frame) => {
'worklet'
console.log("I'm running at 60 FPS!")
runAsync(frame, () => {
'worklet'
console.log("I'm running on another Thread, I can block for longer!")
})
}, [])
```
```js
const frameProcessor = useFrameProcessor((frame) => {
'worklet'
console.log("I'm running at 60 FPS!")
runAtTargetFps(10, () => {
'worklet'
runAsync(frame, () => {
'worklet'
console.log("I'm running on another Thread at 10 FPS, I can block for longer!")
})
})
}, [])
```
2023-02-15 08:47:09 -07:00
|
|
|
}
|
2023-09-26 03:39:17 -06:00
|
|
|
return undefined
|
feat: Sync Frame Processors (plus `runAsync` and `runAtTargetFps`) (#1472)
Before, Frame Processors ran on a separate Thread.
After, Frame Processors run fully synchronous and always at the same FPS as the Camera.
Two new functions have been introduced:
* `runAtTargetFps(fps: number, func: () => void)`: Runs the given code as often as the given `fps`, effectively throttling it's calls.
* `runAsync(frame: Frame, func: () => void)`: Runs the given function on a separate Thread for Frame Processing. A strong reference to the Frame is held as long as the function takes to execute.
You can use `runAtTargetFps` to throttle calls to a specific API (e.g. if your Camera is running at 60 FPS, but you only want to run face detection at ~25 FPS, use `runAtTargetFps(25, ...)`.)
You can use `runAsync` to run a heavy algorithm asynchronous, so that the Camera is not blocked while your algorithm runs. This is useful if your main sync processor draws something, and your async processor is doing some image analysis on the side.
You can also combine both functions.
Examples:
```js
const frameProcessor = useFrameProcessor((frame) => {
'worklet'
console.log("I'm running at 60 FPS!")
}, [])
```
```js
const frameProcessor = useFrameProcessor((frame) => {
'worklet'
console.log("I'm running at 60 FPS!")
runAtTargetFps(10, () => {
'worklet'
console.log("I'm running at 10 FPS!")
})
}, [])
```
```js
const frameProcessor = useFrameProcessor((frame) => {
'worklet'
console.log("I'm running at 60 FPS!")
runAsync(frame, () => {
'worklet'
console.log("I'm running on another Thread, I can block for longer!")
})
}, [])
```
```js
const frameProcessor = useFrameProcessor((frame) => {
'worklet'
console.log("I'm running at 60 FPS!")
runAtTargetFps(10, () => {
'worklet'
runAsync(frame, () => {
'worklet'
console.log("I'm running on another Thread at 10 FPS, I can block for longer!")
})
})
}, [])
```
2023-02-15 08:47:09 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Runs the given function asynchronously, while keeping a strong reference to the Frame.
|
|
|
|
*
|
|
|
|
* For example, if you want to run a heavy face detection algorithm
|
|
|
|
* while still drawing to the screen at 60 FPS, you can use `runAsync(...)`
|
|
|
|
* to offload the face detection algorithm to a separate thread.
|
|
|
|
*
|
|
|
|
* @param frame The current Frame of the Frame Processor.
|
|
|
|
* @param func The function to execute.
|
|
|
|
* @example
|
|
|
|
*
|
|
|
|
* ```ts
|
|
|
|
* const frameProcessor = useFrameProcessor((frame) => {
|
|
|
|
* 'worklet'
|
|
|
|
* console.log('New Frame')
|
|
|
|
* runAsync(frame, () => {
|
|
|
|
* 'worklet'
|
|
|
|
* const faces = detectFaces(frame)
|
|
|
|
* const face = [faces0]
|
|
|
|
* console.log(`Detected a new face: ${face}`)
|
|
|
|
* })
|
|
|
|
* })
|
|
|
|
* ```
|
|
|
|
*/
|
|
|
|
export function runAsync(frame: Frame, func: () => void): void {
|
2023-09-26 03:39:17 -06:00
|
|
|
'worklet'
|
2023-02-21 07:00:48 -07:00
|
|
|
|
|
|
|
if (isAsyncContextBusy.value) {
|
|
|
|
// async context is currently busy, we cannot schedule new work in time.
|
|
|
|
// drop this frame/runAsync call.
|
2023-09-26 03:39:17 -06:00
|
|
|
return
|
2023-02-21 07:00:48 -07:00
|
|
|
}
|
|
|
|
|
feat: Sync Frame Processors (plus `runAsync` and `runAtTargetFps`) (#1472)
Before, Frame Processors ran on a separate Thread.
After, Frame Processors run fully synchronous and always at the same FPS as the Camera.
Two new functions have been introduced:
* `runAtTargetFps(fps: number, func: () => void)`: Runs the given code as often as the given `fps`, effectively throttling it's calls.
* `runAsync(frame: Frame, func: () => void)`: Runs the given function on a separate Thread for Frame Processing. A strong reference to the Frame is held as long as the function takes to execute.
You can use `runAtTargetFps` to throttle calls to a specific API (e.g. if your Camera is running at 60 FPS, but you only want to run face detection at ~25 FPS, use `runAtTargetFps(25, ...)`.)
You can use `runAsync` to run a heavy algorithm asynchronous, so that the Camera is not blocked while your algorithm runs. This is useful if your main sync processor draws something, and your async processor is doing some image analysis on the side.
You can also combine both functions.
Examples:
```js
const frameProcessor = useFrameProcessor((frame) => {
'worklet'
console.log("I'm running at 60 FPS!")
}, [])
```
```js
const frameProcessor = useFrameProcessor((frame) => {
'worklet'
console.log("I'm running at 60 FPS!")
runAtTargetFps(10, () => {
'worklet'
console.log("I'm running at 10 FPS!")
})
}, [])
```
```js
const frameProcessor = useFrameProcessor((frame) => {
'worklet'
console.log("I'm running at 60 FPS!")
runAsync(frame, () => {
'worklet'
console.log("I'm running on another Thread, I can block for longer!")
})
}, [])
```
```js
const frameProcessor = useFrameProcessor((frame) => {
'worklet'
console.log("I'm running at 60 FPS!")
runAtTargetFps(10, () => {
'worklet'
runAsync(frame, () => {
'worklet'
console.log("I'm running on another Thread at 10 FPS, I can block for longer!")
})
})
}, [])
```
2023-02-15 08:47:09 -07:00
|
|
|
// Increment ref count by one
|
2023-09-26 03:39:17 -06:00
|
|
|
const internal = frame as FrameInternal
|
|
|
|
internal.incrementRefCount()
|
feat: Sync Frame Processors (plus `runAsync` and `runAtTargetFps`) (#1472)
Before, Frame Processors ran on a separate Thread.
After, Frame Processors run fully synchronous and always at the same FPS as the Camera.
Two new functions have been introduced:
* `runAtTargetFps(fps: number, func: () => void)`: Runs the given code as often as the given `fps`, effectively throttling it's calls.
* `runAsync(frame: Frame, func: () => void)`: Runs the given function on a separate Thread for Frame Processing. A strong reference to the Frame is held as long as the function takes to execute.
You can use `runAtTargetFps` to throttle calls to a specific API (e.g. if your Camera is running at 60 FPS, but you only want to run face detection at ~25 FPS, use `runAtTargetFps(25, ...)`.)
You can use `runAsync` to run a heavy algorithm asynchronous, so that the Camera is not blocked while your algorithm runs. This is useful if your main sync processor draws something, and your async processor is doing some image analysis on the side.
You can also combine both functions.
Examples:
```js
const frameProcessor = useFrameProcessor((frame) => {
'worklet'
console.log("I'm running at 60 FPS!")
}, [])
```
```js
const frameProcessor = useFrameProcessor((frame) => {
'worklet'
console.log("I'm running at 60 FPS!")
runAtTargetFps(10, () => {
'worklet'
console.log("I'm running at 10 FPS!")
})
}, [])
```
```js
const frameProcessor = useFrameProcessor((frame) => {
'worklet'
console.log("I'm running at 60 FPS!")
runAsync(frame, () => {
'worklet'
console.log("I'm running on another Thread, I can block for longer!")
})
}, [])
```
```js
const frameProcessor = useFrameProcessor((frame) => {
'worklet'
console.log("I'm running at 60 FPS!")
runAtTargetFps(10, () => {
'worklet'
runAsync(frame, () => {
'worklet'
console.log("I'm running on another Thread at 10 FPS, I can block for longer!")
})
})
}, [])
```
2023-02-15 08:47:09 -07:00
|
|
|
|
2023-09-26 03:39:17 -06:00
|
|
|
isAsyncContextBusy.value = true
|
2023-02-21 07:00:48 -07:00
|
|
|
|
feat: Sync Frame Processors (plus `runAsync` and `runAtTargetFps`) (#1472)
Before, Frame Processors ran on a separate Thread.
After, Frame Processors run fully synchronous and always at the same FPS as the Camera.
Two new functions have been introduced:
* `runAtTargetFps(fps: number, func: () => void)`: Runs the given code as often as the given `fps`, effectively throttling it's calls.
* `runAsync(frame: Frame, func: () => void)`: Runs the given function on a separate Thread for Frame Processing. A strong reference to the Frame is held as long as the function takes to execute.
You can use `runAtTargetFps` to throttle calls to a specific API (e.g. if your Camera is running at 60 FPS, but you only want to run face detection at ~25 FPS, use `runAtTargetFps(25, ...)`.)
You can use `runAsync` to run a heavy algorithm asynchronous, so that the Camera is not blocked while your algorithm runs. This is useful if your main sync processor draws something, and your async processor is doing some image analysis on the side.
You can also combine both functions.
Examples:
```js
const frameProcessor = useFrameProcessor((frame) => {
'worklet'
console.log("I'm running at 60 FPS!")
}, [])
```
```js
const frameProcessor = useFrameProcessor((frame) => {
'worklet'
console.log("I'm running at 60 FPS!")
runAtTargetFps(10, () => {
'worklet'
console.log("I'm running at 10 FPS!")
})
}, [])
```
```js
const frameProcessor = useFrameProcessor((frame) => {
'worklet'
console.log("I'm running at 60 FPS!")
runAsync(frame, () => {
'worklet'
console.log("I'm running on another Thread, I can block for longer!")
})
}, [])
```
```js
const frameProcessor = useFrameProcessor((frame) => {
'worklet'
console.log("I'm running at 60 FPS!")
runAtTargetFps(10, () => {
'worklet'
runAsync(frame, () => {
'worklet'
console.log("I'm running on another Thread at 10 FPS, I can block for longer!")
})
})
}, [])
```
2023-02-15 08:47:09 -07:00
|
|
|
// Call in separate background context
|
2023-09-26 03:39:17 -06:00
|
|
|
runOnAsyncContext(frame, func)
|
feat: Sync Frame Processors (plus `runAsync` and `runAtTargetFps`) (#1472)
Before, Frame Processors ran on a separate Thread.
After, Frame Processors run fully synchronous and always at the same FPS as the Camera.
Two new functions have been introduced:
* `runAtTargetFps(fps: number, func: () => void)`: Runs the given code as often as the given `fps`, effectively throttling it's calls.
* `runAsync(frame: Frame, func: () => void)`: Runs the given function on a separate Thread for Frame Processing. A strong reference to the Frame is held as long as the function takes to execute.
You can use `runAtTargetFps` to throttle calls to a specific API (e.g. if your Camera is running at 60 FPS, but you only want to run face detection at ~25 FPS, use `runAtTargetFps(25, ...)`.)
You can use `runAsync` to run a heavy algorithm asynchronous, so that the Camera is not blocked while your algorithm runs. This is useful if your main sync processor draws something, and your async processor is doing some image analysis on the side.
You can also combine both functions.
Examples:
```js
const frameProcessor = useFrameProcessor((frame) => {
'worklet'
console.log("I'm running at 60 FPS!")
}, [])
```
```js
const frameProcessor = useFrameProcessor((frame) => {
'worklet'
console.log("I'm running at 60 FPS!")
runAtTargetFps(10, () => {
'worklet'
console.log("I'm running at 10 FPS!")
})
}, [])
```
```js
const frameProcessor = useFrameProcessor((frame) => {
'worklet'
console.log("I'm running at 60 FPS!")
runAsync(frame, () => {
'worklet'
console.log("I'm running on another Thread, I can block for longer!")
})
}, [])
```
```js
const frameProcessor = useFrameProcessor((frame) => {
'worklet'
console.log("I'm running at 60 FPS!")
runAtTargetFps(10, () => {
'worklet'
runAsync(frame, () => {
'worklet'
console.log("I'm running on another Thread at 10 FPS, I can block for longer!")
})
})
}, [])
```
2023-02-15 08:47:09 -07:00
|
|
|
}
|