Skip to content

Commit aa4f5a5

Browse files
committed
🔧
1 parent 19ed15c commit aa4f5a5

9 files changed

Lines changed: 127 additions & 25 deletions

File tree

apps/example/src/Reanimated/Reanimated.tsx

Lines changed: 2 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,15 @@
11
import React, { useEffect, useRef } from "react";
22
import { StyleSheet, View } from "react-native";
33
import type { CanvasRef, RNCanvasContext } from "react-native-wgpu";
4-
import { Canvas } from "react-native-wgpu";
4+
import { Canvas, registerWebGPUSerializable } from "react-native-wgpu";
55
import type { SharedValue } from "react-native-reanimated";
66
import { runOnUI, useSharedValue } from "react-native-reanimated";
7-
import { registerCustomSerializable } from "react-native-worklets";
87

98
import { redFragWGSL, triangleVertWGSL } from "../Triangle/triangle";
109

11-
// Declare global WebGPU worklet helper functions (installed on main runtime)
12-
declare function __webgpuIsWebGPUObject(obj: unknown): boolean;
13-
declare function __webgpuBox(
14-
obj: object,
15-
): { unbox: () => object; __boxedWebGPU: true };
16-
1710
// Register WebGPU objects for Worklets serialization at module load time
1811
// This allows GPUDevice, GPUCanvasContext, etc. to be passed to worklets
19-
// The unbox() method automatically installs the prototype on the UI runtime if needed
20-
registerCustomSerializable({
21-
name: "WebGPU",
22-
determine: (value: object): value is object => {
23-
"worklet";
24-
return __webgpuIsWebGPUObject(value);
25-
},
26-
pack: (value: object) => {
27-
"worklet";
28-
return __webgpuBox(value);
29-
},
30-
unpack: (boxed: { unbox: () => object }) => {
31-
"worklet";
32-
return boxed.unbox();
33-
},
34-
});
12+
registerWebGPUSerializable();
3513

3614
const webGPUDemo = (
3715
runAnimation: SharedValue<boolean>,

packages/webgpu/package.json

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@
5757
},
5858
"devDependencies": {
5959
"@types/jest": "^29.5.12",
60+
"react-native-reanimated": "^3.16.0",
61+
"react-native-worklets": "^1.0.0",
6062
"@types/lodash": "^4.17.5",
6163
"@types/node": "^20.14.7",
6264
"@types/pixelmatch": "5.2.4",
@@ -95,7 +97,17 @@
9597
},
9698
"peerDependencies": {
9799
"react": "*",
98-
"react-native": "*"
100+
"react-native": "*",
101+
"react-native-reanimated": ">=3.16.0",
102+
"react-native-worklets": ">=1.0.0"
103+
},
104+
"peerDependenciesMeta": {
105+
"react-native-reanimated": {
106+
"optional": true
107+
},
108+
"react-native-worklets": {
109+
"optional": true
110+
}
99111
},
100112
"react-native-builder-bob": {
101113
"source": "src",
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// https://github.com/mrousavy/react-native-vision-camera/blob/main/package/src/dependencies/ModuleProxy.ts
2+
type ImportType = ReturnType<typeof require>;
3+
4+
/**
5+
* Create a lazily-imported module proxy.
6+
* This is useful for lazily requiring optional dependencies.
7+
*/
8+
export const createModuleProxy = <TModule>(
9+
getModule: () => ImportType
10+
): TModule => {
11+
const holder: { module: TModule | undefined } = { module: undefined };
12+
13+
const proxy = new Proxy(holder, {
14+
get: (target, property) => {
15+
if (target.module == null) {
16+
// lazy initialize module via require()
17+
// caller needs to make sure the require() call is wrapped in a try/catch
18+
target.module = getModule() as TModule;
19+
}
20+
return target.module[property as keyof typeof holder.module];
21+
},
22+
});
23+
return proxy as unknown as TModule;
24+
};
25+
26+
export class OptionalDependencyNotInstalledError extends Error {
27+
constructor(name: string) {
28+
super(`${name} is not installed!`);
29+
}
30+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from "./reanimated";
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import type * as ReanimatedT from "react-native-reanimated";
2+
3+
import {
4+
OptionalDependencyNotInstalledError,
5+
createModuleProxy,
6+
} from "../ModuleProxy";
7+
8+
type TReanimated = typeof ReanimatedT;
9+
10+
const Reanimated = createModuleProxy<TReanimated>(() => {
11+
try {
12+
return require("react-native-reanimated");
13+
} catch (e) {
14+
throw new OptionalDependencyNotInstalledError("react-native-reanimated");
15+
}
16+
});
17+
18+
// eslint-disable-next-line import/no-default-export
19+
export default Reanimated;
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import type * as WorkletsT from "react-native-worklets";
2+
3+
import {
4+
OptionalDependencyNotInstalledError,
5+
createModuleProxy,
6+
} from "../ModuleProxy";
7+
8+
type TWorklets = typeof WorkletsT;
9+
10+
const Worklets = createModuleProxy<TWorklets>(() => {
11+
try {
12+
return require("react-native-worklets");
13+
} catch (e) {
14+
throw new OptionalDependencyNotInstalledError("react-native-worklets");
15+
}
16+
});
17+
18+
// eslint-disable-next-line import/no-default-export
19+
export default Worklets;
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export { registerWebGPUSerializable } from "./registerWebGPUSerializable";
2+
export { default as Reanimated } from "./ReanimatedProxy";
3+
export { default as Worklets } from "./WorkletsProxy";
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import Worklets from "./WorkletsProxy";
2+
3+
// Declare global WebGPU worklet helper functions (installed on main runtime)
4+
declare function __webgpuIsWebGPUObject(obj: unknown): boolean;
5+
declare function __webgpuBox(
6+
obj: object
7+
): { unbox: () => object; __boxedWebGPU: true };
8+
9+
let isRegistered = false;
10+
11+
/**
12+
* Register WebGPU objects for Worklets serialization.
13+
* This allows GPUDevice, GPUCanvasContext, etc. to be passed to worklets.
14+
* The unbox() method automatically installs the prototype on the UI runtime if needed.
15+
*
16+
* Call this once before using WebGPU objects in worklets.
17+
*/
18+
export const registerWebGPUSerializable = () => {
19+
if (isRegistered) {
20+
return;
21+
}
22+
isRegistered = true;
23+
24+
Worklets.registerCustomSerializable({
25+
name: "WebGPU",
26+
determine: (value: object): value is object => {
27+
"worklet";
28+
return __webgpuIsWebGPUObject(value);
29+
},
30+
pack: (value: object) => {
31+
"worklet";
32+
return __webgpuBox(value);
33+
},
34+
unpack: (boxed: { unbox: () => object }) => {
35+
"worklet";
36+
return boxed.unbox();
37+
},
38+
});
39+
};

packages/webgpu/src/index.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import type { NativeCanvas, RNCanvasContext } from "./types";
44

55
export * from "./main";
6+
export * from "./external";
67

78
declare global {
89
interface Navigator {

0 commit comments

Comments
 (0)