@react-three/fiber 10.0.0-alpha.2 → 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 +1163 -1375
- package/dist/index.d.cts +1887 -1710
- package/dist/index.d.mts +1887 -1710
- package/dist/index.d.ts +1887 -1710
- package/dist/index.mjs +1124 -1354
- package/dist/legacy.cjs +1129 -1365
- package/dist/legacy.d.cts +1888 -1711
- package/dist/legacy.d.mts +1888 -1711
- package/dist/legacy.d.ts +1888 -1711
- package/dist/legacy.mjs +1090 -1344
- package/dist/webgpu/index.cjs +1720 -1486
- package/dist/webgpu/index.d.cts +2064 -1734
- package/dist/webgpu/index.d.mts +2064 -1734
- package/dist/webgpu/index.d.ts +2064 -1734
- package/dist/webgpu/index.mjs +1678 -1466
- package/package.json +4 -1
- package/readme.md +244 -318
package/dist/index.cjs
CHANGED
|
@@ -7,8 +7,15 @@ const jsxRuntime = require('react/jsx-runtime');
|
|
|
7
7
|
const React = require('react');
|
|
8
8
|
const useMeasure = require('react-use-measure');
|
|
9
9
|
const itsFine = require('its-fine');
|
|
10
|
+
const fiber = require('@react-three/fiber');
|
|
11
|
+
const GroundedSkybox_js = require('three/examples/jsm/objects/GroundedSkybox.js');
|
|
12
|
+
const HDRLoader_js = require('three/examples/jsm/loaders/HDRLoader.js');
|
|
13
|
+
const EXRLoader_js = require('three/examples/jsm/loaders/EXRLoader.js');
|
|
14
|
+
const UltraHDRLoader_js = require('three/examples/jsm/loaders/UltraHDRLoader.js');
|
|
15
|
+
const gainmapJs = require('@monogrid/gainmap-js');
|
|
10
16
|
const Tb = require('scheduler');
|
|
11
17
|
const traditional = require('zustand/traditional');
|
|
18
|
+
const scheduler = require('@pmndrs/scheduler');
|
|
12
19
|
const suspendReact = require('suspend-react');
|
|
13
20
|
|
|
14
21
|
var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
|
|
@@ -57,9 +64,392 @@ const THREE = /*#__PURE__*/_mergeNamespaces({
|
|
|
57
64
|
WebGLRenderer: three.WebGLRenderer
|
|
58
65
|
}, [webgpu__namespace]);
|
|
59
66
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
67
|
+
const primaryRegistry = /* @__PURE__ */ new Map();
|
|
68
|
+
const pendingSubscribers = /* @__PURE__ */ new Map();
|
|
69
|
+
function registerPrimary(id, renderer, store) {
|
|
70
|
+
if (primaryRegistry.has(id)) {
|
|
71
|
+
console.warn(`Canvas with id="${id}" already registered. Overwriting.`);
|
|
72
|
+
}
|
|
73
|
+
const entry = { renderer, store };
|
|
74
|
+
primaryRegistry.set(id, entry);
|
|
75
|
+
const subscribers = pendingSubscribers.get(id);
|
|
76
|
+
if (subscribers) {
|
|
77
|
+
subscribers.forEach((callback) => callback(entry));
|
|
78
|
+
pendingSubscribers.delete(id);
|
|
79
|
+
}
|
|
80
|
+
return () => {
|
|
81
|
+
const currentEntry = primaryRegistry.get(id);
|
|
82
|
+
if (currentEntry?.renderer === renderer) {
|
|
83
|
+
primaryRegistry.delete(id);
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
function getPrimary(id) {
|
|
88
|
+
return primaryRegistry.get(id);
|
|
89
|
+
}
|
|
90
|
+
function waitForPrimary(id, timeout = 5e3) {
|
|
91
|
+
const existing = primaryRegistry.get(id);
|
|
92
|
+
if (existing) {
|
|
93
|
+
return Promise.resolve(existing);
|
|
94
|
+
}
|
|
95
|
+
return new Promise((resolve, reject) => {
|
|
96
|
+
const timeoutId = setTimeout(() => {
|
|
97
|
+
const subscribers = pendingSubscribers.get(id);
|
|
98
|
+
if (subscribers) {
|
|
99
|
+
const index = subscribers.indexOf(callback);
|
|
100
|
+
if (index !== -1) subscribers.splice(index, 1);
|
|
101
|
+
if (subscribers.length === 0) pendingSubscribers.delete(id);
|
|
102
|
+
}
|
|
103
|
+
reject(new Error(`Timeout waiting for canvas with id="${id}". Make sure a <Canvas id="${id}"> is mounted.`));
|
|
104
|
+
}, timeout);
|
|
105
|
+
const callback = (entry) => {
|
|
106
|
+
clearTimeout(timeoutId);
|
|
107
|
+
resolve(entry);
|
|
108
|
+
};
|
|
109
|
+
if (!pendingSubscribers.has(id)) {
|
|
110
|
+
pendingSubscribers.set(id, []);
|
|
111
|
+
}
|
|
112
|
+
pendingSubscribers.get(id).push(callback);
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
function hasPrimary(id) {
|
|
116
|
+
return primaryRegistry.has(id);
|
|
117
|
+
}
|
|
118
|
+
function unregisterPrimary(id) {
|
|
119
|
+
primaryRegistry.delete(id);
|
|
120
|
+
}
|
|
121
|
+
function getPrimaryIds() {
|
|
122
|
+
return Array.from(primaryRegistry.keys());
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const presetsObj = {
|
|
126
|
+
apartment: "lebombo_1k.hdr",
|
|
127
|
+
city: "potsdamer_platz_1k.hdr",
|
|
128
|
+
dawn: "kiara_1_dawn_1k.hdr",
|
|
129
|
+
forest: "forest_slope_1k.hdr",
|
|
130
|
+
lobby: "st_fagans_interior_1k.hdr",
|
|
131
|
+
night: "dikhololo_night_1k.hdr",
|
|
132
|
+
park: "rooitou_park_1k.hdr",
|
|
133
|
+
studio: "studio_small_03_1k.hdr",
|
|
134
|
+
sunset: "venice_sunset_1k.hdr",
|
|
135
|
+
warehouse: "empty_warehouse_01_1k.hdr"
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
const CUBEMAP_ROOT = "https://raw.githack.com/pmndrs/drei-assets/456060a26bbeb8fdf79326f224b6d99b8bcce736/hdri/";
|
|
139
|
+
const isArray = (arr) => Array.isArray(arr);
|
|
140
|
+
const defaultFiles = ["/px.png", "/nx.png", "/py.png", "/ny.png", "/pz.png", "/nz.png"];
|
|
141
|
+
function useEnvironment({
|
|
142
|
+
files = defaultFiles,
|
|
143
|
+
path = "",
|
|
144
|
+
preset = void 0,
|
|
145
|
+
colorSpace = void 0,
|
|
146
|
+
extensions
|
|
147
|
+
} = {}) {
|
|
148
|
+
if (preset) {
|
|
149
|
+
validatePreset(preset);
|
|
150
|
+
files = presetsObj[preset];
|
|
151
|
+
path = CUBEMAP_ROOT;
|
|
152
|
+
}
|
|
153
|
+
const multiFile = isArray(files);
|
|
154
|
+
const { extension, isCubemap } = getExtension(files);
|
|
155
|
+
const loader = getLoader$1(extension);
|
|
156
|
+
if (!loader) throw new Error("useEnvironment: Unrecognized file extension: " + files);
|
|
157
|
+
const renderer = fiber.useThree((state) => state.renderer);
|
|
158
|
+
React.useLayoutEffect(() => {
|
|
159
|
+
if (extension !== "webp" && extension !== "jpg" && extension !== "jpeg") return;
|
|
160
|
+
function clearGainmapTexture() {
|
|
161
|
+
fiber.useLoader.clear(loader, multiFile ? [files] : files);
|
|
162
|
+
}
|
|
163
|
+
renderer.domElement.addEventListener("webglcontextlost", clearGainmapTexture, { once: true });
|
|
164
|
+
}, [extension, files, loader, multiFile, renderer.domElement]);
|
|
165
|
+
const loaderResult = fiber.useLoader(
|
|
166
|
+
loader,
|
|
167
|
+
multiFile ? [files] : files,
|
|
168
|
+
(loader2) => {
|
|
169
|
+
if (extension === "webp" || extension === "jpg" || extension === "jpeg") {
|
|
170
|
+
loader2.setRenderer?.(renderer);
|
|
171
|
+
}
|
|
172
|
+
loader2.setPath?.(path);
|
|
173
|
+
if (extensions) extensions(loader2);
|
|
174
|
+
}
|
|
175
|
+
);
|
|
176
|
+
let texture = multiFile ? (
|
|
177
|
+
// @ts-ignore
|
|
178
|
+
loaderResult[0]
|
|
179
|
+
) : loaderResult;
|
|
180
|
+
if (extension === "jpg" || extension === "jpeg" || extension === "webp") {
|
|
181
|
+
texture = texture.renderTarget?.texture;
|
|
182
|
+
}
|
|
183
|
+
texture.mapping = isCubemap ? webgpu.CubeReflectionMapping : webgpu.EquirectangularReflectionMapping;
|
|
184
|
+
texture.colorSpace = colorSpace ?? (isCubemap ? "srgb" : "srgb-linear");
|
|
185
|
+
return texture;
|
|
186
|
+
}
|
|
187
|
+
const preloadDefaultOptions = {
|
|
188
|
+
files: defaultFiles,
|
|
189
|
+
path: "",
|
|
190
|
+
preset: void 0,
|
|
191
|
+
extensions: void 0
|
|
192
|
+
};
|
|
193
|
+
useEnvironment.preload = (preloadOptions) => {
|
|
194
|
+
const options = { ...preloadDefaultOptions, ...preloadOptions };
|
|
195
|
+
let { files, path = "" } = options;
|
|
196
|
+
const { preset, extensions } = options;
|
|
197
|
+
if (preset) {
|
|
198
|
+
validatePreset(preset);
|
|
199
|
+
files = presetsObj[preset];
|
|
200
|
+
path = CUBEMAP_ROOT;
|
|
201
|
+
}
|
|
202
|
+
const { extension } = getExtension(files);
|
|
203
|
+
if (extension === "webp" || extension === "jpg" || extension === "jpeg") {
|
|
204
|
+
throw new Error("useEnvironment: Preloading gainmaps is not supported");
|
|
205
|
+
}
|
|
206
|
+
const loader = getLoader$1(extension);
|
|
207
|
+
if (!loader) throw new Error("useEnvironment: Unrecognized file extension: " + files);
|
|
208
|
+
fiber.useLoader.preload(loader, isArray(files) ? [files] : files, (loader2) => {
|
|
209
|
+
loader2.setPath?.(path);
|
|
210
|
+
if (extensions) extensions(loader2);
|
|
211
|
+
});
|
|
212
|
+
};
|
|
213
|
+
const clearDefaultOptins = {
|
|
214
|
+
files: defaultFiles,
|
|
215
|
+
preset: void 0
|
|
216
|
+
};
|
|
217
|
+
useEnvironment.clear = (clearOptions) => {
|
|
218
|
+
const options = { ...clearDefaultOptins, ...clearOptions };
|
|
219
|
+
let { files } = options;
|
|
220
|
+
const { preset } = options;
|
|
221
|
+
if (preset) {
|
|
222
|
+
validatePreset(preset);
|
|
223
|
+
files = presetsObj[preset];
|
|
224
|
+
}
|
|
225
|
+
const { extension } = getExtension(files);
|
|
226
|
+
const loader = getLoader$1(extension);
|
|
227
|
+
if (!loader) throw new Error("useEnvironment: Unrecognized file extension: " + files);
|
|
228
|
+
fiber.useLoader.clear(loader, isArray(files) ? [files] : files);
|
|
229
|
+
};
|
|
230
|
+
function validatePreset(preset) {
|
|
231
|
+
if (!(preset in presetsObj)) throw new Error("Preset must be one of: " + Object.keys(presetsObj).join(", "));
|
|
232
|
+
}
|
|
233
|
+
function getExtension(files) {
|
|
234
|
+
const isCubemap = isArray(files) && files.length === 6;
|
|
235
|
+
const isGainmap = isArray(files) && files.length === 3 && files.some((file) => file.endsWith("json"));
|
|
236
|
+
const firstEntry = isArray(files) ? files[0] : files;
|
|
237
|
+
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();
|
|
238
|
+
return { extension, isCubemap, isGainmap };
|
|
239
|
+
}
|
|
240
|
+
function getLoader$1(extension) {
|
|
241
|
+
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;
|
|
242
|
+
return loader;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const isRef$1 = (obj) => obj.current && obj.current.isScene;
|
|
246
|
+
const resolveScene = (scene) => isRef$1(scene) ? scene.current : scene;
|
|
247
|
+
function setEnvProps(background, scene, defaultScene, texture, sceneProps = {}) {
|
|
248
|
+
sceneProps = {
|
|
249
|
+
backgroundBlurriness: 0,
|
|
250
|
+
backgroundIntensity: 1,
|
|
251
|
+
backgroundRotation: [0, 0, 0],
|
|
252
|
+
environmentIntensity: 1,
|
|
253
|
+
environmentRotation: [0, 0, 0],
|
|
254
|
+
...sceneProps
|
|
255
|
+
};
|
|
256
|
+
const target = resolveScene(scene || defaultScene);
|
|
257
|
+
const oldbg = target.background;
|
|
258
|
+
const oldenv = target.environment;
|
|
259
|
+
const oldSceneProps = {
|
|
260
|
+
// @ts-ignore
|
|
261
|
+
backgroundBlurriness: target.backgroundBlurriness,
|
|
262
|
+
// @ts-ignore
|
|
263
|
+
backgroundIntensity: target.backgroundIntensity,
|
|
264
|
+
// @ts-ignore
|
|
265
|
+
backgroundRotation: target.backgroundRotation?.clone?.() ?? [0, 0, 0],
|
|
266
|
+
// @ts-ignore
|
|
267
|
+
environmentIntensity: target.environmentIntensity,
|
|
268
|
+
// @ts-ignore
|
|
269
|
+
environmentRotation: target.environmentRotation?.clone?.() ?? [0, 0, 0]
|
|
270
|
+
};
|
|
271
|
+
if (background !== "only") target.environment = texture;
|
|
272
|
+
if (background) target.background = texture;
|
|
273
|
+
fiber.applyProps(target, sceneProps);
|
|
274
|
+
return () => {
|
|
275
|
+
if (background !== "only") target.environment = oldenv;
|
|
276
|
+
if (background) target.background = oldbg;
|
|
277
|
+
fiber.applyProps(target, oldSceneProps);
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
function EnvironmentMap({ scene, background = false, map, ...config }) {
|
|
281
|
+
const defaultScene = fiber.useThree((state) => state.scene);
|
|
282
|
+
React__namespace.useLayoutEffect(() => {
|
|
283
|
+
if (map) return setEnvProps(background, scene, defaultScene, map, config);
|
|
284
|
+
});
|
|
285
|
+
return null;
|
|
286
|
+
}
|
|
287
|
+
function EnvironmentCube({
|
|
288
|
+
background = false,
|
|
289
|
+
scene,
|
|
290
|
+
blur,
|
|
291
|
+
backgroundBlurriness,
|
|
292
|
+
backgroundIntensity,
|
|
293
|
+
backgroundRotation,
|
|
294
|
+
environmentIntensity,
|
|
295
|
+
environmentRotation,
|
|
296
|
+
...rest
|
|
297
|
+
}) {
|
|
298
|
+
const texture = useEnvironment(rest);
|
|
299
|
+
const defaultScene = fiber.useThree((state) => state.scene);
|
|
300
|
+
React__namespace.useLayoutEffect(() => {
|
|
301
|
+
return setEnvProps(background, scene, defaultScene, texture, {
|
|
302
|
+
backgroundBlurriness: blur ?? backgroundBlurriness,
|
|
303
|
+
backgroundIntensity,
|
|
304
|
+
backgroundRotation,
|
|
305
|
+
environmentIntensity,
|
|
306
|
+
environmentRotation
|
|
307
|
+
});
|
|
308
|
+
});
|
|
309
|
+
React__namespace.useEffect(() => {
|
|
310
|
+
return () => {
|
|
311
|
+
texture.dispose();
|
|
312
|
+
};
|
|
313
|
+
}, [texture]);
|
|
314
|
+
return null;
|
|
315
|
+
}
|
|
316
|
+
function EnvironmentPortal({
|
|
317
|
+
children,
|
|
318
|
+
near = 0.1,
|
|
319
|
+
far = 1e3,
|
|
320
|
+
resolution = 256,
|
|
321
|
+
frames = 1,
|
|
322
|
+
map,
|
|
323
|
+
background = false,
|
|
324
|
+
blur,
|
|
325
|
+
backgroundBlurriness,
|
|
326
|
+
backgroundIntensity,
|
|
327
|
+
backgroundRotation,
|
|
328
|
+
environmentIntensity,
|
|
329
|
+
environmentRotation,
|
|
330
|
+
scene,
|
|
331
|
+
files,
|
|
332
|
+
path,
|
|
333
|
+
preset = void 0,
|
|
334
|
+
extensions
|
|
335
|
+
}) {
|
|
336
|
+
const gl = fiber.useThree((state) => state.gl);
|
|
337
|
+
const defaultScene = fiber.useThree((state) => state.scene);
|
|
338
|
+
const camera = React__namespace.useRef(null);
|
|
339
|
+
const [virtualScene] = React__namespace.useState(() => new webgpu.Scene());
|
|
340
|
+
const fbo = React__namespace.useMemo(() => {
|
|
341
|
+
const fbo2 = new webgpu.WebGLCubeRenderTarget(resolution);
|
|
342
|
+
fbo2.texture.type = webgpu.HalfFloatType;
|
|
343
|
+
return fbo2;
|
|
344
|
+
}, [resolution]);
|
|
345
|
+
React__namespace.useEffect(() => {
|
|
346
|
+
return () => {
|
|
347
|
+
fbo.dispose();
|
|
348
|
+
};
|
|
349
|
+
}, [fbo]);
|
|
350
|
+
React__namespace.useLayoutEffect(() => {
|
|
351
|
+
if (frames === 1) {
|
|
352
|
+
const autoClear = gl.autoClear;
|
|
353
|
+
gl.autoClear = true;
|
|
354
|
+
camera.current.update(gl, virtualScene);
|
|
355
|
+
gl.autoClear = autoClear;
|
|
356
|
+
}
|
|
357
|
+
return setEnvProps(background, scene, defaultScene, fbo.texture, {
|
|
358
|
+
backgroundBlurriness: blur ?? backgroundBlurriness,
|
|
359
|
+
backgroundIntensity,
|
|
360
|
+
backgroundRotation,
|
|
361
|
+
environmentIntensity,
|
|
362
|
+
environmentRotation
|
|
363
|
+
});
|
|
364
|
+
}, [
|
|
365
|
+
children,
|
|
366
|
+
virtualScene,
|
|
367
|
+
fbo.texture,
|
|
368
|
+
scene,
|
|
369
|
+
defaultScene,
|
|
370
|
+
background,
|
|
371
|
+
frames,
|
|
372
|
+
gl,
|
|
373
|
+
blur,
|
|
374
|
+
backgroundBlurriness,
|
|
375
|
+
backgroundIntensity,
|
|
376
|
+
backgroundRotation,
|
|
377
|
+
environmentIntensity,
|
|
378
|
+
environmentRotation
|
|
379
|
+
]);
|
|
380
|
+
let count = 1;
|
|
381
|
+
fiber.useFrame(() => {
|
|
382
|
+
if (frames === Infinity || count < frames) {
|
|
383
|
+
const autoClear = gl.autoClear;
|
|
384
|
+
gl.autoClear = true;
|
|
385
|
+
camera.current.update(gl, virtualScene);
|
|
386
|
+
gl.autoClear = autoClear;
|
|
387
|
+
count++;
|
|
388
|
+
}
|
|
389
|
+
});
|
|
390
|
+
return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: fiber.createPortal(
|
|
391
|
+
/* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
392
|
+
children,
|
|
393
|
+
/* @__PURE__ */ jsxRuntime.jsx("cubeCamera", { ref: camera, args: [near, far, fbo] }),
|
|
394
|
+
files || preset ? /* @__PURE__ */ jsxRuntime.jsx(EnvironmentCube, { background: true, files, preset, path, extensions }) : map ? /* @__PURE__ */ jsxRuntime.jsx(EnvironmentMap, { background: true, map, extensions }) : null
|
|
395
|
+
] }),
|
|
396
|
+
virtualScene
|
|
397
|
+
) });
|
|
398
|
+
}
|
|
399
|
+
function EnvironmentGround(props) {
|
|
400
|
+
const textureDefault = useEnvironment(props);
|
|
401
|
+
const texture = props.map || textureDefault;
|
|
402
|
+
React__namespace.useMemo(() => fiber.extend({ GroundProjectedEnvImpl: GroundedSkybox_js.GroundedSkybox }), []);
|
|
403
|
+
React__namespace.useEffect(() => {
|
|
404
|
+
return () => {
|
|
405
|
+
textureDefault.dispose();
|
|
406
|
+
};
|
|
407
|
+
}, [textureDefault]);
|
|
408
|
+
const height = props.ground?.height ?? 15;
|
|
409
|
+
const radius = props.ground?.radius ?? 60;
|
|
410
|
+
const scale = props.ground?.scale ?? 1e3;
|
|
411
|
+
const args = React__namespace.useMemo(
|
|
412
|
+
() => [texture, height, radius],
|
|
413
|
+
[texture, height, radius]
|
|
414
|
+
);
|
|
415
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
416
|
+
/* @__PURE__ */ jsxRuntime.jsx(EnvironmentMap, { ...props, map: texture }),
|
|
417
|
+
/* @__PURE__ */ jsxRuntime.jsx("groundProjectedEnvImpl", { args, scale })
|
|
418
|
+
] });
|
|
419
|
+
}
|
|
420
|
+
function EnvironmentColor({ color, scene }) {
|
|
421
|
+
const defaultScene = fiber.useThree((state) => state.scene);
|
|
422
|
+
React__namespace.useLayoutEffect(() => {
|
|
423
|
+
if (color === void 0) return;
|
|
424
|
+
const target = resolveScene(scene || defaultScene);
|
|
425
|
+
const oldBg = target.background;
|
|
426
|
+
target.background = new webgpu.Color(color);
|
|
427
|
+
return () => {
|
|
428
|
+
target.background = oldBg;
|
|
429
|
+
};
|
|
430
|
+
});
|
|
431
|
+
return null;
|
|
432
|
+
}
|
|
433
|
+
function EnvironmentDualSource(props) {
|
|
434
|
+
const { backgroundFiles, ...envProps } = props;
|
|
435
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
436
|
+
/* @__PURE__ */ jsxRuntime.jsx(EnvironmentCube, { ...envProps, background: false }),
|
|
437
|
+
/* @__PURE__ */ jsxRuntime.jsx(EnvironmentCube, { ...props, files: backgroundFiles, background: "only" })
|
|
438
|
+
] });
|
|
439
|
+
}
|
|
440
|
+
function Environment(props) {
|
|
441
|
+
if (props.color && !props.files && !props.preset && !props.map) {
|
|
442
|
+
return /* @__PURE__ */ jsxRuntime.jsx(EnvironmentColor, { ...props });
|
|
443
|
+
}
|
|
444
|
+
if (props.backgroundFiles && props.backgroundFiles !== props.files) {
|
|
445
|
+
return /* @__PURE__ */ jsxRuntime.jsx(EnvironmentDualSource, { ...props });
|
|
446
|
+
}
|
|
447
|
+
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 });
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
var __defProp = Object.defineProperty;
|
|
451
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
452
|
+
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
63
453
|
const act = React__namespace["act"];
|
|
64
454
|
const useIsomorphicLayoutEffect = /* @__PURE__ */ (() => typeof window !== "undefined" && (window.document?.createElement || window.navigator?.product === "ReactNative"))() ? React__namespace.useLayoutEffect : React__namespace.useEffect;
|
|
65
455
|
function useMutableCallback(fn) {
|
|
@@ -91,7 +481,7 @@ const ErrorBoundary = /* @__PURE__ */ (() => {
|
|
|
91
481
|
return _a = class extends React__namespace.Component {
|
|
92
482
|
constructor() {
|
|
93
483
|
super(...arguments);
|
|
94
|
-
__publicField
|
|
484
|
+
__publicField(this, "state", { error: false });
|
|
95
485
|
}
|
|
96
486
|
componentDidCatch(err) {
|
|
97
487
|
this.props.set(err);
|
|
@@ -99,7 +489,7 @@ const ErrorBoundary = /* @__PURE__ */ (() => {
|
|
|
99
489
|
render() {
|
|
100
490
|
return this.state.error ? null : this.props.children;
|
|
101
491
|
}
|
|
102
|
-
}, __publicField
|
|
492
|
+
}, __publicField(_a, "getDerivedStateFromError", () => ({ error: true })), _a;
|
|
103
493
|
})();
|
|
104
494
|
|
|
105
495
|
const is = {
|
|
@@ -236,7 +626,8 @@ function prepare(target, root, type, props) {
|
|
|
236
626
|
object,
|
|
237
627
|
eventCount: 0,
|
|
238
628
|
handlers: {},
|
|
239
|
-
isHidden: false
|
|
629
|
+
isHidden: false,
|
|
630
|
+
deferredRefs: []
|
|
240
631
|
};
|
|
241
632
|
if (object) object.__r3f = instance;
|
|
242
633
|
}
|
|
@@ -285,7 +676,7 @@ function createOcclusionObserverNode(store, uniform) {
|
|
|
285
676
|
let occlusionSetupPromise = null;
|
|
286
677
|
function enableOcclusion(store) {
|
|
287
678
|
const state = store.getState();
|
|
288
|
-
const { internal, renderer
|
|
679
|
+
const { internal, renderer } = state;
|
|
289
680
|
if (internal.occlusionEnabled || occlusionSetupPromise) return;
|
|
290
681
|
const hasOcclusionSupport = typeof renderer?.isOccluded === "function";
|
|
291
682
|
if (!hasOcclusionSupport) {
|
|
@@ -448,6 +839,22 @@ function hasVisibilityHandlers(handlers) {
|
|
|
448
839
|
return !!(handlers.onFramed || handlers.onOccluded || handlers.onVisible);
|
|
449
840
|
}
|
|
450
841
|
|
|
842
|
+
const FROM_REF = Symbol.for("@react-three/fiber.fromRef");
|
|
843
|
+
function fromRef(ref) {
|
|
844
|
+
return { [FROM_REF]: ref };
|
|
845
|
+
}
|
|
846
|
+
function isFromRef(value) {
|
|
847
|
+
return value !== null && typeof value === "object" && FROM_REF in value;
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
const ONCE = Symbol.for("@react-three/fiber.once");
|
|
851
|
+
function once(...args) {
|
|
852
|
+
return { [ONCE]: args.length ? args : true };
|
|
853
|
+
}
|
|
854
|
+
function isOnce(value) {
|
|
855
|
+
return value !== null && typeof value === "object" && ONCE in value;
|
|
856
|
+
}
|
|
857
|
+
|
|
451
858
|
const RESERVED_PROPS = [
|
|
452
859
|
"children",
|
|
453
860
|
"key",
|
|
@@ -518,7 +925,7 @@ function getMemoizedPrototype(root) {
|
|
|
518
925
|
ctor = new root.constructor();
|
|
519
926
|
MEMOIZED_PROTOTYPES.set(root.constructor, ctor);
|
|
520
927
|
}
|
|
521
|
-
} catch
|
|
928
|
+
} catch {
|
|
522
929
|
}
|
|
523
930
|
return ctor;
|
|
524
931
|
}
|
|
@@ -564,6 +971,25 @@ function applyProps(object, props) {
|
|
|
564
971
|
continue;
|
|
565
972
|
}
|
|
566
973
|
if (value === void 0) continue;
|
|
974
|
+
if (isFromRef(value)) {
|
|
975
|
+
instance?.deferredRefs?.push({ prop, ref: value[FROM_REF] });
|
|
976
|
+
continue;
|
|
977
|
+
}
|
|
978
|
+
if (isOnce(value)) {
|
|
979
|
+
if (instance?.appliedOnce?.has(prop)) continue;
|
|
980
|
+
if (instance) {
|
|
981
|
+
instance.appliedOnce ?? (instance.appliedOnce = /* @__PURE__ */ new Set());
|
|
982
|
+
instance.appliedOnce.add(prop);
|
|
983
|
+
}
|
|
984
|
+
const { root: targetRoot, key: targetKey } = resolve(object, prop);
|
|
985
|
+
const args = value[ONCE];
|
|
986
|
+
if (typeof targetRoot[targetKey] === "function") {
|
|
987
|
+
targetRoot[targetKey](...args === true ? [] : args);
|
|
988
|
+
} else if (args !== true && args.length > 0) {
|
|
989
|
+
targetRoot[targetKey] = args[0];
|
|
990
|
+
}
|
|
991
|
+
continue;
|
|
992
|
+
}
|
|
567
993
|
let { root, key, target } = resolve(object, prop);
|
|
568
994
|
if (target === void 0 && (typeof root !== "object" || root === null)) {
|
|
569
995
|
throw Error(`R3F: Cannot set "${prop}". Ensure it is an object before setting "${key}".`);
|
|
@@ -586,7 +1012,10 @@ function applyProps(object, props) {
|
|
|
586
1012
|
else target.set(value);
|
|
587
1013
|
} else {
|
|
588
1014
|
root[key] = value;
|
|
589
|
-
if (
|
|
1015
|
+
if (key.endsWith("Node") && root.isMaterial) {
|
|
1016
|
+
root.needsUpdate = true;
|
|
1017
|
+
}
|
|
1018
|
+
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
|
|
590
1019
|
root[key].format === webgpu.RGBAFormat && root[key].type === webgpu.UnsignedByteType) {
|
|
591
1020
|
root[key].colorSpace = rootState.textureColorSpace;
|
|
592
1021
|
}
|
|
@@ -619,38 +1048,60 @@ function applyProps(object, props) {
|
|
|
619
1048
|
return object;
|
|
620
1049
|
}
|
|
621
1050
|
|
|
1051
|
+
const DEFAULT_POINTER_ID = 0;
|
|
1052
|
+
const XR_POINTER_ID_START = 1e3;
|
|
1053
|
+
function getPointerState(internal, pointerId) {
|
|
1054
|
+
let state = internal.pointerMap.get(pointerId);
|
|
1055
|
+
if (!state) {
|
|
1056
|
+
state = {
|
|
1057
|
+
hovered: /* @__PURE__ */ new Map(),
|
|
1058
|
+
captured: /* @__PURE__ */ new Map(),
|
|
1059
|
+
initialClick: [0, 0],
|
|
1060
|
+
initialHits: []
|
|
1061
|
+
};
|
|
1062
|
+
internal.pointerMap.set(pointerId, state);
|
|
1063
|
+
}
|
|
1064
|
+
return state;
|
|
1065
|
+
}
|
|
1066
|
+
function getPointerId(event) {
|
|
1067
|
+
return "pointerId" in event ? event.pointerId : DEFAULT_POINTER_ID;
|
|
1068
|
+
}
|
|
622
1069
|
function makeId(event) {
|
|
623
1070
|
return (event.eventObject || event.object).uuid + "/" + event.index + event.instanceId;
|
|
624
1071
|
}
|
|
625
|
-
function releaseInternalPointerCapture(
|
|
626
|
-
const
|
|
1072
|
+
function releaseInternalPointerCapture(internal, obj, pointerId) {
|
|
1073
|
+
const pointerState = internal.pointerMap.get(pointerId);
|
|
1074
|
+
if (!pointerState) return;
|
|
1075
|
+
const captureData = pointerState.captured.get(obj);
|
|
627
1076
|
if (captureData) {
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
capturedMap.delete(pointerId);
|
|
631
|
-
captureData.target.releasePointerCapture(pointerId);
|
|
632
|
-
}
|
|
1077
|
+
pointerState.captured.delete(obj);
|
|
1078
|
+
captureData.target.releasePointerCapture(pointerId);
|
|
633
1079
|
}
|
|
634
1080
|
}
|
|
635
1081
|
function removeInteractivity(store, object) {
|
|
636
1082
|
const { internal } = store.getState();
|
|
637
1083
|
internal.interaction = internal.interaction.filter((o) => o !== object);
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
1084
|
+
for (const [pointerId, pointerState] of internal.pointerMap) {
|
|
1085
|
+
pointerState.initialHits = pointerState.initialHits.filter((o) => o !== object);
|
|
1086
|
+
pointerState.hovered.forEach((value, key) => {
|
|
1087
|
+
if (value.eventObject === object || value.object === object) {
|
|
1088
|
+
pointerState.hovered.delete(key);
|
|
1089
|
+
}
|
|
1090
|
+
});
|
|
1091
|
+
if (pointerState.captured.has(object)) {
|
|
1092
|
+
releaseInternalPointerCapture(internal, object, pointerId);
|
|
642
1093
|
}
|
|
643
|
-
}
|
|
644
|
-
internal.capturedMap.forEach((captures, pointerId) => {
|
|
645
|
-
releaseInternalPointerCapture(internal.capturedMap, object, captures, pointerId);
|
|
646
|
-
});
|
|
1094
|
+
}
|
|
647
1095
|
unregisterVisibility(store, object);
|
|
648
1096
|
}
|
|
649
1097
|
function createEvents(store) {
|
|
650
|
-
function calculateDistance(event) {
|
|
1098
|
+
function calculateDistance(event, pointerId) {
|
|
651
1099
|
const { internal } = store.getState();
|
|
652
|
-
const
|
|
653
|
-
|
|
1100
|
+
const pointerState = internal.pointerMap.get(pointerId);
|
|
1101
|
+
if (!pointerState) return 0;
|
|
1102
|
+
const [initialX, initialY] = pointerState.initialClick;
|
|
1103
|
+
const dx = event.offsetX - initialX;
|
|
1104
|
+
const dy = event.offsetY - initialY;
|
|
654
1105
|
return Math.round(Math.sqrt(dx * dx + dy * dy));
|
|
655
1106
|
}
|
|
656
1107
|
function filterPointerEvents(objects) {
|
|
@@ -686,6 +1137,15 @@ function createEvents(store) {
|
|
|
686
1137
|
return state2.raycaster.camera ? state2.raycaster.intersectObject(obj, true) : [];
|
|
687
1138
|
}
|
|
688
1139
|
let hits = eventsObjects.flatMap(handleRaycast).sort((a, b) => {
|
|
1140
|
+
const aInteractivePriority = a.object.userData?.interactivePriority;
|
|
1141
|
+
const bInteractivePriority = b.object.userData?.interactivePriority;
|
|
1142
|
+
if (aInteractivePriority !== void 0 || bInteractivePriority !== void 0) {
|
|
1143
|
+
if (aInteractivePriority !== void 0 && bInteractivePriority === void 0) return -1;
|
|
1144
|
+
if (bInteractivePriority !== void 0 && aInteractivePriority === void 0) return 1;
|
|
1145
|
+
if (aInteractivePriority !== bInteractivePriority) {
|
|
1146
|
+
return (bInteractivePriority ?? 0) - (aInteractivePriority ?? 0);
|
|
1147
|
+
}
|
|
1148
|
+
}
|
|
689
1149
|
const aState = getRootState(a.object);
|
|
690
1150
|
const bState = getRootState(b.object);
|
|
691
1151
|
const aPriority = aState?.events?.priority ?? 1;
|
|
@@ -707,9 +1167,13 @@ function createEvents(store) {
|
|
|
707
1167
|
eventObject = eventObject.parent;
|
|
708
1168
|
}
|
|
709
1169
|
}
|
|
710
|
-
if ("pointerId" in event
|
|
711
|
-
|
|
712
|
-
|
|
1170
|
+
if ("pointerId" in event) {
|
|
1171
|
+
const pointerId = event.pointerId;
|
|
1172
|
+
const pointerState = state.internal.pointerMap.get(pointerId);
|
|
1173
|
+
if (pointerState?.captured.size) {
|
|
1174
|
+
for (const captureData of pointerState.captured.values()) {
|
|
1175
|
+
if (!duplicates.has(makeId(captureData.intersection))) intersections.push(captureData.intersection);
|
|
1176
|
+
}
|
|
713
1177
|
}
|
|
714
1178
|
}
|
|
715
1179
|
return intersections;
|
|
@@ -722,27 +1186,25 @@ function createEvents(store) {
|
|
|
722
1186
|
if (state) {
|
|
723
1187
|
const { raycaster, pointer, camera, internal } = state;
|
|
724
1188
|
const unprojectedPoint = new webgpu.Vector3(pointer.x, pointer.y, 0).unproject(camera);
|
|
725
|
-
const hasPointerCapture = (id) =>
|
|
1189
|
+
const hasPointerCapture = (id) => {
|
|
1190
|
+
const pointerState = internal.pointerMap.get(id);
|
|
1191
|
+
return pointerState?.captured.has(hit.eventObject) ?? false;
|
|
1192
|
+
};
|
|
726
1193
|
const setPointerCapture = (id) => {
|
|
727
1194
|
const captureData = { intersection: hit, target: event.target };
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
} else {
|
|
731
|
-
internal.capturedMap.set(id, /* @__PURE__ */ new Map([[hit.eventObject, captureData]]));
|
|
732
|
-
}
|
|
1195
|
+
const pointerState = getPointerState(internal, id);
|
|
1196
|
+
pointerState.captured.set(hit.eventObject, captureData);
|
|
733
1197
|
event.target.setPointerCapture(id);
|
|
734
1198
|
};
|
|
735
1199
|
const releasePointerCapture = (id) => {
|
|
736
|
-
|
|
737
|
-
if (captures) {
|
|
738
|
-
releaseInternalPointerCapture(internal.capturedMap, hit.eventObject, captures, id);
|
|
739
|
-
}
|
|
1200
|
+
releaseInternalPointerCapture(internal, hit.eventObject, id);
|
|
740
1201
|
};
|
|
741
1202
|
const extractEventProps = {};
|
|
742
1203
|
for (const prop in event) {
|
|
743
1204
|
const property = event[prop];
|
|
744
1205
|
if (typeof property !== "function") extractEventProps[prop] = property;
|
|
745
1206
|
}
|
|
1207
|
+
const eventPointerId = "pointerId" in event ? event.pointerId : void 0;
|
|
746
1208
|
const raycastEvent = {
|
|
747
1209
|
...hit,
|
|
748
1210
|
...extractEventProps,
|
|
@@ -753,18 +1215,19 @@ function createEvents(store) {
|
|
|
753
1215
|
unprojectedPoint,
|
|
754
1216
|
ray: raycaster.ray,
|
|
755
1217
|
camera,
|
|
1218
|
+
pointerId: eventPointerId,
|
|
756
1219
|
// Hijack stopPropagation, which just sets a flag
|
|
757
1220
|
stopPropagation() {
|
|
758
|
-
const
|
|
1221
|
+
const pointerState = eventPointerId !== void 0 ? internal.pointerMap.get(eventPointerId) : void 0;
|
|
759
1222
|
if (
|
|
760
1223
|
// ...if this pointer hasn't been captured
|
|
761
|
-
!
|
|
762
|
-
|
|
1224
|
+
!pointerState?.captured.size || // ... or if the hit object is capturing the pointer
|
|
1225
|
+
pointerState.captured.has(hit.eventObject)
|
|
763
1226
|
) {
|
|
764
1227
|
raycastEvent.stopped = localState.stopped = true;
|
|
765
|
-
if (
|
|
1228
|
+
if (pointerState?.hovered.size && Array.from(pointerState.hovered.values()).find((i) => i.eventObject === hit.eventObject)) {
|
|
766
1229
|
const higher = intersections.slice(0, intersections.indexOf(hit));
|
|
767
|
-
cancelPointer([...higher, hit]);
|
|
1230
|
+
cancelPointer([...higher, hit], eventPointerId);
|
|
768
1231
|
}
|
|
769
1232
|
}
|
|
770
1233
|
},
|
|
@@ -780,15 +1243,18 @@ function createEvents(store) {
|
|
|
780
1243
|
}
|
|
781
1244
|
return intersections;
|
|
782
1245
|
}
|
|
783
|
-
function cancelPointer(intersections) {
|
|
1246
|
+
function cancelPointer(intersections, pointerId) {
|
|
784
1247
|
const { internal } = store.getState();
|
|
785
|
-
|
|
1248
|
+
const pid = pointerId ?? DEFAULT_POINTER_ID;
|
|
1249
|
+
const pointerState = internal.pointerMap.get(pid);
|
|
1250
|
+
if (!pointerState) return;
|
|
1251
|
+
for (const [hoveredId, hoveredObj] of pointerState.hovered) {
|
|
786
1252
|
if (!intersections.length || !intersections.find(
|
|
787
1253
|
(hit) => hit.object === hoveredObj.object && hit.index === hoveredObj.index && hit.instanceId === hoveredObj.instanceId
|
|
788
1254
|
)) {
|
|
789
1255
|
const eventObject = hoveredObj.eventObject;
|
|
790
1256
|
const instance = eventObject.__r3f;
|
|
791
|
-
|
|
1257
|
+
pointerState.hovered.delete(hoveredId);
|
|
792
1258
|
if (instance?.eventCount) {
|
|
793
1259
|
const handlers = instance.handlers;
|
|
794
1260
|
const data = { ...hoveredObj, intersections };
|
|
@@ -817,41 +1283,118 @@ function createEvents(store) {
|
|
|
817
1283
|
instance?.handlers.onDropMissed?.(event);
|
|
818
1284
|
}
|
|
819
1285
|
}
|
|
1286
|
+
function cleanupPointer(pointerId) {
|
|
1287
|
+
const { internal } = store.getState();
|
|
1288
|
+
const pointerState = internal.pointerMap.get(pointerId);
|
|
1289
|
+
if (pointerState) {
|
|
1290
|
+
for (const [, hoveredObj] of pointerState.hovered) {
|
|
1291
|
+
const eventObject = hoveredObj.eventObject;
|
|
1292
|
+
const instance = eventObject.__r3f;
|
|
1293
|
+
if (instance?.eventCount) {
|
|
1294
|
+
const handlers = instance.handlers;
|
|
1295
|
+
const data = { ...hoveredObj, intersections: [] };
|
|
1296
|
+
handlers.onPointerOut?.(data);
|
|
1297
|
+
handlers.onPointerLeave?.(data);
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1300
|
+
internal.pointerMap.delete(pointerId);
|
|
1301
|
+
}
|
|
1302
|
+
internal.pointerDirty.delete(pointerId);
|
|
1303
|
+
}
|
|
1304
|
+
function processDeferredPointer(event, pointerId) {
|
|
1305
|
+
const state = store.getState();
|
|
1306
|
+
const { internal } = state;
|
|
1307
|
+
if (!state.events.enabled) return;
|
|
1308
|
+
const filter = filterPointerEvents;
|
|
1309
|
+
const hits = intersect(event, filter);
|
|
1310
|
+
cancelPointer(hits, pointerId);
|
|
1311
|
+
function onIntersect(data) {
|
|
1312
|
+
const eventObject = data.eventObject;
|
|
1313
|
+
const instance = eventObject.__r3f;
|
|
1314
|
+
if (!instance?.eventCount) return;
|
|
1315
|
+
const handlers = instance.handlers;
|
|
1316
|
+
if (handlers.onPointerOver || handlers.onPointerEnter || handlers.onPointerOut || handlers.onPointerLeave) {
|
|
1317
|
+
const id = makeId(data);
|
|
1318
|
+
const pointerState = getPointerState(internal, pointerId);
|
|
1319
|
+
const hoveredItem = pointerState.hovered.get(id);
|
|
1320
|
+
if (!hoveredItem) {
|
|
1321
|
+
pointerState.hovered.set(id, data);
|
|
1322
|
+
handlers.onPointerOver?.(data);
|
|
1323
|
+
handlers.onPointerEnter?.(data);
|
|
1324
|
+
} else if (hoveredItem.stopped) {
|
|
1325
|
+
data.stopPropagation();
|
|
1326
|
+
}
|
|
1327
|
+
}
|
|
1328
|
+
handlers.onPointerMove?.(data);
|
|
1329
|
+
}
|
|
1330
|
+
handleIntersects(hits, event, 0, onIntersect);
|
|
1331
|
+
}
|
|
820
1332
|
function handlePointer(name) {
|
|
821
1333
|
switch (name) {
|
|
822
1334
|
case "onPointerLeave":
|
|
823
|
-
case "onPointerCancel":
|
|
824
1335
|
case "onDragLeave":
|
|
825
1336
|
return () => cancelPointer([]);
|
|
1337
|
+
// Global cancel of these events
|
|
1338
|
+
case "onPointerCancel":
|
|
1339
|
+
return (event) => {
|
|
1340
|
+
const pointerId = getPointerId(event);
|
|
1341
|
+
cleanupPointer(pointerId);
|
|
1342
|
+
};
|
|
826
1343
|
case "onLostPointerCapture":
|
|
827
1344
|
return (event) => {
|
|
828
1345
|
const { internal } = store.getState();
|
|
829
|
-
|
|
1346
|
+
const pointerId = getPointerId(event);
|
|
1347
|
+
const pointerState = internal.pointerMap.get(pointerId);
|
|
1348
|
+
if (pointerState?.captured.size) {
|
|
830
1349
|
requestAnimationFrame(() => {
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
1350
|
+
const pointerState2 = internal.pointerMap.get(pointerId);
|
|
1351
|
+
if (pointerState2?.captured.size) {
|
|
1352
|
+
pointerState2.captured.clear();
|
|
834
1353
|
}
|
|
1354
|
+
cancelPointer([], pointerId);
|
|
835
1355
|
});
|
|
836
1356
|
}
|
|
837
1357
|
};
|
|
838
1358
|
}
|
|
839
1359
|
return function handleEvent(event) {
|
|
840
1360
|
const state = store.getState();
|
|
841
|
-
const { onPointerMissed, onDragOverMissed, onDropMissed, internal } = state;
|
|
1361
|
+
const { onPointerMissed, onDragOverMissed, onDropMissed, internal, events } = state;
|
|
1362
|
+
const pointerId = getPointerId(event);
|
|
842
1363
|
internal.lastEvent.current = event;
|
|
843
|
-
if (!
|
|
1364
|
+
if (!events.enabled) return;
|
|
844
1365
|
const isPointerMove = name === "onPointerMove";
|
|
845
1366
|
const isDragOver = name === "onDragOver";
|
|
846
1367
|
const isDrop = name === "onDrop";
|
|
847
1368
|
const isClickEvent = name === "onClick" || name === "onContextMenu" || name === "onDoubleClick";
|
|
1369
|
+
const isPointerDown = name === "onPointerDown";
|
|
1370
|
+
const isPointerUp = name === "onPointerUp";
|
|
1371
|
+
const isWheel = name === "onWheel";
|
|
1372
|
+
const canDeferRaycasts = events.frameTimedRaycasts && state.frameloop === "always";
|
|
1373
|
+
if (isPointerMove && canDeferRaycasts) {
|
|
1374
|
+
events.compute?.(event, state);
|
|
1375
|
+
internal.pointerDirty.set(pointerId, event);
|
|
1376
|
+
return;
|
|
1377
|
+
}
|
|
1378
|
+
if (isWheel && canDeferRaycasts && !events.alwaysFireOnScroll) {
|
|
1379
|
+
events.compute?.(event, state);
|
|
1380
|
+
internal.pointerDirty.set(pointerId, event);
|
|
1381
|
+
return;
|
|
1382
|
+
}
|
|
1383
|
+
if ((isClickEvent || isPointerDown || isPointerUp) && internal.pointerDirty.has(pointerId)) {
|
|
1384
|
+
const deferredEvent = internal.pointerDirty.get(pointerId);
|
|
1385
|
+
internal.pointerDirty.delete(pointerId);
|
|
1386
|
+
processDeferredPointer(deferredEvent, pointerId);
|
|
1387
|
+
}
|
|
848
1388
|
const filter = isPointerMove || isDragOver || isDrop ? filterPointerEvents : void 0;
|
|
849
1389
|
const hits = intersect(event, filter);
|
|
850
|
-
const delta = isClickEvent ? calculateDistance(event) : 0;
|
|
851
|
-
if (
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
1390
|
+
const delta = isClickEvent ? calculateDistance(event, pointerId) : 0;
|
|
1391
|
+
if (isPointerDown) {
|
|
1392
|
+
const pointerState2 = getPointerState(internal, pointerId);
|
|
1393
|
+
pointerState2.initialClick = [event.offsetX, event.offsetY];
|
|
1394
|
+
pointerState2.initialHits = hits.map((hit) => hit.eventObject);
|
|
1395
|
+
}
|
|
1396
|
+
const pointerState = internal.pointerMap.get(pointerId);
|
|
1397
|
+
const initialHits = pointerState?.initialHits ?? [];
|
|
855
1398
|
if (isClickEvent && !hits.length) {
|
|
856
1399
|
if (delta <= 2) {
|
|
857
1400
|
pointerMissed(event, internal.interaction);
|
|
@@ -866,7 +1409,9 @@ function createEvents(store) {
|
|
|
866
1409
|
dropMissed(event, internal.interaction);
|
|
867
1410
|
if (onDropMissed) onDropMissed(event);
|
|
868
1411
|
}
|
|
869
|
-
if (isPointerMove || isDragOver)
|
|
1412
|
+
if (isPointerMove || isDragOver) {
|
|
1413
|
+
cancelPointer(hits, pointerId);
|
|
1414
|
+
}
|
|
870
1415
|
function onIntersect(data) {
|
|
871
1416
|
const eventObject = data.eventObject;
|
|
872
1417
|
const instance = eventObject.__r3f;
|
|
@@ -875,9 +1420,10 @@ function createEvents(store) {
|
|
|
875
1420
|
if (isPointerMove) {
|
|
876
1421
|
if (handlers.onPointerOver || handlers.onPointerEnter || handlers.onPointerOut || handlers.onPointerLeave) {
|
|
877
1422
|
const id = makeId(data);
|
|
878
|
-
const
|
|
1423
|
+
const pointerState2 = getPointerState(internal, pointerId);
|
|
1424
|
+
const hoveredItem = pointerState2.hovered.get(id);
|
|
879
1425
|
if (!hoveredItem) {
|
|
880
|
-
|
|
1426
|
+
pointerState2.hovered.set(id, data);
|
|
881
1427
|
handlers.onPointerOver?.(data);
|
|
882
1428
|
handlers.onPointerEnter?.(data);
|
|
883
1429
|
} else if (hoveredItem.stopped) {
|
|
@@ -887,9 +1433,10 @@ function createEvents(store) {
|
|
|
887
1433
|
handlers.onPointerMove?.(data);
|
|
888
1434
|
} else if (isDragOver) {
|
|
889
1435
|
const id = makeId(data);
|
|
890
|
-
const
|
|
1436
|
+
const pointerState2 = getPointerState(internal, pointerId);
|
|
1437
|
+
const hoveredItem = pointerState2.hovered.get(id);
|
|
891
1438
|
if (!hoveredItem) {
|
|
892
|
-
|
|
1439
|
+
pointerState2.hovered.set(id, data);
|
|
893
1440
|
handlers.onDragOverEnter?.(data);
|
|
894
1441
|
} else if (hoveredItem.stopped) {
|
|
895
1442
|
data.stopPropagation();
|
|
@@ -900,18 +1447,18 @@ function createEvents(store) {
|
|
|
900
1447
|
} else {
|
|
901
1448
|
const handler = handlers[name];
|
|
902
1449
|
if (handler) {
|
|
903
|
-
if (!isClickEvent ||
|
|
1450
|
+
if (!isClickEvent || initialHits.includes(eventObject)) {
|
|
904
1451
|
pointerMissed(
|
|
905
1452
|
event,
|
|
906
|
-
internal.interaction.filter((object) => !
|
|
1453
|
+
internal.interaction.filter((object) => !initialHits.includes(object))
|
|
907
1454
|
);
|
|
908
1455
|
handler(data);
|
|
909
1456
|
}
|
|
910
1457
|
} else {
|
|
911
|
-
if (isClickEvent &&
|
|
1458
|
+
if (isClickEvent && initialHits.includes(eventObject)) {
|
|
912
1459
|
pointerMissed(
|
|
913
1460
|
event,
|
|
914
|
-
internal.interaction.filter((object) => !
|
|
1461
|
+
internal.interaction.filter((object) => !initialHits.includes(object))
|
|
915
1462
|
);
|
|
916
1463
|
}
|
|
917
1464
|
}
|
|
@@ -920,7 +1467,15 @@ function createEvents(store) {
|
|
|
920
1467
|
handleIntersects(hits, event, delta, onIntersect);
|
|
921
1468
|
};
|
|
922
1469
|
}
|
|
923
|
-
|
|
1470
|
+
function flushDeferredPointers() {
|
|
1471
|
+
const { internal, events } = store.getState();
|
|
1472
|
+
if (!events.frameTimedRaycasts) return;
|
|
1473
|
+
for (const [pointerId, event] of internal.pointerDirty) {
|
|
1474
|
+
processDeferredPointer(event, pointerId);
|
|
1475
|
+
}
|
|
1476
|
+
internal.pointerDirty.clear();
|
|
1477
|
+
}
|
|
1478
|
+
return { handlePointer, flushDeferredPointers, processDeferredPointer };
|
|
924
1479
|
}
|
|
925
1480
|
const DOM_EVENTS = {
|
|
926
1481
|
onClick: ["click", false],
|
|
@@ -939,11 +1494,16 @@ const DOM_EVENTS = {
|
|
|
939
1494
|
onLostPointerCapture: ["lostpointercapture", true]
|
|
940
1495
|
};
|
|
941
1496
|
function createPointerEvents(store) {
|
|
942
|
-
const { handlePointer } = createEvents(store);
|
|
1497
|
+
const { handlePointer, flushDeferredPointers, processDeferredPointer } = createEvents(store);
|
|
1498
|
+
let nextXRPointerId = XR_POINTER_ID_START;
|
|
1499
|
+
const xrPointers = /* @__PURE__ */ new Map();
|
|
943
1500
|
return {
|
|
944
1501
|
priority: 1,
|
|
945
1502
|
enabled: true,
|
|
946
|
-
|
|
1503
|
+
frameTimedRaycasts: true,
|
|
1504
|
+
alwaysFireOnScroll: true,
|
|
1505
|
+
updateOnFrame: false,
|
|
1506
|
+
compute(event, state) {
|
|
947
1507
|
state.pointer.set(event.offsetX / state.size.width * 2 - 1, -(event.offsetY / state.size.height) * 2 + 1);
|
|
948
1508
|
state.raycaster.setFromCamera(state.pointer, state.camera);
|
|
949
1509
|
},
|
|
@@ -952,11 +1512,33 @@ function createPointerEvents(store) {
|
|
|
952
1512
|
(acc, key) => ({ ...acc, [key]: handlePointer(key) }),
|
|
953
1513
|
{}
|
|
954
1514
|
),
|
|
955
|
-
update: () => {
|
|
1515
|
+
update: (pointerId) => {
|
|
956
1516
|
const { events, internal } = store.getState();
|
|
957
|
-
if (
|
|
1517
|
+
if (!events.handlers) return;
|
|
1518
|
+
if (pointerId !== void 0) {
|
|
1519
|
+
const event = internal.pointerDirty.get(pointerId);
|
|
1520
|
+
if (event) {
|
|
1521
|
+
internal.pointerDirty.delete(pointerId);
|
|
1522
|
+
processDeferredPointer(event, pointerId);
|
|
1523
|
+
} else if (internal.lastEvent?.current) {
|
|
1524
|
+
processDeferredPointer(internal.lastEvent.current, pointerId);
|
|
1525
|
+
}
|
|
1526
|
+
} else {
|
|
1527
|
+
flushDeferredPointers();
|
|
1528
|
+
if (internal.lastEvent?.current) {
|
|
1529
|
+
events.handlers.onPointerMove(internal.lastEvent.current);
|
|
1530
|
+
}
|
|
1531
|
+
}
|
|
1532
|
+
},
|
|
1533
|
+
flush: () => {
|
|
1534
|
+
const { events, internal } = store.getState();
|
|
1535
|
+
flushDeferredPointers();
|
|
1536
|
+
if (events.updateOnFrame && internal.lastEvent?.current && events.handlers) {
|
|
1537
|
+
events.handlers.onPointerMove(internal.lastEvent.current);
|
|
1538
|
+
}
|
|
958
1539
|
},
|
|
959
1540
|
connect: (target) => {
|
|
1541
|
+
if (!target) return;
|
|
960
1542
|
const { set, events } = store.getState();
|
|
961
1543
|
events.disconnect?.();
|
|
962
1544
|
set((state) => ({ events: { ...state.events, connected: target } }));
|
|
@@ -980,6 +1562,32 @@ function createPointerEvents(store) {
|
|
|
980
1562
|
}
|
|
981
1563
|
set((state) => ({ events: { ...state.events, connected: void 0 } }));
|
|
982
1564
|
}
|
|
1565
|
+
},
|
|
1566
|
+
registerPointer: (config) => {
|
|
1567
|
+
const pointerId = nextXRPointerId++;
|
|
1568
|
+
xrPointers.set(pointerId, config);
|
|
1569
|
+
const { internal } = store.getState();
|
|
1570
|
+
getPointerState(internal, pointerId);
|
|
1571
|
+
return pointerId;
|
|
1572
|
+
},
|
|
1573
|
+
unregisterPointer: (pointerId) => {
|
|
1574
|
+
xrPointers.delete(pointerId);
|
|
1575
|
+
const { internal } = store.getState();
|
|
1576
|
+
const pointerState = internal.pointerMap.get(pointerId);
|
|
1577
|
+
if (pointerState) {
|
|
1578
|
+
for (const [, hoveredObj] of pointerState.hovered) {
|
|
1579
|
+
const eventObject = hoveredObj.eventObject;
|
|
1580
|
+
const instance = eventObject.__r3f;
|
|
1581
|
+
if (instance?.eventCount) {
|
|
1582
|
+
const handlers = instance.handlers;
|
|
1583
|
+
const data = { ...hoveredObj, intersections: [] };
|
|
1584
|
+
handlers.onPointerOut?.(data);
|
|
1585
|
+
handlers.onPointerLeave?.(data);
|
|
1586
|
+
}
|
|
1587
|
+
}
|
|
1588
|
+
internal.pointerMap.delete(pointerId);
|
|
1589
|
+
}
|
|
1590
|
+
internal.pointerDirty.delete(pointerId);
|
|
983
1591
|
}
|
|
984
1592
|
};
|
|
985
1593
|
}
|
|
@@ -1070,23 +1678,29 @@ const createStore = (invalidate, advance) => {
|
|
|
1070
1678
|
set,
|
|
1071
1679
|
get,
|
|
1072
1680
|
// Mock objects that have to be configured
|
|
1681
|
+
// primaryStore is set after store creation (self-reference for primary, primary's store for secondary)
|
|
1682
|
+
primaryStore: null,
|
|
1073
1683
|
gl: null,
|
|
1074
1684
|
renderer: null,
|
|
1075
1685
|
camera: null,
|
|
1076
1686
|
frustum: new webgpu.Frustum(),
|
|
1077
1687
|
autoUpdateFrustum: true,
|
|
1078
1688
|
raycaster: null,
|
|
1079
|
-
events: {
|
|
1689
|
+
events: {
|
|
1690
|
+
priority: 1,
|
|
1691
|
+
enabled: true,
|
|
1692
|
+
connected: false,
|
|
1693
|
+
frameTimedRaycasts: true,
|
|
1694
|
+
alwaysFireOnScroll: true,
|
|
1695
|
+
updateOnFrame: false
|
|
1696
|
+
},
|
|
1080
1697
|
scene: null,
|
|
1081
1698
|
rootScene: null,
|
|
1082
1699
|
xr: null,
|
|
1083
1700
|
inspector: null,
|
|
1084
1701
|
invalidate: (frames = 1, stackFrames = false) => invalidate(get(), frames, stackFrames),
|
|
1085
1702
|
advance: (timestamp, runGlobalEffects) => advance(timestamp, runGlobalEffects, get()),
|
|
1086
|
-
|
|
1087
|
-
linear: false,
|
|
1088
|
-
flat: false,
|
|
1089
|
-
textureColorSpace: "srgb",
|
|
1703
|
+
textureColorSpace: webgpu.SRGBColorSpace,
|
|
1090
1704
|
isLegacy: false,
|
|
1091
1705
|
webGPUSupported: false,
|
|
1092
1706
|
isNative: false,
|
|
@@ -1144,6 +1758,7 @@ const createStore = (invalidate, advance) => {
|
|
|
1144
1758
|
size: newSize,
|
|
1145
1759
|
viewport: { ...s.viewport, ...getCurrentViewport(state2.camera, defaultTarget, newSize) }
|
|
1146
1760
|
}));
|
|
1761
|
+
scheduler.getScheduler().invalidate();
|
|
1147
1762
|
}
|
|
1148
1763
|
}
|
|
1149
1764
|
return;
|
|
@@ -1158,6 +1773,7 @@ const createStore = (invalidate, advance) => {
|
|
|
1158
1773
|
viewport: { ...s.viewport, ...getCurrentViewport(state2.camera, defaultTarget, size) },
|
|
1159
1774
|
_sizeImperative: true
|
|
1160
1775
|
}));
|
|
1776
|
+
scheduler.getScheduler().invalidate();
|
|
1161
1777
|
},
|
|
1162
1778
|
setDpr: (dpr) => set((state2) => {
|
|
1163
1779
|
const resolved = calculateDpr(dpr);
|
|
@@ -1168,11 +1784,14 @@ const createStore = (invalidate, advance) => {
|
|
|
1168
1784
|
},
|
|
1169
1785
|
setError: (error) => set(() => ({ error })),
|
|
1170
1786
|
error: null,
|
|
1171
|
-
//* TSL State (managed via hooks: useUniforms, useNodes, useTextures,
|
|
1787
|
+
//* TSL State (managed via hooks: useUniforms, useNodes, useBuffers, useGPUStorage, useTextures, useRenderPipeline) ==============================
|
|
1172
1788
|
uniforms: {},
|
|
1173
1789
|
nodes: {},
|
|
1790
|
+
buffers: {},
|
|
1791
|
+
gpuStorage: {},
|
|
1174
1792
|
textures: /* @__PURE__ */ new Map(),
|
|
1175
|
-
|
|
1793
|
+
_textureRefs: /* @__PURE__ */ new Map(),
|
|
1794
|
+
renderPipeline: null,
|
|
1176
1795
|
passes: {},
|
|
1177
1796
|
_hmrVersion: 0,
|
|
1178
1797
|
_sizeImperative: false,
|
|
@@ -1181,12 +1800,16 @@ const createStore = (invalidate, advance) => {
|
|
|
1181
1800
|
internal: {
|
|
1182
1801
|
// Events
|
|
1183
1802
|
interaction: [],
|
|
1184
|
-
hovered: /* @__PURE__ */ new Map(),
|
|
1185
1803
|
subscribers: [],
|
|
1804
|
+
// Per-pointer state (new unified structure)
|
|
1805
|
+
pointerMap: /* @__PURE__ */ new Map(),
|
|
1806
|
+
pointerDirty: /* @__PURE__ */ new Map(),
|
|
1807
|
+
lastEvent: React__namespace.createRef(),
|
|
1808
|
+
// Deprecated but kept for backwards compatibility
|
|
1809
|
+
hovered: /* @__PURE__ */ new Map(),
|
|
1186
1810
|
initialClick: [0, 0],
|
|
1187
1811
|
initialHits: [],
|
|
1188
1812
|
capturedMap: /* @__PURE__ */ new Map(),
|
|
1189
|
-
lastEvent: React__namespace.createRef(),
|
|
1190
1813
|
// Visibility tracking (onFramed, onOccluded, onVisible)
|
|
1191
1814
|
visibilityRegistry: /* @__PURE__ */ new Map(),
|
|
1192
1815
|
// Occlusion system (WebGPU only)
|
|
@@ -1269,13 +1892,22 @@ const createStore = (invalidate, advance) => {
|
|
|
1269
1892
|
rootStore.subscribe(() => {
|
|
1270
1893
|
const { camera, size, viewport, set, internal } = rootStore.getState();
|
|
1271
1894
|
const actualRenderer = internal.actualRenderer;
|
|
1895
|
+
const canvasTarget = internal.canvasTarget;
|
|
1272
1896
|
if (size.width !== oldSize.width || size.height !== oldSize.height || viewport.dpr !== oldDpr) {
|
|
1273
1897
|
oldSize = size;
|
|
1274
1898
|
oldDpr = viewport.dpr;
|
|
1275
1899
|
updateCamera(camera, size);
|
|
1276
|
-
if (
|
|
1277
|
-
|
|
1278
|
-
|
|
1900
|
+
if (internal.isSecondary && canvasTarget) {
|
|
1901
|
+
if (viewport.dpr > 0) canvasTarget.setPixelRatio(viewport.dpr);
|
|
1902
|
+
canvasTarget.setSize(size.width, size.height, false);
|
|
1903
|
+
} else {
|
|
1904
|
+
if (viewport.dpr > 0) actualRenderer.setPixelRatio(viewport.dpr);
|
|
1905
|
+
actualRenderer.setSize(size.width, size.height, false);
|
|
1906
|
+
if (canvasTarget) {
|
|
1907
|
+
if (viewport.dpr > 0) canvasTarget.setPixelRatio(viewport.dpr);
|
|
1908
|
+
canvasTarget.setSize(size.width, size.height, false);
|
|
1909
|
+
}
|
|
1910
|
+
}
|
|
1279
1911
|
}
|
|
1280
1912
|
if (camera !== oldCamera) {
|
|
1281
1913
|
oldCamera = camera;
|
|
@@ -1346,1042 +1978,10 @@ useLoader.clear = function(loader, input) {
|
|
|
1346
1978
|
};
|
|
1347
1979
|
useLoader.loader = getLoader;
|
|
1348
1980
|
|
|
1349
|
-
var __defProp$1 = Object.defineProperty;
|
|
1350
|
-
var __defNormalProp$1 = (obj, key, value) => key in obj ? __defProp$1(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
1351
|
-
var __publicField$1 = (obj, key, value) => __defNormalProp$1(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
1352
|
-
const DEFAULT_PHASES = ["start", "input", "physics", "update", "render", "finish"];
|
|
1353
|
-
class PhaseGraph {
|
|
1354
|
-
constructor() {
|
|
1355
|
-
/** Ordered list of phase nodes */
|
|
1356
|
-
__publicField$1(this, "phases", []);
|
|
1357
|
-
/** Quick lookup by name */
|
|
1358
|
-
__publicField$1(this, "phaseMap", /* @__PURE__ */ new Map());
|
|
1359
|
-
/** Cached ordered names (invalidated on changes) */
|
|
1360
|
-
__publicField$1(this, "orderedNamesCache", null);
|
|
1361
|
-
this.initializeDefaultPhases();
|
|
1362
|
-
}
|
|
1363
|
-
//* Initialization --------------------------------
|
|
1364
|
-
initializeDefaultPhases() {
|
|
1365
|
-
for (const name of DEFAULT_PHASES) {
|
|
1366
|
-
const node = { name, isAutoGenerated: false };
|
|
1367
|
-
this.phases.push(node);
|
|
1368
|
-
this.phaseMap.set(name, node);
|
|
1369
|
-
}
|
|
1370
|
-
this.invalidateCache();
|
|
1371
|
-
}
|
|
1372
|
-
//* Public API --------------------------------
|
|
1373
|
-
/**
|
|
1374
|
-
* Add a named phase to the graph
|
|
1375
|
-
* @param name - Phase name (must be unique)
|
|
1376
|
-
* @param options - Position options (before or after another phase)
|
|
1377
|
-
*/
|
|
1378
|
-
addPhase(name, options = {}) {
|
|
1379
|
-
if (this.phaseMap.has(name)) {
|
|
1380
|
-
console.warn(`[useFrame] Phase "${name}" already exists`);
|
|
1381
|
-
return;
|
|
1382
|
-
}
|
|
1383
|
-
const { before, after } = options;
|
|
1384
|
-
const node = { name, isAutoGenerated: false };
|
|
1385
|
-
let insertIndex = this.phases.length;
|
|
1386
|
-
const targetIndex = this.getPhaseIndex(before ?? after);
|
|
1387
|
-
if (targetIndex !== -1) {
|
|
1388
|
-
insertIndex = before ? targetIndex : targetIndex + 1;
|
|
1389
|
-
} else {
|
|
1390
|
-
const constraintType = before ? "before" : "after";
|
|
1391
|
-
console.warn(`[useFrame] Phase "${before ?? after}" not found for '${constraintType}' constraint`);
|
|
1392
|
-
}
|
|
1393
|
-
this.phases.splice(insertIndex, 0, node);
|
|
1394
|
-
this.phaseMap.set(name, node);
|
|
1395
|
-
this.invalidateCache();
|
|
1396
|
-
}
|
|
1397
|
-
/**
|
|
1398
|
-
* Get ordered list of phase names
|
|
1399
|
-
*/
|
|
1400
|
-
getOrderedPhases() {
|
|
1401
|
-
if (this.orderedNamesCache === null) this.orderedNamesCache = this.phases.map((p) => p.name);
|
|
1402
|
-
return this.orderedNamesCache;
|
|
1403
|
-
}
|
|
1404
|
-
/**
|
|
1405
|
-
* Check if a phase exists
|
|
1406
|
-
*/
|
|
1407
|
-
hasPhase(name) {
|
|
1408
|
-
return this.phaseMap.has(name);
|
|
1409
|
-
}
|
|
1410
|
-
/**
|
|
1411
|
-
* Get the index of a phase (-1 if not found)
|
|
1412
|
-
*/
|
|
1413
|
-
getPhaseIndex(name) {
|
|
1414
|
-
if (!name) return -1;
|
|
1415
|
-
return this.phases.findIndex((p) => p.name === name);
|
|
1416
|
-
}
|
|
1417
|
-
/**
|
|
1418
|
-
* Ensure a phase exists, creating an auto-generated one if needed.
|
|
1419
|
-
* Used for resolving before/after constraints.
|
|
1420
|
-
*
|
|
1421
|
-
* @param name - The phase name to ensure exists
|
|
1422
|
-
* @returns The phase name (may be auto-generated like 'before:render')
|
|
1423
|
-
*/
|
|
1424
|
-
ensurePhase(name) {
|
|
1425
|
-
if (this.phaseMap.has(name)) return name;
|
|
1426
|
-
const node = { name, isAutoGenerated: true };
|
|
1427
|
-
this.phases.push(node);
|
|
1428
|
-
this.phaseMap.set(name, node);
|
|
1429
|
-
this.invalidateCache();
|
|
1430
|
-
return name;
|
|
1431
|
-
}
|
|
1432
|
-
/**
|
|
1433
|
-
* Resolve where a job with before/after constraints should go.
|
|
1434
|
-
* Creates auto-generated phases if needed.
|
|
1435
|
-
*
|
|
1436
|
-
* @param before - Phase(s) to run before
|
|
1437
|
-
* @param after - Phase(s) to run after
|
|
1438
|
-
* @returns The resolved phase name
|
|
1439
|
-
*/
|
|
1440
|
-
resolveConstraintPhase(before, after) {
|
|
1441
|
-
const beforeArr = before ? Array.isArray(before) ? before : [before] : [];
|
|
1442
|
-
const afterArr = after ? Array.isArray(after) ? after : [after] : [];
|
|
1443
|
-
if (beforeArr.length > 0) {
|
|
1444
|
-
return this.ensureAutoPhase(beforeArr[0], "before", 0);
|
|
1445
|
-
}
|
|
1446
|
-
if (afterArr.length > 0) {
|
|
1447
|
-
return this.ensureAutoPhase(afterArr[0], "after", 1);
|
|
1448
|
-
}
|
|
1449
|
-
return "update";
|
|
1450
|
-
}
|
|
1451
|
-
/**
|
|
1452
|
-
* Ensure an auto-generated phase exists relative to a target phase.
|
|
1453
|
-
* Creates the phase if it doesn't exist, inserting it at the correct position.
|
|
1454
|
-
*
|
|
1455
|
-
* @param target - The target phase name to position relative to
|
|
1456
|
-
* @param prefix - Prefix for auto-generated phase name ('before' or 'after')
|
|
1457
|
-
* @param offset - Insertion offset (0 for before, 1 for after)
|
|
1458
|
-
* @returns The auto-generated phase name
|
|
1459
|
-
*/
|
|
1460
|
-
ensureAutoPhase(target, prefix, offset) {
|
|
1461
|
-
const autoName = `${prefix}:${target}`;
|
|
1462
|
-
if (this.phaseMap.has(autoName)) return autoName;
|
|
1463
|
-
const node = { name: autoName, isAutoGenerated: true };
|
|
1464
|
-
const targetIndex = this.getPhaseIndex(target);
|
|
1465
|
-
if (targetIndex !== -1) this.phases.splice(targetIndex + offset, 0, node);
|
|
1466
|
-
else this.phases.push(node);
|
|
1467
|
-
this.phaseMap.set(autoName, node);
|
|
1468
|
-
this.invalidateCache();
|
|
1469
|
-
return autoName;
|
|
1470
|
-
}
|
|
1471
|
-
// Internal --------------------------------
|
|
1472
|
-
invalidateCache() {
|
|
1473
|
-
this.orderedNamesCache = null;
|
|
1474
|
-
}
|
|
1475
|
-
}
|
|
1476
|
-
|
|
1477
|
-
function rebuildSortedJobs(jobs, phaseGraph) {
|
|
1478
|
-
const orderedPhases = phaseGraph.getOrderedPhases();
|
|
1479
|
-
const buckets = /* @__PURE__ */ new Map();
|
|
1480
|
-
for (const phase of orderedPhases) {
|
|
1481
|
-
buckets.set(phase, []);
|
|
1482
|
-
}
|
|
1483
|
-
for (const job of jobs.values()) {
|
|
1484
|
-
if (!job.enabled) continue;
|
|
1485
|
-
let bucket = buckets.get(job.phase);
|
|
1486
|
-
if (!bucket) {
|
|
1487
|
-
bucket = [];
|
|
1488
|
-
buckets.set(job.phase, bucket);
|
|
1489
|
-
}
|
|
1490
|
-
bucket.push(job);
|
|
1491
|
-
}
|
|
1492
|
-
const sortedBuckets = [];
|
|
1493
|
-
for (const phase of orderedPhases) {
|
|
1494
|
-
const bucket = buckets.get(phase);
|
|
1495
|
-
if (!bucket || bucket.length === 0) continue;
|
|
1496
|
-
bucket.sort((a, b) => {
|
|
1497
|
-
if (a.priority !== b.priority) return b.priority - a.priority;
|
|
1498
|
-
return a.index - b.index;
|
|
1499
|
-
});
|
|
1500
|
-
sortedBuckets.push(hasCrossJobConstraints(bucket) ? topologicalSort(bucket) : bucket);
|
|
1501
|
-
}
|
|
1502
|
-
for (const [phase, bucket] of buckets) {
|
|
1503
|
-
if (!orderedPhases.includes(phase) && bucket.length > 0) {
|
|
1504
|
-
bucket.sort((a, b) => {
|
|
1505
|
-
if (a.priority !== b.priority) return b.priority - a.priority;
|
|
1506
|
-
return a.index - b.index;
|
|
1507
|
-
});
|
|
1508
|
-
sortedBuckets.push(bucket);
|
|
1509
|
-
}
|
|
1510
|
-
}
|
|
1511
|
-
return sortedBuckets.flat();
|
|
1512
|
-
}
|
|
1513
|
-
function hasCrossJobConstraints(bucket) {
|
|
1514
|
-
const jobIds = new Set(bucket.map((j) => j.id));
|
|
1515
|
-
for (const job of bucket) {
|
|
1516
|
-
for (const ref of job.before) {
|
|
1517
|
-
if (jobIds.has(ref)) return true;
|
|
1518
|
-
}
|
|
1519
|
-
for (const ref of job.after) {
|
|
1520
|
-
if (jobIds.has(ref)) return true;
|
|
1521
|
-
}
|
|
1522
|
-
}
|
|
1523
|
-
return false;
|
|
1524
|
-
}
|
|
1525
|
-
function topologicalSort(jobs) {
|
|
1526
|
-
const n = jobs.length;
|
|
1527
|
-
if (n <= 1) return jobs;
|
|
1528
|
-
const jobMap = /* @__PURE__ */ new Map();
|
|
1529
|
-
const inDegree = /* @__PURE__ */ new Map();
|
|
1530
|
-
const adjacency = /* @__PURE__ */ new Map();
|
|
1531
|
-
for (const job of jobs) {
|
|
1532
|
-
jobMap.set(job.id, job);
|
|
1533
|
-
inDegree.set(job.id, 0);
|
|
1534
|
-
adjacency.set(job.id, []);
|
|
1535
|
-
}
|
|
1536
|
-
for (const job of jobs) {
|
|
1537
|
-
for (const ref of job.before) {
|
|
1538
|
-
if (jobMap.has(ref)) {
|
|
1539
|
-
adjacency.get(job.id).push(ref);
|
|
1540
|
-
inDegree.set(ref, inDegree.get(ref) + 1);
|
|
1541
|
-
}
|
|
1542
|
-
}
|
|
1543
|
-
for (const ref of job.after) {
|
|
1544
|
-
if (jobMap.has(ref)) {
|
|
1545
|
-
adjacency.get(ref).push(job.id);
|
|
1546
|
-
inDegree.set(job.id, inDegree.get(job.id) + 1);
|
|
1547
|
-
}
|
|
1548
|
-
}
|
|
1549
|
-
}
|
|
1550
|
-
const queue = [];
|
|
1551
|
-
for (const job of jobs) {
|
|
1552
|
-
if (inDegree.get(job.id) === 0) {
|
|
1553
|
-
queue.push(job);
|
|
1554
|
-
}
|
|
1555
|
-
}
|
|
1556
|
-
queue.sort((a, b) => {
|
|
1557
|
-
if (a.priority !== b.priority) return b.priority - a.priority;
|
|
1558
|
-
return a.index - b.index;
|
|
1559
|
-
});
|
|
1560
|
-
const result = [];
|
|
1561
|
-
while (queue.length > 0) {
|
|
1562
|
-
const job = queue.shift();
|
|
1563
|
-
result.push(job);
|
|
1564
|
-
const neighbors = adjacency.get(job.id) || [];
|
|
1565
|
-
for (const neighborId of neighbors) {
|
|
1566
|
-
const newDegree = inDegree.get(neighborId) - 1;
|
|
1567
|
-
inDegree.set(neighborId, newDegree);
|
|
1568
|
-
if (newDegree === 0) {
|
|
1569
|
-
const neighbor = jobMap.get(neighborId);
|
|
1570
|
-
insertSorted(queue, neighbor);
|
|
1571
|
-
}
|
|
1572
|
-
}
|
|
1573
|
-
}
|
|
1574
|
-
if (result.length !== n) {
|
|
1575
|
-
console.warn("[useFrame] Circular dependency detected in job constraints");
|
|
1576
|
-
const resultIds = new Set(result.map((j) => j.id));
|
|
1577
|
-
for (const job of jobs) {
|
|
1578
|
-
if (!resultIds.has(job.id)) result.push(job);
|
|
1579
|
-
}
|
|
1580
|
-
}
|
|
1581
|
-
return result;
|
|
1582
|
-
}
|
|
1583
|
-
function insertSorted(arr, job) {
|
|
1584
|
-
let i = 0;
|
|
1585
|
-
while (i < arr.length) {
|
|
1586
|
-
const cmp = arr[i];
|
|
1587
|
-
if (job.priority > cmp.priority || job.priority === cmp.priority && job.index < cmp.index) {
|
|
1588
|
-
break;
|
|
1589
|
-
}
|
|
1590
|
-
i++;
|
|
1591
|
-
}
|
|
1592
|
-
arr.splice(i, 0, job);
|
|
1593
|
-
}
|
|
1594
|
-
|
|
1595
|
-
function shouldRun(job, now) {
|
|
1596
|
-
if (!job.enabled) return false;
|
|
1597
|
-
if (!job.fps) return true;
|
|
1598
|
-
const minInterval = 1e3 / job.fps;
|
|
1599
|
-
const lastRun = job.lastRun ?? 0;
|
|
1600
|
-
const elapsed = now - lastRun;
|
|
1601
|
-
if (elapsed < minInterval) return false;
|
|
1602
|
-
if (job.drop) {
|
|
1603
|
-
job.lastRun = now;
|
|
1604
|
-
} else {
|
|
1605
|
-
const steps = Math.floor(elapsed / minInterval);
|
|
1606
|
-
job.lastRun = lastRun + steps * minInterval;
|
|
1607
|
-
if (job.lastRun < now - minInterval) {
|
|
1608
|
-
job.lastRun = now;
|
|
1609
|
-
}
|
|
1610
|
-
}
|
|
1611
|
-
return true;
|
|
1612
|
-
}
|
|
1613
|
-
function resetJobTiming(job) {
|
|
1614
|
-
job.lastRun = void 0;
|
|
1615
|
-
}
|
|
1616
|
-
|
|
1617
|
-
var __defProp = Object.defineProperty;
|
|
1618
|
-
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
1619
|
-
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
1620
|
-
const hmrData = (() => {
|
|
1621
|
-
if (typeof process !== "undefined" && process.env.NODE_ENV === "test") return void 0;
|
|
1622
|
-
if (typeof import_meta_hot !== "undefined") return import_meta_hot;
|
|
1623
|
-
try {
|
|
1624
|
-
return (0, eval)("import.meta.hot");
|
|
1625
|
-
} catch {
|
|
1626
|
-
return void 0;
|
|
1627
|
-
}
|
|
1628
|
-
})();
|
|
1629
|
-
const _Scheduler = class _Scheduler {
|
|
1630
|
-
//* Constructor ================================
|
|
1631
|
-
constructor() {
|
|
1632
|
-
//* Critical State ================================
|
|
1633
|
-
__publicField(this, "roots", /* @__PURE__ */ new Map());
|
|
1634
|
-
__publicField(this, "phaseGraph");
|
|
1635
|
-
__publicField(this, "loopState", {
|
|
1636
|
-
running: false,
|
|
1637
|
-
rafHandle: null,
|
|
1638
|
-
lastTime: null,
|
|
1639
|
-
// null = uninitialized, 0+ = valid timestamp
|
|
1640
|
-
frameCount: 0,
|
|
1641
|
-
elapsedTime: 0,
|
|
1642
|
-
createdAt: performance.now()
|
|
1643
|
-
});
|
|
1644
|
-
__publicField(this, "stoppedTime", 0);
|
|
1645
|
-
//* Private State ================================
|
|
1646
|
-
__publicField(this, "nextRootIndex", 0);
|
|
1647
|
-
__publicField(this, "globalBeforeJobs", /* @__PURE__ */ new Map());
|
|
1648
|
-
__publicField(this, "globalAfterJobs", /* @__PURE__ */ new Map());
|
|
1649
|
-
__publicField(this, "nextGlobalIndex", 0);
|
|
1650
|
-
__publicField(this, "idleCallbacks", /* @__PURE__ */ new Set());
|
|
1651
|
-
__publicField(this, "nextJobIndex", 0);
|
|
1652
|
-
__publicField(this, "jobStateListeners", /* @__PURE__ */ new Map());
|
|
1653
|
-
__publicField(this, "pendingFrames", 0);
|
|
1654
|
-
__publicField(this, "_frameloop", "always");
|
|
1655
|
-
//* Independent Mode & Error Handling State ================================
|
|
1656
|
-
__publicField(this, "_independent", false);
|
|
1657
|
-
__publicField(this, "errorHandler", null);
|
|
1658
|
-
__publicField(this, "rootReadyCallbacks", /* @__PURE__ */ new Set());
|
|
1659
|
-
//* Core Loop Execution Methods ================================
|
|
1660
|
-
/**
|
|
1661
|
-
* Main RAF loop callback.
|
|
1662
|
-
* Executes frame, handles demand mode, and schedules next frame.
|
|
1663
|
-
* @param {number} timestamp - RAF timestamp in milliseconds
|
|
1664
|
-
* @returns {void}
|
|
1665
|
-
* @private
|
|
1666
|
-
*/
|
|
1667
|
-
__publicField(this, "loop", (timestamp) => {
|
|
1668
|
-
if (!this.loopState.running) return;
|
|
1669
|
-
this.executeFrame(timestamp);
|
|
1670
|
-
if (this._frameloop === "demand") {
|
|
1671
|
-
this.pendingFrames = Math.max(0, this.pendingFrames - 1);
|
|
1672
|
-
if (this.pendingFrames === 0) {
|
|
1673
|
-
this.notifyIdle(timestamp);
|
|
1674
|
-
return this.stop();
|
|
1675
|
-
}
|
|
1676
|
-
}
|
|
1677
|
-
this.loopState.rafHandle = requestAnimationFrame(this.loop);
|
|
1678
|
-
});
|
|
1679
|
-
this.phaseGraph = new PhaseGraph();
|
|
1680
|
-
}
|
|
1681
|
-
static get instance() {
|
|
1682
|
-
return globalThis[_Scheduler.INSTANCE_KEY] ?? null;
|
|
1683
|
-
}
|
|
1684
|
-
static set instance(value) {
|
|
1685
|
-
globalThis[_Scheduler.INSTANCE_KEY] = value;
|
|
1686
|
-
}
|
|
1687
|
-
/**
|
|
1688
|
-
* Get the global scheduler instance (creates if doesn't exist).
|
|
1689
|
-
* Uses HMR data to preserve instance across hot reloads.
|
|
1690
|
-
* @returns {Scheduler} The singleton scheduler instance
|
|
1691
|
-
*/
|
|
1692
|
-
static get() {
|
|
1693
|
-
if (!_Scheduler.instance && hmrData?.data?.scheduler) {
|
|
1694
|
-
_Scheduler.instance = hmrData.data.scheduler;
|
|
1695
|
-
}
|
|
1696
|
-
if (!_Scheduler.instance) {
|
|
1697
|
-
_Scheduler.instance = new _Scheduler();
|
|
1698
|
-
if (hmrData?.data) {
|
|
1699
|
-
hmrData.data.scheduler = _Scheduler.instance;
|
|
1700
|
-
}
|
|
1701
|
-
}
|
|
1702
|
-
return _Scheduler.instance;
|
|
1703
|
-
}
|
|
1704
|
-
/**
|
|
1705
|
-
* Reset the singleton instance. Stops the loop and clears all state.
|
|
1706
|
-
* Primarily used for testing to ensure clean state between tests.
|
|
1707
|
-
* @returns {void}
|
|
1708
|
-
*/
|
|
1709
|
-
static reset() {
|
|
1710
|
-
if (_Scheduler.instance) {
|
|
1711
|
-
_Scheduler.instance.stop();
|
|
1712
|
-
_Scheduler.instance = null;
|
|
1713
|
-
}
|
|
1714
|
-
if (hmrData?.data) {
|
|
1715
|
-
hmrData.data.scheduler = null;
|
|
1716
|
-
}
|
|
1717
|
-
}
|
|
1718
|
-
//* Getters & Setters ================================
|
|
1719
|
-
get phases() {
|
|
1720
|
-
return this.phaseGraph.getOrderedPhases();
|
|
1721
|
-
}
|
|
1722
|
-
get frameloop() {
|
|
1723
|
-
return this._frameloop;
|
|
1724
|
-
}
|
|
1725
|
-
set frameloop(mode) {
|
|
1726
|
-
if (this._frameloop === mode) return;
|
|
1727
|
-
const wasAlways = this._frameloop === "always";
|
|
1728
|
-
this._frameloop = mode;
|
|
1729
|
-
if (mode === "always" && !this.loopState.running && this.roots.size > 0) this.start();
|
|
1730
|
-
else if (mode !== "always" && wasAlways) this.stop();
|
|
1731
|
-
}
|
|
1732
|
-
get isRunning() {
|
|
1733
|
-
return this.loopState.running;
|
|
1734
|
-
}
|
|
1735
|
-
get isReady() {
|
|
1736
|
-
return this.roots.size > 0;
|
|
1737
|
-
}
|
|
1738
|
-
get independent() {
|
|
1739
|
-
return this._independent;
|
|
1740
|
-
}
|
|
1741
|
-
set independent(value) {
|
|
1742
|
-
this._independent = value;
|
|
1743
|
-
if (value) this.ensureDefaultRoot();
|
|
1744
|
-
}
|
|
1745
|
-
//* Root Management Methods ================================
|
|
1746
|
-
/**
|
|
1747
|
-
* Register a root (Canvas) with the scheduler.
|
|
1748
|
-
* The first root to register starts the RAF loop (if frameloop='always').
|
|
1749
|
-
* @param {string} id - Unique identifier for this root
|
|
1750
|
-
* @param {RootOptions} [options] - Optional configuration with getState and onError callbacks
|
|
1751
|
-
* @returns {() => void} Unsubscribe function to remove this root
|
|
1752
|
-
*/
|
|
1753
|
-
registerRoot(id, options = {}) {
|
|
1754
|
-
if (this.roots.has(id)) {
|
|
1755
|
-
console.warn(`[Scheduler] Root "${id}" already registered`);
|
|
1756
|
-
return () => this.unregisterRoot(id);
|
|
1757
|
-
}
|
|
1758
|
-
const entry = {
|
|
1759
|
-
id,
|
|
1760
|
-
getState: options.getState ?? (() => ({})),
|
|
1761
|
-
jobs: /* @__PURE__ */ new Map(),
|
|
1762
|
-
sortedJobs: [],
|
|
1763
|
-
needsRebuild: false
|
|
1764
|
-
};
|
|
1765
|
-
if (options.onError) {
|
|
1766
|
-
this.errorHandler = options.onError;
|
|
1767
|
-
}
|
|
1768
|
-
this.roots.set(id, entry);
|
|
1769
|
-
if (this.roots.size === 1) {
|
|
1770
|
-
this.notifyRootReady();
|
|
1771
|
-
if (this._frameloop === "always") this.start();
|
|
1772
|
-
}
|
|
1773
|
-
return () => this.unregisterRoot(id);
|
|
1774
|
-
}
|
|
1775
|
-
/**
|
|
1776
|
-
* Unregister a root from the scheduler.
|
|
1777
|
-
* Cleans up all job state listeners for this root's jobs.
|
|
1778
|
-
* The last root to unregister stops the RAF loop.
|
|
1779
|
-
* @param {string} id - The root ID to unregister
|
|
1780
|
-
* @returns {void}
|
|
1781
|
-
*/
|
|
1782
|
-
unregisterRoot(id) {
|
|
1783
|
-
const root = this.roots.get(id);
|
|
1784
|
-
if (!root) return;
|
|
1785
|
-
for (const jobId of root.jobs.keys()) {
|
|
1786
|
-
this.jobStateListeners.delete(jobId);
|
|
1787
|
-
}
|
|
1788
|
-
this.roots.delete(id);
|
|
1789
|
-
if (this.roots.size === 0) {
|
|
1790
|
-
this.stop();
|
|
1791
|
-
this.errorHandler = null;
|
|
1792
|
-
}
|
|
1793
|
-
}
|
|
1794
|
-
/**
|
|
1795
|
-
* Subscribe to be notified when a root becomes available.
|
|
1796
|
-
* Fires immediately if a root already exists.
|
|
1797
|
-
* @param {() => void} callback - Function called when first root registers
|
|
1798
|
-
* @returns {() => void} Unsubscribe function
|
|
1799
|
-
*/
|
|
1800
|
-
onRootReady(callback) {
|
|
1801
|
-
if (this.roots.size > 0) {
|
|
1802
|
-
callback();
|
|
1803
|
-
return () => {
|
|
1804
|
-
};
|
|
1805
|
-
}
|
|
1806
|
-
this.rootReadyCallbacks.add(callback);
|
|
1807
|
-
return () => this.rootReadyCallbacks.delete(callback);
|
|
1808
|
-
}
|
|
1809
|
-
/**
|
|
1810
|
-
* Notify all registered root-ready callbacks.
|
|
1811
|
-
* Called when the first root registers.
|
|
1812
|
-
* @returns {void}
|
|
1813
|
-
* @private
|
|
1814
|
-
*/
|
|
1815
|
-
notifyRootReady() {
|
|
1816
|
-
for (const cb of this.rootReadyCallbacks) {
|
|
1817
|
-
try {
|
|
1818
|
-
cb();
|
|
1819
|
-
} catch (error) {
|
|
1820
|
-
console.error("[Scheduler] Error in root-ready callback:", error);
|
|
1821
|
-
}
|
|
1822
|
-
}
|
|
1823
|
-
this.rootReadyCallbacks.clear();
|
|
1824
|
-
}
|
|
1825
|
-
/**
|
|
1826
|
-
* Ensure a default root exists for independent mode.
|
|
1827
|
-
* Creates a minimal root with no state provider.
|
|
1828
|
-
* @returns {void}
|
|
1829
|
-
* @private
|
|
1830
|
-
*/
|
|
1831
|
-
ensureDefaultRoot() {
|
|
1832
|
-
if (!this.roots.has("__default__")) {
|
|
1833
|
-
this.registerRoot("__default__");
|
|
1834
|
-
}
|
|
1835
|
-
}
|
|
1836
|
-
/**
|
|
1837
|
-
* Trigger error handling for job errors.
|
|
1838
|
-
* Uses the bound error handler if available, otherwise logs to console.
|
|
1839
|
-
* @param {Error} error - The error to handle
|
|
1840
|
-
* @returns {void}
|
|
1841
|
-
*/
|
|
1842
|
-
triggerError(error) {
|
|
1843
|
-
if (this.errorHandler) this.errorHandler(error);
|
|
1844
|
-
else console.error("[Scheduler]", error);
|
|
1845
|
-
}
|
|
1846
|
-
//* Phase Management Methods ================================
|
|
1847
|
-
/**
|
|
1848
|
-
* Add a named phase to the scheduler's execution order.
|
|
1849
|
-
* Marks all roots for rebuild to incorporate the new phase.
|
|
1850
|
-
* @param {string} name - The phase name (e.g., 'physics', 'postprocess')
|
|
1851
|
-
* @param {AddPhaseOptions} [options] - Positioning options (before/after other phases)
|
|
1852
|
-
* @returns {void}
|
|
1853
|
-
* @example
|
|
1854
|
-
* scheduler.addPhase('physics', { before: 'update' });
|
|
1855
|
-
* scheduler.addPhase('postprocess', { after: 'render' });
|
|
1856
|
-
*/
|
|
1857
|
-
addPhase(name, options) {
|
|
1858
|
-
this.phaseGraph.addPhase(name, options);
|
|
1859
|
-
for (const root of this.roots.values()) {
|
|
1860
|
-
root.needsRebuild = true;
|
|
1861
|
-
}
|
|
1862
|
-
}
|
|
1863
|
-
/**
|
|
1864
|
-
* Check if a phase exists in the scheduler.
|
|
1865
|
-
* @param {string} name - The phase name to check
|
|
1866
|
-
* @returns {boolean} True if the phase exists
|
|
1867
|
-
*/
|
|
1868
|
-
hasPhase(name) {
|
|
1869
|
-
return this.phaseGraph.hasPhase(name);
|
|
1870
|
-
}
|
|
1871
|
-
//* Global Job Registration Methods (Deprecated APIs) ================================
|
|
1872
|
-
/**
|
|
1873
|
-
* Register a global job that runs once per frame (not per-root).
|
|
1874
|
-
* Used internally by deprecated addEffect/addAfterEffect APIs.
|
|
1875
|
-
* @param {'before' | 'after'} phase - When to run: 'before' all roots or 'after' all roots
|
|
1876
|
-
* @param {string} id - Unique identifier for this global job
|
|
1877
|
-
* @param {(timestamp: number) => void} callback - Function called each frame with RAF timestamp
|
|
1878
|
-
* @returns {() => void} Unsubscribe function to remove this global job
|
|
1879
|
-
* @deprecated Use useFrame with phases instead
|
|
1880
|
-
*/
|
|
1881
|
-
registerGlobal(phase, id, callback) {
|
|
1882
|
-
const job = { id, callback };
|
|
1883
|
-
if (phase === "before") {
|
|
1884
|
-
this.globalBeforeJobs.set(id, job);
|
|
1885
|
-
} else {
|
|
1886
|
-
this.globalAfterJobs.set(id, job);
|
|
1887
|
-
}
|
|
1888
|
-
return () => {
|
|
1889
|
-
if (phase === "before") this.globalBeforeJobs.delete(id);
|
|
1890
|
-
else this.globalAfterJobs.delete(id);
|
|
1891
|
-
};
|
|
1892
|
-
}
|
|
1893
|
-
//* Idle Callback Methods (Deprecated API) ================================
|
|
1894
|
-
/**
|
|
1895
|
-
* Register an idle callback that fires when the loop stops.
|
|
1896
|
-
* Used internally by deprecated addTail API.
|
|
1897
|
-
* @param {(timestamp: number) => void} callback - Function called when loop becomes idle
|
|
1898
|
-
* @returns {() => void} Unsubscribe function to remove this idle callback
|
|
1899
|
-
* @deprecated Use demand mode with invalidate() instead
|
|
1900
|
-
*/
|
|
1901
|
-
onIdle(callback) {
|
|
1902
|
-
this.idleCallbacks.add(callback);
|
|
1903
|
-
return () => this.idleCallbacks.delete(callback);
|
|
1904
|
-
}
|
|
1905
|
-
/**
|
|
1906
|
-
* Notify all registered idle callbacks.
|
|
1907
|
-
* Called when the loop stops in demand mode.
|
|
1908
|
-
* @param {number} timestamp - The RAF timestamp when idle occurred
|
|
1909
|
-
* @returns {void}
|
|
1910
|
-
* @private
|
|
1911
|
-
*/
|
|
1912
|
-
notifyIdle(timestamp) {
|
|
1913
|
-
for (const cb of this.idleCallbacks) {
|
|
1914
|
-
try {
|
|
1915
|
-
cb(timestamp);
|
|
1916
|
-
} catch (error) {
|
|
1917
|
-
console.error("[Scheduler] Error in idle callback:", error);
|
|
1918
|
-
}
|
|
1919
|
-
}
|
|
1920
|
-
}
|
|
1921
|
-
//* Job Registration & Management Methods ================================
|
|
1922
|
-
/**
|
|
1923
|
-
* Register a job (frame callback) with a specific root.
|
|
1924
|
-
* This is the core registration method used by useFrame internally.
|
|
1925
|
-
* @param {FrameNextCallback} callback - The function to call each frame
|
|
1926
|
-
* @param {JobOptions & { rootId?: string; system?: boolean }} [options] - Job configuration
|
|
1927
|
-
* @param {string} [options.rootId] - Target root ID (defaults to first registered root)
|
|
1928
|
-
* @param {string} [options.id] - Unique job ID (auto-generated if not provided)
|
|
1929
|
-
* @param {string} [options.phase] - Execution phase (defaults to 'update')
|
|
1930
|
-
* @param {number} [options.priority] - Priority within phase (higher = earlier, default 0)
|
|
1931
|
-
* @param {number} [options.fps] - FPS throttle limit
|
|
1932
|
-
* @param {boolean} [options.drop] - Drop frames when behind (default true)
|
|
1933
|
-
* @param {boolean} [options.enabled] - Whether job is active (default true)
|
|
1934
|
-
* @param {boolean} [options.system] - Internal flag for system jobs (not user-facing)
|
|
1935
|
-
* @returns {() => void} Unsubscribe function to remove this job
|
|
1936
|
-
*/
|
|
1937
|
-
register(callback, options = {}) {
|
|
1938
|
-
const rootId = options.rootId;
|
|
1939
|
-
const root = rootId ? this.roots.get(rootId) : this.roots.values().next().value;
|
|
1940
|
-
if (!root) {
|
|
1941
|
-
console.warn("[Scheduler] No root registered. Is this inside a Canvas?");
|
|
1942
|
-
return () => {
|
|
1943
|
-
};
|
|
1944
|
-
}
|
|
1945
|
-
const id = options.id ?? this.generateJobId();
|
|
1946
|
-
let phase = options.phase ?? "update";
|
|
1947
|
-
if (!options.phase && (options.before || options.after)) {
|
|
1948
|
-
phase = this.phaseGraph.resolveConstraintPhase(options.before, options.after);
|
|
1949
|
-
}
|
|
1950
|
-
const before = this.normalizeConstraints(options.before);
|
|
1951
|
-
const after = this.normalizeConstraints(options.after);
|
|
1952
|
-
const job = {
|
|
1953
|
-
id,
|
|
1954
|
-
callback,
|
|
1955
|
-
phase,
|
|
1956
|
-
before,
|
|
1957
|
-
after,
|
|
1958
|
-
priority: options.priority ?? 0,
|
|
1959
|
-
index: this.nextJobIndex++,
|
|
1960
|
-
fps: options.fps,
|
|
1961
|
-
drop: options.drop ?? true,
|
|
1962
|
-
enabled: options.enabled ?? true,
|
|
1963
|
-
system: options.system ?? false
|
|
1964
|
-
};
|
|
1965
|
-
if (root.jobs.has(id)) {
|
|
1966
|
-
console.warn(`[useFrame] Job with id "${id}" already exists, replacing`);
|
|
1967
|
-
}
|
|
1968
|
-
root.jobs.set(id, job);
|
|
1969
|
-
root.needsRebuild = true;
|
|
1970
|
-
return () => this.unregister(id, root.id);
|
|
1971
|
-
}
|
|
1972
|
-
/**
|
|
1973
|
-
* Unregister a job by its ID.
|
|
1974
|
-
* Searches all roots if rootId is not provided.
|
|
1975
|
-
* @param {string} id - The job ID to unregister
|
|
1976
|
-
* @param {string} [rootId] - Optional root ID to search (searches all if not provided)
|
|
1977
|
-
* @returns {void}
|
|
1978
|
-
*/
|
|
1979
|
-
unregister(id, rootId) {
|
|
1980
|
-
const root = rootId ? this.roots.get(rootId) : Array.from(this.roots.values()).find((r) => r.jobs.has(id));
|
|
1981
|
-
if (root?.jobs.delete(id)) {
|
|
1982
|
-
root.needsRebuild = true;
|
|
1983
|
-
this.jobStateListeners.delete(id);
|
|
1984
|
-
}
|
|
1985
|
-
}
|
|
1986
|
-
/**
|
|
1987
|
-
* Update a job's options dynamically.
|
|
1988
|
-
* Searches all roots to find the job by ID.
|
|
1989
|
-
* Phase/constraint changes trigger a rebuild of the sorted job list.
|
|
1990
|
-
* @param {string} id - The job ID to update
|
|
1991
|
-
* @param {Partial<JobOptions>} options - The options to update
|
|
1992
|
-
* @returns {void}
|
|
1993
|
-
*/
|
|
1994
|
-
updateJob(id, options) {
|
|
1995
|
-
let job;
|
|
1996
|
-
let root;
|
|
1997
|
-
for (const r of this.roots.values()) {
|
|
1998
|
-
job = r.jobs.get(id);
|
|
1999
|
-
if (job) {
|
|
2000
|
-
root = r;
|
|
2001
|
-
break;
|
|
2002
|
-
}
|
|
2003
|
-
}
|
|
2004
|
-
if (!job || !root) return;
|
|
2005
|
-
if (options.priority !== void 0) job.priority = options.priority;
|
|
2006
|
-
if (options.fps !== void 0) job.fps = options.fps;
|
|
2007
|
-
if (options.drop !== void 0) job.drop = options.drop;
|
|
2008
|
-
if (options.enabled !== void 0) {
|
|
2009
|
-
const wasEnabled = job.enabled;
|
|
2010
|
-
job.enabled = options.enabled;
|
|
2011
|
-
if (!wasEnabled && job.enabled) resetJobTiming(job);
|
|
2012
|
-
if (wasEnabled !== job.enabled) root.needsRebuild = true;
|
|
2013
|
-
}
|
|
2014
|
-
if (options.phase !== void 0 || options.before !== void 0 || options.after !== void 0) {
|
|
2015
|
-
if (options.phase) job.phase = options.phase;
|
|
2016
|
-
if (options.before !== void 0) job.before = this.normalizeConstraints(options.before);
|
|
2017
|
-
if (options.after !== void 0) job.after = this.normalizeConstraints(options.after);
|
|
2018
|
-
root.needsRebuild = true;
|
|
2019
|
-
}
|
|
2020
|
-
}
|
|
2021
|
-
//* Job State Management Methods ================================
|
|
2022
|
-
/**
|
|
2023
|
-
* Check if a job is currently paused (disabled).
|
|
2024
|
-
* @param {string} id - The job ID to check
|
|
2025
|
-
* @returns {boolean} True if the job exists and is paused
|
|
2026
|
-
*/
|
|
2027
|
-
isJobPaused(id) {
|
|
2028
|
-
for (const root of this.roots.values()) {
|
|
2029
|
-
const job = root.jobs.get(id);
|
|
2030
|
-
if (job) return !job.enabled;
|
|
2031
|
-
}
|
|
2032
|
-
return false;
|
|
2033
|
-
}
|
|
2034
|
-
/**
|
|
2035
|
-
* Subscribe to state changes for a specific job.
|
|
2036
|
-
* Listener is called when job is paused or resumed.
|
|
2037
|
-
* @param {string} id - The job ID to subscribe to
|
|
2038
|
-
* @param {() => void} listener - Callback invoked on state changes
|
|
2039
|
-
* @returns {() => void} Unsubscribe function
|
|
2040
|
-
*/
|
|
2041
|
-
subscribeJobState(id, listener) {
|
|
2042
|
-
if (!this.jobStateListeners.has(id)) {
|
|
2043
|
-
this.jobStateListeners.set(id, /* @__PURE__ */ new Set());
|
|
2044
|
-
}
|
|
2045
|
-
this.jobStateListeners.get(id).add(listener);
|
|
2046
|
-
return () => {
|
|
2047
|
-
this.jobStateListeners.get(id)?.delete(listener);
|
|
2048
|
-
if (this.jobStateListeners.get(id)?.size === 0) {
|
|
2049
|
-
this.jobStateListeners.delete(id);
|
|
2050
|
-
}
|
|
2051
|
-
};
|
|
2052
|
-
}
|
|
2053
|
-
/**
|
|
2054
|
-
* Notify all listeners that a job's state has changed.
|
|
2055
|
-
* @param {string} id - The job ID that changed
|
|
2056
|
-
* @returns {void}
|
|
2057
|
-
* @private
|
|
2058
|
-
*/
|
|
2059
|
-
notifyJobStateChange(id) {
|
|
2060
|
-
this.jobStateListeners.get(id)?.forEach((listener) => listener());
|
|
2061
|
-
}
|
|
2062
|
-
/**
|
|
2063
|
-
* Pause a job by ID (sets enabled=false).
|
|
2064
|
-
* Notifies any subscribed state listeners.
|
|
2065
|
-
* @param {string} id - The job ID to pause
|
|
2066
|
-
* @returns {void}
|
|
2067
|
-
*/
|
|
2068
|
-
pauseJob(id) {
|
|
2069
|
-
this.updateJob(id, { enabled: false });
|
|
2070
|
-
this.notifyJobStateChange(id);
|
|
2071
|
-
}
|
|
2072
|
-
/**
|
|
2073
|
-
* Resume a paused job by ID (sets enabled=true).
|
|
2074
|
-
* Resets job timing to prevent frame accumulation.
|
|
2075
|
-
* Notifies any subscribed state listeners.
|
|
2076
|
-
* @param {string} id - The job ID to resume
|
|
2077
|
-
* @returns {void}
|
|
2078
|
-
*/
|
|
2079
|
-
resumeJob(id) {
|
|
2080
|
-
this.updateJob(id, { enabled: true });
|
|
2081
|
-
this.notifyJobStateChange(id);
|
|
2082
|
-
}
|
|
2083
|
-
//* Frame Loop Control Methods ================================
|
|
2084
|
-
/**
|
|
2085
|
-
* Start the requestAnimationFrame loop.
|
|
2086
|
-
* Resets timing state (elapsedTime, frameCount) on start.
|
|
2087
|
-
* No-op if already running.
|
|
2088
|
-
* @returns {void}
|
|
2089
|
-
*/
|
|
2090
|
-
start() {
|
|
2091
|
-
if (this.loopState.running) return;
|
|
2092
|
-
const { elapsedTime, createdAt } = this.loopState;
|
|
2093
|
-
let adjustedCreated = 0;
|
|
2094
|
-
if (this.stoppedTime > 0) {
|
|
2095
|
-
adjustedCreated = createdAt - (performance.now() - this.stoppedTime);
|
|
2096
|
-
this.stoppedTime = 0;
|
|
2097
|
-
}
|
|
2098
|
-
Object.assign(this.loopState, {
|
|
2099
|
-
running: true,
|
|
2100
|
-
elapsedTime: elapsedTime ?? 0,
|
|
2101
|
-
lastTime: performance.now(),
|
|
2102
|
-
createdAt: adjustedCreated > 0 ? adjustedCreated : performance.now(),
|
|
2103
|
-
frameCount: 0,
|
|
2104
|
-
rafHandle: requestAnimationFrame(this.loop)
|
|
2105
|
-
});
|
|
2106
|
-
}
|
|
2107
|
-
/**
|
|
2108
|
-
* Stop the requestAnimationFrame loop.
|
|
2109
|
-
* Cancels any pending RAF callback.
|
|
2110
|
-
* No-op if not running.
|
|
2111
|
-
* @returns {void}
|
|
2112
|
-
*/
|
|
2113
|
-
stop() {
|
|
2114
|
-
if (!this.loopState.running) return;
|
|
2115
|
-
this.loopState.running = false;
|
|
2116
|
-
if (this.loopState.rafHandle !== null) {
|
|
2117
|
-
cancelAnimationFrame(this.loopState.rafHandle);
|
|
2118
|
-
this.loopState.rafHandle = null;
|
|
2119
|
-
}
|
|
2120
|
-
this.stoppedTime = performance.now();
|
|
2121
|
-
}
|
|
2122
|
-
/**
|
|
2123
|
-
* Request frames to be rendered in demand mode.
|
|
2124
|
-
* Accumulates pending frames (capped at 60) and starts the loop if not running.
|
|
2125
|
-
* No-op if frameloop is not 'demand'.
|
|
2126
|
-
* @param {number} [frames=1] - Number of frames to request
|
|
2127
|
-
* @param {boolean} [stackFrames=false] - Whether to add frames to existing pending count
|
|
2128
|
-
* - `false` (default): Sets pending frames to the specified value (replaces existing count)
|
|
2129
|
-
* - `true`: Adds frames to existing pending count (useful for accumulating invalidations)
|
|
2130
|
-
* @returns {void}
|
|
2131
|
-
* @example
|
|
2132
|
-
* // Request a single frame render
|
|
2133
|
-
* scheduler.invalidate();
|
|
2134
|
-
*
|
|
2135
|
-
* @example
|
|
2136
|
-
* // Request 5 frames (e.g., for animations)
|
|
2137
|
-
* scheduler.invalidate(5);
|
|
2138
|
-
*
|
|
2139
|
-
* @example
|
|
2140
|
-
* // Set pending frames to exactly 3 (don't stack with existing)
|
|
2141
|
-
* scheduler.invalidate(3, false);
|
|
2142
|
-
*
|
|
2143
|
-
* @example
|
|
2144
|
-
* // Add 2 more frames to existing pending count
|
|
2145
|
-
* scheduler.invalidate(2, true);
|
|
2146
|
-
*/
|
|
2147
|
-
invalidate(frames = 1, stackFrames = false) {
|
|
2148
|
-
if (this._frameloop !== "demand") return;
|
|
2149
|
-
const baseFrames = stackFrames ? this.pendingFrames : 0;
|
|
2150
|
-
this.pendingFrames = Math.min(60, baseFrames + frames);
|
|
2151
|
-
if (!this.loopState.running && this.pendingFrames > 0) this.start();
|
|
2152
|
-
}
|
|
2153
|
-
/**
|
|
2154
|
-
* Reset timing state for deterministic testing.
|
|
2155
|
-
* Preserves jobs and roots but resets lastTime, frameCount, elapsedTime, etc.
|
|
2156
|
-
* @returns {void}
|
|
2157
|
-
*/
|
|
2158
|
-
resetTiming() {
|
|
2159
|
-
this.loopState.lastTime = null;
|
|
2160
|
-
this.loopState.frameCount = 0;
|
|
2161
|
-
this.loopState.elapsedTime = 0;
|
|
2162
|
-
this.loopState.createdAt = performance.now();
|
|
2163
|
-
}
|
|
2164
|
-
//* Manual Stepping Methods ================================
|
|
2165
|
-
/**
|
|
2166
|
-
* Manually execute a single frame for all roots.
|
|
2167
|
-
* Useful for frameloop='never' mode or testing scenarios.
|
|
2168
|
-
* @param {number} [timestamp] - Optional timestamp (defaults to performance.now())
|
|
2169
|
-
* @returns {void}
|
|
2170
|
-
* @example
|
|
2171
|
-
* // Manual control mode
|
|
2172
|
-
* scheduler.frameloop = 'never';
|
|
2173
|
-
* scheduler.step(); // Execute one frame
|
|
2174
|
-
*/
|
|
2175
|
-
step(timestamp) {
|
|
2176
|
-
const now = timestamp ?? performance.now();
|
|
2177
|
-
this.executeFrame(now);
|
|
2178
|
-
}
|
|
2179
|
-
/**
|
|
2180
|
-
* Manually execute a single job by its ID.
|
|
2181
|
-
* Useful for testing individual job callbacks in isolation.
|
|
2182
|
-
* @param {string} id - The job ID to step
|
|
2183
|
-
* @param {number} [timestamp] - Optional timestamp (defaults to performance.now())
|
|
2184
|
-
* @returns {void}
|
|
2185
|
-
*/
|
|
2186
|
-
stepJob(id, timestamp) {
|
|
2187
|
-
let job;
|
|
2188
|
-
let root;
|
|
2189
|
-
for (const r of this.roots.values()) {
|
|
2190
|
-
job = r.jobs.get(id);
|
|
2191
|
-
if (job) {
|
|
2192
|
-
root = r;
|
|
2193
|
-
break;
|
|
2194
|
-
}
|
|
2195
|
-
}
|
|
2196
|
-
if (!job || !root) {
|
|
2197
|
-
console.warn(`[Scheduler] Job "${id}" not found`);
|
|
2198
|
-
return;
|
|
2199
|
-
}
|
|
2200
|
-
const now = timestamp ?? performance.now();
|
|
2201
|
-
const deltaMs = this.loopState.lastTime !== null ? now - this.loopState.lastTime : 0;
|
|
2202
|
-
const delta = deltaMs / 1e3;
|
|
2203
|
-
const elapsed = now - this.loopState.createdAt;
|
|
2204
|
-
const providedState = root.getState?.() ?? {};
|
|
2205
|
-
const frameState = {
|
|
2206
|
-
...providedState,
|
|
2207
|
-
time: now,
|
|
2208
|
-
delta,
|
|
2209
|
-
elapsed,
|
|
2210
|
-
frame: this.loopState.frameCount
|
|
2211
|
-
};
|
|
2212
|
-
try {
|
|
2213
|
-
job.callback(frameState, delta);
|
|
2214
|
-
} catch (error) {
|
|
2215
|
-
console.error(`[Scheduler] Error in job "${job.id}":`, error);
|
|
2216
|
-
this.triggerError(error instanceof Error ? error : new Error(String(error)));
|
|
2217
|
-
}
|
|
2218
|
-
}
|
|
2219
|
-
/**
|
|
2220
|
-
* Execute a single frame across all roots.
|
|
2221
|
-
* Order: globalBefore → each root's jobs → globalAfter
|
|
2222
|
-
* @param {number} timestamp - RAF timestamp in milliseconds
|
|
2223
|
-
* @returns {void}
|
|
2224
|
-
* @private
|
|
2225
|
-
*/
|
|
2226
|
-
executeFrame(timestamp) {
|
|
2227
|
-
const deltaMs = this.loopState.lastTime !== null ? timestamp - this.loopState.lastTime : 0;
|
|
2228
|
-
const delta = deltaMs / 1e3;
|
|
2229
|
-
this.loopState.lastTime = timestamp;
|
|
2230
|
-
this.loopState.frameCount++;
|
|
2231
|
-
this.loopState.elapsedTime += deltaMs;
|
|
2232
|
-
this.runGlobalJobs(this.globalBeforeJobs, timestamp);
|
|
2233
|
-
for (const root of this.roots.values()) {
|
|
2234
|
-
this.tickRoot(root, timestamp, delta);
|
|
2235
|
-
}
|
|
2236
|
-
this.runGlobalJobs(this.globalAfterJobs, timestamp);
|
|
2237
|
-
}
|
|
2238
|
-
/**
|
|
2239
|
-
* Run all global jobs from a job map.
|
|
2240
|
-
* Catches and logs errors without stopping execution.
|
|
2241
|
-
* @param {Map<string, GlobalJob>} jobs - The global jobs map to execute
|
|
2242
|
-
* @param {number} timestamp - RAF timestamp in milliseconds
|
|
2243
|
-
* @returns {void}
|
|
2244
|
-
* @private
|
|
2245
|
-
*/
|
|
2246
|
-
runGlobalJobs(jobs, timestamp) {
|
|
2247
|
-
for (const job of jobs.values()) {
|
|
2248
|
-
try {
|
|
2249
|
-
job.callback(timestamp);
|
|
2250
|
-
} catch (error) {
|
|
2251
|
-
console.error(`[Scheduler] Error in global job "${job.id}":`, error);
|
|
2252
|
-
}
|
|
2253
|
-
}
|
|
2254
|
-
}
|
|
2255
|
-
/**
|
|
2256
|
-
* Execute all jobs for a single root in sorted order.
|
|
2257
|
-
* Rebuilds sorted job list if needed, then dispatches each job.
|
|
2258
|
-
* Errors are caught and propagated via triggerError.
|
|
2259
|
-
* @param {RootEntry} root - The root entry to tick
|
|
2260
|
-
* @param {number} timestamp - RAF timestamp in milliseconds
|
|
2261
|
-
* @param {number} delta - Time since last frame in seconds
|
|
2262
|
-
* @returns {void}
|
|
2263
|
-
* @private
|
|
2264
|
-
*/
|
|
2265
|
-
tickRoot(root, timestamp, delta) {
|
|
2266
|
-
if (root.needsRebuild) {
|
|
2267
|
-
root.sortedJobs = rebuildSortedJobs(root.jobs, this.phaseGraph);
|
|
2268
|
-
root.needsRebuild = false;
|
|
2269
|
-
}
|
|
2270
|
-
const providedState = root.getState?.() ?? {};
|
|
2271
|
-
const frameState = {
|
|
2272
|
-
...providedState,
|
|
2273
|
-
time: timestamp,
|
|
2274
|
-
delta,
|
|
2275
|
-
elapsed: this.loopState.elapsedTime / 1e3,
|
|
2276
|
-
// Convert ms to seconds
|
|
2277
|
-
frame: this.loopState.frameCount
|
|
2278
|
-
};
|
|
2279
|
-
for (const job of root.sortedJobs) {
|
|
2280
|
-
if (!shouldRun(job, timestamp)) continue;
|
|
2281
|
-
try {
|
|
2282
|
-
job.callback(frameState, delta);
|
|
2283
|
-
} catch (error) {
|
|
2284
|
-
console.error(`[Scheduler] Error in job "${job.id}":`, error);
|
|
2285
|
-
this.triggerError(error instanceof Error ? error : new Error(String(error)));
|
|
2286
|
-
}
|
|
2287
|
-
}
|
|
2288
|
-
}
|
|
2289
|
-
//* Debug & Inspection Methods ================================
|
|
2290
|
-
/**
|
|
2291
|
-
* Get the total number of registered jobs across all roots.
|
|
2292
|
-
* Includes both per-root jobs and global before/after jobs.
|
|
2293
|
-
* @returns {number} Total job count
|
|
2294
|
-
*/
|
|
2295
|
-
getJobCount() {
|
|
2296
|
-
let count = 0;
|
|
2297
|
-
for (const root of this.roots.values()) {
|
|
2298
|
-
count += root.jobs.size;
|
|
2299
|
-
}
|
|
2300
|
-
return count + this.globalBeforeJobs.size + this.globalAfterJobs.size;
|
|
2301
|
-
}
|
|
2302
|
-
/**
|
|
2303
|
-
* Get all registered job IDs across all roots.
|
|
2304
|
-
* Includes both per-root jobs and global before/after jobs.
|
|
2305
|
-
* @returns {string[]} Array of all job IDs
|
|
2306
|
-
*/
|
|
2307
|
-
getJobIds() {
|
|
2308
|
-
const ids = [];
|
|
2309
|
-
for (const root of this.roots.values()) {
|
|
2310
|
-
ids.push(...root.jobs.keys());
|
|
2311
|
-
}
|
|
2312
|
-
ids.push(...this.globalBeforeJobs.keys());
|
|
2313
|
-
ids.push(...this.globalAfterJobs.keys());
|
|
2314
|
-
return ids;
|
|
2315
|
-
}
|
|
2316
|
-
/**
|
|
2317
|
-
* Get the number of registered roots (Canvas instances).
|
|
2318
|
-
* @returns {number} Number of registered roots
|
|
2319
|
-
*/
|
|
2320
|
-
getRootCount() {
|
|
2321
|
-
return this.roots.size;
|
|
2322
|
-
}
|
|
2323
|
-
/**
|
|
2324
|
-
* Check if any user (non-system) jobs are registered in a specific phase.
|
|
2325
|
-
* Used by the default render job to know if a user has taken over rendering.
|
|
2326
|
-
*
|
|
2327
|
-
* @param phase The phase to check
|
|
2328
|
-
* @param rootId Optional root ID to check (checks all roots if not provided)
|
|
2329
|
-
* @returns true if any user jobs exist in the phase
|
|
2330
|
-
*/
|
|
2331
|
-
hasUserJobsInPhase(phase, rootId) {
|
|
2332
|
-
const rootsToCheck = rootId ? [this.roots.get(rootId)].filter(Boolean) : Array.from(this.roots.values());
|
|
2333
|
-
return rootsToCheck.some((root) => {
|
|
2334
|
-
if (!root) return false;
|
|
2335
|
-
for (const job of root.jobs.values()) {
|
|
2336
|
-
if (job.phase === phase && !job.system && job.enabled) return true;
|
|
2337
|
-
}
|
|
2338
|
-
return false;
|
|
2339
|
-
});
|
|
2340
|
-
}
|
|
2341
|
-
//* Utility Methods ================================
|
|
2342
|
-
/**
|
|
2343
|
-
* Generate a unique root ID for automatic root registration.
|
|
2344
|
-
* @returns {string} A unique root ID in the format 'root_N'
|
|
2345
|
-
*/
|
|
2346
|
-
generateRootId() {
|
|
2347
|
-
return `root_${this.nextRootIndex++}`;
|
|
2348
|
-
}
|
|
2349
|
-
/**
|
|
2350
|
-
* Generate a unique job ID.
|
|
2351
|
-
* @returns {string} A unique job ID in the format 'job_N'
|
|
2352
|
-
* @private
|
|
2353
|
-
*/
|
|
2354
|
-
generateJobId() {
|
|
2355
|
-
return `job_${this.nextJobIndex}`;
|
|
2356
|
-
}
|
|
2357
|
-
/**
|
|
2358
|
-
* Normalize before/after constraints to a Set.
|
|
2359
|
-
* Handles undefined, single string, or array inputs.
|
|
2360
|
-
* @param {string | string[] | undefined} value - The constraint value(s)
|
|
2361
|
-
* @returns {Set<string>} Normalized Set of constraint strings
|
|
2362
|
-
* @private
|
|
2363
|
-
*/
|
|
2364
|
-
normalizeConstraints(value) {
|
|
2365
|
-
if (!value) return /* @__PURE__ */ new Set();
|
|
2366
|
-
if (Array.isArray(value)) return new Set(value);
|
|
2367
|
-
return /* @__PURE__ */ new Set([value]);
|
|
2368
|
-
}
|
|
2369
|
-
};
|
|
2370
|
-
//* Static State & Methods (Singleton Usage) ================================
|
|
2371
|
-
//* Cross-Bundle Singleton Key ==============================
|
|
2372
|
-
// Use Symbol.for() to ensure scheduler is shared across bundle boundaries
|
|
2373
|
-
// This prevents issues when mixing imports from @react-three/fiber and @react-three/fiber/webgpu
|
|
2374
|
-
__publicField(_Scheduler, "INSTANCE_KEY", Symbol.for("@react-three/fiber.scheduler"));
|
|
2375
|
-
let Scheduler = _Scheduler;
|
|
2376
|
-
const getScheduler = () => Scheduler.get();
|
|
2377
|
-
if (hmrData) {
|
|
2378
|
-
hmrData.accept?.();
|
|
2379
|
-
}
|
|
2380
|
-
|
|
2381
1981
|
function useFrame(callback, priorityOrOptions) {
|
|
2382
1982
|
const store = React__namespace.useContext(context);
|
|
2383
1983
|
const isInsideCanvas = store !== null;
|
|
2384
|
-
const scheduler = getScheduler();
|
|
1984
|
+
const scheduler$1 = scheduler.getScheduler();
|
|
2385
1985
|
const optionsKey = typeof priorityOrOptions === "number" ? `p:${priorityOrOptions}` : priorityOrOptions ? JSON.stringify({
|
|
2386
1986
|
id: priorityOrOptions.id,
|
|
2387
1987
|
phase: priorityOrOptions.phase,
|
|
@@ -2429,7 +2029,7 @@ function useFrame(callback, priorityOrOptions) {
|
|
|
2429
2029
|
};
|
|
2430
2030
|
callbackRef.current?.(mergedState, delta);
|
|
2431
2031
|
};
|
|
2432
|
-
const unregister = scheduler.register(wrappedCallback, {
|
|
2032
|
+
const unregister = scheduler$1.register(wrappedCallback, {
|
|
2433
2033
|
id,
|
|
2434
2034
|
rootId,
|
|
2435
2035
|
...options
|
|
@@ -2450,37 +2050,31 @@ function useFrame(callback, priorityOrOptions) {
|
|
|
2450
2050
|
}
|
|
2451
2051
|
};
|
|
2452
2052
|
} else {
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
unregisterJob = registerOutside();
|
|
2462
|
-
});
|
|
2463
|
-
return () => {
|
|
2464
|
-
unsubReady();
|
|
2465
|
-
unregisterJob?.();
|
|
2466
|
-
};
|
|
2053
|
+
return scheduler$1.register(
|
|
2054
|
+
(state, delta) => {
|
|
2055
|
+
const frameState = state;
|
|
2056
|
+
if (!frameState.renderer) return;
|
|
2057
|
+
callbackRef.current?.(frameState, delta);
|
|
2058
|
+
},
|
|
2059
|
+
{ id, ...options }
|
|
2060
|
+
);
|
|
2467
2061
|
}
|
|
2468
|
-
}, [store, scheduler, id, optionsKey, isLegacyPriority, isInsideCanvas]);
|
|
2062
|
+
}, [store, scheduler$1, id, optionsKey, isLegacyPriority, isInsideCanvas]);
|
|
2469
2063
|
const isPaused = React__namespace.useSyncExternalStore(
|
|
2470
2064
|
// Subscribe function
|
|
2471
2065
|
React__namespace.useCallback(
|
|
2472
2066
|
(onStoreChange) => {
|
|
2473
|
-
return getScheduler().subscribeJobState(id, onStoreChange);
|
|
2067
|
+
return scheduler.getScheduler().subscribeJobState(id, onStoreChange);
|
|
2474
2068
|
},
|
|
2475
2069
|
[id]
|
|
2476
2070
|
),
|
|
2477
2071
|
// getSnapshot function
|
|
2478
|
-
React__namespace.useCallback(() => getScheduler().isJobPaused(id), [id]),
|
|
2072
|
+
React__namespace.useCallback(() => scheduler.getScheduler().isJobPaused(id), [id]),
|
|
2479
2073
|
// getServerSnapshot function (SSR)
|
|
2480
2074
|
React__namespace.useCallback(() => false, [])
|
|
2481
2075
|
);
|
|
2482
2076
|
const controls = React__namespace.useMemo(() => {
|
|
2483
|
-
const scheduler2 = getScheduler();
|
|
2077
|
+
const scheduler2 = scheduler.getScheduler();
|
|
2484
2078
|
return {
|
|
2485
2079
|
/** The job's unique ID */
|
|
2486
2080
|
id,
|
|
@@ -2495,7 +2089,7 @@ function useFrame(callback, priorityOrOptions) {
|
|
|
2495
2089
|
* @param timestamp Optional timestamp (defaults to performance.now())
|
|
2496
2090
|
*/
|
|
2497
2091
|
step: (timestamp) => {
|
|
2498
|
-
getScheduler().stepJob(id, timestamp);
|
|
2092
|
+
scheduler.getScheduler().stepJob(id, timestamp);
|
|
2499
2093
|
},
|
|
2500
2094
|
/**
|
|
2501
2095
|
* Manually step ALL jobs in the scheduler.
|
|
@@ -2503,20 +2097,20 @@ function useFrame(callback, priorityOrOptions) {
|
|
|
2503
2097
|
* @param timestamp Optional timestamp (defaults to performance.now())
|
|
2504
2098
|
*/
|
|
2505
2099
|
stepAll: (timestamp) => {
|
|
2506
|
-
getScheduler().step(timestamp);
|
|
2100
|
+
scheduler.getScheduler().step(timestamp);
|
|
2507
2101
|
},
|
|
2508
2102
|
/**
|
|
2509
2103
|
* Pause this job (set enabled=false).
|
|
2510
2104
|
* Job remains registered but won't run.
|
|
2511
2105
|
*/
|
|
2512
2106
|
pause: () => {
|
|
2513
|
-
getScheduler().pauseJob(id);
|
|
2107
|
+
scheduler.getScheduler().pauseJob(id);
|
|
2514
2108
|
},
|
|
2515
2109
|
/**
|
|
2516
2110
|
* Resume this job (set enabled=true).
|
|
2517
2111
|
*/
|
|
2518
2112
|
resume: () => {
|
|
2519
|
-
getScheduler().resumeJob(id);
|
|
2113
|
+
scheduler.getScheduler().resumeJob(id);
|
|
2520
2114
|
},
|
|
2521
2115
|
/**
|
|
2522
2116
|
* Reactive paused state - automatically updates when pause/resume is called.
|
|
@@ -2554,22 +2148,29 @@ function buildFromCache(input, textureCache) {
|
|
|
2554
2148
|
function useTexture(input, optionsOrOnLoad) {
|
|
2555
2149
|
const renderer = useThree((state) => state.internal.actualRenderer);
|
|
2556
2150
|
const store = useStore();
|
|
2557
|
-
const textureCache = useThree((state) => state.textures);
|
|
2558
2151
|
const options = typeof optionsOrOnLoad === "function" ? { onLoad: optionsOrOnLoad } : optionsOrOnLoad ?? {};
|
|
2559
|
-
const { onLoad, cache =
|
|
2152
|
+
const { onLoad, cache = true } = options;
|
|
2153
|
+
const onLoadRef = React.useRef(onLoad);
|
|
2154
|
+
onLoadRef.current = onLoad;
|
|
2155
|
+
const onLoadCalledForRef = React.useRef(null);
|
|
2560
2156
|
const urls = React.useMemo(() => getUrls(input), [input]);
|
|
2561
2157
|
const cachedResult = React.useMemo(() => {
|
|
2562
2158
|
if (!cache) return null;
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2159
|
+
const textures = store.getState().textures;
|
|
2160
|
+
if (!allUrlsCached(urls, textures)) return null;
|
|
2161
|
+
return buildFromCache(input, textures);
|
|
2162
|
+
}, [cache, urls, input, store]);
|
|
2566
2163
|
const loadedTextures = useLoader(
|
|
2567
2164
|
webgpu.TextureLoader,
|
|
2568
2165
|
IsObject(input) ? Object.values(input) : input
|
|
2569
2166
|
);
|
|
2167
|
+
const inputKey = urls.join("\0");
|
|
2570
2168
|
React.useLayoutEffect(() => {
|
|
2571
|
-
if (
|
|
2572
|
-
|
|
2169
|
+
if (cachedResult) return;
|
|
2170
|
+
if (onLoadCalledForRef.current === inputKey) return;
|
|
2171
|
+
onLoadCalledForRef.current = inputKey;
|
|
2172
|
+
onLoadRef.current?.(loadedTextures);
|
|
2173
|
+
}, [cachedResult, loadedTextures, inputKey]);
|
|
2573
2174
|
React.useEffect(() => {
|
|
2574
2175
|
if (cachedResult) return;
|
|
2575
2176
|
if ("initTexture" in renderer) {
|
|
@@ -2602,8 +2203,6 @@ function useTexture(input, optionsOrOnLoad) {
|
|
|
2602
2203
|
}, [input, loadedTextures, cachedResult]);
|
|
2603
2204
|
React.useEffect(() => {
|
|
2604
2205
|
if (!cache) return;
|
|
2605
|
-
if (cachedResult) return;
|
|
2606
|
-
const set = store.setState;
|
|
2607
2206
|
const urlTextureMap = [];
|
|
2608
2207
|
if (typeof input === "string") {
|
|
2609
2208
|
urlTextureMap.push([input, mappedTextures]);
|
|
@@ -2617,18 +2216,32 @@ function useTexture(input, optionsOrOnLoad) {
|
|
|
2617
2216
|
urlTextureMap.push([url, textureRecord[key]]);
|
|
2618
2217
|
}
|
|
2619
2218
|
}
|
|
2620
|
-
|
|
2621
|
-
const
|
|
2622
|
-
let
|
|
2219
|
+
store.setState((state) => {
|
|
2220
|
+
const refs = new Map(state._textureRefs);
|
|
2221
|
+
let textures = state.textures;
|
|
2222
|
+
let added = false;
|
|
2623
2223
|
for (const [url, texture] of urlTextureMap) {
|
|
2624
|
-
if (!
|
|
2625
|
-
|
|
2626
|
-
|
|
2224
|
+
if (!textures.has(url)) {
|
|
2225
|
+
if (!added) {
|
|
2226
|
+
textures = new Map(textures);
|
|
2227
|
+
added = true;
|
|
2228
|
+
}
|
|
2229
|
+
textures.set(url, texture);
|
|
2627
2230
|
}
|
|
2231
|
+
refs.set(url, (refs.get(url) ?? 0) + 1);
|
|
2628
2232
|
}
|
|
2629
|
-
return
|
|
2233
|
+
return added ? { textures, _textureRefs: refs } : { _textureRefs: refs };
|
|
2234
|
+
});
|
|
2235
|
+
return () => store.setState((state) => {
|
|
2236
|
+
const refs = new Map(state._textureRefs);
|
|
2237
|
+
for (const [url] of urlTextureMap) {
|
|
2238
|
+
const next = (refs.get(url) ?? 0) - 1;
|
|
2239
|
+
if (next <= 0) refs.delete(url);
|
|
2240
|
+
else refs.set(url, next);
|
|
2241
|
+
}
|
|
2242
|
+
return { _textureRefs: refs };
|
|
2630
2243
|
});
|
|
2631
|
-
}, [cache, input, mappedTextures, store
|
|
2244
|
+
}, [cache, input, mappedTextures, store]);
|
|
2632
2245
|
return mappedTextures;
|
|
2633
2246
|
}
|
|
2634
2247
|
useTexture.preload = (url) => useLoader.preload(webgpu.TextureLoader, url);
|
|
@@ -2644,108 +2257,92 @@ const Texture = ({
|
|
|
2644
2257
|
return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: children?.(ret) });
|
|
2645
2258
|
};
|
|
2646
2259
|
|
|
2647
|
-
function
|
|
2648
|
-
if (entry instanceof webgpu.Texture) return entry;
|
|
2649
|
-
if (entry && typeof entry === "object" && "value" in entry && entry.value instanceof webgpu.Texture) {
|
|
2650
|
-
return entry.value;
|
|
2651
|
-
}
|
|
2652
|
-
return null;
|
|
2653
|
-
}
|
|
2654
|
-
function useTextures() {
|
|
2260
|
+
function useTextures(selector) {
|
|
2655
2261
|
const store = useStore();
|
|
2656
|
-
|
|
2657
|
-
const set = store.setState;
|
|
2262
|
+
const registry = React.useMemo(() => {
|
|
2658
2263
|
const getState = store.getState;
|
|
2659
|
-
const
|
|
2660
|
-
|
|
2661
|
-
const newMap = new Map(state.textures);
|
|
2662
|
-
newMap.set(key, value);
|
|
2663
|
-
return { textures: newMap };
|
|
2664
|
-
});
|
|
2665
|
-
};
|
|
2666
|
-
const addMultiple = (items) => {
|
|
2667
|
-
set((state) => {
|
|
2668
|
-
const newMap = new Map(state.textures);
|
|
2669
|
-
const entries = items instanceof Map ? items.entries() : Object.entries(items);
|
|
2670
|
-
for (const [key, value] of entries) {
|
|
2671
|
-
newMap.set(key, value);
|
|
2672
|
-
}
|
|
2673
|
-
return { textures: newMap };
|
|
2674
|
-
});
|
|
2675
|
-
};
|
|
2676
|
-
const remove = (key) => {
|
|
2677
|
-
set((state) => {
|
|
2678
|
-
const newMap = new Map(state.textures);
|
|
2679
|
-
newMap.delete(key);
|
|
2680
|
-
return { textures: newMap };
|
|
2681
|
-
});
|
|
2682
|
-
};
|
|
2683
|
-
const removeMultiple = (keys) => {
|
|
2684
|
-
set((state) => {
|
|
2685
|
-
const newMap = new Map(state.textures);
|
|
2686
|
-
for (const key of keys) newMap.delete(key);
|
|
2687
|
-
return { textures: newMap };
|
|
2688
|
-
});
|
|
2689
|
-
};
|
|
2690
|
-
const dispose = (key) => {
|
|
2691
|
-
const entry = getState().textures.get(key);
|
|
2692
|
-
if (entry) {
|
|
2693
|
-
const tex = getTextureValue(entry);
|
|
2694
|
-
tex?.dispose();
|
|
2695
|
-
}
|
|
2696
|
-
remove(key);
|
|
2697
|
-
};
|
|
2698
|
-
const disposeMultiple = (keys) => {
|
|
2699
|
-
const textures = getState().textures;
|
|
2700
|
-
for (const key of keys) {
|
|
2701
|
-
const entry = textures.get(key);
|
|
2702
|
-
if (entry) {
|
|
2703
|
-
const tex = getTextureValue(entry);
|
|
2704
|
-
tex?.dispose();
|
|
2705
|
-
}
|
|
2706
|
-
}
|
|
2707
|
-
removeMultiple(keys);
|
|
2708
|
-
};
|
|
2709
|
-
const disposeAll = () => {
|
|
2710
|
-
const textures = getState().textures;
|
|
2711
|
-
for (const entry of textures.values()) {
|
|
2712
|
-
const tex = getTextureValue(entry);
|
|
2713
|
-
tex?.dispose();
|
|
2714
|
-
}
|
|
2715
|
-
set({ textures: /* @__PURE__ */ new Map() });
|
|
2716
|
-
};
|
|
2264
|
+
const setState = store.setState;
|
|
2265
|
+
const getOne = (key) => getState().textures.get(key);
|
|
2717
2266
|
return {
|
|
2718
|
-
|
|
2719
|
-
get textures() {
|
|
2267
|
+
get all() {
|
|
2720
2268
|
return getState().textures;
|
|
2721
2269
|
},
|
|
2722
|
-
|
|
2723
|
-
|
|
2270
|
+
get(input) {
|
|
2271
|
+
if (typeof input === "string") return getOne(input);
|
|
2272
|
+
if (Array.isArray(input)) return input.map(getOne);
|
|
2273
|
+
const out = {};
|
|
2274
|
+
for (const name in input) out[name] = getOne(input[name]);
|
|
2275
|
+
return out;
|
|
2276
|
+
},
|
|
2724
2277
|
has: (key) => getState().textures.has(key),
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
2278
|
+
add(keyOrRecord, texture) {
|
|
2279
|
+
setState((state) => {
|
|
2280
|
+
const textures = new Map(state.textures);
|
|
2281
|
+
if (typeof keyOrRecord === "string") {
|
|
2282
|
+
textures.set(keyOrRecord, texture);
|
|
2283
|
+
} else {
|
|
2284
|
+
for (const key in keyOrRecord) textures.set(key, keyOrRecord[key]);
|
|
2285
|
+
}
|
|
2286
|
+
return { textures };
|
|
2287
|
+
});
|
|
2288
|
+
},
|
|
2289
|
+
dispose(key, options) {
|
|
2290
|
+
const state = getState();
|
|
2291
|
+
const refs = state._textureRefs.get(key) ?? 0;
|
|
2292
|
+
if (refs > 0 && !options?.force) {
|
|
2293
|
+
console.warn(
|
|
2294
|
+
`[useTextures] "${key}" still has ${refs} active reference(s); skipping dispose. Pass { force: true } to override.`
|
|
2295
|
+
);
|
|
2296
|
+
return false;
|
|
2297
|
+
}
|
|
2298
|
+
state.textures.get(key)?.dispose();
|
|
2299
|
+
setState((s) => {
|
|
2300
|
+
const textures = new Map(s.textures);
|
|
2301
|
+
textures.delete(key);
|
|
2302
|
+
const nextRefs = new Map(s._textureRefs);
|
|
2303
|
+
nextRefs.delete(key);
|
|
2304
|
+
return { textures, _textureRefs: nextRefs };
|
|
2305
|
+
});
|
|
2306
|
+
return true;
|
|
2307
|
+
},
|
|
2308
|
+
disposeAll() {
|
|
2309
|
+
for (const texture of getState().textures.values()) texture.dispose();
|
|
2310
|
+
setState({ textures: /* @__PURE__ */ new Map(), _textureRefs: /* @__PURE__ */ new Map() });
|
|
2311
|
+
}
|
|
2735
2312
|
};
|
|
2736
2313
|
}, [store]);
|
|
2314
|
+
const subscribe = selector ? () => selector(registry) : (state) => state.textures;
|
|
2315
|
+
const selected = useThree(subscribe);
|
|
2316
|
+
return selector ? selected : registry;
|
|
2737
2317
|
}
|
|
2738
2318
|
|
|
2739
|
-
function useRenderTarget(
|
|
2319
|
+
function useRenderTarget(widthOrOptions, heightOrOptions, options) {
|
|
2740
2320
|
const isLegacy = useThree((s) => s.isLegacy);
|
|
2741
2321
|
const size = useThree((s) => s.size);
|
|
2322
|
+
let width;
|
|
2323
|
+
let height;
|
|
2324
|
+
let opts;
|
|
2325
|
+
if (typeof widthOrOptions === "object") {
|
|
2326
|
+
opts = widthOrOptions;
|
|
2327
|
+
} else if (typeof widthOrOptions === "number") {
|
|
2328
|
+
width = widthOrOptions;
|
|
2329
|
+
if (typeof heightOrOptions === "object") {
|
|
2330
|
+
height = widthOrOptions;
|
|
2331
|
+
opts = heightOrOptions;
|
|
2332
|
+
} else if (typeof heightOrOptions === "number") {
|
|
2333
|
+
height = heightOrOptions;
|
|
2334
|
+
opts = options;
|
|
2335
|
+
} else {
|
|
2336
|
+
height = widthOrOptions;
|
|
2337
|
+
}
|
|
2338
|
+
}
|
|
2742
2339
|
return React.useMemo(() => {
|
|
2743
2340
|
const w = width ?? size.width;
|
|
2744
2341
|
const h = height ?? size.height;
|
|
2745
2342
|
{
|
|
2746
|
-
return isLegacy ? new three.WebGLRenderTarget(w, h,
|
|
2343
|
+
return isLegacy ? new three.WebGLRenderTarget(w, h, opts) : new webgpu.RenderTarget(w, h, opts);
|
|
2747
2344
|
}
|
|
2748
|
-
}, [width, height, size.width, size.height,
|
|
2345
|
+
}, [width, height, size.width, size.height, opts, isLegacy]);
|
|
2749
2346
|
}
|
|
2750
2347
|
|
|
2751
2348
|
function useStore() {
|
|
@@ -2773,7 +2370,7 @@ function addEffect(callback) {
|
|
|
2773
2370
|
link: "https://docs.pmnd.rs/react-three-fiber/api/hooks#useframe"
|
|
2774
2371
|
});
|
|
2775
2372
|
const id = `legacy_effect_${effectId++}`;
|
|
2776
|
-
return getScheduler().registerGlobal("before", id, callback);
|
|
2373
|
+
return scheduler.getScheduler().registerGlobal("before", id, callback);
|
|
2777
2374
|
}
|
|
2778
2375
|
function addAfterEffect(callback) {
|
|
2779
2376
|
notifyDepreciated({
|
|
@@ -2782,7 +2379,7 @@ function addAfterEffect(callback) {
|
|
|
2782
2379
|
link: "https://docs.pmnd.rs/react-three-fiber/api/hooks#useframe"
|
|
2783
2380
|
});
|
|
2784
2381
|
const id = `legacy_afterEffect_${effectId++}`;
|
|
2785
|
-
return getScheduler().registerGlobal("after", id, callback);
|
|
2382
|
+
return scheduler.getScheduler().registerGlobal("after", id, callback);
|
|
2786
2383
|
}
|
|
2787
2384
|
function addTail(callback) {
|
|
2788
2385
|
notifyDepreciated({
|
|
@@ -2790,13 +2387,13 @@ function addTail(callback) {
|
|
|
2790
2387
|
body: "Use scheduler.onIdle(callback) instead.\naddTail will be removed in a future version.",
|
|
2791
2388
|
link: "https://docs.pmnd.rs/react-three-fiber/api/hooks#useframe"
|
|
2792
2389
|
});
|
|
2793
|
-
return getScheduler().onIdle(callback);
|
|
2390
|
+
return scheduler.getScheduler().onIdle(callback);
|
|
2794
2391
|
}
|
|
2795
2392
|
function invalidate(state, frames = 1, stackFrames = false) {
|
|
2796
|
-
getScheduler().invalidate(frames, stackFrames);
|
|
2393
|
+
scheduler.getScheduler().invalidate(frames, stackFrames);
|
|
2797
2394
|
}
|
|
2798
|
-
function advance(timestamp
|
|
2799
|
-
getScheduler().step(timestamp);
|
|
2395
|
+
function advance(timestamp) {
|
|
2396
|
+
scheduler.getScheduler().step(timestamp);
|
|
2800
2397
|
}
|
|
2801
2398
|
|
|
2802
2399
|
const version = "10.0.0-alpha.2";
|
|
@@ -14249,6 +13846,7 @@ function swapInstances() {
|
|
|
14249
13846
|
instance.object = instance.props.object ?? new target(...instance.props.args ?? []);
|
|
14250
13847
|
instance.object.__r3f = instance;
|
|
14251
13848
|
setFiberRef(fiber, instance.object);
|
|
13849
|
+
delete instance.appliedOnce;
|
|
14252
13850
|
applyProps(instance.object, instance.props);
|
|
14253
13851
|
if (instance.props.attach) {
|
|
14254
13852
|
attach(parent, instance);
|
|
@@ -14322,8 +13920,22 @@ const reconciler = /* @__PURE__ */ createReconciler({
|
|
|
14322
13920
|
const isTailSibling = fiber.sibling === null || (fiber.flags & Update) === NoFlags;
|
|
14323
13921
|
if (isTailSibling) swapInstances();
|
|
14324
13922
|
},
|
|
14325
|
-
finalizeInitialChildren: () =>
|
|
14326
|
-
|
|
13923
|
+
finalizeInitialChildren: (instance) => {
|
|
13924
|
+
for (const prop in instance.props) {
|
|
13925
|
+
if (isFromRef(instance.props[prop])) return true;
|
|
13926
|
+
}
|
|
13927
|
+
return false;
|
|
13928
|
+
},
|
|
13929
|
+
commitMount(instance) {
|
|
13930
|
+
const resolved = {};
|
|
13931
|
+
for (const prop in instance.props) {
|
|
13932
|
+
const value = instance.props[prop];
|
|
13933
|
+
if (isFromRef(value)) {
|
|
13934
|
+
const ref = value[FROM_REF];
|
|
13935
|
+
if (ref.current != null) resolved[prop] = ref.current;
|
|
13936
|
+
}
|
|
13937
|
+
}
|
|
13938
|
+
if (Object.keys(resolved).length) applyProps(instance.object, resolved);
|
|
14327
13939
|
},
|
|
14328
13940
|
getPublicInstance: (instance) => instance?.object,
|
|
14329
13941
|
prepareForCommit: () => null,
|
|
@@ -14544,6 +14156,9 @@ function createRoot(canvas) {
|
|
|
14544
14156
|
let resolve;
|
|
14545
14157
|
pending = new Promise((_resolve) => resolve = _resolve);
|
|
14546
14158
|
const {
|
|
14159
|
+
id: canvasId,
|
|
14160
|
+
primaryCanvas,
|
|
14161
|
+
scheduler: schedulerConfig,
|
|
14547
14162
|
gl: glConfig,
|
|
14548
14163
|
renderer: rendererConfig,
|
|
14549
14164
|
size: propsSize,
|
|
@@ -14551,10 +14166,6 @@ function createRoot(canvas) {
|
|
|
14551
14166
|
events,
|
|
14552
14167
|
onCreated: onCreatedCallback,
|
|
14553
14168
|
shadows = false,
|
|
14554
|
-
linear = false,
|
|
14555
|
-
flat = false,
|
|
14556
|
-
textureColorSpace = webgpu.SRGBColorSpace,
|
|
14557
|
-
legacy = false,
|
|
14558
14169
|
orthographic = false,
|
|
14559
14170
|
frameloop = "always",
|
|
14560
14171
|
dpr = [1, 2],
|
|
@@ -14566,8 +14177,10 @@ function createRoot(canvas) {
|
|
|
14566
14177
|
onDropMissed,
|
|
14567
14178
|
autoUpdateFrustum = true,
|
|
14568
14179
|
occlusion = false,
|
|
14569
|
-
_sizeProps
|
|
14180
|
+
_sizeProps,
|
|
14181
|
+
forceEven
|
|
14570
14182
|
} = props;
|
|
14183
|
+
const textureColorSpace = is.obj(glConfig) && !is.fun(glConfig) && !isRenderer(glConfig) && glConfig.textureColorSpace || is.obj(rendererConfig) && !is.fun(rendererConfig) && !isRenderer(rendererConfig) && rendererConfig.textureColorSpace || webgpu.SRGBColorSpace;
|
|
14571
14184
|
const state = store.getState();
|
|
14572
14185
|
const defaultGLProps = {
|
|
14573
14186
|
canvas,
|
|
@@ -14576,7 +14189,8 @@ function createRoot(canvas) {
|
|
|
14576
14189
|
alpha: true
|
|
14577
14190
|
};
|
|
14578
14191
|
const defaultGPUProps = {
|
|
14579
|
-
canvas
|
|
14192
|
+
canvas,
|
|
14193
|
+
antialias: true
|
|
14580
14194
|
};
|
|
14581
14195
|
const wantsGL = (state.isLegacy || glConfig || !R3F_BUILD_WEBGPU || !rendererConfig);
|
|
14582
14196
|
if (glConfig && rendererConfig) {
|
|
@@ -14590,19 +14204,61 @@ function createRoot(canvas) {
|
|
|
14590
14204
|
});
|
|
14591
14205
|
}
|
|
14592
14206
|
let renderer = state.internal.actualRenderer;
|
|
14207
|
+
if (primaryCanvas && wantsGL) {
|
|
14208
|
+
throw new Error(
|
|
14209
|
+
"The `primaryCanvas` prop for multi-canvas rendering cannot be used with WebGL. Remove the `gl` prop or use WebGPU."
|
|
14210
|
+
);
|
|
14211
|
+
}
|
|
14593
14212
|
if (wantsGL && !state.internal.actualRenderer) {
|
|
14594
14213
|
renderer = await resolveRenderer(glConfig, defaultGLProps, three.WebGLRenderer);
|
|
14595
14214
|
state.internal.actualRenderer = renderer;
|
|
14596
|
-
state.set({ isLegacy: true, gl: renderer, renderer });
|
|
14215
|
+
state.set({ isLegacy: true, gl: renderer, renderer, primaryStore: store });
|
|
14216
|
+
} else if (!wantsGL && primaryCanvas && !state.internal.actualRenderer) {
|
|
14217
|
+
const primary = await waitForPrimary(primaryCanvas);
|
|
14218
|
+
renderer = primary.renderer;
|
|
14219
|
+
state.internal.actualRenderer = renderer;
|
|
14220
|
+
const canvasTarget = new webgpu.CanvasTarget(canvas);
|
|
14221
|
+
primary.store.setState((prev) => ({
|
|
14222
|
+
internal: { ...prev.internal, isMultiCanvas: true }
|
|
14223
|
+
}));
|
|
14224
|
+
state.set((prev) => ({
|
|
14225
|
+
webGPUSupported: primary.store.getState().webGPUSupported,
|
|
14226
|
+
renderer,
|
|
14227
|
+
primaryStore: primary.store,
|
|
14228
|
+
internal: {
|
|
14229
|
+
...prev.internal,
|
|
14230
|
+
canvasTarget,
|
|
14231
|
+
isMultiCanvas: true,
|
|
14232
|
+
isSecondary: true,
|
|
14233
|
+
targetId: primaryCanvas
|
|
14234
|
+
}
|
|
14235
|
+
}));
|
|
14597
14236
|
} else if (!wantsGL && !state.internal.actualRenderer) {
|
|
14598
14237
|
renderer = await resolveRenderer(rendererConfig, defaultGPUProps, webgpu.WebGPURenderer);
|
|
14599
14238
|
if (!renderer.hasInitialized?.()) {
|
|
14239
|
+
const size2 = computeInitialSize(canvas, propsSize);
|
|
14240
|
+
if (size2.width > 0 && size2.height > 0) {
|
|
14241
|
+
const pixelRatio = calculateDpr(dpr);
|
|
14242
|
+
canvas.width = size2.width * pixelRatio;
|
|
14243
|
+
canvas.height = size2.height * pixelRatio;
|
|
14244
|
+
}
|
|
14600
14245
|
await renderer.init();
|
|
14601
14246
|
}
|
|
14602
14247
|
const backend = renderer.backend;
|
|
14603
14248
|
const isWebGPUBackend = backend && "isWebGPUBackend" in backend;
|
|
14604
14249
|
state.internal.actualRenderer = renderer;
|
|
14605
|
-
state.set({ webGPUSupported: isWebGPUBackend, renderer });
|
|
14250
|
+
state.set({ webGPUSupported: isWebGPUBackend, renderer, primaryStore: store });
|
|
14251
|
+
if (canvasId && !state.internal.isSecondary) {
|
|
14252
|
+
const canvasTarget = new webgpu.CanvasTarget(canvas);
|
|
14253
|
+
const unregisterPrimary = registerPrimary(canvasId, renderer, store);
|
|
14254
|
+
state.set((prev) => ({
|
|
14255
|
+
internal: {
|
|
14256
|
+
...prev.internal,
|
|
14257
|
+
canvasTarget,
|
|
14258
|
+
unregisterPrimary
|
|
14259
|
+
}
|
|
14260
|
+
}));
|
|
14261
|
+
}
|
|
14606
14262
|
}
|
|
14607
14263
|
let raycaster = state.raycaster;
|
|
14608
14264
|
if (!raycaster) state.set({ raycaster: raycaster = new webgpu.Raycaster() });
|
|
@@ -14611,6 +14267,7 @@ function createRoot(canvas) {
|
|
|
14611
14267
|
if (!is.equ(params, raycaster.params, shallowLoose)) {
|
|
14612
14268
|
applyProps(raycaster, { params: { ...raycaster.params, ...params } });
|
|
14613
14269
|
}
|
|
14270
|
+
let tempCamera = state.camera;
|
|
14614
14271
|
if (!state.camera || state.camera === lastCamera && !is.equ(lastCamera, cameraOptions, shallowLoose)) {
|
|
14615
14272
|
lastCamera = cameraOptions;
|
|
14616
14273
|
const isCamera = cameraOptions?.isCamera;
|
|
@@ -14630,6 +14287,7 @@ function createRoot(canvas) {
|
|
|
14630
14287
|
if (!state.camera && !cameraOptions?.rotation) camera.lookAt(0, 0, 0);
|
|
14631
14288
|
}
|
|
14632
14289
|
state.set({ camera });
|
|
14290
|
+
tempCamera = camera;
|
|
14633
14291
|
raycaster.camera = camera;
|
|
14634
14292
|
}
|
|
14635
14293
|
if (!state.scene) {
|
|
@@ -14647,7 +14305,7 @@ function createRoot(canvas) {
|
|
|
14647
14305
|
rootScene: scene,
|
|
14648
14306
|
internal: { ...prev.internal, container: scene }
|
|
14649
14307
|
}));
|
|
14650
|
-
const camera =
|
|
14308
|
+
const camera = tempCamera;
|
|
14651
14309
|
if (camera && !camera.parent) scene.add(camera);
|
|
14652
14310
|
}
|
|
14653
14311
|
if (events && !state.events.handlers) {
|
|
@@ -14664,6 +14322,9 @@ function createRoot(canvas) {
|
|
|
14664
14322
|
if (_sizeProps !== void 0) {
|
|
14665
14323
|
state.set({ _sizeProps });
|
|
14666
14324
|
}
|
|
14325
|
+
if (forceEven !== void 0 && state.internal.forceEven !== forceEven) {
|
|
14326
|
+
state.set((prev) => ({ internal: { ...prev.internal, forceEven } }));
|
|
14327
|
+
}
|
|
14667
14328
|
const size = computeInitialSize(canvas, propsSize);
|
|
14668
14329
|
if (!state._sizeImperative && !is.equ(size, state.size, shallowLoose)) {
|
|
14669
14330
|
const wasImperative = state._sizeImperative;
|
|
@@ -14690,10 +14351,10 @@ function createRoot(canvas) {
|
|
|
14690
14351
|
lastConfiguredProps.performance = performance;
|
|
14691
14352
|
}
|
|
14692
14353
|
if (!state.xr) {
|
|
14693
|
-
const handleXRFrame = (timestamp,
|
|
14354
|
+
const handleXRFrame = (timestamp, _frame) => {
|
|
14694
14355
|
const state2 = store.getState();
|
|
14695
14356
|
if (state2.frameloop === "never") return;
|
|
14696
|
-
advance(timestamp
|
|
14357
|
+
advance(timestamp);
|
|
14697
14358
|
};
|
|
14698
14359
|
const actualRenderer = state.internal.actualRenderer;
|
|
14699
14360
|
const handleSessionChange = () => {
|
|
@@ -14705,16 +14366,16 @@ function createRoot(canvas) {
|
|
|
14705
14366
|
};
|
|
14706
14367
|
const xr = {
|
|
14707
14368
|
connect() {
|
|
14708
|
-
const { gl, renderer: renderer2
|
|
14709
|
-
const
|
|
14710
|
-
|
|
14711
|
-
|
|
14369
|
+
const { gl, renderer: renderer2 } = store.getState();
|
|
14370
|
+
const xrManager = (renderer2 || gl).xr;
|
|
14371
|
+
xrManager.addEventListener("sessionstart", handleSessionChange);
|
|
14372
|
+
xrManager.addEventListener("sessionend", handleSessionChange);
|
|
14712
14373
|
},
|
|
14713
14374
|
disconnect() {
|
|
14714
|
-
const { gl, renderer: renderer2
|
|
14715
|
-
const
|
|
14716
|
-
|
|
14717
|
-
|
|
14375
|
+
const { gl, renderer: renderer2 } = store.getState();
|
|
14376
|
+
const xrManager = (renderer2 || gl).xr;
|
|
14377
|
+
xrManager.removeEventListener("sessionstart", handleSessionChange);
|
|
14378
|
+
xrManager.removeEventListener("sessionend", handleSessionChange);
|
|
14718
14379
|
}
|
|
14719
14380
|
};
|
|
14720
14381
|
if (typeof renderer.xr?.addEventListener === "function") xr.connect();
|
|
@@ -14726,15 +14387,22 @@ function createRoot(canvas) {
|
|
|
14726
14387
|
const oldType = renderer.shadowMap.type;
|
|
14727
14388
|
renderer.shadowMap.enabled = !!shadows;
|
|
14728
14389
|
if (is.boo(shadows)) {
|
|
14729
|
-
renderer.shadowMap.type = webgpu.
|
|
14390
|
+
renderer.shadowMap.type = webgpu.PCFShadowMap;
|
|
14730
14391
|
} else if (is.str(shadows)) {
|
|
14392
|
+
if (shadows === "soft") {
|
|
14393
|
+
notifyDepreciated({
|
|
14394
|
+
heading: 'shadows="soft" is deprecated',
|
|
14395
|
+
body: "Three has depreciated soft and improved basic PCFShadows, we converted for you.",
|
|
14396
|
+
link: "https://github.com/mrdoob/three.js/wiki/Migration-Guide?utm_source=chatgpt.com#181--182"
|
|
14397
|
+
});
|
|
14398
|
+
}
|
|
14731
14399
|
const types = {
|
|
14732
14400
|
basic: webgpu.BasicShadowMap,
|
|
14733
14401
|
percentage: webgpu.PCFShadowMap,
|
|
14734
|
-
soft: webgpu.
|
|
14402
|
+
soft: webgpu.PCFShadowMap,
|
|
14735
14403
|
variance: webgpu.VSMShadowMap
|
|
14736
14404
|
};
|
|
14737
|
-
renderer.shadowMap.type = types[shadows] ?? webgpu.
|
|
14405
|
+
renderer.shadowMap.type = types[shadows] ?? webgpu.PCFShadowMap;
|
|
14738
14406
|
} else if (is.obj(shadows)) {
|
|
14739
14407
|
Object.assign(renderer.shadowMap, shadows);
|
|
14740
14408
|
}
|
|
@@ -14742,57 +14410,70 @@ function createRoot(canvas) {
|
|
|
14742
14410
|
renderer.shadowMap.needsUpdate = true;
|
|
14743
14411
|
}
|
|
14744
14412
|
}
|
|
14745
|
-
{
|
|
14746
|
-
|
|
14747
|
-
|
|
14748
|
-
const flatChanged = flat !== lastConfiguredProps.flat;
|
|
14749
|
-
if (legacyChanged) {
|
|
14750
|
-
if (legacy) {
|
|
14751
|
-
notifyDepreciated({
|
|
14752
|
-
heading: "Legacy Color Management",
|
|
14753
|
-
body: "Legacy color management is deprecated and will be removed in a future version.",
|
|
14754
|
-
link: "https://docs.pmnd.rs/react-three-fiber/api/hooks#useframe"
|
|
14755
|
-
});
|
|
14756
|
-
}
|
|
14757
|
-
}
|
|
14758
|
-
if (legacyChanged) {
|
|
14759
|
-
webgpu.ColorManagement.enabled = !legacy;
|
|
14760
|
-
lastConfiguredProps.legacy = legacy;
|
|
14761
|
-
}
|
|
14762
|
-
if (!configured || linearChanged) {
|
|
14763
|
-
renderer.outputColorSpace = linear ? webgpu.LinearSRGBColorSpace : webgpu.SRGBColorSpace;
|
|
14764
|
-
lastConfiguredProps.linear = linear;
|
|
14765
|
-
}
|
|
14766
|
-
if (!configured || flatChanged) {
|
|
14767
|
-
renderer.toneMapping = flat ? webgpu.NoToneMapping : webgpu.ACESFilmicToneMapping;
|
|
14768
|
-
lastConfiguredProps.flat = flat;
|
|
14769
|
-
}
|
|
14770
|
-
if (legacyChanged && state.legacy !== legacy) state.set(() => ({ legacy }));
|
|
14771
|
-
if (linearChanged && state.linear !== linear) state.set(() => ({ linear }));
|
|
14772
|
-
if (flatChanged && state.flat !== flat) state.set(() => ({ flat }));
|
|
14413
|
+
if (!configured) {
|
|
14414
|
+
renderer.outputColorSpace = webgpu.SRGBColorSpace;
|
|
14415
|
+
renderer.toneMapping = webgpu.ACESFilmicToneMapping;
|
|
14773
14416
|
}
|
|
14774
14417
|
if (textureColorSpace !== lastConfiguredProps.textureColorSpace) {
|
|
14775
14418
|
if (state.textureColorSpace !== textureColorSpace) state.set(() => ({ textureColorSpace }));
|
|
14776
14419
|
lastConfiguredProps.textureColorSpace = textureColorSpace;
|
|
14777
14420
|
}
|
|
14421
|
+
const r3fProps = ["textureColorSpace"];
|
|
14422
|
+
const constructorOnlyProps = ["samples", "antialias", "alpha", "canvas", "powerPreference"];
|
|
14423
|
+
const nonApplyProps = [...r3fProps, ...constructorOnlyProps];
|
|
14778
14424
|
if (glConfig && !is.fun(glConfig) && !isRenderer(glConfig) && !is.equ(glConfig, renderer, shallowLoose)) {
|
|
14779
|
-
|
|
14425
|
+
const glProps = {};
|
|
14426
|
+
for (const key in glConfig) {
|
|
14427
|
+
if (!nonApplyProps.includes(key)) glProps[key] = glConfig[key];
|
|
14428
|
+
}
|
|
14429
|
+
applyProps(renderer, glProps);
|
|
14780
14430
|
}
|
|
14781
14431
|
if (rendererConfig && !is.fun(rendererConfig) && !isRenderer(rendererConfig) && state.renderer) {
|
|
14782
14432
|
const currentRenderer = state.renderer;
|
|
14783
14433
|
if (!is.equ(rendererConfig, currentRenderer, shallowLoose)) {
|
|
14784
|
-
|
|
14434
|
+
const rendererProps = {};
|
|
14435
|
+
for (const key in rendererConfig) {
|
|
14436
|
+
if (!nonApplyProps.includes(key)) rendererProps[key] = rendererConfig[key];
|
|
14437
|
+
}
|
|
14438
|
+
applyProps(currentRenderer, rendererProps);
|
|
14785
14439
|
}
|
|
14786
14440
|
}
|
|
14787
|
-
const scheduler = getScheduler();
|
|
14441
|
+
const scheduler$1 = scheduler.getScheduler();
|
|
14788
14442
|
const rootId = state.internal.rootId;
|
|
14789
14443
|
if (!rootId) {
|
|
14790
|
-
const newRootId = scheduler.generateRootId();
|
|
14791
|
-
const unregisterRoot = scheduler.registerRoot(newRootId, {
|
|
14444
|
+
const newRootId = canvasId || scheduler$1.generateRootId();
|
|
14445
|
+
const unregisterRoot = scheduler$1.registerRoot(newRootId, {
|
|
14792
14446
|
getState: () => store.getState(),
|
|
14793
14447
|
onError: (err) => store.getState().setError(err)
|
|
14794
14448
|
});
|
|
14795
|
-
const
|
|
14449
|
+
const unregisterCanvasTarget = scheduler$1.register(
|
|
14450
|
+
() => {
|
|
14451
|
+
const state2 = store.getState();
|
|
14452
|
+
if (state2.internal.isMultiCanvas && state2.internal.canvasTarget) {
|
|
14453
|
+
const renderer2 = state2.internal.actualRenderer;
|
|
14454
|
+
renderer2.setCanvasTarget(state2.internal.canvasTarget);
|
|
14455
|
+
}
|
|
14456
|
+
},
|
|
14457
|
+
{
|
|
14458
|
+
id: `${newRootId}_canvasTarget`,
|
|
14459
|
+
rootId: newRootId,
|
|
14460
|
+
phase: "start",
|
|
14461
|
+
system: true
|
|
14462
|
+
}
|
|
14463
|
+
);
|
|
14464
|
+
const unregisterEventsFlush = scheduler$1.register(
|
|
14465
|
+
() => {
|
|
14466
|
+
const state2 = store.getState();
|
|
14467
|
+
state2.events.flush?.();
|
|
14468
|
+
},
|
|
14469
|
+
{
|
|
14470
|
+
id: `${newRootId}_events`,
|
|
14471
|
+
rootId: newRootId,
|
|
14472
|
+
phase: "input",
|
|
14473
|
+
system: true
|
|
14474
|
+
}
|
|
14475
|
+
);
|
|
14476
|
+
const unregisterFrustum = scheduler$1.register(
|
|
14796
14477
|
() => {
|
|
14797
14478
|
const state2 = store.getState();
|
|
14798
14479
|
if (state2.autoUpdateFrustum && state2.camera) {
|
|
@@ -14802,11 +14483,11 @@ function createRoot(canvas) {
|
|
|
14802
14483
|
{
|
|
14803
14484
|
id: `${newRootId}_frustum`,
|
|
14804
14485
|
rootId: newRootId,
|
|
14805
|
-
|
|
14486
|
+
before: "render",
|
|
14806
14487
|
system: true
|
|
14807
14488
|
}
|
|
14808
14489
|
);
|
|
14809
|
-
const unregisterVisibility = scheduler.register(
|
|
14490
|
+
const unregisterVisibility = scheduler$1.register(
|
|
14810
14491
|
() => {
|
|
14811
14492
|
const state2 = store.getState();
|
|
14812
14493
|
checkVisibility(state2);
|
|
@@ -14814,30 +14495,34 @@ function createRoot(canvas) {
|
|
|
14814
14495
|
{
|
|
14815
14496
|
id: `${newRootId}_visibility`,
|
|
14816
14497
|
rootId: newRootId,
|
|
14817
|
-
|
|
14498
|
+
before: "render",
|
|
14818
14499
|
system: true,
|
|
14819
14500
|
after: `${newRootId}_frustum`
|
|
14820
14501
|
}
|
|
14821
14502
|
);
|
|
14822
|
-
const unregisterRender = scheduler.register(
|
|
14503
|
+
const unregisterRender = scheduler$1.register(
|
|
14823
14504
|
() => {
|
|
14824
14505
|
const state2 = store.getState();
|
|
14825
14506
|
const renderer2 = state2.internal.actualRenderer;
|
|
14826
|
-
const userHandlesRender = scheduler.hasUserJobsInPhase("render", newRootId);
|
|
14507
|
+
const userHandlesRender = scheduler$1.hasUserJobsInPhase("render", newRootId);
|
|
14827
14508
|
if (userHandlesRender || state2.internal.priority) return;
|
|
14828
14509
|
try {
|
|
14829
|
-
if (state2.
|
|
14510
|
+
if (state2.renderPipeline?.render) state2.renderPipeline.render();
|
|
14830
14511
|
else if (renderer2?.render) renderer2.render(state2.scene, state2.camera);
|
|
14831
14512
|
} catch (error) {
|
|
14832
14513
|
state2.setError(error instanceof Error ? error : new Error(String(error)));
|
|
14833
14514
|
}
|
|
14834
14515
|
},
|
|
14835
14516
|
{
|
|
14836
|
-
|
|
14517
|
+
// Use canvas ID directly as job ID if available, otherwise use generated rootId
|
|
14518
|
+
id: canvasId || `${newRootId}_render`,
|
|
14837
14519
|
rootId: newRootId,
|
|
14838
14520
|
phase: "render",
|
|
14839
|
-
system: true
|
|
14521
|
+
system: true,
|
|
14840
14522
|
// Internal flag: this is a system job, not user-controlled
|
|
14523
|
+
// Apply scheduler config for render ordering and rate limiting
|
|
14524
|
+
...schedulerConfig?.after && { after: schedulerConfig.after },
|
|
14525
|
+
...schedulerConfig?.fps && { fps: schedulerConfig.fps }
|
|
14841
14526
|
}
|
|
14842
14527
|
);
|
|
14843
14528
|
state.set((state2) => ({
|
|
@@ -14846,15 +14531,17 @@ function createRoot(canvas) {
|
|
|
14846
14531
|
rootId: newRootId,
|
|
14847
14532
|
unregisterRoot: () => {
|
|
14848
14533
|
unregisterRoot();
|
|
14534
|
+
unregisterCanvasTarget();
|
|
14535
|
+
unregisterEventsFlush();
|
|
14849
14536
|
unregisterFrustum();
|
|
14850
14537
|
unregisterVisibility();
|
|
14851
14538
|
unregisterRender();
|
|
14852
14539
|
},
|
|
14853
|
-
scheduler
|
|
14540
|
+
scheduler: scheduler$1
|
|
14854
14541
|
}
|
|
14855
14542
|
}));
|
|
14856
14543
|
}
|
|
14857
|
-
scheduler.frameloop = frameloop;
|
|
14544
|
+
scheduler$1.frameloop = frameloop;
|
|
14858
14545
|
onCreated = onCreatedCallback;
|
|
14859
14546
|
configured = true;
|
|
14860
14547
|
resolve();
|
|
@@ -14904,15 +14591,24 @@ function unmountComponentAtNode(canvas, callback) {
|
|
|
14904
14591
|
const renderer = state.internal.actualRenderer;
|
|
14905
14592
|
const unregisterRoot = state.internal.unregisterRoot;
|
|
14906
14593
|
if (unregisterRoot) unregisterRoot();
|
|
14594
|
+
const unregisterPrimary = state.internal.unregisterPrimary;
|
|
14595
|
+
if (unregisterPrimary) unregisterPrimary();
|
|
14596
|
+
const canvasTarget = state.internal.canvasTarget;
|
|
14597
|
+
if (canvasTarget?.dispose) canvasTarget.dispose();
|
|
14907
14598
|
state.events.disconnect?.();
|
|
14908
14599
|
cleanupHelperGroup(root.store);
|
|
14909
|
-
renderer
|
|
14910
|
-
|
|
14911
|
-
|
|
14600
|
+
if (state.isLegacy && renderer) {
|
|
14601
|
+
;
|
|
14602
|
+
renderer.renderLists?.dispose?.();
|
|
14603
|
+
renderer.forceContextLoss?.();
|
|
14604
|
+
}
|
|
14605
|
+
if (!state.internal.isSecondary) {
|
|
14606
|
+
if (renderer?.xr) state.xr.disconnect();
|
|
14607
|
+
}
|
|
14912
14608
|
dispose(state.scene);
|
|
14913
14609
|
_roots.delete(canvas);
|
|
14914
14610
|
if (callback) callback(canvas);
|
|
14915
|
-
} catch
|
|
14611
|
+
} catch {
|
|
14916
14612
|
}
|
|
14917
14613
|
}, 500);
|
|
14918
14614
|
}
|
|
@@ -14920,36 +14616,34 @@ function unmountComponentAtNode(canvas, callback) {
|
|
|
14920
14616
|
}
|
|
14921
14617
|
}
|
|
14922
14618
|
function createPortal(children, container, state) {
|
|
14923
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
14619
|
+
return /* @__PURE__ */ jsxRuntime.jsx(Portal, { children, container, state });
|
|
14924
14620
|
}
|
|
14925
|
-
function
|
|
14621
|
+
function Portal({ children, container, state }) {
|
|
14926
14622
|
const isRef = React.useCallback((obj) => obj && "current" in obj, []);
|
|
14927
|
-
const [resolvedContainer,
|
|
14623
|
+
const [resolvedContainer, _setResolvedContainer] = React.useState(() => {
|
|
14928
14624
|
if (isRef(container)) return container.current ?? null;
|
|
14929
14625
|
return container;
|
|
14930
14626
|
});
|
|
14627
|
+
const setResolvedContainer = React.useCallback(
|
|
14628
|
+
(newContainer) => {
|
|
14629
|
+
if (!newContainer || newContainer === resolvedContainer) return;
|
|
14630
|
+
_setResolvedContainer(isRef(newContainer) ? newContainer.current : newContainer);
|
|
14631
|
+
},
|
|
14632
|
+
[resolvedContainer, _setResolvedContainer, isRef]
|
|
14633
|
+
);
|
|
14931
14634
|
React.useMemo(() => {
|
|
14932
|
-
if (isRef(container)) {
|
|
14933
|
-
|
|
14934
|
-
|
|
14935
|
-
|
|
14936
|
-
const updated = container.current;
|
|
14937
|
-
if (updated && updated !== resolvedContainer) {
|
|
14938
|
-
setResolvedContainer(updated);
|
|
14939
|
-
}
|
|
14940
|
-
});
|
|
14941
|
-
} else if (current !== resolvedContainer) {
|
|
14942
|
-
setResolvedContainer(current);
|
|
14943
|
-
}
|
|
14944
|
-
} else if (container !== resolvedContainer) {
|
|
14945
|
-
setResolvedContainer(container);
|
|
14635
|
+
if (isRef(container) && !container.current) {
|
|
14636
|
+
return queueMicrotask(() => {
|
|
14637
|
+
setResolvedContainer(container.current);
|
|
14638
|
+
});
|
|
14946
14639
|
}
|
|
14947
|
-
|
|
14640
|
+
setResolvedContainer(container);
|
|
14641
|
+
}, [container, isRef, setResolvedContainer]);
|
|
14948
14642
|
if (!resolvedContainer) return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, {});
|
|
14949
14643
|
const portalKey = resolvedContainer.uuid ?? `portal-${resolvedContainer.id ?? "unknown"}`;
|
|
14950
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
14644
|
+
return /* @__PURE__ */ jsxRuntime.jsx(PortalInner, { children, container: resolvedContainer, state }, portalKey);
|
|
14951
14645
|
}
|
|
14952
|
-
function
|
|
14646
|
+
function PortalInner({ state = {}, children, container }) {
|
|
14953
14647
|
const { events, size, injectScene = true, ...rest } = state;
|
|
14954
14648
|
const previousRoot = useStore();
|
|
14955
14649
|
const [raycaster] = React.useState(() => new webgpu.Raycaster());
|
|
@@ -14970,11 +14664,12 @@ function Portal({ state = {}, children, container }) {
|
|
|
14970
14664
|
};
|
|
14971
14665
|
}, [portalScene, container, injectScene]);
|
|
14972
14666
|
const inject = useMutableCallback((rootState, injectState) => {
|
|
14667
|
+
const resolvedSize = { ...rootState.size, ...injectState.size, ...size };
|
|
14973
14668
|
let viewport = void 0;
|
|
14974
|
-
if (injectState.camera && size) {
|
|
14669
|
+
if (injectState.camera && (size || injectState.size)) {
|
|
14975
14670
|
const camera = injectState.camera;
|
|
14976
|
-
viewport = rootState.viewport.getCurrentViewport(camera, new webgpu.Vector3(),
|
|
14977
|
-
if (camera !== rootState.camera) updateCamera(camera,
|
|
14671
|
+
viewport = rootState.viewport.getCurrentViewport(camera, new webgpu.Vector3(), resolvedSize);
|
|
14672
|
+
if (camera !== rootState.camera) updateCamera(camera, resolvedSize);
|
|
14978
14673
|
}
|
|
14979
14674
|
return {
|
|
14980
14675
|
// The intersect consists of the previous root state
|
|
@@ -14991,7 +14686,7 @@ function Portal({ state = {}, children, container }) {
|
|
|
14991
14686
|
previousRoot,
|
|
14992
14687
|
// Events, size and viewport can be overridden by the inject layer
|
|
14993
14688
|
events: { ...rootState.events, ...injectState.events, ...events },
|
|
14994
|
-
size:
|
|
14689
|
+
size: resolvedSize,
|
|
14995
14690
|
viewport: { ...rootState.viewport, ...viewport },
|
|
14996
14691
|
// Layers are allowed to override events
|
|
14997
14692
|
setEvents: (events2) => injectState.set((state2) => ({ ...state2, events: { ...state2.events, ...events2 } })),
|
|
@@ -15003,9 +14698,13 @@ function Portal({ state = {}, children, container }) {
|
|
|
15003
14698
|
const store = traditional.createWithEqualityFn((set, get) => ({ ...rest, set, get }));
|
|
15004
14699
|
const onMutate = (prev) => store.setState((state2) => inject.current(prev, state2));
|
|
15005
14700
|
onMutate(previousRoot.getState());
|
|
15006
|
-
previousRoot.subscribe(onMutate);
|
|
15007
14701
|
return store;
|
|
15008
14702
|
}, [previousRoot, container]);
|
|
14703
|
+
useIsomorphicLayoutEffect(() => {
|
|
14704
|
+
const onMutate = (prev) => usePortalStore.setState((state2) => inject.current(prev, state2));
|
|
14705
|
+
const unsubscribe = previousRoot.subscribe(onMutate);
|
|
14706
|
+
return unsubscribe;
|
|
14707
|
+
}, [previousRoot, usePortalStore]);
|
|
15009
14708
|
return (
|
|
15010
14709
|
// @ts-ignore, reconciler types are not maintained
|
|
15011
14710
|
/* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: reconciler.createPortal(
|
|
@@ -15019,21 +14718,59 @@ function flushSync(fn) {
|
|
|
15019
14718
|
return reconciler.flushSyncFromReconciler(fn);
|
|
15020
14719
|
}
|
|
15021
14720
|
|
|
14721
|
+
function parseBackground(background) {
|
|
14722
|
+
if (!background) return null;
|
|
14723
|
+
if (typeof background === "object" && !background.isColor) {
|
|
14724
|
+
const { backgroundMap, envMap, files, preset, ...rest } = background;
|
|
14725
|
+
return {
|
|
14726
|
+
...rest,
|
|
14727
|
+
preset,
|
|
14728
|
+
files: envMap || files,
|
|
14729
|
+
backgroundFiles: backgroundMap,
|
|
14730
|
+
background: true
|
|
14731
|
+
};
|
|
14732
|
+
}
|
|
14733
|
+
if (typeof background === "number") {
|
|
14734
|
+
return { color: background, background: true };
|
|
14735
|
+
}
|
|
14736
|
+
if (typeof background === "string") {
|
|
14737
|
+
if (background in presetsObj) {
|
|
14738
|
+
return { preset: background, background: true };
|
|
14739
|
+
}
|
|
14740
|
+
if (/^(https?:\/\/|\/|\.\/|\.\.\/)|\.(hdr|exr|jpg|jpeg|png|webp|gif)$/i.test(background)) {
|
|
14741
|
+
return { files: background, background: true };
|
|
14742
|
+
}
|
|
14743
|
+
return { color: background, background: true };
|
|
14744
|
+
}
|
|
14745
|
+
if (background.isColor) {
|
|
14746
|
+
return { color: background, background: true };
|
|
14747
|
+
}
|
|
14748
|
+
return null;
|
|
14749
|
+
}
|
|
14750
|
+
|
|
14751
|
+
function clearHmrCaches(store) {
|
|
14752
|
+
store.setState((state) => ({
|
|
14753
|
+
nodes: {},
|
|
14754
|
+
uniforms: {},
|
|
14755
|
+
buffers: {},
|
|
14756
|
+
gpuStorage: {},
|
|
14757
|
+
_hmrVersion: state._hmrVersion + 1
|
|
14758
|
+
}));
|
|
14759
|
+
}
|
|
14760
|
+
|
|
15022
14761
|
function CanvasImpl({
|
|
15023
14762
|
ref,
|
|
15024
14763
|
children,
|
|
15025
14764
|
fallback,
|
|
15026
14765
|
resize,
|
|
15027
14766
|
style,
|
|
14767
|
+
id,
|
|
15028
14768
|
gl,
|
|
15029
|
-
renderer,
|
|
14769
|
+
renderer: rendererProp,
|
|
15030
14770
|
events = createPointerEvents,
|
|
15031
14771
|
eventSource,
|
|
15032
14772
|
eventPrefix,
|
|
15033
14773
|
shadows,
|
|
15034
|
-
linear,
|
|
15035
|
-
flat,
|
|
15036
|
-
legacy,
|
|
15037
14774
|
orthographic,
|
|
15038
14775
|
frameloop,
|
|
15039
14776
|
dpr,
|
|
@@ -15041,6 +14778,8 @@ function CanvasImpl({
|
|
|
15041
14778
|
raycaster,
|
|
15042
14779
|
camera,
|
|
15043
14780
|
scene,
|
|
14781
|
+
autoUpdateFrustum,
|
|
14782
|
+
occlusion,
|
|
15044
14783
|
onPointerMissed,
|
|
15045
14784
|
onDragOverMissed,
|
|
15046
14785
|
onDropMissed,
|
|
@@ -15048,10 +14787,25 @@ function CanvasImpl({
|
|
|
15048
14787
|
hmr,
|
|
15049
14788
|
width,
|
|
15050
14789
|
height,
|
|
14790
|
+
background,
|
|
14791
|
+
forceEven,
|
|
15051
14792
|
...props
|
|
15052
14793
|
}) {
|
|
14794
|
+
const isRendererConfig = typeof rendererProp === "object" && rendererProp !== null && !("render" in rendererProp) && ("primaryCanvas" in rendererProp || "scheduler" in rendererProp);
|
|
14795
|
+
let primaryCanvas;
|
|
14796
|
+
let scheduler;
|
|
14797
|
+
let renderer;
|
|
14798
|
+
if (isRendererConfig) {
|
|
14799
|
+
const { primaryCanvas: pc, scheduler: sc, ...rest } = rendererProp;
|
|
14800
|
+
primaryCanvas = pc;
|
|
14801
|
+
scheduler = sc;
|
|
14802
|
+
renderer = Object.keys(rest).length > 0 ? rest : rendererProp;
|
|
14803
|
+
} else {
|
|
14804
|
+
renderer = rendererProp;
|
|
14805
|
+
}
|
|
15053
14806
|
React__namespace.useMemo(() => extend(THREE), []);
|
|
15054
14807
|
const Bridge = useBridge();
|
|
14808
|
+
const backgroundProps = React__namespace.useMemo(() => parseBackground(background), [background]);
|
|
15055
14809
|
const hasInitialSizeRef = React__namespace.useRef(false);
|
|
15056
14810
|
const measureConfig = React__namespace.useMemo(() => {
|
|
15057
14811
|
if (!hasInitialSizeRef.current) {
|
|
@@ -15068,15 +14822,20 @@ function CanvasImpl({
|
|
|
15068
14822
|
};
|
|
15069
14823
|
}, [resize, hasInitialSizeRef.current]);
|
|
15070
14824
|
const [containerRef, containerRect] = useMeasure__default(measureConfig);
|
|
15071
|
-
const effectiveSize = React__namespace.useMemo(
|
|
15072
|
-
|
|
15073
|
-
|
|
15074
|
-
|
|
14825
|
+
const effectiveSize = React__namespace.useMemo(() => {
|
|
14826
|
+
let w = width ?? containerRect.width;
|
|
14827
|
+
let h = height ?? containerRect.height;
|
|
14828
|
+
if (forceEven) {
|
|
14829
|
+
w = Math.ceil(w / 2) * 2;
|
|
14830
|
+
h = Math.ceil(h / 2) * 2;
|
|
14831
|
+
}
|
|
14832
|
+
return {
|
|
14833
|
+
width: w,
|
|
14834
|
+
height: h,
|
|
15075
14835
|
top: containerRect.top,
|
|
15076
14836
|
left: containerRect.left
|
|
15077
|
-
}
|
|
15078
|
-
|
|
15079
|
-
);
|
|
14837
|
+
};
|
|
14838
|
+
}, [width, height, containerRect, forceEven]);
|
|
15080
14839
|
if (!hasInitialSizeRef.current && effectiveSize.width > 0 && effectiveSize.height > 0) {
|
|
15081
14840
|
hasInitialSizeRef.current = true;
|
|
15082
14841
|
}
|
|
@@ -15116,23 +14875,26 @@ function CanvasImpl({
|
|
|
15116
14875
|
async function run() {
|
|
15117
14876
|
if (!effectActiveRef.current || !root.current) return;
|
|
15118
14877
|
await root.current.configure({
|
|
14878
|
+
id,
|
|
14879
|
+
primaryCanvas,
|
|
14880
|
+
scheduler,
|
|
15119
14881
|
gl,
|
|
15120
14882
|
renderer,
|
|
15121
14883
|
scene,
|
|
15122
14884
|
events,
|
|
15123
14885
|
shadows,
|
|
15124
|
-
linear,
|
|
15125
|
-
flat,
|
|
15126
|
-
legacy,
|
|
15127
14886
|
orthographic,
|
|
15128
14887
|
frameloop,
|
|
15129
14888
|
dpr,
|
|
15130
14889
|
performance,
|
|
15131
14890
|
raycaster,
|
|
15132
14891
|
camera,
|
|
14892
|
+
autoUpdateFrustum,
|
|
14893
|
+
occlusion,
|
|
15133
14894
|
size: effectiveSize,
|
|
15134
14895
|
// Store size props for reset functionality
|
|
15135
14896
|
_sizeProps: width !== void 0 || height !== void 0 ? { width, height } : null,
|
|
14897
|
+
forceEven,
|
|
15136
14898
|
// Pass mutable reference to onPointerMissed so it's free to update
|
|
15137
14899
|
onPointerMissed: (...args) => handlePointerMissed.current?.(...args),
|
|
15138
14900
|
onDragOverMissed: (...args) => handleDragOverMissed.current?.(...args),
|
|
@@ -15156,7 +14918,10 @@ function CanvasImpl({
|
|
|
15156
14918
|
});
|
|
15157
14919
|
if (!effectActiveRef.current || !root.current) return;
|
|
15158
14920
|
root.current.render(
|
|
15159
|
-
/* @__PURE__ */ jsxRuntime.jsx(Bridge, { children: /* @__PURE__ */ jsxRuntime.jsx(ErrorBoundary, { set: setError, children: /* @__PURE__ */ jsxRuntime.
|
|
14921
|
+
/* @__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: [
|
|
14922
|
+
backgroundProps && /* @__PURE__ */ jsxRuntime.jsx(Environment, { ...backgroundProps }),
|
|
14923
|
+
children ?? null
|
|
14924
|
+
] }) }) })
|
|
15160
14925
|
);
|
|
15161
14926
|
}
|
|
15162
14927
|
run();
|
|
@@ -15183,20 +14948,15 @@ function CanvasImpl({
|
|
|
15183
14948
|
const canvas = canvasRef.current;
|
|
15184
14949
|
if (!canvas) return;
|
|
15185
14950
|
const handleHMR = () => {
|
|
15186
|
-
|
|
15187
|
-
|
|
15188
|
-
rootEntry
|
|
15189
|
-
|
|
15190
|
-
uniforms: {},
|
|
15191
|
-
_hmrVersion: state._hmrVersion + 1
|
|
15192
|
-
}));
|
|
15193
|
-
}
|
|
14951
|
+
queueMicrotask(() => {
|
|
14952
|
+
const rootEntry = _roots.get(canvas);
|
|
14953
|
+
if (rootEntry?.store) clearHmrCaches(rootEntry.store);
|
|
14954
|
+
});
|
|
15194
14955
|
};
|
|
15195
14956
|
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) {
|
|
15196
14957
|
const hot = undefined;
|
|
15197
14958
|
hot.on("vite:afterUpdate", handleHMR);
|
|
15198
|
-
return () => hot.
|
|
15199
|
-
});
|
|
14959
|
+
return () => hot.off?.("vite:afterUpdate", handleHMR);
|
|
15200
14960
|
}
|
|
15201
14961
|
if (typeof module !== "undefined" && module.hot) {
|
|
15202
14962
|
const hot = module.hot;
|
|
@@ -15219,7 +14979,16 @@ function CanvasImpl({
|
|
|
15219
14979
|
...style
|
|
15220
14980
|
},
|
|
15221
14981
|
...props,
|
|
15222
|
-
children: /* @__PURE__ */ jsxRuntime.jsx("div", { ref: containerRef, className: "r3f-canvas-container", style: { width: "100%", height: "100%" }, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
14982
|
+
children: /* @__PURE__ */ jsxRuntime.jsx("div", { ref: containerRef, className: "r3f-canvas-container", style: { width: "100%", height: "100%" }, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
14983
|
+
"canvas",
|
|
14984
|
+
{
|
|
14985
|
+
ref: canvasRef,
|
|
14986
|
+
id,
|
|
14987
|
+
className: "r3f-canvas",
|
|
14988
|
+
style: { display: "block", width: "100%", height: "100%" },
|
|
14989
|
+
children: fallback
|
|
14990
|
+
}
|
|
14991
|
+
) })
|
|
15223
14992
|
}
|
|
15224
14993
|
);
|
|
15225
14994
|
}
|
|
@@ -15229,15 +14998,23 @@ function Canvas(props) {
|
|
|
15229
14998
|
|
|
15230
14999
|
extend(THREE);
|
|
15231
15000
|
|
|
15001
|
+
exports.Scheduler = scheduler.Scheduler;
|
|
15002
|
+
exports.getScheduler = scheduler.getScheduler;
|
|
15232
15003
|
exports.Block = Block;
|
|
15233
15004
|
exports.Canvas = Canvas;
|
|
15005
|
+
exports.Environment = Environment;
|
|
15006
|
+
exports.EnvironmentCube = EnvironmentCube;
|
|
15007
|
+
exports.EnvironmentMap = EnvironmentMap;
|
|
15008
|
+
exports.EnvironmentPortal = EnvironmentPortal;
|
|
15234
15009
|
exports.ErrorBoundary = ErrorBoundary;
|
|
15010
|
+
exports.FROM_REF = FROM_REF;
|
|
15235
15011
|
exports.IsObject = IsObject;
|
|
15012
|
+
exports.ONCE = ONCE;
|
|
15013
|
+
exports.Portal = Portal;
|
|
15236
15014
|
exports.R3F_BUILD_LEGACY = R3F_BUILD_LEGACY;
|
|
15237
15015
|
exports.R3F_BUILD_WEBGPU = R3F_BUILD_WEBGPU;
|
|
15238
15016
|
exports.REACT_INTERNAL_PROPS = REACT_INTERNAL_PROPS;
|
|
15239
15017
|
exports.RESERVED_PROPS = RESERVED_PROPS;
|
|
15240
|
-
exports.Scheduler = Scheduler;
|
|
15241
15018
|
exports.Texture = Texture;
|
|
15242
15019
|
exports._roots = _roots;
|
|
15243
15020
|
exports.act = act;
|
|
@@ -15262,30 +15039,40 @@ exports.events = createPointerEvents;
|
|
|
15262
15039
|
exports.extend = extend;
|
|
15263
15040
|
exports.findInitialRoot = findInitialRoot;
|
|
15264
15041
|
exports.flushSync = flushSync;
|
|
15042
|
+
exports.fromRef = fromRef;
|
|
15265
15043
|
exports.getInstanceProps = getInstanceProps;
|
|
15044
|
+
exports.getPrimary = getPrimary;
|
|
15045
|
+
exports.getPrimaryIds = getPrimaryIds;
|
|
15266
15046
|
exports.getRootState = getRootState;
|
|
15267
|
-
exports.getScheduler = getScheduler;
|
|
15268
15047
|
exports.getUuidPrefix = getUuidPrefix;
|
|
15269
15048
|
exports.hasConstructor = hasConstructor;
|
|
15049
|
+
exports.hasPrimary = hasPrimary;
|
|
15270
15050
|
exports.invalidate = invalidate;
|
|
15271
15051
|
exports.invalidateInstance = invalidateInstance;
|
|
15272
15052
|
exports.is = is;
|
|
15273
15053
|
exports.isColorRepresentation = isColorRepresentation;
|
|
15274
15054
|
exports.isCopyable = isCopyable;
|
|
15055
|
+
exports.isFromRef = isFromRef;
|
|
15275
15056
|
exports.isObject3D = isObject3D;
|
|
15057
|
+
exports.isOnce = isOnce;
|
|
15276
15058
|
exports.isOrthographicCamera = isOrthographicCamera;
|
|
15277
15059
|
exports.isRef = isRef;
|
|
15278
15060
|
exports.isRenderer = isRenderer;
|
|
15279
15061
|
exports.isTexture = isTexture;
|
|
15280
15062
|
exports.isVectorLike = isVectorLike;
|
|
15063
|
+
exports.once = once;
|
|
15281
15064
|
exports.prepare = prepare;
|
|
15065
|
+
exports.presetsObj = presetsObj;
|
|
15282
15066
|
exports.reconciler = reconciler;
|
|
15067
|
+
exports.registerPrimary = registerPrimary;
|
|
15283
15068
|
exports.removeInteractivity = removeInteractivity;
|
|
15284
15069
|
exports.resolve = resolve;
|
|
15285
15070
|
exports.unmountComponentAtNode = unmountComponentAtNode;
|
|
15071
|
+
exports.unregisterPrimary = unregisterPrimary;
|
|
15286
15072
|
exports.updateCamera = updateCamera;
|
|
15287
15073
|
exports.updateFrustum = updateFrustum;
|
|
15288
15074
|
exports.useBridge = useBridge;
|
|
15075
|
+
exports.useEnvironment = useEnvironment;
|
|
15289
15076
|
exports.useFrame = useFrame;
|
|
15290
15077
|
exports.useGraph = useGraph;
|
|
15291
15078
|
exports.useInstanceHandle = useInstanceHandle;
|
|
@@ -15297,3 +15084,4 @@ exports.useStore = useStore;
|
|
|
15297
15084
|
exports.useTexture = useTexture;
|
|
15298
15085
|
exports.useTextures = useTextures;
|
|
15299
15086
|
exports.useThree = useThree;
|
|
15087
|
+
exports.waitForPrimary = waitForPrimary;
|