@react-three/fiber 10.0.0-canary.604355a → 10.0.0-canary.76d2134
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/dist/index.cjs +885 -1422
- package/dist/index.d.cts +309 -811
- package/dist/index.d.mts +309 -811
- package/dist/index.d.ts +309 -811
- package/dist/index.mjs +860 -1396
- package/dist/legacy.cjs +840 -1383
- package/dist/legacy.d.cts +310 -811
- package/dist/legacy.d.mts +310 -811
- package/dist/legacy.d.ts +310 -811
- package/dist/legacy.mjs +814 -1356
- package/dist/webgpu/index.cjs +1341 -1737
- package/dist/webgpu/index.d.cts +425 -825
- package/dist/webgpu/index.d.mts +425 -825
- package/dist/webgpu/index.d.ts +425 -825
- package/dist/webgpu/index.mjs +1310 -1709
- package/package.json +4 -3
- package/readme.md +2 -3
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as webgpu from 'three/webgpu';
|
|
2
|
-
import { RenderTarget, CubeReflectionMapping, EquirectangularReflectionMapping, CubeTextureLoader, Scene,
|
|
3
|
-
import { WebGLRenderTarget, WebGLRenderer } from 'three';
|
|
2
|
+
import { CubeRenderTarget, RenderTarget, CubeReflectionMapping, EquirectangularReflectionMapping, CubeTextureLoader, Scene, HalfFloatType, Color, Frustum, Matrix4, Group, BoxGeometry, MeshBasicNodeMaterial, Mesh, Node, NodeUpdateType, Layers, ShaderMaterial, Uniform, SRGBColorSpace, RGBAFormat, UnsignedByteType, Vector3, Vector2, TextureLoader, Texture as Texture$1, CanvasTarget, Raycaster, OrthographicCamera, PerspectiveCamera, PCFShadowMap, VSMShadowMap, BasicShadowMap, ACESFilmicToneMapping, WebGPURenderer } from 'three/webgpu';
|
|
3
|
+
import { WebGLCubeRenderTarget, WebGLRenderTarget, WebGLRenderer } from 'three';
|
|
4
4
|
import { Inspector } from 'three/addons/inspector/Inspector.js';
|
|
5
5
|
import { jsx, Fragment, jsxs } from 'react/jsx-runtime';
|
|
6
6
|
import * as React from 'react';
|
|
@@ -13,8 +13,10 @@ import { HDRLoader } from 'three/examples/jsm/loaders/HDRLoader.js';
|
|
|
13
13
|
import { EXRLoader } from 'three/examples/jsm/loaders/EXRLoader.js';
|
|
14
14
|
import { UltraHDRLoader } from 'three/examples/jsm/loaders/UltraHDRLoader.js';
|
|
15
15
|
import { GainMapLoader } from '@monogrid/gainmap-js';
|
|
16
|
-
import Tb, {
|
|
16
|
+
import Tb, { unstable_getCurrentPriorityLevel, unstable_IdlePriority, unstable_LowPriority, unstable_NormalPriority, unstable_UserBlockingPriority, unstable_ImmediatePriority, unstable_scheduleCallback } from 'scheduler';
|
|
17
17
|
import { createWithEqualityFn } from 'zustand/traditional';
|
|
18
|
+
import { getScheduler } from '@pmndrs/scheduler';
|
|
19
|
+
export { Scheduler, getScheduler } from '@pmndrs/scheduler';
|
|
18
20
|
import { suspend, preload, clear } from 'suspend-react';
|
|
19
21
|
|
|
20
22
|
function _mergeNamespaces(n, m) {
|
|
@@ -34,10 +36,13 @@ const R3F_BUILD_WEBGPU = true;
|
|
|
34
36
|
|
|
35
37
|
const THREE = /*#__PURE__*/_mergeNamespaces({
|
|
36
38
|
__proto__: null,
|
|
39
|
+
CubeRenderTarget: CubeRenderTarget,
|
|
40
|
+
CubeRenderTargetCompat: CubeRenderTarget,
|
|
37
41
|
Inspector: Inspector,
|
|
38
42
|
R3F_BUILD_LEGACY: R3F_BUILD_LEGACY,
|
|
39
43
|
R3F_BUILD_WEBGPU: R3F_BUILD_WEBGPU,
|
|
40
44
|
RenderTargetCompat: RenderTarget,
|
|
45
|
+
WebGLCubeRenderTarget: WebGLCubeRenderTarget,
|
|
41
46
|
WebGLRenderTarget: WebGLRenderTarget,
|
|
42
47
|
WebGLRenderer: WebGLRenderer
|
|
43
48
|
}, [webgpu]);
|
|
@@ -139,7 +144,7 @@ function useEnvironment({
|
|
|
139
144
|
useLoader$1.clear(loader, multiFile ? [files] : files);
|
|
140
145
|
}
|
|
141
146
|
renderer.domElement.addEventListener("webglcontextlost", clearGainmapTexture, { once: true });
|
|
142
|
-
}, [files, renderer.domElement]);
|
|
147
|
+
}, [extension, files, loader, multiFile, renderer.domElement]);
|
|
143
148
|
const loaderResult = useLoader$1(
|
|
144
149
|
loader,
|
|
145
150
|
multiFile ? [files] : files,
|
|
@@ -311,15 +316,16 @@ function EnvironmentPortal({
|
|
|
311
316
|
preset = void 0,
|
|
312
317
|
extensions
|
|
313
318
|
}) {
|
|
314
|
-
const
|
|
319
|
+
const renderer = useThree$1((state) => state.renderer);
|
|
320
|
+
const isLegacy = useThree$1((state) => state.isLegacy);
|
|
315
321
|
const defaultScene = useThree$1((state) => state.scene);
|
|
316
322
|
const camera = React.useRef(null);
|
|
317
323
|
const [virtualScene] = React.useState(() => new Scene());
|
|
318
324
|
const fbo = React.useMemo(() => {
|
|
319
|
-
const fbo2 = new WebGLCubeRenderTarget(resolution);
|
|
325
|
+
const fbo2 = isLegacy ? new WebGLCubeRenderTarget(resolution) : new CubeRenderTarget(resolution);
|
|
320
326
|
fbo2.texture.type = HalfFloatType;
|
|
321
327
|
return fbo2;
|
|
322
|
-
}, [resolution]);
|
|
328
|
+
}, [resolution, isLegacy]);
|
|
323
329
|
React.useEffect(() => {
|
|
324
330
|
return () => {
|
|
325
331
|
fbo.dispose();
|
|
@@ -327,10 +333,10 @@ function EnvironmentPortal({
|
|
|
327
333
|
}, [fbo]);
|
|
328
334
|
React.useLayoutEffect(() => {
|
|
329
335
|
if (frames === 1) {
|
|
330
|
-
const autoClear =
|
|
331
|
-
|
|
332
|
-
camera.current.update(
|
|
333
|
-
|
|
336
|
+
const autoClear = renderer.autoClear;
|
|
337
|
+
renderer.autoClear = true;
|
|
338
|
+
camera.current.update(renderer, virtualScene);
|
|
339
|
+
renderer.autoClear = autoClear;
|
|
334
340
|
}
|
|
335
341
|
return setEnvProps(background, scene, defaultScene, fbo.texture, {
|
|
336
342
|
backgroundBlurriness: blur ?? backgroundBlurriness,
|
|
@@ -339,14 +345,29 @@ function EnvironmentPortal({
|
|
|
339
345
|
environmentIntensity,
|
|
340
346
|
environmentRotation
|
|
341
347
|
});
|
|
342
|
-
}, [
|
|
348
|
+
}, [
|
|
349
|
+
children,
|
|
350
|
+
virtualScene,
|
|
351
|
+
fbo.texture,
|
|
352
|
+
scene,
|
|
353
|
+
defaultScene,
|
|
354
|
+
background,
|
|
355
|
+
frames,
|
|
356
|
+
renderer,
|
|
357
|
+
blur,
|
|
358
|
+
backgroundBlurriness,
|
|
359
|
+
backgroundIntensity,
|
|
360
|
+
backgroundRotation,
|
|
361
|
+
environmentIntensity,
|
|
362
|
+
environmentRotation
|
|
363
|
+
]);
|
|
343
364
|
let count = 1;
|
|
344
365
|
useFrame$1(() => {
|
|
345
366
|
if (frames === Infinity || count < frames) {
|
|
346
|
-
const autoClear =
|
|
347
|
-
|
|
348
|
-
camera.current.update(
|
|
349
|
-
|
|
367
|
+
const autoClear = renderer.autoClear;
|
|
368
|
+
renderer.autoClear = true;
|
|
369
|
+
camera.current.update(renderer, virtualScene);
|
|
370
|
+
renderer.autoClear = autoClear;
|
|
350
371
|
count++;
|
|
351
372
|
}
|
|
352
373
|
});
|
|
@@ -410,10 +431,9 @@ function Environment(props) {
|
|
|
410
431
|
return props.ground ? /* @__PURE__ */ jsx(EnvironmentGround, { ...props }) : props.map ? /* @__PURE__ */ jsx(EnvironmentMap, { ...props }) : props.children ? /* @__PURE__ */ jsx(EnvironmentPortal, { ...props }) : /* @__PURE__ */ jsx(EnvironmentCube, { ...props });
|
|
411
432
|
}
|
|
412
433
|
|
|
413
|
-
var __defProp
|
|
414
|
-
var __defNormalProp
|
|
415
|
-
var __publicField
|
|
416
|
-
const act = React["act"];
|
|
434
|
+
var __defProp = Object.defineProperty;
|
|
435
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
436
|
+
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
417
437
|
const useIsomorphicLayoutEffect = /* @__PURE__ */ (() => typeof window !== "undefined" && (window.document?.createElement || window.navigator?.product === "ReactNative"))() ? React.useLayoutEffect : React.useEffect;
|
|
418
438
|
function useMutableCallback(fn) {
|
|
419
439
|
const ref = React.useRef(fn);
|
|
@@ -444,7 +464,7 @@ const ErrorBoundary = /* @__PURE__ */ (() => {
|
|
|
444
464
|
return _a = class extends React.Component {
|
|
445
465
|
constructor() {
|
|
446
466
|
super(...arguments);
|
|
447
|
-
__publicField
|
|
467
|
+
__publicField(this, "state", { error: false });
|
|
448
468
|
}
|
|
449
469
|
componentDidCatch(err) {
|
|
450
470
|
this.props.set(err);
|
|
@@ -452,7 +472,7 @@ const ErrorBoundary = /* @__PURE__ */ (() => {
|
|
|
452
472
|
render() {
|
|
453
473
|
return this.state.error ? null : this.props.children;
|
|
454
474
|
}
|
|
455
|
-
}, __publicField
|
|
475
|
+
}, __publicField(_a, "getDerivedStateFromError", () => ({ error: true })), _a;
|
|
456
476
|
})();
|
|
457
477
|
|
|
458
478
|
const is = {
|
|
@@ -589,8 +609,7 @@ function prepare(target, root, type, props) {
|
|
|
589
609
|
object,
|
|
590
610
|
eventCount: 0,
|
|
591
611
|
handlers: {},
|
|
592
|
-
isHidden: false
|
|
593
|
-
deferredRefs: []
|
|
612
|
+
isHidden: false
|
|
594
613
|
};
|
|
595
614
|
if (object) object.__r3f = instance;
|
|
596
615
|
}
|
|
@@ -747,6 +766,17 @@ function unregisterVisibility(store, object) {
|
|
|
747
766
|
internal.visibilityRegistry.delete(object.uuid);
|
|
748
767
|
internal.occlusionCache.delete(object);
|
|
749
768
|
}
|
|
769
|
+
function updateVisibilityHandlers(store, object, handlers) {
|
|
770
|
+
const { internal } = store.getState();
|
|
771
|
+
const entry = internal.visibilityRegistry.get(object.uuid);
|
|
772
|
+
if (!entry) return false;
|
|
773
|
+
entry.handlers = handlers;
|
|
774
|
+
if ((handlers.onOccluded || handlers.onVisible) && !internal.occlusionEnabled) {
|
|
775
|
+
object.occlusionTest = true;
|
|
776
|
+
enableOcclusion(store);
|
|
777
|
+
}
|
|
778
|
+
return true;
|
|
779
|
+
}
|
|
750
780
|
function checkVisibility(state) {
|
|
751
781
|
const { internal, camera } = state;
|
|
752
782
|
const registry = internal.visibilityRegistry;
|
|
@@ -907,9 +937,9 @@ function diffProps(instance, newProps) {
|
|
|
907
937
|
const { root, key } = resolve(instance.object, prop);
|
|
908
938
|
if (root.constructor && root.constructor.length === 0) {
|
|
909
939
|
const ctor = getMemoizedPrototype(root);
|
|
910
|
-
if (!is.und(ctor)) changedProps[
|
|
940
|
+
if (!is.und(ctor)) changedProps[prop] = ctor[key];
|
|
911
941
|
} else {
|
|
912
|
-
changedProps[
|
|
942
|
+
changedProps[prop] = 0;
|
|
913
943
|
}
|
|
914
944
|
}
|
|
915
945
|
return changedProps;
|
|
@@ -934,10 +964,7 @@ function applyProps(object, props) {
|
|
|
934
964
|
continue;
|
|
935
965
|
}
|
|
936
966
|
if (value === void 0) continue;
|
|
937
|
-
if (isFromRef(value))
|
|
938
|
-
instance?.deferredRefs?.push({ prop, ref: value[FROM_REF] });
|
|
939
|
-
continue;
|
|
940
|
-
}
|
|
967
|
+
if (isFromRef(value)) continue;
|
|
941
968
|
if (isOnce(value)) {
|
|
942
969
|
if (instance?.appliedOnce?.has(prop)) continue;
|
|
943
970
|
if (instance) {
|
|
@@ -973,8 +1000,24 @@ function applyProps(object, props) {
|
|
|
973
1000
|
} else if (isVectorLike(target) && is.num(value)) {
|
|
974
1001
|
if ("setScalar" in target && typeof target.setScalar === "function") target.setScalar(value);
|
|
975
1002
|
else target.set(value);
|
|
1003
|
+
} else if (root instanceof ShaderMaterial && key === "uniforms" && is.obj(value)) {
|
|
1004
|
+
if (!is.obj(root.uniforms)) root.uniforms = {};
|
|
1005
|
+
const uniforms = root.uniforms;
|
|
1006
|
+
const nextUniforms = value;
|
|
1007
|
+
for (const name in nextUniforms) {
|
|
1008
|
+
const uniform = nextUniforms[name];
|
|
1009
|
+
const targetUniform = uniforms[name];
|
|
1010
|
+
if (targetUniform) Object.assign(targetUniform, uniform);
|
|
1011
|
+
else {
|
|
1012
|
+
const nextUniform = uniform instanceof Uniform ? uniform.clone() : new Uniform(uniform.value);
|
|
1013
|
+
uniforms[name] = nextUniform;
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
976
1016
|
} else {
|
|
977
1017
|
root[key] = value;
|
|
1018
|
+
if (key.endsWith("Node") && root.isMaterial) {
|
|
1019
|
+
root.needsUpdate = true;
|
|
1020
|
+
}
|
|
978
1021
|
if (rootState && rootState.renderer?.outputColorSpace === SRGBColorSpace && colorMaps.includes(key) && isTexture(value) && root[key]?.isTexture && // sRGB textures must be RGBA8 since r137 https://github.com/mrdoob/three.js/pull/23129
|
|
979
1022
|
root[key].format === RGBAFormat && root[key].type === UnsignedByteType) {
|
|
980
1023
|
root[key].colorSpace = rootState.textureColorSpace;
|
|
@@ -988,6 +1031,9 @@ function applyProps(object, props) {
|
|
|
988
1031
|
if (instance.eventCount && object2.raycast !== null) {
|
|
989
1032
|
rootState.internal.interaction.push(object2);
|
|
990
1033
|
}
|
|
1034
|
+
}
|
|
1035
|
+
if (instance?.parent && rootState?.internal && instance.object?.isObject3D) {
|
|
1036
|
+
const object2 = instance.object;
|
|
991
1037
|
const root = findInitialRoot(instance);
|
|
992
1038
|
const visibilityHandlers = {
|
|
993
1039
|
onFramed: instance.handlers.onFramed,
|
|
@@ -995,7 +1041,9 @@ function applyProps(object, props) {
|
|
|
995
1041
|
onVisible: instance.handlers.onVisible
|
|
996
1042
|
};
|
|
997
1043
|
if (hasVisibilityHandlers(visibilityHandlers)) {
|
|
998
|
-
|
|
1044
|
+
if (!updateVisibilityHandlers(root, object2, visibilityHandlers)) {
|
|
1045
|
+
registerVisibility(root, object2, visibilityHandlers);
|
|
1046
|
+
}
|
|
999
1047
|
} else {
|
|
1000
1048
|
unregisterVisibility(root, object2);
|
|
1001
1049
|
}
|
|
@@ -1008,38 +1056,95 @@ function applyProps(object, props) {
|
|
|
1008
1056
|
return object;
|
|
1009
1057
|
}
|
|
1010
1058
|
|
|
1059
|
+
const DEFAULT_POINTER_ID = 0;
|
|
1060
|
+
const XR_POINTER_ID_START = 1e3;
|
|
1061
|
+
function getPointerState(internal, pointerId) {
|
|
1062
|
+
let state = internal.pointerMap.get(pointerId);
|
|
1063
|
+
if (!state) {
|
|
1064
|
+
state = {
|
|
1065
|
+
hovered: /* @__PURE__ */ new Map(),
|
|
1066
|
+
captured: /* @__PURE__ */ new Map(),
|
|
1067
|
+
initialClick: [0, 0],
|
|
1068
|
+
initialHits: []
|
|
1069
|
+
};
|
|
1070
|
+
internal.pointerMap.set(pointerId, state);
|
|
1071
|
+
}
|
|
1072
|
+
return state;
|
|
1073
|
+
}
|
|
1074
|
+
function getPointerId(event) {
|
|
1075
|
+
return "pointerId" in event ? event.pointerId : DEFAULT_POINTER_ID;
|
|
1076
|
+
}
|
|
1011
1077
|
function makeId(event) {
|
|
1012
1078
|
return (event.eventObject || event.object).uuid + "/" + event.index + event.instanceId;
|
|
1013
1079
|
}
|
|
1014
|
-
function releaseInternalPointerCapture(
|
|
1015
|
-
const
|
|
1080
|
+
function releaseInternalPointerCapture(internal, obj, pointerId) {
|
|
1081
|
+
const pointerState = internal.pointerMap.get(pointerId);
|
|
1082
|
+
if (!pointerState) return;
|
|
1083
|
+
const captureData = pointerState.captured.get(obj);
|
|
1016
1084
|
if (captureData) {
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1085
|
+
pointerState.captured.delete(obj);
|
|
1086
|
+
captureData.target.releasePointerCapture(pointerId);
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
function swapInteractivity(store, object, newObject) {
|
|
1090
|
+
const { internal } = store.getState();
|
|
1091
|
+
for (let i = 0; i < internal.interaction.length; i++) {
|
|
1092
|
+
if (internal.interaction[i] === object) internal.interaction[i] = newObject;
|
|
1093
|
+
}
|
|
1094
|
+
for (const pointerState of internal.pointerMap.values()) {
|
|
1095
|
+
for (let i = 0; i < pointerState.initialHits.length; i++) {
|
|
1096
|
+
if (pointerState.initialHits[i] === object) pointerState.initialHits[i] = newObject;
|
|
1097
|
+
}
|
|
1098
|
+
pointerState.hovered.forEach((value, key) => {
|
|
1099
|
+
if (value.eventObject === object || value.object === object) {
|
|
1100
|
+
pointerState.hovered.delete(key);
|
|
1101
|
+
const next = {
|
|
1102
|
+
...value,
|
|
1103
|
+
eventObject: value.eventObject === object ? newObject : value.eventObject,
|
|
1104
|
+
object: value.object === object ? newObject : value.object
|
|
1105
|
+
};
|
|
1106
|
+
pointerState.hovered.set(makeId(next), next);
|
|
1107
|
+
}
|
|
1108
|
+
});
|
|
1109
|
+
const captureData = pointerState.captured.get(object);
|
|
1110
|
+
if (captureData) {
|
|
1111
|
+
pointerState.captured.delete(object);
|
|
1112
|
+
pointerState.captured.set(newObject, {
|
|
1113
|
+
...captureData,
|
|
1114
|
+
intersection: {
|
|
1115
|
+
...captureData.intersection,
|
|
1116
|
+
object: captureData.intersection.object === object ? newObject : captureData.intersection.object,
|
|
1117
|
+
eventObject: captureData.intersection.eventObject === object ? newObject : captureData.intersection.eventObject
|
|
1118
|
+
}
|
|
1119
|
+
});
|
|
1021
1120
|
}
|
|
1022
1121
|
}
|
|
1122
|
+
unregisterVisibility(store, object);
|
|
1023
1123
|
}
|
|
1024
1124
|
function removeInteractivity(store, object) {
|
|
1025
1125
|
const { internal } = store.getState();
|
|
1026
1126
|
internal.interaction = internal.interaction.filter((o) => o !== object);
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1127
|
+
for (const [pointerId, pointerState] of internal.pointerMap) {
|
|
1128
|
+
pointerState.initialHits = pointerState.initialHits.filter((o) => o !== object);
|
|
1129
|
+
pointerState.hovered.forEach((value, key) => {
|
|
1130
|
+
if (value.eventObject === object || value.object === object) {
|
|
1131
|
+
pointerState.hovered.delete(key);
|
|
1132
|
+
}
|
|
1133
|
+
});
|
|
1134
|
+
if (pointerState.captured.has(object)) {
|
|
1135
|
+
releaseInternalPointerCapture(internal, object, pointerId);
|
|
1031
1136
|
}
|
|
1032
|
-
}
|
|
1033
|
-
internal.capturedMap.forEach((captures, pointerId) => {
|
|
1034
|
-
releaseInternalPointerCapture(internal.capturedMap, object, captures, pointerId);
|
|
1035
|
-
});
|
|
1137
|
+
}
|
|
1036
1138
|
unregisterVisibility(store, object);
|
|
1037
1139
|
}
|
|
1038
1140
|
function createEvents(store) {
|
|
1039
|
-
function calculateDistance(event) {
|
|
1141
|
+
function calculateDistance(event, pointerId) {
|
|
1040
1142
|
const { internal } = store.getState();
|
|
1041
|
-
const
|
|
1042
|
-
|
|
1143
|
+
const pointerState = internal.pointerMap.get(pointerId);
|
|
1144
|
+
if (!pointerState) return 0;
|
|
1145
|
+
const [initialX, initialY] = pointerState.initialClick;
|
|
1146
|
+
const dx = event.offsetX - initialX;
|
|
1147
|
+
const dy = event.offsetY - initialY;
|
|
1043
1148
|
return Math.round(Math.sqrt(dx * dx + dy * dy));
|
|
1044
1149
|
}
|
|
1045
1150
|
function filterPointerEvents(objects) {
|
|
@@ -1075,6 +1180,15 @@ function createEvents(store) {
|
|
|
1075
1180
|
return state2.raycaster.camera ? state2.raycaster.intersectObject(obj, true) : [];
|
|
1076
1181
|
}
|
|
1077
1182
|
let hits = eventsObjects.flatMap(handleRaycast).sort((a, b) => {
|
|
1183
|
+
const aInteractivePriority = a.object.userData?.interactivePriority;
|
|
1184
|
+
const bInteractivePriority = b.object.userData?.interactivePriority;
|
|
1185
|
+
if (aInteractivePriority !== void 0 || bInteractivePriority !== void 0) {
|
|
1186
|
+
if (aInteractivePriority !== void 0 && bInteractivePriority === void 0) return -1;
|
|
1187
|
+
if (bInteractivePriority !== void 0 && aInteractivePriority === void 0) return 1;
|
|
1188
|
+
if (aInteractivePriority !== bInteractivePriority) {
|
|
1189
|
+
return (bInteractivePriority ?? 0) - (aInteractivePriority ?? 0);
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1078
1192
|
const aState = getRootState(a.object);
|
|
1079
1193
|
const bState = getRootState(b.object);
|
|
1080
1194
|
const aPriority = aState?.events?.priority ?? 1;
|
|
@@ -1096,9 +1210,13 @@ function createEvents(store) {
|
|
|
1096
1210
|
eventObject = eventObject.parent;
|
|
1097
1211
|
}
|
|
1098
1212
|
}
|
|
1099
|
-
if ("pointerId" in event
|
|
1100
|
-
|
|
1101
|
-
|
|
1213
|
+
if ("pointerId" in event) {
|
|
1214
|
+
const pointerId = event.pointerId;
|
|
1215
|
+
const pointerState = state.internal.pointerMap.get(pointerId);
|
|
1216
|
+
if (pointerState?.captured.size) {
|
|
1217
|
+
for (const captureData of pointerState.captured.values()) {
|
|
1218
|
+
if (!duplicates.has(makeId(captureData.intersection))) intersections.push(captureData.intersection);
|
|
1219
|
+
}
|
|
1102
1220
|
}
|
|
1103
1221
|
}
|
|
1104
1222
|
return intersections;
|
|
@@ -1111,27 +1229,25 @@ function createEvents(store) {
|
|
|
1111
1229
|
if (state) {
|
|
1112
1230
|
const { raycaster, pointer, camera, internal } = state;
|
|
1113
1231
|
const unprojectedPoint = new Vector3(pointer.x, pointer.y, 0).unproject(camera);
|
|
1114
|
-
const hasPointerCapture = (id) =>
|
|
1232
|
+
const hasPointerCapture = (id) => {
|
|
1233
|
+
const pointerState = internal.pointerMap.get(id);
|
|
1234
|
+
return pointerState?.captured.has(hit.eventObject) ?? false;
|
|
1235
|
+
};
|
|
1115
1236
|
const setPointerCapture = (id) => {
|
|
1116
1237
|
const captureData = { intersection: hit, target: event.target };
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
} else {
|
|
1120
|
-
internal.capturedMap.set(id, /* @__PURE__ */ new Map([[hit.eventObject, captureData]]));
|
|
1121
|
-
}
|
|
1238
|
+
const pointerState = getPointerState(internal, id);
|
|
1239
|
+
pointerState.captured.set(hit.eventObject, captureData);
|
|
1122
1240
|
event.target.setPointerCapture(id);
|
|
1123
1241
|
};
|
|
1124
1242
|
const releasePointerCapture = (id) => {
|
|
1125
|
-
|
|
1126
|
-
if (captures) {
|
|
1127
|
-
releaseInternalPointerCapture(internal.capturedMap, hit.eventObject, captures, id);
|
|
1128
|
-
}
|
|
1243
|
+
releaseInternalPointerCapture(internal, hit.eventObject, id);
|
|
1129
1244
|
};
|
|
1130
1245
|
const extractEventProps = {};
|
|
1131
1246
|
for (const prop in event) {
|
|
1132
1247
|
const property = event[prop];
|
|
1133
1248
|
if (typeof property !== "function") extractEventProps[prop] = property;
|
|
1134
1249
|
}
|
|
1250
|
+
const eventPointerId = "pointerId" in event ? event.pointerId : void 0;
|
|
1135
1251
|
const raycastEvent = {
|
|
1136
1252
|
...hit,
|
|
1137
1253
|
...extractEventProps,
|
|
@@ -1142,18 +1258,19 @@ function createEvents(store) {
|
|
|
1142
1258
|
unprojectedPoint,
|
|
1143
1259
|
ray: raycaster.ray,
|
|
1144
1260
|
camera,
|
|
1261
|
+
pointerId: eventPointerId,
|
|
1145
1262
|
// Hijack stopPropagation, which just sets a flag
|
|
1146
1263
|
stopPropagation() {
|
|
1147
|
-
const
|
|
1264
|
+
const pointerState = eventPointerId !== void 0 ? internal.pointerMap.get(eventPointerId) : void 0;
|
|
1148
1265
|
if (
|
|
1149
1266
|
// ...if this pointer hasn't been captured
|
|
1150
|
-
!
|
|
1151
|
-
|
|
1267
|
+
!pointerState?.captured.size || // ... or if the hit object is capturing the pointer
|
|
1268
|
+
pointerState.captured.has(hit.eventObject)
|
|
1152
1269
|
) {
|
|
1153
1270
|
raycastEvent.stopped = localState.stopped = true;
|
|
1154
|
-
if (
|
|
1271
|
+
if (pointerState?.hovered.size && Array.from(pointerState.hovered.values()).find((i) => i.eventObject === hit.eventObject)) {
|
|
1155
1272
|
const higher = intersections.slice(0, intersections.indexOf(hit));
|
|
1156
|
-
cancelPointer([...higher, hit]);
|
|
1273
|
+
cancelPointer([...higher, hit], eventPointerId);
|
|
1157
1274
|
}
|
|
1158
1275
|
}
|
|
1159
1276
|
},
|
|
@@ -1169,15 +1286,18 @@ function createEvents(store) {
|
|
|
1169
1286
|
}
|
|
1170
1287
|
return intersections;
|
|
1171
1288
|
}
|
|
1172
|
-
function cancelPointer(intersections) {
|
|
1289
|
+
function cancelPointer(intersections, pointerId) {
|
|
1173
1290
|
const { internal } = store.getState();
|
|
1174
|
-
|
|
1291
|
+
const pid = pointerId ?? DEFAULT_POINTER_ID;
|
|
1292
|
+
const pointerState = internal.pointerMap.get(pid);
|
|
1293
|
+
if (!pointerState) return;
|
|
1294
|
+
for (const [hoveredId, hoveredObj] of pointerState.hovered) {
|
|
1175
1295
|
if (!intersections.length || !intersections.find(
|
|
1176
1296
|
(hit) => hit.object === hoveredObj.object && hit.index === hoveredObj.index && hit.instanceId === hoveredObj.instanceId
|
|
1177
1297
|
)) {
|
|
1178
1298
|
const eventObject = hoveredObj.eventObject;
|
|
1179
1299
|
const instance = eventObject.__r3f;
|
|
1180
|
-
|
|
1300
|
+
pointerState.hovered.delete(hoveredId);
|
|
1181
1301
|
if (instance?.eventCount) {
|
|
1182
1302
|
const handlers = instance.handlers;
|
|
1183
1303
|
const data = { ...hoveredObj, intersections };
|
|
@@ -1206,41 +1326,118 @@ function createEvents(store) {
|
|
|
1206
1326
|
instance?.handlers.onDropMissed?.(event);
|
|
1207
1327
|
}
|
|
1208
1328
|
}
|
|
1329
|
+
function cleanupPointer(pointerId) {
|
|
1330
|
+
const { internal } = store.getState();
|
|
1331
|
+
const pointerState = internal.pointerMap.get(pointerId);
|
|
1332
|
+
if (pointerState) {
|
|
1333
|
+
for (const [, hoveredObj] of pointerState.hovered) {
|
|
1334
|
+
const eventObject = hoveredObj.eventObject;
|
|
1335
|
+
const instance = eventObject.__r3f;
|
|
1336
|
+
if (instance?.eventCount) {
|
|
1337
|
+
const handlers = instance.handlers;
|
|
1338
|
+
const data = { ...hoveredObj, intersections: [] };
|
|
1339
|
+
handlers.onPointerOut?.(data);
|
|
1340
|
+
handlers.onPointerLeave?.(data);
|
|
1341
|
+
}
|
|
1342
|
+
}
|
|
1343
|
+
internal.pointerMap.delete(pointerId);
|
|
1344
|
+
}
|
|
1345
|
+
internal.pointerDirty.delete(pointerId);
|
|
1346
|
+
}
|
|
1347
|
+
function processDeferredPointer(event, pointerId) {
|
|
1348
|
+
const state = store.getState();
|
|
1349
|
+
const { internal } = state;
|
|
1350
|
+
if (!state.events.enabled) return;
|
|
1351
|
+
const filter = filterPointerEvents;
|
|
1352
|
+
const hits = intersect(event, filter);
|
|
1353
|
+
cancelPointer(hits, pointerId);
|
|
1354
|
+
function onIntersect(data) {
|
|
1355
|
+
const eventObject = data.eventObject;
|
|
1356
|
+
const instance = eventObject.__r3f;
|
|
1357
|
+
if (!instance?.eventCount) return;
|
|
1358
|
+
const handlers = instance.handlers;
|
|
1359
|
+
if (handlers.onPointerOver || handlers.onPointerEnter || handlers.onPointerOut || handlers.onPointerLeave) {
|
|
1360
|
+
const id = makeId(data);
|
|
1361
|
+
const pointerState = getPointerState(internal, pointerId);
|
|
1362
|
+
const hoveredItem = pointerState.hovered.get(id);
|
|
1363
|
+
if (!hoveredItem) {
|
|
1364
|
+
pointerState.hovered.set(id, data);
|
|
1365
|
+
handlers.onPointerOver?.(data);
|
|
1366
|
+
handlers.onPointerEnter?.(data);
|
|
1367
|
+
} else if (hoveredItem.stopped) {
|
|
1368
|
+
data.stopPropagation();
|
|
1369
|
+
}
|
|
1370
|
+
}
|
|
1371
|
+
handlers.onPointerMove?.(data);
|
|
1372
|
+
}
|
|
1373
|
+
handleIntersects(hits, event, 0, onIntersect);
|
|
1374
|
+
}
|
|
1209
1375
|
function handlePointer(name) {
|
|
1210
1376
|
switch (name) {
|
|
1211
1377
|
case "onPointerLeave":
|
|
1212
|
-
case "onPointerCancel":
|
|
1213
1378
|
case "onDragLeave":
|
|
1214
1379
|
return () => cancelPointer([]);
|
|
1380
|
+
// Global cancel of these events
|
|
1381
|
+
case "onPointerCancel":
|
|
1382
|
+
return (event) => {
|
|
1383
|
+
const pointerId = getPointerId(event);
|
|
1384
|
+
cleanupPointer(pointerId);
|
|
1385
|
+
};
|
|
1215
1386
|
case "onLostPointerCapture":
|
|
1216
1387
|
return (event) => {
|
|
1217
1388
|
const { internal } = store.getState();
|
|
1218
|
-
|
|
1389
|
+
const pointerId = getPointerId(event);
|
|
1390
|
+
const pointerState = internal.pointerMap.get(pointerId);
|
|
1391
|
+
if (pointerState?.captured.size) {
|
|
1219
1392
|
requestAnimationFrame(() => {
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1393
|
+
const pointerState2 = internal.pointerMap.get(pointerId);
|
|
1394
|
+
if (pointerState2?.captured.size) {
|
|
1395
|
+
pointerState2.captured.clear();
|
|
1223
1396
|
}
|
|
1397
|
+
cancelPointer([], pointerId);
|
|
1224
1398
|
});
|
|
1225
1399
|
}
|
|
1226
1400
|
};
|
|
1227
1401
|
}
|
|
1228
1402
|
return function handleEvent(event) {
|
|
1229
1403
|
const state = store.getState();
|
|
1230
|
-
const { onPointerMissed, onDragOverMissed, onDropMissed, internal } = state;
|
|
1404
|
+
const { onPointerMissed, onDragOverMissed, onDropMissed, internal, events } = state;
|
|
1405
|
+
const pointerId = getPointerId(event);
|
|
1231
1406
|
internal.lastEvent.current = event;
|
|
1232
|
-
if (!
|
|
1407
|
+
if (!events.enabled) return;
|
|
1233
1408
|
const isPointerMove = name === "onPointerMove";
|
|
1234
1409
|
const isDragOver = name === "onDragOver";
|
|
1235
1410
|
const isDrop = name === "onDrop";
|
|
1236
1411
|
const isClickEvent = name === "onClick" || name === "onContextMenu" || name === "onDoubleClick";
|
|
1412
|
+
const isPointerDown = name === "onPointerDown";
|
|
1413
|
+
const isPointerUp = name === "onPointerUp";
|
|
1414
|
+
const isWheel = name === "onWheel";
|
|
1415
|
+
const canDeferRaycasts = events.frameTimedRaycasts && state.frameloop === "always";
|
|
1416
|
+
if (isPointerMove && canDeferRaycasts) {
|
|
1417
|
+
events.compute?.(event, state);
|
|
1418
|
+
internal.pointerDirty.set(pointerId, event);
|
|
1419
|
+
return;
|
|
1420
|
+
}
|
|
1421
|
+
if (isWheel && canDeferRaycasts && !events.alwaysFireOnScroll) {
|
|
1422
|
+
events.compute?.(event, state);
|
|
1423
|
+
internal.pointerDirty.set(pointerId, event);
|
|
1424
|
+
return;
|
|
1425
|
+
}
|
|
1426
|
+
if ((isClickEvent || isPointerDown || isPointerUp) && internal.pointerDirty.has(pointerId)) {
|
|
1427
|
+
const deferredEvent = internal.pointerDirty.get(pointerId);
|
|
1428
|
+
internal.pointerDirty.delete(pointerId);
|
|
1429
|
+
processDeferredPointer(deferredEvent, pointerId);
|
|
1430
|
+
}
|
|
1237
1431
|
const filter = isPointerMove || isDragOver || isDrop ? filterPointerEvents : void 0;
|
|
1238
1432
|
const hits = intersect(event, filter);
|
|
1239
|
-
const delta = isClickEvent ? calculateDistance(event) : 0;
|
|
1240
|
-
if (
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1433
|
+
const delta = isClickEvent ? calculateDistance(event, pointerId) : 0;
|
|
1434
|
+
if (isPointerDown) {
|
|
1435
|
+
const pointerState2 = getPointerState(internal, pointerId);
|
|
1436
|
+
pointerState2.initialClick = [event.offsetX, event.offsetY];
|
|
1437
|
+
pointerState2.initialHits = hits.map((hit) => hit.eventObject);
|
|
1438
|
+
}
|
|
1439
|
+
const pointerState = internal.pointerMap.get(pointerId);
|
|
1440
|
+
const initialHits = pointerState?.initialHits ?? [];
|
|
1244
1441
|
if (isClickEvent && !hits.length) {
|
|
1245
1442
|
if (delta <= 2) {
|
|
1246
1443
|
pointerMissed(event, internal.interaction);
|
|
@@ -1255,7 +1452,9 @@ function createEvents(store) {
|
|
|
1255
1452
|
dropMissed(event, internal.interaction);
|
|
1256
1453
|
if (onDropMissed) onDropMissed(event);
|
|
1257
1454
|
}
|
|
1258
|
-
if (isPointerMove || isDragOver)
|
|
1455
|
+
if (isPointerMove || isDragOver) {
|
|
1456
|
+
cancelPointer(hits, pointerId);
|
|
1457
|
+
}
|
|
1259
1458
|
function onIntersect(data) {
|
|
1260
1459
|
const eventObject = data.eventObject;
|
|
1261
1460
|
const instance = eventObject.__r3f;
|
|
@@ -1264,9 +1463,10 @@ function createEvents(store) {
|
|
|
1264
1463
|
if (isPointerMove) {
|
|
1265
1464
|
if (handlers.onPointerOver || handlers.onPointerEnter || handlers.onPointerOut || handlers.onPointerLeave) {
|
|
1266
1465
|
const id = makeId(data);
|
|
1267
|
-
const
|
|
1466
|
+
const pointerState2 = getPointerState(internal, pointerId);
|
|
1467
|
+
const hoveredItem = pointerState2.hovered.get(id);
|
|
1268
1468
|
if (!hoveredItem) {
|
|
1269
|
-
|
|
1469
|
+
pointerState2.hovered.set(id, data);
|
|
1270
1470
|
handlers.onPointerOver?.(data);
|
|
1271
1471
|
handlers.onPointerEnter?.(data);
|
|
1272
1472
|
} else if (hoveredItem.stopped) {
|
|
@@ -1276,9 +1476,10 @@ function createEvents(store) {
|
|
|
1276
1476
|
handlers.onPointerMove?.(data);
|
|
1277
1477
|
} else if (isDragOver) {
|
|
1278
1478
|
const id = makeId(data);
|
|
1279
|
-
const
|
|
1479
|
+
const pointerState2 = getPointerState(internal, pointerId);
|
|
1480
|
+
const hoveredItem = pointerState2.hovered.get(id);
|
|
1280
1481
|
if (!hoveredItem) {
|
|
1281
|
-
|
|
1482
|
+
pointerState2.hovered.set(id, data);
|
|
1282
1483
|
handlers.onDragOverEnter?.(data);
|
|
1283
1484
|
} else if (hoveredItem.stopped) {
|
|
1284
1485
|
data.stopPropagation();
|
|
@@ -1289,18 +1490,18 @@ function createEvents(store) {
|
|
|
1289
1490
|
} else {
|
|
1290
1491
|
const handler = handlers[name];
|
|
1291
1492
|
if (handler) {
|
|
1292
|
-
if (!isClickEvent ||
|
|
1493
|
+
if (!isClickEvent || initialHits.includes(eventObject)) {
|
|
1293
1494
|
pointerMissed(
|
|
1294
1495
|
event,
|
|
1295
|
-
internal.interaction.filter((object) => !
|
|
1496
|
+
internal.interaction.filter((object) => !initialHits.includes(object))
|
|
1296
1497
|
);
|
|
1297
1498
|
handler(data);
|
|
1298
1499
|
}
|
|
1299
1500
|
} else {
|
|
1300
|
-
if (isClickEvent &&
|
|
1501
|
+
if (isClickEvent && initialHits.includes(eventObject)) {
|
|
1301
1502
|
pointerMissed(
|
|
1302
1503
|
event,
|
|
1303
|
-
internal.interaction.filter((object) => !
|
|
1504
|
+
internal.interaction.filter((object) => !initialHits.includes(object))
|
|
1304
1505
|
);
|
|
1305
1506
|
}
|
|
1306
1507
|
}
|
|
@@ -1309,7 +1510,15 @@ function createEvents(store) {
|
|
|
1309
1510
|
handleIntersects(hits, event, delta, onIntersect);
|
|
1310
1511
|
};
|
|
1311
1512
|
}
|
|
1312
|
-
|
|
1513
|
+
function flushDeferredPointers() {
|
|
1514
|
+
const { internal, events } = store.getState();
|
|
1515
|
+
if (!events.frameTimedRaycasts) return;
|
|
1516
|
+
for (const [pointerId, event] of internal.pointerDirty) {
|
|
1517
|
+
processDeferredPointer(event, pointerId);
|
|
1518
|
+
}
|
|
1519
|
+
internal.pointerDirty.clear();
|
|
1520
|
+
}
|
|
1521
|
+
return { handlePointer, flushDeferredPointers, processDeferredPointer };
|
|
1313
1522
|
}
|
|
1314
1523
|
const DOM_EVENTS = {
|
|
1315
1524
|
onClick: ["click", false],
|
|
@@ -1328,10 +1537,15 @@ const DOM_EVENTS = {
|
|
|
1328
1537
|
onLostPointerCapture: ["lostpointercapture", true]
|
|
1329
1538
|
};
|
|
1330
1539
|
function createPointerEvents(store) {
|
|
1331
|
-
const { handlePointer } = createEvents(store);
|
|
1540
|
+
const { handlePointer, flushDeferredPointers, processDeferredPointer } = createEvents(store);
|
|
1541
|
+
let nextXRPointerId = XR_POINTER_ID_START;
|
|
1542
|
+
const xrPointers = /* @__PURE__ */ new Map();
|
|
1332
1543
|
return {
|
|
1333
1544
|
priority: 1,
|
|
1334
1545
|
enabled: true,
|
|
1546
|
+
frameTimedRaycasts: true,
|
|
1547
|
+
alwaysFireOnScroll: true,
|
|
1548
|
+
updateOnFrame: false,
|
|
1335
1549
|
compute(event, state) {
|
|
1336
1550
|
state.pointer.set(event.offsetX / state.size.width * 2 - 1, -(event.offsetY / state.size.height) * 2 + 1);
|
|
1337
1551
|
state.raycaster.setFromCamera(state.pointer, state.camera);
|
|
@@ -1341,11 +1555,33 @@ function createPointerEvents(store) {
|
|
|
1341
1555
|
(acc, key) => ({ ...acc, [key]: handlePointer(key) }),
|
|
1342
1556
|
{}
|
|
1343
1557
|
),
|
|
1344
|
-
update: () => {
|
|
1558
|
+
update: (pointerId) => {
|
|
1345
1559
|
const { events, internal } = store.getState();
|
|
1346
|
-
if (
|
|
1560
|
+
if (!events.handlers) return;
|
|
1561
|
+
if (pointerId !== void 0) {
|
|
1562
|
+
const event = internal.pointerDirty.get(pointerId);
|
|
1563
|
+
if (event) {
|
|
1564
|
+
internal.pointerDirty.delete(pointerId);
|
|
1565
|
+
processDeferredPointer(event, pointerId);
|
|
1566
|
+
} else if (internal.lastEvent?.current) {
|
|
1567
|
+
processDeferredPointer(internal.lastEvent.current, pointerId);
|
|
1568
|
+
}
|
|
1569
|
+
} else {
|
|
1570
|
+
flushDeferredPointers();
|
|
1571
|
+
if (internal.lastEvent?.current) {
|
|
1572
|
+
events.handlers.onPointerMove(internal.lastEvent.current);
|
|
1573
|
+
}
|
|
1574
|
+
}
|
|
1575
|
+
},
|
|
1576
|
+
flush: () => {
|
|
1577
|
+
const { events, internal } = store.getState();
|
|
1578
|
+
flushDeferredPointers();
|
|
1579
|
+
if (events.updateOnFrame && internal.lastEvent?.current && events.handlers) {
|
|
1580
|
+
events.handlers.onPointerMove(internal.lastEvent.current);
|
|
1581
|
+
}
|
|
1347
1582
|
},
|
|
1348
1583
|
connect: (target) => {
|
|
1584
|
+
if (!target) return;
|
|
1349
1585
|
const { set, events } = store.getState();
|
|
1350
1586
|
events.disconnect?.();
|
|
1351
1587
|
set((state) => ({ events: { ...state.events, connected: target } }));
|
|
@@ -1369,6 +1605,33 @@ function createPointerEvents(store) {
|
|
|
1369
1605
|
}
|
|
1370
1606
|
set((state) => ({ events: { ...state.events, connected: void 0 } }));
|
|
1371
1607
|
}
|
|
1608
|
+
},
|
|
1609
|
+
// PREVIEW (A8): the XR pointer registration API may change before stable.
|
|
1610
|
+
registerPointer: (config) => {
|
|
1611
|
+
const pointerId = nextXRPointerId++;
|
|
1612
|
+
xrPointers.set(pointerId, config);
|
|
1613
|
+
const { internal } = store.getState();
|
|
1614
|
+
getPointerState(internal, pointerId);
|
|
1615
|
+
return pointerId;
|
|
1616
|
+
},
|
|
1617
|
+
unregisterPointer: (pointerId) => {
|
|
1618
|
+
xrPointers.delete(pointerId);
|
|
1619
|
+
const { internal } = store.getState();
|
|
1620
|
+
const pointerState = internal.pointerMap.get(pointerId);
|
|
1621
|
+
if (pointerState) {
|
|
1622
|
+
for (const [, hoveredObj] of pointerState.hovered) {
|
|
1623
|
+
const eventObject = hoveredObj.eventObject;
|
|
1624
|
+
const instance = eventObject.__r3f;
|
|
1625
|
+
if (instance?.eventCount) {
|
|
1626
|
+
const handlers = instance.handlers;
|
|
1627
|
+
const data = { ...hoveredObj, intersections: [] };
|
|
1628
|
+
handlers.onPointerOut?.(data);
|
|
1629
|
+
handlers.onPointerLeave?.(data);
|
|
1630
|
+
}
|
|
1631
|
+
}
|
|
1632
|
+
internal.pointerMap.delete(pointerId);
|
|
1633
|
+
}
|
|
1634
|
+
internal.pointerDirty.delete(pointerId);
|
|
1372
1635
|
}
|
|
1373
1636
|
};
|
|
1374
1637
|
}
|
|
@@ -1430,1036 +1693,11 @@ function notifyAlpha({ message, link }) {
|
|
|
1430
1693
|
}
|
|
1431
1694
|
}
|
|
1432
1695
|
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
const
|
|
1437
|
-
|
|
1438
|
-
constructor() {
|
|
1439
|
-
/** Ordered list of phase nodes */
|
|
1440
|
-
__publicField$1(this, "phases", []);
|
|
1441
|
-
/** Quick lookup by name */
|
|
1442
|
-
__publicField$1(this, "phaseMap", /* @__PURE__ */ new Map());
|
|
1443
|
-
/** Cached ordered names (invalidated on changes) */
|
|
1444
|
-
__publicField$1(this, "orderedNamesCache", null);
|
|
1445
|
-
this.initializeDefaultPhases();
|
|
1446
|
-
}
|
|
1447
|
-
//* Initialization --------------------------------
|
|
1448
|
-
initializeDefaultPhases() {
|
|
1449
|
-
for (const name of DEFAULT_PHASES) {
|
|
1450
|
-
const node = { name, isAutoGenerated: false };
|
|
1451
|
-
this.phases.push(node);
|
|
1452
|
-
this.phaseMap.set(name, node);
|
|
1453
|
-
}
|
|
1454
|
-
this.invalidateCache();
|
|
1455
|
-
}
|
|
1456
|
-
//* Public API --------------------------------
|
|
1457
|
-
/**
|
|
1458
|
-
* Add a named phase to the graph
|
|
1459
|
-
* @param name - Phase name (must be unique)
|
|
1460
|
-
* @param options - Position options (before or after another phase)
|
|
1461
|
-
*/
|
|
1462
|
-
addPhase(name, options = {}) {
|
|
1463
|
-
if (this.phaseMap.has(name)) {
|
|
1464
|
-
console.warn(`[useFrame] Phase "${name}" already exists`);
|
|
1465
|
-
return;
|
|
1466
|
-
}
|
|
1467
|
-
const { before, after } = options;
|
|
1468
|
-
const node = { name, isAutoGenerated: false };
|
|
1469
|
-
let insertIndex = this.phases.length;
|
|
1470
|
-
const targetIndex = this.getPhaseIndex(before ?? after);
|
|
1471
|
-
if (targetIndex !== -1) {
|
|
1472
|
-
insertIndex = before ? targetIndex : targetIndex + 1;
|
|
1473
|
-
} else {
|
|
1474
|
-
const constraintType = before ? "before" : "after";
|
|
1475
|
-
console.warn(`[useFrame] Phase "${before ?? after}" not found for '${constraintType}' constraint`);
|
|
1476
|
-
}
|
|
1477
|
-
this.phases.splice(insertIndex, 0, node);
|
|
1478
|
-
this.phaseMap.set(name, node);
|
|
1479
|
-
this.invalidateCache();
|
|
1480
|
-
}
|
|
1481
|
-
/**
|
|
1482
|
-
* Get ordered list of phase names
|
|
1483
|
-
*/
|
|
1484
|
-
getOrderedPhases() {
|
|
1485
|
-
if (this.orderedNamesCache === null) this.orderedNamesCache = this.phases.map((p) => p.name);
|
|
1486
|
-
return this.orderedNamesCache;
|
|
1487
|
-
}
|
|
1488
|
-
/**
|
|
1489
|
-
* Check if a phase exists
|
|
1490
|
-
*/
|
|
1491
|
-
hasPhase(name) {
|
|
1492
|
-
return this.phaseMap.has(name);
|
|
1493
|
-
}
|
|
1494
|
-
/**
|
|
1495
|
-
* Get the index of a phase (-1 if not found)
|
|
1496
|
-
*/
|
|
1497
|
-
getPhaseIndex(name) {
|
|
1498
|
-
if (!name) return -1;
|
|
1499
|
-
return this.phases.findIndex((p) => p.name === name);
|
|
1500
|
-
}
|
|
1501
|
-
/**
|
|
1502
|
-
* Ensure a phase exists, creating an auto-generated one if needed.
|
|
1503
|
-
* Used for resolving before/after constraints.
|
|
1504
|
-
*
|
|
1505
|
-
* @param name - The phase name to ensure exists
|
|
1506
|
-
* @returns The phase name (may be auto-generated like 'before:render')
|
|
1507
|
-
*/
|
|
1508
|
-
ensurePhase(name) {
|
|
1509
|
-
if (this.phaseMap.has(name)) return name;
|
|
1510
|
-
const node = { name, isAutoGenerated: true };
|
|
1511
|
-
this.phases.push(node);
|
|
1512
|
-
this.phaseMap.set(name, node);
|
|
1513
|
-
this.invalidateCache();
|
|
1514
|
-
return name;
|
|
1515
|
-
}
|
|
1516
|
-
/**
|
|
1517
|
-
* Resolve where a job with before/after constraints should go.
|
|
1518
|
-
* Creates auto-generated phases if needed.
|
|
1519
|
-
*
|
|
1520
|
-
* @param before - Phase(s) to run before
|
|
1521
|
-
* @param after - Phase(s) to run after
|
|
1522
|
-
* @returns The resolved phase name
|
|
1523
|
-
*/
|
|
1524
|
-
resolveConstraintPhase(before, after) {
|
|
1525
|
-
const beforeArr = before ? Array.isArray(before) ? before : [before] : [];
|
|
1526
|
-
const afterArr = after ? Array.isArray(after) ? after : [after] : [];
|
|
1527
|
-
if (beforeArr.length > 0) {
|
|
1528
|
-
return this.ensureAutoPhase(beforeArr[0], "before", 0);
|
|
1529
|
-
}
|
|
1530
|
-
if (afterArr.length > 0) {
|
|
1531
|
-
return this.ensureAutoPhase(afterArr[0], "after", 1);
|
|
1532
|
-
}
|
|
1533
|
-
return "update";
|
|
1534
|
-
}
|
|
1535
|
-
/**
|
|
1536
|
-
* Ensure an auto-generated phase exists relative to a target phase.
|
|
1537
|
-
* Creates the phase if it doesn't exist, inserting it at the correct position.
|
|
1538
|
-
*
|
|
1539
|
-
* @param target - The target phase name to position relative to
|
|
1540
|
-
* @param prefix - Prefix for auto-generated phase name ('before' or 'after')
|
|
1541
|
-
* @param offset - Insertion offset (0 for before, 1 for after)
|
|
1542
|
-
* @returns The auto-generated phase name
|
|
1543
|
-
*/
|
|
1544
|
-
ensureAutoPhase(target, prefix, offset) {
|
|
1545
|
-
const autoName = `${prefix}:${target}`;
|
|
1546
|
-
if (this.phaseMap.has(autoName)) return autoName;
|
|
1547
|
-
const node = { name: autoName, isAutoGenerated: true };
|
|
1548
|
-
const targetIndex = this.getPhaseIndex(target);
|
|
1549
|
-
if (targetIndex !== -1) this.phases.splice(targetIndex + offset, 0, node);
|
|
1550
|
-
else this.phases.push(node);
|
|
1551
|
-
this.phaseMap.set(autoName, node);
|
|
1552
|
-
this.invalidateCache();
|
|
1553
|
-
return autoName;
|
|
1554
|
-
}
|
|
1555
|
-
// Internal --------------------------------
|
|
1556
|
-
invalidateCache() {
|
|
1557
|
-
this.orderedNamesCache = null;
|
|
1558
|
-
}
|
|
1559
|
-
}
|
|
1560
|
-
|
|
1561
|
-
function rebuildSortedJobs(jobs, phaseGraph) {
|
|
1562
|
-
const orderedPhases = phaseGraph.getOrderedPhases();
|
|
1563
|
-
const buckets = /* @__PURE__ */ new Map();
|
|
1564
|
-
for (const phase of orderedPhases) {
|
|
1565
|
-
buckets.set(phase, []);
|
|
1566
|
-
}
|
|
1567
|
-
for (const job of jobs.values()) {
|
|
1568
|
-
if (!job.enabled) continue;
|
|
1569
|
-
let bucket = buckets.get(job.phase);
|
|
1570
|
-
if (!bucket) {
|
|
1571
|
-
bucket = [];
|
|
1572
|
-
buckets.set(job.phase, bucket);
|
|
1573
|
-
}
|
|
1574
|
-
bucket.push(job);
|
|
1575
|
-
}
|
|
1576
|
-
const sortedBuckets = [];
|
|
1577
|
-
for (const phase of orderedPhases) {
|
|
1578
|
-
const bucket = buckets.get(phase);
|
|
1579
|
-
if (!bucket || bucket.length === 0) continue;
|
|
1580
|
-
bucket.sort((a, b) => {
|
|
1581
|
-
if (a.priority !== b.priority) return b.priority - a.priority;
|
|
1582
|
-
return a.index - b.index;
|
|
1583
|
-
});
|
|
1584
|
-
sortedBuckets.push(hasCrossJobConstraints(bucket) ? topologicalSort(bucket) : bucket);
|
|
1585
|
-
}
|
|
1586
|
-
for (const [phase, bucket] of buckets) {
|
|
1587
|
-
if (!orderedPhases.includes(phase) && bucket.length > 0) {
|
|
1588
|
-
bucket.sort((a, b) => {
|
|
1589
|
-
if (a.priority !== b.priority) return b.priority - a.priority;
|
|
1590
|
-
return a.index - b.index;
|
|
1591
|
-
});
|
|
1592
|
-
sortedBuckets.push(bucket);
|
|
1593
|
-
}
|
|
1594
|
-
}
|
|
1595
|
-
return sortedBuckets.flat();
|
|
1596
|
-
}
|
|
1597
|
-
function hasCrossJobConstraints(bucket) {
|
|
1598
|
-
const jobIds = new Set(bucket.map((j) => j.id));
|
|
1599
|
-
for (const job of bucket) {
|
|
1600
|
-
for (const ref of job.before) {
|
|
1601
|
-
if (jobIds.has(ref)) return true;
|
|
1602
|
-
}
|
|
1603
|
-
for (const ref of job.after) {
|
|
1604
|
-
if (jobIds.has(ref)) return true;
|
|
1605
|
-
}
|
|
1606
|
-
}
|
|
1607
|
-
return false;
|
|
1608
|
-
}
|
|
1609
|
-
function topologicalSort(jobs) {
|
|
1610
|
-
const n = jobs.length;
|
|
1611
|
-
if (n <= 1) return jobs;
|
|
1612
|
-
const jobMap = /* @__PURE__ */ new Map();
|
|
1613
|
-
const inDegree = /* @__PURE__ */ new Map();
|
|
1614
|
-
const adjacency = /* @__PURE__ */ new Map();
|
|
1615
|
-
for (const job of jobs) {
|
|
1616
|
-
jobMap.set(job.id, job);
|
|
1617
|
-
inDegree.set(job.id, 0);
|
|
1618
|
-
adjacency.set(job.id, []);
|
|
1619
|
-
}
|
|
1620
|
-
for (const job of jobs) {
|
|
1621
|
-
for (const ref of job.before) {
|
|
1622
|
-
if (jobMap.has(ref)) {
|
|
1623
|
-
adjacency.get(job.id).push(ref);
|
|
1624
|
-
inDegree.set(ref, inDegree.get(ref) + 1);
|
|
1625
|
-
}
|
|
1626
|
-
}
|
|
1627
|
-
for (const ref of job.after) {
|
|
1628
|
-
if (jobMap.has(ref)) {
|
|
1629
|
-
adjacency.get(ref).push(job.id);
|
|
1630
|
-
inDegree.set(job.id, inDegree.get(job.id) + 1);
|
|
1631
|
-
}
|
|
1632
|
-
}
|
|
1633
|
-
}
|
|
1634
|
-
const queue = [];
|
|
1635
|
-
for (const job of jobs) {
|
|
1636
|
-
if (inDegree.get(job.id) === 0) {
|
|
1637
|
-
queue.push(job);
|
|
1638
|
-
}
|
|
1639
|
-
}
|
|
1640
|
-
queue.sort((a, b) => {
|
|
1641
|
-
if (a.priority !== b.priority) return b.priority - a.priority;
|
|
1642
|
-
return a.index - b.index;
|
|
1643
|
-
});
|
|
1644
|
-
const result = [];
|
|
1645
|
-
while (queue.length > 0) {
|
|
1646
|
-
const job = queue.shift();
|
|
1647
|
-
result.push(job);
|
|
1648
|
-
const neighbors = adjacency.get(job.id) || [];
|
|
1649
|
-
for (const neighborId of neighbors) {
|
|
1650
|
-
const newDegree = inDegree.get(neighborId) - 1;
|
|
1651
|
-
inDegree.set(neighborId, newDegree);
|
|
1652
|
-
if (newDegree === 0) {
|
|
1653
|
-
const neighbor = jobMap.get(neighborId);
|
|
1654
|
-
insertSorted(queue, neighbor);
|
|
1655
|
-
}
|
|
1656
|
-
}
|
|
1657
|
-
}
|
|
1658
|
-
if (result.length !== n) {
|
|
1659
|
-
console.warn("[useFrame] Circular dependency detected in job constraints");
|
|
1660
|
-
const resultIds = new Set(result.map((j) => j.id));
|
|
1661
|
-
for (const job of jobs) {
|
|
1662
|
-
if (!resultIds.has(job.id)) result.push(job);
|
|
1663
|
-
}
|
|
1664
|
-
}
|
|
1665
|
-
return result;
|
|
1666
|
-
}
|
|
1667
|
-
function insertSorted(arr, job) {
|
|
1668
|
-
let i = 0;
|
|
1669
|
-
while (i < arr.length) {
|
|
1670
|
-
const cmp = arr[i];
|
|
1671
|
-
if (job.priority > cmp.priority || job.priority === cmp.priority && job.index < cmp.index) {
|
|
1672
|
-
break;
|
|
1673
|
-
}
|
|
1674
|
-
i++;
|
|
1675
|
-
}
|
|
1676
|
-
arr.splice(i, 0, job);
|
|
1677
|
-
}
|
|
1678
|
-
|
|
1679
|
-
function shouldRun(job, now) {
|
|
1680
|
-
if (!job.enabled) return false;
|
|
1681
|
-
if (!job.fps) return true;
|
|
1682
|
-
const minInterval = 1e3 / job.fps;
|
|
1683
|
-
const lastRun = job.lastRun ?? 0;
|
|
1684
|
-
const elapsed = now - lastRun;
|
|
1685
|
-
if (elapsed < minInterval) return false;
|
|
1686
|
-
if (job.drop) {
|
|
1687
|
-
job.lastRun = now;
|
|
1688
|
-
} else {
|
|
1689
|
-
const steps = Math.floor(elapsed / minInterval);
|
|
1690
|
-
job.lastRun = lastRun + steps * minInterval;
|
|
1691
|
-
if (job.lastRun < now - minInterval) {
|
|
1692
|
-
job.lastRun = now;
|
|
1693
|
-
}
|
|
1694
|
-
}
|
|
1695
|
-
return true;
|
|
1696
|
-
}
|
|
1697
|
-
function resetJobTiming(job) {
|
|
1698
|
-
job.lastRun = void 0;
|
|
1699
|
-
}
|
|
1700
|
-
|
|
1701
|
-
var __defProp = Object.defineProperty;
|
|
1702
|
-
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
1703
|
-
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
1704
|
-
const hmrData = (() => {
|
|
1705
|
-
if (typeof process !== "undefined" && process.env.NODE_ENV === "test") return void 0;
|
|
1706
|
-
if (typeof import_meta_hot !== "undefined") return import_meta_hot;
|
|
1707
|
-
try {
|
|
1708
|
-
return (0, eval)("import.meta.hot");
|
|
1709
|
-
} catch {
|
|
1710
|
-
return void 0;
|
|
1711
|
-
}
|
|
1712
|
-
})();
|
|
1713
|
-
const _Scheduler = class _Scheduler {
|
|
1714
|
-
//* Constructor ================================
|
|
1715
|
-
constructor() {
|
|
1716
|
-
//* Critical State ================================
|
|
1717
|
-
__publicField(this, "roots", /* @__PURE__ */ new Map());
|
|
1718
|
-
__publicField(this, "phaseGraph");
|
|
1719
|
-
__publicField(this, "loopState", {
|
|
1720
|
-
running: false,
|
|
1721
|
-
rafHandle: null,
|
|
1722
|
-
lastTime: null,
|
|
1723
|
-
// null = uninitialized, 0+ = valid timestamp
|
|
1724
|
-
frameCount: 0,
|
|
1725
|
-
elapsedTime: 0,
|
|
1726
|
-
createdAt: performance.now()
|
|
1727
|
-
});
|
|
1728
|
-
__publicField(this, "stoppedTime", 0);
|
|
1729
|
-
//* Private State ================================
|
|
1730
|
-
__publicField(this, "nextRootIndex", 0);
|
|
1731
|
-
__publicField(this, "globalBeforeJobs", /* @__PURE__ */ new Map());
|
|
1732
|
-
__publicField(this, "globalAfterJobs", /* @__PURE__ */ new Map());
|
|
1733
|
-
__publicField(this, "nextGlobalIndex", 0);
|
|
1734
|
-
__publicField(this, "idleCallbacks", /* @__PURE__ */ new Set());
|
|
1735
|
-
__publicField(this, "nextJobIndex", 0);
|
|
1736
|
-
__publicField(this, "jobStateListeners", /* @__PURE__ */ new Map());
|
|
1737
|
-
__publicField(this, "pendingFrames", 0);
|
|
1738
|
-
__publicField(this, "_frameloop", "always");
|
|
1739
|
-
//* Independent Mode & Error Handling State ================================
|
|
1740
|
-
__publicField(this, "_independent", false);
|
|
1741
|
-
__publicField(this, "errorHandler", null);
|
|
1742
|
-
__publicField(this, "rootReadyCallbacks", /* @__PURE__ */ new Set());
|
|
1743
|
-
//* Core Loop Execution Methods ================================
|
|
1744
|
-
/**
|
|
1745
|
-
* Main RAF loop callback.
|
|
1746
|
-
* Executes frame, handles demand mode, and schedules next frame.
|
|
1747
|
-
* @param {number} timestamp - RAF timestamp in milliseconds
|
|
1748
|
-
* @returns {void}
|
|
1749
|
-
* @private
|
|
1750
|
-
*/
|
|
1751
|
-
__publicField(this, "loop", (timestamp) => {
|
|
1752
|
-
if (!this.loopState.running) return;
|
|
1753
|
-
this.executeFrame(timestamp);
|
|
1754
|
-
if (this._frameloop === "demand") {
|
|
1755
|
-
this.pendingFrames = Math.max(0, this.pendingFrames - 1);
|
|
1756
|
-
if (this.pendingFrames === 0) {
|
|
1757
|
-
this.notifyIdle(timestamp);
|
|
1758
|
-
return this.stop();
|
|
1759
|
-
}
|
|
1760
|
-
}
|
|
1761
|
-
this.loopState.rafHandle = requestAnimationFrame(this.loop);
|
|
1762
|
-
});
|
|
1763
|
-
this.phaseGraph = new PhaseGraph();
|
|
1764
|
-
}
|
|
1765
|
-
static get instance() {
|
|
1766
|
-
return globalThis[_Scheduler.INSTANCE_KEY] ?? null;
|
|
1767
|
-
}
|
|
1768
|
-
static set instance(value) {
|
|
1769
|
-
globalThis[_Scheduler.INSTANCE_KEY] = value;
|
|
1770
|
-
}
|
|
1771
|
-
/**
|
|
1772
|
-
* Get the global scheduler instance (creates if doesn't exist).
|
|
1773
|
-
* Uses HMR data to preserve instance across hot reloads.
|
|
1774
|
-
* @returns {Scheduler} The singleton scheduler instance
|
|
1775
|
-
*/
|
|
1776
|
-
static get() {
|
|
1777
|
-
if (!_Scheduler.instance && hmrData?.data?.scheduler) {
|
|
1778
|
-
_Scheduler.instance = hmrData.data.scheduler;
|
|
1779
|
-
}
|
|
1780
|
-
if (!_Scheduler.instance) {
|
|
1781
|
-
_Scheduler.instance = new _Scheduler();
|
|
1782
|
-
if (hmrData?.data) {
|
|
1783
|
-
hmrData.data.scheduler = _Scheduler.instance;
|
|
1784
|
-
}
|
|
1785
|
-
}
|
|
1786
|
-
return _Scheduler.instance;
|
|
1787
|
-
}
|
|
1788
|
-
/**
|
|
1789
|
-
* Reset the singleton instance. Stops the loop and clears all state.
|
|
1790
|
-
* Primarily used for testing to ensure clean state between tests.
|
|
1791
|
-
* @returns {void}
|
|
1792
|
-
*/
|
|
1793
|
-
static reset() {
|
|
1794
|
-
if (_Scheduler.instance) {
|
|
1795
|
-
_Scheduler.instance.stop();
|
|
1796
|
-
_Scheduler.instance = null;
|
|
1797
|
-
}
|
|
1798
|
-
if (hmrData?.data) {
|
|
1799
|
-
hmrData.data.scheduler = null;
|
|
1800
|
-
}
|
|
1801
|
-
}
|
|
1802
|
-
//* Getters & Setters ================================
|
|
1803
|
-
get phases() {
|
|
1804
|
-
return this.phaseGraph.getOrderedPhases();
|
|
1805
|
-
}
|
|
1806
|
-
get frameloop() {
|
|
1807
|
-
return this._frameloop;
|
|
1808
|
-
}
|
|
1809
|
-
set frameloop(mode) {
|
|
1810
|
-
if (this._frameloop === mode) return;
|
|
1811
|
-
const wasAlways = this._frameloop === "always";
|
|
1812
|
-
this._frameloop = mode;
|
|
1813
|
-
if (mode === "always" && !this.loopState.running && this.roots.size > 0) this.start();
|
|
1814
|
-
else if (mode !== "always" && wasAlways) this.stop();
|
|
1815
|
-
}
|
|
1816
|
-
get isRunning() {
|
|
1817
|
-
return this.loopState.running;
|
|
1818
|
-
}
|
|
1819
|
-
get isReady() {
|
|
1820
|
-
return this.roots.size > 0;
|
|
1821
|
-
}
|
|
1822
|
-
get independent() {
|
|
1823
|
-
return this._independent;
|
|
1824
|
-
}
|
|
1825
|
-
set independent(value) {
|
|
1826
|
-
this._independent = value;
|
|
1827
|
-
if (value) this.ensureDefaultRoot();
|
|
1828
|
-
}
|
|
1829
|
-
//* Root Management Methods ================================
|
|
1830
|
-
/**
|
|
1831
|
-
* Register a root (Canvas) with the scheduler.
|
|
1832
|
-
* The first root to register starts the RAF loop (if frameloop='always').
|
|
1833
|
-
* @param {string} id - Unique identifier for this root
|
|
1834
|
-
* @param {RootOptions} [options] - Optional configuration with getState and onError callbacks
|
|
1835
|
-
* @returns {() => void} Unsubscribe function to remove this root
|
|
1836
|
-
*/
|
|
1837
|
-
registerRoot(id, options = {}) {
|
|
1838
|
-
if (this.roots.has(id)) {
|
|
1839
|
-
console.warn(`[Scheduler] Root "${id}" already registered`);
|
|
1840
|
-
return () => this.unregisterRoot(id);
|
|
1841
|
-
}
|
|
1842
|
-
const entry = {
|
|
1843
|
-
id,
|
|
1844
|
-
getState: options.getState ?? (() => ({})),
|
|
1845
|
-
jobs: /* @__PURE__ */ new Map(),
|
|
1846
|
-
sortedJobs: [],
|
|
1847
|
-
needsRebuild: false
|
|
1848
|
-
};
|
|
1849
|
-
if (options.onError) {
|
|
1850
|
-
this.errorHandler = options.onError;
|
|
1851
|
-
}
|
|
1852
|
-
this.roots.set(id, entry);
|
|
1853
|
-
if (this.roots.size === 1) {
|
|
1854
|
-
this.notifyRootReady();
|
|
1855
|
-
if (this._frameloop === "always") this.start();
|
|
1856
|
-
}
|
|
1857
|
-
return () => this.unregisterRoot(id);
|
|
1858
|
-
}
|
|
1859
|
-
/**
|
|
1860
|
-
* Unregister a root from the scheduler.
|
|
1861
|
-
* Cleans up all job state listeners for this root's jobs.
|
|
1862
|
-
* The last root to unregister stops the RAF loop.
|
|
1863
|
-
* @param {string} id - The root ID to unregister
|
|
1864
|
-
* @returns {void}
|
|
1865
|
-
*/
|
|
1866
|
-
unregisterRoot(id) {
|
|
1867
|
-
const root = this.roots.get(id);
|
|
1868
|
-
if (!root) return;
|
|
1869
|
-
for (const jobId of root.jobs.keys()) {
|
|
1870
|
-
this.jobStateListeners.delete(jobId);
|
|
1871
|
-
}
|
|
1872
|
-
this.roots.delete(id);
|
|
1873
|
-
if (this.roots.size === 0) {
|
|
1874
|
-
this.stop();
|
|
1875
|
-
this.errorHandler = null;
|
|
1876
|
-
}
|
|
1877
|
-
}
|
|
1878
|
-
/**
|
|
1879
|
-
* Subscribe to be notified when a root becomes available.
|
|
1880
|
-
* Fires immediately if a root already exists.
|
|
1881
|
-
* @param {() => void} callback - Function called when first root registers
|
|
1882
|
-
* @returns {() => void} Unsubscribe function
|
|
1883
|
-
*/
|
|
1884
|
-
onRootReady(callback) {
|
|
1885
|
-
if (this.roots.size > 0) {
|
|
1886
|
-
callback();
|
|
1887
|
-
return () => {
|
|
1888
|
-
};
|
|
1889
|
-
}
|
|
1890
|
-
this.rootReadyCallbacks.add(callback);
|
|
1891
|
-
return () => this.rootReadyCallbacks.delete(callback);
|
|
1892
|
-
}
|
|
1893
|
-
/**
|
|
1894
|
-
* Notify all registered root-ready callbacks.
|
|
1895
|
-
* Called when the first root registers.
|
|
1896
|
-
* @returns {void}
|
|
1897
|
-
* @private
|
|
1898
|
-
*/
|
|
1899
|
-
notifyRootReady() {
|
|
1900
|
-
for (const cb of this.rootReadyCallbacks) {
|
|
1901
|
-
try {
|
|
1902
|
-
cb();
|
|
1903
|
-
} catch (error) {
|
|
1904
|
-
console.error("[Scheduler] Error in root-ready callback:", error);
|
|
1905
|
-
}
|
|
1906
|
-
}
|
|
1907
|
-
this.rootReadyCallbacks.clear();
|
|
1908
|
-
}
|
|
1909
|
-
/**
|
|
1910
|
-
* Ensure a default root exists for independent mode.
|
|
1911
|
-
* Creates a minimal root with no state provider.
|
|
1912
|
-
* @returns {void}
|
|
1913
|
-
* @private
|
|
1914
|
-
*/
|
|
1915
|
-
ensureDefaultRoot() {
|
|
1916
|
-
if (!this.roots.has("__default__")) {
|
|
1917
|
-
this.registerRoot("__default__");
|
|
1918
|
-
}
|
|
1919
|
-
}
|
|
1920
|
-
/**
|
|
1921
|
-
* Trigger error handling for job errors.
|
|
1922
|
-
* Uses the bound error handler if available, otherwise logs to console.
|
|
1923
|
-
* @param {Error} error - The error to handle
|
|
1924
|
-
* @returns {void}
|
|
1925
|
-
*/
|
|
1926
|
-
triggerError(error) {
|
|
1927
|
-
if (this.errorHandler) this.errorHandler(error);
|
|
1928
|
-
else console.error("[Scheduler]", error);
|
|
1929
|
-
}
|
|
1930
|
-
//* Phase Management Methods ================================
|
|
1931
|
-
/**
|
|
1932
|
-
* Add a named phase to the scheduler's execution order.
|
|
1933
|
-
* Marks all roots for rebuild to incorporate the new phase.
|
|
1934
|
-
* @param {string} name - The phase name (e.g., 'physics', 'postprocess')
|
|
1935
|
-
* @param {AddPhaseOptions} [options] - Positioning options (before/after other phases)
|
|
1936
|
-
* @returns {void}
|
|
1937
|
-
* @example
|
|
1938
|
-
* scheduler.addPhase('physics', { before: 'update' });
|
|
1939
|
-
* scheduler.addPhase('postprocess', { after: 'render' });
|
|
1940
|
-
*/
|
|
1941
|
-
addPhase(name, options) {
|
|
1942
|
-
this.phaseGraph.addPhase(name, options);
|
|
1943
|
-
for (const root of this.roots.values()) {
|
|
1944
|
-
root.needsRebuild = true;
|
|
1945
|
-
}
|
|
1946
|
-
}
|
|
1947
|
-
/**
|
|
1948
|
-
* Check if a phase exists in the scheduler.
|
|
1949
|
-
* @param {string} name - The phase name to check
|
|
1950
|
-
* @returns {boolean} True if the phase exists
|
|
1951
|
-
*/
|
|
1952
|
-
hasPhase(name) {
|
|
1953
|
-
return this.phaseGraph.hasPhase(name);
|
|
1954
|
-
}
|
|
1955
|
-
//* Global Job Registration Methods (Deprecated APIs) ================================
|
|
1956
|
-
/**
|
|
1957
|
-
* Register a global job that runs once per frame (not per-root).
|
|
1958
|
-
* Used internally by deprecated addEffect/addAfterEffect APIs.
|
|
1959
|
-
* @param {'before' | 'after'} phase - When to run: 'before' all roots or 'after' all roots
|
|
1960
|
-
* @param {string} id - Unique identifier for this global job
|
|
1961
|
-
* @param {(timestamp: number) => void} callback - Function called each frame with RAF timestamp
|
|
1962
|
-
* @returns {() => void} Unsubscribe function to remove this global job
|
|
1963
|
-
* @deprecated Use useFrame with phases instead
|
|
1964
|
-
*/
|
|
1965
|
-
registerGlobal(phase, id, callback) {
|
|
1966
|
-
const job = { id, callback };
|
|
1967
|
-
if (phase === "before") {
|
|
1968
|
-
this.globalBeforeJobs.set(id, job);
|
|
1969
|
-
} else {
|
|
1970
|
-
this.globalAfterJobs.set(id, job);
|
|
1971
|
-
}
|
|
1972
|
-
return () => {
|
|
1973
|
-
if (phase === "before") this.globalBeforeJobs.delete(id);
|
|
1974
|
-
else this.globalAfterJobs.delete(id);
|
|
1975
|
-
};
|
|
1976
|
-
}
|
|
1977
|
-
//* Idle Callback Methods (Deprecated API) ================================
|
|
1978
|
-
/**
|
|
1979
|
-
* Register an idle callback that fires when the loop stops.
|
|
1980
|
-
* Used internally by deprecated addTail API.
|
|
1981
|
-
* @param {(timestamp: number) => void} callback - Function called when loop becomes idle
|
|
1982
|
-
* @returns {() => void} Unsubscribe function to remove this idle callback
|
|
1983
|
-
* @deprecated Use demand mode with invalidate() instead
|
|
1984
|
-
*/
|
|
1985
|
-
onIdle(callback) {
|
|
1986
|
-
this.idleCallbacks.add(callback);
|
|
1987
|
-
return () => this.idleCallbacks.delete(callback);
|
|
1988
|
-
}
|
|
1989
|
-
/**
|
|
1990
|
-
* Notify all registered idle callbacks.
|
|
1991
|
-
* Called when the loop stops in demand mode.
|
|
1992
|
-
* @param {number} timestamp - The RAF timestamp when idle occurred
|
|
1993
|
-
* @returns {void}
|
|
1994
|
-
* @private
|
|
1995
|
-
*/
|
|
1996
|
-
notifyIdle(timestamp) {
|
|
1997
|
-
for (const cb of this.idleCallbacks) {
|
|
1998
|
-
try {
|
|
1999
|
-
cb(timestamp);
|
|
2000
|
-
} catch (error) {
|
|
2001
|
-
console.error("[Scheduler] Error in idle callback:", error);
|
|
2002
|
-
}
|
|
2003
|
-
}
|
|
2004
|
-
}
|
|
2005
|
-
//* Job Registration & Management Methods ================================
|
|
2006
|
-
/**
|
|
2007
|
-
* Register a job (frame callback) with a specific root.
|
|
2008
|
-
* This is the core registration method used by useFrame internally.
|
|
2009
|
-
* @param {FrameNextCallback} callback - The function to call each frame
|
|
2010
|
-
* @param {JobOptions & { rootId?: string; system?: boolean }} [options] - Job configuration
|
|
2011
|
-
* @param {string} [options.rootId] - Target root ID (defaults to first registered root)
|
|
2012
|
-
* @param {string} [options.id] - Unique job ID (auto-generated if not provided)
|
|
2013
|
-
* @param {string} [options.phase] - Execution phase (defaults to 'update')
|
|
2014
|
-
* @param {number} [options.priority] - Priority within phase (higher = earlier, default 0)
|
|
2015
|
-
* @param {number} [options.fps] - FPS throttle limit
|
|
2016
|
-
* @param {boolean} [options.drop] - Drop frames when behind (default true)
|
|
2017
|
-
* @param {boolean} [options.enabled] - Whether job is active (default true)
|
|
2018
|
-
* @param {boolean} [options.system] - Internal flag for system jobs (not user-facing)
|
|
2019
|
-
* @returns {() => void} Unsubscribe function to remove this job
|
|
2020
|
-
*/
|
|
2021
|
-
register(callback, options = {}) {
|
|
2022
|
-
const rootId = options.rootId;
|
|
2023
|
-
const root = rootId ? this.roots.get(rootId) : this.roots.values().next().value;
|
|
2024
|
-
if (!root) {
|
|
2025
|
-
console.warn("[Scheduler] No root registered. Is this inside a Canvas?");
|
|
2026
|
-
return () => {
|
|
2027
|
-
};
|
|
2028
|
-
}
|
|
2029
|
-
const id = options.id ?? this.generateJobId();
|
|
2030
|
-
let phase = options.phase ?? "update";
|
|
2031
|
-
if (!options.phase && (options.before || options.after)) {
|
|
2032
|
-
phase = this.phaseGraph.resolveConstraintPhase(options.before, options.after);
|
|
2033
|
-
}
|
|
2034
|
-
const before = this.normalizeConstraints(options.before);
|
|
2035
|
-
const after = this.normalizeConstraints(options.after);
|
|
2036
|
-
const job = {
|
|
2037
|
-
id,
|
|
2038
|
-
callback,
|
|
2039
|
-
phase,
|
|
2040
|
-
before,
|
|
2041
|
-
after,
|
|
2042
|
-
priority: options.priority ?? 0,
|
|
2043
|
-
index: this.nextJobIndex++,
|
|
2044
|
-
fps: options.fps,
|
|
2045
|
-
drop: options.drop ?? true,
|
|
2046
|
-
enabled: options.enabled ?? true,
|
|
2047
|
-
system: options.system ?? false
|
|
2048
|
-
};
|
|
2049
|
-
if (root.jobs.has(id)) {
|
|
2050
|
-
console.warn(`[useFrame] Job with id "${id}" already exists, replacing`);
|
|
2051
|
-
}
|
|
2052
|
-
root.jobs.set(id, job);
|
|
2053
|
-
root.needsRebuild = true;
|
|
2054
|
-
return () => this.unregister(id, root.id);
|
|
2055
|
-
}
|
|
2056
|
-
/**
|
|
2057
|
-
* Unregister a job by its ID.
|
|
2058
|
-
* Searches all roots if rootId is not provided.
|
|
2059
|
-
* @param {string} id - The job ID to unregister
|
|
2060
|
-
* @param {string} [rootId] - Optional root ID to search (searches all if not provided)
|
|
2061
|
-
* @returns {void}
|
|
2062
|
-
*/
|
|
2063
|
-
unregister(id, rootId) {
|
|
2064
|
-
const root = rootId ? this.roots.get(rootId) : Array.from(this.roots.values()).find((r) => r.jobs.has(id));
|
|
2065
|
-
if (root?.jobs.delete(id)) {
|
|
2066
|
-
root.needsRebuild = true;
|
|
2067
|
-
this.jobStateListeners.delete(id);
|
|
2068
|
-
}
|
|
2069
|
-
}
|
|
2070
|
-
/**
|
|
2071
|
-
* Update a job's options dynamically.
|
|
2072
|
-
* Searches all roots to find the job by ID.
|
|
2073
|
-
* Phase/constraint changes trigger a rebuild of the sorted job list.
|
|
2074
|
-
* @param {string} id - The job ID to update
|
|
2075
|
-
* @param {Partial<JobOptions>} options - The options to update
|
|
2076
|
-
* @returns {void}
|
|
2077
|
-
*/
|
|
2078
|
-
updateJob(id, options) {
|
|
2079
|
-
let job;
|
|
2080
|
-
let root;
|
|
2081
|
-
for (const r of this.roots.values()) {
|
|
2082
|
-
job = r.jobs.get(id);
|
|
2083
|
-
if (job) {
|
|
2084
|
-
root = r;
|
|
2085
|
-
break;
|
|
2086
|
-
}
|
|
2087
|
-
}
|
|
2088
|
-
if (!job || !root) return;
|
|
2089
|
-
if (options.priority !== void 0) job.priority = options.priority;
|
|
2090
|
-
if (options.fps !== void 0) job.fps = options.fps;
|
|
2091
|
-
if (options.drop !== void 0) job.drop = options.drop;
|
|
2092
|
-
if (options.enabled !== void 0) {
|
|
2093
|
-
const wasEnabled = job.enabled;
|
|
2094
|
-
job.enabled = options.enabled;
|
|
2095
|
-
if (!wasEnabled && job.enabled) resetJobTiming(job);
|
|
2096
|
-
if (wasEnabled !== job.enabled) root.needsRebuild = true;
|
|
2097
|
-
}
|
|
2098
|
-
if (options.phase !== void 0 || options.before !== void 0 || options.after !== void 0) {
|
|
2099
|
-
if (options.phase) job.phase = options.phase;
|
|
2100
|
-
if (options.before !== void 0) job.before = this.normalizeConstraints(options.before);
|
|
2101
|
-
if (options.after !== void 0) job.after = this.normalizeConstraints(options.after);
|
|
2102
|
-
root.needsRebuild = true;
|
|
2103
|
-
}
|
|
2104
|
-
}
|
|
2105
|
-
//* Job State Management Methods ================================
|
|
2106
|
-
/**
|
|
2107
|
-
* Check if a job is currently paused (disabled).
|
|
2108
|
-
* @param {string} id - The job ID to check
|
|
2109
|
-
* @returns {boolean} True if the job exists and is paused
|
|
2110
|
-
*/
|
|
2111
|
-
isJobPaused(id) {
|
|
2112
|
-
for (const root of this.roots.values()) {
|
|
2113
|
-
const job = root.jobs.get(id);
|
|
2114
|
-
if (job) return !job.enabled;
|
|
2115
|
-
}
|
|
2116
|
-
return false;
|
|
2117
|
-
}
|
|
2118
|
-
/**
|
|
2119
|
-
* Subscribe to state changes for a specific job.
|
|
2120
|
-
* Listener is called when job is paused or resumed.
|
|
2121
|
-
* @param {string} id - The job ID to subscribe to
|
|
2122
|
-
* @param {() => void} listener - Callback invoked on state changes
|
|
2123
|
-
* @returns {() => void} Unsubscribe function
|
|
2124
|
-
*/
|
|
2125
|
-
subscribeJobState(id, listener) {
|
|
2126
|
-
if (!this.jobStateListeners.has(id)) {
|
|
2127
|
-
this.jobStateListeners.set(id, /* @__PURE__ */ new Set());
|
|
2128
|
-
}
|
|
2129
|
-
this.jobStateListeners.get(id).add(listener);
|
|
2130
|
-
return () => {
|
|
2131
|
-
this.jobStateListeners.get(id)?.delete(listener);
|
|
2132
|
-
if (this.jobStateListeners.get(id)?.size === 0) {
|
|
2133
|
-
this.jobStateListeners.delete(id);
|
|
2134
|
-
}
|
|
2135
|
-
};
|
|
2136
|
-
}
|
|
2137
|
-
/**
|
|
2138
|
-
* Notify all listeners that a job's state has changed.
|
|
2139
|
-
* @param {string} id - The job ID that changed
|
|
2140
|
-
* @returns {void}
|
|
2141
|
-
* @private
|
|
2142
|
-
*/
|
|
2143
|
-
notifyJobStateChange(id) {
|
|
2144
|
-
this.jobStateListeners.get(id)?.forEach((listener) => listener());
|
|
2145
|
-
}
|
|
2146
|
-
/**
|
|
2147
|
-
* Pause a job by ID (sets enabled=false).
|
|
2148
|
-
* Notifies any subscribed state listeners.
|
|
2149
|
-
* @param {string} id - The job ID to pause
|
|
2150
|
-
* @returns {void}
|
|
2151
|
-
*/
|
|
2152
|
-
pauseJob(id) {
|
|
2153
|
-
this.updateJob(id, { enabled: false });
|
|
2154
|
-
this.notifyJobStateChange(id);
|
|
2155
|
-
}
|
|
2156
|
-
/**
|
|
2157
|
-
* Resume a paused job by ID (sets enabled=true).
|
|
2158
|
-
* Resets job timing to prevent frame accumulation.
|
|
2159
|
-
* Notifies any subscribed state listeners.
|
|
2160
|
-
* @param {string} id - The job ID to resume
|
|
2161
|
-
* @returns {void}
|
|
2162
|
-
*/
|
|
2163
|
-
resumeJob(id) {
|
|
2164
|
-
this.updateJob(id, { enabled: true });
|
|
2165
|
-
this.notifyJobStateChange(id);
|
|
2166
|
-
}
|
|
2167
|
-
//* Frame Loop Control Methods ================================
|
|
2168
|
-
/**
|
|
2169
|
-
* Start the requestAnimationFrame loop.
|
|
2170
|
-
* Resets timing state (elapsedTime, frameCount) on start.
|
|
2171
|
-
* No-op if already running.
|
|
2172
|
-
* @returns {void}
|
|
2173
|
-
*/
|
|
2174
|
-
start() {
|
|
2175
|
-
if (this.loopState.running) return;
|
|
2176
|
-
const { elapsedTime, createdAt } = this.loopState;
|
|
2177
|
-
let adjustedCreated = 0;
|
|
2178
|
-
if (this.stoppedTime > 0) {
|
|
2179
|
-
adjustedCreated = createdAt - (performance.now() - this.stoppedTime);
|
|
2180
|
-
this.stoppedTime = 0;
|
|
2181
|
-
}
|
|
2182
|
-
Object.assign(this.loopState, {
|
|
2183
|
-
running: true,
|
|
2184
|
-
elapsedTime: elapsedTime ?? 0,
|
|
2185
|
-
lastTime: performance.now(),
|
|
2186
|
-
createdAt: adjustedCreated > 0 ? adjustedCreated : performance.now(),
|
|
2187
|
-
frameCount: 0,
|
|
2188
|
-
rafHandle: requestAnimationFrame(this.loop)
|
|
2189
|
-
});
|
|
2190
|
-
}
|
|
2191
|
-
/**
|
|
2192
|
-
* Stop the requestAnimationFrame loop.
|
|
2193
|
-
* Cancels any pending RAF callback.
|
|
2194
|
-
* No-op if not running.
|
|
2195
|
-
* @returns {void}
|
|
2196
|
-
*/
|
|
2197
|
-
stop() {
|
|
2198
|
-
if (!this.loopState.running) return;
|
|
2199
|
-
this.loopState.running = false;
|
|
2200
|
-
if (this.loopState.rafHandle !== null) {
|
|
2201
|
-
cancelAnimationFrame(this.loopState.rafHandle);
|
|
2202
|
-
this.loopState.rafHandle = null;
|
|
2203
|
-
}
|
|
2204
|
-
this.stoppedTime = performance.now();
|
|
2205
|
-
}
|
|
2206
|
-
/**
|
|
2207
|
-
* Request frames to be rendered in demand mode.
|
|
2208
|
-
* Accumulates pending frames (capped at 60) and starts the loop if not running.
|
|
2209
|
-
* No-op if frameloop is not 'demand'.
|
|
2210
|
-
* @param {number} [frames=1] - Number of frames to request
|
|
2211
|
-
* @param {boolean} [stackFrames=false] - Whether to add frames to existing pending count
|
|
2212
|
-
* - `false` (default): Sets pending frames to the specified value (replaces existing count)
|
|
2213
|
-
* - `true`: Adds frames to existing pending count (useful for accumulating invalidations)
|
|
2214
|
-
* @returns {void}
|
|
2215
|
-
* @example
|
|
2216
|
-
* // Request a single frame render
|
|
2217
|
-
* scheduler.invalidate();
|
|
2218
|
-
*
|
|
2219
|
-
* @example
|
|
2220
|
-
* // Request 5 frames (e.g., for animations)
|
|
2221
|
-
* scheduler.invalidate(5);
|
|
2222
|
-
*
|
|
2223
|
-
* @example
|
|
2224
|
-
* // Set pending frames to exactly 3 (don't stack with existing)
|
|
2225
|
-
* scheduler.invalidate(3, false);
|
|
2226
|
-
*
|
|
2227
|
-
* @example
|
|
2228
|
-
* // Add 2 more frames to existing pending count
|
|
2229
|
-
* scheduler.invalidate(2, true);
|
|
2230
|
-
*/
|
|
2231
|
-
invalidate(frames = 1, stackFrames = false) {
|
|
2232
|
-
if (this._frameloop !== "demand") return;
|
|
2233
|
-
const baseFrames = stackFrames ? this.pendingFrames : 0;
|
|
2234
|
-
this.pendingFrames = Math.min(60, baseFrames + frames);
|
|
2235
|
-
if (!this.loopState.running && this.pendingFrames > 0) this.start();
|
|
2236
|
-
}
|
|
2237
|
-
/**
|
|
2238
|
-
* Reset timing state for deterministic testing.
|
|
2239
|
-
* Preserves jobs and roots but resets lastTime, frameCount, elapsedTime, etc.
|
|
2240
|
-
* @returns {void}
|
|
2241
|
-
*/
|
|
2242
|
-
resetTiming() {
|
|
2243
|
-
this.loopState.lastTime = null;
|
|
2244
|
-
this.loopState.frameCount = 0;
|
|
2245
|
-
this.loopState.elapsedTime = 0;
|
|
2246
|
-
this.loopState.createdAt = performance.now();
|
|
2247
|
-
}
|
|
2248
|
-
//* Manual Stepping Methods ================================
|
|
2249
|
-
/**
|
|
2250
|
-
* Manually execute a single frame for all roots.
|
|
2251
|
-
* Useful for frameloop='never' mode or testing scenarios.
|
|
2252
|
-
* @param {number} [timestamp] - Optional timestamp (defaults to performance.now())
|
|
2253
|
-
* @returns {void}
|
|
2254
|
-
* @example
|
|
2255
|
-
* // Manual control mode
|
|
2256
|
-
* scheduler.frameloop = 'never';
|
|
2257
|
-
* scheduler.step(); // Execute one frame
|
|
2258
|
-
*/
|
|
2259
|
-
step(timestamp) {
|
|
2260
|
-
const now = timestamp ?? performance.now();
|
|
2261
|
-
this.executeFrame(now);
|
|
2262
|
-
}
|
|
2263
|
-
/**
|
|
2264
|
-
* Manually execute a single job by its ID.
|
|
2265
|
-
* Useful for testing individual job callbacks in isolation.
|
|
2266
|
-
* @param {string} id - The job ID to step
|
|
2267
|
-
* @param {number} [timestamp] - Optional timestamp (defaults to performance.now())
|
|
2268
|
-
* @returns {void}
|
|
2269
|
-
*/
|
|
2270
|
-
stepJob(id, timestamp) {
|
|
2271
|
-
let job;
|
|
2272
|
-
let root;
|
|
2273
|
-
for (const r of this.roots.values()) {
|
|
2274
|
-
job = r.jobs.get(id);
|
|
2275
|
-
if (job) {
|
|
2276
|
-
root = r;
|
|
2277
|
-
break;
|
|
2278
|
-
}
|
|
2279
|
-
}
|
|
2280
|
-
if (!job || !root) {
|
|
2281
|
-
console.warn(`[Scheduler] Job "${id}" not found`);
|
|
2282
|
-
return;
|
|
2283
|
-
}
|
|
2284
|
-
const now = timestamp ?? performance.now();
|
|
2285
|
-
const deltaMs = this.loopState.lastTime !== null ? now - this.loopState.lastTime : 0;
|
|
2286
|
-
const delta = deltaMs / 1e3;
|
|
2287
|
-
const elapsed = now - this.loopState.createdAt;
|
|
2288
|
-
const providedState = root.getState?.() ?? {};
|
|
2289
|
-
const frameState = {
|
|
2290
|
-
...providedState,
|
|
2291
|
-
time: now,
|
|
2292
|
-
delta,
|
|
2293
|
-
elapsed,
|
|
2294
|
-
frame: this.loopState.frameCount
|
|
2295
|
-
};
|
|
2296
|
-
try {
|
|
2297
|
-
job.callback(frameState, delta);
|
|
2298
|
-
} catch (error) {
|
|
2299
|
-
console.error(`[Scheduler] Error in job "${job.id}":`, error);
|
|
2300
|
-
this.triggerError(error instanceof Error ? error : new Error(String(error)));
|
|
2301
|
-
}
|
|
2302
|
-
}
|
|
2303
|
-
/**
|
|
2304
|
-
* Execute a single frame across all roots.
|
|
2305
|
-
* Order: globalBefore → each root's jobs → globalAfter
|
|
2306
|
-
* @param {number} timestamp - RAF timestamp in milliseconds
|
|
2307
|
-
* @returns {void}
|
|
2308
|
-
* @private
|
|
2309
|
-
*/
|
|
2310
|
-
executeFrame(timestamp) {
|
|
2311
|
-
const deltaMs = this.loopState.lastTime !== null ? timestamp - this.loopState.lastTime : 0;
|
|
2312
|
-
const delta = deltaMs / 1e3;
|
|
2313
|
-
this.loopState.lastTime = timestamp;
|
|
2314
|
-
this.loopState.frameCount++;
|
|
2315
|
-
this.loopState.elapsedTime += deltaMs;
|
|
2316
|
-
this.runGlobalJobs(this.globalBeforeJobs, timestamp);
|
|
2317
|
-
for (const root of this.roots.values()) {
|
|
2318
|
-
this.tickRoot(root, timestamp, delta);
|
|
2319
|
-
}
|
|
2320
|
-
this.runGlobalJobs(this.globalAfterJobs, timestamp);
|
|
2321
|
-
}
|
|
2322
|
-
/**
|
|
2323
|
-
* Run all global jobs from a job map.
|
|
2324
|
-
* Catches and logs errors without stopping execution.
|
|
2325
|
-
* @param {Map<string, GlobalJob>} jobs - The global jobs map to execute
|
|
2326
|
-
* @param {number} timestamp - RAF timestamp in milliseconds
|
|
2327
|
-
* @returns {void}
|
|
2328
|
-
* @private
|
|
2329
|
-
*/
|
|
2330
|
-
runGlobalJobs(jobs, timestamp) {
|
|
2331
|
-
for (const job of jobs.values()) {
|
|
2332
|
-
try {
|
|
2333
|
-
job.callback(timestamp);
|
|
2334
|
-
} catch (error) {
|
|
2335
|
-
console.error(`[Scheduler] Error in global job "${job.id}":`, error);
|
|
2336
|
-
}
|
|
2337
|
-
}
|
|
2338
|
-
}
|
|
2339
|
-
/**
|
|
2340
|
-
* Execute all jobs for a single root in sorted order.
|
|
2341
|
-
* Rebuilds sorted job list if needed, then dispatches each job.
|
|
2342
|
-
* Errors are caught and propagated via triggerError.
|
|
2343
|
-
* @param {RootEntry} root - The root entry to tick
|
|
2344
|
-
* @param {number} timestamp - RAF timestamp in milliseconds
|
|
2345
|
-
* @param {number} delta - Time since last frame in seconds
|
|
2346
|
-
* @returns {void}
|
|
2347
|
-
* @private
|
|
2348
|
-
*/
|
|
2349
|
-
tickRoot(root, timestamp, delta) {
|
|
2350
|
-
if (root.needsRebuild) {
|
|
2351
|
-
root.sortedJobs = rebuildSortedJobs(root.jobs, this.phaseGraph);
|
|
2352
|
-
root.needsRebuild = false;
|
|
2353
|
-
}
|
|
2354
|
-
const providedState = root.getState?.() ?? {};
|
|
2355
|
-
const frameState = {
|
|
2356
|
-
...providedState,
|
|
2357
|
-
time: timestamp,
|
|
2358
|
-
delta,
|
|
2359
|
-
elapsed: this.loopState.elapsedTime / 1e3,
|
|
2360
|
-
// Convert ms to seconds
|
|
2361
|
-
frame: this.loopState.frameCount
|
|
2362
|
-
};
|
|
2363
|
-
for (const job of root.sortedJobs) {
|
|
2364
|
-
if (!shouldRun(job, timestamp)) continue;
|
|
2365
|
-
try {
|
|
2366
|
-
job.callback(frameState, delta);
|
|
2367
|
-
} catch (error) {
|
|
2368
|
-
console.error(`[Scheduler] Error in job "${job.id}":`, error);
|
|
2369
|
-
this.triggerError(error instanceof Error ? error : new Error(String(error)));
|
|
2370
|
-
}
|
|
2371
|
-
}
|
|
2372
|
-
}
|
|
2373
|
-
//* Debug & Inspection Methods ================================
|
|
2374
|
-
/**
|
|
2375
|
-
* Get the total number of registered jobs across all roots.
|
|
2376
|
-
* Includes both per-root jobs and global before/after jobs.
|
|
2377
|
-
* @returns {number} Total job count
|
|
2378
|
-
*/
|
|
2379
|
-
getJobCount() {
|
|
2380
|
-
let count = 0;
|
|
2381
|
-
for (const root of this.roots.values()) {
|
|
2382
|
-
count += root.jobs.size;
|
|
2383
|
-
}
|
|
2384
|
-
return count + this.globalBeforeJobs.size + this.globalAfterJobs.size;
|
|
2385
|
-
}
|
|
2386
|
-
/**
|
|
2387
|
-
* Get all registered job IDs across all roots.
|
|
2388
|
-
* Includes both per-root jobs and global before/after jobs.
|
|
2389
|
-
* @returns {string[]} Array of all job IDs
|
|
2390
|
-
*/
|
|
2391
|
-
getJobIds() {
|
|
2392
|
-
const ids = [];
|
|
2393
|
-
for (const root of this.roots.values()) {
|
|
2394
|
-
ids.push(...root.jobs.keys());
|
|
2395
|
-
}
|
|
2396
|
-
ids.push(...this.globalBeforeJobs.keys());
|
|
2397
|
-
ids.push(...this.globalAfterJobs.keys());
|
|
2398
|
-
return ids;
|
|
2399
|
-
}
|
|
2400
|
-
/**
|
|
2401
|
-
* Get the number of registered roots (Canvas instances).
|
|
2402
|
-
* @returns {number} Number of registered roots
|
|
2403
|
-
*/
|
|
2404
|
-
getRootCount() {
|
|
2405
|
-
return this.roots.size;
|
|
2406
|
-
}
|
|
2407
|
-
/**
|
|
2408
|
-
* Check if any user (non-system) jobs are registered in a specific phase.
|
|
2409
|
-
* Used by the default render job to know if a user has taken over rendering.
|
|
2410
|
-
*
|
|
2411
|
-
* @param phase The phase to check
|
|
2412
|
-
* @param rootId Optional root ID to check (checks all roots if not provided)
|
|
2413
|
-
* @returns true if any user jobs exist in the phase
|
|
2414
|
-
*/
|
|
2415
|
-
hasUserJobsInPhase(phase, rootId) {
|
|
2416
|
-
const rootsToCheck = rootId ? [this.roots.get(rootId)].filter(Boolean) : Array.from(this.roots.values());
|
|
2417
|
-
return rootsToCheck.some((root) => {
|
|
2418
|
-
if (!root) return false;
|
|
2419
|
-
for (const job of root.jobs.values()) {
|
|
2420
|
-
if (job.phase === phase && !job.system && job.enabled) return true;
|
|
2421
|
-
}
|
|
2422
|
-
return false;
|
|
2423
|
-
});
|
|
2424
|
-
}
|
|
2425
|
-
//* Utility Methods ================================
|
|
2426
|
-
/**
|
|
2427
|
-
* Generate a unique root ID for automatic root registration.
|
|
2428
|
-
* @returns {string} A unique root ID in the format 'root_N'
|
|
2429
|
-
*/
|
|
2430
|
-
generateRootId() {
|
|
2431
|
-
return `root_${this.nextRootIndex++}`;
|
|
2432
|
-
}
|
|
2433
|
-
/**
|
|
2434
|
-
* Generate a unique job ID.
|
|
2435
|
-
* @returns {string} A unique job ID in the format 'job_N'
|
|
2436
|
-
* @private
|
|
2437
|
-
*/
|
|
2438
|
-
generateJobId() {
|
|
2439
|
-
return `job_${this.nextJobIndex}`;
|
|
2440
|
-
}
|
|
2441
|
-
/**
|
|
2442
|
-
* Normalize before/after constraints to a Set.
|
|
2443
|
-
* Handles undefined, single string, or array inputs.
|
|
2444
|
-
* @param {string | string[] | undefined} value - The constraint value(s)
|
|
2445
|
-
* @returns {Set<string>} Normalized Set of constraint strings
|
|
2446
|
-
* @private
|
|
2447
|
-
*/
|
|
2448
|
-
normalizeConstraints(value) {
|
|
2449
|
-
if (!value) return /* @__PURE__ */ new Set();
|
|
2450
|
-
if (Array.isArray(value)) return new Set(value);
|
|
2451
|
-
return /* @__PURE__ */ new Set([value]);
|
|
2452
|
-
}
|
|
2453
|
-
};
|
|
2454
|
-
//* Static State & Methods (Singleton Usage) ================================
|
|
2455
|
-
//* Cross-Bundle Singleton Key ==============================
|
|
2456
|
-
// Use Symbol.for() to ensure scheduler is shared across bundle boundaries
|
|
2457
|
-
// This prevents issues when mixing imports from @react-three/fiber and @react-three/fiber/webgpu
|
|
2458
|
-
__publicField(_Scheduler, "INSTANCE_KEY", Symbol.for("@react-three/fiber.scheduler"));
|
|
2459
|
-
let Scheduler = _Scheduler;
|
|
2460
|
-
const getScheduler = () => Scheduler.get();
|
|
2461
|
-
if (hmrData) {
|
|
2462
|
-
hmrData.accept?.();
|
|
1696
|
+
const INTERNAL_LIB_MARKERS = ["zustand", "setState", "Object.assign"];
|
|
1697
|
+
function isInternalRendererAccess(stack, callerDepth = 2) {
|
|
1698
|
+
if (!stack) return false;
|
|
1699
|
+
const caller = stack.split("\n")[callerDepth] ?? "";
|
|
1700
|
+
return INTERNAL_LIB_MARKERS.some((m) => caller.includes(m));
|
|
2463
1701
|
}
|
|
2464
1702
|
|
|
2465
1703
|
const R3F_CONTEXT = Symbol.for("@react-three/fiber.context");
|
|
@@ -2499,7 +1737,14 @@ const createStore = (invalidate, advance) => {
|
|
|
2499
1737
|
frustum: new Frustum(),
|
|
2500
1738
|
autoUpdateFrustum: true,
|
|
2501
1739
|
raycaster: null,
|
|
2502
|
-
events: {
|
|
1740
|
+
events: {
|
|
1741
|
+
priority: 1,
|
|
1742
|
+
enabled: true,
|
|
1743
|
+
connected: false,
|
|
1744
|
+
frameTimedRaycasts: true,
|
|
1745
|
+
alwaysFireOnScroll: true,
|
|
1746
|
+
updateOnFrame: false
|
|
1747
|
+
},
|
|
2503
1748
|
scene: null,
|
|
2504
1749
|
rootScene: null,
|
|
2505
1750
|
xr: null,
|
|
@@ -2587,14 +1832,18 @@ const createStore = (invalidate, advance) => {
|
|
|
2587
1832
|
}),
|
|
2588
1833
|
setFrameloop: (frameloop = "always") => {
|
|
2589
1834
|
set(() => ({ frameloop }));
|
|
1835
|
+
getScheduler().frameloop = frameloop;
|
|
2590
1836
|
},
|
|
2591
1837
|
setError: (error) => set(() => ({ error })),
|
|
2592
1838
|
error: null,
|
|
2593
|
-
//* TSL State (managed via hooks: useUniforms, useNodes, useTextures,
|
|
1839
|
+
//* TSL State (managed via hooks: useUniforms, useNodes, useBuffers, useGPUStorage, useTextures, useRenderPipeline) ==============================
|
|
2594
1840
|
uniforms: {},
|
|
2595
1841
|
nodes: {},
|
|
1842
|
+
buffers: {},
|
|
1843
|
+
gpuStorage: {},
|
|
2596
1844
|
textures: /* @__PURE__ */ new Map(),
|
|
2597
|
-
|
|
1845
|
+
_textureRefs: /* @__PURE__ */ new Map(),
|
|
1846
|
+
renderPipeline: null,
|
|
2598
1847
|
passes: {},
|
|
2599
1848
|
_hmrVersion: 0,
|
|
2600
1849
|
_sizeImperative: false,
|
|
@@ -2603,12 +1852,16 @@ const createStore = (invalidate, advance) => {
|
|
|
2603
1852
|
internal: {
|
|
2604
1853
|
// Events
|
|
2605
1854
|
interaction: [],
|
|
2606
|
-
hovered: /* @__PURE__ */ new Map(),
|
|
2607
1855
|
subscribers: [],
|
|
1856
|
+
// Per-pointer state (new unified structure)
|
|
1857
|
+
pointerMap: /* @__PURE__ */ new Map(),
|
|
1858
|
+
pointerDirty: /* @__PURE__ */ new Map(),
|
|
1859
|
+
lastEvent: React.createRef(),
|
|
1860
|
+
// Deprecated but kept for backwards compatibility
|
|
1861
|
+
hovered: /* @__PURE__ */ new Map(),
|
|
2608
1862
|
initialClick: [0, 0],
|
|
2609
1863
|
initialHits: [],
|
|
2610
1864
|
capturedMap: /* @__PURE__ */ new Map(),
|
|
2611
|
-
lastEvent: React.createRef(),
|
|
2612
1865
|
// Visibility tracking (onFramed, onOccluded, onVisible)
|
|
2613
1866
|
visibilityRegistry: /* @__PURE__ */ new Map(),
|
|
2614
1867
|
// Occlusion system (WebGPU only)
|
|
@@ -2647,8 +1900,7 @@ const createStore = (invalidate, advance) => {
|
|
|
2647
1900
|
const currentState = rootStore.getState();
|
|
2648
1901
|
if (!currentState.isLegacy && currentState.internal.actualRenderer) {
|
|
2649
1902
|
const stack = new Error().stack || "";
|
|
2650
|
-
|
|
2651
|
-
if (!isInternalAccess) {
|
|
1903
|
+
if (!isInternalRendererAccess(stack)) {
|
|
2652
1904
|
const cleanedStack = stack.split("\n").slice(2).join("\n") || "Stack trace unavailable";
|
|
2653
1905
|
notifyDepreciated({
|
|
2654
1906
|
heading: "Accessing state.gl in WebGPU mode",
|
|
@@ -2696,14 +1948,16 @@ const createStore = (invalidate, advance) => {
|
|
|
2696
1948
|
oldSize = size;
|
|
2697
1949
|
oldDpr = viewport.dpr;
|
|
2698
1950
|
updateCamera(camera, size);
|
|
2699
|
-
if (canvasTarget) {
|
|
1951
|
+
if (internal.isSecondary && canvasTarget) {
|
|
2700
1952
|
if (viewport.dpr > 0) canvasTarget.setPixelRatio(viewport.dpr);
|
|
2701
|
-
|
|
2702
|
-
canvasTarget.setSize(size.width, size.height, updateStyle);
|
|
1953
|
+
canvasTarget.setSize(size.width, size.height, false);
|
|
2703
1954
|
} else {
|
|
2704
1955
|
if (viewport.dpr > 0) actualRenderer.setPixelRatio(viewport.dpr);
|
|
2705
|
-
|
|
2706
|
-
|
|
1956
|
+
actualRenderer.setSize(size.width, size.height, false);
|
|
1957
|
+
if (canvasTarget) {
|
|
1958
|
+
if (viewport.dpr > 0) canvasTarget.setPixelRatio(viewport.dpr);
|
|
1959
|
+
canvasTarget.setSize(size.width, size.height, false);
|
|
1960
|
+
}
|
|
2707
1961
|
}
|
|
2708
1962
|
}
|
|
2709
1963
|
if (camera !== oldCamera) {
|
|
@@ -2759,19 +2013,25 @@ function loadingFn(extensions, onProgress) {
|
|
|
2759
2013
|
);
|
|
2760
2014
|
};
|
|
2761
2015
|
}
|
|
2762
|
-
function useLoader(loader, input, extensions, onProgress) {
|
|
2763
|
-
const
|
|
2016
|
+
function useLoader(loader, input, extensions, onProgress, cacheKey) {
|
|
2017
|
+
const inputs = Array.isArray(input) ? input : [input];
|
|
2018
|
+
const cacheKeys = Array.isArray(cacheKey ?? input) ? cacheKey ?? input : [cacheKey ?? input];
|
|
2764
2019
|
const fn = loadingFn(extensions, onProgress);
|
|
2765
|
-
const results =
|
|
2020
|
+
const results = cacheKeys.map(
|
|
2021
|
+
(key, index) => suspend(() => fn(loader, inputs[index]), [loader, key], { equal: is.equ })
|
|
2022
|
+
);
|
|
2766
2023
|
return Array.isArray(input) ? results : results[0];
|
|
2767
2024
|
}
|
|
2768
|
-
useLoader.preload = function(loader, input, extensions, onProgress) {
|
|
2769
|
-
const
|
|
2770
|
-
|
|
2025
|
+
useLoader.preload = function(loader, input, extensions, onProgress, cacheKey) {
|
|
2026
|
+
const inputs = Array.isArray(input) ? input : [input];
|
|
2027
|
+
const cacheKeys = Array.isArray(cacheKey ?? input) ? cacheKey ?? input : [cacheKey ?? input];
|
|
2028
|
+
cacheKeys.forEach(
|
|
2029
|
+
(key, index) => preload(() => loadingFn(extensions, onProgress)(loader, inputs[index]), [loader, key])
|
|
2030
|
+
);
|
|
2771
2031
|
};
|
|
2772
|
-
useLoader.clear = function(loader, input) {
|
|
2773
|
-
const
|
|
2774
|
-
|
|
2032
|
+
useLoader.clear = function(loader, input, cacheKey) {
|
|
2033
|
+
const cacheKeys = Array.isArray(cacheKey ?? input) ? cacheKey ?? input : [cacheKey ?? input];
|
|
2034
|
+
cacheKeys.forEach((key) => clear([loader, key]));
|
|
2775
2035
|
};
|
|
2776
2036
|
useLoader.loader = getLoader;
|
|
2777
2037
|
|
|
@@ -2847,20 +2107,14 @@ function useFrame(callback, priorityOrOptions) {
|
|
|
2847
2107
|
}
|
|
2848
2108
|
};
|
|
2849
2109
|
} else {
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
unregisterJob = registerOutside();
|
|
2859
|
-
});
|
|
2860
|
-
return () => {
|
|
2861
|
-
unsubReady();
|
|
2862
|
-
unregisterJob?.();
|
|
2863
|
-
};
|
|
2110
|
+
return scheduler.register(
|
|
2111
|
+
(state, delta) => {
|
|
2112
|
+
const frameState = state;
|
|
2113
|
+
if (!frameState.renderer) return;
|
|
2114
|
+
callbackRef.current?.(frameState, delta);
|
|
2115
|
+
},
|
|
2116
|
+
{ id, ...options }
|
|
2117
|
+
);
|
|
2864
2118
|
}
|
|
2865
2119
|
}, [store, scheduler, id, optionsKey, isLegacyPriority, isInsideCanvas]);
|
|
2866
2120
|
const isPaused = React.useSyncExternalStore(
|
|
@@ -2951,18 +2205,18 @@ function buildFromCache(input, textureCache) {
|
|
|
2951
2205
|
function useTexture(input, optionsOrOnLoad) {
|
|
2952
2206
|
const renderer = useThree((state) => state.internal.actualRenderer);
|
|
2953
2207
|
const store = useStore();
|
|
2954
|
-
const textureCache = useThree((state) => state.textures);
|
|
2955
2208
|
const options = typeof optionsOrOnLoad === "function" ? { onLoad: optionsOrOnLoad } : optionsOrOnLoad ?? {};
|
|
2956
|
-
const { onLoad, cache =
|
|
2209
|
+
const { onLoad, cache = true } = options;
|
|
2957
2210
|
const onLoadRef = useRef(onLoad);
|
|
2958
2211
|
onLoadRef.current = onLoad;
|
|
2959
2212
|
const onLoadCalledForRef = useRef(null);
|
|
2960
2213
|
const urls = useMemo(() => getUrls(input), [input]);
|
|
2961
2214
|
const cachedResult = useMemo(() => {
|
|
2962
2215
|
if (!cache) return null;
|
|
2963
|
-
|
|
2964
|
-
|
|
2965
|
-
|
|
2216
|
+
const textures = store.getState().textures;
|
|
2217
|
+
if (!allUrlsCached(urls, textures)) return null;
|
|
2218
|
+
return buildFromCache(input, textures);
|
|
2219
|
+
}, [cache, urls, input, store]);
|
|
2966
2220
|
const loadedTextures = useLoader(
|
|
2967
2221
|
TextureLoader,
|
|
2968
2222
|
IsObject(input) ? Object.values(input) : input
|
|
@@ -3006,8 +2260,6 @@ function useTexture(input, optionsOrOnLoad) {
|
|
|
3006
2260
|
}, [input, loadedTextures, cachedResult]);
|
|
3007
2261
|
useEffect(() => {
|
|
3008
2262
|
if (!cache) return;
|
|
3009
|
-
if (cachedResult) return;
|
|
3010
|
-
const set = store.setState;
|
|
3011
2263
|
const urlTextureMap = [];
|
|
3012
2264
|
if (typeof input === "string") {
|
|
3013
2265
|
urlTextureMap.push([input, mappedTextures]);
|
|
@@ -3021,18 +2273,32 @@ function useTexture(input, optionsOrOnLoad) {
|
|
|
3021
2273
|
urlTextureMap.push([url, textureRecord[key]]);
|
|
3022
2274
|
}
|
|
3023
2275
|
}
|
|
3024
|
-
|
|
3025
|
-
const
|
|
3026
|
-
let
|
|
2276
|
+
store.setState((state) => {
|
|
2277
|
+
const refs = new Map(state._textureRefs);
|
|
2278
|
+
let textures = state.textures;
|
|
2279
|
+
let added = false;
|
|
3027
2280
|
for (const [url, texture] of urlTextureMap) {
|
|
3028
|
-
if (!
|
|
3029
|
-
|
|
3030
|
-
|
|
2281
|
+
if (!textures.has(url)) {
|
|
2282
|
+
if (!added) {
|
|
2283
|
+
textures = new Map(textures);
|
|
2284
|
+
added = true;
|
|
2285
|
+
}
|
|
2286
|
+
textures.set(url, texture);
|
|
3031
2287
|
}
|
|
2288
|
+
refs.set(url, (refs.get(url) ?? 0) + 1);
|
|
3032
2289
|
}
|
|
3033
|
-
return
|
|
2290
|
+
return added ? { textures, _textureRefs: refs } : { _textureRefs: refs };
|
|
2291
|
+
});
|
|
2292
|
+
return () => store.setState((state) => {
|
|
2293
|
+
const refs = new Map(state._textureRefs);
|
|
2294
|
+
for (const [url] of urlTextureMap) {
|
|
2295
|
+
const next = (refs.get(url) ?? 0) - 1;
|
|
2296
|
+
if (next <= 0) refs.delete(url);
|
|
2297
|
+
else refs.set(url, next);
|
|
2298
|
+
}
|
|
2299
|
+
return { _textureRefs: refs };
|
|
3034
2300
|
});
|
|
3035
|
-
}, [cache, input, mappedTextures, store
|
|
2301
|
+
}, [cache, input, mappedTextures, store]);
|
|
3036
2302
|
return mappedTextures;
|
|
3037
2303
|
}
|
|
3038
2304
|
useTexture.preload = (url) => useLoader.preload(TextureLoader, url);
|
|
@@ -3048,96 +2314,63 @@ const Texture = ({
|
|
|
3048
2314
|
return /* @__PURE__ */ jsx(Fragment, { children: children?.(ret) });
|
|
3049
2315
|
};
|
|
3050
2316
|
|
|
3051
|
-
function
|
|
3052
|
-
if (entry instanceof Texture$1) return entry;
|
|
3053
|
-
if (entry && typeof entry === "object" && "value" in entry && entry.value instanceof Texture$1) {
|
|
3054
|
-
return entry.value;
|
|
3055
|
-
}
|
|
3056
|
-
return null;
|
|
3057
|
-
}
|
|
3058
|
-
function useTextures() {
|
|
2317
|
+
function useTextures(selector) {
|
|
3059
2318
|
const store = useStore();
|
|
3060
|
-
|
|
3061
|
-
const set = store.setState;
|
|
2319
|
+
const registry = useMemo(() => {
|
|
3062
2320
|
const getState = store.getState;
|
|
3063
|
-
const
|
|
3064
|
-
|
|
3065
|
-
const newMap = new Map(state.textures);
|
|
3066
|
-
newMap.set(key, value);
|
|
3067
|
-
return { textures: newMap };
|
|
3068
|
-
});
|
|
3069
|
-
};
|
|
3070
|
-
const addMultiple = (items) => {
|
|
3071
|
-
set((state) => {
|
|
3072
|
-
const newMap = new Map(state.textures);
|
|
3073
|
-
const entries = items instanceof Map ? items.entries() : Object.entries(items);
|
|
3074
|
-
for (const [key, value] of entries) {
|
|
3075
|
-
newMap.set(key, value);
|
|
3076
|
-
}
|
|
3077
|
-
return { textures: newMap };
|
|
3078
|
-
});
|
|
3079
|
-
};
|
|
3080
|
-
const remove = (key) => {
|
|
3081
|
-
set((state) => {
|
|
3082
|
-
const newMap = new Map(state.textures);
|
|
3083
|
-
newMap.delete(key);
|
|
3084
|
-
return { textures: newMap };
|
|
3085
|
-
});
|
|
3086
|
-
};
|
|
3087
|
-
const removeMultiple = (keys) => {
|
|
3088
|
-
set((state) => {
|
|
3089
|
-
const newMap = new Map(state.textures);
|
|
3090
|
-
for (const key of keys) newMap.delete(key);
|
|
3091
|
-
return { textures: newMap };
|
|
3092
|
-
});
|
|
3093
|
-
};
|
|
3094
|
-
const dispose = (key) => {
|
|
3095
|
-
const entry = getState().textures.get(key);
|
|
3096
|
-
if (entry) {
|
|
3097
|
-
const tex = getTextureValue(entry);
|
|
3098
|
-
tex?.dispose();
|
|
3099
|
-
}
|
|
3100
|
-
remove(key);
|
|
3101
|
-
};
|
|
3102
|
-
const disposeMultiple = (keys) => {
|
|
3103
|
-
const textures = getState().textures;
|
|
3104
|
-
for (const key of keys) {
|
|
3105
|
-
const entry = textures.get(key);
|
|
3106
|
-
if (entry) {
|
|
3107
|
-
const tex = getTextureValue(entry);
|
|
3108
|
-
tex?.dispose();
|
|
3109
|
-
}
|
|
3110
|
-
}
|
|
3111
|
-
removeMultiple(keys);
|
|
3112
|
-
};
|
|
3113
|
-
const disposeAll = () => {
|
|
3114
|
-
const textures = getState().textures;
|
|
3115
|
-
for (const entry of textures.values()) {
|
|
3116
|
-
const tex = getTextureValue(entry);
|
|
3117
|
-
tex?.dispose();
|
|
3118
|
-
}
|
|
3119
|
-
set({ textures: /* @__PURE__ */ new Map() });
|
|
3120
|
-
};
|
|
2321
|
+
const setState = store.setState;
|
|
2322
|
+
const getOne = (key) => getState().textures.get(key);
|
|
3121
2323
|
return {
|
|
3122
|
-
|
|
3123
|
-
get textures() {
|
|
2324
|
+
get all() {
|
|
3124
2325
|
return getState().textures;
|
|
3125
2326
|
},
|
|
3126
|
-
|
|
3127
|
-
|
|
2327
|
+
get(input) {
|
|
2328
|
+
if (typeof input === "string") return getOne(input);
|
|
2329
|
+
if (Array.isArray(input)) return input.map(getOne);
|
|
2330
|
+
const out = {};
|
|
2331
|
+
for (const name in input) out[name] = getOne(input[name]);
|
|
2332
|
+
return out;
|
|
2333
|
+
},
|
|
3128
2334
|
has: (key) => getState().textures.has(key),
|
|
3129
|
-
|
|
3130
|
-
|
|
3131
|
-
|
|
3132
|
-
|
|
3133
|
-
|
|
3134
|
-
|
|
3135
|
-
|
|
3136
|
-
|
|
3137
|
-
|
|
3138
|
-
|
|
2335
|
+
add(keyOrRecord, texture) {
|
|
2336
|
+
setState((state) => {
|
|
2337
|
+
const textures = new Map(state.textures);
|
|
2338
|
+
if (typeof keyOrRecord === "string") {
|
|
2339
|
+
textures.set(keyOrRecord, texture);
|
|
2340
|
+
} else {
|
|
2341
|
+
for (const key in keyOrRecord) textures.set(key, keyOrRecord[key]);
|
|
2342
|
+
}
|
|
2343
|
+
return { textures };
|
|
2344
|
+
});
|
|
2345
|
+
},
|
|
2346
|
+
dispose(key, options) {
|
|
2347
|
+
const state = getState();
|
|
2348
|
+
const refs = state._textureRefs.get(key) ?? 0;
|
|
2349
|
+
if (refs > 0 && !options?.force) {
|
|
2350
|
+
console.warn(
|
|
2351
|
+
`[useTextures] "${key}" still has ${refs} active reference(s); skipping dispose. Pass { force: true } to override.`
|
|
2352
|
+
);
|
|
2353
|
+
return false;
|
|
2354
|
+
}
|
|
2355
|
+
state.textures.get(key)?.dispose();
|
|
2356
|
+
setState((s) => {
|
|
2357
|
+
const textures = new Map(s.textures);
|
|
2358
|
+
textures.delete(key);
|
|
2359
|
+
const nextRefs = new Map(s._textureRefs);
|
|
2360
|
+
nextRefs.delete(key);
|
|
2361
|
+
return { textures, _textureRefs: nextRefs };
|
|
2362
|
+
});
|
|
2363
|
+
return true;
|
|
2364
|
+
},
|
|
2365
|
+
disposeAll() {
|
|
2366
|
+
for (const texture of getState().textures.values()) texture.dispose();
|
|
2367
|
+
setState({ textures: /* @__PURE__ */ new Map(), _textureRefs: /* @__PURE__ */ new Map() });
|
|
2368
|
+
}
|
|
3139
2369
|
};
|
|
3140
2370
|
}, [store]);
|
|
2371
|
+
const subscribe = selector ? () => selector(registry) : (state) => state.textures;
|
|
2372
|
+
const selected = useThree(subscribe);
|
|
2373
|
+
return selector ? selected : registry;
|
|
3141
2374
|
}
|
|
3142
2375
|
|
|
3143
2376
|
function useRenderTarget(widthOrOptions, heightOrOptions, options) {
|
|
@@ -3220,7 +2453,7 @@ function advance(timestamp) {
|
|
|
3220
2453
|
getScheduler().step(timestamp);
|
|
3221
2454
|
}
|
|
3222
2455
|
|
|
3223
|
-
const version = "10.0.0-alpha.
|
|
2456
|
+
const version = "10.0.0-alpha.3";
|
|
3224
2457
|
const packageData = {
|
|
3225
2458
|
version: version};
|
|
3226
2459
|
|
|
@@ -14476,7 +13709,7 @@ function n0() {
|
|
|
14476
13709
|
var t0 = n0();
|
|
14477
13710
|
const r0 = Xb(t0);
|
|
14478
13711
|
|
|
14479
|
-
const t = 1, o = 8, r = 32, e = 2;
|
|
13712
|
+
const t = 1, o = 8, r = 32, e = 2, i$1 = 268435456;
|
|
14480
13713
|
|
|
14481
13714
|
function createReconciler(config) {
|
|
14482
13715
|
const reconciler2 = r0(config);
|
|
@@ -14641,7 +13874,15 @@ function setFiberRef(fiber, publicInstance) {
|
|
|
14641
13874
|
}
|
|
14642
13875
|
}
|
|
14643
13876
|
const reconstructed = [];
|
|
14644
|
-
function
|
|
13877
|
+
function flushReconstructedInstances() {
|
|
13878
|
+
if (reconstructed.length === 0) return;
|
|
13879
|
+
try {
|
|
13880
|
+
swapReconstructedInstances();
|
|
13881
|
+
} finally {
|
|
13882
|
+
reconstructed.length = 0;
|
|
13883
|
+
}
|
|
13884
|
+
}
|
|
13885
|
+
function swapReconstructedInstances() {
|
|
14645
13886
|
for (const [instance] of reconstructed) {
|
|
14646
13887
|
const parent = instance.parent;
|
|
14647
13888
|
if (parent) {
|
|
@@ -14659,6 +13900,9 @@ function swapInstances() {
|
|
|
14659
13900
|
}
|
|
14660
13901
|
}
|
|
14661
13902
|
if (instance.isHidden) unhideInstance(instance);
|
|
13903
|
+
if (isObject3D(instance.object) && !instance.parent) {
|
|
13904
|
+
removeInteractivity(findInitialRoot(instance), instance.object);
|
|
13905
|
+
}
|
|
14662
13906
|
if (instance.object.__r3f) delete instance.object.__r3f;
|
|
14663
13907
|
if (instance.type !== "primitive") disposeOnIdle(instance.object);
|
|
14664
13908
|
}
|
|
@@ -14667,10 +13911,16 @@ function swapInstances() {
|
|
|
14667
13911
|
const parent = instance.parent;
|
|
14668
13912
|
if (parent) {
|
|
14669
13913
|
const target = catalogue[toPascalCase(instance.type)];
|
|
13914
|
+
const prevObject = instance.object;
|
|
14670
13915
|
instance.object = instance.props.object ?? new target(...instance.props.args ?? []);
|
|
14671
13916
|
instance.object.__r3f = instance;
|
|
14672
13917
|
setFiberRef(fiber, instance.object);
|
|
13918
|
+
if (isObject3D(prevObject) && isObject3D(instance.object)) {
|
|
13919
|
+
swapInteractivity(findInitialRoot(instance), prevObject, instance.object);
|
|
13920
|
+
}
|
|
14673
13921
|
delete instance.appliedOnce;
|
|
13922
|
+
instance.eventCount = 0;
|
|
13923
|
+
instance.handlers = {};
|
|
14674
13924
|
applyProps(instance.object, instance.props);
|
|
14675
13925
|
if (instance.props.attach) {
|
|
14676
13926
|
attach(parent, instance);
|
|
@@ -14687,14 +13937,114 @@ function swapInstances() {
|
|
|
14687
13937
|
invalidateInstance(instance);
|
|
14688
13938
|
}
|
|
14689
13939
|
}
|
|
14690
|
-
reconstructed.length = 0;
|
|
14691
13940
|
}
|
|
14692
13941
|
const handleTextInstance = () => {
|
|
14693
13942
|
};
|
|
14694
13943
|
const NO_CONTEXT = {};
|
|
14695
13944
|
let currentUpdatePriority = NoEventPriority;
|
|
14696
|
-
|
|
14697
|
-
|
|
13945
|
+
function getEventPriority(type) {
|
|
13946
|
+
switch (type) {
|
|
13947
|
+
case "beforetoggle":
|
|
13948
|
+
case "cancel":
|
|
13949
|
+
case "click":
|
|
13950
|
+
case "close":
|
|
13951
|
+
case "contextmenu":
|
|
13952
|
+
case "copy":
|
|
13953
|
+
case "cut":
|
|
13954
|
+
case "auxclick":
|
|
13955
|
+
case "dblclick":
|
|
13956
|
+
case "dragend":
|
|
13957
|
+
case "dragstart":
|
|
13958
|
+
case "drop":
|
|
13959
|
+
case "focusin":
|
|
13960
|
+
case "focusout":
|
|
13961
|
+
case "input":
|
|
13962
|
+
case "invalid":
|
|
13963
|
+
case "keydown":
|
|
13964
|
+
case "keypress":
|
|
13965
|
+
case "keyup":
|
|
13966
|
+
case "mousedown":
|
|
13967
|
+
case "mouseup":
|
|
13968
|
+
case "paste":
|
|
13969
|
+
case "pause":
|
|
13970
|
+
case "play":
|
|
13971
|
+
case "pointercancel":
|
|
13972
|
+
case "pointerdown":
|
|
13973
|
+
case "pointerup":
|
|
13974
|
+
case "ratechange":
|
|
13975
|
+
case "reset":
|
|
13976
|
+
case "resize":
|
|
13977
|
+
case "seeked":
|
|
13978
|
+
case "submit":
|
|
13979
|
+
case "touchcancel":
|
|
13980
|
+
case "touchend":
|
|
13981
|
+
case "touchstart":
|
|
13982
|
+
case "volumechange":
|
|
13983
|
+
case "change":
|
|
13984
|
+
case "selectionchange":
|
|
13985
|
+
case "compositionstart":
|
|
13986
|
+
case "compositionend":
|
|
13987
|
+
case "compositionupdate":
|
|
13988
|
+
case "beforeinput":
|
|
13989
|
+
case "blur":
|
|
13990
|
+
case "fullscreenchange":
|
|
13991
|
+
case "focus":
|
|
13992
|
+
case "hashchange":
|
|
13993
|
+
case "popstate":
|
|
13994
|
+
case "select":
|
|
13995
|
+
case "selectstart":
|
|
13996
|
+
return e;
|
|
13997
|
+
case "drag":
|
|
13998
|
+
case "dragenter":
|
|
13999
|
+
case "dragexit":
|
|
14000
|
+
case "dragleave":
|
|
14001
|
+
case "dragover":
|
|
14002
|
+
case "mousemove":
|
|
14003
|
+
case "mouseout":
|
|
14004
|
+
case "mouseover":
|
|
14005
|
+
case "pointermove":
|
|
14006
|
+
case "pointerout":
|
|
14007
|
+
case "pointerover":
|
|
14008
|
+
case "scroll":
|
|
14009
|
+
case "touchmove":
|
|
14010
|
+
case "wheel":
|
|
14011
|
+
case "mouseenter":
|
|
14012
|
+
case "mouseleave":
|
|
14013
|
+
case "pointerenter":
|
|
14014
|
+
case "pointerleave":
|
|
14015
|
+
return o;
|
|
14016
|
+
case "message": {
|
|
14017
|
+
switch (unstable_getCurrentPriorityLevel()) {
|
|
14018
|
+
case unstable_ImmediatePriority:
|
|
14019
|
+
return e;
|
|
14020
|
+
case unstable_UserBlockingPriority:
|
|
14021
|
+
return o;
|
|
14022
|
+
case unstable_NormalPriority:
|
|
14023
|
+
case unstable_LowPriority:
|
|
14024
|
+
return r;
|
|
14025
|
+
case unstable_IdlePriority:
|
|
14026
|
+
return i$1;
|
|
14027
|
+
default:
|
|
14028
|
+
return r;
|
|
14029
|
+
}
|
|
14030
|
+
}
|
|
14031
|
+
default:
|
|
14032
|
+
return r;
|
|
14033
|
+
}
|
|
14034
|
+
}
|
|
14035
|
+
function scheduleMicrotask(callback) {
|
|
14036
|
+
if (typeof queueMicrotask === "function") {
|
|
14037
|
+
queueMicrotask(callback);
|
|
14038
|
+
} else if (typeof Promise !== "undefined") {
|
|
14039
|
+
Promise.resolve().then(callback).catch((error) => {
|
|
14040
|
+
setTimeout(() => {
|
|
14041
|
+
throw error;
|
|
14042
|
+
});
|
|
14043
|
+
});
|
|
14044
|
+
} else {
|
|
14045
|
+
setTimeout(callback);
|
|
14046
|
+
}
|
|
14047
|
+
}
|
|
14698
14048
|
const reconciler = /* @__PURE__ */ createReconciler({
|
|
14699
14049
|
isPrimaryRenderer: false,
|
|
14700
14050
|
warnsIfNotActing: false,
|
|
@@ -14733,16 +14083,15 @@ const reconciler = /* @__PURE__ */ createReconciler({
|
|
|
14733
14083
|
else if (newProps.args?.length !== oldProps.args?.length) reconstruct = true;
|
|
14734
14084
|
else if (newProps.args?.some((value, index) => value !== oldProps.args?.[index])) reconstruct = true;
|
|
14735
14085
|
if (reconstruct) {
|
|
14736
|
-
reconstructed.push([instance,
|
|
14086
|
+
reconstructed.push([instance, getInstanceProps(newProps), fiber]);
|
|
14737
14087
|
} else {
|
|
14738
14088
|
const changedProps = diffProps(instance, newProps);
|
|
14739
|
-
|
|
14740
|
-
|
|
14741
|
-
|
|
14742
|
-
|
|
14089
|
+
const attach2 = instance.props.attach;
|
|
14090
|
+
instance.props = getInstanceProps(newProps);
|
|
14091
|
+
if (attach2 !== void 0) instance.props.attach = attach2;
|
|
14092
|
+
else delete instance.props.attach;
|
|
14093
|
+
if (Object.keys(changedProps).length) applyProps(instance.object, changedProps);
|
|
14743
14094
|
}
|
|
14744
|
-
const isTailSibling = fiber.sibling === null || (fiber.flags & Update) === NoFlags;
|
|
14745
|
-
if (isTailSibling) swapInstances();
|
|
14746
14095
|
},
|
|
14747
14096
|
finalizeInitialChildren: (instance) => {
|
|
14748
14097
|
for (const prop in instance.props) {
|
|
@@ -14767,8 +14116,9 @@ const reconciler = /* @__PURE__ */ createReconciler({
|
|
|
14767
14116
|
const target = container.getState().internal.container ?? container.getState().scene;
|
|
14768
14117
|
return prepare(target, container, "", {});
|
|
14769
14118
|
},
|
|
14770
|
-
|
|
14771
|
-
|
|
14119
|
+
// Reconstructed instances are swapped once all mutations are committed,
|
|
14120
|
+
// before layout effects run so refs point to the new objects
|
|
14121
|
+
resetAfterCommit: flushReconstructedInstances,
|
|
14772
14122
|
shouldSetTextContent: () => false,
|
|
14773
14123
|
clearContainer: () => false,
|
|
14774
14124
|
hideInstance,
|
|
@@ -14776,6 +14126,9 @@ const reconciler = /* @__PURE__ */ createReconciler({
|
|
|
14776
14126
|
createTextInstance: handleTextInstance,
|
|
14777
14127
|
hideTextInstance: handleTextInstance,
|
|
14778
14128
|
unhideTextInstance: handleTextInstance,
|
|
14129
|
+
// Mirrors react-dom, which flushes discrete work in microtasks
|
|
14130
|
+
supportsMicrotasks: true,
|
|
14131
|
+
scheduleMicrotask,
|
|
14779
14132
|
scheduleTimeout: typeof setTimeout === "function" ? setTimeout : void 0,
|
|
14780
14133
|
cancelTimeout: typeof clearTimeout === "function" ? clearTimeout : void 0,
|
|
14781
14134
|
noTimeout: -1,
|
|
@@ -14816,28 +14169,9 @@ const reconciler = /* @__PURE__ */ createReconciler({
|
|
|
14816
14169
|
},
|
|
14817
14170
|
resolveUpdatePriority() {
|
|
14818
14171
|
if (currentUpdatePriority !== NoEventPriority) return currentUpdatePriority;
|
|
14819
|
-
|
|
14820
|
-
|
|
14821
|
-
|
|
14822
|
-
case "dblclick":
|
|
14823
|
-
case "dragenter":
|
|
14824
|
-
case "dragleave":
|
|
14825
|
-
case "drop":
|
|
14826
|
-
case "pointercancel":
|
|
14827
|
-
case "pointerdown":
|
|
14828
|
-
case "pointerup":
|
|
14829
|
-
return e;
|
|
14830
|
-
case "dragover":
|
|
14831
|
-
case "pointermove":
|
|
14832
|
-
case "pointerout":
|
|
14833
|
-
case "pointerover":
|
|
14834
|
-
case "pointerenter":
|
|
14835
|
-
case "pointerleave":
|
|
14836
|
-
case "wheel":
|
|
14837
|
-
return o;
|
|
14838
|
-
default:
|
|
14839
|
-
return r;
|
|
14840
|
-
}
|
|
14172
|
+
const eventType = typeof window !== "undefined" ? window.event?.type : void 0;
|
|
14173
|
+
if (eventType === void 0) return r;
|
|
14174
|
+
return getEventPriority(eventType);
|
|
14841
14175
|
},
|
|
14842
14176
|
resetFormInstance() {
|
|
14843
14177
|
},
|
|
@@ -14886,11 +14220,15 @@ const reconciler = /* @__PURE__ */ createReconciler({
|
|
|
14886
14220
|
cloneMutableTextInstance(textInstance) {
|
|
14887
14221
|
return textInstance;
|
|
14888
14222
|
},
|
|
14223
|
+
// React 19.2 added these for root-level view transitions. They only run for hosts that opt into
|
|
14224
|
+
// view transitions by returning a container from cloneRootViewTransitionContainer; R3F renders
|
|
14225
|
+
// into a WebGL/WebGPU canvas, where cloning the root has no meaning, so we never opt in and
|
|
14226
|
+
// React never calls them. They stay as no-ops rather than throws: the host config is shared by
|
|
14227
|
+
// every root, and a throw here would turn a React-internal probe into a hard crash for users.
|
|
14228
|
+
// Guarded by a test asserting a full render + unmount never reaches them.
|
|
14889
14229
|
cloneRootViewTransitionContainer(_rootContainer) {
|
|
14890
|
-
throw new Error("Not implemented.");
|
|
14891
14230
|
},
|
|
14892
14231
|
removeRootViewTransitionClone(_rootContainer, _clone) {
|
|
14893
|
-
throw new Error("Not implemented.");
|
|
14894
14232
|
},
|
|
14895
14233
|
// https://github.com/facebook/react/pull/32465
|
|
14896
14234
|
createFragmentInstance: (_fiber) => null,
|
|
@@ -14975,6 +14313,7 @@ function createRoot(canvas) {
|
|
|
14975
14313
|
const lastConfiguredProps = {};
|
|
14976
14314
|
let configured = false;
|
|
14977
14315
|
let pending = null;
|
|
14316
|
+
let rendererSetup = null;
|
|
14978
14317
|
return {
|
|
14979
14318
|
async configure(props = {}) {
|
|
14980
14319
|
let resolve;
|
|
@@ -14990,7 +14329,6 @@ function createRoot(canvas) {
|
|
|
14990
14329
|
events,
|
|
14991
14330
|
onCreated: onCreatedCallback,
|
|
14992
14331
|
shadows = false,
|
|
14993
|
-
textureColorSpace = SRGBColorSpace,
|
|
14994
14332
|
orthographic = false,
|
|
14995
14333
|
frameloop = "always",
|
|
14996
14334
|
dpr = [1, 2],
|
|
@@ -15005,7 +14343,8 @@ function createRoot(canvas) {
|
|
|
15005
14343
|
_sizeProps,
|
|
15006
14344
|
forceEven
|
|
15007
14345
|
} = props;
|
|
15008
|
-
const
|
|
14346
|
+
const textureColorSpace = is.obj(glConfig) && !is.fun(glConfig) && !isRenderer(glConfig) && glConfig.textureColorSpace || is.obj(rendererConfig) && !is.fun(rendererConfig) && !isRenderer(rendererConfig) && rendererConfig.textureColorSpace || SRGBColorSpace;
|
|
14347
|
+
let state = store.getState();
|
|
15009
14348
|
const defaultGLProps = {
|
|
15010
14349
|
canvas,
|
|
15011
14350
|
powerPreference: "high-performance",
|
|
@@ -15033,50 +14372,68 @@ function createRoot(canvas) {
|
|
|
15033
14372
|
"The `primaryCanvas` prop for multi-canvas rendering cannot be used with WebGL. Remove the `gl` prop or use WebGPU."
|
|
15034
14373
|
);
|
|
15035
14374
|
}
|
|
15036
|
-
if (
|
|
15037
|
-
|
|
15038
|
-
|
|
15039
|
-
|
|
15040
|
-
|
|
15041
|
-
|
|
15042
|
-
|
|
15043
|
-
|
|
15044
|
-
|
|
15045
|
-
|
|
15046
|
-
|
|
15047
|
-
|
|
15048
|
-
|
|
15049
|
-
|
|
15050
|
-
|
|
15051
|
-
|
|
15052
|
-
|
|
15053
|
-
|
|
15054
|
-
|
|
15055
|
-
|
|
15056
|
-
|
|
15057
|
-
|
|
15058
|
-
|
|
15059
|
-
|
|
15060
|
-
|
|
15061
|
-
|
|
15062
|
-
|
|
15063
|
-
|
|
15064
|
-
|
|
15065
|
-
|
|
15066
|
-
|
|
15067
|
-
|
|
15068
|
-
|
|
15069
|
-
|
|
15070
|
-
|
|
15071
|
-
|
|
15072
|
-
|
|
15073
|
-
|
|
15074
|
-
|
|
15075
|
-
|
|
15076
|
-
|
|
14375
|
+
if (!state.internal.actualRenderer) {
|
|
14376
|
+
if (!rendererSetup) {
|
|
14377
|
+
rendererSetup = (async () => {
|
|
14378
|
+
if (wantsGL) {
|
|
14379
|
+
renderer = await resolveRenderer(glConfig, defaultGLProps, WebGLRenderer);
|
|
14380
|
+
state.internal.actualRenderer = renderer;
|
|
14381
|
+
state.set({ isLegacy: true, gl: renderer, renderer, primaryStore: store });
|
|
14382
|
+
} else if (!wantsGL && primaryCanvas) {
|
|
14383
|
+
const primary = await waitForPrimary(primaryCanvas);
|
|
14384
|
+
renderer = primary.renderer;
|
|
14385
|
+
state.internal.actualRenderer = renderer;
|
|
14386
|
+
const canvasTarget = new CanvasTarget(canvas);
|
|
14387
|
+
primary.store.setState((prev) => ({
|
|
14388
|
+
internal: { ...prev.internal, isMultiCanvas: true }
|
|
14389
|
+
}));
|
|
14390
|
+
state.set((prev) => ({
|
|
14391
|
+
webGPUSupported: primary.store.getState().webGPUSupported,
|
|
14392
|
+
renderer,
|
|
14393
|
+
primaryStore: primary.store,
|
|
14394
|
+
internal: {
|
|
14395
|
+
...prev.internal,
|
|
14396
|
+
canvasTarget,
|
|
14397
|
+
isMultiCanvas: true,
|
|
14398
|
+
isSecondary: true,
|
|
14399
|
+
targetId: primaryCanvas
|
|
14400
|
+
}
|
|
14401
|
+
}));
|
|
14402
|
+
} else if (!wantsGL) {
|
|
14403
|
+
renderer = await resolveRenderer(rendererConfig, defaultGPUProps, WebGPURenderer);
|
|
14404
|
+
if (!renderer.hasInitialized?.()) {
|
|
14405
|
+
const size2 = computeInitialSize(canvas, propsSize);
|
|
14406
|
+
if (size2.width > 0 && size2.height > 0) {
|
|
14407
|
+
const pixelRatio = calculateDpr(dpr);
|
|
14408
|
+
canvas.width = size2.width * pixelRatio;
|
|
14409
|
+
canvas.height = size2.height * pixelRatio;
|
|
14410
|
+
}
|
|
14411
|
+
await renderer.init();
|
|
14412
|
+
}
|
|
14413
|
+
const backend = renderer.backend;
|
|
14414
|
+
const isWebGPUBackend = backend && "isWebGPUBackend" in backend;
|
|
14415
|
+
state.internal.actualRenderer = renderer;
|
|
14416
|
+
state.set({ webGPUSupported: isWebGPUBackend, renderer, primaryStore: store });
|
|
14417
|
+
if (canvasId && !state.internal.isSecondary) {
|
|
14418
|
+
const canvasTarget = new CanvasTarget(canvas);
|
|
14419
|
+
const unregisterPrimary = registerPrimary(canvasId, renderer, store);
|
|
14420
|
+
state.set((prev) => ({
|
|
14421
|
+
internal: {
|
|
14422
|
+
...prev.internal,
|
|
14423
|
+
canvasTarget,
|
|
14424
|
+
unregisterPrimary
|
|
14425
|
+
}
|
|
14426
|
+
}));
|
|
14427
|
+
}
|
|
15077
14428
|
}
|
|
15078
|
-
}))
|
|
14429
|
+
})().catch((err) => {
|
|
14430
|
+
rendererSetup = null;
|
|
14431
|
+
throw err;
|
|
14432
|
+
});
|
|
15079
14433
|
}
|
|
14434
|
+
await rendererSetup;
|
|
14435
|
+
state = store.getState();
|
|
14436
|
+
renderer = state.internal.actualRenderer;
|
|
15080
14437
|
}
|
|
15081
14438
|
let raycaster = state.raycaster;
|
|
15082
14439
|
if (!raycaster) state.set({ raycaster: raycaster = new Raycaster() });
|
|
@@ -15169,7 +14526,7 @@ function createRoot(canvas) {
|
|
|
15169
14526
|
lastConfiguredProps.performance = performance;
|
|
15170
14527
|
}
|
|
15171
14528
|
if (!state.xr) {
|
|
15172
|
-
const handleXRFrame = (timestamp,
|
|
14529
|
+
const handleXRFrame = (timestamp, _frame) => {
|
|
15173
14530
|
const state2 = store.getState();
|
|
15174
14531
|
if (state2.frameloop === "never") return;
|
|
15175
14532
|
advance(timestamp);
|
|
@@ -15205,15 +14562,22 @@ function createRoot(canvas) {
|
|
|
15205
14562
|
const oldType = renderer.shadowMap.type;
|
|
15206
14563
|
renderer.shadowMap.enabled = !!shadows;
|
|
15207
14564
|
if (is.boo(shadows)) {
|
|
15208
|
-
renderer.shadowMap.type =
|
|
14565
|
+
renderer.shadowMap.type = PCFShadowMap;
|
|
15209
14566
|
} else if (is.str(shadows)) {
|
|
14567
|
+
if (shadows === "soft") {
|
|
14568
|
+
notifyDepreciated({
|
|
14569
|
+
heading: 'shadows="soft" is deprecated',
|
|
14570
|
+
body: "Three has depreciated soft and improved basic PCFShadows, we converted for you.",
|
|
14571
|
+
link: "https://github.com/mrdoob/three.js/wiki/Migration-Guide?utm_source=chatgpt.com#181--182"
|
|
14572
|
+
});
|
|
14573
|
+
}
|
|
15210
14574
|
const types = {
|
|
15211
14575
|
basic: BasicShadowMap,
|
|
15212
14576
|
percentage: PCFShadowMap,
|
|
15213
|
-
soft:
|
|
14577
|
+
soft: PCFShadowMap,
|
|
15214
14578
|
variance: VSMShadowMap
|
|
15215
14579
|
};
|
|
15216
|
-
renderer.shadowMap.type = types[shadows] ??
|
|
14580
|
+
renderer.shadowMap.type = types[shadows] ?? PCFShadowMap;
|
|
15217
14581
|
} else if (is.obj(shadows)) {
|
|
15218
14582
|
Object.assign(renderer.shadowMap, shadows);
|
|
15219
14583
|
}
|
|
@@ -15229,13 +14593,24 @@ function createRoot(canvas) {
|
|
|
15229
14593
|
if (state.textureColorSpace !== textureColorSpace) state.set(() => ({ textureColorSpace }));
|
|
15230
14594
|
lastConfiguredProps.textureColorSpace = textureColorSpace;
|
|
15231
14595
|
}
|
|
14596
|
+
const r3fProps = ["textureColorSpace"];
|
|
14597
|
+
const constructorOnlyProps = ["samples", "antialias", "alpha", "canvas", "powerPreference"];
|
|
14598
|
+
const nonApplyProps = [...r3fProps, ...constructorOnlyProps];
|
|
15232
14599
|
if (glConfig && !is.fun(glConfig) && !isRenderer(glConfig) && !is.equ(glConfig, renderer, shallowLoose)) {
|
|
15233
|
-
|
|
14600
|
+
const glProps = {};
|
|
14601
|
+
for (const key in glConfig) {
|
|
14602
|
+
if (!nonApplyProps.includes(key)) glProps[key] = glConfig[key];
|
|
14603
|
+
}
|
|
14604
|
+
applyProps(renderer, glProps);
|
|
15234
14605
|
}
|
|
15235
14606
|
if (rendererConfig && !is.fun(rendererConfig) && !isRenderer(rendererConfig) && state.renderer) {
|
|
15236
14607
|
const currentRenderer = state.renderer;
|
|
15237
14608
|
if (!is.equ(rendererConfig, currentRenderer, shallowLoose)) {
|
|
15238
|
-
|
|
14609
|
+
const rendererProps = {};
|
|
14610
|
+
for (const key in rendererConfig) {
|
|
14611
|
+
if (!nonApplyProps.includes(key)) rendererProps[key] = rendererConfig[key];
|
|
14612
|
+
}
|
|
14613
|
+
applyProps(currentRenderer, rendererProps);
|
|
15239
14614
|
}
|
|
15240
14615
|
}
|
|
15241
14616
|
const scheduler = getScheduler();
|
|
@@ -15261,6 +14636,18 @@ function createRoot(canvas) {
|
|
|
15261
14636
|
system: true
|
|
15262
14637
|
}
|
|
15263
14638
|
);
|
|
14639
|
+
const unregisterEventsFlush = scheduler.register(
|
|
14640
|
+
() => {
|
|
14641
|
+
const state2 = store.getState();
|
|
14642
|
+
state2.events.flush?.();
|
|
14643
|
+
},
|
|
14644
|
+
{
|
|
14645
|
+
id: `${newRootId}_events`,
|
|
14646
|
+
rootId: newRootId,
|
|
14647
|
+
phase: "input",
|
|
14648
|
+
system: true
|
|
14649
|
+
}
|
|
14650
|
+
);
|
|
15264
14651
|
const unregisterFrustum = scheduler.register(
|
|
15265
14652
|
() => {
|
|
15266
14653
|
const state2 = store.getState();
|
|
@@ -15271,7 +14658,7 @@ function createRoot(canvas) {
|
|
|
15271
14658
|
{
|
|
15272
14659
|
id: `${newRootId}_frustum`,
|
|
15273
14660
|
rootId: newRootId,
|
|
15274
|
-
|
|
14661
|
+
before: "render",
|
|
15275
14662
|
system: true
|
|
15276
14663
|
}
|
|
15277
14664
|
);
|
|
@@ -15283,7 +14670,7 @@ function createRoot(canvas) {
|
|
|
15283
14670
|
{
|
|
15284
14671
|
id: `${newRootId}_visibility`,
|
|
15285
14672
|
rootId: newRootId,
|
|
15286
|
-
|
|
14673
|
+
before: "render",
|
|
15287
14674
|
system: true,
|
|
15288
14675
|
after: `${newRootId}_frustum`
|
|
15289
14676
|
}
|
|
@@ -15295,7 +14682,7 @@ function createRoot(canvas) {
|
|
|
15295
14682
|
const userHandlesRender = scheduler.hasUserJobsInPhase("render", newRootId);
|
|
15296
14683
|
if (userHandlesRender || state2.internal.priority) return;
|
|
15297
14684
|
try {
|
|
15298
|
-
if (state2.
|
|
14685
|
+
if (state2.renderPipeline?.render) state2.renderPipeline.render();
|
|
15299
14686
|
else if (renderer2?.render) renderer2.render(state2.scene, state2.camera);
|
|
15300
14687
|
} catch (error) {
|
|
15301
14688
|
state2.setError(error instanceof Error ? error : new Error(String(error)));
|
|
@@ -15320,6 +14707,7 @@ function createRoot(canvas) {
|
|
|
15320
14707
|
unregisterRoot: () => {
|
|
15321
14708
|
unregisterRoot();
|
|
15322
14709
|
unregisterCanvasTarget();
|
|
14710
|
+
unregisterEventsFlush();
|
|
15323
14711
|
unregisterFrustum();
|
|
15324
14712
|
unregisterVisibility();
|
|
15325
14713
|
unregisterRender();
|
|
@@ -15395,7 +14783,8 @@ function unmountComponentAtNode(canvas, callback) {
|
|
|
15395
14783
|
dispose(state.scene);
|
|
15396
14784
|
_roots.delete(canvas);
|
|
15397
14785
|
if (callback) callback(canvas);
|
|
15398
|
-
} catch {
|
|
14786
|
+
} catch (error) {
|
|
14787
|
+
console.warn("[R3F] Error while unmounting root; teardown may be incomplete:", error);
|
|
15399
14788
|
}
|
|
15400
14789
|
}, 500);
|
|
15401
14790
|
}
|
|
@@ -15458,6 +14847,7 @@ function PortalInner({ state = {}, children, container }) {
|
|
|
15458
14847
|
viewport = rootState.viewport.getCurrentViewport(camera, new Vector3(), resolvedSize);
|
|
15459
14848
|
if (camera !== rootState.camera) updateCamera(camera, resolvedSize);
|
|
15460
14849
|
}
|
|
14850
|
+
const set = injectState.set;
|
|
15461
14851
|
return {
|
|
15462
14852
|
// The intersect consists of the previous root state
|
|
15463
14853
|
...rootState,
|
|
@@ -15476,7 +14866,7 @@ function PortalInner({ state = {}, children, container }) {
|
|
|
15476
14866
|
size: resolvedSize,
|
|
15477
14867
|
viewport: { ...rootState.viewport, ...viewport },
|
|
15478
14868
|
// Layers are allowed to override events
|
|
15479
|
-
setEvents: (events2) =>
|
|
14869
|
+
setEvents: (events2) => set((state2) => ({ ...state2, events: { ...state2.events, ...events2 } })),
|
|
15480
14870
|
// Container for child attachment - the portalScene (injected or container itself)
|
|
15481
14871
|
internal: { ...rootState.internal, ...injectState.internal, container: portalScene }
|
|
15482
14872
|
};
|
|
@@ -15485,9 +14875,13 @@ function PortalInner({ state = {}, children, container }) {
|
|
|
15485
14875
|
const store = createWithEqualityFn((set, get) => ({ ...rest, set, get }));
|
|
15486
14876
|
const onMutate = (prev) => store.setState((state2) => inject.current(prev, state2));
|
|
15487
14877
|
onMutate(previousRoot.getState());
|
|
15488
|
-
previousRoot.subscribe(onMutate);
|
|
15489
14878
|
return store;
|
|
15490
14879
|
}, [previousRoot, container]);
|
|
14880
|
+
useIsomorphicLayoutEffect(() => {
|
|
14881
|
+
const onMutate = (prev) => usePortalStore.setState((state2) => inject.current(prev, state2));
|
|
14882
|
+
const unsubscribe = previousRoot.subscribe(onMutate);
|
|
14883
|
+
return unsubscribe;
|
|
14884
|
+
}, [previousRoot, usePortalStore]);
|
|
15491
14885
|
return (
|
|
15492
14886
|
// @ts-ignore, reconciler types are not maintained
|
|
15493
14887
|
/* @__PURE__ */ jsx(Fragment, { children: reconciler.createPortal(
|
|
@@ -15501,6 +14895,81 @@ function flushSync(fn) {
|
|
|
15501
14895
|
return reconciler.flushSyncFromReconciler(fn);
|
|
15502
14896
|
}
|
|
15503
14897
|
|
|
14898
|
+
function parseBackground(background) {
|
|
14899
|
+
if (!background) return null;
|
|
14900
|
+
if (typeof background === "object" && !background.isColor) {
|
|
14901
|
+
const { backgroundMap, envMap, files, preset, ...rest } = background;
|
|
14902
|
+
return {
|
|
14903
|
+
...rest,
|
|
14904
|
+
preset,
|
|
14905
|
+
files: envMap || files,
|
|
14906
|
+
backgroundFiles: backgroundMap,
|
|
14907
|
+
background: true
|
|
14908
|
+
};
|
|
14909
|
+
}
|
|
14910
|
+
if (typeof background === "number") {
|
|
14911
|
+
return { color: background, background: true };
|
|
14912
|
+
}
|
|
14913
|
+
if (typeof background === "string") {
|
|
14914
|
+
if (background in presetsObj) {
|
|
14915
|
+
return { preset: background, background: true };
|
|
14916
|
+
}
|
|
14917
|
+
if (/^(https?:\/\/|\/|\.\/|\.\.\/)|\.(hdr|exr|jpg|jpeg|png|webp|gif)$/i.test(background)) {
|
|
14918
|
+
return { files: background, background: true };
|
|
14919
|
+
}
|
|
14920
|
+
return { color: background, background: true };
|
|
14921
|
+
}
|
|
14922
|
+
if (background.isColor) {
|
|
14923
|
+
return { color: background, background: true };
|
|
14924
|
+
}
|
|
14925
|
+
return null;
|
|
14926
|
+
}
|
|
14927
|
+
|
|
14928
|
+
function parseRendererConfig(rendererProp) {
|
|
14929
|
+
const isRendererConfig = typeof rendererProp === "object" && rendererProp !== null && !("render" in rendererProp) && ("primaryCanvas" in rendererProp || "scheduler" in rendererProp);
|
|
14930
|
+
if (isRendererConfig) {
|
|
14931
|
+
const { primaryCanvas: pc, scheduler: sc, ...rest } = rendererProp;
|
|
14932
|
+
return {
|
|
14933
|
+
primaryCanvas: pc,
|
|
14934
|
+
scheduler: sc,
|
|
14935
|
+
renderer: Object.keys(rest).length > 0 ? rest : rendererProp
|
|
14936
|
+
};
|
|
14937
|
+
}
|
|
14938
|
+
return { primaryCanvas: void 0, scheduler: void 0, renderer: rendererProp };
|
|
14939
|
+
}
|
|
14940
|
+
|
|
14941
|
+
const RESOURCE_KINDS = ["nodes", "uniforms", "buffers", "gpuStorage"];
|
|
14942
|
+
const registries = /* @__PURE__ */ new WeakMap();
|
|
14943
|
+
function getKindRegistry(store, kind) {
|
|
14944
|
+
let registry = registries.get(store);
|
|
14945
|
+
if (!registry) {
|
|
14946
|
+
registry = {
|
|
14947
|
+
nodes: { valid: /* @__PURE__ */ new Set(), staged: /* @__PURE__ */ new Map() },
|
|
14948
|
+
uniforms: { valid: /* @__PURE__ */ new Set(), staged: /* @__PURE__ */ new Map() },
|
|
14949
|
+
buffers: { valid: /* @__PURE__ */ new Set(), staged: /* @__PURE__ */ new Map() },
|
|
14950
|
+
gpuStorage: { valid: /* @__PURE__ */ new Set(), staged: /* @__PURE__ */ new Map() }
|
|
14951
|
+
};
|
|
14952
|
+
registries.set(store, registry);
|
|
14953
|
+
}
|
|
14954
|
+
return registry[kind];
|
|
14955
|
+
}
|
|
14956
|
+
function invalidateResource(store, kind, scopeKey) {
|
|
14957
|
+
const registry = getKindRegistry(store, kind);
|
|
14958
|
+
{
|
|
14959
|
+
registry.valid.clear();
|
|
14960
|
+
registry.staged.clear();
|
|
14961
|
+
}
|
|
14962
|
+
}
|
|
14963
|
+
function invalidateAllResources(store) {
|
|
14964
|
+
for (const kind of RESOURCE_KINDS) invalidateResource(store, kind);
|
|
14965
|
+
}
|
|
14966
|
+
|
|
14967
|
+
function clearHmrCaches(store) {
|
|
14968
|
+
const primary = store.getState().primaryStore ?? store;
|
|
14969
|
+
invalidateAllResources(primary);
|
|
14970
|
+
primary.setState((state) => ({ _hmrVersion: state._hmrVersion + 1 }));
|
|
14971
|
+
}
|
|
14972
|
+
|
|
15504
14973
|
function CanvasImpl({
|
|
15505
14974
|
ref,
|
|
15506
14975
|
children,
|
|
@@ -15521,6 +14990,8 @@ function CanvasImpl({
|
|
|
15521
14990
|
raycaster,
|
|
15522
14991
|
camera,
|
|
15523
14992
|
scene,
|
|
14993
|
+
autoUpdateFrustum,
|
|
14994
|
+
occlusion,
|
|
15524
14995
|
onPointerMissed,
|
|
15525
14996
|
onDragOverMissed,
|
|
15526
14997
|
onDropMissed,
|
|
@@ -15532,39 +15003,10 @@ function CanvasImpl({
|
|
|
15532
15003
|
forceEven,
|
|
15533
15004
|
...props
|
|
15534
15005
|
}) {
|
|
15535
|
-
const { primaryCanvas, scheduler,
|
|
15536
|
-
const renderer = Object.keys(rendererConfig).length > 0 ? rendererConfig : rendererProp;
|
|
15006
|
+
const { primaryCanvas, scheduler, renderer } = parseRendererConfig(rendererProp);
|
|
15537
15007
|
React.useMemo(() => extend(THREE), []);
|
|
15538
15008
|
const Bridge = useBridge();
|
|
15539
|
-
const backgroundProps = React.useMemo(() =>
|
|
15540
|
-
if (!background) return null;
|
|
15541
|
-
if (typeof background === "object" && !background.isColor) {
|
|
15542
|
-
const { backgroundMap, envMap, files, preset, ...rest } = background;
|
|
15543
|
-
return {
|
|
15544
|
-
...rest,
|
|
15545
|
-
preset,
|
|
15546
|
-
files: envMap || files,
|
|
15547
|
-
backgroundFiles: backgroundMap,
|
|
15548
|
-
background: true
|
|
15549
|
-
};
|
|
15550
|
-
}
|
|
15551
|
-
if (typeof background === "number") {
|
|
15552
|
-
return { color: background, background: true };
|
|
15553
|
-
}
|
|
15554
|
-
if (typeof background === "string") {
|
|
15555
|
-
if (background in presetsObj) {
|
|
15556
|
-
return { preset: background, background: true };
|
|
15557
|
-
}
|
|
15558
|
-
if (/^(https?:\/\/|\/|\.\/|\.\.\/)|\\.(hdr|exr|jpg|jpeg|png|webp|gif)$/i.test(background)) {
|
|
15559
|
-
return { files: background, background: true };
|
|
15560
|
-
}
|
|
15561
|
-
return { color: background, background: true };
|
|
15562
|
-
}
|
|
15563
|
-
if (background.isColor) {
|
|
15564
|
-
return { color: background, background: true };
|
|
15565
|
-
}
|
|
15566
|
-
return null;
|
|
15567
|
-
}, [background]);
|
|
15009
|
+
const backgroundProps = React.useMemo(() => parseBackground(background), [background]);
|
|
15568
15010
|
const hasInitialSizeRef = React.useRef(false);
|
|
15569
15011
|
const measureConfig = React.useMemo(() => {
|
|
15570
15012
|
if (!hasInitialSizeRef.current) {
|
|
@@ -15606,6 +15048,7 @@ function CanvasImpl({
|
|
|
15606
15048
|
const handleDropMissed = useMutableCallback(onDropMissed);
|
|
15607
15049
|
const [block, setBlock] = React.useState(false);
|
|
15608
15050
|
const [error, setError] = React.useState(false);
|
|
15051
|
+
const [fallbackVisible, setFallbackVisible] = React.useState(false);
|
|
15609
15052
|
if (block) throw block;
|
|
15610
15053
|
if (error) throw error;
|
|
15611
15054
|
const root = React.useRef(null);
|
|
@@ -15613,6 +15056,7 @@ function CanvasImpl({
|
|
|
15613
15056
|
const unsubscribeErrorRef = React.useRef(null);
|
|
15614
15057
|
useIsomorphicLayoutEffect(() => {
|
|
15615
15058
|
effectActiveRef.current = true;
|
|
15059
|
+
if (fallbackVisible) return;
|
|
15616
15060
|
const canvas = canvasRef.current;
|
|
15617
15061
|
if (effectiveSize.width > 0 && effectiveSize.height > 0 && canvas) {
|
|
15618
15062
|
if (!root.current) {
|
|
@@ -15633,7 +15077,7 @@ function CanvasImpl({
|
|
|
15633
15077
|
}
|
|
15634
15078
|
async function run() {
|
|
15635
15079
|
if (!effectActiveRef.current || !root.current) return;
|
|
15636
|
-
await root.current.configure({
|
|
15080
|
+
const configured = await root.current.configure({
|
|
15637
15081
|
id,
|
|
15638
15082
|
primaryCanvas,
|
|
15639
15083
|
scheduler,
|
|
@@ -15648,6 +15092,8 @@ function CanvasImpl({
|
|
|
15648
15092
|
performance,
|
|
15649
15093
|
raycaster,
|
|
15650
15094
|
camera,
|
|
15095
|
+
autoUpdateFrustum,
|
|
15096
|
+
occlusion,
|
|
15651
15097
|
size: effectiveSize,
|
|
15652
15098
|
// Store size props for reset functionality
|
|
15653
15099
|
_sizeProps: width !== void 0 || height !== void 0 ? { width, height } : null,
|
|
@@ -15672,8 +15118,20 @@ function CanvasImpl({
|
|
|
15672
15118
|
}
|
|
15673
15119
|
onCreated?.(state);
|
|
15674
15120
|
}
|
|
15675
|
-
})
|
|
15676
|
-
|
|
15121
|
+
}).then(
|
|
15122
|
+
() => true,
|
|
15123
|
+
// Renderer setup failed (e.g. no WebGL/WebGPU support). The `fallback` prop lives
|
|
15124
|
+
// inside <canvas>, which browsers don't display, so surface it as visible DOM
|
|
15125
|
+
// instead; with no fallback, rethrow to an external error boundary. (#3757)
|
|
15126
|
+
(setupError) => {
|
|
15127
|
+
if (effectActiveRef.current) {
|
|
15128
|
+
if (fallback != null) setFallbackVisible(true);
|
|
15129
|
+
else setError(setupError);
|
|
15130
|
+
}
|
|
15131
|
+
return false;
|
|
15132
|
+
}
|
|
15133
|
+
);
|
|
15134
|
+
if (!configured || !effectActiveRef.current || !root.current) return;
|
|
15677
15135
|
root.current.render(
|
|
15678
15136
|
/* @__PURE__ */ jsx(Bridge, { children: /* @__PURE__ */ jsx(ErrorBoundary, { set: setError, children: /* @__PURE__ */ jsxs(React.Suspense, { fallback: /* @__PURE__ */ jsx(Block, { set: setBlock }), children: [
|
|
15679
15137
|
backgroundProps && /* @__PURE__ */ jsx(Environment, { ...backgroundProps }),
|
|
@@ -15707,20 +15165,13 @@ function CanvasImpl({
|
|
|
15707
15165
|
const handleHMR = () => {
|
|
15708
15166
|
queueMicrotask(() => {
|
|
15709
15167
|
const rootEntry = _roots.get(canvas);
|
|
15710
|
-
if (rootEntry?.store)
|
|
15711
|
-
rootEntry.store.setState((state) => ({
|
|
15712
|
-
nodes: {},
|
|
15713
|
-
uniforms: {},
|
|
15714
|
-
_hmrVersion: state._hmrVersion + 1
|
|
15715
|
-
}));
|
|
15716
|
-
}
|
|
15168
|
+
if (rootEntry?.store) clearHmrCaches(rootEntry.store);
|
|
15717
15169
|
});
|
|
15718
15170
|
};
|
|
15719
15171
|
if (typeof import.meta !== "undefined" && import.meta.hot) {
|
|
15720
15172
|
const hot = import.meta.hot;
|
|
15721
15173
|
hot.on("vite:afterUpdate", handleHMR);
|
|
15722
|
-
return () => hot.
|
|
15723
|
-
});
|
|
15174
|
+
return () => hot.off?.("vite:afterUpdate", handleHMR);
|
|
15724
15175
|
}
|
|
15725
15176
|
if (typeof module !== "undefined" && module.hot) {
|
|
15726
15177
|
const hot = module.hot;
|
|
@@ -15743,7 +15194,20 @@ function CanvasImpl({
|
|
|
15743
15194
|
...style
|
|
15744
15195
|
},
|
|
15745
15196
|
...props,
|
|
15746
|
-
children:
|
|
15197
|
+
children: fallbackVisible ? (
|
|
15198
|
+
// Renderer setup failed: render the fallback as visible DOM. Inside <canvas> (below)
|
|
15199
|
+
// it exists in the tree but browsers never paint it, which is the whole bug (#3757).
|
|
15200
|
+
fallback
|
|
15201
|
+
) : /* @__PURE__ */ jsx("div", { ref: containerRef, className: "r3f-canvas-container", style: { width: "100%", height: "100%" }, children: /* @__PURE__ */ jsx(
|
|
15202
|
+
"canvas",
|
|
15203
|
+
{
|
|
15204
|
+
ref: canvasRef,
|
|
15205
|
+
id,
|
|
15206
|
+
className: "r3f-canvas",
|
|
15207
|
+
style: { display: "block", width: "100%", height: "100%" },
|
|
15208
|
+
children: fallback
|
|
15209
|
+
}
|
|
15210
|
+
) })
|
|
15747
15211
|
}
|
|
15748
15212
|
);
|
|
15749
15213
|
}
|
|
@@ -15753,4 +15217,4 @@ function Canvas(props) {
|
|
|
15753
15217
|
|
|
15754
15218
|
extend(THREE);
|
|
15755
15219
|
|
|
15756
|
-
export { Block, Canvas, Environment, EnvironmentCube, EnvironmentMap, EnvironmentPortal, ErrorBoundary, FROM_REF, IsObject, ONCE, Portal, R3F_BUILD_LEGACY, R3F_BUILD_WEBGPU, REACT_INTERNAL_PROPS, RESERVED_PROPS,
|
|
15220
|
+
export { Block, Canvas, Environment, EnvironmentCube, EnvironmentMap, EnvironmentPortal, ErrorBoundary, FROM_REF, IsObject, ONCE, Portal, R3F_BUILD_LEGACY, R3F_BUILD_WEBGPU, REACT_INTERNAL_PROPS, RESERVED_PROPS, Texture, _roots, addAfterEffect, addEffect, addTail, advance, applyProps, attach, buildGraph, calculateDpr, context, createEvents, createPointerEvents, createPortal, createRoot, createStore, detach, diffProps, dispose, createPointerEvents as events, extend, findInitialRoot, flushSync, fromRef, getInstanceProps, getPrimary, getPrimaryIds, getRootState, getUuidPrefix, hasConstructor, hasPrimary, invalidate, invalidateInstance, is, isColorRepresentation, isCopyable, isFromRef, isObject3D, isOnce, isOrthographicCamera, isRef, isRenderer, isTexture, isVectorLike, once, prepare, presetsObj, reconciler, registerPrimary, removeInteractivity, resolve, swapInteractivity, unmountComponentAtNode, unregisterPrimary, updateCamera, updateFrustum, useBridge, useEnvironment, useFrame, useGraph, useInstanceHandle, useIsomorphicLayoutEffect, useLoader, useMutableCallback, useRenderTarget, useStore, useTexture, useTextures, useThree, waitForPrimary };
|