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