Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Sync Frame Processors #1472

Merged
merged 35 commits into from
Feb 15, 2023
Merged

feat: Sync Frame Processors #1472

merged 35 commits into from
Feb 15, 2023

Conversation

mrousavy
Copy link
Owner

@mrousavy mrousavy commented Feb 13, 2023

What

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:

const frameProcessor = useFrameProcessor((frame) => {
  'worklet'
  console.log("I'm running at 60 FPS!")
}, [])
const frameProcessor = useFrameProcessor((frame) => {
  'worklet'
  console.log("I'm running at 60 FPS!")

  runAtTargetFps(10, () => {
    'worklet'
    console.log("I'm running at 10 FPS!")
  })
}, [])
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!")
  })
}, [])
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!")
    })
  })
}, [])

Changes

Tested on

Related issues

@github-actions
Copy link
Contributor

github-actions bot commented Feb 13, 2023

yarn.lock changes

Click to toggle table visibility
Name Status Previous Current
jsc-android UPDATED 250230.2.1 250231.0.0
react-native-codegen UPDATED 0.71.3 0.71.5
react-native-gradle-plugin UPDATED 0.71.14 0.71.15
react-native-worklets ADDED - 0.1.0
react-native UPDATED 0.71.2 0.71.3

* })
* ```
*/
export function runAsync(func: () => void): void {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[tsc] <6133> reported by reviewdog 🐶
'func' is declared but its value is never read.

@@ -11,3 +12,77 @@ type TFrameProcessorPlugins = Record<string, FrameProcessor>;
* All natively installed Frame Processor Plugins.
*/
export const FrameProcessorPlugins = global.FrameProcessorPlugins as TFrameProcessorPlugins;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[tsc] <7017> reported by reviewdog 🐶
Element implicitly has an 'any' type because type 'typeof globalThis' has no index signature.

@@ -80,10 +81,26 @@ jsi::Value FrameHostObject::get(jsi::Runtime& runtime, const jsi::PropNameID& pr
this->assertIsFrameStrong(runtime, name);
return jsi::Value(this->frame->getPlanesCount());
}
if (name == "refCount") {
return jsi::Value((double) _refCount);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[cpplint] reported by reviewdog 🐶
Using C-style cast. Use static_cast(...) instead [readability/casting] [4]

auto name = propName.utf8(runtime);

if (name == "refCount") {
_refCount = (size_t) value.asNumber();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[cpplint] reported by reviewdog 🐶
Using C-style cast. Use static_cast<size_t>(...) instead [readability/casting] [4]

}

runAtTargetFps.__initData = {
code: runAtTargetFps.asString,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[tsc] <2339> reported by reviewdog 🐶
Property 'asString' does not exist on type 'typeof runAtTargetFps'.


runAtTargetFps.__initData = {
code: runAtTargetFps.asString,
location: runAtTargetFps.__location,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[tsc] <2339> reported by reviewdog 🐶
Property '__location' does not exist on type 'typeof runAtTargetFps'.

'worklet';
// Increment ref count by one
frame.refCount++;
func.__initData = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[tsc] <2339> reported by reviewdog 🐶
Property '__initData' does not exist on type '() => void'.

// Increment ref count by one
frame.refCount++;
func.__initData = {
code: func.asString,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[tsc] <2339> reported by reviewdog 🐶
Property 'asString' does not exist on type '() => void'.

frame.refCount++;
func.__initData = {
code: func.asString,
location: func.__location,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[tsc] <2339> reported by reviewdog 🐶
Property '__location' does not exist on type '() => void'.

};

const fn = Worklets.createRunInContextFn(wrapper, context);
fn.__initData = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[tsc] <2339> reported by reviewdog 🐶
Property '__initData' does not exist on type '() => Promise'.


const fn = Worklets.createRunInContextFn(wrapper, context);
fn.__initData = {
code: fn.asString,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[tsc] <2339> reported by reviewdog 🐶
Property 'asString' does not exist on type '() => Promise'.

const fn = Worklets.createRunInContextFn(wrapper, context);
fn.__initData = {
code: fn.asString,
location: fn.__location,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[tsc] <2339> reported by reviewdog 🐶
Property '__location' does not exist on type '() => Promise'.

}

runAsync.__initData = {
code: runAsync.asString,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[tsc] <2339> reported by reviewdog 🐶
Property 'asString' does not exist on type 'typeof runAsync'.


runAsync.__initData = {
code: runAsync.asString,
location: runAsync.__location,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[tsc] <2339> reported by reviewdog 🐶
Property '__location' does not exist on type 'typeof runAsync'.

@@ -80,10 +83,30 @@ jsi::Value FrameHostObject::get(jsi::Runtime& runtime, const jsi::PropNameID& pr
this->assertIsFrameStrong(runtime, name);
return jsi::Value(this->frame->getPlanesCount());
}
if (name == "refCount") {
if (!_refCount) {
_refCount = std::make_shared<RNWorklet::JsiSharedValue>(jsi::Value(0),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[cpplint] reported by reviewdog 🐶
Add #include for make_shared<> [build/include_what_you_use] [4]

@@ -34,6 +37,7 @@ class JSI_EXPORT FrameHostObject : public jsi::HostObject {
static auto constexpr TAG = "VisionCamera";

void assertIsFrameStrong(jsi::Runtime& runtime, const std::string& accessedPropName) const; // NOLINT(runtime/references)
std::shared_ptr<RNWorklet::JsiSharedValue> _refCount;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[cpplint] reported by reviewdog 🐶
Add #include for shared_ptr<> [build/include_what_you_use] [4]

frameProcessor(frame);

// Potentially delete Frame if we were the last ref (no runAsync)
frame.refCount.value--;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[tsc] <2339> reported by reviewdog 🐶
Property 'refCount' does not exist on type 'Frame'.


// Potentially delete Frame if we were the last ref (no runAsync)
frame.refCount.value--;
if (frame.refCount.value <= 0) frame.close();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[tsc] <2339> reported by reviewdog 🐶
Property 'refCount' does not exist on type 'Frame'.


// Potentially delete Frame if we were the last ref (no runAsync)
frame.refCount.value--;
if (frame.refCount.value <= 0) frame.close();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[tsc] <2339> reported by reviewdog 🐶
Property 'close' does not exist on type 'Frame'.

@mrousavy mrousavy marked this pull request as ready for review February 15, 2023 15:46
@mrousavy mrousavy merged commit 30b5615 into v3 Feb 15, 2023
@mrousavy mrousavy deleted the feat/sync-frame-processors branch February 15, 2023 15:47
mrousavy added a commit that referenced this pull request Jul 31, 2023
…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!")
    })
  })
}, [])
```
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

1 participant