@react-three/fiber 10.0.0-alpha.1 → 10.0.0-canary.164c7be
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/dist/index.cjs +1256 -1419
- package/dist/index.d.cts +1887 -1699
- package/dist/index.d.mts +1887 -1699
- package/dist/index.d.ts +1887 -1699
- package/dist/index.mjs +1216 -1398
- package/dist/legacy.cjs +1222 -1410
- package/dist/legacy.d.cts +1888 -1700
- package/dist/legacy.d.mts +1888 -1700
- package/dist/legacy.d.ts +1888 -1700
- package/dist/legacy.mjs +1182 -1389
- package/dist/webgpu/index.cjs +1937 -1470
- package/dist/webgpu/index.d.cts +2145 -1722
- package/dist/webgpu/index.d.mts +2145 -1722
- package/dist/webgpu/index.d.ts +2145 -1722
- package/dist/webgpu/index.mjs +1890 -1449
- package/package.json +8 -5
- package/react-reconciler/constants.js +1 -9
- package/react-reconciler/index.js +4 -20
- package/readme.md +244 -318
package/dist/webgpu/index.cjs
CHANGED
|
@@ -6,13 +6,21 @@ const jsxRuntime = require('react/jsx-runtime');
|
|
|
6
6
|
const React = require('react');
|
|
7
7
|
const useMeasure = require('react-use-measure');
|
|
8
8
|
const itsFine = require('its-fine');
|
|
9
|
+
const fiber = require('@react-three/fiber');
|
|
10
|
+
const GroundedSkybox_js = require('three/examples/jsm/objects/GroundedSkybox.js');
|
|
11
|
+
const HDRLoader_js = require('three/examples/jsm/loaders/HDRLoader.js');
|
|
12
|
+
const EXRLoader_js = require('three/examples/jsm/loaders/EXRLoader.js');
|
|
13
|
+
const UltraHDRLoader_js = require('three/examples/jsm/loaders/UltraHDRLoader.js');
|
|
14
|
+
const gainmapJs = require('@monogrid/gainmap-js');
|
|
9
15
|
const Tb = require('scheduler');
|
|
10
16
|
const traditional = require('zustand/traditional');
|
|
17
|
+
const scheduler = require('@pmndrs/scheduler');
|
|
11
18
|
const suspendReact = require('suspend-react');
|
|
12
19
|
const lite = require('dequal/lite');
|
|
13
20
|
require('zustand/shallow');
|
|
14
21
|
const tsl = require('three/tsl');
|
|
15
22
|
|
|
23
|
+
var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
|
|
16
24
|
function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e.default : e; }
|
|
17
25
|
|
|
18
26
|
function _interopNamespaceCompat(e) {
|
|
@@ -66,9 +74,392 @@ const THREE = /*#__PURE__*/_mergeNamespaces({
|
|
|
66
74
|
WebGLRenderer: WebGLRenderer
|
|
67
75
|
}, [webgpu__namespace]);
|
|
68
76
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
77
|
+
const primaryRegistry = /* @__PURE__ */ new Map();
|
|
78
|
+
const pendingSubscribers = /* @__PURE__ */ new Map();
|
|
79
|
+
function registerPrimary(id, renderer, store) {
|
|
80
|
+
if (primaryRegistry.has(id)) {
|
|
81
|
+
console.warn(`Canvas with id="${id}" already registered. Overwriting.`);
|
|
82
|
+
}
|
|
83
|
+
const entry = { renderer, store };
|
|
84
|
+
primaryRegistry.set(id, entry);
|
|
85
|
+
const subscribers = pendingSubscribers.get(id);
|
|
86
|
+
if (subscribers) {
|
|
87
|
+
subscribers.forEach((callback) => callback(entry));
|
|
88
|
+
pendingSubscribers.delete(id);
|
|
89
|
+
}
|
|
90
|
+
return () => {
|
|
91
|
+
const currentEntry = primaryRegistry.get(id);
|
|
92
|
+
if (currentEntry?.renderer === renderer) {
|
|
93
|
+
primaryRegistry.delete(id);
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
function getPrimary(id) {
|
|
98
|
+
return primaryRegistry.get(id);
|
|
99
|
+
}
|
|
100
|
+
function waitForPrimary(id, timeout = 5e3) {
|
|
101
|
+
const existing = primaryRegistry.get(id);
|
|
102
|
+
if (existing) {
|
|
103
|
+
return Promise.resolve(existing);
|
|
104
|
+
}
|
|
105
|
+
return new Promise((resolve, reject) => {
|
|
106
|
+
const timeoutId = setTimeout(() => {
|
|
107
|
+
const subscribers = pendingSubscribers.get(id);
|
|
108
|
+
if (subscribers) {
|
|
109
|
+
const index = subscribers.indexOf(callback);
|
|
110
|
+
if (index !== -1) subscribers.splice(index, 1);
|
|
111
|
+
if (subscribers.length === 0) pendingSubscribers.delete(id);
|
|
112
|
+
}
|
|
113
|
+
reject(new Error(`Timeout waiting for canvas with id="${id}". Make sure a <Canvas id="${id}"> is mounted.`));
|
|
114
|
+
}, timeout);
|
|
115
|
+
const callback = (entry) => {
|
|
116
|
+
clearTimeout(timeoutId);
|
|
117
|
+
resolve(entry);
|
|
118
|
+
};
|
|
119
|
+
if (!pendingSubscribers.has(id)) {
|
|
120
|
+
pendingSubscribers.set(id, []);
|
|
121
|
+
}
|
|
122
|
+
pendingSubscribers.get(id).push(callback);
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
function hasPrimary(id) {
|
|
126
|
+
return primaryRegistry.has(id);
|
|
127
|
+
}
|
|
128
|
+
function unregisterPrimary(id) {
|
|
129
|
+
primaryRegistry.delete(id);
|
|
130
|
+
}
|
|
131
|
+
function getPrimaryIds() {
|
|
132
|
+
return Array.from(primaryRegistry.keys());
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const presetsObj = {
|
|
136
|
+
apartment: "lebombo_1k.hdr",
|
|
137
|
+
city: "potsdamer_platz_1k.hdr",
|
|
138
|
+
dawn: "kiara_1_dawn_1k.hdr",
|
|
139
|
+
forest: "forest_slope_1k.hdr",
|
|
140
|
+
lobby: "st_fagans_interior_1k.hdr",
|
|
141
|
+
night: "dikhololo_night_1k.hdr",
|
|
142
|
+
park: "rooitou_park_1k.hdr",
|
|
143
|
+
studio: "studio_small_03_1k.hdr",
|
|
144
|
+
sunset: "venice_sunset_1k.hdr",
|
|
145
|
+
warehouse: "empty_warehouse_01_1k.hdr"
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
const CUBEMAP_ROOT = "https://raw.githack.com/pmndrs/drei-assets/456060a26bbeb8fdf79326f224b6d99b8bcce736/hdri/";
|
|
149
|
+
const isArray = (arr) => Array.isArray(arr);
|
|
150
|
+
const defaultFiles = ["/px.png", "/nx.png", "/py.png", "/ny.png", "/pz.png", "/nz.png"];
|
|
151
|
+
function useEnvironment({
|
|
152
|
+
files = defaultFiles,
|
|
153
|
+
path = "",
|
|
154
|
+
preset = void 0,
|
|
155
|
+
colorSpace = void 0,
|
|
156
|
+
extensions
|
|
157
|
+
} = {}) {
|
|
158
|
+
if (preset) {
|
|
159
|
+
validatePreset(preset);
|
|
160
|
+
files = presetsObj[preset];
|
|
161
|
+
path = CUBEMAP_ROOT;
|
|
162
|
+
}
|
|
163
|
+
const multiFile = isArray(files);
|
|
164
|
+
const { extension, isCubemap } = getExtension(files);
|
|
165
|
+
const loader = getLoader$1(extension);
|
|
166
|
+
if (!loader) throw new Error("useEnvironment: Unrecognized file extension: " + files);
|
|
167
|
+
const renderer = fiber.useThree((state) => state.renderer);
|
|
168
|
+
React.useLayoutEffect(() => {
|
|
169
|
+
if (extension !== "webp" && extension !== "jpg" && extension !== "jpeg") return;
|
|
170
|
+
function clearGainmapTexture() {
|
|
171
|
+
fiber.useLoader.clear(loader, multiFile ? [files] : files);
|
|
172
|
+
}
|
|
173
|
+
renderer.domElement.addEventListener("webglcontextlost", clearGainmapTexture, { once: true });
|
|
174
|
+
}, [extension, files, loader, multiFile, renderer.domElement]);
|
|
175
|
+
const loaderResult = fiber.useLoader(
|
|
176
|
+
loader,
|
|
177
|
+
multiFile ? [files] : files,
|
|
178
|
+
(loader2) => {
|
|
179
|
+
if (extension === "webp" || extension === "jpg" || extension === "jpeg") {
|
|
180
|
+
loader2.setRenderer?.(renderer);
|
|
181
|
+
}
|
|
182
|
+
loader2.setPath?.(path);
|
|
183
|
+
if (extensions) extensions(loader2);
|
|
184
|
+
}
|
|
185
|
+
);
|
|
186
|
+
let texture = multiFile ? (
|
|
187
|
+
// @ts-ignore
|
|
188
|
+
loaderResult[0]
|
|
189
|
+
) : loaderResult;
|
|
190
|
+
if (extension === "jpg" || extension === "jpeg" || extension === "webp") {
|
|
191
|
+
texture = texture.renderTarget?.texture;
|
|
192
|
+
}
|
|
193
|
+
texture.mapping = isCubemap ? webgpu.CubeReflectionMapping : webgpu.EquirectangularReflectionMapping;
|
|
194
|
+
texture.colorSpace = colorSpace ?? (isCubemap ? "srgb" : "srgb-linear");
|
|
195
|
+
return texture;
|
|
196
|
+
}
|
|
197
|
+
const preloadDefaultOptions = {
|
|
198
|
+
files: defaultFiles,
|
|
199
|
+
path: "",
|
|
200
|
+
preset: void 0,
|
|
201
|
+
extensions: void 0
|
|
202
|
+
};
|
|
203
|
+
useEnvironment.preload = (preloadOptions) => {
|
|
204
|
+
const options = { ...preloadDefaultOptions, ...preloadOptions };
|
|
205
|
+
let { files, path = "" } = options;
|
|
206
|
+
const { preset, extensions } = options;
|
|
207
|
+
if (preset) {
|
|
208
|
+
validatePreset(preset);
|
|
209
|
+
files = presetsObj[preset];
|
|
210
|
+
path = CUBEMAP_ROOT;
|
|
211
|
+
}
|
|
212
|
+
const { extension } = getExtension(files);
|
|
213
|
+
if (extension === "webp" || extension === "jpg" || extension === "jpeg") {
|
|
214
|
+
throw new Error("useEnvironment: Preloading gainmaps is not supported");
|
|
215
|
+
}
|
|
216
|
+
const loader = getLoader$1(extension);
|
|
217
|
+
if (!loader) throw new Error("useEnvironment: Unrecognized file extension: " + files);
|
|
218
|
+
fiber.useLoader.preload(loader, isArray(files) ? [files] : files, (loader2) => {
|
|
219
|
+
loader2.setPath?.(path);
|
|
220
|
+
if (extensions) extensions(loader2);
|
|
221
|
+
});
|
|
222
|
+
};
|
|
223
|
+
const clearDefaultOptins = {
|
|
224
|
+
files: defaultFiles,
|
|
225
|
+
preset: void 0
|
|
226
|
+
};
|
|
227
|
+
useEnvironment.clear = (clearOptions) => {
|
|
228
|
+
const options = { ...clearDefaultOptins, ...clearOptions };
|
|
229
|
+
let { files } = options;
|
|
230
|
+
const { preset } = options;
|
|
231
|
+
if (preset) {
|
|
232
|
+
validatePreset(preset);
|
|
233
|
+
files = presetsObj[preset];
|
|
234
|
+
}
|
|
235
|
+
const { extension } = getExtension(files);
|
|
236
|
+
const loader = getLoader$1(extension);
|
|
237
|
+
if (!loader) throw new Error("useEnvironment: Unrecognized file extension: " + files);
|
|
238
|
+
fiber.useLoader.clear(loader, isArray(files) ? [files] : files);
|
|
239
|
+
};
|
|
240
|
+
function validatePreset(preset) {
|
|
241
|
+
if (!(preset in presetsObj)) throw new Error("Preset must be one of: " + Object.keys(presetsObj).join(", "));
|
|
242
|
+
}
|
|
243
|
+
function getExtension(files) {
|
|
244
|
+
const isCubemap = isArray(files) && files.length === 6;
|
|
245
|
+
const isGainmap = isArray(files) && files.length === 3 && files.some((file) => file.endsWith("json"));
|
|
246
|
+
const firstEntry = isArray(files) ? files[0] : files;
|
|
247
|
+
const extension = isCubemap ? "cube" : isGainmap ? "webp" : firstEntry.startsWith("data:application/exr") ? "exr" : firstEntry.startsWith("data:application/hdr") ? "hdr" : firstEntry.startsWith("data:image/jpeg") ? "jpg" : firstEntry.split(".").pop()?.split("?")?.shift()?.toLowerCase();
|
|
248
|
+
return { extension, isCubemap, isGainmap };
|
|
249
|
+
}
|
|
250
|
+
function getLoader$1(extension) {
|
|
251
|
+
const loader = extension === "cube" ? webgpu.CubeTextureLoader : extension === "hdr" ? HDRLoader_js.HDRLoader : extension === "exr" ? EXRLoader_js.EXRLoader : extension === "jpg" || extension === "jpeg" ? UltraHDRLoader_js.UltraHDRLoader : extension === "webp" ? gainmapJs.GainMapLoader : null;
|
|
252
|
+
return loader;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const isRef$1 = (obj) => obj.current && obj.current.isScene;
|
|
256
|
+
const resolveScene = (scene) => isRef$1(scene) ? scene.current : scene;
|
|
257
|
+
function setEnvProps(background, scene, defaultScene, texture, sceneProps = {}) {
|
|
258
|
+
sceneProps = {
|
|
259
|
+
backgroundBlurriness: 0,
|
|
260
|
+
backgroundIntensity: 1,
|
|
261
|
+
backgroundRotation: [0, 0, 0],
|
|
262
|
+
environmentIntensity: 1,
|
|
263
|
+
environmentRotation: [0, 0, 0],
|
|
264
|
+
...sceneProps
|
|
265
|
+
};
|
|
266
|
+
const target = resolveScene(scene || defaultScene);
|
|
267
|
+
const oldbg = target.background;
|
|
268
|
+
const oldenv = target.environment;
|
|
269
|
+
const oldSceneProps = {
|
|
270
|
+
// @ts-ignore
|
|
271
|
+
backgroundBlurriness: target.backgroundBlurriness,
|
|
272
|
+
// @ts-ignore
|
|
273
|
+
backgroundIntensity: target.backgroundIntensity,
|
|
274
|
+
// @ts-ignore
|
|
275
|
+
backgroundRotation: target.backgroundRotation?.clone?.() ?? [0, 0, 0],
|
|
276
|
+
// @ts-ignore
|
|
277
|
+
environmentIntensity: target.environmentIntensity,
|
|
278
|
+
// @ts-ignore
|
|
279
|
+
environmentRotation: target.environmentRotation?.clone?.() ?? [0, 0, 0]
|
|
280
|
+
};
|
|
281
|
+
if (background !== "only") target.environment = texture;
|
|
282
|
+
if (background) target.background = texture;
|
|
283
|
+
fiber.applyProps(target, sceneProps);
|
|
284
|
+
return () => {
|
|
285
|
+
if (background !== "only") target.environment = oldenv;
|
|
286
|
+
if (background) target.background = oldbg;
|
|
287
|
+
fiber.applyProps(target, oldSceneProps);
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
function EnvironmentMap({ scene, background = false, map, ...config }) {
|
|
291
|
+
const defaultScene = fiber.useThree((state) => state.scene);
|
|
292
|
+
React__namespace.useLayoutEffect(() => {
|
|
293
|
+
if (map) return setEnvProps(background, scene, defaultScene, map, config);
|
|
294
|
+
});
|
|
295
|
+
return null;
|
|
296
|
+
}
|
|
297
|
+
function EnvironmentCube({
|
|
298
|
+
background = false,
|
|
299
|
+
scene,
|
|
300
|
+
blur,
|
|
301
|
+
backgroundBlurriness,
|
|
302
|
+
backgroundIntensity,
|
|
303
|
+
backgroundRotation,
|
|
304
|
+
environmentIntensity,
|
|
305
|
+
environmentRotation,
|
|
306
|
+
...rest
|
|
307
|
+
}) {
|
|
308
|
+
const texture = useEnvironment(rest);
|
|
309
|
+
const defaultScene = fiber.useThree((state) => state.scene);
|
|
310
|
+
React__namespace.useLayoutEffect(() => {
|
|
311
|
+
return setEnvProps(background, scene, defaultScene, texture, {
|
|
312
|
+
backgroundBlurriness: blur ?? backgroundBlurriness,
|
|
313
|
+
backgroundIntensity,
|
|
314
|
+
backgroundRotation,
|
|
315
|
+
environmentIntensity,
|
|
316
|
+
environmentRotation
|
|
317
|
+
});
|
|
318
|
+
});
|
|
319
|
+
React__namespace.useEffect(() => {
|
|
320
|
+
return () => {
|
|
321
|
+
texture.dispose();
|
|
322
|
+
};
|
|
323
|
+
}, [texture]);
|
|
324
|
+
return null;
|
|
325
|
+
}
|
|
326
|
+
function EnvironmentPortal({
|
|
327
|
+
children,
|
|
328
|
+
near = 0.1,
|
|
329
|
+
far = 1e3,
|
|
330
|
+
resolution = 256,
|
|
331
|
+
frames = 1,
|
|
332
|
+
map,
|
|
333
|
+
background = false,
|
|
334
|
+
blur,
|
|
335
|
+
backgroundBlurriness,
|
|
336
|
+
backgroundIntensity,
|
|
337
|
+
backgroundRotation,
|
|
338
|
+
environmentIntensity,
|
|
339
|
+
environmentRotation,
|
|
340
|
+
scene,
|
|
341
|
+
files,
|
|
342
|
+
path,
|
|
343
|
+
preset = void 0,
|
|
344
|
+
extensions
|
|
345
|
+
}) {
|
|
346
|
+
const gl = fiber.useThree((state) => state.gl);
|
|
347
|
+
const defaultScene = fiber.useThree((state) => state.scene);
|
|
348
|
+
const camera = React__namespace.useRef(null);
|
|
349
|
+
const [virtualScene] = React__namespace.useState(() => new webgpu.Scene());
|
|
350
|
+
const fbo = React__namespace.useMemo(() => {
|
|
351
|
+
const fbo2 = new webgpu.WebGLCubeRenderTarget(resolution);
|
|
352
|
+
fbo2.texture.type = webgpu.HalfFloatType;
|
|
353
|
+
return fbo2;
|
|
354
|
+
}, [resolution]);
|
|
355
|
+
React__namespace.useEffect(() => {
|
|
356
|
+
return () => {
|
|
357
|
+
fbo.dispose();
|
|
358
|
+
};
|
|
359
|
+
}, [fbo]);
|
|
360
|
+
React__namespace.useLayoutEffect(() => {
|
|
361
|
+
if (frames === 1) {
|
|
362
|
+
const autoClear = gl.autoClear;
|
|
363
|
+
gl.autoClear = true;
|
|
364
|
+
camera.current.update(gl, virtualScene);
|
|
365
|
+
gl.autoClear = autoClear;
|
|
366
|
+
}
|
|
367
|
+
return setEnvProps(background, scene, defaultScene, fbo.texture, {
|
|
368
|
+
backgroundBlurriness: blur ?? backgroundBlurriness,
|
|
369
|
+
backgroundIntensity,
|
|
370
|
+
backgroundRotation,
|
|
371
|
+
environmentIntensity,
|
|
372
|
+
environmentRotation
|
|
373
|
+
});
|
|
374
|
+
}, [
|
|
375
|
+
children,
|
|
376
|
+
virtualScene,
|
|
377
|
+
fbo.texture,
|
|
378
|
+
scene,
|
|
379
|
+
defaultScene,
|
|
380
|
+
background,
|
|
381
|
+
frames,
|
|
382
|
+
gl,
|
|
383
|
+
blur,
|
|
384
|
+
backgroundBlurriness,
|
|
385
|
+
backgroundIntensity,
|
|
386
|
+
backgroundRotation,
|
|
387
|
+
environmentIntensity,
|
|
388
|
+
environmentRotation
|
|
389
|
+
]);
|
|
390
|
+
let count = 1;
|
|
391
|
+
fiber.useFrame(() => {
|
|
392
|
+
if (frames === Infinity || count < frames) {
|
|
393
|
+
const autoClear = gl.autoClear;
|
|
394
|
+
gl.autoClear = true;
|
|
395
|
+
camera.current.update(gl, virtualScene);
|
|
396
|
+
gl.autoClear = autoClear;
|
|
397
|
+
count++;
|
|
398
|
+
}
|
|
399
|
+
});
|
|
400
|
+
return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: fiber.createPortal(
|
|
401
|
+
/* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
402
|
+
children,
|
|
403
|
+
/* @__PURE__ */ jsxRuntime.jsx("cubeCamera", { ref: camera, args: [near, far, fbo] }),
|
|
404
|
+
files || preset ? /* @__PURE__ */ jsxRuntime.jsx(EnvironmentCube, { background: true, files, preset, path, extensions }) : map ? /* @__PURE__ */ jsxRuntime.jsx(EnvironmentMap, { background: true, map, extensions }) : null
|
|
405
|
+
] }),
|
|
406
|
+
virtualScene
|
|
407
|
+
) });
|
|
408
|
+
}
|
|
409
|
+
function EnvironmentGround(props) {
|
|
410
|
+
const textureDefault = useEnvironment(props);
|
|
411
|
+
const texture = props.map || textureDefault;
|
|
412
|
+
React__namespace.useMemo(() => fiber.extend({ GroundProjectedEnvImpl: GroundedSkybox_js.GroundedSkybox }), []);
|
|
413
|
+
React__namespace.useEffect(() => {
|
|
414
|
+
return () => {
|
|
415
|
+
textureDefault.dispose();
|
|
416
|
+
};
|
|
417
|
+
}, [textureDefault]);
|
|
418
|
+
const height = props.ground?.height ?? 15;
|
|
419
|
+
const radius = props.ground?.radius ?? 60;
|
|
420
|
+
const scale = props.ground?.scale ?? 1e3;
|
|
421
|
+
const args = React__namespace.useMemo(
|
|
422
|
+
() => [texture, height, radius],
|
|
423
|
+
[texture, height, radius]
|
|
424
|
+
);
|
|
425
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
426
|
+
/* @__PURE__ */ jsxRuntime.jsx(EnvironmentMap, { ...props, map: texture }),
|
|
427
|
+
/* @__PURE__ */ jsxRuntime.jsx("groundProjectedEnvImpl", { args, scale })
|
|
428
|
+
] });
|
|
429
|
+
}
|
|
430
|
+
function EnvironmentColor({ color, scene }) {
|
|
431
|
+
const defaultScene = fiber.useThree((state) => state.scene);
|
|
432
|
+
React__namespace.useLayoutEffect(() => {
|
|
433
|
+
if (color === void 0) return;
|
|
434
|
+
const target = resolveScene(scene || defaultScene);
|
|
435
|
+
const oldBg = target.background;
|
|
436
|
+
target.background = new webgpu.Color(color);
|
|
437
|
+
return () => {
|
|
438
|
+
target.background = oldBg;
|
|
439
|
+
};
|
|
440
|
+
});
|
|
441
|
+
return null;
|
|
442
|
+
}
|
|
443
|
+
function EnvironmentDualSource(props) {
|
|
444
|
+
const { backgroundFiles, ...envProps } = props;
|
|
445
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
446
|
+
/* @__PURE__ */ jsxRuntime.jsx(EnvironmentCube, { ...envProps, background: false }),
|
|
447
|
+
/* @__PURE__ */ jsxRuntime.jsx(EnvironmentCube, { ...props, files: backgroundFiles, background: "only" })
|
|
448
|
+
] });
|
|
449
|
+
}
|
|
450
|
+
function Environment(props) {
|
|
451
|
+
if (props.color && !props.files && !props.preset && !props.map) {
|
|
452
|
+
return /* @__PURE__ */ jsxRuntime.jsx(EnvironmentColor, { ...props });
|
|
453
|
+
}
|
|
454
|
+
if (props.backgroundFiles && props.backgroundFiles !== props.files) {
|
|
455
|
+
return /* @__PURE__ */ jsxRuntime.jsx(EnvironmentDualSource, { ...props });
|
|
456
|
+
}
|
|
457
|
+
return props.ground ? /* @__PURE__ */ jsxRuntime.jsx(EnvironmentGround, { ...props }) : props.map ? /* @__PURE__ */ jsxRuntime.jsx(EnvironmentMap, { ...props }) : props.children ? /* @__PURE__ */ jsxRuntime.jsx(EnvironmentPortal, { ...props }) : /* @__PURE__ */ jsxRuntime.jsx(EnvironmentCube, { ...props });
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
var __defProp$1 = Object.defineProperty;
|
|
461
|
+
var __defNormalProp$1 = (obj, key, value) => key in obj ? __defProp$1(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
462
|
+
var __publicField$1 = (obj, key, value) => __defNormalProp$1(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
72
463
|
const act = React__namespace["act"];
|
|
73
464
|
const useIsomorphicLayoutEffect = /* @__PURE__ */ (() => typeof window !== "undefined" && (window.document?.createElement || window.navigator?.product === "ReactNative"))() ? React__namespace.useLayoutEffect : React__namespace.useEffect;
|
|
74
465
|
function useMutableCallback(fn) {
|
|
@@ -100,7 +491,7 @@ const ErrorBoundary = /* @__PURE__ */ (() => {
|
|
|
100
491
|
return _a = class extends React__namespace.Component {
|
|
101
492
|
constructor() {
|
|
102
493
|
super(...arguments);
|
|
103
|
-
__publicField$
|
|
494
|
+
__publicField$1(this, "state", { error: false });
|
|
104
495
|
}
|
|
105
496
|
componentDidCatch(err) {
|
|
106
497
|
this.props.set(err);
|
|
@@ -108,7 +499,7 @@ const ErrorBoundary = /* @__PURE__ */ (() => {
|
|
|
108
499
|
render() {
|
|
109
500
|
return this.state.error ? null : this.props.children;
|
|
110
501
|
}
|
|
111
|
-
}, __publicField$
|
|
502
|
+
}, __publicField$1(_a, "getDerivedStateFromError", () => ({ error: true })), _a;
|
|
112
503
|
})();
|
|
113
504
|
|
|
114
505
|
const is = {
|
|
@@ -245,7 +636,8 @@ function prepare(target, root, type, props) {
|
|
|
245
636
|
object,
|
|
246
637
|
eventCount: 0,
|
|
247
638
|
handlers: {},
|
|
248
|
-
isHidden: false
|
|
639
|
+
isHidden: false,
|
|
640
|
+
deferredRefs: []
|
|
249
641
|
};
|
|
250
642
|
if (object) object.__r3f = instance;
|
|
251
643
|
}
|
|
@@ -294,7 +686,7 @@ function createOcclusionObserverNode(store, uniform) {
|
|
|
294
686
|
let occlusionSetupPromise = null;
|
|
295
687
|
function enableOcclusion(store) {
|
|
296
688
|
const state = store.getState();
|
|
297
|
-
const { internal, renderer
|
|
689
|
+
const { internal, renderer } = state;
|
|
298
690
|
if (internal.occlusionEnabled || occlusionSetupPromise) return;
|
|
299
691
|
const hasOcclusionSupport = typeof renderer?.isOccluded === "function";
|
|
300
692
|
if (!hasOcclusionSupport) {
|
|
@@ -457,6 +849,22 @@ function hasVisibilityHandlers(handlers) {
|
|
|
457
849
|
return !!(handlers.onFramed || handlers.onOccluded || handlers.onVisible);
|
|
458
850
|
}
|
|
459
851
|
|
|
852
|
+
const FROM_REF = Symbol.for("@react-three/fiber.fromRef");
|
|
853
|
+
function fromRef(ref) {
|
|
854
|
+
return { [FROM_REF]: ref };
|
|
855
|
+
}
|
|
856
|
+
function isFromRef(value) {
|
|
857
|
+
return value !== null && typeof value === "object" && FROM_REF in value;
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
const ONCE = Symbol.for("@react-three/fiber.once");
|
|
861
|
+
function once(...args) {
|
|
862
|
+
return { [ONCE]: args.length ? args : true };
|
|
863
|
+
}
|
|
864
|
+
function isOnce(value) {
|
|
865
|
+
return value !== null && typeof value === "object" && ONCE in value;
|
|
866
|
+
}
|
|
867
|
+
|
|
460
868
|
const RESERVED_PROPS = [
|
|
461
869
|
"children",
|
|
462
870
|
"key",
|
|
@@ -527,7 +935,7 @@ function getMemoizedPrototype(root) {
|
|
|
527
935
|
ctor = new root.constructor();
|
|
528
936
|
MEMOIZED_PROTOTYPES.set(root.constructor, ctor);
|
|
529
937
|
}
|
|
530
|
-
} catch
|
|
938
|
+
} catch {
|
|
531
939
|
}
|
|
532
940
|
return ctor;
|
|
533
941
|
}
|
|
@@ -558,7 +966,7 @@ function applyProps(object, props) {
|
|
|
558
966
|
const rootState = instance && findInitialRoot(instance).getState();
|
|
559
967
|
const prevHandlers = instance?.eventCount;
|
|
560
968
|
for (const prop in props) {
|
|
561
|
-
|
|
969
|
+
const value = props[prop];
|
|
562
970
|
if (RESERVED_PROPS.includes(prop)) continue;
|
|
563
971
|
if (instance && EVENT_REGEX.test(prop)) {
|
|
564
972
|
if (typeof value === "function") instance.handlers[prop] = value;
|
|
@@ -573,6 +981,25 @@ function applyProps(object, props) {
|
|
|
573
981
|
continue;
|
|
574
982
|
}
|
|
575
983
|
if (value === void 0) continue;
|
|
984
|
+
if (isFromRef(value)) {
|
|
985
|
+
instance?.deferredRefs?.push({ prop, ref: value[FROM_REF] });
|
|
986
|
+
continue;
|
|
987
|
+
}
|
|
988
|
+
if (isOnce(value)) {
|
|
989
|
+
if (instance?.appliedOnce?.has(prop)) continue;
|
|
990
|
+
if (instance) {
|
|
991
|
+
instance.appliedOnce ?? (instance.appliedOnce = /* @__PURE__ */ new Set());
|
|
992
|
+
instance.appliedOnce.add(prop);
|
|
993
|
+
}
|
|
994
|
+
const { root: targetRoot, key: targetKey } = resolve(object, prop);
|
|
995
|
+
const args = value[ONCE];
|
|
996
|
+
if (typeof targetRoot[targetKey] === "function") {
|
|
997
|
+
targetRoot[targetKey](...args === true ? [] : args);
|
|
998
|
+
} else if (args !== true && args.length > 0) {
|
|
999
|
+
targetRoot[targetKey] = args[0];
|
|
1000
|
+
}
|
|
1001
|
+
continue;
|
|
1002
|
+
}
|
|
576
1003
|
let { root, key, target } = resolve(object, prop);
|
|
577
1004
|
if (target === void 0 && (typeof root !== "object" || root === null)) {
|
|
578
1005
|
throw Error(`R3F: Cannot set "${prop}". Ensure it is an object before setting "${key}".`);
|
|
@@ -595,7 +1022,10 @@ function applyProps(object, props) {
|
|
|
595
1022
|
else target.set(value);
|
|
596
1023
|
} else {
|
|
597
1024
|
root[key] = value;
|
|
598
|
-
if (
|
|
1025
|
+
if (key.endsWith("Node") && root.isMaterial) {
|
|
1026
|
+
root.needsUpdate = true;
|
|
1027
|
+
}
|
|
1028
|
+
if (rootState && rootState.renderer?.outputColorSpace === webgpu.SRGBColorSpace && colorMaps.includes(key) && isTexture(value) && root[key]?.isTexture && // sRGB textures must be RGBA8 since r137 https://github.com/mrdoob/three.js/pull/23129
|
|
599
1029
|
root[key].format === webgpu.RGBAFormat && root[key].type === webgpu.UnsignedByteType) {
|
|
600
1030
|
root[key].colorSpace = rootState.textureColorSpace;
|
|
601
1031
|
}
|
|
@@ -628,38 +1058,60 @@ function applyProps(object, props) {
|
|
|
628
1058
|
return object;
|
|
629
1059
|
}
|
|
630
1060
|
|
|
1061
|
+
const DEFAULT_POINTER_ID = 0;
|
|
1062
|
+
const XR_POINTER_ID_START = 1e3;
|
|
1063
|
+
function getPointerState(internal, pointerId) {
|
|
1064
|
+
let state = internal.pointerMap.get(pointerId);
|
|
1065
|
+
if (!state) {
|
|
1066
|
+
state = {
|
|
1067
|
+
hovered: /* @__PURE__ */ new Map(),
|
|
1068
|
+
captured: /* @__PURE__ */ new Map(),
|
|
1069
|
+
initialClick: [0, 0],
|
|
1070
|
+
initialHits: []
|
|
1071
|
+
};
|
|
1072
|
+
internal.pointerMap.set(pointerId, state);
|
|
1073
|
+
}
|
|
1074
|
+
return state;
|
|
1075
|
+
}
|
|
1076
|
+
function getPointerId(event) {
|
|
1077
|
+
return "pointerId" in event ? event.pointerId : DEFAULT_POINTER_ID;
|
|
1078
|
+
}
|
|
631
1079
|
function makeId(event) {
|
|
632
1080
|
return (event.eventObject || event.object).uuid + "/" + event.index + event.instanceId;
|
|
633
1081
|
}
|
|
634
|
-
function releaseInternalPointerCapture(
|
|
635
|
-
const
|
|
1082
|
+
function releaseInternalPointerCapture(internal, obj, pointerId) {
|
|
1083
|
+
const pointerState = internal.pointerMap.get(pointerId);
|
|
1084
|
+
if (!pointerState) return;
|
|
1085
|
+
const captureData = pointerState.captured.get(obj);
|
|
636
1086
|
if (captureData) {
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
capturedMap.delete(pointerId);
|
|
640
|
-
captureData.target.releasePointerCapture(pointerId);
|
|
641
|
-
}
|
|
1087
|
+
pointerState.captured.delete(obj);
|
|
1088
|
+
captureData.target.releasePointerCapture(pointerId);
|
|
642
1089
|
}
|
|
643
1090
|
}
|
|
644
1091
|
function removeInteractivity(store, object) {
|
|
645
1092
|
const { internal } = store.getState();
|
|
646
1093
|
internal.interaction = internal.interaction.filter((o) => o !== object);
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
1094
|
+
for (const [pointerId, pointerState] of internal.pointerMap) {
|
|
1095
|
+
pointerState.initialHits = pointerState.initialHits.filter((o) => o !== object);
|
|
1096
|
+
pointerState.hovered.forEach((value, key) => {
|
|
1097
|
+
if (value.eventObject === object || value.object === object) {
|
|
1098
|
+
pointerState.hovered.delete(key);
|
|
1099
|
+
}
|
|
1100
|
+
});
|
|
1101
|
+
if (pointerState.captured.has(object)) {
|
|
1102
|
+
releaseInternalPointerCapture(internal, object, pointerId);
|
|
651
1103
|
}
|
|
652
|
-
}
|
|
653
|
-
internal.capturedMap.forEach((captures, pointerId) => {
|
|
654
|
-
releaseInternalPointerCapture(internal.capturedMap, object, captures, pointerId);
|
|
655
|
-
});
|
|
1104
|
+
}
|
|
656
1105
|
unregisterVisibility(store, object);
|
|
657
1106
|
}
|
|
658
1107
|
function createEvents(store) {
|
|
659
|
-
function calculateDistance(event) {
|
|
1108
|
+
function calculateDistance(event, pointerId) {
|
|
660
1109
|
const { internal } = store.getState();
|
|
661
|
-
const
|
|
662
|
-
|
|
1110
|
+
const pointerState = internal.pointerMap.get(pointerId);
|
|
1111
|
+
if (!pointerState) return 0;
|
|
1112
|
+
const [initialX, initialY] = pointerState.initialClick;
|
|
1113
|
+
const dx = event.offsetX - initialX;
|
|
1114
|
+
const dy = event.offsetY - initialY;
|
|
663
1115
|
return Math.round(Math.sqrt(dx * dx + dy * dy));
|
|
664
1116
|
}
|
|
665
1117
|
function filterPointerEvents(objects) {
|
|
@@ -695,6 +1147,15 @@ function createEvents(store) {
|
|
|
695
1147
|
return state2.raycaster.camera ? state2.raycaster.intersectObject(obj, true) : [];
|
|
696
1148
|
}
|
|
697
1149
|
let hits = eventsObjects.flatMap(handleRaycast).sort((a, b) => {
|
|
1150
|
+
const aInteractivePriority = a.object.userData?.interactivePriority;
|
|
1151
|
+
const bInteractivePriority = b.object.userData?.interactivePriority;
|
|
1152
|
+
if (aInteractivePriority !== void 0 || bInteractivePriority !== void 0) {
|
|
1153
|
+
if (aInteractivePriority !== void 0 && bInteractivePriority === void 0) return -1;
|
|
1154
|
+
if (bInteractivePriority !== void 0 && aInteractivePriority === void 0) return 1;
|
|
1155
|
+
if (aInteractivePriority !== bInteractivePriority) {
|
|
1156
|
+
return (bInteractivePriority ?? 0) - (aInteractivePriority ?? 0);
|
|
1157
|
+
}
|
|
1158
|
+
}
|
|
698
1159
|
const aState = getRootState(a.object);
|
|
699
1160
|
const bState = getRootState(b.object);
|
|
700
1161
|
const aPriority = aState?.events?.priority ?? 1;
|
|
@@ -710,14 +1171,19 @@ function createEvents(store) {
|
|
|
710
1171
|
for (const hit of hits) {
|
|
711
1172
|
let eventObject = hit.object;
|
|
712
1173
|
while (eventObject) {
|
|
713
|
-
if (eventObject.__r3f?.eventCount)
|
|
1174
|
+
if (eventObject.__r3f?.eventCount) {
|
|
714
1175
|
intersections.push({ ...hit, eventObject });
|
|
1176
|
+
}
|
|
715
1177
|
eventObject = eventObject.parent;
|
|
716
1178
|
}
|
|
717
1179
|
}
|
|
718
|
-
if ("pointerId" in event
|
|
719
|
-
|
|
720
|
-
|
|
1180
|
+
if ("pointerId" in event) {
|
|
1181
|
+
const pointerId = event.pointerId;
|
|
1182
|
+
const pointerState = state.internal.pointerMap.get(pointerId);
|
|
1183
|
+
if (pointerState?.captured.size) {
|
|
1184
|
+
for (const captureData of pointerState.captured.values()) {
|
|
1185
|
+
if (!duplicates.has(makeId(captureData.intersection))) intersections.push(captureData.intersection);
|
|
1186
|
+
}
|
|
721
1187
|
}
|
|
722
1188
|
}
|
|
723
1189
|
return intersections;
|
|
@@ -730,28 +1196,26 @@ function createEvents(store) {
|
|
|
730
1196
|
if (state) {
|
|
731
1197
|
const { raycaster, pointer, camera, internal } = state;
|
|
732
1198
|
const unprojectedPoint = new webgpu.Vector3(pointer.x, pointer.y, 0).unproject(camera);
|
|
733
|
-
const hasPointerCapture = (id) =>
|
|
1199
|
+
const hasPointerCapture = (id) => {
|
|
1200
|
+
const pointerState = internal.pointerMap.get(id);
|
|
1201
|
+
return pointerState?.captured.has(hit.eventObject) ?? false;
|
|
1202
|
+
};
|
|
734
1203
|
const setPointerCapture = (id) => {
|
|
735
1204
|
const captureData = { intersection: hit, target: event.target };
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
} else {
|
|
739
|
-
internal.capturedMap.set(id, /* @__PURE__ */ new Map([[hit.eventObject, captureData]]));
|
|
740
|
-
}
|
|
1205
|
+
const pointerState = getPointerState(internal, id);
|
|
1206
|
+
pointerState.captured.set(hit.eventObject, captureData);
|
|
741
1207
|
event.target.setPointerCapture(id);
|
|
742
1208
|
};
|
|
743
1209
|
const releasePointerCapture = (id) => {
|
|
744
|
-
|
|
745
|
-
if (captures) {
|
|
746
|
-
releaseInternalPointerCapture(internal.capturedMap, hit.eventObject, captures, id);
|
|
747
|
-
}
|
|
1210
|
+
releaseInternalPointerCapture(internal, hit.eventObject, id);
|
|
748
1211
|
};
|
|
749
|
-
|
|
750
|
-
for (
|
|
751
|
-
|
|
1212
|
+
const extractEventProps = {};
|
|
1213
|
+
for (const prop in event) {
|
|
1214
|
+
const property = event[prop];
|
|
752
1215
|
if (typeof property !== "function") extractEventProps[prop] = property;
|
|
753
1216
|
}
|
|
754
|
-
|
|
1217
|
+
const eventPointerId = "pointerId" in event ? event.pointerId : void 0;
|
|
1218
|
+
const raycastEvent = {
|
|
755
1219
|
...hit,
|
|
756
1220
|
...extractEventProps,
|
|
757
1221
|
pointer,
|
|
@@ -761,18 +1225,19 @@ function createEvents(store) {
|
|
|
761
1225
|
unprojectedPoint,
|
|
762
1226
|
ray: raycaster.ray,
|
|
763
1227
|
camera,
|
|
1228
|
+
pointerId: eventPointerId,
|
|
764
1229
|
// Hijack stopPropagation, which just sets a flag
|
|
765
1230
|
stopPropagation() {
|
|
766
|
-
const
|
|
1231
|
+
const pointerState = eventPointerId !== void 0 ? internal.pointerMap.get(eventPointerId) : void 0;
|
|
767
1232
|
if (
|
|
768
1233
|
// ...if this pointer hasn't been captured
|
|
769
|
-
!
|
|
770
|
-
|
|
1234
|
+
!pointerState?.captured.size || // ... or if the hit object is capturing the pointer
|
|
1235
|
+
pointerState.captured.has(hit.eventObject)
|
|
771
1236
|
) {
|
|
772
1237
|
raycastEvent.stopped = localState.stopped = true;
|
|
773
|
-
if (
|
|
1238
|
+
if (pointerState?.hovered.size && Array.from(pointerState.hovered.values()).find((i) => i.eventObject === hit.eventObject)) {
|
|
774
1239
|
const higher = intersections.slice(0, intersections.indexOf(hit));
|
|
775
|
-
cancelPointer([...higher, hit]);
|
|
1240
|
+
cancelPointer([...higher, hit], eventPointerId);
|
|
776
1241
|
}
|
|
777
1242
|
}
|
|
778
1243
|
},
|
|
@@ -788,15 +1253,18 @@ function createEvents(store) {
|
|
|
788
1253
|
}
|
|
789
1254
|
return intersections;
|
|
790
1255
|
}
|
|
791
|
-
function cancelPointer(intersections) {
|
|
1256
|
+
function cancelPointer(intersections, pointerId) {
|
|
792
1257
|
const { internal } = store.getState();
|
|
793
|
-
|
|
1258
|
+
const pid = pointerId ?? DEFAULT_POINTER_ID;
|
|
1259
|
+
const pointerState = internal.pointerMap.get(pid);
|
|
1260
|
+
if (!pointerState) return;
|
|
1261
|
+
for (const [hoveredId, hoveredObj] of pointerState.hovered) {
|
|
794
1262
|
if (!intersections.length || !intersections.find(
|
|
795
1263
|
(hit) => hit.object === hoveredObj.object && hit.index === hoveredObj.index && hit.instanceId === hoveredObj.instanceId
|
|
796
1264
|
)) {
|
|
797
1265
|
const eventObject = hoveredObj.eventObject;
|
|
798
1266
|
const instance = eventObject.__r3f;
|
|
799
|
-
|
|
1267
|
+
pointerState.hovered.delete(hoveredId);
|
|
800
1268
|
if (instance?.eventCount) {
|
|
801
1269
|
const handlers = instance.handlers;
|
|
802
1270
|
const data = { ...hoveredObj, intersections };
|
|
@@ -825,41 +1293,118 @@ function createEvents(store) {
|
|
|
825
1293
|
instance?.handlers.onDropMissed?.(event);
|
|
826
1294
|
}
|
|
827
1295
|
}
|
|
1296
|
+
function cleanupPointer(pointerId) {
|
|
1297
|
+
const { internal } = store.getState();
|
|
1298
|
+
const pointerState = internal.pointerMap.get(pointerId);
|
|
1299
|
+
if (pointerState) {
|
|
1300
|
+
for (const [, hoveredObj] of pointerState.hovered) {
|
|
1301
|
+
const eventObject = hoveredObj.eventObject;
|
|
1302
|
+
const instance = eventObject.__r3f;
|
|
1303
|
+
if (instance?.eventCount) {
|
|
1304
|
+
const handlers = instance.handlers;
|
|
1305
|
+
const data = { ...hoveredObj, intersections: [] };
|
|
1306
|
+
handlers.onPointerOut?.(data);
|
|
1307
|
+
handlers.onPointerLeave?.(data);
|
|
1308
|
+
}
|
|
1309
|
+
}
|
|
1310
|
+
internal.pointerMap.delete(pointerId);
|
|
1311
|
+
}
|
|
1312
|
+
internal.pointerDirty.delete(pointerId);
|
|
1313
|
+
}
|
|
1314
|
+
function processDeferredPointer(event, pointerId) {
|
|
1315
|
+
const state = store.getState();
|
|
1316
|
+
const { internal } = state;
|
|
1317
|
+
if (!state.events.enabled) return;
|
|
1318
|
+
const filter = filterPointerEvents;
|
|
1319
|
+
const hits = intersect(event, filter);
|
|
1320
|
+
cancelPointer(hits, pointerId);
|
|
1321
|
+
function onIntersect(data) {
|
|
1322
|
+
const eventObject = data.eventObject;
|
|
1323
|
+
const instance = eventObject.__r3f;
|
|
1324
|
+
if (!instance?.eventCount) return;
|
|
1325
|
+
const handlers = instance.handlers;
|
|
1326
|
+
if (handlers.onPointerOver || handlers.onPointerEnter || handlers.onPointerOut || handlers.onPointerLeave) {
|
|
1327
|
+
const id = makeId(data);
|
|
1328
|
+
const pointerState = getPointerState(internal, pointerId);
|
|
1329
|
+
const hoveredItem = pointerState.hovered.get(id);
|
|
1330
|
+
if (!hoveredItem) {
|
|
1331
|
+
pointerState.hovered.set(id, data);
|
|
1332
|
+
handlers.onPointerOver?.(data);
|
|
1333
|
+
handlers.onPointerEnter?.(data);
|
|
1334
|
+
} else if (hoveredItem.stopped) {
|
|
1335
|
+
data.stopPropagation();
|
|
1336
|
+
}
|
|
1337
|
+
}
|
|
1338
|
+
handlers.onPointerMove?.(data);
|
|
1339
|
+
}
|
|
1340
|
+
handleIntersects(hits, event, 0, onIntersect);
|
|
1341
|
+
}
|
|
828
1342
|
function handlePointer(name) {
|
|
829
1343
|
switch (name) {
|
|
830
1344
|
case "onPointerLeave":
|
|
831
|
-
case "onPointerCancel":
|
|
832
1345
|
case "onDragLeave":
|
|
833
1346
|
return () => cancelPointer([]);
|
|
1347
|
+
// Global cancel of these events
|
|
1348
|
+
case "onPointerCancel":
|
|
1349
|
+
return (event) => {
|
|
1350
|
+
const pointerId = getPointerId(event);
|
|
1351
|
+
cleanupPointer(pointerId);
|
|
1352
|
+
};
|
|
834
1353
|
case "onLostPointerCapture":
|
|
835
1354
|
return (event) => {
|
|
836
1355
|
const { internal } = store.getState();
|
|
837
|
-
|
|
1356
|
+
const pointerId = getPointerId(event);
|
|
1357
|
+
const pointerState = internal.pointerMap.get(pointerId);
|
|
1358
|
+
if (pointerState?.captured.size) {
|
|
838
1359
|
requestAnimationFrame(() => {
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
1360
|
+
const pointerState2 = internal.pointerMap.get(pointerId);
|
|
1361
|
+
if (pointerState2?.captured.size) {
|
|
1362
|
+
pointerState2.captured.clear();
|
|
842
1363
|
}
|
|
1364
|
+
cancelPointer([], pointerId);
|
|
843
1365
|
});
|
|
844
1366
|
}
|
|
845
1367
|
};
|
|
846
1368
|
}
|
|
847
1369
|
return function handleEvent(event) {
|
|
848
1370
|
const state = store.getState();
|
|
849
|
-
const { onPointerMissed, onDragOverMissed, onDropMissed, internal } = state;
|
|
1371
|
+
const { onPointerMissed, onDragOverMissed, onDropMissed, internal, events } = state;
|
|
1372
|
+
const pointerId = getPointerId(event);
|
|
850
1373
|
internal.lastEvent.current = event;
|
|
851
|
-
if (!
|
|
1374
|
+
if (!events.enabled) return;
|
|
852
1375
|
const isPointerMove = name === "onPointerMove";
|
|
853
1376
|
const isDragOver = name === "onDragOver";
|
|
854
1377
|
const isDrop = name === "onDrop";
|
|
855
1378
|
const isClickEvent = name === "onClick" || name === "onContextMenu" || name === "onDoubleClick";
|
|
1379
|
+
const isPointerDown = name === "onPointerDown";
|
|
1380
|
+
const isPointerUp = name === "onPointerUp";
|
|
1381
|
+
const isWheel = name === "onWheel";
|
|
1382
|
+
const canDeferRaycasts = events.frameTimedRaycasts && state.frameloop === "always";
|
|
1383
|
+
if (isPointerMove && canDeferRaycasts) {
|
|
1384
|
+
events.compute?.(event, state);
|
|
1385
|
+
internal.pointerDirty.set(pointerId, event);
|
|
1386
|
+
return;
|
|
1387
|
+
}
|
|
1388
|
+
if (isWheel && canDeferRaycasts && !events.alwaysFireOnScroll) {
|
|
1389
|
+
events.compute?.(event, state);
|
|
1390
|
+
internal.pointerDirty.set(pointerId, event);
|
|
1391
|
+
return;
|
|
1392
|
+
}
|
|
1393
|
+
if ((isClickEvent || isPointerDown || isPointerUp) && internal.pointerDirty.has(pointerId)) {
|
|
1394
|
+
const deferredEvent = internal.pointerDirty.get(pointerId);
|
|
1395
|
+
internal.pointerDirty.delete(pointerId);
|
|
1396
|
+
processDeferredPointer(deferredEvent, pointerId);
|
|
1397
|
+
}
|
|
856
1398
|
const filter = isPointerMove || isDragOver || isDrop ? filterPointerEvents : void 0;
|
|
857
1399
|
const hits = intersect(event, filter);
|
|
858
|
-
const delta = isClickEvent ? calculateDistance(event) : 0;
|
|
859
|
-
if (
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
1400
|
+
const delta = isClickEvent ? calculateDistance(event, pointerId) : 0;
|
|
1401
|
+
if (isPointerDown) {
|
|
1402
|
+
const pointerState2 = getPointerState(internal, pointerId);
|
|
1403
|
+
pointerState2.initialClick = [event.offsetX, event.offsetY];
|
|
1404
|
+
pointerState2.initialHits = hits.map((hit) => hit.eventObject);
|
|
1405
|
+
}
|
|
1406
|
+
const pointerState = internal.pointerMap.get(pointerId);
|
|
1407
|
+
const initialHits = pointerState?.initialHits ?? [];
|
|
863
1408
|
if (isClickEvent && !hits.length) {
|
|
864
1409
|
if (delta <= 2) {
|
|
865
1410
|
pointerMissed(event, internal.interaction);
|
|
@@ -874,7 +1419,9 @@ function createEvents(store) {
|
|
|
874
1419
|
dropMissed(event, internal.interaction);
|
|
875
1420
|
if (onDropMissed) onDropMissed(event);
|
|
876
1421
|
}
|
|
877
|
-
if (isPointerMove || isDragOver)
|
|
1422
|
+
if (isPointerMove || isDragOver) {
|
|
1423
|
+
cancelPointer(hits, pointerId);
|
|
1424
|
+
}
|
|
878
1425
|
function onIntersect(data) {
|
|
879
1426
|
const eventObject = data.eventObject;
|
|
880
1427
|
const instance = eventObject.__r3f;
|
|
@@ -883,9 +1430,10 @@ function createEvents(store) {
|
|
|
883
1430
|
if (isPointerMove) {
|
|
884
1431
|
if (handlers.onPointerOver || handlers.onPointerEnter || handlers.onPointerOut || handlers.onPointerLeave) {
|
|
885
1432
|
const id = makeId(data);
|
|
886
|
-
const
|
|
1433
|
+
const pointerState2 = getPointerState(internal, pointerId);
|
|
1434
|
+
const hoveredItem = pointerState2.hovered.get(id);
|
|
887
1435
|
if (!hoveredItem) {
|
|
888
|
-
|
|
1436
|
+
pointerState2.hovered.set(id, data);
|
|
889
1437
|
handlers.onPointerOver?.(data);
|
|
890
1438
|
handlers.onPointerEnter?.(data);
|
|
891
1439
|
} else if (hoveredItem.stopped) {
|
|
@@ -895,9 +1443,10 @@ function createEvents(store) {
|
|
|
895
1443
|
handlers.onPointerMove?.(data);
|
|
896
1444
|
} else if (isDragOver) {
|
|
897
1445
|
const id = makeId(data);
|
|
898
|
-
const
|
|
1446
|
+
const pointerState2 = getPointerState(internal, pointerId);
|
|
1447
|
+
const hoveredItem = pointerState2.hovered.get(id);
|
|
899
1448
|
if (!hoveredItem) {
|
|
900
|
-
|
|
1449
|
+
pointerState2.hovered.set(id, data);
|
|
901
1450
|
handlers.onDragOverEnter?.(data);
|
|
902
1451
|
} else if (hoveredItem.stopped) {
|
|
903
1452
|
data.stopPropagation();
|
|
@@ -908,18 +1457,18 @@ function createEvents(store) {
|
|
|
908
1457
|
} else {
|
|
909
1458
|
const handler = handlers[name];
|
|
910
1459
|
if (handler) {
|
|
911
|
-
if (!isClickEvent ||
|
|
1460
|
+
if (!isClickEvent || initialHits.includes(eventObject)) {
|
|
912
1461
|
pointerMissed(
|
|
913
1462
|
event,
|
|
914
|
-
internal.interaction.filter((object) => !
|
|
1463
|
+
internal.interaction.filter((object) => !initialHits.includes(object))
|
|
915
1464
|
);
|
|
916
1465
|
handler(data);
|
|
917
1466
|
}
|
|
918
1467
|
} else {
|
|
919
|
-
if (isClickEvent &&
|
|
1468
|
+
if (isClickEvent && initialHits.includes(eventObject)) {
|
|
920
1469
|
pointerMissed(
|
|
921
1470
|
event,
|
|
922
|
-
internal.interaction.filter((object) => !
|
|
1471
|
+
internal.interaction.filter((object) => !initialHits.includes(object))
|
|
923
1472
|
);
|
|
924
1473
|
}
|
|
925
1474
|
}
|
|
@@ -928,7 +1477,15 @@ function createEvents(store) {
|
|
|
928
1477
|
handleIntersects(hits, event, delta, onIntersect);
|
|
929
1478
|
};
|
|
930
1479
|
}
|
|
931
|
-
|
|
1480
|
+
function flushDeferredPointers() {
|
|
1481
|
+
const { internal, events } = store.getState();
|
|
1482
|
+
if (!events.frameTimedRaycasts) return;
|
|
1483
|
+
for (const [pointerId, event] of internal.pointerDirty) {
|
|
1484
|
+
processDeferredPointer(event, pointerId);
|
|
1485
|
+
}
|
|
1486
|
+
internal.pointerDirty.clear();
|
|
1487
|
+
}
|
|
1488
|
+
return { handlePointer, flushDeferredPointers, processDeferredPointer };
|
|
932
1489
|
}
|
|
933
1490
|
const DOM_EVENTS = {
|
|
934
1491
|
onClick: ["click", false],
|
|
@@ -947,11 +1504,16 @@ const DOM_EVENTS = {
|
|
|
947
1504
|
onLostPointerCapture: ["lostpointercapture", true]
|
|
948
1505
|
};
|
|
949
1506
|
function createPointerEvents(store) {
|
|
950
|
-
const { handlePointer } = createEvents(store);
|
|
1507
|
+
const { handlePointer, flushDeferredPointers, processDeferredPointer } = createEvents(store);
|
|
1508
|
+
let nextXRPointerId = XR_POINTER_ID_START;
|
|
1509
|
+
const xrPointers = /* @__PURE__ */ new Map();
|
|
951
1510
|
return {
|
|
952
1511
|
priority: 1,
|
|
953
1512
|
enabled: true,
|
|
954
|
-
|
|
1513
|
+
frameTimedRaycasts: true,
|
|
1514
|
+
alwaysFireOnScroll: true,
|
|
1515
|
+
updateOnFrame: false,
|
|
1516
|
+
compute(event, state) {
|
|
955
1517
|
state.pointer.set(event.offsetX / state.size.width * 2 - 1, -(event.offsetY / state.size.height) * 2 + 1);
|
|
956
1518
|
state.raycaster.setFromCamera(state.pointer, state.camera);
|
|
957
1519
|
},
|
|
@@ -960,11 +1522,33 @@ function createPointerEvents(store) {
|
|
|
960
1522
|
(acc, key) => ({ ...acc, [key]: handlePointer(key) }),
|
|
961
1523
|
{}
|
|
962
1524
|
),
|
|
963
|
-
update: () => {
|
|
1525
|
+
update: (pointerId) => {
|
|
1526
|
+
const { events, internal } = store.getState();
|
|
1527
|
+
if (!events.handlers) return;
|
|
1528
|
+
if (pointerId !== void 0) {
|
|
1529
|
+
const event = internal.pointerDirty.get(pointerId);
|
|
1530
|
+
if (event) {
|
|
1531
|
+
internal.pointerDirty.delete(pointerId);
|
|
1532
|
+
processDeferredPointer(event, pointerId);
|
|
1533
|
+
} else if (internal.lastEvent?.current) {
|
|
1534
|
+
processDeferredPointer(internal.lastEvent.current, pointerId);
|
|
1535
|
+
}
|
|
1536
|
+
} else {
|
|
1537
|
+
flushDeferredPointers();
|
|
1538
|
+
if (internal.lastEvent?.current) {
|
|
1539
|
+
events.handlers.onPointerMove(internal.lastEvent.current);
|
|
1540
|
+
}
|
|
1541
|
+
}
|
|
1542
|
+
},
|
|
1543
|
+
flush: () => {
|
|
964
1544
|
const { events, internal } = store.getState();
|
|
965
|
-
|
|
1545
|
+
flushDeferredPointers();
|
|
1546
|
+
if (events.updateOnFrame && internal.lastEvent?.current && events.handlers) {
|
|
1547
|
+
events.handlers.onPointerMove(internal.lastEvent.current);
|
|
1548
|
+
}
|
|
966
1549
|
},
|
|
967
1550
|
connect: (target) => {
|
|
1551
|
+
if (!target) return;
|
|
968
1552
|
const { set, events } = store.getState();
|
|
969
1553
|
events.disconnect?.();
|
|
970
1554
|
set((state) => ({ events: { ...state.events, connected: target } }));
|
|
@@ -988,6 +1572,32 @@ function createPointerEvents(store) {
|
|
|
988
1572
|
}
|
|
989
1573
|
set((state) => ({ events: { ...state.events, connected: void 0 } }));
|
|
990
1574
|
}
|
|
1575
|
+
},
|
|
1576
|
+
registerPointer: (config) => {
|
|
1577
|
+
const pointerId = nextXRPointerId++;
|
|
1578
|
+
xrPointers.set(pointerId, config);
|
|
1579
|
+
const { internal } = store.getState();
|
|
1580
|
+
getPointerState(internal, pointerId);
|
|
1581
|
+
return pointerId;
|
|
1582
|
+
},
|
|
1583
|
+
unregisterPointer: (pointerId) => {
|
|
1584
|
+
xrPointers.delete(pointerId);
|
|
1585
|
+
const { internal } = store.getState();
|
|
1586
|
+
const pointerState = internal.pointerMap.get(pointerId);
|
|
1587
|
+
if (pointerState) {
|
|
1588
|
+
for (const [, hoveredObj] of pointerState.hovered) {
|
|
1589
|
+
const eventObject = hoveredObj.eventObject;
|
|
1590
|
+
const instance = eventObject.__r3f;
|
|
1591
|
+
if (instance?.eventCount) {
|
|
1592
|
+
const handlers = instance.handlers;
|
|
1593
|
+
const data = { ...hoveredObj, intersections: [] };
|
|
1594
|
+
handlers.onPointerOut?.(data);
|
|
1595
|
+
handlers.onPointerLeave?.(data);
|
|
1596
|
+
}
|
|
1597
|
+
}
|
|
1598
|
+
internal.pointerMap.delete(pointerId);
|
|
1599
|
+
}
|
|
1600
|
+
internal.pointerDirty.delete(pointerId);
|
|
991
1601
|
}
|
|
992
1602
|
};
|
|
993
1603
|
}
|
|
@@ -1078,23 +1688,29 @@ const createStore = (invalidate, advance) => {
|
|
|
1078
1688
|
set,
|
|
1079
1689
|
get,
|
|
1080
1690
|
// Mock objects that have to be configured
|
|
1691
|
+
// primaryStore is set after store creation (self-reference for primary, primary's store for secondary)
|
|
1692
|
+
primaryStore: null,
|
|
1081
1693
|
gl: null,
|
|
1082
1694
|
renderer: null,
|
|
1083
1695
|
camera: null,
|
|
1084
1696
|
frustum: new webgpu.Frustum(),
|
|
1085
1697
|
autoUpdateFrustum: true,
|
|
1086
1698
|
raycaster: null,
|
|
1087
|
-
events: {
|
|
1699
|
+
events: {
|
|
1700
|
+
priority: 1,
|
|
1701
|
+
enabled: true,
|
|
1702
|
+
connected: false,
|
|
1703
|
+
frameTimedRaycasts: true,
|
|
1704
|
+
alwaysFireOnScroll: true,
|
|
1705
|
+
updateOnFrame: false
|
|
1706
|
+
},
|
|
1088
1707
|
scene: null,
|
|
1089
1708
|
rootScene: null,
|
|
1090
1709
|
xr: null,
|
|
1091
1710
|
inspector: null,
|
|
1092
1711
|
invalidate: (frames = 1, stackFrames = false) => invalidate(get(), frames, stackFrames),
|
|
1093
1712
|
advance: (timestamp, runGlobalEffects) => advance(timestamp, runGlobalEffects, get()),
|
|
1094
|
-
|
|
1095
|
-
linear: false,
|
|
1096
|
-
flat: false,
|
|
1097
|
-
textureColorSpace: "srgb",
|
|
1713
|
+
textureColorSpace: webgpu.SRGBColorSpace,
|
|
1098
1714
|
isLegacy: false,
|
|
1099
1715
|
webGPUSupported: false,
|
|
1100
1716
|
isNative: false,
|
|
@@ -1134,10 +1750,40 @@ const createStore = (invalidate, advance) => {
|
|
|
1134
1750
|
getCurrentViewport
|
|
1135
1751
|
},
|
|
1136
1752
|
setEvents: (events) => set((state2) => ({ ...state2, events: { ...state2.events, ...events } })),
|
|
1137
|
-
setSize: (width, height, top
|
|
1138
|
-
const
|
|
1139
|
-
|
|
1140
|
-
|
|
1753
|
+
setSize: (width, height, top, left) => {
|
|
1754
|
+
const state2 = get();
|
|
1755
|
+
if (width === void 0) {
|
|
1756
|
+
set({ _sizeImperative: false });
|
|
1757
|
+
if (state2._sizeProps) {
|
|
1758
|
+
const { width: propW, height: propH } = state2._sizeProps;
|
|
1759
|
+
if (propW !== void 0 || propH !== void 0) {
|
|
1760
|
+
const currentSize = state2.size;
|
|
1761
|
+
const newSize = {
|
|
1762
|
+
width: propW ?? currentSize.width,
|
|
1763
|
+
height: propH ?? currentSize.height,
|
|
1764
|
+
top: currentSize.top,
|
|
1765
|
+
left: currentSize.left
|
|
1766
|
+
};
|
|
1767
|
+
set((s) => ({
|
|
1768
|
+
size: newSize,
|
|
1769
|
+
viewport: { ...s.viewport, ...getCurrentViewport(state2.camera, defaultTarget, newSize) }
|
|
1770
|
+
}));
|
|
1771
|
+
scheduler.getScheduler().invalidate();
|
|
1772
|
+
}
|
|
1773
|
+
}
|
|
1774
|
+
return;
|
|
1775
|
+
}
|
|
1776
|
+
const w = width;
|
|
1777
|
+
const h = height ?? width;
|
|
1778
|
+
const t = top ?? state2.size.top;
|
|
1779
|
+
const l = left ?? state2.size.left;
|
|
1780
|
+
const size = { width: w, height: h, top: t, left: l };
|
|
1781
|
+
set((s) => ({
|
|
1782
|
+
size,
|
|
1783
|
+
viewport: { ...s.viewport, ...getCurrentViewport(state2.camera, defaultTarget, size) },
|
|
1784
|
+
_sizeImperative: true
|
|
1785
|
+
}));
|
|
1786
|
+
scheduler.getScheduler().invalidate();
|
|
1141
1787
|
},
|
|
1142
1788
|
setDpr: (dpr) => set((state2) => {
|
|
1143
1789
|
const resolved = calculateDpr(dpr);
|
|
@@ -1148,22 +1794,32 @@ const createStore = (invalidate, advance) => {
|
|
|
1148
1794
|
},
|
|
1149
1795
|
setError: (error) => set(() => ({ error })),
|
|
1150
1796
|
error: null,
|
|
1151
|
-
//* TSL State (managed via hooks: useUniforms, useNodes, useTextures,
|
|
1797
|
+
//* TSL State (managed via hooks: useUniforms, useNodes, useBuffers, useGPUStorage, useTextures, useRenderPipeline) ==============================
|
|
1152
1798
|
uniforms: {},
|
|
1153
1799
|
nodes: {},
|
|
1800
|
+
buffers: {},
|
|
1801
|
+
gpuStorage: {},
|
|
1154
1802
|
textures: /* @__PURE__ */ new Map(),
|
|
1155
|
-
|
|
1803
|
+
_textureRefs: /* @__PURE__ */ new Map(),
|
|
1804
|
+
renderPipeline: null,
|
|
1156
1805
|
passes: {},
|
|
1806
|
+
_hmrVersion: 0,
|
|
1807
|
+
_sizeImperative: false,
|
|
1808
|
+
_sizeProps: null,
|
|
1157
1809
|
previousRoot: void 0,
|
|
1158
1810
|
internal: {
|
|
1159
1811
|
// Events
|
|
1160
1812
|
interaction: [],
|
|
1161
|
-
hovered: /* @__PURE__ */ new Map(),
|
|
1162
1813
|
subscribers: [],
|
|
1814
|
+
// Per-pointer state (new unified structure)
|
|
1815
|
+
pointerMap: /* @__PURE__ */ new Map(),
|
|
1816
|
+
pointerDirty: /* @__PURE__ */ new Map(),
|
|
1817
|
+
lastEvent: React__namespace.createRef(),
|
|
1818
|
+
// Deprecated but kept for backwards compatibility
|
|
1819
|
+
hovered: /* @__PURE__ */ new Map(),
|
|
1163
1820
|
initialClick: [0, 0],
|
|
1164
1821
|
initialHits: [],
|
|
1165
1822
|
capturedMap: /* @__PURE__ */ new Map(),
|
|
1166
|
-
lastEvent: React__namespace.createRef(),
|
|
1167
1823
|
// Visibility tracking (onFramed, onOccluded, onVisible)
|
|
1168
1824
|
visibilityRegistry: /* @__PURE__ */ new Map(),
|
|
1169
1825
|
// Occlusion system (WebGPU only)
|
|
@@ -1246,13 +1902,22 @@ const createStore = (invalidate, advance) => {
|
|
|
1246
1902
|
rootStore.subscribe(() => {
|
|
1247
1903
|
const { camera, size, viewport, set, internal } = rootStore.getState();
|
|
1248
1904
|
const actualRenderer = internal.actualRenderer;
|
|
1905
|
+
const canvasTarget = internal.canvasTarget;
|
|
1249
1906
|
if (size.width !== oldSize.width || size.height !== oldSize.height || viewport.dpr !== oldDpr) {
|
|
1250
1907
|
oldSize = size;
|
|
1251
1908
|
oldDpr = viewport.dpr;
|
|
1252
1909
|
updateCamera(camera, size);
|
|
1253
|
-
if (
|
|
1254
|
-
|
|
1255
|
-
|
|
1910
|
+
if (internal.isSecondary && canvasTarget) {
|
|
1911
|
+
if (viewport.dpr > 0) canvasTarget.setPixelRatio(viewport.dpr);
|
|
1912
|
+
canvasTarget.setSize(size.width, size.height, false);
|
|
1913
|
+
} else {
|
|
1914
|
+
if (viewport.dpr > 0) actualRenderer.setPixelRatio(viewport.dpr);
|
|
1915
|
+
actualRenderer.setSize(size.width, size.height, false);
|
|
1916
|
+
if (canvasTarget) {
|
|
1917
|
+
if (viewport.dpr > 0) canvasTarget.setPixelRatio(viewport.dpr);
|
|
1918
|
+
canvasTarget.setSize(size.width, size.height, false);
|
|
1919
|
+
}
|
|
1920
|
+
}
|
|
1256
1921
|
}
|
|
1257
1922
|
if (camera !== oldCamera) {
|
|
1258
1923
|
oldCamera = camera;
|
|
@@ -1323,1076 +1988,45 @@ useLoader.clear = function(loader, input) {
|
|
|
1323
1988
|
};
|
|
1324
1989
|
useLoader.loader = getLoader;
|
|
1325
1990
|
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
const
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
}
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
else {
|
|
1366
|
-
const constraintType = before ? "before" : "after";
|
|
1367
|
-
console.warn(`[useFrame] Phase "${before ?? after}" not found for '${constraintType}' constraint`);
|
|
1368
|
-
}
|
|
1369
|
-
this.phases.splice(insertIndex, 0, node);
|
|
1370
|
-
this.phaseMap.set(name, node);
|
|
1371
|
-
this.invalidateCache();
|
|
1372
|
-
}
|
|
1373
|
-
/**
|
|
1374
|
-
* Get ordered list of phase names
|
|
1375
|
-
*/
|
|
1376
|
-
getOrderedPhases() {
|
|
1377
|
-
if (this.orderedNamesCache === null) this.orderedNamesCache = this.phases.map((p) => p.name);
|
|
1378
|
-
return this.orderedNamesCache;
|
|
1379
|
-
}
|
|
1380
|
-
/**
|
|
1381
|
-
* Check if a phase exists
|
|
1382
|
-
*/
|
|
1383
|
-
hasPhase(name) {
|
|
1384
|
-
return this.phaseMap.has(name);
|
|
1385
|
-
}
|
|
1386
|
-
/**
|
|
1387
|
-
* Get the index of a phase (-1 if not found)
|
|
1388
|
-
*/
|
|
1389
|
-
getPhaseIndex(name) {
|
|
1390
|
-
if (!name) return -1;
|
|
1391
|
-
return this.phases.findIndex((p) => p.name === name);
|
|
1392
|
-
}
|
|
1393
|
-
/**
|
|
1394
|
-
* Ensure a phase exists, creating an auto-generated one if needed.
|
|
1395
|
-
* Used for resolving before/after constraints.
|
|
1396
|
-
*
|
|
1397
|
-
* @param name - The phase name to ensure exists
|
|
1398
|
-
* @returns The phase name (may be auto-generated like 'before:render')
|
|
1399
|
-
*/
|
|
1400
|
-
ensurePhase(name) {
|
|
1401
|
-
if (this.phaseMap.has(name)) return name;
|
|
1402
|
-
const node = { name, isAutoGenerated: true };
|
|
1403
|
-
this.phases.push(node);
|
|
1404
|
-
this.phaseMap.set(name, node);
|
|
1405
|
-
this.invalidateCache();
|
|
1406
|
-
return name;
|
|
1407
|
-
}
|
|
1408
|
-
/**
|
|
1409
|
-
* Resolve where a job with before/after constraints should go.
|
|
1410
|
-
* Creates auto-generated phases if needed.
|
|
1411
|
-
*
|
|
1412
|
-
* @param before - Phase(s) to run before
|
|
1413
|
-
* @param after - Phase(s) to run after
|
|
1414
|
-
* @returns The resolved phase name
|
|
1415
|
-
*/
|
|
1416
|
-
resolveConstraintPhase(before, after) {
|
|
1417
|
-
const beforeArr = before ? Array.isArray(before) ? before : [before] : [];
|
|
1418
|
-
const afterArr = after ? Array.isArray(after) ? after : [after] : [];
|
|
1419
|
-
if (beforeArr.length > 0) {
|
|
1420
|
-
return this.ensureAutoPhase(beforeArr[0], "before", 0);
|
|
1421
|
-
}
|
|
1422
|
-
if (afterArr.length > 0) {
|
|
1423
|
-
return this.ensureAutoPhase(afterArr[0], "after", 1);
|
|
1424
|
-
}
|
|
1425
|
-
return "update";
|
|
1426
|
-
}
|
|
1427
|
-
/**
|
|
1428
|
-
* Ensure an auto-generated phase exists relative to a target phase.
|
|
1429
|
-
* Creates the phase if it doesn't exist, inserting it at the correct position.
|
|
1430
|
-
*
|
|
1431
|
-
* @param target - The target phase name to position relative to
|
|
1432
|
-
* @param prefix - Prefix for auto-generated phase name ('before' or 'after')
|
|
1433
|
-
* @param offset - Insertion offset (0 for before, 1 for after)
|
|
1434
|
-
* @returns The auto-generated phase name
|
|
1435
|
-
*/
|
|
1436
|
-
ensureAutoPhase(target, prefix, offset) {
|
|
1437
|
-
const autoName = `${prefix}:${target}`;
|
|
1438
|
-
if (this.phaseMap.has(autoName)) return autoName;
|
|
1439
|
-
const node = { name: autoName, isAutoGenerated: true };
|
|
1440
|
-
const targetIndex = this.getPhaseIndex(target);
|
|
1441
|
-
if (targetIndex !== -1) this.phases.splice(targetIndex + offset, 0, node);
|
|
1442
|
-
else this.phases.push(node);
|
|
1443
|
-
this.phaseMap.set(autoName, node);
|
|
1444
|
-
this.invalidateCache();
|
|
1445
|
-
return autoName;
|
|
1446
|
-
}
|
|
1447
|
-
// Internal --------------------------------
|
|
1448
|
-
invalidateCache() {
|
|
1449
|
-
this.orderedNamesCache = null;
|
|
1450
|
-
}
|
|
1451
|
-
}
|
|
1452
|
-
|
|
1453
|
-
function rebuildSortedJobs(jobs, phaseGraph) {
|
|
1454
|
-
const orderedPhases = phaseGraph.getOrderedPhases();
|
|
1455
|
-
const buckets = /* @__PURE__ */ new Map();
|
|
1456
|
-
for (const phase of orderedPhases) {
|
|
1457
|
-
buckets.set(phase, []);
|
|
1458
|
-
}
|
|
1459
|
-
for (const job of jobs.values()) {
|
|
1460
|
-
if (!job.enabled) continue;
|
|
1461
|
-
let bucket = buckets.get(job.phase);
|
|
1462
|
-
if (!bucket) {
|
|
1463
|
-
bucket = [];
|
|
1464
|
-
buckets.set(job.phase, bucket);
|
|
1465
|
-
}
|
|
1466
|
-
bucket.push(job);
|
|
1467
|
-
}
|
|
1468
|
-
const sortedBuckets = [];
|
|
1469
|
-
for (const phase of orderedPhases) {
|
|
1470
|
-
const bucket = buckets.get(phase);
|
|
1471
|
-
if (!bucket || bucket.length === 0) continue;
|
|
1472
|
-
bucket.sort((a, b) => {
|
|
1473
|
-
if (a.priority !== b.priority) return b.priority - a.priority;
|
|
1474
|
-
return a.index - b.index;
|
|
1475
|
-
});
|
|
1476
|
-
sortedBuckets.push(hasCrossJobConstraints(bucket) ? topologicalSort(bucket) : bucket);
|
|
1477
|
-
}
|
|
1478
|
-
for (const [phase, bucket] of buckets) {
|
|
1479
|
-
if (!orderedPhases.includes(phase) && bucket.length > 0) {
|
|
1480
|
-
bucket.sort((a, b) => {
|
|
1481
|
-
if (a.priority !== b.priority) return b.priority - a.priority;
|
|
1482
|
-
return a.index - b.index;
|
|
1483
|
-
});
|
|
1484
|
-
sortedBuckets.push(bucket);
|
|
1485
|
-
}
|
|
1486
|
-
}
|
|
1487
|
-
return sortedBuckets.flat();
|
|
1488
|
-
}
|
|
1489
|
-
function hasCrossJobConstraints(bucket) {
|
|
1490
|
-
const jobIds = new Set(bucket.map((j) => j.id));
|
|
1491
|
-
for (const job of bucket) {
|
|
1492
|
-
for (const ref of job.before) {
|
|
1493
|
-
if (jobIds.has(ref)) return true;
|
|
1494
|
-
}
|
|
1495
|
-
for (const ref of job.after) {
|
|
1496
|
-
if (jobIds.has(ref)) return true;
|
|
1497
|
-
}
|
|
1498
|
-
}
|
|
1499
|
-
return false;
|
|
1500
|
-
}
|
|
1501
|
-
function topologicalSort(jobs) {
|
|
1502
|
-
const n = jobs.length;
|
|
1503
|
-
if (n <= 1) return jobs;
|
|
1504
|
-
const jobMap = /* @__PURE__ */ new Map();
|
|
1505
|
-
const inDegree = /* @__PURE__ */ new Map();
|
|
1506
|
-
const adjacency = /* @__PURE__ */ new Map();
|
|
1507
|
-
for (const job of jobs) {
|
|
1508
|
-
jobMap.set(job.id, job);
|
|
1509
|
-
inDegree.set(job.id, 0);
|
|
1510
|
-
adjacency.set(job.id, []);
|
|
1511
|
-
}
|
|
1512
|
-
for (const job of jobs) {
|
|
1513
|
-
for (const ref of job.before) {
|
|
1514
|
-
if (jobMap.has(ref)) {
|
|
1515
|
-
adjacency.get(job.id).push(ref);
|
|
1516
|
-
inDegree.set(ref, inDegree.get(ref) + 1);
|
|
1517
|
-
}
|
|
1518
|
-
}
|
|
1519
|
-
for (const ref of job.after) {
|
|
1520
|
-
if (jobMap.has(ref)) {
|
|
1521
|
-
adjacency.get(ref).push(job.id);
|
|
1522
|
-
inDegree.set(job.id, inDegree.get(job.id) + 1);
|
|
1523
|
-
}
|
|
1524
|
-
}
|
|
1525
|
-
}
|
|
1526
|
-
const queue = [];
|
|
1527
|
-
for (const job of jobs) {
|
|
1528
|
-
if (inDegree.get(job.id) === 0) {
|
|
1529
|
-
queue.push(job);
|
|
1530
|
-
}
|
|
1531
|
-
}
|
|
1532
|
-
queue.sort((a, b) => {
|
|
1533
|
-
if (a.priority !== b.priority) return b.priority - a.priority;
|
|
1534
|
-
return a.index - b.index;
|
|
1535
|
-
});
|
|
1536
|
-
const result = [];
|
|
1537
|
-
while (queue.length > 0) {
|
|
1538
|
-
const job = queue.shift();
|
|
1539
|
-
result.push(job);
|
|
1540
|
-
const neighbors = adjacency.get(job.id) || [];
|
|
1541
|
-
for (const neighborId of neighbors) {
|
|
1542
|
-
const newDegree = inDegree.get(neighborId) - 1;
|
|
1543
|
-
inDegree.set(neighborId, newDegree);
|
|
1544
|
-
if (newDegree === 0) {
|
|
1545
|
-
const neighbor = jobMap.get(neighborId);
|
|
1546
|
-
insertSorted(queue, neighbor);
|
|
1547
|
-
}
|
|
1548
|
-
}
|
|
1549
|
-
}
|
|
1550
|
-
if (result.length !== n) {
|
|
1551
|
-
console.warn("[useFrame] Circular dependency detected in job constraints");
|
|
1552
|
-
const resultIds = new Set(result.map((j) => j.id));
|
|
1553
|
-
for (const job of jobs) {
|
|
1554
|
-
if (!resultIds.has(job.id)) result.push(job);
|
|
1555
|
-
}
|
|
1556
|
-
}
|
|
1557
|
-
return result;
|
|
1558
|
-
}
|
|
1559
|
-
function insertSorted(arr, job) {
|
|
1560
|
-
let i = 0;
|
|
1561
|
-
while (i < arr.length) {
|
|
1562
|
-
const cmp = arr[i];
|
|
1563
|
-
if (job.priority > cmp.priority || job.priority === cmp.priority && job.index < cmp.index) {
|
|
1564
|
-
break;
|
|
1565
|
-
}
|
|
1566
|
-
i++;
|
|
1567
|
-
}
|
|
1568
|
-
arr.splice(i, 0, job);
|
|
1569
|
-
}
|
|
1570
|
-
|
|
1571
|
-
function shouldRun(job, now) {
|
|
1572
|
-
if (!job.enabled) return false;
|
|
1573
|
-
if (!job.fps) return true;
|
|
1574
|
-
const minInterval = 1e3 / job.fps;
|
|
1575
|
-
const lastRun = job.lastRun ?? 0;
|
|
1576
|
-
const elapsed = now - lastRun;
|
|
1577
|
-
if (elapsed < minInterval) return false;
|
|
1578
|
-
if (job.drop) {
|
|
1579
|
-
job.lastRun = now;
|
|
1580
|
-
} else {
|
|
1581
|
-
const steps = Math.floor(elapsed / minInterval);
|
|
1582
|
-
job.lastRun = lastRun + steps * minInterval;
|
|
1583
|
-
if (job.lastRun < now - minInterval) {
|
|
1584
|
-
job.lastRun = now;
|
|
1585
|
-
}
|
|
1586
|
-
}
|
|
1587
|
-
return true;
|
|
1588
|
-
}
|
|
1589
|
-
function resetJobTiming(job) {
|
|
1590
|
-
job.lastRun = void 0;
|
|
1591
|
-
}
|
|
1592
|
-
|
|
1593
|
-
var __defProp = Object.defineProperty;
|
|
1594
|
-
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
1595
|
-
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
1596
|
-
const hmrData = (() => {
|
|
1597
|
-
if (typeof process !== "undefined" && process.env.NODE_ENV === "test") return void 0;
|
|
1598
|
-
if (typeof import_meta_hot !== "undefined") return import_meta_hot;
|
|
1599
|
-
try {
|
|
1600
|
-
return (0, eval)("import.meta.hot");
|
|
1601
|
-
} catch {
|
|
1602
|
-
return void 0;
|
|
1603
|
-
}
|
|
1604
|
-
})();
|
|
1605
|
-
const _Scheduler = class _Scheduler {
|
|
1606
|
-
//* Constructor ================================
|
|
1607
|
-
constructor() {
|
|
1608
|
-
//* Critical State ================================
|
|
1609
|
-
__publicField(this, "roots", /* @__PURE__ */ new Map());
|
|
1610
|
-
__publicField(this, "phaseGraph");
|
|
1611
|
-
__publicField(this, "loopState", {
|
|
1612
|
-
running: false,
|
|
1613
|
-
rafHandle: null,
|
|
1614
|
-
lastTime: null,
|
|
1615
|
-
// null = uninitialized, 0+ = valid timestamp
|
|
1616
|
-
frameCount: 0,
|
|
1617
|
-
elapsedTime: 0,
|
|
1618
|
-
createdAt: performance.now()
|
|
1619
|
-
});
|
|
1620
|
-
__publicField(this, "stoppedTime", 0);
|
|
1621
|
-
//* Private State ================================
|
|
1622
|
-
__publicField(this, "nextRootIndex", 0);
|
|
1623
|
-
__publicField(this, "globalBeforeJobs", /* @__PURE__ */ new Map());
|
|
1624
|
-
__publicField(this, "globalAfterJobs", /* @__PURE__ */ new Map());
|
|
1625
|
-
__publicField(this, "nextGlobalIndex", 0);
|
|
1626
|
-
__publicField(this, "idleCallbacks", /* @__PURE__ */ new Set());
|
|
1627
|
-
__publicField(this, "nextJobIndex", 0);
|
|
1628
|
-
__publicField(this, "jobStateListeners", /* @__PURE__ */ new Map());
|
|
1629
|
-
__publicField(this, "pendingFrames", 0);
|
|
1630
|
-
__publicField(this, "_frameloop", "always");
|
|
1631
|
-
//* Independent Mode & Error Handling State ================================
|
|
1632
|
-
__publicField(this, "_independent", false);
|
|
1633
|
-
__publicField(this, "errorHandler", null);
|
|
1634
|
-
__publicField(this, "rootReadyCallbacks", /* @__PURE__ */ new Set());
|
|
1635
|
-
//* Core Loop Execution Methods ================================
|
|
1636
|
-
/**
|
|
1637
|
-
* Main RAF loop callback.
|
|
1638
|
-
* Executes frame, handles demand mode, and schedules next frame.
|
|
1639
|
-
* @param {number} timestamp - RAF timestamp in milliseconds
|
|
1640
|
-
* @returns {void}
|
|
1641
|
-
* @private
|
|
1642
|
-
*/
|
|
1643
|
-
__publicField(this, "loop", (timestamp) => {
|
|
1644
|
-
if (!this.loopState.running) return;
|
|
1645
|
-
this.executeFrame(timestamp);
|
|
1646
|
-
if (this._frameloop === "demand") {
|
|
1647
|
-
this.pendingFrames = Math.max(0, this.pendingFrames - 1);
|
|
1648
|
-
if (this.pendingFrames === 0) {
|
|
1649
|
-
this.notifyIdle(timestamp);
|
|
1650
|
-
return this.stop();
|
|
1651
|
-
}
|
|
1652
|
-
}
|
|
1653
|
-
this.loopState.rafHandle = requestAnimationFrame(this.loop);
|
|
1654
|
-
});
|
|
1655
|
-
this.phaseGraph = new PhaseGraph();
|
|
1656
|
-
}
|
|
1657
|
-
static get instance() {
|
|
1658
|
-
return globalThis[_Scheduler.INSTANCE_KEY] ?? null;
|
|
1659
|
-
}
|
|
1660
|
-
static set instance(value) {
|
|
1661
|
-
globalThis[_Scheduler.INSTANCE_KEY] = value;
|
|
1662
|
-
}
|
|
1663
|
-
/**
|
|
1664
|
-
* Get the global scheduler instance (creates if doesn't exist).
|
|
1665
|
-
* Uses HMR data to preserve instance across hot reloads.
|
|
1666
|
-
* @returns {Scheduler} The singleton scheduler instance
|
|
1667
|
-
*/
|
|
1668
|
-
static get() {
|
|
1669
|
-
if (!_Scheduler.instance && hmrData?.data?.scheduler) {
|
|
1670
|
-
_Scheduler.instance = hmrData.data.scheduler;
|
|
1671
|
-
}
|
|
1672
|
-
if (!_Scheduler.instance) {
|
|
1673
|
-
_Scheduler.instance = new _Scheduler();
|
|
1674
|
-
if (hmrData?.data) {
|
|
1675
|
-
hmrData.data.scheduler = _Scheduler.instance;
|
|
1676
|
-
}
|
|
1677
|
-
}
|
|
1678
|
-
return _Scheduler.instance;
|
|
1679
|
-
}
|
|
1680
|
-
/**
|
|
1681
|
-
* Reset the singleton instance. Stops the loop and clears all state.
|
|
1682
|
-
* Primarily used for testing to ensure clean state between tests.
|
|
1683
|
-
* @returns {void}
|
|
1684
|
-
*/
|
|
1685
|
-
static reset() {
|
|
1686
|
-
if (_Scheduler.instance) {
|
|
1687
|
-
_Scheduler.instance.stop();
|
|
1688
|
-
_Scheduler.instance = null;
|
|
1689
|
-
}
|
|
1690
|
-
if (hmrData?.data) {
|
|
1691
|
-
hmrData.data.scheduler = null;
|
|
1692
|
-
}
|
|
1693
|
-
}
|
|
1694
|
-
//* Getters & Setters ================================
|
|
1695
|
-
get phases() {
|
|
1696
|
-
return this.phaseGraph.getOrderedPhases();
|
|
1697
|
-
}
|
|
1698
|
-
get frameloop() {
|
|
1699
|
-
return this._frameloop;
|
|
1700
|
-
}
|
|
1701
|
-
set frameloop(mode) {
|
|
1702
|
-
if (this._frameloop === mode) return;
|
|
1703
|
-
const wasAlways = this._frameloop === "always";
|
|
1704
|
-
this._frameloop = mode;
|
|
1705
|
-
if (mode === "always" && !this.loopState.running && this.roots.size > 0) this.start();
|
|
1706
|
-
else if (mode !== "always" && wasAlways) this.stop();
|
|
1707
|
-
}
|
|
1708
|
-
get isRunning() {
|
|
1709
|
-
return this.loopState.running;
|
|
1710
|
-
}
|
|
1711
|
-
get isReady() {
|
|
1712
|
-
return this.roots.size > 0;
|
|
1713
|
-
}
|
|
1714
|
-
get independent() {
|
|
1715
|
-
return this._independent;
|
|
1716
|
-
}
|
|
1717
|
-
set independent(value) {
|
|
1718
|
-
this._independent = value;
|
|
1719
|
-
if (value) this.ensureDefaultRoot();
|
|
1720
|
-
}
|
|
1721
|
-
//* Root Management Methods ================================
|
|
1722
|
-
/**
|
|
1723
|
-
* Register a root (Canvas) with the scheduler.
|
|
1724
|
-
* The first root to register starts the RAF loop (if frameloop='always').
|
|
1725
|
-
* @param {string} id - Unique identifier for this root
|
|
1726
|
-
* @param {RootOptions} [options] - Optional configuration with getState and onError callbacks
|
|
1727
|
-
* @returns {() => void} Unsubscribe function to remove this root
|
|
1728
|
-
*/
|
|
1729
|
-
registerRoot(id, options = {}) {
|
|
1730
|
-
if (this.roots.has(id)) {
|
|
1731
|
-
console.warn(`[Scheduler] Root "${id}" already registered`);
|
|
1732
|
-
return () => this.unregisterRoot(id);
|
|
1733
|
-
}
|
|
1734
|
-
const entry = {
|
|
1735
|
-
id,
|
|
1736
|
-
getState: options.getState ?? (() => ({})),
|
|
1737
|
-
jobs: /* @__PURE__ */ new Map(),
|
|
1738
|
-
sortedJobs: [],
|
|
1739
|
-
needsRebuild: false
|
|
1740
|
-
};
|
|
1741
|
-
if (options.onError) {
|
|
1742
|
-
this.errorHandler = options.onError;
|
|
1743
|
-
}
|
|
1744
|
-
this.roots.set(id, entry);
|
|
1745
|
-
if (this.roots.size === 1) {
|
|
1746
|
-
this.notifyRootReady();
|
|
1747
|
-
if (this._frameloop === "always") this.start();
|
|
1748
|
-
}
|
|
1749
|
-
return () => this.unregisterRoot(id);
|
|
1750
|
-
}
|
|
1751
|
-
/**
|
|
1752
|
-
* Unregister a root from the scheduler.
|
|
1753
|
-
* Cleans up all job state listeners for this root's jobs.
|
|
1754
|
-
* The last root to unregister stops the RAF loop.
|
|
1755
|
-
* @param {string} id - The root ID to unregister
|
|
1756
|
-
* @returns {void}
|
|
1757
|
-
*/
|
|
1758
|
-
unregisterRoot(id) {
|
|
1759
|
-
const root = this.roots.get(id);
|
|
1760
|
-
if (!root) return;
|
|
1761
|
-
for (const jobId of root.jobs.keys()) {
|
|
1762
|
-
this.jobStateListeners.delete(jobId);
|
|
1763
|
-
}
|
|
1764
|
-
this.roots.delete(id);
|
|
1765
|
-
if (this.roots.size === 0) {
|
|
1766
|
-
this.stop();
|
|
1767
|
-
this.errorHandler = null;
|
|
1768
|
-
}
|
|
1769
|
-
}
|
|
1770
|
-
/**
|
|
1771
|
-
* Subscribe to be notified when a root becomes available.
|
|
1772
|
-
* Fires immediately if a root already exists.
|
|
1773
|
-
* @param {() => void} callback - Function called when first root registers
|
|
1774
|
-
* @returns {() => void} Unsubscribe function
|
|
1775
|
-
*/
|
|
1776
|
-
onRootReady(callback) {
|
|
1777
|
-
if (this.roots.size > 0) {
|
|
1778
|
-
callback();
|
|
1779
|
-
return () => {
|
|
1780
|
-
};
|
|
1781
|
-
}
|
|
1782
|
-
this.rootReadyCallbacks.add(callback);
|
|
1783
|
-
return () => this.rootReadyCallbacks.delete(callback);
|
|
1784
|
-
}
|
|
1785
|
-
/**
|
|
1786
|
-
* Notify all registered root-ready callbacks.
|
|
1787
|
-
* Called when the first root registers.
|
|
1788
|
-
* @returns {void}
|
|
1789
|
-
* @private
|
|
1790
|
-
*/
|
|
1791
|
-
notifyRootReady() {
|
|
1792
|
-
for (const cb of this.rootReadyCallbacks) {
|
|
1793
|
-
try {
|
|
1794
|
-
cb();
|
|
1795
|
-
} catch (error) {
|
|
1796
|
-
console.error("[Scheduler] Error in root-ready callback:", error);
|
|
1797
|
-
}
|
|
1798
|
-
}
|
|
1799
|
-
this.rootReadyCallbacks.clear();
|
|
1800
|
-
}
|
|
1801
|
-
/**
|
|
1802
|
-
* Ensure a default root exists for independent mode.
|
|
1803
|
-
* Creates a minimal root with no state provider.
|
|
1804
|
-
* @returns {void}
|
|
1805
|
-
* @private
|
|
1806
|
-
*/
|
|
1807
|
-
ensureDefaultRoot() {
|
|
1808
|
-
if (!this.roots.has("__default__")) {
|
|
1809
|
-
this.registerRoot("__default__");
|
|
1810
|
-
}
|
|
1811
|
-
}
|
|
1812
|
-
/**
|
|
1813
|
-
* Trigger error handling for job errors.
|
|
1814
|
-
* Uses the bound error handler if available, otherwise logs to console.
|
|
1815
|
-
* @param {Error} error - The error to handle
|
|
1816
|
-
* @returns {void}
|
|
1817
|
-
*/
|
|
1818
|
-
triggerError(error) {
|
|
1819
|
-
if (this.errorHandler) this.errorHandler(error);
|
|
1820
|
-
else console.error("[Scheduler]", error);
|
|
1821
|
-
}
|
|
1822
|
-
//* Phase Management Methods ================================
|
|
1823
|
-
/**
|
|
1824
|
-
* Add a named phase to the scheduler's execution order.
|
|
1825
|
-
* Marks all roots for rebuild to incorporate the new phase.
|
|
1826
|
-
* @param {string} name - The phase name (e.g., 'physics', 'postprocess')
|
|
1827
|
-
* @param {AddPhaseOptions} [options] - Positioning options (before/after other phases)
|
|
1828
|
-
* @returns {void}
|
|
1829
|
-
* @example
|
|
1830
|
-
* scheduler.addPhase('physics', { before: 'update' });
|
|
1831
|
-
* scheduler.addPhase('postprocess', { after: 'render' });
|
|
1832
|
-
*/
|
|
1833
|
-
addPhase(name, options) {
|
|
1834
|
-
this.phaseGraph.addPhase(name, options);
|
|
1835
|
-
for (const root of this.roots.values()) {
|
|
1836
|
-
root.needsRebuild = true;
|
|
1837
|
-
}
|
|
1838
|
-
}
|
|
1839
|
-
/**
|
|
1840
|
-
* Check if a phase exists in the scheduler.
|
|
1841
|
-
* @param {string} name - The phase name to check
|
|
1842
|
-
* @returns {boolean} True if the phase exists
|
|
1843
|
-
*/
|
|
1844
|
-
hasPhase(name) {
|
|
1845
|
-
return this.phaseGraph.hasPhase(name);
|
|
1846
|
-
}
|
|
1847
|
-
//* Global Job Registration Methods (Deprecated APIs) ================================
|
|
1848
|
-
/**
|
|
1849
|
-
* Register a global job that runs once per frame (not per-root).
|
|
1850
|
-
* Used internally by deprecated addEffect/addAfterEffect APIs.
|
|
1851
|
-
* @param {'before' | 'after'} phase - When to run: 'before' all roots or 'after' all roots
|
|
1852
|
-
* @param {string} id - Unique identifier for this global job
|
|
1853
|
-
* @param {(timestamp: number) => void} callback - Function called each frame with RAF timestamp
|
|
1854
|
-
* @returns {() => void} Unsubscribe function to remove this global job
|
|
1855
|
-
* @deprecated Use useFrame with phases instead
|
|
1856
|
-
*/
|
|
1857
|
-
registerGlobal(phase, id, callback) {
|
|
1858
|
-
const job = { id, callback };
|
|
1859
|
-
if (phase === "before") {
|
|
1860
|
-
this.globalBeforeJobs.set(id, job);
|
|
1861
|
-
} else {
|
|
1862
|
-
this.globalAfterJobs.set(id, job);
|
|
1863
|
-
}
|
|
1864
|
-
return () => {
|
|
1865
|
-
if (phase === "before") this.globalBeforeJobs.delete(id);
|
|
1866
|
-
else this.globalAfterJobs.delete(id);
|
|
1867
|
-
};
|
|
1868
|
-
}
|
|
1869
|
-
//* Idle Callback Methods (Deprecated API) ================================
|
|
1870
|
-
/**
|
|
1871
|
-
* Register an idle callback that fires when the loop stops.
|
|
1872
|
-
* Used internally by deprecated addTail API.
|
|
1873
|
-
* @param {(timestamp: number) => void} callback - Function called when loop becomes idle
|
|
1874
|
-
* @returns {() => void} Unsubscribe function to remove this idle callback
|
|
1875
|
-
* @deprecated Use demand mode with invalidate() instead
|
|
1876
|
-
*/
|
|
1877
|
-
onIdle(callback) {
|
|
1878
|
-
this.idleCallbacks.add(callback);
|
|
1879
|
-
return () => this.idleCallbacks.delete(callback);
|
|
1880
|
-
}
|
|
1881
|
-
/**
|
|
1882
|
-
* Notify all registered idle callbacks.
|
|
1883
|
-
* Called when the loop stops in demand mode.
|
|
1884
|
-
* @param {number} timestamp - The RAF timestamp when idle occurred
|
|
1885
|
-
* @returns {void}
|
|
1886
|
-
* @private
|
|
1887
|
-
*/
|
|
1888
|
-
notifyIdle(timestamp) {
|
|
1889
|
-
for (const cb of this.idleCallbacks) {
|
|
1890
|
-
try {
|
|
1891
|
-
cb(timestamp);
|
|
1892
|
-
} catch (error) {
|
|
1893
|
-
console.error("[Scheduler] Error in idle callback:", error);
|
|
1894
|
-
}
|
|
1895
|
-
}
|
|
1896
|
-
}
|
|
1897
|
-
//* Job Registration & Management Methods ================================
|
|
1898
|
-
/**
|
|
1899
|
-
* Register a job (frame callback) with a specific root.
|
|
1900
|
-
* This is the core registration method used by useFrame internally.
|
|
1901
|
-
* @param {FrameNextCallback} callback - The function to call each frame
|
|
1902
|
-
* @param {JobOptions & { rootId?: string; system?: boolean }} [options] - Job configuration
|
|
1903
|
-
* @param {string} [options.rootId] - Target root ID (defaults to first registered root)
|
|
1904
|
-
* @param {string} [options.id] - Unique job ID (auto-generated if not provided)
|
|
1905
|
-
* @param {string} [options.phase] - Execution phase (defaults to 'update')
|
|
1906
|
-
* @param {number} [options.priority] - Priority within phase (higher = earlier, default 0)
|
|
1907
|
-
* @param {number} [options.fps] - FPS throttle limit
|
|
1908
|
-
* @param {boolean} [options.drop] - Drop frames when behind (default true)
|
|
1909
|
-
* @param {boolean} [options.enabled] - Whether job is active (default true)
|
|
1910
|
-
* @param {boolean} [options.system] - Internal flag for system jobs (not user-facing)
|
|
1911
|
-
* @returns {() => void} Unsubscribe function to remove this job
|
|
1912
|
-
*/
|
|
1913
|
-
register(callback, options = {}) {
|
|
1914
|
-
const rootId = options.rootId;
|
|
1915
|
-
const root = rootId ? this.roots.get(rootId) : this.roots.values().next().value;
|
|
1916
|
-
if (!root) {
|
|
1917
|
-
console.warn("[Scheduler] No root registered. Is this inside a Canvas?");
|
|
1918
|
-
return () => {
|
|
1919
|
-
};
|
|
1920
|
-
}
|
|
1921
|
-
const id = options.id ?? this.generateJobId();
|
|
1922
|
-
let phase = options.phase ?? "update";
|
|
1923
|
-
if (!options.phase && (options.before || options.after)) {
|
|
1924
|
-
phase = this.phaseGraph.resolveConstraintPhase(options.before, options.after);
|
|
1925
|
-
}
|
|
1926
|
-
const before = this.normalizeConstraints(options.before);
|
|
1927
|
-
const after = this.normalizeConstraints(options.after);
|
|
1928
|
-
const job = {
|
|
1929
|
-
id,
|
|
1930
|
-
callback,
|
|
1931
|
-
phase,
|
|
1932
|
-
before,
|
|
1933
|
-
after,
|
|
1934
|
-
priority: options.priority ?? 0,
|
|
1935
|
-
index: this.nextJobIndex++,
|
|
1936
|
-
fps: options.fps,
|
|
1937
|
-
drop: options.drop ?? true,
|
|
1938
|
-
enabled: options.enabled ?? true,
|
|
1939
|
-
system: options.system ?? false
|
|
1940
|
-
};
|
|
1941
|
-
if (root.jobs.has(id)) {
|
|
1942
|
-
console.warn(`[useFrame] Job with id "${id}" already exists, replacing`);
|
|
1943
|
-
}
|
|
1944
|
-
root.jobs.set(id, job);
|
|
1945
|
-
root.needsRebuild = true;
|
|
1946
|
-
return () => this.unregister(id, root.id);
|
|
1947
|
-
}
|
|
1948
|
-
/**
|
|
1949
|
-
* Unregister a job by its ID.
|
|
1950
|
-
* Searches all roots if rootId is not provided.
|
|
1951
|
-
* @param {string} id - The job ID to unregister
|
|
1952
|
-
* @param {string} [rootId] - Optional root ID to search (searches all if not provided)
|
|
1953
|
-
* @returns {void}
|
|
1954
|
-
*/
|
|
1955
|
-
unregister(id, rootId) {
|
|
1956
|
-
const root = rootId ? this.roots.get(rootId) : Array.from(this.roots.values()).find((r) => r.jobs.has(id));
|
|
1957
|
-
if (root?.jobs.delete(id)) {
|
|
1958
|
-
root.needsRebuild = true;
|
|
1959
|
-
this.jobStateListeners.delete(id);
|
|
1960
|
-
}
|
|
1961
|
-
}
|
|
1962
|
-
/**
|
|
1963
|
-
* Update a job's options dynamically.
|
|
1964
|
-
* Searches all roots to find the job by ID.
|
|
1965
|
-
* Phase/constraint changes trigger a rebuild of the sorted job list.
|
|
1966
|
-
* @param {string} id - The job ID to update
|
|
1967
|
-
* @param {Partial<JobOptions>} options - The options to update
|
|
1968
|
-
* @returns {void}
|
|
1969
|
-
*/
|
|
1970
|
-
updateJob(id, options) {
|
|
1971
|
-
let job;
|
|
1972
|
-
let root;
|
|
1973
|
-
for (const r of this.roots.values()) {
|
|
1974
|
-
job = r.jobs.get(id);
|
|
1975
|
-
if (job) {
|
|
1976
|
-
root = r;
|
|
1977
|
-
break;
|
|
1978
|
-
}
|
|
1979
|
-
}
|
|
1980
|
-
if (!job || !root) return;
|
|
1981
|
-
if (options.priority !== void 0) job.priority = options.priority;
|
|
1982
|
-
if (options.fps !== void 0) job.fps = options.fps;
|
|
1983
|
-
if (options.drop !== void 0) job.drop = options.drop;
|
|
1984
|
-
if (options.enabled !== void 0) {
|
|
1985
|
-
const wasEnabled = job.enabled;
|
|
1986
|
-
job.enabled = options.enabled;
|
|
1987
|
-
if (!wasEnabled && job.enabled) resetJobTiming(job);
|
|
1988
|
-
if (wasEnabled !== job.enabled) root.needsRebuild = true;
|
|
1989
|
-
}
|
|
1990
|
-
if (options.phase !== void 0 || options.before !== void 0 || options.after !== void 0) {
|
|
1991
|
-
if (options.phase) job.phase = options.phase;
|
|
1992
|
-
if (options.before !== void 0) job.before = this.normalizeConstraints(options.before);
|
|
1993
|
-
if (options.after !== void 0) job.after = this.normalizeConstraints(options.after);
|
|
1994
|
-
root.needsRebuild = true;
|
|
1995
|
-
}
|
|
1996
|
-
}
|
|
1997
|
-
//* Job State Management Methods ================================
|
|
1998
|
-
/**
|
|
1999
|
-
* Check if a job is currently paused (disabled).
|
|
2000
|
-
* @param {string} id - The job ID to check
|
|
2001
|
-
* @returns {boolean} True if the job exists and is paused
|
|
2002
|
-
*/
|
|
2003
|
-
isJobPaused(id) {
|
|
2004
|
-
for (const root of this.roots.values()) {
|
|
2005
|
-
const job = root.jobs.get(id);
|
|
2006
|
-
if (job) return !job.enabled;
|
|
2007
|
-
}
|
|
2008
|
-
return false;
|
|
2009
|
-
}
|
|
2010
|
-
/**
|
|
2011
|
-
* Subscribe to state changes for a specific job.
|
|
2012
|
-
* Listener is called when job is paused or resumed.
|
|
2013
|
-
* @param {string} id - The job ID to subscribe to
|
|
2014
|
-
* @param {() => void} listener - Callback invoked on state changes
|
|
2015
|
-
* @returns {() => void} Unsubscribe function
|
|
2016
|
-
*/
|
|
2017
|
-
subscribeJobState(id, listener) {
|
|
2018
|
-
if (!this.jobStateListeners.has(id)) {
|
|
2019
|
-
this.jobStateListeners.set(id, /* @__PURE__ */ new Set());
|
|
2020
|
-
}
|
|
2021
|
-
this.jobStateListeners.get(id).add(listener);
|
|
2022
|
-
return () => {
|
|
2023
|
-
this.jobStateListeners.get(id)?.delete(listener);
|
|
2024
|
-
if (this.jobStateListeners.get(id)?.size === 0) {
|
|
2025
|
-
this.jobStateListeners.delete(id);
|
|
2026
|
-
}
|
|
2027
|
-
};
|
|
2028
|
-
}
|
|
2029
|
-
/**
|
|
2030
|
-
* Notify all listeners that a job's state has changed.
|
|
2031
|
-
* @param {string} id - The job ID that changed
|
|
2032
|
-
* @returns {void}
|
|
2033
|
-
* @private
|
|
2034
|
-
*/
|
|
2035
|
-
notifyJobStateChange(id) {
|
|
2036
|
-
this.jobStateListeners.get(id)?.forEach((listener) => listener());
|
|
2037
|
-
}
|
|
2038
|
-
/**
|
|
2039
|
-
* Pause a job by ID (sets enabled=false).
|
|
2040
|
-
* Notifies any subscribed state listeners.
|
|
2041
|
-
* @param {string} id - The job ID to pause
|
|
2042
|
-
* @returns {void}
|
|
2043
|
-
*/
|
|
2044
|
-
pauseJob(id) {
|
|
2045
|
-
this.updateJob(id, { enabled: false });
|
|
2046
|
-
this.notifyJobStateChange(id);
|
|
2047
|
-
}
|
|
2048
|
-
/**
|
|
2049
|
-
* Resume a paused job by ID (sets enabled=true).
|
|
2050
|
-
* Resets job timing to prevent frame accumulation.
|
|
2051
|
-
* Notifies any subscribed state listeners.
|
|
2052
|
-
* @param {string} id - The job ID to resume
|
|
2053
|
-
* @returns {void}
|
|
2054
|
-
*/
|
|
2055
|
-
resumeJob(id) {
|
|
2056
|
-
this.updateJob(id, { enabled: true });
|
|
2057
|
-
this.notifyJobStateChange(id);
|
|
2058
|
-
}
|
|
2059
|
-
//* Frame Loop Control Methods ================================
|
|
2060
|
-
/**
|
|
2061
|
-
* Start the requestAnimationFrame loop.
|
|
2062
|
-
* Resets timing state (elapsedTime, frameCount) on start.
|
|
2063
|
-
* No-op if already running.
|
|
2064
|
-
* @returns {void}
|
|
2065
|
-
*/
|
|
2066
|
-
start() {
|
|
2067
|
-
if (this.loopState.running) return;
|
|
2068
|
-
const { elapsedTime, createdAt } = this.loopState;
|
|
2069
|
-
let adjustedCreated = 0;
|
|
2070
|
-
if (this.stoppedTime > 0) {
|
|
2071
|
-
adjustedCreated = createdAt - (performance.now() - this.stoppedTime);
|
|
2072
|
-
this.stoppedTime = 0;
|
|
2073
|
-
}
|
|
2074
|
-
Object.assign(this.loopState, {
|
|
2075
|
-
running: true,
|
|
2076
|
-
elapsedTime: elapsedTime ?? 0,
|
|
2077
|
-
lastTime: performance.now(),
|
|
2078
|
-
createdAt: adjustedCreated > 0 ? adjustedCreated : performance.now(),
|
|
2079
|
-
frameCount: 0,
|
|
2080
|
-
rafHandle: requestAnimationFrame(this.loop)
|
|
2081
|
-
});
|
|
2082
|
-
}
|
|
2083
|
-
/**
|
|
2084
|
-
* Stop the requestAnimationFrame loop.
|
|
2085
|
-
* Cancels any pending RAF callback.
|
|
2086
|
-
* No-op if not running.
|
|
2087
|
-
* @returns {void}
|
|
2088
|
-
*/
|
|
2089
|
-
stop() {
|
|
2090
|
-
if (!this.loopState.running) return;
|
|
2091
|
-
this.loopState.running = false;
|
|
2092
|
-
if (this.loopState.rafHandle !== null) {
|
|
2093
|
-
cancelAnimationFrame(this.loopState.rafHandle);
|
|
2094
|
-
this.loopState.rafHandle = null;
|
|
2095
|
-
}
|
|
2096
|
-
this.stoppedTime = performance.now();
|
|
2097
|
-
}
|
|
2098
|
-
/**
|
|
2099
|
-
* Request frames to be rendered in demand mode.
|
|
2100
|
-
* Accumulates pending frames (capped at 60) and starts the loop if not running.
|
|
2101
|
-
* No-op if frameloop is not 'demand'.
|
|
2102
|
-
* @param {number} [frames=1] - Number of frames to request
|
|
2103
|
-
* @param {boolean} [stackFrames=false] - Whether to add frames to existing pending count
|
|
2104
|
-
* - `false` (default): Sets pending frames to the specified value (replaces existing count)
|
|
2105
|
-
* - `true`: Adds frames to existing pending count (useful for accumulating invalidations)
|
|
2106
|
-
* @returns {void}
|
|
2107
|
-
* @example
|
|
2108
|
-
* // Request a single frame render
|
|
2109
|
-
* scheduler.invalidate();
|
|
2110
|
-
*
|
|
2111
|
-
* @example
|
|
2112
|
-
* // Request 5 frames (e.g., for animations)
|
|
2113
|
-
* scheduler.invalidate(5);
|
|
2114
|
-
*
|
|
2115
|
-
* @example
|
|
2116
|
-
* // Set pending frames to exactly 3 (don't stack with existing)
|
|
2117
|
-
* scheduler.invalidate(3, false);
|
|
2118
|
-
*
|
|
2119
|
-
* @example
|
|
2120
|
-
* // Add 2 more frames to existing pending count
|
|
2121
|
-
* scheduler.invalidate(2, true);
|
|
2122
|
-
*/
|
|
2123
|
-
invalidate(frames = 1, stackFrames = false) {
|
|
2124
|
-
if (this._frameloop !== "demand") return;
|
|
2125
|
-
const baseFrames = stackFrames ? this.pendingFrames : 0;
|
|
2126
|
-
this.pendingFrames = Math.min(60, baseFrames + frames);
|
|
2127
|
-
if (!this.loopState.running && this.pendingFrames > 0) this.start();
|
|
2128
|
-
}
|
|
2129
|
-
/**
|
|
2130
|
-
* Reset timing state for deterministic testing.
|
|
2131
|
-
* Preserves jobs and roots but resets lastTime, frameCount, elapsedTime, etc.
|
|
2132
|
-
* @returns {void}
|
|
2133
|
-
*/
|
|
2134
|
-
resetTiming() {
|
|
2135
|
-
this.loopState.lastTime = null;
|
|
2136
|
-
this.loopState.frameCount = 0;
|
|
2137
|
-
this.loopState.elapsedTime = 0;
|
|
2138
|
-
this.loopState.createdAt = performance.now();
|
|
2139
|
-
}
|
|
2140
|
-
//* Manual Stepping Methods ================================
|
|
2141
|
-
/**
|
|
2142
|
-
* Manually execute a single frame for all roots.
|
|
2143
|
-
* Useful for frameloop='never' mode or testing scenarios.
|
|
2144
|
-
* @param {number} [timestamp] - Optional timestamp (defaults to performance.now())
|
|
2145
|
-
* @returns {void}
|
|
2146
|
-
* @example
|
|
2147
|
-
* // Manual control mode
|
|
2148
|
-
* scheduler.frameloop = 'never';
|
|
2149
|
-
* scheduler.step(); // Execute one frame
|
|
2150
|
-
*/
|
|
2151
|
-
step(timestamp) {
|
|
2152
|
-
const now = timestamp ?? performance.now();
|
|
2153
|
-
this.executeFrame(now);
|
|
2154
|
-
}
|
|
2155
|
-
/**
|
|
2156
|
-
* Manually execute a single job by its ID.
|
|
2157
|
-
* Useful for testing individual job callbacks in isolation.
|
|
2158
|
-
* @param {string} id - The job ID to step
|
|
2159
|
-
* @param {number} [timestamp] - Optional timestamp (defaults to performance.now())
|
|
2160
|
-
* @returns {void}
|
|
2161
|
-
*/
|
|
2162
|
-
stepJob(id, timestamp) {
|
|
2163
|
-
let job;
|
|
2164
|
-
let root;
|
|
2165
|
-
for (const r of this.roots.values()) {
|
|
2166
|
-
job = r.jobs.get(id);
|
|
2167
|
-
if (job) {
|
|
2168
|
-
root = r;
|
|
2169
|
-
break;
|
|
2170
|
-
}
|
|
2171
|
-
}
|
|
2172
|
-
if (!job || !root) {
|
|
2173
|
-
console.warn(`[Scheduler] Job "${id}" not found`);
|
|
2174
|
-
return;
|
|
2175
|
-
}
|
|
2176
|
-
const now = timestamp ?? performance.now();
|
|
2177
|
-
const deltaMs = this.loopState.lastTime !== null ? now - this.loopState.lastTime : 0;
|
|
2178
|
-
const delta = deltaMs / 1e3;
|
|
2179
|
-
const elapsed = now - this.loopState.createdAt;
|
|
2180
|
-
const providedState = root.getState?.() ?? {};
|
|
2181
|
-
const frameState = {
|
|
2182
|
-
...providedState,
|
|
2183
|
-
time: now,
|
|
2184
|
-
delta,
|
|
2185
|
-
elapsed,
|
|
2186
|
-
frame: this.loopState.frameCount
|
|
2187
|
-
};
|
|
2188
|
-
try {
|
|
2189
|
-
job.callback(frameState, delta);
|
|
2190
|
-
} catch (error) {
|
|
2191
|
-
console.error(`[Scheduler] Error in job "${job.id}":`, error);
|
|
2192
|
-
this.triggerError(error instanceof Error ? error : new Error(String(error)));
|
|
2193
|
-
}
|
|
2194
|
-
}
|
|
2195
|
-
/**
|
|
2196
|
-
* Execute a single frame across all roots.
|
|
2197
|
-
* Order: globalBefore → each root's jobs → globalAfter
|
|
2198
|
-
* @param {number} timestamp - RAF timestamp in milliseconds
|
|
2199
|
-
* @returns {void}
|
|
2200
|
-
* @private
|
|
2201
|
-
*/
|
|
2202
|
-
executeFrame(timestamp) {
|
|
2203
|
-
const deltaMs = this.loopState.lastTime !== null ? timestamp - this.loopState.lastTime : 0;
|
|
2204
|
-
const delta = deltaMs / 1e3;
|
|
2205
|
-
this.loopState.lastTime = timestamp;
|
|
2206
|
-
this.loopState.frameCount++;
|
|
2207
|
-
this.loopState.elapsedTime += deltaMs;
|
|
2208
|
-
this.runGlobalJobs(this.globalBeforeJobs, timestamp);
|
|
2209
|
-
for (const root of this.roots.values()) {
|
|
2210
|
-
this.tickRoot(root, timestamp, delta);
|
|
2211
|
-
}
|
|
2212
|
-
this.runGlobalJobs(this.globalAfterJobs, timestamp);
|
|
2213
|
-
}
|
|
2214
|
-
/**
|
|
2215
|
-
* Run all global jobs from a job map.
|
|
2216
|
-
* Catches and logs errors without stopping execution.
|
|
2217
|
-
* @param {Map<string, GlobalJob>} jobs - The global jobs map to execute
|
|
2218
|
-
* @param {number} timestamp - RAF timestamp in milliseconds
|
|
2219
|
-
* @returns {void}
|
|
2220
|
-
* @private
|
|
2221
|
-
*/
|
|
2222
|
-
runGlobalJobs(jobs, timestamp) {
|
|
2223
|
-
for (const job of jobs.values()) {
|
|
2224
|
-
try {
|
|
2225
|
-
job.callback(timestamp);
|
|
2226
|
-
} catch (error) {
|
|
2227
|
-
console.error(`[Scheduler] Error in global job "${job.id}":`, error);
|
|
2228
|
-
}
|
|
2229
|
-
}
|
|
2230
|
-
}
|
|
2231
|
-
/**
|
|
2232
|
-
* Execute all jobs for a single root in sorted order.
|
|
2233
|
-
* Rebuilds sorted job list if needed, then dispatches each job.
|
|
2234
|
-
* Errors are caught and propagated via triggerError.
|
|
2235
|
-
* @param {RootEntry} root - The root entry to tick
|
|
2236
|
-
* @param {number} timestamp - RAF timestamp in milliseconds
|
|
2237
|
-
* @param {number} delta - Time since last frame in seconds
|
|
2238
|
-
* @returns {void}
|
|
2239
|
-
* @private
|
|
2240
|
-
*/
|
|
2241
|
-
tickRoot(root, timestamp, delta) {
|
|
2242
|
-
if (root.needsRebuild) {
|
|
2243
|
-
root.sortedJobs = rebuildSortedJobs(root.jobs, this.phaseGraph);
|
|
2244
|
-
root.needsRebuild = false;
|
|
2245
|
-
}
|
|
2246
|
-
const providedState = root.getState?.() ?? {};
|
|
2247
|
-
const frameState = {
|
|
2248
|
-
...providedState,
|
|
2249
|
-
time: timestamp,
|
|
2250
|
-
delta,
|
|
2251
|
-
elapsed: this.loopState.elapsedTime / 1e3,
|
|
2252
|
-
// Convert ms to seconds
|
|
2253
|
-
frame: this.loopState.frameCount
|
|
2254
|
-
};
|
|
2255
|
-
for (const job of root.sortedJobs) {
|
|
2256
|
-
if (!shouldRun(job, timestamp)) continue;
|
|
2257
|
-
try {
|
|
2258
|
-
job.callback(frameState, delta);
|
|
2259
|
-
} catch (error) {
|
|
2260
|
-
console.error(`[Scheduler] Error in job "${job.id}":`, error);
|
|
2261
|
-
this.triggerError(error instanceof Error ? error : new Error(String(error)));
|
|
2262
|
-
}
|
|
2263
|
-
}
|
|
2264
|
-
}
|
|
2265
|
-
//* Debug & Inspection Methods ================================
|
|
2266
|
-
/**
|
|
2267
|
-
* Get the total number of registered jobs across all roots.
|
|
2268
|
-
* Includes both per-root jobs and global before/after jobs.
|
|
2269
|
-
* @returns {number} Total job count
|
|
2270
|
-
*/
|
|
2271
|
-
getJobCount() {
|
|
2272
|
-
let count = 0;
|
|
2273
|
-
for (const root of this.roots.values()) {
|
|
2274
|
-
count += root.jobs.size;
|
|
2275
|
-
}
|
|
2276
|
-
return count + this.globalBeforeJobs.size + this.globalAfterJobs.size;
|
|
2277
|
-
}
|
|
2278
|
-
/**
|
|
2279
|
-
* Get all registered job IDs across all roots.
|
|
2280
|
-
* Includes both per-root jobs and global before/after jobs.
|
|
2281
|
-
* @returns {string[]} Array of all job IDs
|
|
2282
|
-
*/
|
|
2283
|
-
getJobIds() {
|
|
2284
|
-
const ids = [];
|
|
2285
|
-
for (const root of this.roots.values()) {
|
|
2286
|
-
ids.push(...root.jobs.keys());
|
|
2287
|
-
}
|
|
2288
|
-
ids.push(...this.globalBeforeJobs.keys());
|
|
2289
|
-
ids.push(...this.globalAfterJobs.keys());
|
|
2290
|
-
return ids;
|
|
2291
|
-
}
|
|
2292
|
-
/**
|
|
2293
|
-
* Get the number of registered roots (Canvas instances).
|
|
2294
|
-
* @returns {number} Number of registered roots
|
|
2295
|
-
*/
|
|
2296
|
-
getRootCount() {
|
|
2297
|
-
return this.roots.size;
|
|
2298
|
-
}
|
|
2299
|
-
/**
|
|
2300
|
-
* Check if any user (non-system) jobs are registered in a specific phase.
|
|
2301
|
-
* Used by the default render job to know if a user has taken over rendering.
|
|
2302
|
-
*
|
|
2303
|
-
* @param phase The phase to check
|
|
2304
|
-
* @param rootId Optional root ID to check (checks all roots if not provided)
|
|
2305
|
-
* @returns true if any user jobs exist in the phase
|
|
2306
|
-
*/
|
|
2307
|
-
hasUserJobsInPhase(phase, rootId) {
|
|
2308
|
-
const rootsToCheck = rootId ? [this.roots.get(rootId)].filter(Boolean) : Array.from(this.roots.values());
|
|
2309
|
-
return rootsToCheck.some((root) => {
|
|
2310
|
-
if (!root) return false;
|
|
2311
|
-
for (const job of root.jobs.values()) {
|
|
2312
|
-
if (job.phase === phase && !job.system && job.enabled) return true;
|
|
2313
|
-
}
|
|
2314
|
-
return false;
|
|
2315
|
-
});
|
|
2316
|
-
}
|
|
2317
|
-
//* Utility Methods ================================
|
|
2318
|
-
/**
|
|
2319
|
-
* Generate a unique root ID for automatic root registration.
|
|
2320
|
-
* @returns {string} A unique root ID in the format 'root_N'
|
|
2321
|
-
*/
|
|
2322
|
-
generateRootId() {
|
|
2323
|
-
return `root_${this.nextRootIndex++}`;
|
|
2324
|
-
}
|
|
2325
|
-
/**
|
|
2326
|
-
* Generate a unique job ID.
|
|
2327
|
-
* @returns {string} A unique job ID in the format 'job_N'
|
|
2328
|
-
* @private
|
|
2329
|
-
*/
|
|
2330
|
-
generateJobId() {
|
|
2331
|
-
return `job_${this.nextJobIndex}`;
|
|
2332
|
-
}
|
|
2333
|
-
/**
|
|
2334
|
-
* Normalize before/after constraints to a Set.
|
|
2335
|
-
* Handles undefined, single string, or array inputs.
|
|
2336
|
-
* @param {string | string[] | undefined} value - The constraint value(s)
|
|
2337
|
-
* @returns {Set<string>} Normalized Set of constraint strings
|
|
2338
|
-
* @private
|
|
2339
|
-
*/
|
|
2340
|
-
normalizeConstraints(value) {
|
|
2341
|
-
if (!value) return /* @__PURE__ */ new Set();
|
|
2342
|
-
if (Array.isArray(value)) return new Set(value);
|
|
2343
|
-
return /* @__PURE__ */ new Set([value]);
|
|
2344
|
-
}
|
|
2345
|
-
};
|
|
2346
|
-
//* Static State & Methods (Singleton Usage) ================================
|
|
2347
|
-
//* Cross-Bundle Singleton Key ==============================
|
|
2348
|
-
// Use Symbol.for() to ensure scheduler is shared across bundle boundaries
|
|
2349
|
-
// This prevents issues when mixing imports from @react-three/fiber and @react-three/fiber/webgpu
|
|
2350
|
-
__publicField(_Scheduler, "INSTANCE_KEY", Symbol.for("@react-three/fiber.scheduler"));
|
|
2351
|
-
let Scheduler = _Scheduler;
|
|
2352
|
-
const getScheduler = () => Scheduler.get();
|
|
2353
|
-
if (hmrData) {
|
|
2354
|
-
hmrData.accept?.();
|
|
2355
|
-
}
|
|
2356
|
-
|
|
2357
|
-
function useFrame(callback, priorityOrOptions) {
|
|
2358
|
-
const store = React__namespace.useContext(context);
|
|
2359
|
-
const isInsideCanvas = store !== null;
|
|
2360
|
-
const scheduler = getScheduler();
|
|
2361
|
-
const optionsKey = typeof priorityOrOptions === "number" ? `p:${priorityOrOptions}` : priorityOrOptions ? JSON.stringify({
|
|
2362
|
-
id: priorityOrOptions.id,
|
|
2363
|
-
phase: priorityOrOptions.phase,
|
|
2364
|
-
priority: priorityOrOptions.priority,
|
|
2365
|
-
fps: priorityOrOptions.fps,
|
|
2366
|
-
drop: priorityOrOptions.drop,
|
|
2367
|
-
enabled: priorityOrOptions.enabled,
|
|
2368
|
-
before: priorityOrOptions.before,
|
|
2369
|
-
after: priorityOrOptions.after
|
|
2370
|
-
}) : "";
|
|
2371
|
-
const options = React__namespace.useMemo(() => {
|
|
2372
|
-
return typeof priorityOrOptions === "number" ? { priority: priorityOrOptions } : priorityOrOptions ?? {};
|
|
2373
|
-
}, [optionsKey]);
|
|
2374
|
-
const reactId = React__namespace.useId();
|
|
2375
|
-
const id = options.id ?? reactId;
|
|
2376
|
-
const callbackRef = useMutableCallback(callback);
|
|
2377
|
-
const isLegacyPriority = typeof priorityOrOptions === "number" && priorityOrOptions > 0;
|
|
2378
|
-
useIsomorphicLayoutEffect(() => {
|
|
2379
|
-
if (!callback) return;
|
|
2380
|
-
if (isInsideCanvas) {
|
|
2381
|
-
const state = store.getState();
|
|
2382
|
-
const rootId = state.internal.rootId;
|
|
2383
|
-
if (isLegacyPriority) {
|
|
2384
|
-
state.internal.priority++;
|
|
2385
|
-
let parentRoot = state.previousRoot;
|
|
2386
|
-
while (parentRoot) {
|
|
2387
|
-
const parentState = parentRoot.getState();
|
|
2388
|
-
if (parentState?.internal) parentState.internal.priority++;
|
|
2389
|
-
parentRoot = parentState?.previousRoot;
|
|
2390
|
-
}
|
|
2391
|
-
notifyDepreciated({
|
|
2392
|
-
heading: "useFrame with numeric priority is deprecated",
|
|
2393
|
-
body: 'Using useFrame(callback, number) to control render order is deprecated.\n\nFor custom rendering, use: useFrame(callback, { phase: "render" })\nFor execution order within update phase, use: useFrame(callback, { priority: number })',
|
|
2394
|
-
link: "https://docs.pmnd.rs/react-three-fiber/api/hooks#useframe"
|
|
2395
|
-
});
|
|
1991
|
+
function useFrame(callback, priorityOrOptions) {
|
|
1992
|
+
const store = React__namespace.useContext(context);
|
|
1993
|
+
const isInsideCanvas = store !== null;
|
|
1994
|
+
const scheduler$1 = scheduler.getScheduler();
|
|
1995
|
+
const optionsKey = typeof priorityOrOptions === "number" ? `p:${priorityOrOptions}` : priorityOrOptions ? JSON.stringify({
|
|
1996
|
+
id: priorityOrOptions.id,
|
|
1997
|
+
phase: priorityOrOptions.phase,
|
|
1998
|
+
priority: priorityOrOptions.priority,
|
|
1999
|
+
fps: priorityOrOptions.fps,
|
|
2000
|
+
drop: priorityOrOptions.drop,
|
|
2001
|
+
enabled: priorityOrOptions.enabled,
|
|
2002
|
+
before: priorityOrOptions.before,
|
|
2003
|
+
after: priorityOrOptions.after
|
|
2004
|
+
}) : "";
|
|
2005
|
+
const options = React__namespace.useMemo(() => {
|
|
2006
|
+
return typeof priorityOrOptions === "number" ? { priority: priorityOrOptions } : priorityOrOptions ?? {};
|
|
2007
|
+
}, [optionsKey]);
|
|
2008
|
+
const reactId = React__namespace.useId();
|
|
2009
|
+
const id = options.id ?? reactId;
|
|
2010
|
+
const callbackRef = useMutableCallback(callback);
|
|
2011
|
+
const isLegacyPriority = typeof priorityOrOptions === "number" && priorityOrOptions > 0;
|
|
2012
|
+
useIsomorphicLayoutEffect(() => {
|
|
2013
|
+
if (!callback) return;
|
|
2014
|
+
if (isInsideCanvas) {
|
|
2015
|
+
const state = store.getState();
|
|
2016
|
+
const rootId = state.internal.rootId;
|
|
2017
|
+
if (isLegacyPriority) {
|
|
2018
|
+
state.internal.priority++;
|
|
2019
|
+
let parentRoot = state.previousRoot;
|
|
2020
|
+
while (parentRoot) {
|
|
2021
|
+
const parentState = parentRoot.getState();
|
|
2022
|
+
if (parentState?.internal) parentState.internal.priority++;
|
|
2023
|
+
parentRoot = parentState?.previousRoot;
|
|
2024
|
+
}
|
|
2025
|
+
notifyDepreciated({
|
|
2026
|
+
heading: "useFrame with numeric priority is deprecated",
|
|
2027
|
+
body: 'Using useFrame(callback, number) to control render order is deprecated.\n\nFor custom rendering, use: useFrame(callback, { phase: "render" })\nFor execution order within update phase, use: useFrame(callback, { priority: number })',
|
|
2028
|
+
link: "https://docs.pmnd.rs/react-three-fiber/api/hooks#useframe"
|
|
2029
|
+
});
|
|
2396
2030
|
}
|
|
2397
2031
|
const wrappedCallback = (frameState, delta) => {
|
|
2398
2032
|
const localState = store.getState();
|
|
@@ -2405,7 +2039,7 @@ function useFrame(callback, priorityOrOptions) {
|
|
|
2405
2039
|
};
|
|
2406
2040
|
callbackRef.current?.(mergedState, delta);
|
|
2407
2041
|
};
|
|
2408
|
-
const unregister = scheduler.register(wrappedCallback, {
|
|
2042
|
+
const unregister = scheduler$1.register(wrappedCallback, {
|
|
2409
2043
|
id,
|
|
2410
2044
|
rootId,
|
|
2411
2045
|
...options
|
|
@@ -2426,37 +2060,31 @@ function useFrame(callback, priorityOrOptions) {
|
|
|
2426
2060
|
}
|
|
2427
2061
|
};
|
|
2428
2062
|
} else {
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
unregisterJob = registerOutside();
|
|
2438
|
-
});
|
|
2439
|
-
return () => {
|
|
2440
|
-
unsubReady();
|
|
2441
|
-
unregisterJob?.();
|
|
2442
|
-
};
|
|
2063
|
+
return scheduler$1.register(
|
|
2064
|
+
(state, delta) => {
|
|
2065
|
+
const frameState = state;
|
|
2066
|
+
if (!frameState.renderer) return;
|
|
2067
|
+
callbackRef.current?.(frameState, delta);
|
|
2068
|
+
},
|
|
2069
|
+
{ id, ...options }
|
|
2070
|
+
);
|
|
2443
2071
|
}
|
|
2444
|
-
}, [store, scheduler, id, optionsKey, isLegacyPriority, isInsideCanvas]);
|
|
2072
|
+
}, [store, scheduler$1, id, optionsKey, isLegacyPriority, isInsideCanvas]);
|
|
2445
2073
|
const isPaused = React__namespace.useSyncExternalStore(
|
|
2446
2074
|
// Subscribe function
|
|
2447
2075
|
React__namespace.useCallback(
|
|
2448
2076
|
(onStoreChange) => {
|
|
2449
|
-
return getScheduler().subscribeJobState(id, onStoreChange);
|
|
2077
|
+
return scheduler.getScheduler().subscribeJobState(id, onStoreChange);
|
|
2450
2078
|
},
|
|
2451
2079
|
[id]
|
|
2452
2080
|
),
|
|
2453
2081
|
// getSnapshot function
|
|
2454
|
-
React__namespace.useCallback(() => getScheduler().isJobPaused(id), [id]),
|
|
2082
|
+
React__namespace.useCallback(() => scheduler.getScheduler().isJobPaused(id), [id]),
|
|
2455
2083
|
// getServerSnapshot function (SSR)
|
|
2456
2084
|
React__namespace.useCallback(() => false, [])
|
|
2457
2085
|
);
|
|
2458
2086
|
const controls = React__namespace.useMemo(() => {
|
|
2459
|
-
const scheduler2 = getScheduler();
|
|
2087
|
+
const scheduler2 = scheduler.getScheduler();
|
|
2460
2088
|
return {
|
|
2461
2089
|
/** The job's unique ID */
|
|
2462
2090
|
id,
|
|
@@ -2471,7 +2099,7 @@ function useFrame(callback, priorityOrOptions) {
|
|
|
2471
2099
|
* @param timestamp Optional timestamp (defaults to performance.now())
|
|
2472
2100
|
*/
|
|
2473
2101
|
step: (timestamp) => {
|
|
2474
|
-
getScheduler().stepJob(id, timestamp);
|
|
2102
|
+
scheduler.getScheduler().stepJob(id, timestamp);
|
|
2475
2103
|
},
|
|
2476
2104
|
/**
|
|
2477
2105
|
* Manually step ALL jobs in the scheduler.
|
|
@@ -2479,20 +2107,20 @@ function useFrame(callback, priorityOrOptions) {
|
|
|
2479
2107
|
* @param timestamp Optional timestamp (defaults to performance.now())
|
|
2480
2108
|
*/
|
|
2481
2109
|
stepAll: (timestamp) => {
|
|
2482
|
-
getScheduler().step(timestamp);
|
|
2110
|
+
scheduler.getScheduler().step(timestamp);
|
|
2483
2111
|
},
|
|
2484
2112
|
/**
|
|
2485
2113
|
* Pause this job (set enabled=false).
|
|
2486
2114
|
* Job remains registered but won't run.
|
|
2487
2115
|
*/
|
|
2488
2116
|
pause: () => {
|
|
2489
|
-
getScheduler().pauseJob(id);
|
|
2117
|
+
scheduler.getScheduler().pauseJob(id);
|
|
2490
2118
|
},
|
|
2491
2119
|
/**
|
|
2492
2120
|
* Resume this job (set enabled=true).
|
|
2493
2121
|
*/
|
|
2494
2122
|
resume: () => {
|
|
2495
|
-
getScheduler().resumeJob(id);
|
|
2123
|
+
scheduler.getScheduler().resumeJob(id);
|
|
2496
2124
|
},
|
|
2497
2125
|
/**
|
|
2498
2126
|
* Reactive paused state - automatically updates when pause/resume is called.
|
|
@@ -2530,22 +2158,29 @@ function buildFromCache(input, textureCache) {
|
|
|
2530
2158
|
function useTexture(input, optionsOrOnLoad) {
|
|
2531
2159
|
const renderer = useThree((state) => state.internal.actualRenderer);
|
|
2532
2160
|
const store = useStore();
|
|
2533
|
-
const textureCache = useThree((state) => state.textures);
|
|
2534
2161
|
const options = typeof optionsOrOnLoad === "function" ? { onLoad: optionsOrOnLoad } : optionsOrOnLoad ?? {};
|
|
2535
|
-
const { onLoad, cache =
|
|
2162
|
+
const { onLoad, cache = true } = options;
|
|
2163
|
+
const onLoadRef = React.useRef(onLoad);
|
|
2164
|
+
onLoadRef.current = onLoad;
|
|
2165
|
+
const onLoadCalledForRef = React.useRef(null);
|
|
2536
2166
|
const urls = React.useMemo(() => getUrls(input), [input]);
|
|
2537
2167
|
const cachedResult = React.useMemo(() => {
|
|
2538
2168
|
if (!cache) return null;
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2169
|
+
const textures = store.getState().textures;
|
|
2170
|
+
if (!allUrlsCached(urls, textures)) return null;
|
|
2171
|
+
return buildFromCache(input, textures);
|
|
2172
|
+
}, [cache, urls, input, store]);
|
|
2542
2173
|
const loadedTextures = useLoader(
|
|
2543
2174
|
webgpu.TextureLoader,
|
|
2544
2175
|
IsObject(input) ? Object.values(input) : input
|
|
2545
2176
|
);
|
|
2177
|
+
const inputKey = urls.join("\0");
|
|
2546
2178
|
React.useLayoutEffect(() => {
|
|
2547
|
-
if (
|
|
2548
|
-
|
|
2179
|
+
if (cachedResult) return;
|
|
2180
|
+
if (onLoadCalledForRef.current === inputKey) return;
|
|
2181
|
+
onLoadCalledForRef.current = inputKey;
|
|
2182
|
+
onLoadRef.current?.(loadedTextures);
|
|
2183
|
+
}, [cachedResult, loadedTextures, inputKey]);
|
|
2549
2184
|
React.useEffect(() => {
|
|
2550
2185
|
if (cachedResult) return;
|
|
2551
2186
|
if ("initTexture" in renderer) {
|
|
@@ -2578,8 +2213,6 @@ function useTexture(input, optionsOrOnLoad) {
|
|
|
2578
2213
|
}, [input, loadedTextures, cachedResult]);
|
|
2579
2214
|
React.useEffect(() => {
|
|
2580
2215
|
if (!cache) return;
|
|
2581
|
-
if (cachedResult) return;
|
|
2582
|
-
const set = store.setState;
|
|
2583
2216
|
const urlTextureMap = [];
|
|
2584
2217
|
if (typeof input === "string") {
|
|
2585
2218
|
urlTextureMap.push([input, mappedTextures]);
|
|
@@ -2593,18 +2226,32 @@ function useTexture(input, optionsOrOnLoad) {
|
|
|
2593
2226
|
urlTextureMap.push([url, textureRecord[key]]);
|
|
2594
2227
|
}
|
|
2595
2228
|
}
|
|
2596
|
-
|
|
2597
|
-
const
|
|
2598
|
-
let
|
|
2229
|
+
store.setState((state) => {
|
|
2230
|
+
const refs = new Map(state._textureRefs);
|
|
2231
|
+
let textures = state.textures;
|
|
2232
|
+
let added = false;
|
|
2599
2233
|
for (const [url, texture] of urlTextureMap) {
|
|
2600
|
-
if (!
|
|
2601
|
-
|
|
2602
|
-
|
|
2234
|
+
if (!textures.has(url)) {
|
|
2235
|
+
if (!added) {
|
|
2236
|
+
textures = new Map(textures);
|
|
2237
|
+
added = true;
|
|
2238
|
+
}
|
|
2239
|
+
textures.set(url, texture);
|
|
2603
2240
|
}
|
|
2241
|
+
refs.set(url, (refs.get(url) ?? 0) + 1);
|
|
2604
2242
|
}
|
|
2605
|
-
return
|
|
2243
|
+
return added ? { textures, _textureRefs: refs } : { _textureRefs: refs };
|
|
2244
|
+
});
|
|
2245
|
+
return () => store.setState((state) => {
|
|
2246
|
+
const refs = new Map(state._textureRefs);
|
|
2247
|
+
for (const [url] of urlTextureMap) {
|
|
2248
|
+
const next = (refs.get(url) ?? 0) - 1;
|
|
2249
|
+
if (next <= 0) refs.delete(url);
|
|
2250
|
+
else refs.set(url, next);
|
|
2251
|
+
}
|
|
2252
|
+
return { _textureRefs: refs };
|
|
2606
2253
|
});
|
|
2607
|
-
}, [cache, input, mappedTextures, store
|
|
2254
|
+
}, [cache, input, mappedTextures, store]);
|
|
2608
2255
|
return mappedTextures;
|
|
2609
2256
|
}
|
|
2610
2257
|
useTexture.preload = (url) => useLoader.preload(webgpu.TextureLoader, url);
|
|
@@ -2620,106 +2267,90 @@ const Texture = ({
|
|
|
2620
2267
|
return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: children?.(ret) });
|
|
2621
2268
|
};
|
|
2622
2269
|
|
|
2623
|
-
function
|
|
2624
|
-
if (entry instanceof webgpu.Texture) return entry;
|
|
2625
|
-
if (entry && typeof entry === "object" && "value" in entry && entry.value instanceof webgpu.Texture) {
|
|
2626
|
-
return entry.value;
|
|
2627
|
-
}
|
|
2628
|
-
return null;
|
|
2629
|
-
}
|
|
2630
|
-
function useTextures() {
|
|
2270
|
+
function useTextures(selector) {
|
|
2631
2271
|
const store = useStore();
|
|
2632
|
-
|
|
2633
|
-
const set = store.setState;
|
|
2272
|
+
const registry = React.useMemo(() => {
|
|
2634
2273
|
const getState = store.getState;
|
|
2635
|
-
const
|
|
2636
|
-
|
|
2637
|
-
const newMap = new Map(state.textures);
|
|
2638
|
-
newMap.set(key, value);
|
|
2639
|
-
return { textures: newMap };
|
|
2640
|
-
});
|
|
2641
|
-
};
|
|
2642
|
-
const addMultiple = (items) => {
|
|
2643
|
-
set((state) => {
|
|
2644
|
-
const newMap = new Map(state.textures);
|
|
2645
|
-
const entries = items instanceof Map ? items.entries() : Object.entries(items);
|
|
2646
|
-
for (const [key, value] of entries) {
|
|
2647
|
-
newMap.set(key, value);
|
|
2648
|
-
}
|
|
2649
|
-
return { textures: newMap };
|
|
2650
|
-
});
|
|
2651
|
-
};
|
|
2652
|
-
const remove = (key) => {
|
|
2653
|
-
set((state) => {
|
|
2654
|
-
const newMap = new Map(state.textures);
|
|
2655
|
-
newMap.delete(key);
|
|
2656
|
-
return { textures: newMap };
|
|
2657
|
-
});
|
|
2658
|
-
};
|
|
2659
|
-
const removeMultiple = (keys) => {
|
|
2660
|
-
set((state) => {
|
|
2661
|
-
const newMap = new Map(state.textures);
|
|
2662
|
-
for (const key of keys) newMap.delete(key);
|
|
2663
|
-
return { textures: newMap };
|
|
2664
|
-
});
|
|
2665
|
-
};
|
|
2666
|
-
const dispose = (key) => {
|
|
2667
|
-
const entry = getState().textures.get(key);
|
|
2668
|
-
if (entry) {
|
|
2669
|
-
const tex = getTextureValue(entry);
|
|
2670
|
-
tex?.dispose();
|
|
2671
|
-
}
|
|
2672
|
-
remove(key);
|
|
2673
|
-
};
|
|
2674
|
-
const disposeMultiple = (keys) => {
|
|
2675
|
-
const textures = getState().textures;
|
|
2676
|
-
for (const key of keys) {
|
|
2677
|
-
const entry = textures.get(key);
|
|
2678
|
-
if (entry) {
|
|
2679
|
-
const tex = getTextureValue(entry);
|
|
2680
|
-
tex?.dispose();
|
|
2681
|
-
}
|
|
2682
|
-
}
|
|
2683
|
-
removeMultiple(keys);
|
|
2684
|
-
};
|
|
2685
|
-
const disposeAll = () => {
|
|
2686
|
-
const textures = getState().textures;
|
|
2687
|
-
for (const entry of textures.values()) {
|
|
2688
|
-
const tex = getTextureValue(entry);
|
|
2689
|
-
tex?.dispose();
|
|
2690
|
-
}
|
|
2691
|
-
set({ textures: /* @__PURE__ */ new Map() });
|
|
2692
|
-
};
|
|
2274
|
+
const setState = store.setState;
|
|
2275
|
+
const getOne = (key) => getState().textures.get(key);
|
|
2693
2276
|
return {
|
|
2694
|
-
|
|
2695
|
-
get textures() {
|
|
2277
|
+
get all() {
|
|
2696
2278
|
return getState().textures;
|
|
2697
2279
|
},
|
|
2698
|
-
|
|
2699
|
-
|
|
2280
|
+
get(input) {
|
|
2281
|
+
if (typeof input === "string") return getOne(input);
|
|
2282
|
+
if (Array.isArray(input)) return input.map(getOne);
|
|
2283
|
+
const out = {};
|
|
2284
|
+
for (const name in input) out[name] = getOne(input[name]);
|
|
2285
|
+
return out;
|
|
2286
|
+
},
|
|
2700
2287
|
has: (key) => getState().textures.has(key),
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2288
|
+
add(keyOrRecord, texture) {
|
|
2289
|
+
setState((state) => {
|
|
2290
|
+
const textures = new Map(state.textures);
|
|
2291
|
+
if (typeof keyOrRecord === "string") {
|
|
2292
|
+
textures.set(keyOrRecord, texture);
|
|
2293
|
+
} else {
|
|
2294
|
+
for (const key in keyOrRecord) textures.set(key, keyOrRecord[key]);
|
|
2295
|
+
}
|
|
2296
|
+
return { textures };
|
|
2297
|
+
});
|
|
2298
|
+
},
|
|
2299
|
+
dispose(key, options) {
|
|
2300
|
+
const state = getState();
|
|
2301
|
+
const refs = state._textureRefs.get(key) ?? 0;
|
|
2302
|
+
if (refs > 0 && !options?.force) {
|
|
2303
|
+
console.warn(
|
|
2304
|
+
`[useTextures] "${key}" still has ${refs} active reference(s); skipping dispose. Pass { force: true } to override.`
|
|
2305
|
+
);
|
|
2306
|
+
return false;
|
|
2307
|
+
}
|
|
2308
|
+
state.textures.get(key)?.dispose();
|
|
2309
|
+
setState((s) => {
|
|
2310
|
+
const textures = new Map(s.textures);
|
|
2311
|
+
textures.delete(key);
|
|
2312
|
+
const nextRefs = new Map(s._textureRefs);
|
|
2313
|
+
nextRefs.delete(key);
|
|
2314
|
+
return { textures, _textureRefs: nextRefs };
|
|
2315
|
+
});
|
|
2316
|
+
return true;
|
|
2317
|
+
},
|
|
2318
|
+
disposeAll() {
|
|
2319
|
+
for (const texture of getState().textures.values()) texture.dispose();
|
|
2320
|
+
setState({ textures: /* @__PURE__ */ new Map(), _textureRefs: /* @__PURE__ */ new Map() });
|
|
2321
|
+
}
|
|
2711
2322
|
};
|
|
2712
2323
|
}, [store]);
|
|
2324
|
+
const subscribe = selector ? () => selector(registry) : (state) => state.textures;
|
|
2325
|
+
const selected = useThree(subscribe);
|
|
2326
|
+
return selector ? selected : registry;
|
|
2713
2327
|
}
|
|
2714
2328
|
|
|
2715
|
-
function useRenderTarget(
|
|
2329
|
+
function useRenderTarget(widthOrOptions, heightOrOptions, options) {
|
|
2716
2330
|
const isLegacy = useThree((s) => s.isLegacy);
|
|
2717
2331
|
const size = useThree((s) => s.size);
|
|
2332
|
+
let width;
|
|
2333
|
+
let height;
|
|
2334
|
+
let opts;
|
|
2335
|
+
if (typeof widthOrOptions === "object") {
|
|
2336
|
+
opts = widthOrOptions;
|
|
2337
|
+
} else if (typeof widthOrOptions === "number") {
|
|
2338
|
+
width = widthOrOptions;
|
|
2339
|
+
if (typeof heightOrOptions === "object") {
|
|
2340
|
+
height = widthOrOptions;
|
|
2341
|
+
opts = heightOrOptions;
|
|
2342
|
+
} else if (typeof heightOrOptions === "number") {
|
|
2343
|
+
height = heightOrOptions;
|
|
2344
|
+
opts = options;
|
|
2345
|
+
} else {
|
|
2346
|
+
height = widthOrOptions;
|
|
2347
|
+
}
|
|
2348
|
+
}
|
|
2718
2349
|
return React.useMemo(() => {
|
|
2719
2350
|
const w = width ?? size.width;
|
|
2720
2351
|
const h = height ?? size.height;
|
|
2721
|
-
return new webgpu.RenderTarget(w, h,
|
|
2722
|
-
}, [width, height, size.width, size.height,
|
|
2352
|
+
return new webgpu.RenderTarget(w, h, opts);
|
|
2353
|
+
}, [width, height, size.width, size.height, opts, isLegacy]);
|
|
2723
2354
|
}
|
|
2724
2355
|
|
|
2725
2356
|
function useStore() {
|
|
@@ -2747,7 +2378,7 @@ function addEffect(callback) {
|
|
|
2747
2378
|
link: "https://docs.pmnd.rs/react-three-fiber/api/hooks#useframe"
|
|
2748
2379
|
});
|
|
2749
2380
|
const id = `legacy_effect_${effectId++}`;
|
|
2750
|
-
return getScheduler().registerGlobal("before", id, callback);
|
|
2381
|
+
return scheduler.getScheduler().registerGlobal("before", id, callback);
|
|
2751
2382
|
}
|
|
2752
2383
|
function addAfterEffect(callback) {
|
|
2753
2384
|
notifyDepreciated({
|
|
@@ -2756,7 +2387,7 @@ function addAfterEffect(callback) {
|
|
|
2756
2387
|
link: "https://docs.pmnd.rs/react-three-fiber/api/hooks#useframe"
|
|
2757
2388
|
});
|
|
2758
2389
|
const id = `legacy_afterEffect_${effectId++}`;
|
|
2759
|
-
return getScheduler().registerGlobal("after", id, callback);
|
|
2390
|
+
return scheduler.getScheduler().registerGlobal("after", id, callback);
|
|
2760
2391
|
}
|
|
2761
2392
|
function addTail(callback) {
|
|
2762
2393
|
notifyDepreciated({
|
|
@@ -2764,33 +2395,23 @@ function addTail(callback) {
|
|
|
2764
2395
|
body: "Use scheduler.onIdle(callback) instead.\naddTail will be removed in a future version.",
|
|
2765
2396
|
link: "https://docs.pmnd.rs/react-three-fiber/api/hooks#useframe"
|
|
2766
2397
|
});
|
|
2767
|
-
return getScheduler().onIdle(callback);
|
|
2398
|
+
return scheduler.getScheduler().onIdle(callback);
|
|
2768
2399
|
}
|
|
2769
2400
|
function invalidate(state, frames = 1, stackFrames = false) {
|
|
2770
|
-
getScheduler().invalidate(frames, stackFrames);
|
|
2401
|
+
scheduler.getScheduler().invalidate(frames, stackFrames);
|
|
2771
2402
|
}
|
|
2772
|
-
function advance(timestamp
|
|
2773
|
-
getScheduler().step(timestamp);
|
|
2403
|
+
function advance(timestamp) {
|
|
2404
|
+
scheduler.getScheduler().step(timestamp);
|
|
2774
2405
|
}
|
|
2775
2406
|
|
|
2776
|
-
const version = "10.0.0-alpha.
|
|
2407
|
+
const version = "10.0.0-alpha.2";
|
|
2777
2408
|
const packageData = {
|
|
2778
2409
|
version: version};
|
|
2779
2410
|
|
|
2780
2411
|
function Xb(Tt) {
|
|
2781
2412
|
return Tt && Tt.__esModule && Object.prototype.hasOwnProperty.call(Tt, "default") ? Tt.default : Tt;
|
|
2782
2413
|
}
|
|
2783
|
-
var Rm = { exports: {} }, Og = { exports: {} };
|
|
2784
|
-
/**
|
|
2785
|
-
* @license React
|
|
2786
|
-
* react-reconciler.production.js
|
|
2787
|
-
*
|
|
2788
|
-
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2789
|
-
*
|
|
2790
|
-
* This source code is licensed under the MIT license found in the
|
|
2791
|
-
* LICENSE file in the root directory of this source tree.
|
|
2792
|
-
*/
|
|
2793
|
-
var _b;
|
|
2414
|
+
var Rm = { exports: {} }, Og = { exports: {} }, _b;
|
|
2794
2415
|
function Kb() {
|
|
2795
2416
|
return _b || (_b = 1, (function(Tt) {
|
|
2796
2417
|
Tt.exports = function(m) {
|
|
@@ -3862,7 +3483,6 @@ Error generating stack: ` + l.message + `
|
|
|
3862
3483
|
if (J === cl || J === jc) throw J;
|
|
3863
3484
|
var Ge = Yn(29, J, null, P.mode);
|
|
3864
3485
|
return Ge.lanes = H, Ge.return = P, Ge;
|
|
3865
|
-
} finally {
|
|
3866
3486
|
}
|
|
3867
3487
|
};
|
|
3868
3488
|
}
|
|
@@ -4516,7 +4136,6 @@ Error generating stack: ` + l.message + `
|
|
|
4516
4136
|
var h = r.lastRenderedState, y = d(h, a);
|
|
4517
4137
|
if (c.hasEagerState = true, c.eagerState = y, jn(y, h)) return go(t, r, c, 0), Ne === null && Bn(), false;
|
|
4518
4138
|
} catch {
|
|
4519
|
-
} finally {
|
|
4520
4139
|
}
|
|
4521
4140
|
if (a = yo(t, r, c, l), a !== null) return nt(a, t, l), ns(a, r, l), true;
|
|
4522
4141
|
}
|
|
@@ -6937,10 +6556,7 @@ Error generating stack: ` + l.message + `
|
|
|
6937
6556
|
function vr(t, r) {
|
|
6938
6557
|
Sf(t, r), (t = t.alternate) && Sf(t, r);
|
|
6939
6558
|
}
|
|
6940
|
-
var ie = {}, Fm = React__default, tt = Tb__default, Lt = Object.assign, hc = Symbol.for("react.element"), zs = Symbol.for("react.transitional.element"), sa = Symbol.for("react.portal"), $a = Symbol.for("react.fragment"), kf = Symbol.for("react.strict_mode"), Cs = Symbol.for("react.profiler"), mc = Symbol.for("react.consumer"), Io = Symbol.for("react.context"), Zi = Symbol.for("react.forward_ref"), Va = Symbol.for("react.suspense"), Te = Symbol.for("react.suspense_list"), wf = Symbol.for("react.memo"), ua = Symbol.for("react.lazy");
|
|
6941
|
-
var gc = Symbol.for("react.activity");
|
|
6942
|
-
var $r = Symbol.for("react.memo_cache_sentinel");
|
|
6943
|
-
var Pf = Symbol.iterator, xf = Symbol.for("react.client.reference"), ca = Array.isArray, M = Fm.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE, Yp = m.rendererVersion, zf = m.rendererPackageName, Cf = m.extraDevToolsConfig, Ts = m.getPublicInstance, Hm = m.getRootHostContext, Xp = m.getChildHostContext, Am = m.prepareForCommit, _s = m.resetAfterCommit, Vr = m.createInstance;
|
|
6559
|
+
var ie = {}, Fm = React__default, tt = Tb__default, Lt = Object.assign, hc = Symbol.for("react.element"), zs = Symbol.for("react.transitional.element"), sa = Symbol.for("react.portal"), $a = Symbol.for("react.fragment"), kf = Symbol.for("react.strict_mode"), Cs = Symbol.for("react.profiler"), mc = Symbol.for("react.consumer"), Io = Symbol.for("react.context"), Zi = Symbol.for("react.forward_ref"), Va = Symbol.for("react.suspense"), Te = Symbol.for("react.suspense_list"), wf = Symbol.for("react.memo"), ua = Symbol.for("react.lazy"), gc = Symbol.for("react.activity"), $r = Symbol.for("react.memo_cache_sentinel"), Pf = Symbol.iterator, xf = Symbol.for("react.client.reference"), ca = Array.isArray, M = Fm.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE, Yp = m.rendererVersion, zf = m.rendererPackageName, Cf = m.extraDevToolsConfig, Ts = m.getPublicInstance, Hm = m.getRootHostContext, Xp = m.getChildHostContext, Am = m.prepareForCommit, _s = m.resetAfterCommit, Vr = m.createInstance;
|
|
6944
6560
|
m.cloneMutableInstance;
|
|
6945
6561
|
var yc = m.appendInitialChild, Kp = m.finalizeInitialChildren, Rs = m.shouldSetTextContent, bc = m.createTextInstance;
|
|
6946
6562
|
m.cloneMutableTextInstance;
|
|
@@ -7309,17 +6925,7 @@ No matching component was found for:
|
|
|
7309
6925
|
}, Tt.exports.default = Tt.exports, Object.defineProperty(Tt.exports, "__esModule", { value: true });
|
|
7310
6926
|
})(Og)), Og.exports;
|
|
7311
6927
|
}
|
|
7312
|
-
var Mg = { exports: {} };
|
|
7313
|
-
/**
|
|
7314
|
-
* @license React
|
|
7315
|
-
* react-reconciler.development.js
|
|
7316
|
-
*
|
|
7317
|
-
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
7318
|
-
*
|
|
7319
|
-
* This source code is licensed under the MIT license found in the
|
|
7320
|
-
* LICENSE file in the root directory of this source tree.
|
|
7321
|
-
*/
|
|
7322
|
-
var Rb;
|
|
6928
|
+
var Mg = { exports: {} }, Rb;
|
|
7323
6929
|
function e0() {
|
|
7324
6930
|
return Rb || (Rb = 1, (function(Tt) {
|
|
7325
6931
|
process.env.NODE_ENV !== "production" && (Tt.exports = function(m) {
|
|
@@ -13086,10 +12692,7 @@ Check the render method of %s.`, G(di) || "Unknown")), i = zo(n), i.payload = {
|
|
|
13086
12692
|
function Ic() {
|
|
13087
12693
|
return di;
|
|
13088
12694
|
}
|
|
13089
|
-
var le = {}, qm = React__default, St = Tb__default, ze = Object.assign, Uh = Symbol.for("react.element"), Ho = Symbol.for("react.transitional.element"), Ao = Symbol.for("react.portal"), ol = Symbol.for("react.fragment"), Lc = Symbol.for("react.strict_mode"), Uf = Symbol.for("react.profiler"), ei = Symbol.for("react.consumer"), on = Symbol.for("react.context"), jn = Symbol.for("react.forward_ref"), Nc = Symbol.for("react.suspense"), Bf = Symbol.for("react.suspense_list"), al = Symbol.for("react.memo"), kt = Symbol.for("react.lazy");
|
|
13090
|
-
var Ds = Symbol.for("react.activity");
|
|
13091
|
-
var Bh = Symbol.for("react.memo_cache_sentinel");
|
|
13092
|
-
var ni = Symbol.iterator, il = Symbol.for("react.client.reference"), fn = Array.isArray, x = qm.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE, Jt = m.rendererVersion, Zt = m.rendererPackageName, jo = m.extraDevToolsConfig, ot = m.getPublicInstance, Zr = m.getRootHostContext, Dn = m.getChildHostContext, Ws = m.prepareForCommit, pa = m.resetAfterCommit, Fc = m.createInstance;
|
|
12695
|
+
var le = {}, qm = React__default, St = Tb__default, ze = Object.assign, Uh = Symbol.for("react.element"), Ho = Symbol.for("react.transitional.element"), Ao = Symbol.for("react.portal"), ol = Symbol.for("react.fragment"), Lc = Symbol.for("react.strict_mode"), Uf = Symbol.for("react.profiler"), ei = Symbol.for("react.consumer"), on = Symbol.for("react.context"), jn = Symbol.for("react.forward_ref"), Nc = Symbol.for("react.suspense"), Bf = Symbol.for("react.suspense_list"), al = Symbol.for("react.memo"), kt = Symbol.for("react.lazy"), Ds = Symbol.for("react.activity"), Bh = Symbol.for("react.memo_cache_sentinel"), ni = Symbol.iterator, il = Symbol.for("react.client.reference"), fn = Array.isArray, x = qm.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE, Jt = m.rendererVersion, Zt = m.rendererPackageName, jo = m.extraDevToolsConfig, ot = m.getPublicInstance, Zr = m.getRootHostContext, Dn = m.getChildHostContext, Ws = m.prepareForCommit, pa = m.resetAfterCommit, Fc = m.createInstance;
|
|
13093
12696
|
m.cloneMutableInstance;
|
|
13094
12697
|
var bn = m.appendInitialChild, Ue = m.finalizeInitialChildren, ue = m.shouldSetTextContent, Do = m.createTextInstance;
|
|
13095
12698
|
m.cloneMutableTextInstance;
|
|
@@ -14057,15 +13660,6 @@ function n0() {
|
|
|
14057
13660
|
var t0 = n0();
|
|
14058
13661
|
const r0 = Xb(t0);
|
|
14059
13662
|
|
|
14060
|
-
/**
|
|
14061
|
-
* @license React
|
|
14062
|
-
* react-reconciler-constants.production.js
|
|
14063
|
-
*
|
|
14064
|
-
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
14065
|
-
*
|
|
14066
|
-
* This source code is licensed under the MIT license found in the
|
|
14067
|
-
* LICENSE file in the root directory of this source tree.
|
|
14068
|
-
*/
|
|
14069
13663
|
const t = 1, o = 8, r = 32, e = 2;
|
|
14070
13664
|
|
|
14071
13665
|
function createReconciler(config) {
|
|
@@ -14092,10 +13686,11 @@ function extend(objects) {
|
|
|
14092
13686
|
function validateInstance(type, props) {
|
|
14093
13687
|
const name = toPascalCase(type);
|
|
14094
13688
|
const target = catalogue[name];
|
|
14095
|
-
if (type !== "primitive" && !target)
|
|
13689
|
+
if (type !== "primitive" && !target) {
|
|
14096
13690
|
throw new Error(
|
|
14097
13691
|
`R3F: ${name} is not part of the THREE namespace! Did you forget to extend? See: https://docs.pmnd.rs/react-three-fiber/api/objects#using-3rd-party-objects-declaratively`
|
|
14098
13692
|
);
|
|
13693
|
+
}
|
|
14099
13694
|
if (type === "primitive" && !props.object) throw new Error(`R3F: Primitives without 'object' are invalid!`);
|
|
14100
13695
|
if (props.args !== void 0 && !Array.isArray(props.args)) throw new Error("R3F: The args prop must be an array!");
|
|
14101
13696
|
}
|
|
@@ -14259,6 +13854,7 @@ function swapInstances() {
|
|
|
14259
13854
|
instance.object = instance.props.object ?? new target(...instance.props.args ?? []);
|
|
14260
13855
|
instance.object.__r3f = instance;
|
|
14261
13856
|
setFiberRef(fiber, instance.object);
|
|
13857
|
+
delete instance.appliedOnce;
|
|
14262
13858
|
applyProps(instance.object, instance.props);
|
|
14263
13859
|
if (instance.props.attach) {
|
|
14264
13860
|
attach(parent, instance);
|
|
@@ -14332,8 +13928,22 @@ const reconciler = /* @__PURE__ */ createReconciler({
|
|
|
14332
13928
|
const isTailSibling = fiber.sibling === null || (fiber.flags & Update) === NoFlags;
|
|
14333
13929
|
if (isTailSibling) swapInstances();
|
|
14334
13930
|
},
|
|
14335
|
-
finalizeInitialChildren: () =>
|
|
14336
|
-
|
|
13931
|
+
finalizeInitialChildren: (instance) => {
|
|
13932
|
+
for (const prop in instance.props) {
|
|
13933
|
+
if (isFromRef(instance.props[prop])) return true;
|
|
13934
|
+
}
|
|
13935
|
+
return false;
|
|
13936
|
+
},
|
|
13937
|
+
commitMount(instance) {
|
|
13938
|
+
const resolved = {};
|
|
13939
|
+
for (const prop in instance.props) {
|
|
13940
|
+
const value = instance.props[prop];
|
|
13941
|
+
if (isFromRef(value)) {
|
|
13942
|
+
const ref = value[FROM_REF];
|
|
13943
|
+
if (ref.current != null) resolved[prop] = ref.current;
|
|
13944
|
+
}
|
|
13945
|
+
}
|
|
13946
|
+
if (Object.keys(resolved).length) applyProps(instance.object, resolved);
|
|
14337
13947
|
},
|
|
14338
13948
|
getPublicInstance: (instance) => instance?.object,
|
|
14339
13949
|
prepareForCommit: () => null,
|
|
@@ -14546,14 +14156,17 @@ function createRoot(canvas) {
|
|
|
14546
14156
|
if (!prevRoot) _roots.set(canvas, { fiber, store });
|
|
14547
14157
|
let onCreated;
|
|
14548
14158
|
let lastCamera;
|
|
14549
|
-
|
|
14159
|
+
const lastConfiguredProps = {};
|
|
14550
14160
|
let configured = false;
|
|
14551
14161
|
let pending = null;
|
|
14552
14162
|
return {
|
|
14553
14163
|
async configure(props = {}) {
|
|
14554
14164
|
let resolve;
|
|
14555
14165
|
pending = new Promise((_resolve) => resolve = _resolve);
|
|
14556
|
-
|
|
14166
|
+
const {
|
|
14167
|
+
id: canvasId,
|
|
14168
|
+
primaryCanvas,
|
|
14169
|
+
scheduler: schedulerConfig,
|
|
14557
14170
|
gl: glConfig,
|
|
14558
14171
|
renderer: rendererConfig,
|
|
14559
14172
|
size: propsSize,
|
|
@@ -14561,10 +14174,6 @@ function createRoot(canvas) {
|
|
|
14561
14174
|
events,
|
|
14562
14175
|
onCreated: onCreatedCallback,
|
|
14563
14176
|
shadows = false,
|
|
14564
|
-
linear = false,
|
|
14565
|
-
flat = false,
|
|
14566
|
-
textureColorSpace = webgpu.SRGBColorSpace,
|
|
14567
|
-
legacy = false,
|
|
14568
14177
|
orthographic = false,
|
|
14569
14178
|
frameloop = "always",
|
|
14570
14179
|
dpr = [1, 2],
|
|
@@ -14575,11 +14184,15 @@ function createRoot(canvas) {
|
|
|
14575
14184
|
onDragOverMissed,
|
|
14576
14185
|
onDropMissed,
|
|
14577
14186
|
autoUpdateFrustum = true,
|
|
14578
|
-
occlusion = false
|
|
14187
|
+
occlusion = false,
|
|
14188
|
+
_sizeProps,
|
|
14189
|
+
forceEven
|
|
14579
14190
|
} = props;
|
|
14580
|
-
|
|
14191
|
+
const textureColorSpace = is.obj(glConfig) && !is.fun(glConfig) && !isRenderer(glConfig) && glConfig.textureColorSpace || is.obj(rendererConfig) && !is.fun(rendererConfig) && !isRenderer(rendererConfig) && rendererConfig.textureColorSpace || webgpu.SRGBColorSpace;
|
|
14192
|
+
const state = store.getState();
|
|
14581
14193
|
const defaultGPUProps = {
|
|
14582
|
-
canvas
|
|
14194
|
+
canvas,
|
|
14195
|
+
antialias: true
|
|
14583
14196
|
};
|
|
14584
14197
|
if (glConfig && !R3F_BUILD_LEGACY) {
|
|
14585
14198
|
throw new Error(
|
|
@@ -14590,22 +14203,61 @@ function createRoot(canvas) {
|
|
|
14590
14203
|
throw new Error("Cannot use both gl and renderer props at the same time");
|
|
14591
14204
|
}
|
|
14592
14205
|
let renderer = state.internal.actualRenderer;
|
|
14593
|
-
if (!state.internal.actualRenderer) {
|
|
14206
|
+
if (primaryCanvas && !state.internal.actualRenderer) {
|
|
14207
|
+
const primary = await waitForPrimary(primaryCanvas);
|
|
14208
|
+
renderer = primary.renderer;
|
|
14209
|
+
state.internal.actualRenderer = renderer;
|
|
14210
|
+
const canvasTarget = new webgpu.CanvasTarget(canvas);
|
|
14211
|
+
primary.store.setState((prev) => ({
|
|
14212
|
+
internal: { ...prev.internal, isMultiCanvas: true }
|
|
14213
|
+
}));
|
|
14214
|
+
state.set((prev) => ({
|
|
14215
|
+
webGPUSupported: primary.store.getState().webGPUSupported,
|
|
14216
|
+
renderer,
|
|
14217
|
+
primaryStore: primary.store,
|
|
14218
|
+
internal: {
|
|
14219
|
+
...prev.internal,
|
|
14220
|
+
canvasTarget,
|
|
14221
|
+
isMultiCanvas: true,
|
|
14222
|
+
isSecondary: true,
|
|
14223
|
+
targetId: primaryCanvas
|
|
14224
|
+
}
|
|
14225
|
+
}));
|
|
14226
|
+
} else if (!state.internal.actualRenderer) {
|
|
14594
14227
|
renderer = await resolveRenderer(rendererConfig, defaultGPUProps, webgpu.WebGPURenderer);
|
|
14595
14228
|
if (!renderer.hasInitialized?.()) {
|
|
14229
|
+
const size2 = computeInitialSize(canvas, propsSize);
|
|
14230
|
+
if (size2.width > 0 && size2.height > 0) {
|
|
14231
|
+
const pixelRatio = calculateDpr(dpr);
|
|
14232
|
+
canvas.width = size2.width * pixelRatio;
|
|
14233
|
+
canvas.height = size2.height * pixelRatio;
|
|
14234
|
+
}
|
|
14596
14235
|
await renderer.init();
|
|
14597
14236
|
}
|
|
14598
14237
|
const backend = renderer.backend;
|
|
14599
14238
|
const isWebGPUBackend = backend && "isWebGPUBackend" in backend;
|
|
14600
14239
|
state.internal.actualRenderer = renderer;
|
|
14601
|
-
state.set({ webGPUSupported: isWebGPUBackend, renderer });
|
|
14240
|
+
state.set({ webGPUSupported: isWebGPUBackend, renderer, primaryStore: store });
|
|
14241
|
+
if (canvasId && !state.internal.isSecondary) {
|
|
14242
|
+
const canvasTarget = new webgpu.CanvasTarget(canvas);
|
|
14243
|
+
const unregisterPrimary = registerPrimary(canvasId, renderer, store);
|
|
14244
|
+
state.set((prev) => ({
|
|
14245
|
+
internal: {
|
|
14246
|
+
...prev.internal,
|
|
14247
|
+
canvasTarget,
|
|
14248
|
+
unregisterPrimary
|
|
14249
|
+
}
|
|
14250
|
+
}));
|
|
14251
|
+
}
|
|
14602
14252
|
}
|
|
14603
14253
|
let raycaster = state.raycaster;
|
|
14604
14254
|
if (!raycaster) state.set({ raycaster: raycaster = new webgpu.Raycaster() });
|
|
14605
14255
|
const { params, ...options } = raycastOptions || {};
|
|
14606
14256
|
if (!is.equ(options, raycaster, shallowLoose)) applyProps(raycaster, { ...options });
|
|
14607
|
-
if (!is.equ(params, raycaster.params, shallowLoose))
|
|
14257
|
+
if (!is.equ(params, raycaster.params, shallowLoose)) {
|
|
14608
14258
|
applyProps(raycaster, { params: { ...raycaster.params, ...params } });
|
|
14259
|
+
}
|
|
14260
|
+
let tempCamera = state.camera;
|
|
14609
14261
|
if (!state.camera || state.camera === lastCamera && !is.equ(lastCamera, cameraOptions, shallowLoose)) {
|
|
14610
14262
|
lastCamera = cameraOptions;
|
|
14611
14263
|
const isCamera = cameraOptions?.isCamera;
|
|
@@ -14625,6 +14277,7 @@ function createRoot(canvas) {
|
|
|
14625
14277
|
if (!state.camera && !cameraOptions?.rotation) camera.lookAt(0, 0, 0);
|
|
14626
14278
|
}
|
|
14627
14279
|
state.set({ camera });
|
|
14280
|
+
tempCamera = camera;
|
|
14628
14281
|
raycaster.camera = camera;
|
|
14629
14282
|
}
|
|
14630
14283
|
if (!state.scene) {
|
|
@@ -14642,7 +14295,7 @@ function createRoot(canvas) {
|
|
|
14642
14295
|
rootScene: scene,
|
|
14643
14296
|
internal: { ...prev.internal, container: scene }
|
|
14644
14297
|
}));
|
|
14645
|
-
const camera =
|
|
14298
|
+
const camera = tempCamera;
|
|
14646
14299
|
if (camera && !camera.parent) scene.add(camera);
|
|
14647
14300
|
}
|
|
14648
14301
|
if (events && !state.events.handlers) {
|
|
@@ -14656,9 +14309,17 @@ function createRoot(canvas) {
|
|
|
14656
14309
|
wasEnabled = enabled;
|
|
14657
14310
|
});
|
|
14658
14311
|
}
|
|
14312
|
+
if (_sizeProps !== void 0) {
|
|
14313
|
+
state.set({ _sizeProps });
|
|
14314
|
+
}
|
|
14315
|
+
if (forceEven !== void 0 && state.internal.forceEven !== forceEven) {
|
|
14316
|
+
state.set((prev) => ({ internal: { ...prev.internal, forceEven } }));
|
|
14317
|
+
}
|
|
14659
14318
|
const size = computeInitialSize(canvas, propsSize);
|
|
14660
|
-
if (!is.equ(size, state.size, shallowLoose)) {
|
|
14319
|
+
if (!state._sizeImperative && !is.equ(size, state.size, shallowLoose)) {
|
|
14320
|
+
const wasImperative = state._sizeImperative;
|
|
14661
14321
|
state.setSize(size.width, size.height, size.top, size.left);
|
|
14322
|
+
if (!wasImperative) state.set({ _sizeImperative: false });
|
|
14662
14323
|
}
|
|
14663
14324
|
if (dpr !== void 0 && !is.equ(dpr, lastConfiguredProps.dpr, shallowLoose)) {
|
|
14664
14325
|
state.setDpr(dpr);
|
|
@@ -14680,10 +14341,10 @@ function createRoot(canvas) {
|
|
|
14680
14341
|
lastConfiguredProps.performance = performance;
|
|
14681
14342
|
}
|
|
14682
14343
|
if (!state.xr) {
|
|
14683
|
-
const handleXRFrame = (timestamp,
|
|
14344
|
+
const handleXRFrame = (timestamp, _frame) => {
|
|
14684
14345
|
const state2 = store.getState();
|
|
14685
14346
|
if (state2.frameloop === "never") return;
|
|
14686
|
-
advance(timestamp
|
|
14347
|
+
advance(timestamp);
|
|
14687
14348
|
};
|
|
14688
14349
|
const actualRenderer = state.internal.actualRenderer;
|
|
14689
14350
|
const handleSessionChange = () => {
|
|
@@ -14695,16 +14356,16 @@ function createRoot(canvas) {
|
|
|
14695
14356
|
};
|
|
14696
14357
|
const xr = {
|
|
14697
14358
|
connect() {
|
|
14698
|
-
const { gl, renderer: renderer2
|
|
14699
|
-
const
|
|
14700
|
-
|
|
14701
|
-
|
|
14359
|
+
const { gl, renderer: renderer2 } = store.getState();
|
|
14360
|
+
const xrManager = (renderer2 || gl).xr;
|
|
14361
|
+
xrManager.addEventListener("sessionstart", handleSessionChange);
|
|
14362
|
+
xrManager.addEventListener("sessionend", handleSessionChange);
|
|
14702
14363
|
},
|
|
14703
14364
|
disconnect() {
|
|
14704
|
-
const { gl, renderer: renderer2
|
|
14705
|
-
const
|
|
14706
|
-
|
|
14707
|
-
|
|
14365
|
+
const { gl, renderer: renderer2 } = store.getState();
|
|
14366
|
+
const xrManager = (renderer2 || gl).xr;
|
|
14367
|
+
xrManager.removeEventListener("sessionstart", handleSessionChange);
|
|
14368
|
+
xrManager.removeEventListener("sessionend", handleSessionChange);
|
|
14708
14369
|
}
|
|
14709
14370
|
};
|
|
14710
14371
|
if (typeof renderer.xr?.addEventListener === "function") xr.connect();
|
|
@@ -14716,42 +14377,93 @@ function createRoot(canvas) {
|
|
|
14716
14377
|
const oldType = renderer.shadowMap.type;
|
|
14717
14378
|
renderer.shadowMap.enabled = !!shadows;
|
|
14718
14379
|
if (is.boo(shadows)) {
|
|
14719
|
-
renderer.shadowMap.type = webgpu.
|
|
14380
|
+
renderer.shadowMap.type = webgpu.PCFShadowMap;
|
|
14720
14381
|
} else if (is.str(shadows)) {
|
|
14382
|
+
if (shadows === "soft") {
|
|
14383
|
+
notifyDepreciated({
|
|
14384
|
+
heading: 'shadows="soft" is deprecated',
|
|
14385
|
+
body: "Three has depreciated soft and improved basic PCFShadows, we converted for you.",
|
|
14386
|
+
link: "https://github.com/mrdoob/three.js/wiki/Migration-Guide?utm_source=chatgpt.com#181--182"
|
|
14387
|
+
});
|
|
14388
|
+
}
|
|
14721
14389
|
const types = {
|
|
14722
14390
|
basic: webgpu.BasicShadowMap,
|
|
14723
14391
|
percentage: webgpu.PCFShadowMap,
|
|
14724
|
-
soft: webgpu.
|
|
14392
|
+
soft: webgpu.PCFShadowMap,
|
|
14725
14393
|
variance: webgpu.VSMShadowMap
|
|
14726
14394
|
};
|
|
14727
|
-
renderer.shadowMap.type = types[shadows] ?? webgpu.
|
|
14395
|
+
renderer.shadowMap.type = types[shadows] ?? webgpu.PCFShadowMap;
|
|
14728
14396
|
} else if (is.obj(shadows)) {
|
|
14729
14397
|
Object.assign(renderer.shadowMap, shadows);
|
|
14730
14398
|
}
|
|
14731
|
-
if (oldEnabled !== renderer.shadowMap.enabled || oldType !== renderer.shadowMap.type)
|
|
14399
|
+
if (oldEnabled !== renderer.shadowMap.enabled || oldType !== renderer.shadowMap.type) {
|
|
14732
14400
|
renderer.shadowMap.needsUpdate = true;
|
|
14401
|
+
}
|
|
14402
|
+
}
|
|
14403
|
+
if (!configured) {
|
|
14404
|
+
renderer.outputColorSpace = webgpu.SRGBColorSpace;
|
|
14405
|
+
renderer.toneMapping = webgpu.ACESFilmicToneMapping;
|
|
14733
14406
|
}
|
|
14734
14407
|
if (textureColorSpace !== lastConfiguredProps.textureColorSpace) {
|
|
14735
14408
|
if (state.textureColorSpace !== textureColorSpace) state.set(() => ({ textureColorSpace }));
|
|
14736
14409
|
lastConfiguredProps.textureColorSpace = textureColorSpace;
|
|
14737
14410
|
}
|
|
14738
|
-
|
|
14739
|
-
|
|
14411
|
+
const r3fProps = ["textureColorSpace"];
|
|
14412
|
+
const constructorOnlyProps = ["samples", "antialias", "alpha", "canvas", "powerPreference"];
|
|
14413
|
+
const nonApplyProps = [...r3fProps, ...constructorOnlyProps];
|
|
14414
|
+
if (glConfig && !is.fun(glConfig) && !isRenderer(glConfig) && !is.equ(glConfig, renderer, shallowLoose)) {
|
|
14415
|
+
const glProps = {};
|
|
14416
|
+
for (const key in glConfig) {
|
|
14417
|
+
if (!nonApplyProps.includes(key)) glProps[key] = glConfig[key];
|
|
14418
|
+
}
|
|
14419
|
+
applyProps(renderer, glProps);
|
|
14420
|
+
}
|
|
14740
14421
|
if (rendererConfig && !is.fun(rendererConfig) && !isRenderer(rendererConfig) && state.renderer) {
|
|
14741
14422
|
const currentRenderer = state.renderer;
|
|
14742
14423
|
if (!is.equ(rendererConfig, currentRenderer, shallowLoose)) {
|
|
14743
|
-
|
|
14424
|
+
const rendererProps = {};
|
|
14425
|
+
for (const key in rendererConfig) {
|
|
14426
|
+
if (!nonApplyProps.includes(key)) rendererProps[key] = rendererConfig[key];
|
|
14427
|
+
}
|
|
14428
|
+
applyProps(currentRenderer, rendererProps);
|
|
14744
14429
|
}
|
|
14745
14430
|
}
|
|
14746
|
-
const scheduler = getScheduler();
|
|
14431
|
+
const scheduler$1 = scheduler.getScheduler();
|
|
14747
14432
|
const rootId = state.internal.rootId;
|
|
14748
14433
|
if (!rootId) {
|
|
14749
|
-
const newRootId = scheduler.generateRootId();
|
|
14750
|
-
const unregisterRoot = scheduler.registerRoot(newRootId, {
|
|
14434
|
+
const newRootId = canvasId || scheduler$1.generateRootId();
|
|
14435
|
+
const unregisterRoot = scheduler$1.registerRoot(newRootId, {
|
|
14751
14436
|
getState: () => store.getState(),
|
|
14752
14437
|
onError: (err) => store.getState().setError(err)
|
|
14753
14438
|
});
|
|
14754
|
-
const
|
|
14439
|
+
const unregisterCanvasTarget = scheduler$1.register(
|
|
14440
|
+
() => {
|
|
14441
|
+
const state2 = store.getState();
|
|
14442
|
+
if (state2.internal.isMultiCanvas && state2.internal.canvasTarget) {
|
|
14443
|
+
const renderer2 = state2.internal.actualRenderer;
|
|
14444
|
+
renderer2.setCanvasTarget(state2.internal.canvasTarget);
|
|
14445
|
+
}
|
|
14446
|
+
},
|
|
14447
|
+
{
|
|
14448
|
+
id: `${newRootId}_canvasTarget`,
|
|
14449
|
+
rootId: newRootId,
|
|
14450
|
+
phase: "start",
|
|
14451
|
+
system: true
|
|
14452
|
+
}
|
|
14453
|
+
);
|
|
14454
|
+
const unregisterEventsFlush = scheduler$1.register(
|
|
14455
|
+
() => {
|
|
14456
|
+
const state2 = store.getState();
|
|
14457
|
+
state2.events.flush?.();
|
|
14458
|
+
},
|
|
14459
|
+
{
|
|
14460
|
+
id: `${newRootId}_events`,
|
|
14461
|
+
rootId: newRootId,
|
|
14462
|
+
phase: "input",
|
|
14463
|
+
system: true
|
|
14464
|
+
}
|
|
14465
|
+
);
|
|
14466
|
+
const unregisterFrustum = scheduler$1.register(
|
|
14755
14467
|
() => {
|
|
14756
14468
|
const state2 = store.getState();
|
|
14757
14469
|
if (state2.autoUpdateFrustum && state2.camera) {
|
|
@@ -14761,11 +14473,11 @@ function createRoot(canvas) {
|
|
|
14761
14473
|
{
|
|
14762
14474
|
id: `${newRootId}_frustum`,
|
|
14763
14475
|
rootId: newRootId,
|
|
14764
|
-
|
|
14476
|
+
before: "render",
|
|
14765
14477
|
system: true
|
|
14766
14478
|
}
|
|
14767
14479
|
);
|
|
14768
|
-
const unregisterVisibility = scheduler.register(
|
|
14480
|
+
const unregisterVisibility = scheduler$1.register(
|
|
14769
14481
|
() => {
|
|
14770
14482
|
const state2 = store.getState();
|
|
14771
14483
|
checkVisibility(state2);
|
|
@@ -14773,30 +14485,34 @@ function createRoot(canvas) {
|
|
|
14773
14485
|
{
|
|
14774
14486
|
id: `${newRootId}_visibility`,
|
|
14775
14487
|
rootId: newRootId,
|
|
14776
|
-
|
|
14488
|
+
before: "render",
|
|
14777
14489
|
system: true,
|
|
14778
14490
|
after: `${newRootId}_frustum`
|
|
14779
14491
|
}
|
|
14780
14492
|
);
|
|
14781
|
-
const unregisterRender = scheduler.register(
|
|
14493
|
+
const unregisterRender = scheduler$1.register(
|
|
14782
14494
|
() => {
|
|
14783
14495
|
const state2 = store.getState();
|
|
14784
14496
|
const renderer2 = state2.internal.actualRenderer;
|
|
14785
|
-
const userHandlesRender = scheduler.hasUserJobsInPhase("render", newRootId);
|
|
14497
|
+
const userHandlesRender = scheduler$1.hasUserJobsInPhase("render", newRootId);
|
|
14786
14498
|
if (userHandlesRender || state2.internal.priority) return;
|
|
14787
14499
|
try {
|
|
14788
|
-
if (state2.
|
|
14500
|
+
if (state2.renderPipeline?.render) state2.renderPipeline.render();
|
|
14789
14501
|
else if (renderer2?.render) renderer2.render(state2.scene, state2.camera);
|
|
14790
14502
|
} catch (error) {
|
|
14791
14503
|
state2.setError(error instanceof Error ? error : new Error(String(error)));
|
|
14792
14504
|
}
|
|
14793
14505
|
},
|
|
14794
14506
|
{
|
|
14795
|
-
|
|
14507
|
+
// Use canvas ID directly as job ID if available, otherwise use generated rootId
|
|
14508
|
+
id: canvasId || `${newRootId}_render`,
|
|
14796
14509
|
rootId: newRootId,
|
|
14797
14510
|
phase: "render",
|
|
14798
|
-
system: true
|
|
14511
|
+
system: true,
|
|
14799
14512
|
// Internal flag: this is a system job, not user-controlled
|
|
14513
|
+
// Apply scheduler config for render ordering and rate limiting
|
|
14514
|
+
...schedulerConfig?.after && { after: schedulerConfig.after },
|
|
14515
|
+
...schedulerConfig?.fps && { fps: schedulerConfig.fps }
|
|
14800
14516
|
}
|
|
14801
14517
|
);
|
|
14802
14518
|
state.set((state2) => ({
|
|
@@ -14805,15 +14521,17 @@ function createRoot(canvas) {
|
|
|
14805
14521
|
rootId: newRootId,
|
|
14806
14522
|
unregisterRoot: () => {
|
|
14807
14523
|
unregisterRoot();
|
|
14524
|
+
unregisterCanvasTarget();
|
|
14525
|
+
unregisterEventsFlush();
|
|
14808
14526
|
unregisterFrustum();
|
|
14809
14527
|
unregisterVisibility();
|
|
14810
14528
|
unregisterRender();
|
|
14811
14529
|
},
|
|
14812
|
-
scheduler
|
|
14530
|
+
scheduler: scheduler$1
|
|
14813
14531
|
}
|
|
14814
14532
|
}));
|
|
14815
14533
|
}
|
|
14816
|
-
scheduler.frameloop = frameloop;
|
|
14534
|
+
scheduler$1.frameloop = frameloop;
|
|
14817
14535
|
onCreated = onCreatedCallback;
|
|
14818
14536
|
configured = true;
|
|
14819
14537
|
resolve();
|
|
@@ -14863,15 +14581,24 @@ function unmountComponentAtNode(canvas, callback) {
|
|
|
14863
14581
|
const renderer = state.internal.actualRenderer;
|
|
14864
14582
|
const unregisterRoot = state.internal.unregisterRoot;
|
|
14865
14583
|
if (unregisterRoot) unregisterRoot();
|
|
14584
|
+
const unregisterPrimary = state.internal.unregisterPrimary;
|
|
14585
|
+
if (unregisterPrimary) unregisterPrimary();
|
|
14586
|
+
const canvasTarget = state.internal.canvasTarget;
|
|
14587
|
+
if (canvasTarget?.dispose) canvasTarget.dispose();
|
|
14866
14588
|
state.events.disconnect?.();
|
|
14867
14589
|
cleanupHelperGroup(root.store);
|
|
14868
|
-
renderer
|
|
14869
|
-
|
|
14870
|
-
|
|
14590
|
+
if (state.isLegacy && renderer) {
|
|
14591
|
+
;
|
|
14592
|
+
renderer.renderLists?.dispose?.();
|
|
14593
|
+
renderer.forceContextLoss?.();
|
|
14594
|
+
}
|
|
14595
|
+
if (!state.internal.isSecondary) {
|
|
14596
|
+
if (renderer?.xr) state.xr.disconnect();
|
|
14597
|
+
}
|
|
14871
14598
|
dispose(state.scene);
|
|
14872
14599
|
_roots.delete(canvas);
|
|
14873
14600
|
if (callback) callback(canvas);
|
|
14874
|
-
} catch
|
|
14601
|
+
} catch {
|
|
14875
14602
|
}
|
|
14876
14603
|
}, 500);
|
|
14877
14604
|
}
|
|
@@ -14879,36 +14606,34 @@ function unmountComponentAtNode(canvas, callback) {
|
|
|
14879
14606
|
}
|
|
14880
14607
|
}
|
|
14881
14608
|
function createPortal(children, container, state) {
|
|
14882
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
14609
|
+
return /* @__PURE__ */ jsxRuntime.jsx(Portal, { children, container, state });
|
|
14883
14610
|
}
|
|
14884
|
-
function
|
|
14611
|
+
function Portal({ children, container, state }) {
|
|
14885
14612
|
const isRef = React.useCallback((obj) => obj && "current" in obj, []);
|
|
14886
|
-
const [resolvedContainer,
|
|
14613
|
+
const [resolvedContainer, _setResolvedContainer] = React.useState(() => {
|
|
14887
14614
|
if (isRef(container)) return container.current ?? null;
|
|
14888
14615
|
return container;
|
|
14889
14616
|
});
|
|
14617
|
+
const setResolvedContainer = React.useCallback(
|
|
14618
|
+
(newContainer) => {
|
|
14619
|
+
if (!newContainer || newContainer === resolvedContainer) return;
|
|
14620
|
+
_setResolvedContainer(isRef(newContainer) ? newContainer.current : newContainer);
|
|
14621
|
+
},
|
|
14622
|
+
[resolvedContainer, _setResolvedContainer, isRef]
|
|
14623
|
+
);
|
|
14890
14624
|
React.useMemo(() => {
|
|
14891
|
-
if (isRef(container)) {
|
|
14892
|
-
|
|
14893
|
-
|
|
14894
|
-
|
|
14895
|
-
const updated = container.current;
|
|
14896
|
-
if (updated && updated !== resolvedContainer) {
|
|
14897
|
-
setResolvedContainer(updated);
|
|
14898
|
-
}
|
|
14899
|
-
});
|
|
14900
|
-
} else if (current !== resolvedContainer) {
|
|
14901
|
-
setResolvedContainer(current);
|
|
14902
|
-
}
|
|
14903
|
-
} else if (container !== resolvedContainer) {
|
|
14904
|
-
setResolvedContainer(container);
|
|
14625
|
+
if (isRef(container) && !container.current) {
|
|
14626
|
+
return queueMicrotask(() => {
|
|
14627
|
+
setResolvedContainer(container.current);
|
|
14628
|
+
});
|
|
14905
14629
|
}
|
|
14906
|
-
|
|
14630
|
+
setResolvedContainer(container);
|
|
14631
|
+
}, [container, isRef, setResolvedContainer]);
|
|
14907
14632
|
if (!resolvedContainer) return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, {});
|
|
14908
14633
|
const portalKey = resolvedContainer.uuid ?? `portal-${resolvedContainer.id ?? "unknown"}`;
|
|
14909
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
14634
|
+
return /* @__PURE__ */ jsxRuntime.jsx(PortalInner, { children, container: resolvedContainer, state }, portalKey);
|
|
14910
14635
|
}
|
|
14911
|
-
function
|
|
14636
|
+
function PortalInner({ state = {}, children, container }) {
|
|
14912
14637
|
const { events, size, injectScene = true, ...rest } = state;
|
|
14913
14638
|
const previousRoot = useStore();
|
|
14914
14639
|
const [raycaster] = React.useState(() => new webgpu.Raycaster());
|
|
@@ -14929,11 +14654,12 @@ function Portal({ state = {}, children, container }) {
|
|
|
14929
14654
|
};
|
|
14930
14655
|
}, [portalScene, container, injectScene]);
|
|
14931
14656
|
const inject = useMutableCallback((rootState, injectState) => {
|
|
14657
|
+
const resolvedSize = { ...rootState.size, ...injectState.size, ...size };
|
|
14932
14658
|
let viewport = void 0;
|
|
14933
|
-
if (injectState.camera && size) {
|
|
14659
|
+
if (injectState.camera && (size || injectState.size)) {
|
|
14934
14660
|
const camera = injectState.camera;
|
|
14935
|
-
viewport = rootState.viewport.getCurrentViewport(camera, new webgpu.Vector3(),
|
|
14936
|
-
if (camera !== rootState.camera) updateCamera(camera,
|
|
14661
|
+
viewport = rootState.viewport.getCurrentViewport(camera, new webgpu.Vector3(), resolvedSize);
|
|
14662
|
+
if (camera !== rootState.camera) updateCamera(camera, resolvedSize);
|
|
14937
14663
|
}
|
|
14938
14664
|
return {
|
|
14939
14665
|
// The intersect consists of the previous root state
|
|
@@ -14950,7 +14676,7 @@ function Portal({ state = {}, children, container }) {
|
|
|
14950
14676
|
previousRoot,
|
|
14951
14677
|
// Events, size and viewport can be overridden by the inject layer
|
|
14952
14678
|
events: { ...rootState.events, ...injectState.events, ...events },
|
|
14953
|
-
size:
|
|
14679
|
+
size: resolvedSize,
|
|
14954
14680
|
viewport: { ...rootState.viewport, ...viewport },
|
|
14955
14681
|
// Layers are allowed to override events
|
|
14956
14682
|
setEvents: (events2) => injectState.set((state2) => ({ ...state2, events: { ...state2.events, ...events2 } })),
|
|
@@ -14962,9 +14688,13 @@ function Portal({ state = {}, children, container }) {
|
|
|
14962
14688
|
const store = traditional.createWithEqualityFn((set, get) => ({ ...rest, set, get }));
|
|
14963
14689
|
const onMutate = (prev) => store.setState((state2) => inject.current(prev, state2));
|
|
14964
14690
|
onMutate(previousRoot.getState());
|
|
14965
|
-
previousRoot.subscribe(onMutate);
|
|
14966
14691
|
return store;
|
|
14967
14692
|
}, [previousRoot, container]);
|
|
14693
|
+
useIsomorphicLayoutEffect(() => {
|
|
14694
|
+
const onMutate = (prev) => usePortalStore.setState((state2) => inject.current(prev, state2));
|
|
14695
|
+
const unsubscribe = previousRoot.subscribe(onMutate);
|
|
14696
|
+
return unsubscribe;
|
|
14697
|
+
}, [previousRoot, usePortalStore]);
|
|
14968
14698
|
return (
|
|
14969
14699
|
// @ts-ignore, reconciler types are not maintained
|
|
14970
14700
|
/* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: reconciler.createPortal(
|
|
@@ -14978,21 +14708,59 @@ function flushSync(fn) {
|
|
|
14978
14708
|
return reconciler.flushSyncFromReconciler(fn);
|
|
14979
14709
|
}
|
|
14980
14710
|
|
|
14711
|
+
function parseBackground(background) {
|
|
14712
|
+
if (!background) return null;
|
|
14713
|
+
if (typeof background === "object" && !background.isColor) {
|
|
14714
|
+
const { backgroundMap, envMap, files, preset, ...rest } = background;
|
|
14715
|
+
return {
|
|
14716
|
+
...rest,
|
|
14717
|
+
preset,
|
|
14718
|
+
files: envMap || files,
|
|
14719
|
+
backgroundFiles: backgroundMap,
|
|
14720
|
+
background: true
|
|
14721
|
+
};
|
|
14722
|
+
}
|
|
14723
|
+
if (typeof background === "number") {
|
|
14724
|
+
return { color: background, background: true };
|
|
14725
|
+
}
|
|
14726
|
+
if (typeof background === "string") {
|
|
14727
|
+
if (background in presetsObj) {
|
|
14728
|
+
return { preset: background, background: true };
|
|
14729
|
+
}
|
|
14730
|
+
if (/^(https?:\/\/|\/|\.\/|\.\.\/)|\.(hdr|exr|jpg|jpeg|png|webp|gif)$/i.test(background)) {
|
|
14731
|
+
return { files: background, background: true };
|
|
14732
|
+
}
|
|
14733
|
+
return { color: background, background: true };
|
|
14734
|
+
}
|
|
14735
|
+
if (background.isColor) {
|
|
14736
|
+
return { color: background, background: true };
|
|
14737
|
+
}
|
|
14738
|
+
return null;
|
|
14739
|
+
}
|
|
14740
|
+
|
|
14741
|
+
function clearHmrCaches(store) {
|
|
14742
|
+
store.setState((state) => ({
|
|
14743
|
+
nodes: {},
|
|
14744
|
+
uniforms: {},
|
|
14745
|
+
buffers: {},
|
|
14746
|
+
gpuStorage: {},
|
|
14747
|
+
_hmrVersion: state._hmrVersion + 1
|
|
14748
|
+
}));
|
|
14749
|
+
}
|
|
14750
|
+
|
|
14981
14751
|
function CanvasImpl({
|
|
14982
14752
|
ref,
|
|
14983
14753
|
children,
|
|
14984
14754
|
fallback,
|
|
14985
14755
|
resize,
|
|
14986
14756
|
style,
|
|
14757
|
+
id,
|
|
14987
14758
|
gl,
|
|
14988
|
-
renderer,
|
|
14759
|
+
renderer: rendererProp,
|
|
14989
14760
|
events = createPointerEvents,
|
|
14990
14761
|
eventSource,
|
|
14991
14762
|
eventPrefix,
|
|
14992
14763
|
shadows,
|
|
14993
|
-
linear,
|
|
14994
|
-
flat,
|
|
14995
|
-
legacy,
|
|
14996
14764
|
orthographic,
|
|
14997
14765
|
frameloop,
|
|
14998
14766
|
dpr,
|
|
@@ -15000,14 +14768,34 @@ function CanvasImpl({
|
|
|
15000
14768
|
raycaster,
|
|
15001
14769
|
camera,
|
|
15002
14770
|
scene,
|
|
14771
|
+
autoUpdateFrustum,
|
|
14772
|
+
occlusion,
|
|
15003
14773
|
onPointerMissed,
|
|
15004
14774
|
onDragOverMissed,
|
|
15005
14775
|
onDropMissed,
|
|
15006
14776
|
onCreated,
|
|
14777
|
+
hmr,
|
|
14778
|
+
width,
|
|
14779
|
+
height,
|
|
14780
|
+
background,
|
|
14781
|
+
forceEven,
|
|
15007
14782
|
...props
|
|
15008
14783
|
}) {
|
|
14784
|
+
const isRendererConfig = typeof rendererProp === "object" && rendererProp !== null && !("render" in rendererProp) && ("primaryCanvas" in rendererProp || "scheduler" in rendererProp);
|
|
14785
|
+
let primaryCanvas;
|
|
14786
|
+
let scheduler;
|
|
14787
|
+
let renderer;
|
|
14788
|
+
if (isRendererConfig) {
|
|
14789
|
+
const { primaryCanvas: pc, scheduler: sc, ...rest } = rendererProp;
|
|
14790
|
+
primaryCanvas = pc;
|
|
14791
|
+
scheduler = sc;
|
|
14792
|
+
renderer = Object.keys(rest).length > 0 ? rest : rendererProp;
|
|
14793
|
+
} else {
|
|
14794
|
+
renderer = rendererProp;
|
|
14795
|
+
}
|
|
15009
14796
|
React__namespace.useMemo(() => extend(THREE), []);
|
|
15010
14797
|
const Bridge = useBridge();
|
|
14798
|
+
const backgroundProps = React__namespace.useMemo(() => parseBackground(background), [background]);
|
|
15011
14799
|
const hasInitialSizeRef = React__namespace.useRef(false);
|
|
15012
14800
|
const measureConfig = React__namespace.useMemo(() => {
|
|
15013
14801
|
if (!hasInitialSizeRef.current) {
|
|
@@ -15024,7 +14812,21 @@ function CanvasImpl({
|
|
|
15024
14812
|
};
|
|
15025
14813
|
}, [resize, hasInitialSizeRef.current]);
|
|
15026
14814
|
const [containerRef, containerRect] = useMeasure__default(measureConfig);
|
|
15027
|
-
|
|
14815
|
+
const effectiveSize = React__namespace.useMemo(() => {
|
|
14816
|
+
let w = width ?? containerRect.width;
|
|
14817
|
+
let h = height ?? containerRect.height;
|
|
14818
|
+
if (forceEven) {
|
|
14819
|
+
w = Math.ceil(w / 2) * 2;
|
|
14820
|
+
h = Math.ceil(h / 2) * 2;
|
|
14821
|
+
}
|
|
14822
|
+
return {
|
|
14823
|
+
width: w,
|
|
14824
|
+
height: h,
|
|
14825
|
+
top: containerRect.top,
|
|
14826
|
+
left: containerRect.left
|
|
14827
|
+
};
|
|
14828
|
+
}, [width, height, containerRect, forceEven]);
|
|
14829
|
+
if (!hasInitialSizeRef.current && effectiveSize.width > 0 && effectiveSize.height > 0) {
|
|
15028
14830
|
hasInitialSizeRef.current = true;
|
|
15029
14831
|
}
|
|
15030
14832
|
const canvasRef = React__namespace.useRef(null);
|
|
@@ -15043,7 +14845,7 @@ function CanvasImpl({
|
|
|
15043
14845
|
useIsomorphicLayoutEffect(() => {
|
|
15044
14846
|
effectActiveRef.current = true;
|
|
15045
14847
|
const canvas = canvasRef.current;
|
|
15046
|
-
if (
|
|
14848
|
+
if (effectiveSize.width > 0 && effectiveSize.height > 0 && canvas) {
|
|
15047
14849
|
if (!root.current) {
|
|
15048
14850
|
root.current = createRoot(canvas);
|
|
15049
14851
|
notifyAlpha({
|
|
@@ -15063,21 +14865,26 @@ function CanvasImpl({
|
|
|
15063
14865
|
async function run() {
|
|
15064
14866
|
if (!effectActiveRef.current || !root.current) return;
|
|
15065
14867
|
await root.current.configure({
|
|
14868
|
+
id,
|
|
14869
|
+
primaryCanvas,
|
|
14870
|
+
scheduler,
|
|
15066
14871
|
gl,
|
|
15067
14872
|
renderer,
|
|
15068
14873
|
scene,
|
|
15069
14874
|
events,
|
|
15070
14875
|
shadows,
|
|
15071
|
-
linear,
|
|
15072
|
-
flat,
|
|
15073
|
-
legacy,
|
|
15074
14876
|
orthographic,
|
|
15075
14877
|
frameloop,
|
|
15076
14878
|
dpr,
|
|
15077
14879
|
performance,
|
|
15078
14880
|
raycaster,
|
|
15079
14881
|
camera,
|
|
15080
|
-
|
|
14882
|
+
autoUpdateFrustum,
|
|
14883
|
+
occlusion,
|
|
14884
|
+
size: effectiveSize,
|
|
14885
|
+
// Store size props for reset functionality
|
|
14886
|
+
_sizeProps: width !== void 0 || height !== void 0 ? { width, height } : null,
|
|
14887
|
+
forceEven,
|
|
15081
14888
|
// Pass mutable reference to onPointerMissed so it's free to update
|
|
15082
14889
|
onPointerMissed: (...args) => handlePointerMissed.current?.(...args),
|
|
15083
14890
|
onDragOverMissed: (...args) => handleDragOverMissed.current?.(...args),
|
|
@@ -15101,7 +14908,10 @@ function CanvasImpl({
|
|
|
15101
14908
|
});
|
|
15102
14909
|
if (!effectActiveRef.current || !root.current) return;
|
|
15103
14910
|
root.current.render(
|
|
15104
|
-
/* @__PURE__ */ jsxRuntime.jsx(Bridge, { children: /* @__PURE__ */ jsxRuntime.jsx(ErrorBoundary, { set: setError, children: /* @__PURE__ */ jsxRuntime.
|
|
14911
|
+
/* @__PURE__ */ jsxRuntime.jsx(Bridge, { children: /* @__PURE__ */ jsxRuntime.jsx(ErrorBoundary, { set: setError, children: /* @__PURE__ */ jsxRuntime.jsxs(React__namespace.Suspense, { fallback: /* @__PURE__ */ jsxRuntime.jsx(Block, { set: setBlock }), children: [
|
|
14912
|
+
backgroundProps && /* @__PURE__ */ jsxRuntime.jsx(Environment, { ...backgroundProps }),
|
|
14913
|
+
children ?? null
|
|
14914
|
+
] }) }) })
|
|
15105
14915
|
);
|
|
15106
14916
|
}
|
|
15107
14917
|
run();
|
|
@@ -15122,7 +14932,29 @@ function CanvasImpl({
|
|
|
15122
14932
|
root.current = null;
|
|
15123
14933
|
};
|
|
15124
14934
|
}
|
|
15125
|
-
}, []);
|
|
14935
|
+
}, []);
|
|
14936
|
+
React__namespace.useEffect(() => {
|
|
14937
|
+
if (hmr === false) return;
|
|
14938
|
+
const canvas = canvasRef.current;
|
|
14939
|
+
if (!canvas) return;
|
|
14940
|
+
const handleHMR = () => {
|
|
14941
|
+
queueMicrotask(() => {
|
|
14942
|
+
const rootEntry = _roots.get(canvas);
|
|
14943
|
+
if (rootEntry?.store) clearHmrCaches(rootEntry.store);
|
|
14944
|
+
});
|
|
14945
|
+
};
|
|
14946
|
+
if (typeof ({ url: (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)) }) !== "undefined" && undefined) {
|
|
14947
|
+
const hot = undefined;
|
|
14948
|
+
hot.on("vite:afterUpdate", handleHMR);
|
|
14949
|
+
return () => hot.off?.("vite:afterUpdate", handleHMR);
|
|
14950
|
+
}
|
|
14951
|
+
if (typeof module !== "undefined" && module.hot) {
|
|
14952
|
+
const hot = module.hot;
|
|
14953
|
+
hot.addStatusHandler((status) => {
|
|
14954
|
+
if (status === "idle") handleHMR();
|
|
14955
|
+
});
|
|
14956
|
+
}
|
|
14957
|
+
}, [hmr]);
|
|
15126
14958
|
const pointerEvents = eventSource ? "none" : "auto";
|
|
15127
14959
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
15128
14960
|
"div",
|
|
@@ -15137,7 +14969,16 @@ function CanvasImpl({
|
|
|
15137
14969
|
...style
|
|
15138
14970
|
},
|
|
15139
14971
|
...props,
|
|
15140
|
-
children: /* @__PURE__ */ jsxRuntime.jsx("div", { ref: containerRef, className: "r3f-canvas-container", style: { width: "100%", height: "100%" }, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
14972
|
+
children: /* @__PURE__ */ jsxRuntime.jsx("div", { ref: containerRef, className: "r3f-canvas-container", style: { width: "100%", height: "100%" }, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
14973
|
+
"canvas",
|
|
14974
|
+
{
|
|
14975
|
+
ref: canvasRef,
|
|
14976
|
+
id,
|
|
14977
|
+
className: "r3f-canvas",
|
|
14978
|
+
style: { display: "block", width: "100%", height: "100%" },
|
|
14979
|
+
children: fallback
|
|
14980
|
+
}
|
|
14981
|
+
) })
|
|
15141
14982
|
}
|
|
15142
14983
|
);
|
|
15143
14984
|
}
|
|
@@ -15145,6 +14986,114 @@ function Canvas(props) {
|
|
|
15145
14986
|
return /* @__PURE__ */ jsxRuntime.jsx(itsFine.FiberProvider, { children: /* @__PURE__ */ jsxRuntime.jsx(CanvasImpl, { ...props }) });
|
|
15146
14987
|
}
|
|
15147
14988
|
|
|
14989
|
+
var __defProp = Object.defineProperty;
|
|
14990
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
14991
|
+
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
14992
|
+
var _a;
|
|
14993
|
+
const INTERNAL_DATA = Symbol("ScopedStore.data");
|
|
14994
|
+
_a = INTERNAL_DATA;
|
|
14995
|
+
const _ScopedStore = class _ScopedStore {
|
|
14996
|
+
constructor(data) {
|
|
14997
|
+
__publicField(this, _a);
|
|
14998
|
+
this[INTERNAL_DATA] = data;
|
|
14999
|
+
return new Proxy(this, {
|
|
15000
|
+
get(target, prop, receiver) {
|
|
15001
|
+
if (typeof prop === "string") {
|
|
15002
|
+
if (prop === "scope" || prop === "has" || prop === "keys") {
|
|
15003
|
+
return Reflect.get(target, prop, receiver);
|
|
15004
|
+
}
|
|
15005
|
+
return target[INTERNAL_DATA][prop];
|
|
15006
|
+
}
|
|
15007
|
+
return Reflect.get(target, prop, receiver);
|
|
15008
|
+
},
|
|
15009
|
+
has(target, prop) {
|
|
15010
|
+
return typeof prop === "string" ? prop in target[INTERNAL_DATA] : Reflect.has(target, prop);
|
|
15011
|
+
},
|
|
15012
|
+
ownKeys(target) {
|
|
15013
|
+
return Reflect.ownKeys(target[INTERNAL_DATA]);
|
|
15014
|
+
},
|
|
15015
|
+
getOwnPropertyDescriptor(target, prop) {
|
|
15016
|
+
if (typeof prop === "string" && prop in target[INTERNAL_DATA]) {
|
|
15017
|
+
return {
|
|
15018
|
+
configurable: true,
|
|
15019
|
+
enumerable: true,
|
|
15020
|
+
value: target[INTERNAL_DATA][prop]
|
|
15021
|
+
};
|
|
15022
|
+
}
|
|
15023
|
+
return void 0;
|
|
15024
|
+
}
|
|
15025
|
+
});
|
|
15026
|
+
}
|
|
15027
|
+
/**
|
|
15028
|
+
* Access a nested scope by key.
|
|
15029
|
+
* If the key doesn't exist or isn't a scope object, returns an empty ScopedStore.
|
|
15030
|
+
*/
|
|
15031
|
+
scope(key) {
|
|
15032
|
+
const data = this[INTERNAL_DATA][key];
|
|
15033
|
+
return new _ScopedStore(
|
|
15034
|
+
data && typeof data === "object" ? data : {}
|
|
15035
|
+
);
|
|
15036
|
+
}
|
|
15037
|
+
/**
|
|
15038
|
+
* Check if a key exists in the store.
|
|
15039
|
+
*/
|
|
15040
|
+
has(key) {
|
|
15041
|
+
return key in this[INTERNAL_DATA];
|
|
15042
|
+
}
|
|
15043
|
+
/**
|
|
15044
|
+
* Get all keys in the store.
|
|
15045
|
+
*/
|
|
15046
|
+
keys() {
|
|
15047
|
+
return Object.keys(this[INTERNAL_DATA]);
|
|
15048
|
+
}
|
|
15049
|
+
};
|
|
15050
|
+
let ScopedStore = _ScopedStore;
|
|
15051
|
+
function createScopedStore(data) {
|
|
15052
|
+
return new ScopedStore(data);
|
|
15053
|
+
}
|
|
15054
|
+
function createLazyCreatorState(state) {
|
|
15055
|
+
let _uniforms = null;
|
|
15056
|
+
let _nodes = null;
|
|
15057
|
+
let _buffers = null;
|
|
15058
|
+
let _gpuStorage = null;
|
|
15059
|
+
return Object.create(state, {
|
|
15060
|
+
uniforms: {
|
|
15061
|
+
get() {
|
|
15062
|
+
return _uniforms ?? (_uniforms = createScopedStore(state.uniforms));
|
|
15063
|
+
}
|
|
15064
|
+
},
|
|
15065
|
+
nodes: {
|
|
15066
|
+
get() {
|
|
15067
|
+
return _nodes ?? (_nodes = createScopedStore(state.nodes));
|
|
15068
|
+
}
|
|
15069
|
+
},
|
|
15070
|
+
buffers: {
|
|
15071
|
+
get() {
|
|
15072
|
+
return _buffers ?? (_buffers = createScopedStore(state.buffers));
|
|
15073
|
+
}
|
|
15074
|
+
},
|
|
15075
|
+
gpuStorage: {
|
|
15076
|
+
get() {
|
|
15077
|
+
return _gpuStorage ?? (_gpuStorage = createScopedStore(state.gpuStorage));
|
|
15078
|
+
}
|
|
15079
|
+
}
|
|
15080
|
+
});
|
|
15081
|
+
}
|
|
15082
|
+
|
|
15083
|
+
function resolvePrimary(local) {
|
|
15084
|
+
return local.getState().primaryStore ?? local;
|
|
15085
|
+
}
|
|
15086
|
+
function usePrimaryStore() {
|
|
15087
|
+
const local = useStore();
|
|
15088
|
+
const primary = local.getState().primaryStore;
|
|
15089
|
+
return React.useMemo(() => primary ?? local, [primary, local]);
|
|
15090
|
+
}
|
|
15091
|
+
function usePrimaryThree(selector = (state) => state, equalityFn) {
|
|
15092
|
+
const local = useStore();
|
|
15093
|
+
const primary = resolvePrimary(local);
|
|
15094
|
+
return primary(selector, equalityFn);
|
|
15095
|
+
}
|
|
15096
|
+
|
|
15148
15097
|
function addTexture(set, key, value) {
|
|
15149
15098
|
set((state) => {
|
|
15150
15099
|
const newMap = new Map(state.textures);
|
|
@@ -15184,6 +15133,27 @@ function createTextureOperations(set) {
|
|
|
15184
15133
|
removeMultiple: (keys) => removeTextures(set, keys)
|
|
15185
15134
|
};
|
|
15186
15135
|
}
|
|
15136
|
+
function extractTSLValue(value) {
|
|
15137
|
+
if (value === null || value === void 0) return value;
|
|
15138
|
+
if (typeof value !== "object") return value;
|
|
15139
|
+
const node = value;
|
|
15140
|
+
if (!node.isNode) return value;
|
|
15141
|
+
if (node.isConstNode) {
|
|
15142
|
+
return node.value;
|
|
15143
|
+
}
|
|
15144
|
+
if ("value" in node) {
|
|
15145
|
+
let extractedValue = node.value;
|
|
15146
|
+
if (typeof node.traverse === "function") {
|
|
15147
|
+
node.traverse((n) => {
|
|
15148
|
+
if (n.isConstNode) {
|
|
15149
|
+
extractedValue = n.value;
|
|
15150
|
+
}
|
|
15151
|
+
});
|
|
15152
|
+
}
|
|
15153
|
+
return extractedValue;
|
|
15154
|
+
}
|
|
15155
|
+
return value;
|
|
15156
|
+
}
|
|
15187
15157
|
function vectorize(inObject) {
|
|
15188
15158
|
if (inObject === null || inObject === void 0) return inObject;
|
|
15189
15159
|
if (typeof inObject === "string") {
|
|
@@ -15196,9 +15166,16 @@ function vectorize(inObject) {
|
|
|
15196
15166
|
}
|
|
15197
15167
|
if (typeof inObject !== "object") return inObject;
|
|
15198
15168
|
const obj = inObject;
|
|
15169
|
+
if (obj.isNode) {
|
|
15170
|
+
return extractTSLValue(inObject);
|
|
15171
|
+
}
|
|
15199
15172
|
if (obj.isVector2 || obj.isVector3 || obj.isVector4) return inObject;
|
|
15200
15173
|
if (obj.isMatrix3 || obj.isMatrix4) return inObject;
|
|
15201
15174
|
if (obj.isColor || obj.isEuler || obj.isQuaternion || obj.isSpherical) return inObject;
|
|
15175
|
+
if ("r" in obj && "g" in obj && "b" in obj && typeof obj.r === "number" && typeof obj.g === "number" && typeof obj.b === "number") {
|
|
15176
|
+
const scale = obj.r > 1 || obj.g > 1 || obj.b > 1 ? 1 / 255 : 1;
|
|
15177
|
+
return new webgpu.Color(obj.r * scale, obj.g * scale, obj.b * scale);
|
|
15178
|
+
}
|
|
15202
15179
|
if ("x" in obj && "y" in obj && typeof obj.x === "number" && typeof obj.y === "number") {
|
|
15203
15180
|
if ("w" in obj && typeof obj.w === "number" && "z" in obj && typeof obj.z === "number") {
|
|
15204
15181
|
return new webgpu.Vector4(obj.x, obj.y, obj.z, obj.w);
|
|
@@ -15222,7 +15199,7 @@ function useCompareMemoize(value, deep) {
|
|
|
15222
15199
|
|
|
15223
15200
|
const isUniformNode$1 = (value) => value !== null && typeof value === "object" && "value" in value && "uuid" in value;
|
|
15224
15201
|
function useUniforms(creatorOrScope, scope) {
|
|
15225
|
-
const store =
|
|
15202
|
+
const store = usePrimaryStore();
|
|
15226
15203
|
const removeUniforms2 = React.useCallback(
|
|
15227
15204
|
(names, targetScope) => {
|
|
15228
15205
|
const nameArray = Array.isArray(names) ? names : [names];
|
|
@@ -15258,21 +15235,55 @@ function useUniforms(creatorOrScope, scope) {
|
|
|
15258
15235
|
},
|
|
15259
15236
|
[store]
|
|
15260
15237
|
);
|
|
15238
|
+
const rebuildUniforms = React.useCallback(
|
|
15239
|
+
(targetScope) => {
|
|
15240
|
+
store.setState((state) => {
|
|
15241
|
+
let newUniforms = {};
|
|
15242
|
+
if (targetScope && targetScope !== "root") {
|
|
15243
|
+
const { [targetScope]: _, ...rest } = state.uniforms;
|
|
15244
|
+
newUniforms = rest;
|
|
15245
|
+
} else if (targetScope === "root") {
|
|
15246
|
+
for (const [key, value] of Object.entries(state.uniforms)) {
|
|
15247
|
+
if (!isUniformNode$1(value)) newUniforms[key] = value;
|
|
15248
|
+
}
|
|
15249
|
+
}
|
|
15250
|
+
return { uniforms: newUniforms, _hmrVersion: state._hmrVersion + 1 };
|
|
15251
|
+
});
|
|
15252
|
+
},
|
|
15253
|
+
[store]
|
|
15254
|
+
);
|
|
15261
15255
|
const inputForMemoization = React.useMemo(() => {
|
|
15262
|
-
|
|
15256
|
+
let raw = creatorOrScope;
|
|
15257
|
+
if (is.fun(creatorOrScope)) {
|
|
15258
|
+
const wrappedState = createLazyCreatorState(store.getState());
|
|
15259
|
+
raw = creatorOrScope(wrappedState);
|
|
15260
|
+
}
|
|
15261
|
+
if (raw && typeof raw === "object" && !Array.isArray(raw)) {
|
|
15262
|
+
const normalized = {};
|
|
15263
|
+
for (const [key, value] of Object.entries(raw)) {
|
|
15264
|
+
normalized[key] = vectorize(value);
|
|
15265
|
+
}
|
|
15266
|
+
return normalized;
|
|
15267
|
+
}
|
|
15268
|
+
return raw;
|
|
15263
15269
|
}, [creatorOrScope, store]);
|
|
15264
15270
|
const memoizedInput = useCompareMemoize(inputForMemoization);
|
|
15271
|
+
const isReader = memoizedInput === void 0 || typeof memoizedInput === "string";
|
|
15272
|
+
const storeUniforms = usePrimaryThree((s) => s.uniforms);
|
|
15273
|
+
const hmrVersion = usePrimaryThree((s) => s._hmrVersion);
|
|
15274
|
+
const readerDep = isReader ? storeUniforms : null;
|
|
15275
|
+
const creatorDep = isReader ? null : hmrVersion;
|
|
15265
15276
|
const uniforms = React.useMemo(() => {
|
|
15266
|
-
const state = store.getState();
|
|
15267
|
-
const set = store.setState;
|
|
15268
15277
|
if (memoizedInput === void 0) {
|
|
15269
|
-
return
|
|
15278
|
+
return storeUniforms;
|
|
15270
15279
|
}
|
|
15271
15280
|
if (typeof memoizedInput === "string") {
|
|
15272
|
-
const scopeData =
|
|
15281
|
+
const scopeData = storeUniforms[memoizedInput];
|
|
15273
15282
|
if (scopeData && !isUniformNode$1(scopeData)) return scopeData;
|
|
15274
15283
|
return {};
|
|
15275
15284
|
}
|
|
15285
|
+
const state = store.getState();
|
|
15286
|
+
const set = store.setState;
|
|
15276
15287
|
if (typeof memoizedInput !== "object" || memoizedInput === null) {
|
|
15277
15288
|
throw new Error("Invalid uniform input");
|
|
15278
15289
|
}
|
|
@@ -15316,8 +15327,23 @@ function useUniforms(creatorOrScope, scope) {
|
|
|
15316
15327
|
}
|
|
15317
15328
|
}
|
|
15318
15329
|
return result;
|
|
15319
|
-
}, [store, memoizedInput, scope]);
|
|
15320
|
-
return { ...uniforms, removeUniforms: removeUniforms2, clearUniforms };
|
|
15330
|
+
}, [store, memoizedInput, scope, readerDep, creatorDep]);
|
|
15331
|
+
return { ...uniforms, removeUniforms: removeUniforms2, clearUniforms, rebuildUniforms };
|
|
15332
|
+
}
|
|
15333
|
+
function rebuildAllUniforms(store, scope) {
|
|
15334
|
+
store = store.getState().primaryStore ?? store;
|
|
15335
|
+
store.setState((state) => {
|
|
15336
|
+
let newUniforms = {};
|
|
15337
|
+
if (scope && scope !== "root") {
|
|
15338
|
+
const { [scope]: _, ...rest } = state.uniforms;
|
|
15339
|
+
newUniforms = rest;
|
|
15340
|
+
} else if (scope === "root") {
|
|
15341
|
+
for (const [key, value] of Object.entries(state.uniforms)) {
|
|
15342
|
+
if (!isUniformNode$1(value)) newUniforms[key] = value;
|
|
15343
|
+
}
|
|
15344
|
+
}
|
|
15345
|
+
return { uniforms: newUniforms, _hmrVersion: state._hmrVersion + 1 };
|
|
15346
|
+
});
|
|
15321
15347
|
}
|
|
15322
15348
|
function removeUniforms(set, names, scope) {
|
|
15323
15349
|
set((state) => {
|
|
@@ -15382,15 +15408,17 @@ function isSameThreeType(a, b) {
|
|
|
15382
15408
|
}
|
|
15383
15409
|
|
|
15384
15410
|
const isUniformNode = (value) => value !== null && typeof value === "object" && "value" in value && "uuid" in value;
|
|
15411
|
+
const isTSLNode$1 = (value) => value !== null && typeof value === "object" && "uuid" in value && "nodeType" in value;
|
|
15385
15412
|
function useUniform(name, value) {
|
|
15386
15413
|
const store = useStore();
|
|
15414
|
+
const hmrVersion = useThree((s) => s._hmrVersion);
|
|
15387
15415
|
return React.useMemo(() => {
|
|
15388
15416
|
const state = store.getState();
|
|
15389
15417
|
const set = store.setState;
|
|
15390
15418
|
const existing = state.uniforms[name];
|
|
15391
15419
|
if (existing && isUniformNode(existing)) {
|
|
15392
|
-
if (value !== void 0) {
|
|
15393
|
-
existing.value = value;
|
|
15420
|
+
if (value !== void 0 && !isTSLNode$1(value) && !isUniformNode(value)) {
|
|
15421
|
+
existing.value = typeof value === "string" ? new webgpu.Color(value) : value;
|
|
15394
15422
|
}
|
|
15395
15423
|
return existing;
|
|
15396
15424
|
}
|
|
@@ -15399,7 +15427,24 @@ function useUniform(name, value) {
|
|
|
15399
15427
|
`[useUniform] Uniform "${name}" not found. Create it first with: useUniform('${name}', initialValue)`
|
|
15400
15428
|
);
|
|
15401
15429
|
}
|
|
15402
|
-
|
|
15430
|
+
if (isUniformNode(value)) {
|
|
15431
|
+
const node2 = value;
|
|
15432
|
+
if (typeof node2.setName === "function") {
|
|
15433
|
+
node2.setName(name);
|
|
15434
|
+
}
|
|
15435
|
+
set((s) => ({
|
|
15436
|
+
uniforms: { ...s.uniforms, [name]: node2 }
|
|
15437
|
+
}));
|
|
15438
|
+
return node2;
|
|
15439
|
+
}
|
|
15440
|
+
let node;
|
|
15441
|
+
if (isTSLNode$1(value)) {
|
|
15442
|
+
node = tsl.uniform(value);
|
|
15443
|
+
} else if (typeof value === "string") {
|
|
15444
|
+
node = tsl.uniform(new webgpu.Color(value));
|
|
15445
|
+
} else {
|
|
15446
|
+
node = tsl.uniform(value);
|
|
15447
|
+
}
|
|
15403
15448
|
if (typeof node.setName === "function") {
|
|
15404
15449
|
node.setName(name);
|
|
15405
15450
|
}
|
|
@@ -15410,12 +15455,12 @@ function useUniform(name, value) {
|
|
|
15410
15455
|
}
|
|
15411
15456
|
}));
|
|
15412
15457
|
return node;
|
|
15413
|
-
}, [store, name]);
|
|
15458
|
+
}, [store, name, hmrVersion]);
|
|
15414
15459
|
}
|
|
15415
15460
|
|
|
15416
15461
|
const isTSLNode = (value) => value !== null && typeof value === "object" && ("uuid" in value || "nodeType" in value);
|
|
15417
15462
|
function useNodes(creatorOrScope, scope) {
|
|
15418
|
-
const store =
|
|
15463
|
+
const store = usePrimaryStore();
|
|
15419
15464
|
const removeNodes2 = React.useCallback(
|
|
15420
15465
|
(names, targetScope) => {
|
|
15421
15466
|
const nameArray = Array.isArray(names) ? names : [names];
|
|
@@ -15451,19 +15496,46 @@ function useNodes(creatorOrScope, scope) {
|
|
|
15451
15496
|
},
|
|
15452
15497
|
[store]
|
|
15453
15498
|
);
|
|
15499
|
+
const rebuildNodes = React.useCallback(
|
|
15500
|
+
(targetScope) => {
|
|
15501
|
+
store.setState((state) => {
|
|
15502
|
+
let newNodes = state.nodes;
|
|
15503
|
+
if (targetScope && targetScope !== "root") {
|
|
15504
|
+
const { [targetScope]: _, ...rest } = state.nodes;
|
|
15505
|
+
newNodes = rest;
|
|
15506
|
+
} else if (targetScope === "root") {
|
|
15507
|
+
newNodes = {};
|
|
15508
|
+
for (const [key, value] of Object.entries(state.nodes)) {
|
|
15509
|
+
if (!isTSLNode(value)) newNodes[key] = value;
|
|
15510
|
+
}
|
|
15511
|
+
} else {
|
|
15512
|
+
newNodes = {};
|
|
15513
|
+
}
|
|
15514
|
+
return { nodes: newNodes, _hmrVersion: state._hmrVersion + 1 };
|
|
15515
|
+
});
|
|
15516
|
+
},
|
|
15517
|
+
[store]
|
|
15518
|
+
);
|
|
15519
|
+
const isReader = creatorOrScope === void 0 || typeof creatorOrScope === "string";
|
|
15520
|
+
const storeNodes = usePrimaryThree((s) => s.nodes);
|
|
15521
|
+
const hmrVersion = usePrimaryThree((s) => s._hmrVersion);
|
|
15522
|
+
const scopeDep = typeof creatorOrScope === "string" ? creatorOrScope : scope;
|
|
15523
|
+
const readerDep = isReader ? storeNodes : null;
|
|
15524
|
+
const creatorDep = isReader ? null : hmrVersion;
|
|
15454
15525
|
const nodes = React.useMemo(() => {
|
|
15455
|
-
const state = store.getState();
|
|
15456
|
-
const set = store.setState;
|
|
15457
15526
|
if (creatorOrScope === void 0) {
|
|
15458
|
-
return
|
|
15527
|
+
return storeNodes;
|
|
15459
15528
|
}
|
|
15460
15529
|
if (typeof creatorOrScope === "string") {
|
|
15461
|
-
const scopeData =
|
|
15530
|
+
const scopeData = storeNodes[creatorOrScope];
|
|
15462
15531
|
if (scopeData && !isTSLNode(scopeData)) return scopeData;
|
|
15463
15532
|
return {};
|
|
15464
15533
|
}
|
|
15534
|
+
const state = store.getState();
|
|
15535
|
+
const set = store.setState;
|
|
15465
15536
|
const creator = creatorOrScope;
|
|
15466
|
-
const
|
|
15537
|
+
const wrappedState = createLazyCreatorState(state);
|
|
15538
|
+
const created = creator(wrappedState);
|
|
15467
15539
|
const result = {};
|
|
15468
15540
|
let hasNewNodes = false;
|
|
15469
15541
|
if (scope) {
|
|
@@ -15472,7 +15544,7 @@ function useNodes(creatorOrScope, scope) {
|
|
|
15472
15544
|
if (currentScope[name]) {
|
|
15473
15545
|
result[name] = currentScope[name];
|
|
15474
15546
|
} else {
|
|
15475
|
-
|
|
15547
|
+
node.setName?.(`${scope}.${name}`);
|
|
15476
15548
|
result[name] = node;
|
|
15477
15549
|
hasNewNodes = true;
|
|
15478
15550
|
}
|
|
@@ -15492,7 +15564,7 @@ function useNodes(creatorOrScope, scope) {
|
|
|
15492
15564
|
if (existing && isTSLNode(existing)) {
|
|
15493
15565
|
result[name] = existing;
|
|
15494
15566
|
} else {
|
|
15495
|
-
|
|
15567
|
+
node.setName?.(name);
|
|
15496
15568
|
result[name] = node;
|
|
15497
15569
|
hasNewNodes = true;
|
|
15498
15570
|
}
|
|
@@ -15501,8 +15573,26 @@ function useNodes(creatorOrScope, scope) {
|
|
|
15501
15573
|
set((s) => ({ nodes: { ...s.nodes, ...result } }));
|
|
15502
15574
|
}
|
|
15503
15575
|
return result;
|
|
15504
|
-
}, [store,
|
|
15505
|
-
return { ...nodes, removeNodes: removeNodes2, clearNodes };
|
|
15576
|
+
}, [store, scopeDep, readerDep, creatorDep]);
|
|
15577
|
+
return { ...nodes, removeNodes: removeNodes2, clearNodes, rebuildNodes };
|
|
15578
|
+
}
|
|
15579
|
+
function rebuildAllNodes(store, scope) {
|
|
15580
|
+
store = store.getState().primaryStore ?? store;
|
|
15581
|
+
store.setState((state) => {
|
|
15582
|
+
let newNodes = state.nodes;
|
|
15583
|
+
if (scope && scope !== "root") {
|
|
15584
|
+
const { [scope]: _, ...rest } = state.nodes;
|
|
15585
|
+
newNodes = rest;
|
|
15586
|
+
} else if (scope === "root") {
|
|
15587
|
+
newNodes = {};
|
|
15588
|
+
for (const [key, value] of Object.entries(state.nodes)) {
|
|
15589
|
+
if (!isTSLNode(value)) newNodes[key] = value;
|
|
15590
|
+
}
|
|
15591
|
+
} else {
|
|
15592
|
+
newNodes = {};
|
|
15593
|
+
}
|
|
15594
|
+
return { nodes: newNodes, _hmrVersion: state._hmrVersion + 1 };
|
|
15595
|
+
});
|
|
15506
15596
|
}
|
|
15507
15597
|
function removeNodes(set, names, scope) {
|
|
15508
15598
|
set((state) => {
|
|
@@ -15532,17 +15622,364 @@ function clearRootNodes(set) {
|
|
|
15532
15622
|
});
|
|
15533
15623
|
}
|
|
15534
15624
|
function useLocalNodes(creator) {
|
|
15535
|
-
const store =
|
|
15536
|
-
const uniforms =
|
|
15537
|
-
const nodes =
|
|
15538
|
-
const textures =
|
|
15625
|
+
const store = usePrimaryStore();
|
|
15626
|
+
const uniforms = usePrimaryThree((s) => s.uniforms);
|
|
15627
|
+
const nodes = usePrimaryThree((s) => s.nodes);
|
|
15628
|
+
const textures = usePrimaryThree((s) => s.textures);
|
|
15629
|
+
const hmrVersion = usePrimaryThree((s) => s._hmrVersion);
|
|
15539
15630
|
return React.useMemo(() => {
|
|
15631
|
+
const wrappedState = createLazyCreatorState(store.getState());
|
|
15632
|
+
return creator(wrappedState);
|
|
15633
|
+
}, [store, creator, uniforms, nodes, textures, hmrVersion]);
|
|
15634
|
+
}
|
|
15635
|
+
|
|
15636
|
+
const isBufferLike = (value) => {
|
|
15637
|
+
if (value === null || typeof value !== "object") return false;
|
|
15638
|
+
if (ArrayBuffer.isView(value)) return true;
|
|
15639
|
+
if ("isBufferAttribute" in value) return true;
|
|
15640
|
+
if ("uuid" in value || "nodeType" in value) return true;
|
|
15641
|
+
return false;
|
|
15642
|
+
};
|
|
15643
|
+
const disposeBuffer = (buffer) => {
|
|
15644
|
+
if (buffer === null || typeof buffer !== "object") return;
|
|
15645
|
+
if ("dispose" in buffer && typeof buffer.dispose === "function") {
|
|
15646
|
+
buffer.dispose();
|
|
15647
|
+
}
|
|
15648
|
+
};
|
|
15649
|
+
function useBuffers(creatorOrScope, scope) {
|
|
15650
|
+
const store = usePrimaryStore();
|
|
15651
|
+
const removeBuffers = React.useCallback(
|
|
15652
|
+
(names, targetScope) => {
|
|
15653
|
+
const nameArray = Array.isArray(names) ? names : [names];
|
|
15654
|
+
store.setState((state) => {
|
|
15655
|
+
if (targetScope) {
|
|
15656
|
+
const currentScope = { ...state.buffers[targetScope] };
|
|
15657
|
+
for (const name of nameArray) delete currentScope[name];
|
|
15658
|
+
return { buffers: { ...state.buffers, [targetScope]: currentScope } };
|
|
15659
|
+
}
|
|
15660
|
+
const buffers2 = { ...state.buffers };
|
|
15661
|
+
for (const name of nameArray) if (isBufferLike(buffers2[name])) delete buffers2[name];
|
|
15662
|
+
return { buffers: buffers2 };
|
|
15663
|
+
});
|
|
15664
|
+
},
|
|
15665
|
+
[store]
|
|
15666
|
+
);
|
|
15667
|
+
const clearBuffers = React.useCallback(
|
|
15668
|
+
(targetScope) => {
|
|
15669
|
+
store.setState((state) => {
|
|
15670
|
+
if (targetScope && targetScope !== "root") {
|
|
15671
|
+
const { [targetScope]: _, ...rest } = state.buffers;
|
|
15672
|
+
return { buffers: rest };
|
|
15673
|
+
}
|
|
15674
|
+
if (targetScope === "root") {
|
|
15675
|
+
const buffers2 = {};
|
|
15676
|
+
for (const [key, value] of Object.entries(state.buffers)) {
|
|
15677
|
+
if (!isBufferLike(value)) buffers2[key] = value;
|
|
15678
|
+
}
|
|
15679
|
+
return { buffers: buffers2 };
|
|
15680
|
+
}
|
|
15681
|
+
return { buffers: {} };
|
|
15682
|
+
});
|
|
15683
|
+
},
|
|
15684
|
+
[store]
|
|
15685
|
+
);
|
|
15686
|
+
const rebuildBuffers = React.useCallback(
|
|
15687
|
+
(targetScope) => {
|
|
15688
|
+
store.setState((state) => {
|
|
15689
|
+
let newBuffers = state.buffers;
|
|
15690
|
+
if (targetScope && targetScope !== "root") {
|
|
15691
|
+
const { [targetScope]: _, ...rest } = state.buffers;
|
|
15692
|
+
newBuffers = rest;
|
|
15693
|
+
} else if (targetScope === "root") {
|
|
15694
|
+
newBuffers = {};
|
|
15695
|
+
for (const [key, value] of Object.entries(state.buffers)) {
|
|
15696
|
+
if (!isBufferLike(value)) newBuffers[key] = value;
|
|
15697
|
+
}
|
|
15698
|
+
} else {
|
|
15699
|
+
newBuffers = {};
|
|
15700
|
+
}
|
|
15701
|
+
return { buffers: newBuffers, _hmrVersion: state._hmrVersion + 1 };
|
|
15702
|
+
});
|
|
15703
|
+
},
|
|
15704
|
+
[store]
|
|
15705
|
+
);
|
|
15706
|
+
const disposeBuffers = React.useCallback(
|
|
15707
|
+
(names, targetScope) => {
|
|
15708
|
+
const nameArray = Array.isArray(names) ? names : [names];
|
|
15709
|
+
const state = store.getState();
|
|
15710
|
+
for (const name of nameArray) {
|
|
15711
|
+
const buffer = targetScope ? state.buffers[targetScope]?.[name] : state.buffers[name];
|
|
15712
|
+
if (buffer && isBufferLike(buffer)) {
|
|
15713
|
+
disposeBuffer(buffer);
|
|
15714
|
+
}
|
|
15715
|
+
}
|
|
15716
|
+
removeBuffers(names, targetScope);
|
|
15717
|
+
},
|
|
15718
|
+
[store, removeBuffers]
|
|
15719
|
+
);
|
|
15720
|
+
const isReader = creatorOrScope === void 0 || typeof creatorOrScope === "string";
|
|
15721
|
+
const storeBuffers = usePrimaryThree((s) => s.buffers);
|
|
15722
|
+
const hmrVersion = usePrimaryThree((s) => s._hmrVersion);
|
|
15723
|
+
const scopeDep = typeof creatorOrScope === "string" ? creatorOrScope : scope;
|
|
15724
|
+
const readerDep = isReader ? storeBuffers : null;
|
|
15725
|
+
const creatorDep = isReader ? null : hmrVersion;
|
|
15726
|
+
const buffers = React.useMemo(() => {
|
|
15727
|
+
if (creatorOrScope === void 0) {
|
|
15728
|
+
return storeBuffers;
|
|
15729
|
+
}
|
|
15730
|
+
if (typeof creatorOrScope === "string") {
|
|
15731
|
+
const scopeData = storeBuffers[creatorOrScope];
|
|
15732
|
+
if (scopeData && !isBufferLike(scopeData)) return scopeData;
|
|
15733
|
+
return {};
|
|
15734
|
+
}
|
|
15735
|
+
const state = store.getState();
|
|
15736
|
+
const set = store.setState;
|
|
15737
|
+
const creator = creatorOrScope;
|
|
15738
|
+
const wrappedState = createLazyCreatorState(state);
|
|
15739
|
+
const created = creator(wrappedState);
|
|
15740
|
+
const result = {};
|
|
15741
|
+
let hasNewBuffers = false;
|
|
15742
|
+
if (scope) {
|
|
15743
|
+
const currentScope = state.buffers[scope] ?? {};
|
|
15744
|
+
for (const [name, buffer] of Object.entries(created)) {
|
|
15745
|
+
if (currentScope[name]) {
|
|
15746
|
+
result[name] = currentScope[name];
|
|
15747
|
+
} else {
|
|
15748
|
+
if ("setName" in buffer && typeof buffer.setName === "function") {
|
|
15749
|
+
buffer.setName(`${scope}.${name}`);
|
|
15750
|
+
}
|
|
15751
|
+
result[name] = buffer;
|
|
15752
|
+
hasNewBuffers = true;
|
|
15753
|
+
}
|
|
15754
|
+
}
|
|
15755
|
+
if (hasNewBuffers) {
|
|
15756
|
+
set((s) => ({
|
|
15757
|
+
buffers: {
|
|
15758
|
+
...s.buffers,
|
|
15759
|
+
[scope]: { ...s.buffers[scope], ...result }
|
|
15760
|
+
}
|
|
15761
|
+
}));
|
|
15762
|
+
}
|
|
15763
|
+
return result;
|
|
15764
|
+
}
|
|
15765
|
+
for (const [name, buffer] of Object.entries(created)) {
|
|
15766
|
+
const existing = state.buffers[name];
|
|
15767
|
+
if (existing && isBufferLike(existing)) {
|
|
15768
|
+
result[name] = existing;
|
|
15769
|
+
} else {
|
|
15770
|
+
if ("setName" in buffer && typeof buffer.setName === "function") {
|
|
15771
|
+
buffer.setName(name);
|
|
15772
|
+
}
|
|
15773
|
+
result[name] = buffer;
|
|
15774
|
+
hasNewBuffers = true;
|
|
15775
|
+
}
|
|
15776
|
+
}
|
|
15777
|
+
if (hasNewBuffers) {
|
|
15778
|
+
set((s) => ({ buffers: { ...s.buffers, ...result } }));
|
|
15779
|
+
}
|
|
15780
|
+
return result;
|
|
15781
|
+
}, [store, scopeDep, readerDep, creatorDep]);
|
|
15782
|
+
return { ...buffers, removeBuffers, clearBuffers, rebuildBuffers, disposeBuffers };
|
|
15783
|
+
}
|
|
15784
|
+
function rebuildAllBuffers(store, scope) {
|
|
15785
|
+
store = store.getState().primaryStore ?? store;
|
|
15786
|
+
store.setState((state) => {
|
|
15787
|
+
let newBuffers = state.buffers;
|
|
15788
|
+
if (scope && scope !== "root") {
|
|
15789
|
+
const { [scope]: _, ...rest } = state.buffers;
|
|
15790
|
+
newBuffers = rest;
|
|
15791
|
+
} else if (scope === "root") {
|
|
15792
|
+
newBuffers = {};
|
|
15793
|
+
for (const [key, value] of Object.entries(state.buffers)) {
|
|
15794
|
+
if (!isBufferLike(value)) newBuffers[key] = value;
|
|
15795
|
+
}
|
|
15796
|
+
} else {
|
|
15797
|
+
newBuffers = {};
|
|
15798
|
+
}
|
|
15799
|
+
return { buffers: newBuffers, _hmrVersion: state._hmrVersion + 1 };
|
|
15800
|
+
});
|
|
15801
|
+
}
|
|
15802
|
+
|
|
15803
|
+
const isStorageLike = (value) => {
|
|
15804
|
+
if (value === null || typeof value !== "object") return false;
|
|
15805
|
+
if ("isTexture" in value) return true;
|
|
15806
|
+
if ("isData3DTexture" in value) return true;
|
|
15807
|
+
if ("uuid" in value || "nodeType" in value) return true;
|
|
15808
|
+
return false;
|
|
15809
|
+
};
|
|
15810
|
+
const disposeStorage = (storage) => {
|
|
15811
|
+
if (storage === null || typeof storage !== "object") return;
|
|
15812
|
+
if ("dispose" in storage && typeof storage.dispose === "function") {
|
|
15813
|
+
storage.dispose();
|
|
15814
|
+
}
|
|
15815
|
+
};
|
|
15816
|
+
function useGPUStorage(creatorOrScope, scope) {
|
|
15817
|
+
const store = usePrimaryStore();
|
|
15818
|
+
const removeStorage = React.useCallback(
|
|
15819
|
+
(names, targetScope) => {
|
|
15820
|
+
const nameArray = Array.isArray(names) ? names : [names];
|
|
15821
|
+
store.setState((state) => {
|
|
15822
|
+
if (targetScope) {
|
|
15823
|
+
const currentScope = { ...state.gpuStorage[targetScope] };
|
|
15824
|
+
for (const name of nameArray) delete currentScope[name];
|
|
15825
|
+
return { gpuStorage: { ...state.gpuStorage, [targetScope]: currentScope } };
|
|
15826
|
+
}
|
|
15827
|
+
const gpuStorage2 = { ...state.gpuStorage };
|
|
15828
|
+
for (const name of nameArray) if (isStorageLike(gpuStorage2[name])) delete gpuStorage2[name];
|
|
15829
|
+
return { gpuStorage: gpuStorage2 };
|
|
15830
|
+
});
|
|
15831
|
+
},
|
|
15832
|
+
[store]
|
|
15833
|
+
);
|
|
15834
|
+
const clearStorage = React.useCallback(
|
|
15835
|
+
(targetScope) => {
|
|
15836
|
+
store.setState((state) => {
|
|
15837
|
+
if (targetScope && targetScope !== "root") {
|
|
15838
|
+
const { [targetScope]: _, ...rest } = state.gpuStorage;
|
|
15839
|
+
return { gpuStorage: rest };
|
|
15840
|
+
}
|
|
15841
|
+
if (targetScope === "root") {
|
|
15842
|
+
const gpuStorage2 = {};
|
|
15843
|
+
for (const [key, value] of Object.entries(state.gpuStorage)) {
|
|
15844
|
+
if (!isStorageLike(value)) gpuStorage2[key] = value;
|
|
15845
|
+
}
|
|
15846
|
+
return { gpuStorage: gpuStorage2 };
|
|
15847
|
+
}
|
|
15848
|
+
return { gpuStorage: {} };
|
|
15849
|
+
});
|
|
15850
|
+
},
|
|
15851
|
+
[store]
|
|
15852
|
+
);
|
|
15853
|
+
const rebuildStorage = React.useCallback(
|
|
15854
|
+
(targetScope) => {
|
|
15855
|
+
store.setState((state) => {
|
|
15856
|
+
let newStorage = state.gpuStorage;
|
|
15857
|
+
if (targetScope && targetScope !== "root") {
|
|
15858
|
+
const { [targetScope]: _, ...rest } = state.gpuStorage;
|
|
15859
|
+
newStorage = rest;
|
|
15860
|
+
} else if (targetScope === "root") {
|
|
15861
|
+
newStorage = {};
|
|
15862
|
+
for (const [key, value] of Object.entries(state.gpuStorage)) {
|
|
15863
|
+
if (!isStorageLike(value)) newStorage[key] = value;
|
|
15864
|
+
}
|
|
15865
|
+
} else {
|
|
15866
|
+
newStorage = {};
|
|
15867
|
+
}
|
|
15868
|
+
return { gpuStorage: newStorage, _hmrVersion: state._hmrVersion + 1 };
|
|
15869
|
+
});
|
|
15870
|
+
},
|
|
15871
|
+
[store]
|
|
15872
|
+
);
|
|
15873
|
+
const disposeStorageFn = React.useCallback(
|
|
15874
|
+
(names, targetScope) => {
|
|
15875
|
+
const nameArray = Array.isArray(names) ? names : [names];
|
|
15876
|
+
const state = store.getState();
|
|
15877
|
+
for (const name of nameArray) {
|
|
15878
|
+
const storage = targetScope ? state.gpuStorage[targetScope]?.[name] : state.gpuStorage[name];
|
|
15879
|
+
if (storage && isStorageLike(storage)) {
|
|
15880
|
+
disposeStorage(storage);
|
|
15881
|
+
}
|
|
15882
|
+
}
|
|
15883
|
+
removeStorage(names, targetScope);
|
|
15884
|
+
},
|
|
15885
|
+
[store, removeStorage]
|
|
15886
|
+
);
|
|
15887
|
+
const isReader = creatorOrScope === void 0 || typeof creatorOrScope === "string";
|
|
15888
|
+
const storeStorage = usePrimaryThree((s) => s.gpuStorage);
|
|
15889
|
+
const hmrVersion = usePrimaryThree((s) => s._hmrVersion);
|
|
15890
|
+
const scopeDep = typeof creatorOrScope === "string" ? creatorOrScope : scope;
|
|
15891
|
+
const readerDep = isReader ? storeStorage : null;
|
|
15892
|
+
const creatorDep = isReader ? null : hmrVersion;
|
|
15893
|
+
const gpuStorage = React.useMemo(() => {
|
|
15894
|
+
if (creatorOrScope === void 0) {
|
|
15895
|
+
return storeStorage;
|
|
15896
|
+
}
|
|
15897
|
+
if (typeof creatorOrScope === "string") {
|
|
15898
|
+
const scopeData = storeStorage[creatorOrScope];
|
|
15899
|
+
if (scopeData && !isStorageLike(scopeData)) return scopeData;
|
|
15900
|
+
return {};
|
|
15901
|
+
}
|
|
15540
15902
|
const state = store.getState();
|
|
15541
|
-
|
|
15542
|
-
|
|
15903
|
+
const set = store.setState;
|
|
15904
|
+
const creator = creatorOrScope;
|
|
15905
|
+
const wrappedState = createLazyCreatorState(state);
|
|
15906
|
+
const created = creator(wrappedState);
|
|
15907
|
+
const result = {};
|
|
15908
|
+
let hasNewStorage = false;
|
|
15909
|
+
if (scope) {
|
|
15910
|
+
const currentScope = state.gpuStorage[scope] ?? {};
|
|
15911
|
+
for (const [name, storage] of Object.entries(created)) {
|
|
15912
|
+
if (currentScope[name]) {
|
|
15913
|
+
result[name] = currentScope[name];
|
|
15914
|
+
} else {
|
|
15915
|
+
if ("setName" in storage && typeof storage.setName === "function") {
|
|
15916
|
+
storage.setName(`${scope}.${name}`);
|
|
15917
|
+
}
|
|
15918
|
+
if ("name" in storage && typeof storage.name === "string") {
|
|
15919
|
+
storage.name = `${scope}.${name}`;
|
|
15920
|
+
}
|
|
15921
|
+
result[name] = storage;
|
|
15922
|
+
hasNewStorage = true;
|
|
15923
|
+
}
|
|
15924
|
+
}
|
|
15925
|
+
if (hasNewStorage) {
|
|
15926
|
+
set((s) => ({
|
|
15927
|
+
gpuStorage: {
|
|
15928
|
+
...s.gpuStorage,
|
|
15929
|
+
[scope]: { ...s.gpuStorage[scope], ...result }
|
|
15930
|
+
}
|
|
15931
|
+
}));
|
|
15932
|
+
}
|
|
15933
|
+
return result;
|
|
15934
|
+
}
|
|
15935
|
+
for (const [name, storage] of Object.entries(created)) {
|
|
15936
|
+
const existing = state.gpuStorage[name];
|
|
15937
|
+
if (existing && isStorageLike(existing)) {
|
|
15938
|
+
result[name] = existing;
|
|
15939
|
+
} else {
|
|
15940
|
+
if ("setName" in storage && typeof storage.setName === "function") {
|
|
15941
|
+
storage.setName(name);
|
|
15942
|
+
}
|
|
15943
|
+
if ("name" in storage && typeof storage.name === "string") {
|
|
15944
|
+
storage.name = name;
|
|
15945
|
+
}
|
|
15946
|
+
result[name] = storage;
|
|
15947
|
+
hasNewStorage = true;
|
|
15948
|
+
}
|
|
15949
|
+
}
|
|
15950
|
+
if (hasNewStorage) {
|
|
15951
|
+
set((s) => ({ gpuStorage: { ...s.gpuStorage, ...result } }));
|
|
15952
|
+
}
|
|
15953
|
+
return result;
|
|
15954
|
+
}, [store, scopeDep, readerDep, creatorDep]);
|
|
15955
|
+
return {
|
|
15956
|
+
...gpuStorage,
|
|
15957
|
+
removeStorage,
|
|
15958
|
+
clearStorage,
|
|
15959
|
+
rebuildStorage,
|
|
15960
|
+
disposeStorage: disposeStorageFn
|
|
15961
|
+
};
|
|
15962
|
+
}
|
|
15963
|
+
function rebuildAllStorage(store, scope) {
|
|
15964
|
+
store = store.getState().primaryStore ?? store;
|
|
15965
|
+
store.setState((state) => {
|
|
15966
|
+
let newStorage = state.gpuStorage;
|
|
15967
|
+
if (scope && scope !== "root") {
|
|
15968
|
+
const { [scope]: _, ...rest } = state.gpuStorage;
|
|
15969
|
+
newStorage = rest;
|
|
15970
|
+
} else if (scope === "root") {
|
|
15971
|
+
newStorage = {};
|
|
15972
|
+
for (const [key, value] of Object.entries(state.gpuStorage)) {
|
|
15973
|
+
if (!isStorageLike(value)) newStorage[key] = value;
|
|
15974
|
+
}
|
|
15975
|
+
} else {
|
|
15976
|
+
newStorage = {};
|
|
15977
|
+
}
|
|
15978
|
+
return { gpuStorage: newStorage, _hmrVersion: state._hmrVersion + 1 };
|
|
15979
|
+
});
|
|
15543
15980
|
}
|
|
15544
15981
|
|
|
15545
|
-
function
|
|
15982
|
+
function useRenderPipeline(mainCB, setupCB) {
|
|
15546
15983
|
const store = useStore();
|
|
15547
15984
|
const { scene, camera, renderer, isLegacy } = useThree();
|
|
15548
15985
|
const callbacksRanRef = React.useRef(false);
|
|
@@ -15552,12 +15989,16 @@ function usePostProcessing(mainCB, setupCB) {
|
|
|
15552
15989
|
mainCBRef.current = mainCB;
|
|
15553
15990
|
setupCBRef.current = setupCB;
|
|
15554
15991
|
const [rebuildVersion, setRebuildVersion] = React.useState(0);
|
|
15992
|
+
React.useEffect(() => {
|
|
15993
|
+
callbacksRanRef.current = false;
|
|
15994
|
+
scenePassCacheRef.current = null;
|
|
15995
|
+
}, []);
|
|
15555
15996
|
const clearPasses = React.useCallback(() => {
|
|
15556
15997
|
store.setState({ passes: {} });
|
|
15557
15998
|
}, [store]);
|
|
15558
15999
|
const reset = React.useCallback(() => {
|
|
15559
16000
|
store.setState({
|
|
15560
|
-
|
|
16001
|
+
renderPipeline: null,
|
|
15561
16002
|
passes: {}
|
|
15562
16003
|
});
|
|
15563
16004
|
callbacksRanRef.current = false;
|
|
@@ -15570,13 +16011,13 @@ function usePostProcessing(mainCB, setupCB) {
|
|
|
15570
16011
|
}, []);
|
|
15571
16012
|
React.useLayoutEffect(() => {
|
|
15572
16013
|
if (isLegacy) {
|
|
15573
|
-
throw new Error("
|
|
16014
|
+
throw new Error("useRenderPipeline is only available with WebGPU renderer. Set renderer prop on Canvas.");
|
|
15574
16015
|
}
|
|
15575
16016
|
if (!renderer || !scene || !camera) return;
|
|
15576
16017
|
const state = store.getState();
|
|
15577
16018
|
const set = store.setState;
|
|
15578
16019
|
try {
|
|
15579
|
-
let pp = state.
|
|
16020
|
+
let pp = state.renderPipeline;
|
|
15580
16021
|
let currentPasses = { ...state.passes };
|
|
15581
16022
|
let justCreatedPP = false;
|
|
15582
16023
|
if (!pp) {
|
|
@@ -15593,7 +16034,7 @@ function usePostProcessing(mainCB, setupCB) {
|
|
|
15593
16034
|
}
|
|
15594
16035
|
currentPasses.scenePass = scenePass;
|
|
15595
16036
|
if (!pp.outputNode || justCreatedPP) pp.outputNode = scenePass;
|
|
15596
|
-
set({
|
|
16037
|
+
set({ renderPipeline: pp, passes: currentPasses });
|
|
15597
16038
|
const shouldRunCallbacks = justCreatedPP || !callbacksRanRef.current || !cacheValid;
|
|
15598
16039
|
if (shouldRunCallbacks) {
|
|
15599
16040
|
if (setupCBRef.current) {
|
|
@@ -15615,33 +16056,41 @@ function usePostProcessing(mainCB, setupCB) {
|
|
|
15615
16056
|
callbacksRanRef.current = true;
|
|
15616
16057
|
}
|
|
15617
16058
|
} catch (error) {
|
|
15618
|
-
console.error("[
|
|
16059
|
+
console.error("[useRenderPipeline] Setup error:", error);
|
|
15619
16060
|
}
|
|
15620
16061
|
}, [store, renderer, scene, camera, isLegacy, rebuildVersion]);
|
|
15621
16062
|
const passes = useThree((s) => s.passes);
|
|
15622
|
-
const
|
|
16063
|
+
const renderPipeline = useThree((s) => s.renderPipeline);
|
|
15623
16064
|
return {
|
|
15624
16065
|
passes,
|
|
15625
|
-
|
|
16066
|
+
renderPipeline,
|
|
15626
16067
|
clearPasses,
|
|
15627
16068
|
reset,
|
|
15628
16069
|
rebuild,
|
|
15629
|
-
// isReady indicates if
|
|
15630
|
-
isReady:
|
|
16070
|
+
// isReady indicates if RenderPipeline is configured and ready for rendering
|
|
16071
|
+
isReady: renderPipeline !== null
|
|
15631
16072
|
};
|
|
15632
16073
|
}
|
|
15633
16074
|
|
|
15634
16075
|
extend(THREE);
|
|
15635
16076
|
|
|
16077
|
+
exports.Scheduler = scheduler.Scheduler;
|
|
16078
|
+
exports.getScheduler = scheduler.getScheduler;
|
|
15636
16079
|
exports.Block = Block;
|
|
15637
16080
|
exports.Canvas = Canvas;
|
|
16081
|
+
exports.Environment = Environment;
|
|
16082
|
+
exports.EnvironmentCube = EnvironmentCube;
|
|
16083
|
+
exports.EnvironmentMap = EnvironmentMap;
|
|
16084
|
+
exports.EnvironmentPortal = EnvironmentPortal;
|
|
15638
16085
|
exports.ErrorBoundary = ErrorBoundary;
|
|
16086
|
+
exports.FROM_REF = FROM_REF;
|
|
15639
16087
|
exports.IsObject = IsObject;
|
|
16088
|
+
exports.ONCE = ONCE;
|
|
16089
|
+
exports.Portal = Portal;
|
|
15640
16090
|
exports.R3F_BUILD_LEGACY = R3F_BUILD_LEGACY;
|
|
15641
16091
|
exports.R3F_BUILD_WEBGPU = R3F_BUILD_WEBGPU;
|
|
15642
16092
|
exports.REACT_INTERNAL_PROPS = REACT_INTERNAL_PROPS;
|
|
15643
16093
|
exports.RESERVED_PROPS = RESERVED_PROPS;
|
|
15644
|
-
exports.Scheduler = Scheduler;
|
|
15645
16094
|
exports.Texture = Texture;
|
|
15646
16095
|
exports._roots = _roots;
|
|
15647
16096
|
exports.act = act;
|
|
@@ -15662,6 +16111,7 @@ exports.createEvents = createEvents;
|
|
|
15662
16111
|
exports.createPointerEvents = createPointerEvents;
|
|
15663
16112
|
exports.createPortal = createPortal;
|
|
15664
16113
|
exports.createRoot = createRoot;
|
|
16114
|
+
exports.createScopedStore = createScopedStore;
|
|
15665
16115
|
exports.createStore = createStore;
|
|
15666
16116
|
exports.createTextureOperations = createTextureOperations;
|
|
15667
16117
|
exports.detach = detach;
|
|
@@ -15671,33 +16121,49 @@ exports.events = createPointerEvents;
|
|
|
15671
16121
|
exports.extend = extend;
|
|
15672
16122
|
exports.findInitialRoot = findInitialRoot;
|
|
15673
16123
|
exports.flushSync = flushSync;
|
|
16124
|
+
exports.fromRef = fromRef;
|
|
15674
16125
|
exports.getInstanceProps = getInstanceProps;
|
|
16126
|
+
exports.getPrimary = getPrimary;
|
|
16127
|
+
exports.getPrimaryIds = getPrimaryIds;
|
|
15675
16128
|
exports.getRootState = getRootState;
|
|
15676
|
-
exports.getScheduler = getScheduler;
|
|
15677
16129
|
exports.getUuidPrefix = getUuidPrefix;
|
|
15678
16130
|
exports.hasConstructor = hasConstructor;
|
|
16131
|
+
exports.hasPrimary = hasPrimary;
|
|
15679
16132
|
exports.invalidate = invalidate;
|
|
15680
16133
|
exports.invalidateInstance = invalidateInstance;
|
|
15681
16134
|
exports.is = is;
|
|
15682
16135
|
exports.isColorRepresentation = isColorRepresentation;
|
|
15683
16136
|
exports.isCopyable = isCopyable;
|
|
16137
|
+
exports.isFromRef = isFromRef;
|
|
15684
16138
|
exports.isObject3D = isObject3D;
|
|
16139
|
+
exports.isOnce = isOnce;
|
|
15685
16140
|
exports.isOrthographicCamera = isOrthographicCamera;
|
|
15686
16141
|
exports.isRef = isRef;
|
|
15687
16142
|
exports.isRenderer = isRenderer;
|
|
15688
16143
|
exports.isTexture = isTexture;
|
|
15689
16144
|
exports.isVectorLike = isVectorLike;
|
|
16145
|
+
exports.once = once;
|
|
15690
16146
|
exports.prepare = prepare;
|
|
16147
|
+
exports.presetsObj = presetsObj;
|
|
16148
|
+
exports.rebuildAllBuffers = rebuildAllBuffers;
|
|
16149
|
+
exports.rebuildAllNodes = rebuildAllNodes;
|
|
16150
|
+
exports.rebuildAllStorage = rebuildAllStorage;
|
|
16151
|
+
exports.rebuildAllUniforms = rebuildAllUniforms;
|
|
15691
16152
|
exports.reconciler = reconciler;
|
|
16153
|
+
exports.registerPrimary = registerPrimary;
|
|
15692
16154
|
exports.removeInteractivity = removeInteractivity;
|
|
15693
16155
|
exports.removeNodes = removeNodes;
|
|
15694
16156
|
exports.removeUniforms = removeUniforms;
|
|
15695
16157
|
exports.resolve = resolve;
|
|
15696
16158
|
exports.unmountComponentAtNode = unmountComponentAtNode;
|
|
16159
|
+
exports.unregisterPrimary = unregisterPrimary;
|
|
15697
16160
|
exports.updateCamera = updateCamera;
|
|
15698
16161
|
exports.updateFrustum = updateFrustum;
|
|
15699
16162
|
exports.useBridge = useBridge;
|
|
16163
|
+
exports.useBuffers = useBuffers;
|
|
16164
|
+
exports.useEnvironment = useEnvironment;
|
|
15700
16165
|
exports.useFrame = useFrame;
|
|
16166
|
+
exports.useGPUStorage = useGPUStorage;
|
|
15701
16167
|
exports.useGraph = useGraph;
|
|
15702
16168
|
exports.useInstanceHandle = useInstanceHandle;
|
|
15703
16169
|
exports.useIsomorphicLayoutEffect = useIsomorphicLayoutEffect;
|
|
@@ -15705,7 +16171,7 @@ exports.useLoader = useLoader;
|
|
|
15705
16171
|
exports.useLocalNodes = useLocalNodes;
|
|
15706
16172
|
exports.useMutableCallback = useMutableCallback;
|
|
15707
16173
|
exports.useNodes = useNodes;
|
|
15708
|
-
exports.
|
|
16174
|
+
exports.useRenderPipeline = useRenderPipeline;
|
|
15709
16175
|
exports.useRenderTarget = useRenderTarget;
|
|
15710
16176
|
exports.useStore = useStore;
|
|
15711
16177
|
exports.useTexture = useTexture;
|
|
@@ -15713,3 +16179,4 @@ exports.useTextures = useTextures;
|
|
|
15713
16179
|
exports.useThree = useThree;
|
|
15714
16180
|
exports.useUniform = useUniform;
|
|
15715
16181
|
exports.useUniforms = useUniforms;
|
|
16182
|
+
exports.waitForPrimary = waitForPrimary;
|