# Worklet Runtime/Multithreading proposal This proposal defines the API for running **worklets** on other **threads** than JS and UI thread which also requires the use of other **runtimes**. Potential use cases suggested by the community: https://twitter.com/tomekzaw_/status/1691849748712444292 ## Shareables Shareables are C++ data structures which serve as an immutable runtime-agnostic intermediate format for passing serializable data between JS runtimes. Previously, shareables would keep an instance of `JSRuntimeHelper` and behave differently across runtimes (only RN and UI runtime were supported). Adapting shareables to work with arbitrary runtimes is in scope of this feature. ## Worklet runtime * wrapper around `std::shared_ptr<jsi::Runtime>` * runtime-agnostic (support JSC/Hermes/V8) * built-in debugging support (Chrome DevTools Protocol) * has JSI bindings and core functions already injected * `valueUnpacker`, `callGuardDEV` -- required * `_scheduleOnJS`, `_makeShareableClone` -- required for `runOnJS` * `console.*`, `performance.*` -- optional * provides methods to call worklets via callGuardDEV (show errors in RedBox) * is a `jsi::HostObject` so it can be created in JS and passed to C++ side ```ts const runtime = createWorkletRuntime("foo"); // captures `valueUnpacker` and `makeShareableClone` // and passes it to JSI binding `_createWorkletRuntime` // TODO: measure execution time console.log(runtime.name); // foo ``` * we provide a function `extractWorkletRuntime` as a symbol in `libreanimated.so` to avoid problems with casting and RTTI between dynamic shared libraries on Android ```cpp std::shared_ptr<WorkletRuntime> workletRuntime = extractWorkletRuntime(rt, hostObject); ``` * it's possible to access `jsi::Runtime &` directly * returns a reference instead of shared pointer so that the lifetime of underlying runtime is controlled only by `WorkletRuntime` instance ```cpp jsi::Runtime &rt = workletRuntime->getRuntime(); ``` ## Running worklets from C++ * that's how we will call worklets internally (in Reanimated codebase) ```cpp std::shared_ptr<ShareableWorklet> shareableWorklet = ...; workletRuntime->runGuarded(shareableWorklet); ``` * we will also expose the following public header: ```cpp! // SomePublicHeader.h namespace worklets { class Worklet; // no need to specify fields and methods class WorkletRuntime; // no need to specify fields and methods std::shared_ptr<WorkletRuntime> extractWorkletRuntime( jsi::Runtime &rt, const jsi::Value &workletRuntime); std::shared_ptr<Worklet> extractWorklet( jsi::Runtime &rt, const jsi::Value &worklet); std::shared_ptr<Worklet> executeWorklet( const std::shared_ptr<WorkletRuntime> &workletRuntime, const std::shared_ptr<Worklet> &worklet); } // namespace worklets ``` * here's the actual implementation as an adapter: ```cpp class WorkletRuntime { reanimated::WorkletRuntime reanimatedWorkletRuntime; } ``` * here's the external use: ```cpp! #include <RNWorklets/SomePublicHeader.h> auto workletRuntime = extractWorkletRuntime(rt, hostObject); auto worklet = extractWorklet(rt, value); executeWorklet(workletRuntime, worklet); ``` ## Running worklets from JS * analogous API to `runOnUI` or `runOnJS` ```ts const runtime = /* ... */; runOnRuntime(runtime, () => { 'worklet'; // code executed on a system background thread })(...args); ``` ```ts runOnRuntime(runtime, () => { 'worklet'; const y = f(x); runOnJS(callback)(y); })(x); ``` ## Background task * not to confuse with iOS background tasks (https://developer.apple.com/documentation/backgroundtasks) ```ts! const config = { runtime: /* ... */, // more stuff in the future } const result = await backgroundTask(worklet, config); ``` ```ts const result = await backgroundTask(() => { 'worklet'; return fib(30); }); ``` ## Cancelling background tasks We can't simply stop the backgroujnd thread or terminate the runtime in a non-consistent state. Instead, the user should explicitely mark cancellation points. ```ts! const shouldYield = makeMutable(false); runOnRuntime(runtime, () => { 'worklet'; // some code here if (shouldYield.value) { return; } // moar code here })(); const handlePress = () => { shouldYield.value = true; }; ``` ## Next steps ### Implement WebWorker API https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API ```typescript const myWorker = new Worker("worker.js"); myWorker.postMessage("ping"); onmessage = (e) => { console.log(e.data); postMessage("pong"); }; myWorker.onmessage = (e) => { console.log(e.data); }; ``` ### Bundling worklets This would allow using third-party libraries inside worklets.