Skip to content

Commit

Permalink
workaround to #43
Browse files Browse the repository at this point in the history
  • Loading branch information
luicfrr committed Apr 23, 2024
1 parent f6d2bd2 commit 5a0d896
Showing 1 changed file with 107 additions and 11 deletions.
118 changes: 107 additions & 11 deletions src/Camera.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
import React from 'react'
import {
Camera as VisionCamera,
runAsync,
useFrameProcessor
} from 'react-native-vision-camera'
import { useRunInJS } from 'react-native-worklets-core'
import { useSharedValue } from 'react-native-worklets-core'
import { useFaceDetector } from './FaceDetector'

// types
import type { ForwardedRef } from 'react'
import type {
DependencyList,
ForwardedRef
} from 'react'
import type {
CameraProps,
Frame,
Expand All @@ -19,6 +21,15 @@ import type {
FaceDetectionOptions
} from './FaceDetector'

type UseWorkletType = (
frame: FrameInternal
) => Promise<void>

type UseRunInJSType = (
faces: Face[],
frame: Frame
) => Promise<void | Promise<void>>

type CallbackType = (
faces: Face[],
frame: Frame
Expand All @@ -29,6 +40,43 @@ type ComponentType = {
faceDetectionCallback: CallbackType
} & CameraProps

/**
* Create a Worklet function that persists between re-renders.
* The returned function can be called from both a Worklet context and the JS context, but will execute on a Worklet context.
*
* @param {function} func The Worklet. Must be marked with the `'worklet'` directive.
* @param {DependencyList} dependencyList The React dependencies of this Worklet.
* @returns {UseWorkletType} A memoized Worklet
*/
function useWorklet(
func: ( frame: FrameInternal ) => void,
dependencyList: DependencyList
): UseWorkletType {
const worklet = React.useMemo( () => {
const context: any = 'VisionCamera.async'
return Worklets.createRunInContextFn( func, context )
}, dependencyList )

return worklet
}

/**
* Create a Worklet function that runs the giver function on JS context.
* The returned function can be called from a Worklet to hop back to the JS thread.
*
* @param {function} func The Worklet. Must be marked with the `'worklet'` directive.
* @param {DependencyList} dependencyList The React dependencies of this Worklet.
* @returns {UseRunInJSType} a memoized Worklet
*/
function useRunInJS(
func: CallbackType,
dependencyList: DependencyList
): UseRunInJSType {
return React.useMemo( () => (
Worklets.createRunInJsFn( func )
), dependencyList )
}

/**
* Vision camera wrapper
*
Expand All @@ -43,6 +91,24 @@ export const Camera = React.forwardRef( ( {
ref: ForwardedRef<VisionCamera>
) => {
const { detectFaces } = useFaceDetector( faceDetectionOptions )
/**
* Is there an async task already running?
*/
const isAsyncContextBusy = useSharedValue( false )

/**
* Throws logs/errors back on js thread
*/
const logOnJs = Worklets.createRunInJsFn( (
log: string,
error?: Error
) => {
if ( error ) {
console.error( log, error.message ?? JSON.stringify( error ) )
} else {
console.log( log )
}
} )

/**
* Runs on detection callback on js thread
Expand All @@ -52,29 +118,59 @@ export const Camera = React.forwardRef( ( {
] )

/**
* Camera frame processor
* Async context that will handle face detection
*/
const cameraFrameProcessor = useFrameProcessor( ( frame ) => {
const runOnAsyncContext = useWorklet( (
frame: FrameInternal
) => {
'worklet'
runAsync( frame, () => {
'worklet'
const internal = frame as FrameInternal
try {
const faces = detectFaces( frame )

// increment frame count so we can use frame on
// js side without frame processor getting stuck
internal.incrementRefCount()
frame.incrementRefCount()
runOnJs(
faces,
frame
).finally( () => {
'worklet'
// finally decrement frame count so it can be dropped
internal.decrementRefCount()
frame.decrementRefCount()
} )
} )
} catch ( error: any ) {
logOnJs( 'Execution error:', error )
} finally {
frame.decrementRefCount()
isAsyncContextBusy.value = false
}
}, [ runOnJs ] )

/**
* Detect faces on frame on an async context without blocking camera preview
*
* @param {Frame} frame Current frame
*/
function runAsync( frame: Frame ) {
'worklet'
if ( isAsyncContextBusy.value ) return
// set async context as busy
isAsyncContextBusy.value = true
// cast to internal frame and increment ref count
const internal = frame as FrameInternal
internal.incrementRefCount()
// detect faces in async context
runOnAsyncContext( internal )
}

/**
* Camera frame processor
*/
const cameraFrameProcessor = useFrameProcessor( ( frame ) => {
'worklet'
runAsync( frame )
}, [ runOnAsyncContext ] )

return <VisionCamera
{ ...props }
ref={ ref }
Expand Down

0 comments on commit 5a0d896

Please sign in to comment.