@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/webgpu/index.cjs
CHANGED
|
@@ -14,6 +14,7 @@ const UltraHDRLoader_js = require('three/examples/jsm/loaders/UltraHDRLoader.js'
|
|
|
14
14
|
const gainmapJs = require('@monogrid/gainmap-js');
|
|
15
15
|
const Tb = require('scheduler');
|
|
16
16
|
const traditional = require('zustand/traditional');
|
|
17
|
+
const scheduler = require('@pmndrs/scheduler');
|
|
17
18
|
const suspendReact = require('suspend-react');
|
|
18
19
|
const lite = require('dequal/lite');
|
|
19
20
|
require('zustand/shallow');
|
|
@@ -62,13 +63,16 @@ const WebGLRenderer = class WebGLRenderer2 {
|
|
|
62
63
|
}
|
|
63
64
|
};
|
|
64
65
|
const WebGLRenderTarget = null;
|
|
66
|
+
const WebGLCubeRenderTarget = null;
|
|
65
67
|
|
|
66
68
|
const THREE = /*#__PURE__*/_mergeNamespaces({
|
|
67
69
|
__proto__: null,
|
|
70
|
+
CubeRenderTargetCompat: webgpu.CubeRenderTarget,
|
|
68
71
|
Inspector: Inspector_js.Inspector,
|
|
69
72
|
R3F_BUILD_LEGACY: R3F_BUILD_LEGACY,
|
|
70
73
|
R3F_BUILD_WEBGPU: R3F_BUILD_WEBGPU,
|
|
71
74
|
RenderTargetCompat: webgpu.RenderTarget,
|
|
75
|
+
WebGLCubeRenderTarget: WebGLCubeRenderTarget,
|
|
72
76
|
WebGLRenderTarget: WebGLRenderTarget,
|
|
73
77
|
WebGLRenderer: WebGLRenderer
|
|
74
78
|
}, [webgpu__namespace]);
|
|
@@ -170,7 +174,7 @@ function useEnvironment({
|
|
|
170
174
|
fiber.useLoader.clear(loader, multiFile ? [files] : files);
|
|
171
175
|
}
|
|
172
176
|
renderer.domElement.addEventListener("webglcontextlost", clearGainmapTexture, { once: true });
|
|
173
|
-
}, [files, renderer.domElement]);
|
|
177
|
+
}, [extension, files, loader, multiFile, renderer.domElement]);
|
|
174
178
|
const loaderResult = fiber.useLoader(
|
|
175
179
|
loader,
|
|
176
180
|
multiFile ? [files] : files,
|
|
@@ -342,15 +346,16 @@ function EnvironmentPortal({
|
|
|
342
346
|
preset = void 0,
|
|
343
347
|
extensions
|
|
344
348
|
}) {
|
|
345
|
-
const
|
|
349
|
+
const renderer = fiber.useThree((state) => state.renderer);
|
|
350
|
+
const isLegacy = fiber.useThree((state) => state.isLegacy);
|
|
346
351
|
const defaultScene = fiber.useThree((state) => state.scene);
|
|
347
352
|
const camera = React__namespace.useRef(null);
|
|
348
353
|
const [virtualScene] = React__namespace.useState(() => new webgpu.Scene());
|
|
349
354
|
const fbo = React__namespace.useMemo(() => {
|
|
350
|
-
const fbo2 = new webgpu.
|
|
355
|
+
const fbo2 = isLegacy ? new WebGLCubeRenderTarget(resolution) : new webgpu.CubeRenderTarget(resolution);
|
|
351
356
|
fbo2.texture.type = webgpu.HalfFloatType;
|
|
352
357
|
return fbo2;
|
|
353
|
-
}, [resolution]);
|
|
358
|
+
}, [resolution, isLegacy]);
|
|
354
359
|
React__namespace.useEffect(() => {
|
|
355
360
|
return () => {
|
|
356
361
|
fbo.dispose();
|
|
@@ -358,10 +363,10 @@ function EnvironmentPortal({
|
|
|
358
363
|
}, [fbo]);
|
|
359
364
|
React__namespace.useLayoutEffect(() => {
|
|
360
365
|
if (frames === 1) {
|
|
361
|
-
const autoClear =
|
|
362
|
-
|
|
363
|
-
camera.current.update(
|
|
364
|
-
|
|
366
|
+
const autoClear = renderer.autoClear;
|
|
367
|
+
renderer.autoClear = true;
|
|
368
|
+
camera.current.update(renderer, virtualScene);
|
|
369
|
+
renderer.autoClear = autoClear;
|
|
365
370
|
}
|
|
366
371
|
return setEnvProps(background, scene, defaultScene, fbo.texture, {
|
|
367
372
|
backgroundBlurriness: blur ?? backgroundBlurriness,
|
|
@@ -370,14 +375,29 @@ function EnvironmentPortal({
|
|
|
370
375
|
environmentIntensity,
|
|
371
376
|
environmentRotation
|
|
372
377
|
});
|
|
373
|
-
}, [
|
|
378
|
+
}, [
|
|
379
|
+
children,
|
|
380
|
+
virtualScene,
|
|
381
|
+
fbo.texture,
|
|
382
|
+
scene,
|
|
383
|
+
defaultScene,
|
|
384
|
+
background,
|
|
385
|
+
frames,
|
|
386
|
+
renderer,
|
|
387
|
+
blur,
|
|
388
|
+
backgroundBlurriness,
|
|
389
|
+
backgroundIntensity,
|
|
390
|
+
backgroundRotation,
|
|
391
|
+
environmentIntensity,
|
|
392
|
+
environmentRotation
|
|
393
|
+
]);
|
|
374
394
|
let count = 1;
|
|
375
395
|
fiber.useFrame(() => {
|
|
376
396
|
if (frames === Infinity || count < frames) {
|
|
377
|
-
const autoClear =
|
|
378
|
-
|
|
379
|
-
camera.current.update(
|
|
380
|
-
|
|
397
|
+
const autoClear = renderer.autoClear;
|
|
398
|
+
renderer.autoClear = true;
|
|
399
|
+
camera.current.update(renderer, virtualScene);
|
|
400
|
+
renderer.autoClear = autoClear;
|
|
381
401
|
count++;
|
|
382
402
|
}
|
|
383
403
|
});
|
|
@@ -441,10 +461,9 @@ function Environment(props) {
|
|
|
441
461
|
return props.ground ? /* @__PURE__ */ jsxRuntime.jsx(EnvironmentGround, { ...props }) : props.map ? /* @__PURE__ */ jsxRuntime.jsx(EnvironmentMap, { ...props }) : props.children ? /* @__PURE__ */ jsxRuntime.jsx(EnvironmentPortal, { ...props }) : /* @__PURE__ */ jsxRuntime.jsx(EnvironmentCube, { ...props });
|
|
442
462
|
}
|
|
443
463
|
|
|
444
|
-
var __defProp$
|
|
445
|
-
var __defNormalProp$
|
|
446
|
-
var __publicField$
|
|
447
|
-
const act = React__namespace["act"];
|
|
464
|
+
var __defProp$1 = Object.defineProperty;
|
|
465
|
+
var __defNormalProp$1 = (obj, key, value) => key in obj ? __defProp$1(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
466
|
+
var __publicField$1 = (obj, key, value) => __defNormalProp$1(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
448
467
|
const useIsomorphicLayoutEffect = /* @__PURE__ */ (() => typeof window !== "undefined" && (window.document?.createElement || window.navigator?.product === "ReactNative"))() ? React__namespace.useLayoutEffect : React__namespace.useEffect;
|
|
449
468
|
function useMutableCallback(fn) {
|
|
450
469
|
const ref = React__namespace.useRef(fn);
|
|
@@ -475,7 +494,7 @@ const ErrorBoundary = /* @__PURE__ */ (() => {
|
|
|
475
494
|
return _a = class extends React__namespace.Component {
|
|
476
495
|
constructor() {
|
|
477
496
|
super(...arguments);
|
|
478
|
-
__publicField$
|
|
497
|
+
__publicField$1(this, "state", { error: false });
|
|
479
498
|
}
|
|
480
499
|
componentDidCatch(err) {
|
|
481
500
|
this.props.set(err);
|
|
@@ -483,7 +502,7 @@ const ErrorBoundary = /* @__PURE__ */ (() => {
|
|
|
483
502
|
render() {
|
|
484
503
|
return this.state.error ? null : this.props.children;
|
|
485
504
|
}
|
|
486
|
-
}, __publicField$
|
|
505
|
+
}, __publicField$1(_a, "getDerivedStateFromError", () => ({ error: true })), _a;
|
|
487
506
|
})();
|
|
488
507
|
|
|
489
508
|
const is = {
|
|
@@ -620,8 +639,7 @@ function prepare(target, root, type, props) {
|
|
|
620
639
|
object,
|
|
621
640
|
eventCount: 0,
|
|
622
641
|
handlers: {},
|
|
623
|
-
isHidden: false
|
|
624
|
-
deferredRefs: []
|
|
642
|
+
isHidden: false
|
|
625
643
|
};
|
|
626
644
|
if (object) object.__r3f = instance;
|
|
627
645
|
}
|
|
@@ -778,6 +796,17 @@ function unregisterVisibility(store, object) {
|
|
|
778
796
|
internal.visibilityRegistry.delete(object.uuid);
|
|
779
797
|
internal.occlusionCache.delete(object);
|
|
780
798
|
}
|
|
799
|
+
function updateVisibilityHandlers(store, object, handlers) {
|
|
800
|
+
const { internal } = store.getState();
|
|
801
|
+
const entry = internal.visibilityRegistry.get(object.uuid);
|
|
802
|
+
if (!entry) return false;
|
|
803
|
+
entry.handlers = handlers;
|
|
804
|
+
if ((handlers.onOccluded || handlers.onVisible) && !internal.occlusionEnabled) {
|
|
805
|
+
object.occlusionTest = true;
|
|
806
|
+
enableOcclusion(store);
|
|
807
|
+
}
|
|
808
|
+
return true;
|
|
809
|
+
}
|
|
781
810
|
function checkVisibility(state) {
|
|
782
811
|
const { internal, camera } = state;
|
|
783
812
|
const registry = internal.visibilityRegistry;
|
|
@@ -938,9 +967,9 @@ function diffProps(instance, newProps) {
|
|
|
938
967
|
const { root, key } = resolve(instance.object, prop);
|
|
939
968
|
if (root.constructor && root.constructor.length === 0) {
|
|
940
969
|
const ctor = getMemoizedPrototype(root);
|
|
941
|
-
if (!is.und(ctor)) changedProps[
|
|
970
|
+
if (!is.und(ctor)) changedProps[prop] = ctor[key];
|
|
942
971
|
} else {
|
|
943
|
-
changedProps[
|
|
972
|
+
changedProps[prop] = 0;
|
|
944
973
|
}
|
|
945
974
|
}
|
|
946
975
|
return changedProps;
|
|
@@ -965,10 +994,7 @@ function applyProps(object, props) {
|
|
|
965
994
|
continue;
|
|
966
995
|
}
|
|
967
996
|
if (value === void 0) continue;
|
|
968
|
-
if (isFromRef(value))
|
|
969
|
-
instance?.deferredRefs?.push({ prop, ref: value[FROM_REF] });
|
|
970
|
-
continue;
|
|
971
|
-
}
|
|
997
|
+
if (isFromRef(value)) continue;
|
|
972
998
|
if (isOnce(value)) {
|
|
973
999
|
if (instance?.appliedOnce?.has(prop)) continue;
|
|
974
1000
|
if (instance) {
|
|
@@ -1004,8 +1030,24 @@ function applyProps(object, props) {
|
|
|
1004
1030
|
} else if (isVectorLike(target) && is.num(value)) {
|
|
1005
1031
|
if ("setScalar" in target && typeof target.setScalar === "function") target.setScalar(value);
|
|
1006
1032
|
else target.set(value);
|
|
1033
|
+
} else if (root instanceof webgpu.ShaderMaterial && key === "uniforms" && is.obj(value)) {
|
|
1034
|
+
if (!is.obj(root.uniforms)) root.uniforms = {};
|
|
1035
|
+
const uniforms = root.uniforms;
|
|
1036
|
+
const nextUniforms = value;
|
|
1037
|
+
for (const name in nextUniforms) {
|
|
1038
|
+
const uniform = nextUniforms[name];
|
|
1039
|
+
const targetUniform = uniforms[name];
|
|
1040
|
+
if (targetUniform) Object.assign(targetUniform, uniform);
|
|
1041
|
+
else {
|
|
1042
|
+
const nextUniform = uniform instanceof webgpu.Uniform ? uniform.clone() : new webgpu.Uniform(uniform.value);
|
|
1043
|
+
uniforms[name] = nextUniform;
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1007
1046
|
} else {
|
|
1008
1047
|
root[key] = value;
|
|
1048
|
+
if (key.endsWith("Node") && root.isMaterial) {
|
|
1049
|
+
root.needsUpdate = true;
|
|
1050
|
+
}
|
|
1009
1051
|
if (rootState && rootState.renderer?.outputColorSpace === webgpu.SRGBColorSpace && colorMaps.includes(key) && isTexture(value) && root[key]?.isTexture && // sRGB textures must be RGBA8 since r137 https://github.com/mrdoob/three.js/pull/23129
|
|
1010
1052
|
root[key].format === webgpu.RGBAFormat && root[key].type === webgpu.UnsignedByteType) {
|
|
1011
1053
|
root[key].colorSpace = rootState.textureColorSpace;
|
|
@@ -1019,6 +1061,9 @@ function applyProps(object, props) {
|
|
|
1019
1061
|
if (instance.eventCount && object2.raycast !== null) {
|
|
1020
1062
|
rootState.internal.interaction.push(object2);
|
|
1021
1063
|
}
|
|
1064
|
+
}
|
|
1065
|
+
if (instance?.parent && rootState?.internal && instance.object?.isObject3D) {
|
|
1066
|
+
const object2 = instance.object;
|
|
1022
1067
|
const root = findInitialRoot(instance);
|
|
1023
1068
|
const visibilityHandlers = {
|
|
1024
1069
|
onFramed: instance.handlers.onFramed,
|
|
@@ -1026,7 +1071,9 @@ function applyProps(object, props) {
|
|
|
1026
1071
|
onVisible: instance.handlers.onVisible
|
|
1027
1072
|
};
|
|
1028
1073
|
if (hasVisibilityHandlers(visibilityHandlers)) {
|
|
1029
|
-
|
|
1074
|
+
if (!updateVisibilityHandlers(root, object2, visibilityHandlers)) {
|
|
1075
|
+
registerVisibility(root, object2, visibilityHandlers);
|
|
1076
|
+
}
|
|
1030
1077
|
} else {
|
|
1031
1078
|
unregisterVisibility(root, object2);
|
|
1032
1079
|
}
|
|
@@ -1039,38 +1086,95 @@ function applyProps(object, props) {
|
|
|
1039
1086
|
return object;
|
|
1040
1087
|
}
|
|
1041
1088
|
|
|
1089
|
+
const DEFAULT_POINTER_ID = 0;
|
|
1090
|
+
const XR_POINTER_ID_START = 1e3;
|
|
1091
|
+
function getPointerState(internal, pointerId) {
|
|
1092
|
+
let state = internal.pointerMap.get(pointerId);
|
|
1093
|
+
if (!state) {
|
|
1094
|
+
state = {
|
|
1095
|
+
hovered: /* @__PURE__ */ new Map(),
|
|
1096
|
+
captured: /* @__PURE__ */ new Map(),
|
|
1097
|
+
initialClick: [0, 0],
|
|
1098
|
+
initialHits: []
|
|
1099
|
+
};
|
|
1100
|
+
internal.pointerMap.set(pointerId, state);
|
|
1101
|
+
}
|
|
1102
|
+
return state;
|
|
1103
|
+
}
|
|
1104
|
+
function getPointerId(event) {
|
|
1105
|
+
return "pointerId" in event ? event.pointerId : DEFAULT_POINTER_ID;
|
|
1106
|
+
}
|
|
1042
1107
|
function makeId(event) {
|
|
1043
1108
|
return (event.eventObject || event.object).uuid + "/" + event.index + event.instanceId;
|
|
1044
1109
|
}
|
|
1045
|
-
function releaseInternalPointerCapture(
|
|
1046
|
-
const
|
|
1110
|
+
function releaseInternalPointerCapture(internal, obj, pointerId) {
|
|
1111
|
+
const pointerState = internal.pointerMap.get(pointerId);
|
|
1112
|
+
if (!pointerState) return;
|
|
1113
|
+
const captureData = pointerState.captured.get(obj);
|
|
1047
1114
|
if (captureData) {
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1115
|
+
pointerState.captured.delete(obj);
|
|
1116
|
+
captureData.target.releasePointerCapture(pointerId);
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
function swapInteractivity(store, object, newObject) {
|
|
1120
|
+
const { internal } = store.getState();
|
|
1121
|
+
for (let i = 0; i < internal.interaction.length; i++) {
|
|
1122
|
+
if (internal.interaction[i] === object) internal.interaction[i] = newObject;
|
|
1123
|
+
}
|
|
1124
|
+
for (const pointerState of internal.pointerMap.values()) {
|
|
1125
|
+
for (let i = 0; i < pointerState.initialHits.length; i++) {
|
|
1126
|
+
if (pointerState.initialHits[i] === object) pointerState.initialHits[i] = newObject;
|
|
1127
|
+
}
|
|
1128
|
+
pointerState.hovered.forEach((value, key) => {
|
|
1129
|
+
if (value.eventObject === object || value.object === object) {
|
|
1130
|
+
pointerState.hovered.delete(key);
|
|
1131
|
+
const next = {
|
|
1132
|
+
...value,
|
|
1133
|
+
eventObject: value.eventObject === object ? newObject : value.eventObject,
|
|
1134
|
+
object: value.object === object ? newObject : value.object
|
|
1135
|
+
};
|
|
1136
|
+
pointerState.hovered.set(makeId(next), next);
|
|
1137
|
+
}
|
|
1138
|
+
});
|
|
1139
|
+
const captureData = pointerState.captured.get(object);
|
|
1140
|
+
if (captureData) {
|
|
1141
|
+
pointerState.captured.delete(object);
|
|
1142
|
+
pointerState.captured.set(newObject, {
|
|
1143
|
+
...captureData,
|
|
1144
|
+
intersection: {
|
|
1145
|
+
...captureData.intersection,
|
|
1146
|
+
object: captureData.intersection.object === object ? newObject : captureData.intersection.object,
|
|
1147
|
+
eventObject: captureData.intersection.eventObject === object ? newObject : captureData.intersection.eventObject
|
|
1148
|
+
}
|
|
1149
|
+
});
|
|
1052
1150
|
}
|
|
1053
1151
|
}
|
|
1152
|
+
unregisterVisibility(store, object);
|
|
1054
1153
|
}
|
|
1055
1154
|
function removeInteractivity(store, object) {
|
|
1056
1155
|
const { internal } = store.getState();
|
|
1057
1156
|
internal.interaction = internal.interaction.filter((o) => o !== object);
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1157
|
+
for (const [pointerId, pointerState] of internal.pointerMap) {
|
|
1158
|
+
pointerState.initialHits = pointerState.initialHits.filter((o) => o !== object);
|
|
1159
|
+
pointerState.hovered.forEach((value, key) => {
|
|
1160
|
+
if (value.eventObject === object || value.object === object) {
|
|
1161
|
+
pointerState.hovered.delete(key);
|
|
1162
|
+
}
|
|
1163
|
+
});
|
|
1164
|
+
if (pointerState.captured.has(object)) {
|
|
1165
|
+
releaseInternalPointerCapture(internal, object, pointerId);
|
|
1062
1166
|
}
|
|
1063
|
-
}
|
|
1064
|
-
internal.capturedMap.forEach((captures, pointerId) => {
|
|
1065
|
-
releaseInternalPointerCapture(internal.capturedMap, object, captures, pointerId);
|
|
1066
|
-
});
|
|
1167
|
+
}
|
|
1067
1168
|
unregisterVisibility(store, object);
|
|
1068
1169
|
}
|
|
1069
1170
|
function createEvents(store) {
|
|
1070
|
-
function calculateDistance(event) {
|
|
1171
|
+
function calculateDistance(event, pointerId) {
|
|
1071
1172
|
const { internal } = store.getState();
|
|
1072
|
-
const
|
|
1073
|
-
|
|
1173
|
+
const pointerState = internal.pointerMap.get(pointerId);
|
|
1174
|
+
if (!pointerState) return 0;
|
|
1175
|
+
const [initialX, initialY] = pointerState.initialClick;
|
|
1176
|
+
const dx = event.offsetX - initialX;
|
|
1177
|
+
const dy = event.offsetY - initialY;
|
|
1074
1178
|
return Math.round(Math.sqrt(dx * dx + dy * dy));
|
|
1075
1179
|
}
|
|
1076
1180
|
function filterPointerEvents(objects) {
|
|
@@ -1106,6 +1210,15 @@ function createEvents(store) {
|
|
|
1106
1210
|
return state2.raycaster.camera ? state2.raycaster.intersectObject(obj, true) : [];
|
|
1107
1211
|
}
|
|
1108
1212
|
let hits = eventsObjects.flatMap(handleRaycast).sort((a, b) => {
|
|
1213
|
+
const aInteractivePriority = a.object.userData?.interactivePriority;
|
|
1214
|
+
const bInteractivePriority = b.object.userData?.interactivePriority;
|
|
1215
|
+
if (aInteractivePriority !== void 0 || bInteractivePriority !== void 0) {
|
|
1216
|
+
if (aInteractivePriority !== void 0 && bInteractivePriority === void 0) return -1;
|
|
1217
|
+
if (bInteractivePriority !== void 0 && aInteractivePriority === void 0) return 1;
|
|
1218
|
+
if (aInteractivePriority !== bInteractivePriority) {
|
|
1219
|
+
return (bInteractivePriority ?? 0) - (aInteractivePriority ?? 0);
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1109
1222
|
const aState = getRootState(a.object);
|
|
1110
1223
|
const bState = getRootState(b.object);
|
|
1111
1224
|
const aPriority = aState?.events?.priority ?? 1;
|
|
@@ -1127,9 +1240,13 @@ function createEvents(store) {
|
|
|
1127
1240
|
eventObject = eventObject.parent;
|
|
1128
1241
|
}
|
|
1129
1242
|
}
|
|
1130
|
-
if ("pointerId" in event
|
|
1131
|
-
|
|
1132
|
-
|
|
1243
|
+
if ("pointerId" in event) {
|
|
1244
|
+
const pointerId = event.pointerId;
|
|
1245
|
+
const pointerState = state.internal.pointerMap.get(pointerId);
|
|
1246
|
+
if (pointerState?.captured.size) {
|
|
1247
|
+
for (const captureData of pointerState.captured.values()) {
|
|
1248
|
+
if (!duplicates.has(makeId(captureData.intersection))) intersections.push(captureData.intersection);
|
|
1249
|
+
}
|
|
1133
1250
|
}
|
|
1134
1251
|
}
|
|
1135
1252
|
return intersections;
|
|
@@ -1142,27 +1259,25 @@ function createEvents(store) {
|
|
|
1142
1259
|
if (state) {
|
|
1143
1260
|
const { raycaster, pointer, camera, internal } = state;
|
|
1144
1261
|
const unprojectedPoint = new webgpu.Vector3(pointer.x, pointer.y, 0).unproject(camera);
|
|
1145
|
-
const hasPointerCapture = (id) =>
|
|
1262
|
+
const hasPointerCapture = (id) => {
|
|
1263
|
+
const pointerState = internal.pointerMap.get(id);
|
|
1264
|
+
return pointerState?.captured.has(hit.eventObject) ?? false;
|
|
1265
|
+
};
|
|
1146
1266
|
const setPointerCapture = (id) => {
|
|
1147
1267
|
const captureData = { intersection: hit, target: event.target };
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
} else {
|
|
1151
|
-
internal.capturedMap.set(id, /* @__PURE__ */ new Map([[hit.eventObject, captureData]]));
|
|
1152
|
-
}
|
|
1268
|
+
const pointerState = getPointerState(internal, id);
|
|
1269
|
+
pointerState.captured.set(hit.eventObject, captureData);
|
|
1153
1270
|
event.target.setPointerCapture(id);
|
|
1154
1271
|
};
|
|
1155
1272
|
const releasePointerCapture = (id) => {
|
|
1156
|
-
|
|
1157
|
-
if (captures) {
|
|
1158
|
-
releaseInternalPointerCapture(internal.capturedMap, hit.eventObject, captures, id);
|
|
1159
|
-
}
|
|
1273
|
+
releaseInternalPointerCapture(internal, hit.eventObject, id);
|
|
1160
1274
|
};
|
|
1161
1275
|
const extractEventProps = {};
|
|
1162
1276
|
for (const prop in event) {
|
|
1163
1277
|
const property = event[prop];
|
|
1164
1278
|
if (typeof property !== "function") extractEventProps[prop] = property;
|
|
1165
1279
|
}
|
|
1280
|
+
const eventPointerId = "pointerId" in event ? event.pointerId : void 0;
|
|
1166
1281
|
const raycastEvent = {
|
|
1167
1282
|
...hit,
|
|
1168
1283
|
...extractEventProps,
|
|
@@ -1173,18 +1288,19 @@ function createEvents(store) {
|
|
|
1173
1288
|
unprojectedPoint,
|
|
1174
1289
|
ray: raycaster.ray,
|
|
1175
1290
|
camera,
|
|
1291
|
+
pointerId: eventPointerId,
|
|
1176
1292
|
// Hijack stopPropagation, which just sets a flag
|
|
1177
1293
|
stopPropagation() {
|
|
1178
|
-
const
|
|
1294
|
+
const pointerState = eventPointerId !== void 0 ? internal.pointerMap.get(eventPointerId) : void 0;
|
|
1179
1295
|
if (
|
|
1180
1296
|
// ...if this pointer hasn't been captured
|
|
1181
|
-
!
|
|
1182
|
-
|
|
1297
|
+
!pointerState?.captured.size || // ... or if the hit object is capturing the pointer
|
|
1298
|
+
pointerState.captured.has(hit.eventObject)
|
|
1183
1299
|
) {
|
|
1184
1300
|
raycastEvent.stopped = localState.stopped = true;
|
|
1185
|
-
if (
|
|
1301
|
+
if (pointerState?.hovered.size && Array.from(pointerState.hovered.values()).find((i) => i.eventObject === hit.eventObject)) {
|
|
1186
1302
|
const higher = intersections.slice(0, intersections.indexOf(hit));
|
|
1187
|
-
cancelPointer([...higher, hit]);
|
|
1303
|
+
cancelPointer([...higher, hit], eventPointerId);
|
|
1188
1304
|
}
|
|
1189
1305
|
}
|
|
1190
1306
|
},
|
|
@@ -1200,15 +1316,18 @@ function createEvents(store) {
|
|
|
1200
1316
|
}
|
|
1201
1317
|
return intersections;
|
|
1202
1318
|
}
|
|
1203
|
-
function cancelPointer(intersections) {
|
|
1319
|
+
function cancelPointer(intersections, pointerId) {
|
|
1204
1320
|
const { internal } = store.getState();
|
|
1205
|
-
|
|
1321
|
+
const pid = pointerId ?? DEFAULT_POINTER_ID;
|
|
1322
|
+
const pointerState = internal.pointerMap.get(pid);
|
|
1323
|
+
if (!pointerState) return;
|
|
1324
|
+
for (const [hoveredId, hoveredObj] of pointerState.hovered) {
|
|
1206
1325
|
if (!intersections.length || !intersections.find(
|
|
1207
1326
|
(hit) => hit.object === hoveredObj.object && hit.index === hoveredObj.index && hit.instanceId === hoveredObj.instanceId
|
|
1208
1327
|
)) {
|
|
1209
1328
|
const eventObject = hoveredObj.eventObject;
|
|
1210
1329
|
const instance = eventObject.__r3f;
|
|
1211
|
-
|
|
1330
|
+
pointerState.hovered.delete(hoveredId);
|
|
1212
1331
|
if (instance?.eventCount) {
|
|
1213
1332
|
const handlers = instance.handlers;
|
|
1214
1333
|
const data = { ...hoveredObj, intersections };
|
|
@@ -1237,41 +1356,118 @@ function createEvents(store) {
|
|
|
1237
1356
|
instance?.handlers.onDropMissed?.(event);
|
|
1238
1357
|
}
|
|
1239
1358
|
}
|
|
1359
|
+
function cleanupPointer(pointerId) {
|
|
1360
|
+
const { internal } = store.getState();
|
|
1361
|
+
const pointerState = internal.pointerMap.get(pointerId);
|
|
1362
|
+
if (pointerState) {
|
|
1363
|
+
for (const [, hoveredObj] of pointerState.hovered) {
|
|
1364
|
+
const eventObject = hoveredObj.eventObject;
|
|
1365
|
+
const instance = eventObject.__r3f;
|
|
1366
|
+
if (instance?.eventCount) {
|
|
1367
|
+
const handlers = instance.handlers;
|
|
1368
|
+
const data = { ...hoveredObj, intersections: [] };
|
|
1369
|
+
handlers.onPointerOut?.(data);
|
|
1370
|
+
handlers.onPointerLeave?.(data);
|
|
1371
|
+
}
|
|
1372
|
+
}
|
|
1373
|
+
internal.pointerMap.delete(pointerId);
|
|
1374
|
+
}
|
|
1375
|
+
internal.pointerDirty.delete(pointerId);
|
|
1376
|
+
}
|
|
1377
|
+
function processDeferredPointer(event, pointerId) {
|
|
1378
|
+
const state = store.getState();
|
|
1379
|
+
const { internal } = state;
|
|
1380
|
+
if (!state.events.enabled) return;
|
|
1381
|
+
const filter = filterPointerEvents;
|
|
1382
|
+
const hits = intersect(event, filter);
|
|
1383
|
+
cancelPointer(hits, pointerId);
|
|
1384
|
+
function onIntersect(data) {
|
|
1385
|
+
const eventObject = data.eventObject;
|
|
1386
|
+
const instance = eventObject.__r3f;
|
|
1387
|
+
if (!instance?.eventCount) return;
|
|
1388
|
+
const handlers = instance.handlers;
|
|
1389
|
+
if (handlers.onPointerOver || handlers.onPointerEnter || handlers.onPointerOut || handlers.onPointerLeave) {
|
|
1390
|
+
const id = makeId(data);
|
|
1391
|
+
const pointerState = getPointerState(internal, pointerId);
|
|
1392
|
+
const hoveredItem = pointerState.hovered.get(id);
|
|
1393
|
+
if (!hoveredItem) {
|
|
1394
|
+
pointerState.hovered.set(id, data);
|
|
1395
|
+
handlers.onPointerOver?.(data);
|
|
1396
|
+
handlers.onPointerEnter?.(data);
|
|
1397
|
+
} else if (hoveredItem.stopped) {
|
|
1398
|
+
data.stopPropagation();
|
|
1399
|
+
}
|
|
1400
|
+
}
|
|
1401
|
+
handlers.onPointerMove?.(data);
|
|
1402
|
+
}
|
|
1403
|
+
handleIntersects(hits, event, 0, onIntersect);
|
|
1404
|
+
}
|
|
1240
1405
|
function handlePointer(name) {
|
|
1241
1406
|
switch (name) {
|
|
1242
1407
|
case "onPointerLeave":
|
|
1243
|
-
case "onPointerCancel":
|
|
1244
1408
|
case "onDragLeave":
|
|
1245
1409
|
return () => cancelPointer([]);
|
|
1410
|
+
// Global cancel of these events
|
|
1411
|
+
case "onPointerCancel":
|
|
1412
|
+
return (event) => {
|
|
1413
|
+
const pointerId = getPointerId(event);
|
|
1414
|
+
cleanupPointer(pointerId);
|
|
1415
|
+
};
|
|
1246
1416
|
case "onLostPointerCapture":
|
|
1247
1417
|
return (event) => {
|
|
1248
1418
|
const { internal } = store.getState();
|
|
1249
|
-
|
|
1419
|
+
const pointerId = getPointerId(event);
|
|
1420
|
+
const pointerState = internal.pointerMap.get(pointerId);
|
|
1421
|
+
if (pointerState?.captured.size) {
|
|
1250
1422
|
requestAnimationFrame(() => {
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1423
|
+
const pointerState2 = internal.pointerMap.get(pointerId);
|
|
1424
|
+
if (pointerState2?.captured.size) {
|
|
1425
|
+
pointerState2.captured.clear();
|
|
1254
1426
|
}
|
|
1427
|
+
cancelPointer([], pointerId);
|
|
1255
1428
|
});
|
|
1256
1429
|
}
|
|
1257
1430
|
};
|
|
1258
1431
|
}
|
|
1259
1432
|
return function handleEvent(event) {
|
|
1260
1433
|
const state = store.getState();
|
|
1261
|
-
const { onPointerMissed, onDragOverMissed, onDropMissed, internal } = state;
|
|
1434
|
+
const { onPointerMissed, onDragOverMissed, onDropMissed, internal, events } = state;
|
|
1435
|
+
const pointerId = getPointerId(event);
|
|
1262
1436
|
internal.lastEvent.current = event;
|
|
1263
|
-
if (!
|
|
1437
|
+
if (!events.enabled) return;
|
|
1264
1438
|
const isPointerMove = name === "onPointerMove";
|
|
1265
1439
|
const isDragOver = name === "onDragOver";
|
|
1266
1440
|
const isDrop = name === "onDrop";
|
|
1267
1441
|
const isClickEvent = name === "onClick" || name === "onContextMenu" || name === "onDoubleClick";
|
|
1442
|
+
const isPointerDown = name === "onPointerDown";
|
|
1443
|
+
const isPointerUp = name === "onPointerUp";
|
|
1444
|
+
const isWheel = name === "onWheel";
|
|
1445
|
+
const canDeferRaycasts = events.frameTimedRaycasts && state.frameloop === "always";
|
|
1446
|
+
if (isPointerMove && canDeferRaycasts) {
|
|
1447
|
+
events.compute?.(event, state);
|
|
1448
|
+
internal.pointerDirty.set(pointerId, event);
|
|
1449
|
+
return;
|
|
1450
|
+
}
|
|
1451
|
+
if (isWheel && canDeferRaycasts && !events.alwaysFireOnScroll) {
|
|
1452
|
+
events.compute?.(event, state);
|
|
1453
|
+
internal.pointerDirty.set(pointerId, event);
|
|
1454
|
+
return;
|
|
1455
|
+
}
|
|
1456
|
+
if ((isClickEvent || isPointerDown || isPointerUp) && internal.pointerDirty.has(pointerId)) {
|
|
1457
|
+
const deferredEvent = internal.pointerDirty.get(pointerId);
|
|
1458
|
+
internal.pointerDirty.delete(pointerId);
|
|
1459
|
+
processDeferredPointer(deferredEvent, pointerId);
|
|
1460
|
+
}
|
|
1268
1461
|
const filter = isPointerMove || isDragOver || isDrop ? filterPointerEvents : void 0;
|
|
1269
1462
|
const hits = intersect(event, filter);
|
|
1270
|
-
const delta = isClickEvent ? calculateDistance(event) : 0;
|
|
1271
|
-
if (
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1463
|
+
const delta = isClickEvent ? calculateDistance(event, pointerId) : 0;
|
|
1464
|
+
if (isPointerDown) {
|
|
1465
|
+
const pointerState2 = getPointerState(internal, pointerId);
|
|
1466
|
+
pointerState2.initialClick = [event.offsetX, event.offsetY];
|
|
1467
|
+
pointerState2.initialHits = hits.map((hit) => hit.eventObject);
|
|
1468
|
+
}
|
|
1469
|
+
const pointerState = internal.pointerMap.get(pointerId);
|
|
1470
|
+
const initialHits = pointerState?.initialHits ?? [];
|
|
1275
1471
|
if (isClickEvent && !hits.length) {
|
|
1276
1472
|
if (delta <= 2) {
|
|
1277
1473
|
pointerMissed(event, internal.interaction);
|
|
@@ -1286,7 +1482,9 @@ function createEvents(store) {
|
|
|
1286
1482
|
dropMissed(event, internal.interaction);
|
|
1287
1483
|
if (onDropMissed) onDropMissed(event);
|
|
1288
1484
|
}
|
|
1289
|
-
if (isPointerMove || isDragOver)
|
|
1485
|
+
if (isPointerMove || isDragOver) {
|
|
1486
|
+
cancelPointer(hits, pointerId);
|
|
1487
|
+
}
|
|
1290
1488
|
function onIntersect(data) {
|
|
1291
1489
|
const eventObject = data.eventObject;
|
|
1292
1490
|
const instance = eventObject.__r3f;
|
|
@@ -1295,9 +1493,10 @@ function createEvents(store) {
|
|
|
1295
1493
|
if (isPointerMove) {
|
|
1296
1494
|
if (handlers.onPointerOver || handlers.onPointerEnter || handlers.onPointerOut || handlers.onPointerLeave) {
|
|
1297
1495
|
const id = makeId(data);
|
|
1298
|
-
const
|
|
1496
|
+
const pointerState2 = getPointerState(internal, pointerId);
|
|
1497
|
+
const hoveredItem = pointerState2.hovered.get(id);
|
|
1299
1498
|
if (!hoveredItem) {
|
|
1300
|
-
|
|
1499
|
+
pointerState2.hovered.set(id, data);
|
|
1301
1500
|
handlers.onPointerOver?.(data);
|
|
1302
1501
|
handlers.onPointerEnter?.(data);
|
|
1303
1502
|
} else if (hoveredItem.stopped) {
|
|
@@ -1307,9 +1506,10 @@ function createEvents(store) {
|
|
|
1307
1506
|
handlers.onPointerMove?.(data);
|
|
1308
1507
|
} else if (isDragOver) {
|
|
1309
1508
|
const id = makeId(data);
|
|
1310
|
-
const
|
|
1509
|
+
const pointerState2 = getPointerState(internal, pointerId);
|
|
1510
|
+
const hoveredItem = pointerState2.hovered.get(id);
|
|
1311
1511
|
if (!hoveredItem) {
|
|
1312
|
-
|
|
1512
|
+
pointerState2.hovered.set(id, data);
|
|
1313
1513
|
handlers.onDragOverEnter?.(data);
|
|
1314
1514
|
} else if (hoveredItem.stopped) {
|
|
1315
1515
|
data.stopPropagation();
|
|
@@ -1320,18 +1520,18 @@ function createEvents(store) {
|
|
|
1320
1520
|
} else {
|
|
1321
1521
|
const handler = handlers[name];
|
|
1322
1522
|
if (handler) {
|
|
1323
|
-
if (!isClickEvent ||
|
|
1523
|
+
if (!isClickEvent || initialHits.includes(eventObject)) {
|
|
1324
1524
|
pointerMissed(
|
|
1325
1525
|
event,
|
|
1326
|
-
internal.interaction.filter((object) => !
|
|
1526
|
+
internal.interaction.filter((object) => !initialHits.includes(object))
|
|
1327
1527
|
);
|
|
1328
1528
|
handler(data);
|
|
1329
1529
|
}
|
|
1330
1530
|
} else {
|
|
1331
|
-
if (isClickEvent &&
|
|
1531
|
+
if (isClickEvent && initialHits.includes(eventObject)) {
|
|
1332
1532
|
pointerMissed(
|
|
1333
1533
|
event,
|
|
1334
|
-
internal.interaction.filter((object) => !
|
|
1534
|
+
internal.interaction.filter((object) => !initialHits.includes(object))
|
|
1335
1535
|
);
|
|
1336
1536
|
}
|
|
1337
1537
|
}
|
|
@@ -1340,7 +1540,15 @@ function createEvents(store) {
|
|
|
1340
1540
|
handleIntersects(hits, event, delta, onIntersect);
|
|
1341
1541
|
};
|
|
1342
1542
|
}
|
|
1343
|
-
|
|
1543
|
+
function flushDeferredPointers() {
|
|
1544
|
+
const { internal, events } = store.getState();
|
|
1545
|
+
if (!events.frameTimedRaycasts) return;
|
|
1546
|
+
for (const [pointerId, event] of internal.pointerDirty) {
|
|
1547
|
+
processDeferredPointer(event, pointerId);
|
|
1548
|
+
}
|
|
1549
|
+
internal.pointerDirty.clear();
|
|
1550
|
+
}
|
|
1551
|
+
return { handlePointer, flushDeferredPointers, processDeferredPointer };
|
|
1344
1552
|
}
|
|
1345
1553
|
const DOM_EVENTS = {
|
|
1346
1554
|
onClick: ["click", false],
|
|
@@ -1359,10 +1567,15 @@ const DOM_EVENTS = {
|
|
|
1359
1567
|
onLostPointerCapture: ["lostpointercapture", true]
|
|
1360
1568
|
};
|
|
1361
1569
|
function createPointerEvents(store) {
|
|
1362
|
-
const { handlePointer } = createEvents(store);
|
|
1570
|
+
const { handlePointer, flushDeferredPointers, processDeferredPointer } = createEvents(store);
|
|
1571
|
+
let nextXRPointerId = XR_POINTER_ID_START;
|
|
1572
|
+
const xrPointers = /* @__PURE__ */ new Map();
|
|
1363
1573
|
return {
|
|
1364
1574
|
priority: 1,
|
|
1365
1575
|
enabled: true,
|
|
1576
|
+
frameTimedRaycasts: true,
|
|
1577
|
+
alwaysFireOnScroll: true,
|
|
1578
|
+
updateOnFrame: false,
|
|
1366
1579
|
compute(event, state) {
|
|
1367
1580
|
state.pointer.set(event.offsetX / state.size.width * 2 - 1, -(event.offsetY / state.size.height) * 2 + 1);
|
|
1368
1581
|
state.raycaster.setFromCamera(state.pointer, state.camera);
|
|
@@ -1372,11 +1585,33 @@ function createPointerEvents(store) {
|
|
|
1372
1585
|
(acc, key) => ({ ...acc, [key]: handlePointer(key) }),
|
|
1373
1586
|
{}
|
|
1374
1587
|
),
|
|
1375
|
-
update: () => {
|
|
1588
|
+
update: (pointerId) => {
|
|
1589
|
+
const { events, internal } = store.getState();
|
|
1590
|
+
if (!events.handlers) return;
|
|
1591
|
+
if (pointerId !== void 0) {
|
|
1592
|
+
const event = internal.pointerDirty.get(pointerId);
|
|
1593
|
+
if (event) {
|
|
1594
|
+
internal.pointerDirty.delete(pointerId);
|
|
1595
|
+
processDeferredPointer(event, pointerId);
|
|
1596
|
+
} else if (internal.lastEvent?.current) {
|
|
1597
|
+
processDeferredPointer(internal.lastEvent.current, pointerId);
|
|
1598
|
+
}
|
|
1599
|
+
} else {
|
|
1600
|
+
flushDeferredPointers();
|
|
1601
|
+
if (internal.lastEvent?.current) {
|
|
1602
|
+
events.handlers.onPointerMove(internal.lastEvent.current);
|
|
1603
|
+
}
|
|
1604
|
+
}
|
|
1605
|
+
},
|
|
1606
|
+
flush: () => {
|
|
1376
1607
|
const { events, internal } = store.getState();
|
|
1377
|
-
|
|
1608
|
+
flushDeferredPointers();
|
|
1609
|
+
if (events.updateOnFrame && internal.lastEvent?.current && events.handlers) {
|
|
1610
|
+
events.handlers.onPointerMove(internal.lastEvent.current);
|
|
1611
|
+
}
|
|
1378
1612
|
},
|
|
1379
1613
|
connect: (target) => {
|
|
1614
|
+
if (!target) return;
|
|
1380
1615
|
const { set, events } = store.getState();
|
|
1381
1616
|
events.disconnect?.();
|
|
1382
1617
|
set((state) => ({ events: { ...state.events, connected: target } }));
|
|
@@ -1400,6 +1635,33 @@ function createPointerEvents(store) {
|
|
|
1400
1635
|
}
|
|
1401
1636
|
set((state) => ({ events: { ...state.events, connected: void 0 } }));
|
|
1402
1637
|
}
|
|
1638
|
+
},
|
|
1639
|
+
// PREVIEW (A8): the XR pointer registration API may change before stable.
|
|
1640
|
+
registerPointer: (config) => {
|
|
1641
|
+
const pointerId = nextXRPointerId++;
|
|
1642
|
+
xrPointers.set(pointerId, config);
|
|
1643
|
+
const { internal } = store.getState();
|
|
1644
|
+
getPointerState(internal, pointerId);
|
|
1645
|
+
return pointerId;
|
|
1646
|
+
},
|
|
1647
|
+
unregisterPointer: (pointerId) => {
|
|
1648
|
+
xrPointers.delete(pointerId);
|
|
1649
|
+
const { internal } = store.getState();
|
|
1650
|
+
const pointerState = internal.pointerMap.get(pointerId);
|
|
1651
|
+
if (pointerState) {
|
|
1652
|
+
for (const [, hoveredObj] of pointerState.hovered) {
|
|
1653
|
+
const eventObject = hoveredObj.eventObject;
|
|
1654
|
+
const instance = eventObject.__r3f;
|
|
1655
|
+
if (instance?.eventCount) {
|
|
1656
|
+
const handlers = instance.handlers;
|
|
1657
|
+
const data = { ...hoveredObj, intersections: [] };
|
|
1658
|
+
handlers.onPointerOut?.(data);
|
|
1659
|
+
handlers.onPointerLeave?.(data);
|
|
1660
|
+
}
|
|
1661
|
+
}
|
|
1662
|
+
internal.pointerMap.delete(pointerId);
|
|
1663
|
+
}
|
|
1664
|
+
internal.pointerDirty.delete(pointerId);
|
|
1403
1665
|
}
|
|
1404
1666
|
};
|
|
1405
1667
|
}
|
|
@@ -1461,1058 +1723,33 @@ function notifyAlpha({ message, link }) {
|
|
|
1461
1723
|
}
|
|
1462
1724
|
}
|
|
1463
1725
|
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
const
|
|
1468
|
-
|
|
1469
|
-
constructor() {
|
|
1470
|
-
/** Ordered list of phase nodes */
|
|
1471
|
-
__publicField$2(this, "phases", []);
|
|
1472
|
-
/** Quick lookup by name */
|
|
1473
|
-
__publicField$2(this, "phaseMap", /* @__PURE__ */ new Map());
|
|
1474
|
-
/** Cached ordered names (invalidated on changes) */
|
|
1475
|
-
__publicField$2(this, "orderedNamesCache", null);
|
|
1476
|
-
this.initializeDefaultPhases();
|
|
1477
|
-
}
|
|
1478
|
-
//* Initialization --------------------------------
|
|
1479
|
-
initializeDefaultPhases() {
|
|
1480
|
-
for (const name of DEFAULT_PHASES) {
|
|
1481
|
-
const node = { name, isAutoGenerated: false };
|
|
1482
|
-
this.phases.push(node);
|
|
1483
|
-
this.phaseMap.set(name, node);
|
|
1484
|
-
}
|
|
1485
|
-
this.invalidateCache();
|
|
1486
|
-
}
|
|
1487
|
-
//* Public API --------------------------------
|
|
1488
|
-
/**
|
|
1489
|
-
* Add a named phase to the graph
|
|
1490
|
-
* @param name - Phase name (must be unique)
|
|
1491
|
-
* @param options - Position options (before or after another phase)
|
|
1492
|
-
*/
|
|
1493
|
-
addPhase(name, options = {}) {
|
|
1494
|
-
if (this.phaseMap.has(name)) {
|
|
1495
|
-
console.warn(`[useFrame] Phase "${name}" already exists`);
|
|
1496
|
-
return;
|
|
1497
|
-
}
|
|
1498
|
-
const { before, after } = options;
|
|
1499
|
-
const node = { name, isAutoGenerated: false };
|
|
1500
|
-
let insertIndex = this.phases.length;
|
|
1501
|
-
const targetIndex = this.getPhaseIndex(before ?? after);
|
|
1502
|
-
if (targetIndex !== -1) {
|
|
1503
|
-
insertIndex = before ? targetIndex : targetIndex + 1;
|
|
1504
|
-
} else {
|
|
1505
|
-
const constraintType = before ? "before" : "after";
|
|
1506
|
-
console.warn(`[useFrame] Phase "${before ?? after}" not found for '${constraintType}' constraint`);
|
|
1507
|
-
}
|
|
1508
|
-
this.phases.splice(insertIndex, 0, node);
|
|
1509
|
-
this.phaseMap.set(name, node);
|
|
1510
|
-
this.invalidateCache();
|
|
1511
|
-
}
|
|
1512
|
-
/**
|
|
1513
|
-
* Get ordered list of phase names
|
|
1514
|
-
*/
|
|
1515
|
-
getOrderedPhases() {
|
|
1516
|
-
if (this.orderedNamesCache === null) this.orderedNamesCache = this.phases.map((p) => p.name);
|
|
1517
|
-
return this.orderedNamesCache;
|
|
1518
|
-
}
|
|
1519
|
-
/**
|
|
1520
|
-
* Check if a phase exists
|
|
1521
|
-
*/
|
|
1522
|
-
hasPhase(name) {
|
|
1523
|
-
return this.phaseMap.has(name);
|
|
1524
|
-
}
|
|
1525
|
-
/**
|
|
1526
|
-
* Get the index of a phase (-1 if not found)
|
|
1527
|
-
*/
|
|
1528
|
-
getPhaseIndex(name) {
|
|
1529
|
-
if (!name) return -1;
|
|
1530
|
-
return this.phases.findIndex((p) => p.name === name);
|
|
1531
|
-
}
|
|
1532
|
-
/**
|
|
1533
|
-
* Ensure a phase exists, creating an auto-generated one if needed.
|
|
1534
|
-
* Used for resolving before/after constraints.
|
|
1535
|
-
*
|
|
1536
|
-
* @param name - The phase name to ensure exists
|
|
1537
|
-
* @returns The phase name (may be auto-generated like 'before:render')
|
|
1538
|
-
*/
|
|
1539
|
-
ensurePhase(name) {
|
|
1540
|
-
if (this.phaseMap.has(name)) return name;
|
|
1541
|
-
const node = { name, isAutoGenerated: true };
|
|
1542
|
-
this.phases.push(node);
|
|
1543
|
-
this.phaseMap.set(name, node);
|
|
1544
|
-
this.invalidateCache();
|
|
1545
|
-
return name;
|
|
1546
|
-
}
|
|
1547
|
-
/**
|
|
1548
|
-
* Resolve where a job with before/after constraints should go.
|
|
1549
|
-
* Creates auto-generated phases if needed.
|
|
1550
|
-
*
|
|
1551
|
-
* @param before - Phase(s) to run before
|
|
1552
|
-
* @param after - Phase(s) to run after
|
|
1553
|
-
* @returns The resolved phase name
|
|
1554
|
-
*/
|
|
1555
|
-
resolveConstraintPhase(before, after) {
|
|
1556
|
-
const beforeArr = before ? Array.isArray(before) ? before : [before] : [];
|
|
1557
|
-
const afterArr = after ? Array.isArray(after) ? after : [after] : [];
|
|
1558
|
-
if (beforeArr.length > 0) {
|
|
1559
|
-
return this.ensureAutoPhase(beforeArr[0], "before", 0);
|
|
1560
|
-
}
|
|
1561
|
-
if (afterArr.length > 0) {
|
|
1562
|
-
return this.ensureAutoPhase(afterArr[0], "after", 1);
|
|
1563
|
-
}
|
|
1564
|
-
return "update";
|
|
1565
|
-
}
|
|
1566
|
-
/**
|
|
1567
|
-
* Ensure an auto-generated phase exists relative to a target phase.
|
|
1568
|
-
* Creates the phase if it doesn't exist, inserting it at the correct position.
|
|
1569
|
-
*
|
|
1570
|
-
* @param target - The target phase name to position relative to
|
|
1571
|
-
* @param prefix - Prefix for auto-generated phase name ('before' or 'after')
|
|
1572
|
-
* @param offset - Insertion offset (0 for before, 1 for after)
|
|
1573
|
-
* @returns The auto-generated phase name
|
|
1574
|
-
*/
|
|
1575
|
-
ensureAutoPhase(target, prefix, offset) {
|
|
1576
|
-
const autoName = `${prefix}:${target}`;
|
|
1577
|
-
if (this.phaseMap.has(autoName)) return autoName;
|
|
1578
|
-
const node = { name: autoName, isAutoGenerated: true };
|
|
1579
|
-
const targetIndex = this.getPhaseIndex(target);
|
|
1580
|
-
if (targetIndex !== -1) this.phases.splice(targetIndex + offset, 0, node);
|
|
1581
|
-
else this.phases.push(node);
|
|
1582
|
-
this.phaseMap.set(autoName, node);
|
|
1583
|
-
this.invalidateCache();
|
|
1584
|
-
return autoName;
|
|
1585
|
-
}
|
|
1586
|
-
// Internal --------------------------------
|
|
1587
|
-
invalidateCache() {
|
|
1588
|
-
this.orderedNamesCache = null;
|
|
1589
|
-
}
|
|
1590
|
-
}
|
|
1591
|
-
|
|
1592
|
-
function rebuildSortedJobs(jobs, phaseGraph) {
|
|
1593
|
-
const orderedPhases = phaseGraph.getOrderedPhases();
|
|
1594
|
-
const buckets = /* @__PURE__ */ new Map();
|
|
1595
|
-
for (const phase of orderedPhases) {
|
|
1596
|
-
buckets.set(phase, []);
|
|
1597
|
-
}
|
|
1598
|
-
for (const job of jobs.values()) {
|
|
1599
|
-
if (!job.enabled) continue;
|
|
1600
|
-
let bucket = buckets.get(job.phase);
|
|
1601
|
-
if (!bucket) {
|
|
1602
|
-
bucket = [];
|
|
1603
|
-
buckets.set(job.phase, bucket);
|
|
1604
|
-
}
|
|
1605
|
-
bucket.push(job);
|
|
1606
|
-
}
|
|
1607
|
-
const sortedBuckets = [];
|
|
1608
|
-
for (const phase of orderedPhases) {
|
|
1609
|
-
const bucket = buckets.get(phase);
|
|
1610
|
-
if (!bucket || bucket.length === 0) continue;
|
|
1611
|
-
bucket.sort((a, b) => {
|
|
1612
|
-
if (a.priority !== b.priority) return b.priority - a.priority;
|
|
1613
|
-
return a.index - b.index;
|
|
1614
|
-
});
|
|
1615
|
-
sortedBuckets.push(hasCrossJobConstraints(bucket) ? topologicalSort(bucket) : bucket);
|
|
1616
|
-
}
|
|
1617
|
-
for (const [phase, bucket] of buckets) {
|
|
1618
|
-
if (!orderedPhases.includes(phase) && bucket.length > 0) {
|
|
1619
|
-
bucket.sort((a, b) => {
|
|
1620
|
-
if (a.priority !== b.priority) return b.priority - a.priority;
|
|
1621
|
-
return a.index - b.index;
|
|
1622
|
-
});
|
|
1623
|
-
sortedBuckets.push(bucket);
|
|
1624
|
-
}
|
|
1625
|
-
}
|
|
1626
|
-
return sortedBuckets.flat();
|
|
1627
|
-
}
|
|
1628
|
-
function hasCrossJobConstraints(bucket) {
|
|
1629
|
-
const jobIds = new Set(bucket.map((j) => j.id));
|
|
1630
|
-
for (const job of bucket) {
|
|
1631
|
-
for (const ref of job.before) {
|
|
1632
|
-
if (jobIds.has(ref)) return true;
|
|
1633
|
-
}
|
|
1634
|
-
for (const ref of job.after) {
|
|
1635
|
-
if (jobIds.has(ref)) return true;
|
|
1636
|
-
}
|
|
1637
|
-
}
|
|
1638
|
-
return false;
|
|
1639
|
-
}
|
|
1640
|
-
function topologicalSort(jobs) {
|
|
1641
|
-
const n = jobs.length;
|
|
1642
|
-
if (n <= 1) return jobs;
|
|
1643
|
-
const jobMap = /* @__PURE__ */ new Map();
|
|
1644
|
-
const inDegree = /* @__PURE__ */ new Map();
|
|
1645
|
-
const adjacency = /* @__PURE__ */ new Map();
|
|
1646
|
-
for (const job of jobs) {
|
|
1647
|
-
jobMap.set(job.id, job);
|
|
1648
|
-
inDegree.set(job.id, 0);
|
|
1649
|
-
adjacency.set(job.id, []);
|
|
1650
|
-
}
|
|
1651
|
-
for (const job of jobs) {
|
|
1652
|
-
for (const ref of job.before) {
|
|
1653
|
-
if (jobMap.has(ref)) {
|
|
1654
|
-
adjacency.get(job.id).push(ref);
|
|
1655
|
-
inDegree.set(ref, inDegree.get(ref) + 1);
|
|
1656
|
-
}
|
|
1657
|
-
}
|
|
1658
|
-
for (const ref of job.after) {
|
|
1659
|
-
if (jobMap.has(ref)) {
|
|
1660
|
-
adjacency.get(ref).push(job.id);
|
|
1661
|
-
inDegree.set(job.id, inDegree.get(job.id) + 1);
|
|
1662
|
-
}
|
|
1663
|
-
}
|
|
1664
|
-
}
|
|
1665
|
-
const queue = [];
|
|
1666
|
-
for (const job of jobs) {
|
|
1667
|
-
if (inDegree.get(job.id) === 0) {
|
|
1668
|
-
queue.push(job);
|
|
1669
|
-
}
|
|
1670
|
-
}
|
|
1671
|
-
queue.sort((a, b) => {
|
|
1672
|
-
if (a.priority !== b.priority) return b.priority - a.priority;
|
|
1673
|
-
return a.index - b.index;
|
|
1674
|
-
});
|
|
1675
|
-
const result = [];
|
|
1676
|
-
while (queue.length > 0) {
|
|
1677
|
-
const job = queue.shift();
|
|
1678
|
-
result.push(job);
|
|
1679
|
-
const neighbors = adjacency.get(job.id) || [];
|
|
1680
|
-
for (const neighborId of neighbors) {
|
|
1681
|
-
const newDegree = inDegree.get(neighborId) - 1;
|
|
1682
|
-
inDegree.set(neighborId, newDegree);
|
|
1683
|
-
if (newDegree === 0) {
|
|
1684
|
-
const neighbor = jobMap.get(neighborId);
|
|
1685
|
-
insertSorted(queue, neighbor);
|
|
1686
|
-
}
|
|
1687
|
-
}
|
|
1688
|
-
}
|
|
1689
|
-
if (result.length !== n) {
|
|
1690
|
-
console.warn("[useFrame] Circular dependency detected in job constraints");
|
|
1691
|
-
const resultIds = new Set(result.map((j) => j.id));
|
|
1692
|
-
for (const job of jobs) {
|
|
1693
|
-
if (!resultIds.has(job.id)) result.push(job);
|
|
1694
|
-
}
|
|
1695
|
-
}
|
|
1696
|
-
return result;
|
|
1697
|
-
}
|
|
1698
|
-
function insertSorted(arr, job) {
|
|
1699
|
-
let i = 0;
|
|
1700
|
-
while (i < arr.length) {
|
|
1701
|
-
const cmp = arr[i];
|
|
1702
|
-
if (job.priority > cmp.priority || job.priority === cmp.priority && job.index < cmp.index) {
|
|
1703
|
-
break;
|
|
1704
|
-
}
|
|
1705
|
-
i++;
|
|
1706
|
-
}
|
|
1707
|
-
arr.splice(i, 0, job);
|
|
1708
|
-
}
|
|
1709
|
-
|
|
1710
|
-
function shouldRun(job, now) {
|
|
1711
|
-
if (!job.enabled) return false;
|
|
1712
|
-
if (!job.fps) return true;
|
|
1713
|
-
const minInterval = 1e3 / job.fps;
|
|
1714
|
-
const lastRun = job.lastRun ?? 0;
|
|
1715
|
-
const elapsed = now - lastRun;
|
|
1716
|
-
if (elapsed < minInterval) return false;
|
|
1717
|
-
if (job.drop) {
|
|
1718
|
-
job.lastRun = now;
|
|
1719
|
-
} else {
|
|
1720
|
-
const steps = Math.floor(elapsed / minInterval);
|
|
1721
|
-
job.lastRun = lastRun + steps * minInterval;
|
|
1722
|
-
if (job.lastRun < now - minInterval) {
|
|
1723
|
-
job.lastRun = now;
|
|
1724
|
-
}
|
|
1725
|
-
}
|
|
1726
|
-
return true;
|
|
1727
|
-
}
|
|
1728
|
-
function resetJobTiming(job) {
|
|
1729
|
-
job.lastRun = void 0;
|
|
1726
|
+
const INTERNAL_LIB_MARKERS = ["zustand", "setState", "Object.assign"];
|
|
1727
|
+
function isInternalRendererAccess(stack, callerDepth = 2) {
|
|
1728
|
+
if (!stack) return false;
|
|
1729
|
+
const caller = stack.split("\n")[callerDepth] ?? "";
|
|
1730
|
+
return INTERNAL_LIB_MARKERS.some((m) => caller.includes(m));
|
|
1730
1731
|
}
|
|
1731
1732
|
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
const
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
const
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
rafHandle: null,
|
|
1753
|
-
lastTime: null,
|
|
1754
|
-
// null = uninitialized, 0+ = valid timestamp
|
|
1755
|
-
frameCount: 0,
|
|
1756
|
-
elapsedTime: 0,
|
|
1757
|
-
createdAt: performance.now()
|
|
1758
|
-
});
|
|
1759
|
-
__publicField$1(this, "stoppedTime", 0);
|
|
1760
|
-
//* Private State ================================
|
|
1761
|
-
__publicField$1(this, "nextRootIndex", 0);
|
|
1762
|
-
__publicField$1(this, "globalBeforeJobs", /* @__PURE__ */ new Map());
|
|
1763
|
-
__publicField$1(this, "globalAfterJobs", /* @__PURE__ */ new Map());
|
|
1764
|
-
__publicField$1(this, "nextGlobalIndex", 0);
|
|
1765
|
-
__publicField$1(this, "idleCallbacks", /* @__PURE__ */ new Set());
|
|
1766
|
-
__publicField$1(this, "nextJobIndex", 0);
|
|
1767
|
-
__publicField$1(this, "jobStateListeners", /* @__PURE__ */ new Map());
|
|
1768
|
-
__publicField$1(this, "pendingFrames", 0);
|
|
1769
|
-
__publicField$1(this, "_frameloop", "always");
|
|
1770
|
-
//* Independent Mode & Error Handling State ================================
|
|
1771
|
-
__publicField$1(this, "_independent", false);
|
|
1772
|
-
__publicField$1(this, "errorHandler", null);
|
|
1773
|
-
__publicField$1(this, "rootReadyCallbacks", /* @__PURE__ */ new Set());
|
|
1774
|
-
//* Core Loop Execution Methods ================================
|
|
1775
|
-
/**
|
|
1776
|
-
* Main RAF loop callback.
|
|
1777
|
-
* Executes frame, handles demand mode, and schedules next frame.
|
|
1778
|
-
* @param {number} timestamp - RAF timestamp in milliseconds
|
|
1779
|
-
* @returns {void}
|
|
1780
|
-
* @private
|
|
1781
|
-
*/
|
|
1782
|
-
__publicField$1(this, "loop", (timestamp) => {
|
|
1783
|
-
if (!this.loopState.running) return;
|
|
1784
|
-
this.executeFrame(timestamp);
|
|
1785
|
-
if (this._frameloop === "demand") {
|
|
1786
|
-
this.pendingFrames = Math.max(0, this.pendingFrames - 1);
|
|
1787
|
-
if (this.pendingFrames === 0) {
|
|
1788
|
-
this.notifyIdle(timestamp);
|
|
1789
|
-
return this.stop();
|
|
1790
|
-
}
|
|
1791
|
-
}
|
|
1792
|
-
this.loopState.rafHandle = requestAnimationFrame(this.loop);
|
|
1793
|
-
});
|
|
1794
|
-
this.phaseGraph = new PhaseGraph();
|
|
1795
|
-
}
|
|
1796
|
-
static get instance() {
|
|
1797
|
-
return globalThis[_Scheduler.INSTANCE_KEY] ?? null;
|
|
1798
|
-
}
|
|
1799
|
-
static set instance(value) {
|
|
1800
|
-
globalThis[_Scheduler.INSTANCE_KEY] = value;
|
|
1801
|
-
}
|
|
1802
|
-
/**
|
|
1803
|
-
* Get the global scheduler instance (creates if doesn't exist).
|
|
1804
|
-
* Uses HMR data to preserve instance across hot reloads.
|
|
1805
|
-
* @returns {Scheduler} The singleton scheduler instance
|
|
1806
|
-
*/
|
|
1807
|
-
static get() {
|
|
1808
|
-
if (!_Scheduler.instance && hmrData?.data?.scheduler) {
|
|
1809
|
-
_Scheduler.instance = hmrData.data.scheduler;
|
|
1810
|
-
}
|
|
1811
|
-
if (!_Scheduler.instance) {
|
|
1812
|
-
_Scheduler.instance = new _Scheduler();
|
|
1813
|
-
if (hmrData?.data) {
|
|
1814
|
-
hmrData.data.scheduler = _Scheduler.instance;
|
|
1815
|
-
}
|
|
1816
|
-
}
|
|
1817
|
-
return _Scheduler.instance;
|
|
1818
|
-
}
|
|
1819
|
-
/**
|
|
1820
|
-
* Reset the singleton instance. Stops the loop and clears all state.
|
|
1821
|
-
* Primarily used for testing to ensure clean state between tests.
|
|
1822
|
-
* @returns {void}
|
|
1823
|
-
*/
|
|
1824
|
-
static reset() {
|
|
1825
|
-
if (_Scheduler.instance) {
|
|
1826
|
-
_Scheduler.instance.stop();
|
|
1827
|
-
_Scheduler.instance = null;
|
|
1828
|
-
}
|
|
1829
|
-
if (hmrData?.data) {
|
|
1830
|
-
hmrData.data.scheduler = null;
|
|
1831
|
-
}
|
|
1832
|
-
}
|
|
1833
|
-
//* Getters & Setters ================================
|
|
1834
|
-
get phases() {
|
|
1835
|
-
return this.phaseGraph.getOrderedPhases();
|
|
1836
|
-
}
|
|
1837
|
-
get frameloop() {
|
|
1838
|
-
return this._frameloop;
|
|
1839
|
-
}
|
|
1840
|
-
set frameloop(mode) {
|
|
1841
|
-
if (this._frameloop === mode) return;
|
|
1842
|
-
const wasAlways = this._frameloop === "always";
|
|
1843
|
-
this._frameloop = mode;
|
|
1844
|
-
if (mode === "always" && !this.loopState.running && this.roots.size > 0) this.start();
|
|
1845
|
-
else if (mode !== "always" && wasAlways) this.stop();
|
|
1846
|
-
}
|
|
1847
|
-
get isRunning() {
|
|
1848
|
-
return this.loopState.running;
|
|
1849
|
-
}
|
|
1850
|
-
get isReady() {
|
|
1851
|
-
return this.roots.size > 0;
|
|
1852
|
-
}
|
|
1853
|
-
get independent() {
|
|
1854
|
-
return this._independent;
|
|
1855
|
-
}
|
|
1856
|
-
set independent(value) {
|
|
1857
|
-
this._independent = value;
|
|
1858
|
-
if (value) this.ensureDefaultRoot();
|
|
1859
|
-
}
|
|
1860
|
-
//* Root Management Methods ================================
|
|
1861
|
-
/**
|
|
1862
|
-
* Register a root (Canvas) with the scheduler.
|
|
1863
|
-
* The first root to register starts the RAF loop (if frameloop='always').
|
|
1864
|
-
* @param {string} id - Unique identifier for this root
|
|
1865
|
-
* @param {RootOptions} [options] - Optional configuration with getState and onError callbacks
|
|
1866
|
-
* @returns {() => void} Unsubscribe function to remove this root
|
|
1867
|
-
*/
|
|
1868
|
-
registerRoot(id, options = {}) {
|
|
1869
|
-
if (this.roots.has(id)) {
|
|
1870
|
-
console.warn(`[Scheduler] Root "${id}" already registered`);
|
|
1871
|
-
return () => this.unregisterRoot(id);
|
|
1872
|
-
}
|
|
1873
|
-
const entry = {
|
|
1874
|
-
id,
|
|
1875
|
-
getState: options.getState ?? (() => ({})),
|
|
1876
|
-
jobs: /* @__PURE__ */ new Map(),
|
|
1877
|
-
sortedJobs: [],
|
|
1878
|
-
needsRebuild: false
|
|
1879
|
-
};
|
|
1880
|
-
if (options.onError) {
|
|
1881
|
-
this.errorHandler = options.onError;
|
|
1882
|
-
}
|
|
1883
|
-
this.roots.set(id, entry);
|
|
1884
|
-
if (this.roots.size === 1) {
|
|
1885
|
-
this.notifyRootReady();
|
|
1886
|
-
if (this._frameloop === "always") this.start();
|
|
1887
|
-
}
|
|
1888
|
-
return () => this.unregisterRoot(id);
|
|
1889
|
-
}
|
|
1890
|
-
/**
|
|
1891
|
-
* Unregister a root from the scheduler.
|
|
1892
|
-
* Cleans up all job state listeners for this root's jobs.
|
|
1893
|
-
* The last root to unregister stops the RAF loop.
|
|
1894
|
-
* @param {string} id - The root ID to unregister
|
|
1895
|
-
* @returns {void}
|
|
1896
|
-
*/
|
|
1897
|
-
unregisterRoot(id) {
|
|
1898
|
-
const root = this.roots.get(id);
|
|
1899
|
-
if (!root) return;
|
|
1900
|
-
for (const jobId of root.jobs.keys()) {
|
|
1901
|
-
this.jobStateListeners.delete(jobId);
|
|
1902
|
-
}
|
|
1903
|
-
this.roots.delete(id);
|
|
1904
|
-
if (this.roots.size === 0) {
|
|
1905
|
-
this.stop();
|
|
1906
|
-
this.errorHandler = null;
|
|
1907
|
-
}
|
|
1908
|
-
}
|
|
1909
|
-
/**
|
|
1910
|
-
* Subscribe to be notified when a root becomes available.
|
|
1911
|
-
* Fires immediately if a root already exists.
|
|
1912
|
-
* @param {() => void} callback - Function called when first root registers
|
|
1913
|
-
* @returns {() => void} Unsubscribe function
|
|
1914
|
-
*/
|
|
1915
|
-
onRootReady(callback) {
|
|
1916
|
-
if (this.roots.size > 0) {
|
|
1917
|
-
callback();
|
|
1918
|
-
return () => {
|
|
1919
|
-
};
|
|
1920
|
-
}
|
|
1921
|
-
this.rootReadyCallbacks.add(callback);
|
|
1922
|
-
return () => this.rootReadyCallbacks.delete(callback);
|
|
1923
|
-
}
|
|
1924
|
-
/**
|
|
1925
|
-
* Notify all registered root-ready callbacks.
|
|
1926
|
-
* Called when the first root registers.
|
|
1927
|
-
* @returns {void}
|
|
1928
|
-
* @private
|
|
1929
|
-
*/
|
|
1930
|
-
notifyRootReady() {
|
|
1931
|
-
for (const cb of this.rootReadyCallbacks) {
|
|
1932
|
-
try {
|
|
1933
|
-
cb();
|
|
1934
|
-
} catch (error) {
|
|
1935
|
-
console.error("[Scheduler] Error in root-ready callback:", error);
|
|
1936
|
-
}
|
|
1937
|
-
}
|
|
1938
|
-
this.rootReadyCallbacks.clear();
|
|
1939
|
-
}
|
|
1940
|
-
/**
|
|
1941
|
-
* Ensure a default root exists for independent mode.
|
|
1942
|
-
* Creates a minimal root with no state provider.
|
|
1943
|
-
* @returns {void}
|
|
1944
|
-
* @private
|
|
1945
|
-
*/
|
|
1946
|
-
ensureDefaultRoot() {
|
|
1947
|
-
if (!this.roots.has("__default__")) {
|
|
1948
|
-
this.registerRoot("__default__");
|
|
1949
|
-
}
|
|
1950
|
-
}
|
|
1951
|
-
/**
|
|
1952
|
-
* Trigger error handling for job errors.
|
|
1953
|
-
* Uses the bound error handler if available, otherwise logs to console.
|
|
1954
|
-
* @param {Error} error - The error to handle
|
|
1955
|
-
* @returns {void}
|
|
1956
|
-
*/
|
|
1957
|
-
triggerError(error) {
|
|
1958
|
-
if (this.errorHandler) this.errorHandler(error);
|
|
1959
|
-
else console.error("[Scheduler]", error);
|
|
1960
|
-
}
|
|
1961
|
-
//* Phase Management Methods ================================
|
|
1962
|
-
/**
|
|
1963
|
-
* Add a named phase to the scheduler's execution order.
|
|
1964
|
-
* Marks all roots for rebuild to incorporate the new phase.
|
|
1965
|
-
* @param {string} name - The phase name (e.g., 'physics', 'postprocess')
|
|
1966
|
-
* @param {AddPhaseOptions} [options] - Positioning options (before/after other phases)
|
|
1967
|
-
* @returns {void}
|
|
1968
|
-
* @example
|
|
1969
|
-
* scheduler.addPhase('physics', { before: 'update' });
|
|
1970
|
-
* scheduler.addPhase('postprocess', { after: 'render' });
|
|
1971
|
-
*/
|
|
1972
|
-
addPhase(name, options) {
|
|
1973
|
-
this.phaseGraph.addPhase(name, options);
|
|
1974
|
-
for (const root of this.roots.values()) {
|
|
1975
|
-
root.needsRebuild = true;
|
|
1976
|
-
}
|
|
1977
|
-
}
|
|
1978
|
-
/**
|
|
1979
|
-
* Check if a phase exists in the scheduler.
|
|
1980
|
-
* @param {string} name - The phase name to check
|
|
1981
|
-
* @returns {boolean} True if the phase exists
|
|
1982
|
-
*/
|
|
1983
|
-
hasPhase(name) {
|
|
1984
|
-
return this.phaseGraph.hasPhase(name);
|
|
1985
|
-
}
|
|
1986
|
-
//* Global Job Registration Methods (Deprecated APIs) ================================
|
|
1987
|
-
/**
|
|
1988
|
-
* Register a global job that runs once per frame (not per-root).
|
|
1989
|
-
* Used internally by deprecated addEffect/addAfterEffect APIs.
|
|
1990
|
-
* @param {'before' | 'after'} phase - When to run: 'before' all roots or 'after' all roots
|
|
1991
|
-
* @param {string} id - Unique identifier for this global job
|
|
1992
|
-
* @param {(timestamp: number) => void} callback - Function called each frame with RAF timestamp
|
|
1993
|
-
* @returns {() => void} Unsubscribe function to remove this global job
|
|
1994
|
-
* @deprecated Use useFrame with phases instead
|
|
1995
|
-
*/
|
|
1996
|
-
registerGlobal(phase, id, callback) {
|
|
1997
|
-
const job = { id, callback };
|
|
1998
|
-
if (phase === "before") {
|
|
1999
|
-
this.globalBeforeJobs.set(id, job);
|
|
2000
|
-
} else {
|
|
2001
|
-
this.globalAfterJobs.set(id, job);
|
|
2002
|
-
}
|
|
2003
|
-
return () => {
|
|
2004
|
-
if (phase === "before") this.globalBeforeJobs.delete(id);
|
|
2005
|
-
else this.globalAfterJobs.delete(id);
|
|
2006
|
-
};
|
|
2007
|
-
}
|
|
2008
|
-
//* Idle Callback Methods (Deprecated API) ================================
|
|
2009
|
-
/**
|
|
2010
|
-
* Register an idle callback that fires when the loop stops.
|
|
2011
|
-
* Used internally by deprecated addTail API.
|
|
2012
|
-
* @param {(timestamp: number) => void} callback - Function called when loop becomes idle
|
|
2013
|
-
* @returns {() => void} Unsubscribe function to remove this idle callback
|
|
2014
|
-
* @deprecated Use demand mode with invalidate() instead
|
|
2015
|
-
*/
|
|
2016
|
-
onIdle(callback) {
|
|
2017
|
-
this.idleCallbacks.add(callback);
|
|
2018
|
-
return () => this.idleCallbacks.delete(callback);
|
|
2019
|
-
}
|
|
2020
|
-
/**
|
|
2021
|
-
* Notify all registered idle callbacks.
|
|
2022
|
-
* Called when the loop stops in demand mode.
|
|
2023
|
-
* @param {number} timestamp - The RAF timestamp when idle occurred
|
|
2024
|
-
* @returns {void}
|
|
2025
|
-
* @private
|
|
2026
|
-
*/
|
|
2027
|
-
notifyIdle(timestamp) {
|
|
2028
|
-
for (const cb of this.idleCallbacks) {
|
|
2029
|
-
try {
|
|
2030
|
-
cb(timestamp);
|
|
2031
|
-
} catch (error) {
|
|
2032
|
-
console.error("[Scheduler] Error in idle callback:", error);
|
|
2033
|
-
}
|
|
2034
|
-
}
|
|
2035
|
-
}
|
|
2036
|
-
//* Job Registration & Management Methods ================================
|
|
2037
|
-
/**
|
|
2038
|
-
* Register a job (frame callback) with a specific root.
|
|
2039
|
-
* This is the core registration method used by useFrame internally.
|
|
2040
|
-
* @param {FrameNextCallback} callback - The function to call each frame
|
|
2041
|
-
* @param {JobOptions & { rootId?: string; system?: boolean }} [options] - Job configuration
|
|
2042
|
-
* @param {string} [options.rootId] - Target root ID (defaults to first registered root)
|
|
2043
|
-
* @param {string} [options.id] - Unique job ID (auto-generated if not provided)
|
|
2044
|
-
* @param {string} [options.phase] - Execution phase (defaults to 'update')
|
|
2045
|
-
* @param {number} [options.priority] - Priority within phase (higher = earlier, default 0)
|
|
2046
|
-
* @param {number} [options.fps] - FPS throttle limit
|
|
2047
|
-
* @param {boolean} [options.drop] - Drop frames when behind (default true)
|
|
2048
|
-
* @param {boolean} [options.enabled] - Whether job is active (default true)
|
|
2049
|
-
* @param {boolean} [options.system] - Internal flag for system jobs (not user-facing)
|
|
2050
|
-
* @returns {() => void} Unsubscribe function to remove this job
|
|
2051
|
-
*/
|
|
2052
|
-
register(callback, options = {}) {
|
|
2053
|
-
const rootId = options.rootId;
|
|
2054
|
-
const root = rootId ? this.roots.get(rootId) : this.roots.values().next().value;
|
|
2055
|
-
if (!root) {
|
|
2056
|
-
console.warn("[Scheduler] No root registered. Is this inside a Canvas?");
|
|
2057
|
-
return () => {
|
|
2058
|
-
};
|
|
2059
|
-
}
|
|
2060
|
-
const id = options.id ?? this.generateJobId();
|
|
2061
|
-
let phase = options.phase ?? "update";
|
|
2062
|
-
if (!options.phase && (options.before || options.after)) {
|
|
2063
|
-
phase = this.phaseGraph.resolveConstraintPhase(options.before, options.after);
|
|
2064
|
-
}
|
|
2065
|
-
const before = this.normalizeConstraints(options.before);
|
|
2066
|
-
const after = this.normalizeConstraints(options.after);
|
|
2067
|
-
const job = {
|
|
2068
|
-
id,
|
|
2069
|
-
callback,
|
|
2070
|
-
phase,
|
|
2071
|
-
before,
|
|
2072
|
-
after,
|
|
2073
|
-
priority: options.priority ?? 0,
|
|
2074
|
-
index: this.nextJobIndex++,
|
|
2075
|
-
fps: options.fps,
|
|
2076
|
-
drop: options.drop ?? true,
|
|
2077
|
-
enabled: options.enabled ?? true,
|
|
2078
|
-
system: options.system ?? false
|
|
2079
|
-
};
|
|
2080
|
-
if (root.jobs.has(id)) {
|
|
2081
|
-
console.warn(`[useFrame] Job with id "${id}" already exists, replacing`);
|
|
2082
|
-
}
|
|
2083
|
-
root.jobs.set(id, job);
|
|
2084
|
-
root.needsRebuild = true;
|
|
2085
|
-
return () => this.unregister(id, root.id);
|
|
2086
|
-
}
|
|
2087
|
-
/**
|
|
2088
|
-
* Unregister a job by its ID.
|
|
2089
|
-
* Searches all roots if rootId is not provided.
|
|
2090
|
-
* @param {string} id - The job ID to unregister
|
|
2091
|
-
* @param {string} [rootId] - Optional root ID to search (searches all if not provided)
|
|
2092
|
-
* @returns {void}
|
|
2093
|
-
*/
|
|
2094
|
-
unregister(id, rootId) {
|
|
2095
|
-
const root = rootId ? this.roots.get(rootId) : Array.from(this.roots.values()).find((r) => r.jobs.has(id));
|
|
2096
|
-
if (root?.jobs.delete(id)) {
|
|
2097
|
-
root.needsRebuild = true;
|
|
2098
|
-
this.jobStateListeners.delete(id);
|
|
2099
|
-
}
|
|
2100
|
-
}
|
|
2101
|
-
/**
|
|
2102
|
-
* Update a job's options dynamically.
|
|
2103
|
-
* Searches all roots to find the job by ID.
|
|
2104
|
-
* Phase/constraint changes trigger a rebuild of the sorted job list.
|
|
2105
|
-
* @param {string} id - The job ID to update
|
|
2106
|
-
* @param {Partial<JobOptions>} options - The options to update
|
|
2107
|
-
* @returns {void}
|
|
2108
|
-
*/
|
|
2109
|
-
updateJob(id, options) {
|
|
2110
|
-
let job;
|
|
2111
|
-
let root;
|
|
2112
|
-
for (const r of this.roots.values()) {
|
|
2113
|
-
job = r.jobs.get(id);
|
|
2114
|
-
if (job) {
|
|
2115
|
-
root = r;
|
|
2116
|
-
break;
|
|
2117
|
-
}
|
|
2118
|
-
}
|
|
2119
|
-
if (!job || !root) return;
|
|
2120
|
-
if (options.priority !== void 0) job.priority = options.priority;
|
|
2121
|
-
if (options.fps !== void 0) job.fps = options.fps;
|
|
2122
|
-
if (options.drop !== void 0) job.drop = options.drop;
|
|
2123
|
-
if (options.enabled !== void 0) {
|
|
2124
|
-
const wasEnabled = job.enabled;
|
|
2125
|
-
job.enabled = options.enabled;
|
|
2126
|
-
if (!wasEnabled && job.enabled) resetJobTiming(job);
|
|
2127
|
-
if (wasEnabled !== job.enabled) root.needsRebuild = true;
|
|
2128
|
-
}
|
|
2129
|
-
if (options.phase !== void 0 || options.before !== void 0 || options.after !== void 0) {
|
|
2130
|
-
if (options.phase) job.phase = options.phase;
|
|
2131
|
-
if (options.before !== void 0) job.before = this.normalizeConstraints(options.before);
|
|
2132
|
-
if (options.after !== void 0) job.after = this.normalizeConstraints(options.after);
|
|
2133
|
-
root.needsRebuild = true;
|
|
2134
|
-
}
|
|
2135
|
-
}
|
|
2136
|
-
//* Job State Management Methods ================================
|
|
2137
|
-
/**
|
|
2138
|
-
* Check if a job is currently paused (disabled).
|
|
2139
|
-
* @param {string} id - The job ID to check
|
|
2140
|
-
* @returns {boolean} True if the job exists and is paused
|
|
2141
|
-
*/
|
|
2142
|
-
isJobPaused(id) {
|
|
2143
|
-
for (const root of this.roots.values()) {
|
|
2144
|
-
const job = root.jobs.get(id);
|
|
2145
|
-
if (job) return !job.enabled;
|
|
2146
|
-
}
|
|
2147
|
-
return false;
|
|
2148
|
-
}
|
|
2149
|
-
/**
|
|
2150
|
-
* Subscribe to state changes for a specific job.
|
|
2151
|
-
* Listener is called when job is paused or resumed.
|
|
2152
|
-
* @param {string} id - The job ID to subscribe to
|
|
2153
|
-
* @param {() => void} listener - Callback invoked on state changes
|
|
2154
|
-
* @returns {() => void} Unsubscribe function
|
|
2155
|
-
*/
|
|
2156
|
-
subscribeJobState(id, listener) {
|
|
2157
|
-
if (!this.jobStateListeners.has(id)) {
|
|
2158
|
-
this.jobStateListeners.set(id, /* @__PURE__ */ new Set());
|
|
2159
|
-
}
|
|
2160
|
-
this.jobStateListeners.get(id).add(listener);
|
|
2161
|
-
return () => {
|
|
2162
|
-
this.jobStateListeners.get(id)?.delete(listener);
|
|
2163
|
-
if (this.jobStateListeners.get(id)?.size === 0) {
|
|
2164
|
-
this.jobStateListeners.delete(id);
|
|
2165
|
-
}
|
|
2166
|
-
};
|
|
2167
|
-
}
|
|
2168
|
-
/**
|
|
2169
|
-
* Notify all listeners that a job's state has changed.
|
|
2170
|
-
* @param {string} id - The job ID that changed
|
|
2171
|
-
* @returns {void}
|
|
2172
|
-
* @private
|
|
2173
|
-
*/
|
|
2174
|
-
notifyJobStateChange(id) {
|
|
2175
|
-
this.jobStateListeners.get(id)?.forEach((listener) => listener());
|
|
2176
|
-
}
|
|
2177
|
-
/**
|
|
2178
|
-
* Pause a job by ID (sets enabled=false).
|
|
2179
|
-
* Notifies any subscribed state listeners.
|
|
2180
|
-
* @param {string} id - The job ID to pause
|
|
2181
|
-
* @returns {void}
|
|
2182
|
-
*/
|
|
2183
|
-
pauseJob(id) {
|
|
2184
|
-
this.updateJob(id, { enabled: false });
|
|
2185
|
-
this.notifyJobStateChange(id);
|
|
2186
|
-
}
|
|
2187
|
-
/**
|
|
2188
|
-
* Resume a paused job by ID (sets enabled=true).
|
|
2189
|
-
* Resets job timing to prevent frame accumulation.
|
|
2190
|
-
* Notifies any subscribed state listeners.
|
|
2191
|
-
* @param {string} id - The job ID to resume
|
|
2192
|
-
* @returns {void}
|
|
2193
|
-
*/
|
|
2194
|
-
resumeJob(id) {
|
|
2195
|
-
this.updateJob(id, { enabled: true });
|
|
2196
|
-
this.notifyJobStateChange(id);
|
|
2197
|
-
}
|
|
2198
|
-
//* Frame Loop Control Methods ================================
|
|
2199
|
-
/**
|
|
2200
|
-
* Start the requestAnimationFrame loop.
|
|
2201
|
-
* Resets timing state (elapsedTime, frameCount) on start.
|
|
2202
|
-
* No-op if already running.
|
|
2203
|
-
* @returns {void}
|
|
2204
|
-
*/
|
|
2205
|
-
start() {
|
|
2206
|
-
if (this.loopState.running) return;
|
|
2207
|
-
const { elapsedTime, createdAt } = this.loopState;
|
|
2208
|
-
let adjustedCreated = 0;
|
|
2209
|
-
if (this.stoppedTime > 0) {
|
|
2210
|
-
adjustedCreated = createdAt - (performance.now() - this.stoppedTime);
|
|
2211
|
-
this.stoppedTime = 0;
|
|
2212
|
-
}
|
|
2213
|
-
Object.assign(this.loopState, {
|
|
2214
|
-
running: true,
|
|
2215
|
-
elapsedTime: elapsedTime ?? 0,
|
|
2216
|
-
lastTime: performance.now(),
|
|
2217
|
-
createdAt: adjustedCreated > 0 ? adjustedCreated : performance.now(),
|
|
2218
|
-
frameCount: 0,
|
|
2219
|
-
rafHandle: requestAnimationFrame(this.loop)
|
|
2220
|
-
});
|
|
2221
|
-
}
|
|
2222
|
-
/**
|
|
2223
|
-
* Stop the requestAnimationFrame loop.
|
|
2224
|
-
* Cancels any pending RAF callback.
|
|
2225
|
-
* No-op if not running.
|
|
2226
|
-
* @returns {void}
|
|
2227
|
-
*/
|
|
2228
|
-
stop() {
|
|
2229
|
-
if (!this.loopState.running) return;
|
|
2230
|
-
this.loopState.running = false;
|
|
2231
|
-
if (this.loopState.rafHandle !== null) {
|
|
2232
|
-
cancelAnimationFrame(this.loopState.rafHandle);
|
|
2233
|
-
this.loopState.rafHandle = null;
|
|
2234
|
-
}
|
|
2235
|
-
this.stoppedTime = performance.now();
|
|
2236
|
-
}
|
|
2237
|
-
/**
|
|
2238
|
-
* Request frames to be rendered in demand mode.
|
|
2239
|
-
* Accumulates pending frames (capped at 60) and starts the loop if not running.
|
|
2240
|
-
* No-op if frameloop is not 'demand'.
|
|
2241
|
-
* @param {number} [frames=1] - Number of frames to request
|
|
2242
|
-
* @param {boolean} [stackFrames=false] - Whether to add frames to existing pending count
|
|
2243
|
-
* - `false` (default): Sets pending frames to the specified value (replaces existing count)
|
|
2244
|
-
* - `true`: Adds frames to existing pending count (useful for accumulating invalidations)
|
|
2245
|
-
* @returns {void}
|
|
2246
|
-
* @example
|
|
2247
|
-
* // Request a single frame render
|
|
2248
|
-
* scheduler.invalidate();
|
|
2249
|
-
*
|
|
2250
|
-
* @example
|
|
2251
|
-
* // Request 5 frames (e.g., for animations)
|
|
2252
|
-
* scheduler.invalidate(5);
|
|
2253
|
-
*
|
|
2254
|
-
* @example
|
|
2255
|
-
* // Set pending frames to exactly 3 (don't stack with existing)
|
|
2256
|
-
* scheduler.invalidate(3, false);
|
|
2257
|
-
*
|
|
2258
|
-
* @example
|
|
2259
|
-
* // Add 2 more frames to existing pending count
|
|
2260
|
-
* scheduler.invalidate(2, true);
|
|
2261
|
-
*/
|
|
2262
|
-
invalidate(frames = 1, stackFrames = false) {
|
|
2263
|
-
if (this._frameloop !== "demand") return;
|
|
2264
|
-
const baseFrames = stackFrames ? this.pendingFrames : 0;
|
|
2265
|
-
this.pendingFrames = Math.min(60, baseFrames + frames);
|
|
2266
|
-
if (!this.loopState.running && this.pendingFrames > 0) this.start();
|
|
2267
|
-
}
|
|
2268
|
-
/**
|
|
2269
|
-
* Reset timing state for deterministic testing.
|
|
2270
|
-
* Preserves jobs and roots but resets lastTime, frameCount, elapsedTime, etc.
|
|
2271
|
-
* @returns {void}
|
|
2272
|
-
*/
|
|
2273
|
-
resetTiming() {
|
|
2274
|
-
this.loopState.lastTime = null;
|
|
2275
|
-
this.loopState.frameCount = 0;
|
|
2276
|
-
this.loopState.elapsedTime = 0;
|
|
2277
|
-
this.loopState.createdAt = performance.now();
|
|
2278
|
-
}
|
|
2279
|
-
//* Manual Stepping Methods ================================
|
|
2280
|
-
/**
|
|
2281
|
-
* Manually execute a single frame for all roots.
|
|
2282
|
-
* Useful for frameloop='never' mode or testing scenarios.
|
|
2283
|
-
* @param {number} [timestamp] - Optional timestamp (defaults to performance.now())
|
|
2284
|
-
* @returns {void}
|
|
2285
|
-
* @example
|
|
2286
|
-
* // Manual control mode
|
|
2287
|
-
* scheduler.frameloop = 'never';
|
|
2288
|
-
* scheduler.step(); // Execute one frame
|
|
2289
|
-
*/
|
|
2290
|
-
step(timestamp) {
|
|
2291
|
-
const now = timestamp ?? performance.now();
|
|
2292
|
-
this.executeFrame(now);
|
|
2293
|
-
}
|
|
2294
|
-
/**
|
|
2295
|
-
* Manually execute a single job by its ID.
|
|
2296
|
-
* Useful for testing individual job callbacks in isolation.
|
|
2297
|
-
* @param {string} id - The job ID to step
|
|
2298
|
-
* @param {number} [timestamp] - Optional timestamp (defaults to performance.now())
|
|
2299
|
-
* @returns {void}
|
|
2300
|
-
*/
|
|
2301
|
-
stepJob(id, timestamp) {
|
|
2302
|
-
let job;
|
|
2303
|
-
let root;
|
|
2304
|
-
for (const r of this.roots.values()) {
|
|
2305
|
-
job = r.jobs.get(id);
|
|
2306
|
-
if (job) {
|
|
2307
|
-
root = r;
|
|
2308
|
-
break;
|
|
2309
|
-
}
|
|
2310
|
-
}
|
|
2311
|
-
if (!job || !root) {
|
|
2312
|
-
console.warn(`[Scheduler] Job "${id}" not found`);
|
|
2313
|
-
return;
|
|
2314
|
-
}
|
|
2315
|
-
const now = timestamp ?? performance.now();
|
|
2316
|
-
const deltaMs = this.loopState.lastTime !== null ? now - this.loopState.lastTime : 0;
|
|
2317
|
-
const delta = deltaMs / 1e3;
|
|
2318
|
-
const elapsed = now - this.loopState.createdAt;
|
|
2319
|
-
const providedState = root.getState?.() ?? {};
|
|
2320
|
-
const frameState = {
|
|
2321
|
-
...providedState,
|
|
2322
|
-
time: now,
|
|
2323
|
-
delta,
|
|
2324
|
-
elapsed,
|
|
2325
|
-
frame: this.loopState.frameCount
|
|
2326
|
-
};
|
|
2327
|
-
try {
|
|
2328
|
-
job.callback(frameState, delta);
|
|
2329
|
-
} catch (error) {
|
|
2330
|
-
console.error(`[Scheduler] Error in job "${job.id}":`, error);
|
|
2331
|
-
this.triggerError(error instanceof Error ? error : new Error(String(error)));
|
|
2332
|
-
}
|
|
2333
|
-
}
|
|
2334
|
-
/**
|
|
2335
|
-
* Execute a single frame across all roots.
|
|
2336
|
-
* Order: globalBefore → each root's jobs → globalAfter
|
|
2337
|
-
* @param {number} timestamp - RAF timestamp in milliseconds
|
|
2338
|
-
* @returns {void}
|
|
2339
|
-
* @private
|
|
2340
|
-
*/
|
|
2341
|
-
executeFrame(timestamp) {
|
|
2342
|
-
const deltaMs = this.loopState.lastTime !== null ? timestamp - this.loopState.lastTime : 0;
|
|
2343
|
-
const delta = deltaMs / 1e3;
|
|
2344
|
-
this.loopState.lastTime = timestamp;
|
|
2345
|
-
this.loopState.frameCount++;
|
|
2346
|
-
this.loopState.elapsedTime += deltaMs;
|
|
2347
|
-
this.runGlobalJobs(this.globalBeforeJobs, timestamp);
|
|
2348
|
-
for (const root of this.roots.values()) {
|
|
2349
|
-
this.tickRoot(root, timestamp, delta);
|
|
2350
|
-
}
|
|
2351
|
-
this.runGlobalJobs(this.globalAfterJobs, timestamp);
|
|
2352
|
-
}
|
|
2353
|
-
/**
|
|
2354
|
-
* Run all global jobs from a job map.
|
|
2355
|
-
* Catches and logs errors without stopping execution.
|
|
2356
|
-
* @param {Map<string, GlobalJob>} jobs - The global jobs map to execute
|
|
2357
|
-
* @param {number} timestamp - RAF timestamp in milliseconds
|
|
2358
|
-
* @returns {void}
|
|
2359
|
-
* @private
|
|
2360
|
-
*/
|
|
2361
|
-
runGlobalJobs(jobs, timestamp) {
|
|
2362
|
-
for (const job of jobs.values()) {
|
|
2363
|
-
try {
|
|
2364
|
-
job.callback(timestamp);
|
|
2365
|
-
} catch (error) {
|
|
2366
|
-
console.error(`[Scheduler] Error in global job "${job.id}":`, error);
|
|
2367
|
-
}
|
|
2368
|
-
}
|
|
2369
|
-
}
|
|
2370
|
-
/**
|
|
2371
|
-
* Execute all jobs for a single root in sorted order.
|
|
2372
|
-
* Rebuilds sorted job list if needed, then dispatches each job.
|
|
2373
|
-
* Errors are caught and propagated via triggerError.
|
|
2374
|
-
* @param {RootEntry} root - The root entry to tick
|
|
2375
|
-
* @param {number} timestamp - RAF timestamp in milliseconds
|
|
2376
|
-
* @param {number} delta - Time since last frame in seconds
|
|
2377
|
-
* @returns {void}
|
|
2378
|
-
* @private
|
|
2379
|
-
*/
|
|
2380
|
-
tickRoot(root, timestamp, delta) {
|
|
2381
|
-
if (root.needsRebuild) {
|
|
2382
|
-
root.sortedJobs = rebuildSortedJobs(root.jobs, this.phaseGraph);
|
|
2383
|
-
root.needsRebuild = false;
|
|
2384
|
-
}
|
|
2385
|
-
const providedState = root.getState?.() ?? {};
|
|
2386
|
-
const frameState = {
|
|
2387
|
-
...providedState,
|
|
2388
|
-
time: timestamp,
|
|
2389
|
-
delta,
|
|
2390
|
-
elapsed: this.loopState.elapsedTime / 1e3,
|
|
2391
|
-
// Convert ms to seconds
|
|
2392
|
-
frame: this.loopState.frameCount
|
|
2393
|
-
};
|
|
2394
|
-
for (const job of root.sortedJobs) {
|
|
2395
|
-
if (!shouldRun(job, timestamp)) continue;
|
|
2396
|
-
try {
|
|
2397
|
-
job.callback(frameState, delta);
|
|
2398
|
-
} catch (error) {
|
|
2399
|
-
console.error(`[Scheduler] Error in job "${job.id}":`, error);
|
|
2400
|
-
this.triggerError(error instanceof Error ? error : new Error(String(error)));
|
|
2401
|
-
}
|
|
2402
|
-
}
|
|
2403
|
-
}
|
|
2404
|
-
//* Debug & Inspection Methods ================================
|
|
2405
|
-
/**
|
|
2406
|
-
* Get the total number of registered jobs across all roots.
|
|
2407
|
-
* Includes both per-root jobs and global before/after jobs.
|
|
2408
|
-
* @returns {number} Total job count
|
|
2409
|
-
*/
|
|
2410
|
-
getJobCount() {
|
|
2411
|
-
let count = 0;
|
|
2412
|
-
for (const root of this.roots.values()) {
|
|
2413
|
-
count += root.jobs.size;
|
|
2414
|
-
}
|
|
2415
|
-
return count + this.globalBeforeJobs.size + this.globalAfterJobs.size;
|
|
2416
|
-
}
|
|
2417
|
-
/**
|
|
2418
|
-
* Get all registered job IDs across all roots.
|
|
2419
|
-
* Includes both per-root jobs and global before/after jobs.
|
|
2420
|
-
* @returns {string[]} Array of all job IDs
|
|
2421
|
-
*/
|
|
2422
|
-
getJobIds() {
|
|
2423
|
-
const ids = [];
|
|
2424
|
-
for (const root of this.roots.values()) {
|
|
2425
|
-
ids.push(...root.jobs.keys());
|
|
2426
|
-
}
|
|
2427
|
-
ids.push(...this.globalBeforeJobs.keys());
|
|
2428
|
-
ids.push(...this.globalAfterJobs.keys());
|
|
2429
|
-
return ids;
|
|
2430
|
-
}
|
|
2431
|
-
/**
|
|
2432
|
-
* Get the number of registered roots (Canvas instances).
|
|
2433
|
-
* @returns {number} Number of registered roots
|
|
2434
|
-
*/
|
|
2435
|
-
getRootCount() {
|
|
2436
|
-
return this.roots.size;
|
|
2437
|
-
}
|
|
2438
|
-
/**
|
|
2439
|
-
* Check if any user (non-system) jobs are registered in a specific phase.
|
|
2440
|
-
* Used by the default render job to know if a user has taken over rendering.
|
|
2441
|
-
*
|
|
2442
|
-
* @param phase The phase to check
|
|
2443
|
-
* @param rootId Optional root ID to check (checks all roots if not provided)
|
|
2444
|
-
* @returns true if any user jobs exist in the phase
|
|
2445
|
-
*/
|
|
2446
|
-
hasUserJobsInPhase(phase, rootId) {
|
|
2447
|
-
const rootsToCheck = rootId ? [this.roots.get(rootId)].filter(Boolean) : Array.from(this.roots.values());
|
|
2448
|
-
return rootsToCheck.some((root) => {
|
|
2449
|
-
if (!root) return false;
|
|
2450
|
-
for (const job of root.jobs.values()) {
|
|
2451
|
-
if (job.phase === phase && !job.system && job.enabled) return true;
|
|
2452
|
-
}
|
|
2453
|
-
return false;
|
|
2454
|
-
});
|
|
2455
|
-
}
|
|
2456
|
-
//* Utility Methods ================================
|
|
2457
|
-
/**
|
|
2458
|
-
* Generate a unique root ID for automatic root registration.
|
|
2459
|
-
* @returns {string} A unique root ID in the format 'root_N'
|
|
2460
|
-
*/
|
|
2461
|
-
generateRootId() {
|
|
2462
|
-
return `root_${this.nextRootIndex++}`;
|
|
2463
|
-
}
|
|
2464
|
-
/**
|
|
2465
|
-
* Generate a unique job ID.
|
|
2466
|
-
* @returns {string} A unique job ID in the format 'job_N'
|
|
2467
|
-
* @private
|
|
2468
|
-
*/
|
|
2469
|
-
generateJobId() {
|
|
2470
|
-
return `job_${this.nextJobIndex}`;
|
|
2471
|
-
}
|
|
2472
|
-
/**
|
|
2473
|
-
* Normalize before/after constraints to a Set.
|
|
2474
|
-
* Handles undefined, single string, or array inputs.
|
|
2475
|
-
* @param {string | string[] | undefined} value - The constraint value(s)
|
|
2476
|
-
* @returns {Set<string>} Normalized Set of constraint strings
|
|
2477
|
-
* @private
|
|
2478
|
-
*/
|
|
2479
|
-
normalizeConstraints(value) {
|
|
2480
|
-
if (!value) return /* @__PURE__ */ new Set();
|
|
2481
|
-
if (Array.isArray(value)) return new Set(value);
|
|
2482
|
-
return /* @__PURE__ */ new Set([value]);
|
|
2483
|
-
}
|
|
2484
|
-
};
|
|
2485
|
-
//* Static State & Methods (Singleton Usage) ================================
|
|
2486
|
-
//* Cross-Bundle Singleton Key ==============================
|
|
2487
|
-
// Use Symbol.for() to ensure scheduler is shared across bundle boundaries
|
|
2488
|
-
// This prevents issues when mixing imports from @react-three/fiber and @react-three/fiber/webgpu
|
|
2489
|
-
__publicField$1(_Scheduler, "INSTANCE_KEY", Symbol.for("@react-three/fiber.scheduler"));
|
|
2490
|
-
let Scheduler = _Scheduler;
|
|
2491
|
-
const getScheduler = () => Scheduler.get();
|
|
2492
|
-
if (hmrData) {
|
|
2493
|
-
hmrData.accept?.();
|
|
2494
|
-
}
|
|
2495
|
-
|
|
2496
|
-
const R3F_CONTEXT = Symbol.for("@react-three/fiber.context");
|
|
2497
|
-
const context = globalThis[R3F_CONTEXT] ?? (globalThis[R3F_CONTEXT] = React__namespace.createContext(null));
|
|
2498
|
-
const createStore = (invalidate, advance) => {
|
|
2499
|
-
const rootStore = traditional.createWithEqualityFn((set, get) => {
|
|
2500
|
-
const position = new webgpu.Vector3();
|
|
2501
|
-
const defaultTarget = new webgpu.Vector3();
|
|
2502
|
-
const tempTarget = new webgpu.Vector3();
|
|
2503
|
-
function getCurrentViewport(camera = get().camera, target = defaultTarget, size = get().size) {
|
|
2504
|
-
const { width, height, top, left } = size;
|
|
2505
|
-
const aspect = width / height;
|
|
2506
|
-
if (target.isVector3) tempTarget.copy(target);
|
|
2507
|
-
else tempTarget.set(...target);
|
|
2508
|
-
const distance = camera.getWorldPosition(position).distanceTo(tempTarget);
|
|
2509
|
-
if (isOrthographicCamera(camera)) {
|
|
2510
|
-
return { width: width / camera.zoom, height: height / camera.zoom, top, left, factor: 1, distance, aspect };
|
|
2511
|
-
} else {
|
|
2512
|
-
const fov = camera.fov * Math.PI / 180;
|
|
2513
|
-
const h = 2 * Math.tan(fov / 2) * distance;
|
|
2514
|
-
const w = h * (width / height);
|
|
2515
|
-
return { width: w, height: h, top, left, factor: width / w, distance, aspect };
|
|
1733
|
+
const R3F_CONTEXT = Symbol.for("@react-three/fiber.context");
|
|
1734
|
+
const context = globalThis[R3F_CONTEXT] ?? (globalThis[R3F_CONTEXT] = React__namespace.createContext(null));
|
|
1735
|
+
const createStore = (invalidate, advance) => {
|
|
1736
|
+
const rootStore = traditional.createWithEqualityFn((set, get) => {
|
|
1737
|
+
const position = new webgpu.Vector3();
|
|
1738
|
+
const defaultTarget = new webgpu.Vector3();
|
|
1739
|
+
const tempTarget = new webgpu.Vector3();
|
|
1740
|
+
function getCurrentViewport(camera = get().camera, target = defaultTarget, size = get().size) {
|
|
1741
|
+
const { width, height, top, left } = size;
|
|
1742
|
+
const aspect = width / height;
|
|
1743
|
+
if (target.isVector3) tempTarget.copy(target);
|
|
1744
|
+
else tempTarget.set(...target);
|
|
1745
|
+
const distance = camera.getWorldPosition(position).distanceTo(tempTarget);
|
|
1746
|
+
if (isOrthographicCamera(camera)) {
|
|
1747
|
+
return { width: width / camera.zoom, height: height / camera.zoom, top, left, factor: 1, distance, aspect };
|
|
1748
|
+
} else {
|
|
1749
|
+
const fov = camera.fov * Math.PI / 180;
|
|
1750
|
+
const h = 2 * Math.tan(fov / 2) * distance;
|
|
1751
|
+
const w = h * (width / height);
|
|
1752
|
+
return { width: w, height: h, top, left, factor: width / w, distance, aspect };
|
|
2516
1753
|
}
|
|
2517
1754
|
}
|
|
2518
1755
|
let performanceTimeout = void 0;
|
|
@@ -2530,7 +1767,14 @@ const createStore = (invalidate, advance) => {
|
|
|
2530
1767
|
frustum: new webgpu.Frustum(),
|
|
2531
1768
|
autoUpdateFrustum: true,
|
|
2532
1769
|
raycaster: null,
|
|
2533
|
-
events: {
|
|
1770
|
+
events: {
|
|
1771
|
+
priority: 1,
|
|
1772
|
+
enabled: true,
|
|
1773
|
+
connected: false,
|
|
1774
|
+
frameTimedRaycasts: true,
|
|
1775
|
+
alwaysFireOnScroll: true,
|
|
1776
|
+
updateOnFrame: false
|
|
1777
|
+
},
|
|
2534
1778
|
scene: null,
|
|
2535
1779
|
rootScene: null,
|
|
2536
1780
|
xr: null,
|
|
@@ -2595,7 +1839,7 @@ const createStore = (invalidate, advance) => {
|
|
|
2595
1839
|
size: newSize,
|
|
2596
1840
|
viewport: { ...s.viewport, ...getCurrentViewport(state2.camera, defaultTarget, newSize) }
|
|
2597
1841
|
}));
|
|
2598
|
-
getScheduler().invalidate();
|
|
1842
|
+
scheduler.getScheduler().invalidate();
|
|
2599
1843
|
}
|
|
2600
1844
|
}
|
|
2601
1845
|
return;
|
|
@@ -2610,7 +1854,7 @@ const createStore = (invalidate, advance) => {
|
|
|
2610
1854
|
viewport: { ...s.viewport, ...getCurrentViewport(state2.camera, defaultTarget, size) },
|
|
2611
1855
|
_sizeImperative: true
|
|
2612
1856
|
}));
|
|
2613
|
-
getScheduler().invalidate();
|
|
1857
|
+
scheduler.getScheduler().invalidate();
|
|
2614
1858
|
},
|
|
2615
1859
|
setDpr: (dpr) => set((state2) => {
|
|
2616
1860
|
const resolved = calculateDpr(dpr);
|
|
@@ -2618,14 +1862,18 @@ const createStore = (invalidate, advance) => {
|
|
|
2618
1862
|
}),
|
|
2619
1863
|
setFrameloop: (frameloop = "always") => {
|
|
2620
1864
|
set(() => ({ frameloop }));
|
|
1865
|
+
scheduler.getScheduler().frameloop = frameloop;
|
|
2621
1866
|
},
|
|
2622
1867
|
setError: (error) => set(() => ({ error })),
|
|
2623
1868
|
error: null,
|
|
2624
|
-
//* TSL State (managed via hooks: useUniforms, useNodes, useTextures,
|
|
1869
|
+
//* TSL State (managed via hooks: useUniforms, useNodes, useBuffers, useGPUStorage, useTextures, useRenderPipeline) ==============================
|
|
2625
1870
|
uniforms: {},
|
|
2626
1871
|
nodes: {},
|
|
1872
|
+
buffers: {},
|
|
1873
|
+
gpuStorage: {},
|
|
2627
1874
|
textures: /* @__PURE__ */ new Map(),
|
|
2628
|
-
|
|
1875
|
+
_textureRefs: /* @__PURE__ */ new Map(),
|
|
1876
|
+
renderPipeline: null,
|
|
2629
1877
|
passes: {},
|
|
2630
1878
|
_hmrVersion: 0,
|
|
2631
1879
|
_sizeImperative: false,
|
|
@@ -2634,12 +1882,16 @@ const createStore = (invalidate, advance) => {
|
|
|
2634
1882
|
internal: {
|
|
2635
1883
|
// Events
|
|
2636
1884
|
interaction: [],
|
|
2637
|
-
hovered: /* @__PURE__ */ new Map(),
|
|
2638
1885
|
subscribers: [],
|
|
1886
|
+
// Per-pointer state (new unified structure)
|
|
1887
|
+
pointerMap: /* @__PURE__ */ new Map(),
|
|
1888
|
+
pointerDirty: /* @__PURE__ */ new Map(),
|
|
1889
|
+
lastEvent: React__namespace.createRef(),
|
|
1890
|
+
// Deprecated but kept for backwards compatibility
|
|
1891
|
+
hovered: /* @__PURE__ */ new Map(),
|
|
2639
1892
|
initialClick: [0, 0],
|
|
2640
1893
|
initialHits: [],
|
|
2641
1894
|
capturedMap: /* @__PURE__ */ new Map(),
|
|
2642
|
-
lastEvent: React__namespace.createRef(),
|
|
2643
1895
|
// Visibility tracking (onFramed, onOccluded, onVisible)
|
|
2644
1896
|
visibilityRegistry: /* @__PURE__ */ new Map(),
|
|
2645
1897
|
// Occlusion system (WebGPU only)
|
|
@@ -2678,8 +1930,7 @@ const createStore = (invalidate, advance) => {
|
|
|
2678
1930
|
const currentState = rootStore.getState();
|
|
2679
1931
|
if (!currentState.isLegacy && currentState.internal.actualRenderer) {
|
|
2680
1932
|
const stack = new Error().stack || "";
|
|
2681
|
-
|
|
2682
|
-
if (!isInternalAccess) {
|
|
1933
|
+
if (!isInternalRendererAccess(stack)) {
|
|
2683
1934
|
const cleanedStack = stack.split("\n").slice(2).join("\n") || "Stack trace unavailable";
|
|
2684
1935
|
notifyDepreciated({
|
|
2685
1936
|
heading: "Accessing state.gl in WebGPU mode",
|
|
@@ -2727,14 +1978,16 @@ const createStore = (invalidate, advance) => {
|
|
|
2727
1978
|
oldSize = size;
|
|
2728
1979
|
oldDpr = viewport.dpr;
|
|
2729
1980
|
updateCamera(camera, size);
|
|
2730
|
-
if (canvasTarget) {
|
|
1981
|
+
if (internal.isSecondary && canvasTarget) {
|
|
2731
1982
|
if (viewport.dpr > 0) canvasTarget.setPixelRatio(viewport.dpr);
|
|
2732
|
-
|
|
2733
|
-
canvasTarget.setSize(size.width, size.height, updateStyle);
|
|
1983
|
+
canvasTarget.setSize(size.width, size.height, false);
|
|
2734
1984
|
} else {
|
|
2735
1985
|
if (viewport.dpr > 0) actualRenderer.setPixelRatio(viewport.dpr);
|
|
2736
|
-
|
|
2737
|
-
|
|
1986
|
+
actualRenderer.setSize(size.width, size.height, false);
|
|
1987
|
+
if (canvasTarget) {
|
|
1988
|
+
if (viewport.dpr > 0) canvasTarget.setPixelRatio(viewport.dpr);
|
|
1989
|
+
canvasTarget.setSize(size.width, size.height, false);
|
|
1990
|
+
}
|
|
2738
1991
|
}
|
|
2739
1992
|
}
|
|
2740
1993
|
if (camera !== oldCamera) {
|
|
@@ -2790,26 +2043,32 @@ function loadingFn(extensions, onProgress) {
|
|
|
2790
2043
|
);
|
|
2791
2044
|
};
|
|
2792
2045
|
}
|
|
2793
|
-
function useLoader(loader, input, extensions, onProgress) {
|
|
2794
|
-
const
|
|
2046
|
+
function useLoader(loader, input, extensions, onProgress, cacheKey) {
|
|
2047
|
+
const inputs = Array.isArray(input) ? input : [input];
|
|
2048
|
+
const cacheKeys = Array.isArray(cacheKey ?? input) ? cacheKey ?? input : [cacheKey ?? input];
|
|
2795
2049
|
const fn = loadingFn(extensions, onProgress);
|
|
2796
|
-
const results =
|
|
2050
|
+
const results = cacheKeys.map(
|
|
2051
|
+
(key, index) => suspendReact.suspend(() => fn(loader, inputs[index]), [loader, key], { equal: is.equ })
|
|
2052
|
+
);
|
|
2797
2053
|
return Array.isArray(input) ? results : results[0];
|
|
2798
2054
|
}
|
|
2799
|
-
useLoader.preload = function(loader, input, extensions, onProgress) {
|
|
2800
|
-
const
|
|
2801
|
-
|
|
2055
|
+
useLoader.preload = function(loader, input, extensions, onProgress, cacheKey) {
|
|
2056
|
+
const inputs = Array.isArray(input) ? input : [input];
|
|
2057
|
+
const cacheKeys = Array.isArray(cacheKey ?? input) ? cacheKey ?? input : [cacheKey ?? input];
|
|
2058
|
+
cacheKeys.forEach(
|
|
2059
|
+
(key, index) => suspendReact.preload(() => loadingFn(extensions, onProgress)(loader, inputs[index]), [loader, key])
|
|
2060
|
+
);
|
|
2802
2061
|
};
|
|
2803
|
-
useLoader.clear = function(loader, input) {
|
|
2804
|
-
const
|
|
2805
|
-
|
|
2062
|
+
useLoader.clear = function(loader, input, cacheKey) {
|
|
2063
|
+
const cacheKeys = Array.isArray(cacheKey ?? input) ? cacheKey ?? input : [cacheKey ?? input];
|
|
2064
|
+
cacheKeys.forEach((key) => suspendReact.clear([loader, key]));
|
|
2806
2065
|
};
|
|
2807
2066
|
useLoader.loader = getLoader;
|
|
2808
2067
|
|
|
2809
2068
|
function useFrame(callback, priorityOrOptions) {
|
|
2810
2069
|
const store = React__namespace.useContext(context);
|
|
2811
2070
|
const isInsideCanvas = store !== null;
|
|
2812
|
-
const scheduler = getScheduler();
|
|
2071
|
+
const scheduler$1 = scheduler.getScheduler();
|
|
2813
2072
|
const optionsKey = typeof priorityOrOptions === "number" ? `p:${priorityOrOptions}` : priorityOrOptions ? JSON.stringify({
|
|
2814
2073
|
id: priorityOrOptions.id,
|
|
2815
2074
|
phase: priorityOrOptions.phase,
|
|
@@ -2857,7 +2116,7 @@ function useFrame(callback, priorityOrOptions) {
|
|
|
2857
2116
|
};
|
|
2858
2117
|
callbackRef.current?.(mergedState, delta);
|
|
2859
2118
|
};
|
|
2860
|
-
const unregister = scheduler.register(wrappedCallback, {
|
|
2119
|
+
const unregister = scheduler$1.register(wrappedCallback, {
|
|
2861
2120
|
id,
|
|
2862
2121
|
rootId,
|
|
2863
2122
|
...options
|
|
@@ -2878,37 +2137,31 @@ function useFrame(callback, priorityOrOptions) {
|
|
|
2878
2137
|
}
|
|
2879
2138
|
};
|
|
2880
2139
|
} else {
|
|
2881
|
-
|
|
2882
|
-
|
|
2883
|
-
|
|
2884
|
-
|
|
2885
|
-
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
|
|
2889
|
-
unregisterJob = registerOutside();
|
|
2890
|
-
});
|
|
2891
|
-
return () => {
|
|
2892
|
-
unsubReady();
|
|
2893
|
-
unregisterJob?.();
|
|
2894
|
-
};
|
|
2140
|
+
return scheduler$1.register(
|
|
2141
|
+
(state, delta) => {
|
|
2142
|
+
const frameState = state;
|
|
2143
|
+
if (!frameState.renderer) return;
|
|
2144
|
+
callbackRef.current?.(frameState, delta);
|
|
2145
|
+
},
|
|
2146
|
+
{ id, ...options }
|
|
2147
|
+
);
|
|
2895
2148
|
}
|
|
2896
|
-
}, [store, scheduler, id, optionsKey, isLegacyPriority, isInsideCanvas]);
|
|
2149
|
+
}, [store, scheduler$1, id, optionsKey, isLegacyPriority, isInsideCanvas]);
|
|
2897
2150
|
const isPaused = React__namespace.useSyncExternalStore(
|
|
2898
2151
|
// Subscribe function
|
|
2899
2152
|
React__namespace.useCallback(
|
|
2900
2153
|
(onStoreChange) => {
|
|
2901
|
-
return getScheduler().subscribeJobState(id, onStoreChange);
|
|
2154
|
+
return scheduler.getScheduler().subscribeJobState(id, onStoreChange);
|
|
2902
2155
|
},
|
|
2903
2156
|
[id]
|
|
2904
2157
|
),
|
|
2905
2158
|
// getSnapshot function
|
|
2906
|
-
React__namespace.useCallback(() => getScheduler().isJobPaused(id), [id]),
|
|
2159
|
+
React__namespace.useCallback(() => scheduler.getScheduler().isJobPaused(id), [id]),
|
|
2907
2160
|
// getServerSnapshot function (SSR)
|
|
2908
2161
|
React__namespace.useCallback(() => false, [])
|
|
2909
2162
|
);
|
|
2910
2163
|
const controls = React__namespace.useMemo(() => {
|
|
2911
|
-
const scheduler2 = getScheduler();
|
|
2164
|
+
const scheduler2 = scheduler.getScheduler();
|
|
2912
2165
|
return {
|
|
2913
2166
|
/** The job's unique ID */
|
|
2914
2167
|
id,
|
|
@@ -2923,7 +2176,7 @@ function useFrame(callback, priorityOrOptions) {
|
|
|
2923
2176
|
* @param timestamp Optional timestamp (defaults to performance.now())
|
|
2924
2177
|
*/
|
|
2925
2178
|
step: (timestamp) => {
|
|
2926
|
-
getScheduler().stepJob(id, timestamp);
|
|
2179
|
+
scheduler.getScheduler().stepJob(id, timestamp);
|
|
2927
2180
|
},
|
|
2928
2181
|
/**
|
|
2929
2182
|
* Manually step ALL jobs in the scheduler.
|
|
@@ -2931,20 +2184,20 @@ function useFrame(callback, priorityOrOptions) {
|
|
|
2931
2184
|
* @param timestamp Optional timestamp (defaults to performance.now())
|
|
2932
2185
|
*/
|
|
2933
2186
|
stepAll: (timestamp) => {
|
|
2934
|
-
getScheduler().step(timestamp);
|
|
2187
|
+
scheduler.getScheduler().step(timestamp);
|
|
2935
2188
|
},
|
|
2936
2189
|
/**
|
|
2937
2190
|
* Pause this job (set enabled=false).
|
|
2938
2191
|
* Job remains registered but won't run.
|
|
2939
2192
|
*/
|
|
2940
2193
|
pause: () => {
|
|
2941
|
-
getScheduler().pauseJob(id);
|
|
2194
|
+
scheduler.getScheduler().pauseJob(id);
|
|
2942
2195
|
},
|
|
2943
2196
|
/**
|
|
2944
2197
|
* Resume this job (set enabled=true).
|
|
2945
2198
|
*/
|
|
2946
2199
|
resume: () => {
|
|
2947
|
-
getScheduler().resumeJob(id);
|
|
2200
|
+
scheduler.getScheduler().resumeJob(id);
|
|
2948
2201
|
},
|
|
2949
2202
|
/**
|
|
2950
2203
|
* Reactive paused state - automatically updates when pause/resume is called.
|
|
@@ -2982,18 +2235,18 @@ function buildFromCache(input, textureCache) {
|
|
|
2982
2235
|
function useTexture(input, optionsOrOnLoad) {
|
|
2983
2236
|
const renderer = useThree((state) => state.internal.actualRenderer);
|
|
2984
2237
|
const store = useStore();
|
|
2985
|
-
const textureCache = useThree((state) => state.textures);
|
|
2986
2238
|
const options = typeof optionsOrOnLoad === "function" ? { onLoad: optionsOrOnLoad } : optionsOrOnLoad ?? {};
|
|
2987
|
-
const { onLoad, cache =
|
|
2239
|
+
const { onLoad, cache = true } = options;
|
|
2988
2240
|
const onLoadRef = React.useRef(onLoad);
|
|
2989
2241
|
onLoadRef.current = onLoad;
|
|
2990
2242
|
const onLoadCalledForRef = React.useRef(null);
|
|
2991
2243
|
const urls = React.useMemo(() => getUrls(input), [input]);
|
|
2992
2244
|
const cachedResult = React.useMemo(() => {
|
|
2993
2245
|
if (!cache) return null;
|
|
2994
|
-
|
|
2995
|
-
|
|
2996
|
-
|
|
2246
|
+
const textures = store.getState().textures;
|
|
2247
|
+
if (!allUrlsCached(urls, textures)) return null;
|
|
2248
|
+
return buildFromCache(input, textures);
|
|
2249
|
+
}, [cache, urls, input, store]);
|
|
2997
2250
|
const loadedTextures = useLoader(
|
|
2998
2251
|
webgpu.TextureLoader,
|
|
2999
2252
|
IsObject(input) ? Object.values(input) : input
|
|
@@ -3037,8 +2290,6 @@ function useTexture(input, optionsOrOnLoad) {
|
|
|
3037
2290
|
}, [input, loadedTextures, cachedResult]);
|
|
3038
2291
|
React.useEffect(() => {
|
|
3039
2292
|
if (!cache) return;
|
|
3040
|
-
if (cachedResult) return;
|
|
3041
|
-
const set = store.setState;
|
|
3042
2293
|
const urlTextureMap = [];
|
|
3043
2294
|
if (typeof input === "string") {
|
|
3044
2295
|
urlTextureMap.push([input, mappedTextures]);
|
|
@@ -3052,18 +2303,32 @@ function useTexture(input, optionsOrOnLoad) {
|
|
|
3052
2303
|
urlTextureMap.push([url, textureRecord[key]]);
|
|
3053
2304
|
}
|
|
3054
2305
|
}
|
|
3055
|
-
|
|
3056
|
-
const
|
|
3057
|
-
let
|
|
2306
|
+
store.setState((state) => {
|
|
2307
|
+
const refs = new Map(state._textureRefs);
|
|
2308
|
+
let textures = state.textures;
|
|
2309
|
+
let added = false;
|
|
3058
2310
|
for (const [url, texture] of urlTextureMap) {
|
|
3059
|
-
if (!
|
|
3060
|
-
|
|
3061
|
-
|
|
2311
|
+
if (!textures.has(url)) {
|
|
2312
|
+
if (!added) {
|
|
2313
|
+
textures = new Map(textures);
|
|
2314
|
+
added = true;
|
|
2315
|
+
}
|
|
2316
|
+
textures.set(url, texture);
|
|
3062
2317
|
}
|
|
2318
|
+
refs.set(url, (refs.get(url) ?? 0) + 1);
|
|
3063
2319
|
}
|
|
3064
|
-
return
|
|
2320
|
+
return added ? { textures, _textureRefs: refs } : { _textureRefs: refs };
|
|
2321
|
+
});
|
|
2322
|
+
return () => store.setState((state) => {
|
|
2323
|
+
const refs = new Map(state._textureRefs);
|
|
2324
|
+
for (const [url] of urlTextureMap) {
|
|
2325
|
+
const next = (refs.get(url) ?? 0) - 1;
|
|
2326
|
+
if (next <= 0) refs.delete(url);
|
|
2327
|
+
else refs.set(url, next);
|
|
2328
|
+
}
|
|
2329
|
+
return { _textureRefs: refs };
|
|
3065
2330
|
});
|
|
3066
|
-
}, [cache, input, mappedTextures, store
|
|
2331
|
+
}, [cache, input, mappedTextures, store]);
|
|
3067
2332
|
return mappedTextures;
|
|
3068
2333
|
}
|
|
3069
2334
|
useTexture.preload = (url) => useLoader.preload(webgpu.TextureLoader, url);
|
|
@@ -3079,96 +2344,63 @@ const Texture = ({
|
|
|
3079
2344
|
return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: children?.(ret) });
|
|
3080
2345
|
};
|
|
3081
2346
|
|
|
3082
|
-
function
|
|
3083
|
-
if (entry instanceof webgpu.Texture) return entry;
|
|
3084
|
-
if (entry && typeof entry === "object" && "value" in entry && entry.value instanceof webgpu.Texture) {
|
|
3085
|
-
return entry.value;
|
|
3086
|
-
}
|
|
3087
|
-
return null;
|
|
3088
|
-
}
|
|
3089
|
-
function useTextures() {
|
|
2347
|
+
function useTextures(selector) {
|
|
3090
2348
|
const store = useStore();
|
|
3091
|
-
|
|
3092
|
-
const set = store.setState;
|
|
2349
|
+
const registry = React.useMemo(() => {
|
|
3093
2350
|
const getState = store.getState;
|
|
3094
|
-
const
|
|
3095
|
-
|
|
3096
|
-
const newMap = new Map(state.textures);
|
|
3097
|
-
newMap.set(key, value);
|
|
3098
|
-
return { textures: newMap };
|
|
3099
|
-
});
|
|
3100
|
-
};
|
|
3101
|
-
const addMultiple = (items) => {
|
|
3102
|
-
set((state) => {
|
|
3103
|
-
const newMap = new Map(state.textures);
|
|
3104
|
-
const entries = items instanceof Map ? items.entries() : Object.entries(items);
|
|
3105
|
-
for (const [key, value] of entries) {
|
|
3106
|
-
newMap.set(key, value);
|
|
3107
|
-
}
|
|
3108
|
-
return { textures: newMap };
|
|
3109
|
-
});
|
|
3110
|
-
};
|
|
3111
|
-
const remove = (key) => {
|
|
3112
|
-
set((state) => {
|
|
3113
|
-
const newMap = new Map(state.textures);
|
|
3114
|
-
newMap.delete(key);
|
|
3115
|
-
return { textures: newMap };
|
|
3116
|
-
});
|
|
3117
|
-
};
|
|
3118
|
-
const removeMultiple = (keys) => {
|
|
3119
|
-
set((state) => {
|
|
3120
|
-
const newMap = new Map(state.textures);
|
|
3121
|
-
for (const key of keys) newMap.delete(key);
|
|
3122
|
-
return { textures: newMap };
|
|
3123
|
-
});
|
|
3124
|
-
};
|
|
3125
|
-
const dispose = (key) => {
|
|
3126
|
-
const entry = getState().textures.get(key);
|
|
3127
|
-
if (entry) {
|
|
3128
|
-
const tex = getTextureValue(entry);
|
|
3129
|
-
tex?.dispose();
|
|
3130
|
-
}
|
|
3131
|
-
remove(key);
|
|
3132
|
-
};
|
|
3133
|
-
const disposeMultiple = (keys) => {
|
|
3134
|
-
const textures = getState().textures;
|
|
3135
|
-
for (const key of keys) {
|
|
3136
|
-
const entry = textures.get(key);
|
|
3137
|
-
if (entry) {
|
|
3138
|
-
const tex = getTextureValue(entry);
|
|
3139
|
-
tex?.dispose();
|
|
3140
|
-
}
|
|
3141
|
-
}
|
|
3142
|
-
removeMultiple(keys);
|
|
3143
|
-
};
|
|
3144
|
-
const disposeAll = () => {
|
|
3145
|
-
const textures = getState().textures;
|
|
3146
|
-
for (const entry of textures.values()) {
|
|
3147
|
-
const tex = getTextureValue(entry);
|
|
3148
|
-
tex?.dispose();
|
|
3149
|
-
}
|
|
3150
|
-
set({ textures: /* @__PURE__ */ new Map() });
|
|
3151
|
-
};
|
|
2351
|
+
const setState = store.setState;
|
|
2352
|
+
const getOne = (key) => getState().textures.get(key);
|
|
3152
2353
|
return {
|
|
3153
|
-
|
|
3154
|
-
get textures() {
|
|
2354
|
+
get all() {
|
|
3155
2355
|
return getState().textures;
|
|
3156
2356
|
},
|
|
3157
|
-
|
|
3158
|
-
|
|
2357
|
+
get(input) {
|
|
2358
|
+
if (typeof input === "string") return getOne(input);
|
|
2359
|
+
if (Array.isArray(input)) return input.map(getOne);
|
|
2360
|
+
const out = {};
|
|
2361
|
+
for (const name in input) out[name] = getOne(input[name]);
|
|
2362
|
+
return out;
|
|
2363
|
+
},
|
|
3159
2364
|
has: (key) => getState().textures.has(key),
|
|
3160
|
-
|
|
3161
|
-
|
|
3162
|
-
|
|
3163
|
-
|
|
3164
|
-
|
|
3165
|
-
|
|
3166
|
-
|
|
3167
|
-
|
|
3168
|
-
|
|
3169
|
-
|
|
2365
|
+
add(keyOrRecord, texture) {
|
|
2366
|
+
setState((state) => {
|
|
2367
|
+
const textures = new Map(state.textures);
|
|
2368
|
+
if (typeof keyOrRecord === "string") {
|
|
2369
|
+
textures.set(keyOrRecord, texture);
|
|
2370
|
+
} else {
|
|
2371
|
+
for (const key in keyOrRecord) textures.set(key, keyOrRecord[key]);
|
|
2372
|
+
}
|
|
2373
|
+
return { textures };
|
|
2374
|
+
});
|
|
2375
|
+
},
|
|
2376
|
+
dispose(key, options) {
|
|
2377
|
+
const state = getState();
|
|
2378
|
+
const refs = state._textureRefs.get(key) ?? 0;
|
|
2379
|
+
if (refs > 0 && !options?.force) {
|
|
2380
|
+
console.warn(
|
|
2381
|
+
`[useTextures] "${key}" still has ${refs} active reference(s); skipping dispose. Pass { force: true } to override.`
|
|
2382
|
+
);
|
|
2383
|
+
return false;
|
|
2384
|
+
}
|
|
2385
|
+
state.textures.get(key)?.dispose();
|
|
2386
|
+
setState((s) => {
|
|
2387
|
+
const textures = new Map(s.textures);
|
|
2388
|
+
textures.delete(key);
|
|
2389
|
+
const nextRefs = new Map(s._textureRefs);
|
|
2390
|
+
nextRefs.delete(key);
|
|
2391
|
+
return { textures, _textureRefs: nextRefs };
|
|
2392
|
+
});
|
|
2393
|
+
return true;
|
|
2394
|
+
},
|
|
2395
|
+
disposeAll() {
|
|
2396
|
+
for (const texture of getState().textures.values()) texture.dispose();
|
|
2397
|
+
setState({ textures: /* @__PURE__ */ new Map(), _textureRefs: /* @__PURE__ */ new Map() });
|
|
2398
|
+
}
|
|
3170
2399
|
};
|
|
3171
2400
|
}, [store]);
|
|
2401
|
+
const subscribe = selector ? () => selector(registry) : (state) => state.textures;
|
|
2402
|
+
const selected = useThree(subscribe);
|
|
2403
|
+
return selector ? selected : registry;
|
|
3172
2404
|
}
|
|
3173
2405
|
|
|
3174
2406
|
function useRenderTarget(widthOrOptions, heightOrOptions, options) {
|
|
@@ -3223,7 +2455,7 @@ function addEffect(callback) {
|
|
|
3223
2455
|
link: "https://docs.pmnd.rs/react-three-fiber/api/hooks#useframe"
|
|
3224
2456
|
});
|
|
3225
2457
|
const id = `legacy_effect_${effectId++}`;
|
|
3226
|
-
return getScheduler().registerGlobal("before", id, callback);
|
|
2458
|
+
return scheduler.getScheduler().registerGlobal("before", id, callback);
|
|
3227
2459
|
}
|
|
3228
2460
|
function addAfterEffect(callback) {
|
|
3229
2461
|
notifyDepreciated({
|
|
@@ -3232,7 +2464,7 @@ function addAfterEffect(callback) {
|
|
|
3232
2464
|
link: "https://docs.pmnd.rs/react-three-fiber/api/hooks#useframe"
|
|
3233
2465
|
});
|
|
3234
2466
|
const id = `legacy_afterEffect_${effectId++}`;
|
|
3235
|
-
return getScheduler().registerGlobal("after", id, callback);
|
|
2467
|
+
return scheduler.getScheduler().registerGlobal("after", id, callback);
|
|
3236
2468
|
}
|
|
3237
2469
|
function addTail(callback) {
|
|
3238
2470
|
notifyDepreciated({
|
|
@@ -3240,16 +2472,16 @@ function addTail(callback) {
|
|
|
3240
2472
|
body: "Use scheduler.onIdle(callback) instead.\naddTail will be removed in a future version.",
|
|
3241
2473
|
link: "https://docs.pmnd.rs/react-three-fiber/api/hooks#useframe"
|
|
3242
2474
|
});
|
|
3243
|
-
return getScheduler().onIdle(callback);
|
|
2475
|
+
return scheduler.getScheduler().onIdle(callback);
|
|
3244
2476
|
}
|
|
3245
2477
|
function invalidate(state, frames = 1, stackFrames = false) {
|
|
3246
|
-
getScheduler().invalidate(frames, stackFrames);
|
|
2478
|
+
scheduler.getScheduler().invalidate(frames, stackFrames);
|
|
3247
2479
|
}
|
|
3248
2480
|
function advance(timestamp) {
|
|
3249
|
-
getScheduler().step(timestamp);
|
|
2481
|
+
scheduler.getScheduler().step(timestamp);
|
|
3250
2482
|
}
|
|
3251
2483
|
|
|
3252
|
-
const version = "10.0.0-alpha.
|
|
2484
|
+
const version = "10.0.0-alpha.3";
|
|
3253
2485
|
const packageData = {
|
|
3254
2486
|
version: version};
|
|
3255
2487
|
|
|
@@ -14505,7 +13737,7 @@ function n0() {
|
|
|
14505
13737
|
var t0 = n0();
|
|
14506
13738
|
const r0 = Xb(t0);
|
|
14507
13739
|
|
|
14508
|
-
const t = 1, o = 8, r = 32, e = 2;
|
|
13740
|
+
const t = 1, o = 8, r = 32, e = 2, i$1 = 268435456;
|
|
14509
13741
|
|
|
14510
13742
|
function createReconciler(config) {
|
|
14511
13743
|
const reconciler2 = r0(config);
|
|
@@ -14670,7 +13902,15 @@ function setFiberRef(fiber, publicInstance) {
|
|
|
14670
13902
|
}
|
|
14671
13903
|
}
|
|
14672
13904
|
const reconstructed = [];
|
|
14673
|
-
function
|
|
13905
|
+
function flushReconstructedInstances() {
|
|
13906
|
+
if (reconstructed.length === 0) return;
|
|
13907
|
+
try {
|
|
13908
|
+
swapReconstructedInstances();
|
|
13909
|
+
} finally {
|
|
13910
|
+
reconstructed.length = 0;
|
|
13911
|
+
}
|
|
13912
|
+
}
|
|
13913
|
+
function swapReconstructedInstances() {
|
|
14674
13914
|
for (const [instance] of reconstructed) {
|
|
14675
13915
|
const parent = instance.parent;
|
|
14676
13916
|
if (parent) {
|
|
@@ -14688,6 +13928,9 @@ function swapInstances() {
|
|
|
14688
13928
|
}
|
|
14689
13929
|
}
|
|
14690
13930
|
if (instance.isHidden) unhideInstance(instance);
|
|
13931
|
+
if (isObject3D(instance.object) && !instance.parent) {
|
|
13932
|
+
removeInteractivity(findInitialRoot(instance), instance.object);
|
|
13933
|
+
}
|
|
14691
13934
|
if (instance.object.__r3f) delete instance.object.__r3f;
|
|
14692
13935
|
if (instance.type !== "primitive") disposeOnIdle(instance.object);
|
|
14693
13936
|
}
|
|
@@ -14696,10 +13939,16 @@ function swapInstances() {
|
|
|
14696
13939
|
const parent = instance.parent;
|
|
14697
13940
|
if (parent) {
|
|
14698
13941
|
const target = catalogue[toPascalCase(instance.type)];
|
|
13942
|
+
const prevObject = instance.object;
|
|
14699
13943
|
instance.object = instance.props.object ?? new target(...instance.props.args ?? []);
|
|
14700
13944
|
instance.object.__r3f = instance;
|
|
14701
13945
|
setFiberRef(fiber, instance.object);
|
|
13946
|
+
if (isObject3D(prevObject) && isObject3D(instance.object)) {
|
|
13947
|
+
swapInteractivity(findInitialRoot(instance), prevObject, instance.object);
|
|
13948
|
+
}
|
|
14702
13949
|
delete instance.appliedOnce;
|
|
13950
|
+
instance.eventCount = 0;
|
|
13951
|
+
instance.handlers = {};
|
|
14703
13952
|
applyProps(instance.object, instance.props);
|
|
14704
13953
|
if (instance.props.attach) {
|
|
14705
13954
|
attach(parent, instance);
|
|
@@ -14716,14 +13965,114 @@ function swapInstances() {
|
|
|
14716
13965
|
invalidateInstance(instance);
|
|
14717
13966
|
}
|
|
14718
13967
|
}
|
|
14719
|
-
reconstructed.length = 0;
|
|
14720
13968
|
}
|
|
14721
13969
|
const handleTextInstance = () => {
|
|
14722
13970
|
};
|
|
14723
13971
|
const NO_CONTEXT = {};
|
|
14724
13972
|
let currentUpdatePriority = NoEventPriority;
|
|
14725
|
-
|
|
14726
|
-
|
|
13973
|
+
function getEventPriority(type) {
|
|
13974
|
+
switch (type) {
|
|
13975
|
+
case "beforetoggle":
|
|
13976
|
+
case "cancel":
|
|
13977
|
+
case "click":
|
|
13978
|
+
case "close":
|
|
13979
|
+
case "contextmenu":
|
|
13980
|
+
case "copy":
|
|
13981
|
+
case "cut":
|
|
13982
|
+
case "auxclick":
|
|
13983
|
+
case "dblclick":
|
|
13984
|
+
case "dragend":
|
|
13985
|
+
case "dragstart":
|
|
13986
|
+
case "drop":
|
|
13987
|
+
case "focusin":
|
|
13988
|
+
case "focusout":
|
|
13989
|
+
case "input":
|
|
13990
|
+
case "invalid":
|
|
13991
|
+
case "keydown":
|
|
13992
|
+
case "keypress":
|
|
13993
|
+
case "keyup":
|
|
13994
|
+
case "mousedown":
|
|
13995
|
+
case "mouseup":
|
|
13996
|
+
case "paste":
|
|
13997
|
+
case "pause":
|
|
13998
|
+
case "play":
|
|
13999
|
+
case "pointercancel":
|
|
14000
|
+
case "pointerdown":
|
|
14001
|
+
case "pointerup":
|
|
14002
|
+
case "ratechange":
|
|
14003
|
+
case "reset":
|
|
14004
|
+
case "resize":
|
|
14005
|
+
case "seeked":
|
|
14006
|
+
case "submit":
|
|
14007
|
+
case "touchcancel":
|
|
14008
|
+
case "touchend":
|
|
14009
|
+
case "touchstart":
|
|
14010
|
+
case "volumechange":
|
|
14011
|
+
case "change":
|
|
14012
|
+
case "selectionchange":
|
|
14013
|
+
case "compositionstart":
|
|
14014
|
+
case "compositionend":
|
|
14015
|
+
case "compositionupdate":
|
|
14016
|
+
case "beforeinput":
|
|
14017
|
+
case "blur":
|
|
14018
|
+
case "fullscreenchange":
|
|
14019
|
+
case "focus":
|
|
14020
|
+
case "hashchange":
|
|
14021
|
+
case "popstate":
|
|
14022
|
+
case "select":
|
|
14023
|
+
case "selectstart":
|
|
14024
|
+
return e;
|
|
14025
|
+
case "drag":
|
|
14026
|
+
case "dragenter":
|
|
14027
|
+
case "dragexit":
|
|
14028
|
+
case "dragleave":
|
|
14029
|
+
case "dragover":
|
|
14030
|
+
case "mousemove":
|
|
14031
|
+
case "mouseout":
|
|
14032
|
+
case "mouseover":
|
|
14033
|
+
case "pointermove":
|
|
14034
|
+
case "pointerout":
|
|
14035
|
+
case "pointerover":
|
|
14036
|
+
case "scroll":
|
|
14037
|
+
case "touchmove":
|
|
14038
|
+
case "wheel":
|
|
14039
|
+
case "mouseenter":
|
|
14040
|
+
case "mouseleave":
|
|
14041
|
+
case "pointerenter":
|
|
14042
|
+
case "pointerleave":
|
|
14043
|
+
return o;
|
|
14044
|
+
case "message": {
|
|
14045
|
+
switch (Tb.unstable_getCurrentPriorityLevel()) {
|
|
14046
|
+
case Tb.unstable_ImmediatePriority:
|
|
14047
|
+
return e;
|
|
14048
|
+
case Tb.unstable_UserBlockingPriority:
|
|
14049
|
+
return o;
|
|
14050
|
+
case Tb.unstable_NormalPriority:
|
|
14051
|
+
case Tb.unstable_LowPriority:
|
|
14052
|
+
return r;
|
|
14053
|
+
case Tb.unstable_IdlePriority:
|
|
14054
|
+
return i$1;
|
|
14055
|
+
default:
|
|
14056
|
+
return r;
|
|
14057
|
+
}
|
|
14058
|
+
}
|
|
14059
|
+
default:
|
|
14060
|
+
return r;
|
|
14061
|
+
}
|
|
14062
|
+
}
|
|
14063
|
+
function scheduleMicrotask(callback) {
|
|
14064
|
+
if (typeof queueMicrotask === "function") {
|
|
14065
|
+
queueMicrotask(callback);
|
|
14066
|
+
} else if (typeof Promise !== "undefined") {
|
|
14067
|
+
Promise.resolve().then(callback).catch((error) => {
|
|
14068
|
+
setTimeout(() => {
|
|
14069
|
+
throw error;
|
|
14070
|
+
});
|
|
14071
|
+
});
|
|
14072
|
+
} else {
|
|
14073
|
+
setTimeout(callback);
|
|
14074
|
+
}
|
|
14075
|
+
}
|
|
14727
14076
|
const reconciler = /* @__PURE__ */ createReconciler({
|
|
14728
14077
|
isPrimaryRenderer: false,
|
|
14729
14078
|
warnsIfNotActing: false,
|
|
@@ -14762,16 +14111,15 @@ const reconciler = /* @__PURE__ */ createReconciler({
|
|
|
14762
14111
|
else if (newProps.args?.length !== oldProps.args?.length) reconstruct = true;
|
|
14763
14112
|
else if (newProps.args?.some((value, index) => value !== oldProps.args?.[index])) reconstruct = true;
|
|
14764
14113
|
if (reconstruct) {
|
|
14765
|
-
reconstructed.push([instance,
|
|
14114
|
+
reconstructed.push([instance, getInstanceProps(newProps), fiber]);
|
|
14766
14115
|
} else {
|
|
14767
14116
|
const changedProps = diffProps(instance, newProps);
|
|
14768
|
-
|
|
14769
|
-
|
|
14770
|
-
|
|
14771
|
-
|
|
14117
|
+
const attach2 = instance.props.attach;
|
|
14118
|
+
instance.props = getInstanceProps(newProps);
|
|
14119
|
+
if (attach2 !== void 0) instance.props.attach = attach2;
|
|
14120
|
+
else delete instance.props.attach;
|
|
14121
|
+
if (Object.keys(changedProps).length) applyProps(instance.object, changedProps);
|
|
14772
14122
|
}
|
|
14773
|
-
const isTailSibling = fiber.sibling === null || (fiber.flags & Update) === NoFlags;
|
|
14774
|
-
if (isTailSibling) swapInstances();
|
|
14775
14123
|
},
|
|
14776
14124
|
finalizeInitialChildren: (instance) => {
|
|
14777
14125
|
for (const prop in instance.props) {
|
|
@@ -14796,8 +14144,9 @@ const reconciler = /* @__PURE__ */ createReconciler({
|
|
|
14796
14144
|
const target = container.getState().internal.container ?? container.getState().scene;
|
|
14797
14145
|
return prepare(target, container, "", {});
|
|
14798
14146
|
},
|
|
14799
|
-
|
|
14800
|
-
|
|
14147
|
+
// Reconstructed instances are swapped once all mutations are committed,
|
|
14148
|
+
// before layout effects run so refs point to the new objects
|
|
14149
|
+
resetAfterCommit: flushReconstructedInstances,
|
|
14801
14150
|
shouldSetTextContent: () => false,
|
|
14802
14151
|
clearContainer: () => false,
|
|
14803
14152
|
hideInstance,
|
|
@@ -14805,6 +14154,9 @@ const reconciler = /* @__PURE__ */ createReconciler({
|
|
|
14805
14154
|
createTextInstance: handleTextInstance,
|
|
14806
14155
|
hideTextInstance: handleTextInstance,
|
|
14807
14156
|
unhideTextInstance: handleTextInstance,
|
|
14157
|
+
// Mirrors react-dom, which flushes discrete work in microtasks
|
|
14158
|
+
supportsMicrotasks: true,
|
|
14159
|
+
scheduleMicrotask,
|
|
14808
14160
|
scheduleTimeout: typeof setTimeout === "function" ? setTimeout : void 0,
|
|
14809
14161
|
cancelTimeout: typeof clearTimeout === "function" ? clearTimeout : void 0,
|
|
14810
14162
|
noTimeout: -1,
|
|
@@ -14845,28 +14197,9 @@ const reconciler = /* @__PURE__ */ createReconciler({
|
|
|
14845
14197
|
},
|
|
14846
14198
|
resolveUpdatePriority() {
|
|
14847
14199
|
if (currentUpdatePriority !== NoEventPriority) return currentUpdatePriority;
|
|
14848
|
-
|
|
14849
|
-
|
|
14850
|
-
|
|
14851
|
-
case "dblclick":
|
|
14852
|
-
case "dragenter":
|
|
14853
|
-
case "dragleave":
|
|
14854
|
-
case "drop":
|
|
14855
|
-
case "pointercancel":
|
|
14856
|
-
case "pointerdown":
|
|
14857
|
-
case "pointerup":
|
|
14858
|
-
return e;
|
|
14859
|
-
case "dragover":
|
|
14860
|
-
case "pointermove":
|
|
14861
|
-
case "pointerout":
|
|
14862
|
-
case "pointerover":
|
|
14863
|
-
case "pointerenter":
|
|
14864
|
-
case "pointerleave":
|
|
14865
|
-
case "wheel":
|
|
14866
|
-
return o;
|
|
14867
|
-
default:
|
|
14868
|
-
return r;
|
|
14869
|
-
}
|
|
14200
|
+
const eventType = typeof window !== "undefined" ? window.event?.type : void 0;
|
|
14201
|
+
if (eventType === void 0) return r;
|
|
14202
|
+
return getEventPriority(eventType);
|
|
14870
14203
|
},
|
|
14871
14204
|
resetFormInstance() {
|
|
14872
14205
|
},
|
|
@@ -14915,11 +14248,15 @@ const reconciler = /* @__PURE__ */ createReconciler({
|
|
|
14915
14248
|
cloneMutableTextInstance(textInstance) {
|
|
14916
14249
|
return textInstance;
|
|
14917
14250
|
},
|
|
14251
|
+
// React 19.2 added these for root-level view transitions. They only run for hosts that opt into
|
|
14252
|
+
// view transitions by returning a container from cloneRootViewTransitionContainer; R3F renders
|
|
14253
|
+
// into a WebGL/WebGPU canvas, where cloning the root has no meaning, so we never opt in and
|
|
14254
|
+
// React never calls them. They stay as no-ops rather than throws: the host config is shared by
|
|
14255
|
+
// every root, and a throw here would turn a React-internal probe into a hard crash for users.
|
|
14256
|
+
// Guarded by a test asserting a full render + unmount never reaches them.
|
|
14918
14257
|
cloneRootViewTransitionContainer(_rootContainer) {
|
|
14919
|
-
throw new Error("Not implemented.");
|
|
14920
14258
|
},
|
|
14921
14259
|
removeRootViewTransitionClone(_rootContainer, _clone) {
|
|
14922
|
-
throw new Error("Not implemented.");
|
|
14923
14260
|
},
|
|
14924
14261
|
// https://github.com/facebook/react/pull/32465
|
|
14925
14262
|
createFragmentInstance: (_fiber) => null,
|
|
@@ -15004,6 +14341,7 @@ function createRoot(canvas) {
|
|
|
15004
14341
|
const lastConfiguredProps = {};
|
|
15005
14342
|
let configured = false;
|
|
15006
14343
|
let pending = null;
|
|
14344
|
+
let rendererSetup = null;
|
|
15007
14345
|
return {
|
|
15008
14346
|
async configure(props = {}) {
|
|
15009
14347
|
let resolve;
|
|
@@ -15019,7 +14357,6 @@ function createRoot(canvas) {
|
|
|
15019
14357
|
events,
|
|
15020
14358
|
onCreated: onCreatedCallback,
|
|
15021
14359
|
shadows = false,
|
|
15022
|
-
textureColorSpace = webgpu.SRGBColorSpace,
|
|
15023
14360
|
orthographic = false,
|
|
15024
14361
|
frameloop = "always",
|
|
15025
14362
|
dpr = [1, 2],
|
|
@@ -15034,7 +14371,8 @@ function createRoot(canvas) {
|
|
|
15034
14371
|
_sizeProps,
|
|
15035
14372
|
forceEven
|
|
15036
14373
|
} = props;
|
|
15037
|
-
const
|
|
14374
|
+
const textureColorSpace = is.obj(glConfig) && !is.fun(glConfig) && !isRenderer(glConfig) && glConfig.textureColorSpace || is.obj(rendererConfig) && !is.fun(rendererConfig) && !isRenderer(rendererConfig) && rendererConfig.textureColorSpace || webgpu.SRGBColorSpace;
|
|
14375
|
+
let state = store.getState();
|
|
15038
14376
|
const defaultGPUProps = {
|
|
15039
14377
|
canvas,
|
|
15040
14378
|
antialias: true
|
|
@@ -15048,46 +14386,64 @@ function createRoot(canvas) {
|
|
|
15048
14386
|
throw new Error("Cannot use both gl and renderer props at the same time");
|
|
15049
14387
|
}
|
|
15050
14388
|
let renderer = state.internal.actualRenderer;
|
|
15051
|
-
if (
|
|
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
|
-
|
|
15077
|
-
|
|
15078
|
-
|
|
15079
|
-
|
|
15080
|
-
|
|
15081
|
-
|
|
15082
|
-
|
|
15083
|
-
|
|
15084
|
-
|
|
15085
|
-
|
|
15086
|
-
|
|
15087
|
-
|
|
14389
|
+
if (!state.internal.actualRenderer) {
|
|
14390
|
+
if (!rendererSetup) {
|
|
14391
|
+
rendererSetup = (async () => {
|
|
14392
|
+
if (primaryCanvas) {
|
|
14393
|
+
const primary = await waitForPrimary(primaryCanvas);
|
|
14394
|
+
renderer = primary.renderer;
|
|
14395
|
+
state.internal.actualRenderer = renderer;
|
|
14396
|
+
const canvasTarget = new webgpu.CanvasTarget(canvas);
|
|
14397
|
+
primary.store.setState((prev) => ({
|
|
14398
|
+
internal: { ...prev.internal, isMultiCanvas: true }
|
|
14399
|
+
}));
|
|
14400
|
+
state.set((prev) => ({
|
|
14401
|
+
webGPUSupported: primary.store.getState().webGPUSupported,
|
|
14402
|
+
renderer,
|
|
14403
|
+
primaryStore: primary.store,
|
|
14404
|
+
internal: {
|
|
14405
|
+
...prev.internal,
|
|
14406
|
+
canvasTarget,
|
|
14407
|
+
isMultiCanvas: true,
|
|
14408
|
+
isSecondary: true,
|
|
14409
|
+
targetId: primaryCanvas
|
|
14410
|
+
}
|
|
14411
|
+
}));
|
|
14412
|
+
} else {
|
|
14413
|
+
renderer = await resolveRenderer(rendererConfig, defaultGPUProps, webgpu.WebGPURenderer);
|
|
14414
|
+
if (!renderer.hasInitialized?.()) {
|
|
14415
|
+
const size2 = computeInitialSize(canvas, propsSize);
|
|
14416
|
+
if (size2.width > 0 && size2.height > 0) {
|
|
14417
|
+
const pixelRatio = calculateDpr(dpr);
|
|
14418
|
+
canvas.width = size2.width * pixelRatio;
|
|
14419
|
+
canvas.height = size2.height * pixelRatio;
|
|
14420
|
+
}
|
|
14421
|
+
await renderer.init();
|
|
14422
|
+
}
|
|
14423
|
+
const backend = renderer.backend;
|
|
14424
|
+
const isWebGPUBackend = backend && "isWebGPUBackend" in backend;
|
|
14425
|
+
state.internal.actualRenderer = renderer;
|
|
14426
|
+
state.set({ webGPUSupported: isWebGPUBackend, renderer, primaryStore: store });
|
|
14427
|
+
if (canvasId && !state.internal.isSecondary) {
|
|
14428
|
+
const canvasTarget = new webgpu.CanvasTarget(canvas);
|
|
14429
|
+
const unregisterPrimary = registerPrimary(canvasId, renderer, store);
|
|
14430
|
+
state.set((prev) => ({
|
|
14431
|
+
internal: {
|
|
14432
|
+
...prev.internal,
|
|
14433
|
+
canvasTarget,
|
|
14434
|
+
unregisterPrimary
|
|
14435
|
+
}
|
|
14436
|
+
}));
|
|
14437
|
+
}
|
|
15088
14438
|
}
|
|
15089
|
-
}))
|
|
14439
|
+
})().catch((err) => {
|
|
14440
|
+
rendererSetup = null;
|
|
14441
|
+
throw err;
|
|
14442
|
+
});
|
|
15090
14443
|
}
|
|
14444
|
+
await rendererSetup;
|
|
14445
|
+
state = store.getState();
|
|
14446
|
+
renderer = state.internal.actualRenderer;
|
|
15091
14447
|
}
|
|
15092
14448
|
let raycaster = state.raycaster;
|
|
15093
14449
|
if (!raycaster) state.set({ raycaster: raycaster = new webgpu.Raycaster() });
|
|
@@ -15180,7 +14536,7 @@ function createRoot(canvas) {
|
|
|
15180
14536
|
lastConfiguredProps.performance = performance;
|
|
15181
14537
|
}
|
|
15182
14538
|
if (!state.xr) {
|
|
15183
|
-
const handleXRFrame = (timestamp,
|
|
14539
|
+
const handleXRFrame = (timestamp, _frame) => {
|
|
15184
14540
|
const state2 = store.getState();
|
|
15185
14541
|
if (state2.frameloop === "never") return;
|
|
15186
14542
|
advance(timestamp);
|
|
@@ -15216,15 +14572,22 @@ function createRoot(canvas) {
|
|
|
15216
14572
|
const oldType = renderer.shadowMap.type;
|
|
15217
14573
|
renderer.shadowMap.enabled = !!shadows;
|
|
15218
14574
|
if (is.boo(shadows)) {
|
|
15219
|
-
renderer.shadowMap.type = webgpu.
|
|
14575
|
+
renderer.shadowMap.type = webgpu.PCFShadowMap;
|
|
15220
14576
|
} else if (is.str(shadows)) {
|
|
14577
|
+
if (shadows === "soft") {
|
|
14578
|
+
notifyDepreciated({
|
|
14579
|
+
heading: 'shadows="soft" is deprecated',
|
|
14580
|
+
body: "Three has depreciated soft and improved basic PCFShadows, we converted for you.",
|
|
14581
|
+
link: "https://github.com/mrdoob/three.js/wiki/Migration-Guide?utm_source=chatgpt.com#181--182"
|
|
14582
|
+
});
|
|
14583
|
+
}
|
|
15221
14584
|
const types = {
|
|
15222
14585
|
basic: webgpu.BasicShadowMap,
|
|
15223
14586
|
percentage: webgpu.PCFShadowMap,
|
|
15224
|
-
soft: webgpu.
|
|
14587
|
+
soft: webgpu.PCFShadowMap,
|
|
15225
14588
|
variance: webgpu.VSMShadowMap
|
|
15226
14589
|
};
|
|
15227
|
-
renderer.shadowMap.type = types[shadows] ?? webgpu.
|
|
14590
|
+
renderer.shadowMap.type = types[shadows] ?? webgpu.PCFShadowMap;
|
|
15228
14591
|
} else if (is.obj(shadows)) {
|
|
15229
14592
|
Object.assign(renderer.shadowMap, shadows);
|
|
15230
14593
|
}
|
|
@@ -15240,24 +14603,35 @@ function createRoot(canvas) {
|
|
|
15240
14603
|
if (state.textureColorSpace !== textureColorSpace) state.set(() => ({ textureColorSpace }));
|
|
15241
14604
|
lastConfiguredProps.textureColorSpace = textureColorSpace;
|
|
15242
14605
|
}
|
|
14606
|
+
const r3fProps = ["textureColorSpace"];
|
|
14607
|
+
const constructorOnlyProps = ["samples", "antialias", "alpha", "canvas", "powerPreference"];
|
|
14608
|
+
const nonApplyProps = [...r3fProps, ...constructorOnlyProps];
|
|
15243
14609
|
if (glConfig && !is.fun(glConfig) && !isRenderer(glConfig) && !is.equ(glConfig, renderer, shallowLoose)) {
|
|
15244
|
-
|
|
14610
|
+
const glProps = {};
|
|
14611
|
+
for (const key in glConfig) {
|
|
14612
|
+
if (!nonApplyProps.includes(key)) glProps[key] = glConfig[key];
|
|
14613
|
+
}
|
|
14614
|
+
applyProps(renderer, glProps);
|
|
15245
14615
|
}
|
|
15246
14616
|
if (rendererConfig && !is.fun(rendererConfig) && !isRenderer(rendererConfig) && state.renderer) {
|
|
15247
14617
|
const currentRenderer = state.renderer;
|
|
15248
14618
|
if (!is.equ(rendererConfig, currentRenderer, shallowLoose)) {
|
|
15249
|
-
|
|
14619
|
+
const rendererProps = {};
|
|
14620
|
+
for (const key in rendererConfig) {
|
|
14621
|
+
if (!nonApplyProps.includes(key)) rendererProps[key] = rendererConfig[key];
|
|
14622
|
+
}
|
|
14623
|
+
applyProps(currentRenderer, rendererProps);
|
|
15250
14624
|
}
|
|
15251
14625
|
}
|
|
15252
|
-
const scheduler = getScheduler();
|
|
14626
|
+
const scheduler$1 = scheduler.getScheduler();
|
|
15253
14627
|
const rootId = state.internal.rootId;
|
|
15254
14628
|
if (!rootId) {
|
|
15255
|
-
const newRootId = canvasId || scheduler.generateRootId();
|
|
15256
|
-
const unregisterRoot = scheduler.registerRoot(newRootId, {
|
|
14629
|
+
const newRootId = canvasId || scheduler$1.generateRootId();
|
|
14630
|
+
const unregisterRoot = scheduler$1.registerRoot(newRootId, {
|
|
15257
14631
|
getState: () => store.getState(),
|
|
15258
14632
|
onError: (err) => store.getState().setError(err)
|
|
15259
14633
|
});
|
|
15260
|
-
const unregisterCanvasTarget = scheduler.register(
|
|
14634
|
+
const unregisterCanvasTarget = scheduler$1.register(
|
|
15261
14635
|
() => {
|
|
15262
14636
|
const state2 = store.getState();
|
|
15263
14637
|
if (state2.internal.isMultiCanvas && state2.internal.canvasTarget) {
|
|
@@ -15272,7 +14646,19 @@ function createRoot(canvas) {
|
|
|
15272
14646
|
system: true
|
|
15273
14647
|
}
|
|
15274
14648
|
);
|
|
15275
|
-
const
|
|
14649
|
+
const unregisterEventsFlush = scheduler$1.register(
|
|
14650
|
+
() => {
|
|
14651
|
+
const state2 = store.getState();
|
|
14652
|
+
state2.events.flush?.();
|
|
14653
|
+
},
|
|
14654
|
+
{
|
|
14655
|
+
id: `${newRootId}_events`,
|
|
14656
|
+
rootId: newRootId,
|
|
14657
|
+
phase: "input",
|
|
14658
|
+
system: true
|
|
14659
|
+
}
|
|
14660
|
+
);
|
|
14661
|
+
const unregisterFrustum = scheduler$1.register(
|
|
15276
14662
|
() => {
|
|
15277
14663
|
const state2 = store.getState();
|
|
15278
14664
|
if (state2.autoUpdateFrustum && state2.camera) {
|
|
@@ -15282,11 +14668,11 @@ function createRoot(canvas) {
|
|
|
15282
14668
|
{
|
|
15283
14669
|
id: `${newRootId}_frustum`,
|
|
15284
14670
|
rootId: newRootId,
|
|
15285
|
-
|
|
14671
|
+
before: "render",
|
|
15286
14672
|
system: true
|
|
15287
14673
|
}
|
|
15288
14674
|
);
|
|
15289
|
-
const unregisterVisibility = scheduler.register(
|
|
14675
|
+
const unregisterVisibility = scheduler$1.register(
|
|
15290
14676
|
() => {
|
|
15291
14677
|
const state2 = store.getState();
|
|
15292
14678
|
checkVisibility(state2);
|
|
@@ -15294,19 +14680,19 @@ function createRoot(canvas) {
|
|
|
15294
14680
|
{
|
|
15295
14681
|
id: `${newRootId}_visibility`,
|
|
15296
14682
|
rootId: newRootId,
|
|
15297
|
-
|
|
14683
|
+
before: "render",
|
|
15298
14684
|
system: true,
|
|
15299
14685
|
after: `${newRootId}_frustum`
|
|
15300
14686
|
}
|
|
15301
14687
|
);
|
|
15302
|
-
const unregisterRender = scheduler.register(
|
|
14688
|
+
const unregisterRender = scheduler$1.register(
|
|
15303
14689
|
() => {
|
|
15304
14690
|
const state2 = store.getState();
|
|
15305
14691
|
const renderer2 = state2.internal.actualRenderer;
|
|
15306
|
-
const userHandlesRender = scheduler.hasUserJobsInPhase("render", newRootId);
|
|
14692
|
+
const userHandlesRender = scheduler$1.hasUserJobsInPhase("render", newRootId);
|
|
15307
14693
|
if (userHandlesRender || state2.internal.priority) return;
|
|
15308
14694
|
try {
|
|
15309
|
-
if (state2.
|
|
14695
|
+
if (state2.renderPipeline?.render) state2.renderPipeline.render();
|
|
15310
14696
|
else if (renderer2?.render) renderer2.render(state2.scene, state2.camera);
|
|
15311
14697
|
} catch (error) {
|
|
15312
14698
|
state2.setError(error instanceof Error ? error : new Error(String(error)));
|
|
@@ -15331,15 +14717,16 @@ function createRoot(canvas) {
|
|
|
15331
14717
|
unregisterRoot: () => {
|
|
15332
14718
|
unregisterRoot();
|
|
15333
14719
|
unregisterCanvasTarget();
|
|
14720
|
+
unregisterEventsFlush();
|
|
15334
14721
|
unregisterFrustum();
|
|
15335
14722
|
unregisterVisibility();
|
|
15336
14723
|
unregisterRender();
|
|
15337
14724
|
},
|
|
15338
|
-
scheduler
|
|
14725
|
+
scheduler: scheduler$1
|
|
15339
14726
|
}
|
|
15340
14727
|
}));
|
|
15341
14728
|
}
|
|
15342
|
-
scheduler.frameloop = frameloop;
|
|
14729
|
+
scheduler$1.frameloop = frameloop;
|
|
15343
14730
|
onCreated = onCreatedCallback;
|
|
15344
14731
|
configured = true;
|
|
15345
14732
|
resolve();
|
|
@@ -15406,7 +14793,8 @@ function unmountComponentAtNode(canvas, callback) {
|
|
|
15406
14793
|
dispose(state.scene);
|
|
15407
14794
|
_roots.delete(canvas);
|
|
15408
14795
|
if (callback) callback(canvas);
|
|
15409
|
-
} catch {
|
|
14796
|
+
} catch (error) {
|
|
14797
|
+
console.warn("[R3F] Error while unmounting root; teardown may be incomplete:", error);
|
|
15410
14798
|
}
|
|
15411
14799
|
}, 500);
|
|
15412
14800
|
}
|
|
@@ -15469,6 +14857,7 @@ function PortalInner({ state = {}, children, container }) {
|
|
|
15469
14857
|
viewport = rootState.viewport.getCurrentViewport(camera, new webgpu.Vector3(), resolvedSize);
|
|
15470
14858
|
if (camera !== rootState.camera) updateCamera(camera, resolvedSize);
|
|
15471
14859
|
}
|
|
14860
|
+
const set = injectState.set;
|
|
15472
14861
|
return {
|
|
15473
14862
|
// The intersect consists of the previous root state
|
|
15474
14863
|
...rootState,
|
|
@@ -15487,7 +14876,7 @@ function PortalInner({ state = {}, children, container }) {
|
|
|
15487
14876
|
size: resolvedSize,
|
|
15488
14877
|
viewport: { ...rootState.viewport, ...viewport },
|
|
15489
14878
|
// Layers are allowed to override events
|
|
15490
|
-
setEvents: (events2) =>
|
|
14879
|
+
setEvents: (events2) => set((state2) => ({ ...state2, events: { ...state2.events, ...events2 } })),
|
|
15491
14880
|
// Container for child attachment - the portalScene (injected or container itself)
|
|
15492
14881
|
internal: { ...rootState.internal, ...injectState.internal, container: portalScene }
|
|
15493
14882
|
};
|
|
@@ -15496,9 +14885,13 @@ function PortalInner({ state = {}, children, container }) {
|
|
|
15496
14885
|
const store = traditional.createWithEqualityFn((set, get) => ({ ...rest, set, get }));
|
|
15497
14886
|
const onMutate = (prev) => store.setState((state2) => inject.current(prev, state2));
|
|
15498
14887
|
onMutate(previousRoot.getState());
|
|
15499
|
-
previousRoot.subscribe(onMutate);
|
|
15500
14888
|
return store;
|
|
15501
14889
|
}, [previousRoot, container]);
|
|
14890
|
+
useIsomorphicLayoutEffect(() => {
|
|
14891
|
+
const onMutate = (prev) => usePortalStore.setState((state2) => inject.current(prev, state2));
|
|
14892
|
+
const unsubscribe = previousRoot.subscribe(onMutate);
|
|
14893
|
+
return unsubscribe;
|
|
14894
|
+
}, [previousRoot, usePortalStore]);
|
|
15502
14895
|
return (
|
|
15503
14896
|
// @ts-ignore, reconciler types are not maintained
|
|
15504
14897
|
/* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: reconciler.createPortal(
|
|
@@ -15512,6 +14905,178 @@ function flushSync(fn) {
|
|
|
15512
14905
|
return reconciler.flushSyncFromReconciler(fn);
|
|
15513
14906
|
}
|
|
15514
14907
|
|
|
14908
|
+
function parseBackground(background) {
|
|
14909
|
+
if (!background) return null;
|
|
14910
|
+
if (typeof background === "object" && !background.isColor) {
|
|
14911
|
+
const { backgroundMap, envMap, files, preset, ...rest } = background;
|
|
14912
|
+
return {
|
|
14913
|
+
...rest,
|
|
14914
|
+
preset,
|
|
14915
|
+
files: envMap || files,
|
|
14916
|
+
backgroundFiles: backgroundMap,
|
|
14917
|
+
background: true
|
|
14918
|
+
};
|
|
14919
|
+
}
|
|
14920
|
+
if (typeof background === "number") {
|
|
14921
|
+
return { color: background, background: true };
|
|
14922
|
+
}
|
|
14923
|
+
if (typeof background === "string") {
|
|
14924
|
+
if (background in presetsObj) {
|
|
14925
|
+
return { preset: background, background: true };
|
|
14926
|
+
}
|
|
14927
|
+
if (/^(https?:\/\/|\/|\.\/|\.\.\/)|\.(hdr|exr|jpg|jpeg|png|webp|gif)$/i.test(background)) {
|
|
14928
|
+
return { files: background, background: true };
|
|
14929
|
+
}
|
|
14930
|
+
return { color: background, background: true };
|
|
14931
|
+
}
|
|
14932
|
+
if (background.isColor) {
|
|
14933
|
+
return { color: background, background: true };
|
|
14934
|
+
}
|
|
14935
|
+
return null;
|
|
14936
|
+
}
|
|
14937
|
+
|
|
14938
|
+
function parseRendererConfig(rendererProp) {
|
|
14939
|
+
const isRendererConfig = typeof rendererProp === "object" && rendererProp !== null && !("render" in rendererProp) && ("primaryCanvas" in rendererProp || "scheduler" in rendererProp);
|
|
14940
|
+
if (isRendererConfig) {
|
|
14941
|
+
const { primaryCanvas: pc, scheduler: sc, ...rest } = rendererProp;
|
|
14942
|
+
return {
|
|
14943
|
+
primaryCanvas: pc,
|
|
14944
|
+
scheduler: sc,
|
|
14945
|
+
renderer: Object.keys(rest).length > 0 ? rest : rendererProp
|
|
14946
|
+
};
|
|
14947
|
+
}
|
|
14948
|
+
return { primaryCanvas: void 0, scheduler: void 0, renderer: rendererProp };
|
|
14949
|
+
}
|
|
14950
|
+
|
|
14951
|
+
const RESOURCE_KINDS = ["nodes", "uniforms", "buffers", "gpuStorage"];
|
|
14952
|
+
const ROOT_SCOPE = "";
|
|
14953
|
+
const registries = /* @__PURE__ */ new WeakMap();
|
|
14954
|
+
function getKindRegistry(store, kind) {
|
|
14955
|
+
let registry = registries.get(store);
|
|
14956
|
+
if (!registry) {
|
|
14957
|
+
registry = {
|
|
14958
|
+
nodes: { valid: /* @__PURE__ */ new Set(), staged: /* @__PURE__ */ new Map() },
|
|
14959
|
+
uniforms: { valid: /* @__PURE__ */ new Set(), staged: /* @__PURE__ */ new Map() },
|
|
14960
|
+
buffers: { valid: /* @__PURE__ */ new Set(), staged: /* @__PURE__ */ new Map() },
|
|
14961
|
+
gpuStorage: { valid: /* @__PURE__ */ new Set(), staged: /* @__PURE__ */ new Map() }
|
|
14962
|
+
};
|
|
14963
|
+
registries.set(store, registry);
|
|
14964
|
+
}
|
|
14965
|
+
return registry[kind];
|
|
14966
|
+
}
|
|
14967
|
+
function resolvePrimary$1(store) {
|
|
14968
|
+
return store.getState().primaryStore ?? store;
|
|
14969
|
+
}
|
|
14970
|
+
function isScopeValid(store, kind, scopeKey) {
|
|
14971
|
+
return getKindRegistry(store, kind).valid.has(scopeKey);
|
|
14972
|
+
}
|
|
14973
|
+
function peekStaged(store, kind, scopeKey) {
|
|
14974
|
+
return getKindRegistry(store, kind).staged.get(scopeKey);
|
|
14975
|
+
}
|
|
14976
|
+
function stageEntries(store, kind, scopeKey, entries) {
|
|
14977
|
+
const { staged } = getKindRegistry(store, kind);
|
|
14978
|
+
const current = staged.get(scopeKey);
|
|
14979
|
+
if (current) Object.assign(current, entries);
|
|
14980
|
+
else staged.set(scopeKey, { ...entries });
|
|
14981
|
+
}
|
|
14982
|
+
function readCommittedScope(map, scopeKey, isLeaf) {
|
|
14983
|
+
if (scopeKey === ROOT_SCOPE) {
|
|
14984
|
+
const leaves = {};
|
|
14985
|
+
for (const [key, value] of Object.entries(map)) if (isLeaf(value)) leaves[key] = value;
|
|
14986
|
+
return leaves;
|
|
14987
|
+
}
|
|
14988
|
+
const scopeData = map[scopeKey];
|
|
14989
|
+
if (scopeData && typeof scopeData === "object" && !isLeaf(scopeData)) return scopeData;
|
|
14990
|
+
return {};
|
|
14991
|
+
}
|
|
14992
|
+
function withStagedOverlay(store, kind, committed) {
|
|
14993
|
+
const { staged } = getKindRegistry(store, kind);
|
|
14994
|
+
if (staged.size === 0) return committed;
|
|
14995
|
+
const view = { ...committed };
|
|
14996
|
+
for (const [scopeKey, entries] of staged) {
|
|
14997
|
+
if (scopeKey === ROOT_SCOPE) Object.assign(view, entries);
|
|
14998
|
+
else view[scopeKey] = { ...view[scopeKey], ...entries };
|
|
14999
|
+
}
|
|
15000
|
+
return view;
|
|
15001
|
+
}
|
|
15002
|
+
function flushStagedScope(store, kind, scopeKey, isLeaf) {
|
|
15003
|
+
const registry = getKindRegistry(store, kind);
|
|
15004
|
+
const staged = registry.staged.get(scopeKey);
|
|
15005
|
+
if (!staged) return;
|
|
15006
|
+
registry.staged.delete(scopeKey);
|
|
15007
|
+
const replace = !registry.valid.has(scopeKey);
|
|
15008
|
+
store.setState((state) => {
|
|
15009
|
+
const map = state[kind];
|
|
15010
|
+
if (scopeKey === ROOT_SCOPE) {
|
|
15011
|
+
if (!replace) return { [kind]: { ...map, ...staged } };
|
|
15012
|
+
const next = {};
|
|
15013
|
+
for (const [key, value] of Object.entries(map)) if (!isLeaf(value)) next[key] = value;
|
|
15014
|
+
return { [kind]: { ...next, ...staged } };
|
|
15015
|
+
}
|
|
15016
|
+
const current = replace ? {} : map[scopeKey] ?? {};
|
|
15017
|
+
return { [kind]: { ...map, [scopeKey]: { ...current, ...staged } } };
|
|
15018
|
+
});
|
|
15019
|
+
registry.valid.add(scopeKey);
|
|
15020
|
+
}
|
|
15021
|
+
function invalidateResource(store, kind, scopeKey) {
|
|
15022
|
+
const registry = getKindRegistry(store, kind);
|
|
15023
|
+
if (scopeKey === void 0) {
|
|
15024
|
+
registry.valid.clear();
|
|
15025
|
+
registry.staged.clear();
|
|
15026
|
+
} else {
|
|
15027
|
+
registry.valid.delete(scopeKey);
|
|
15028
|
+
registry.staged.delete(scopeKey);
|
|
15029
|
+
}
|
|
15030
|
+
}
|
|
15031
|
+
function rebuildResource(store, kind, userScope) {
|
|
15032
|
+
const primary = resolvePrimary$1(store);
|
|
15033
|
+
invalidateResource(primary, kind, userScope === "root" ? ROOT_SCOPE : userScope);
|
|
15034
|
+
primary.setState((state) => ({ _hmrVersion: state._hmrVersion + 1 }));
|
|
15035
|
+
}
|
|
15036
|
+
function invalidateAllResources(store) {
|
|
15037
|
+
for (const kind of RESOURCE_KINDS) invalidateResource(store, kind);
|
|
15038
|
+
}
|
|
15039
|
+
function removeResourceEntries(store, kind, names, scope, isLeaf) {
|
|
15040
|
+
const staged = peekStaged(store, kind, scope ?? ROOT_SCOPE);
|
|
15041
|
+
if (staged) for (const name of names) delete staged[name];
|
|
15042
|
+
store.setState((state) => {
|
|
15043
|
+
const map = state[kind];
|
|
15044
|
+
if (scope) {
|
|
15045
|
+
const currentScope = { ...map[scope] };
|
|
15046
|
+
for (const name of names) delete currentScope[name];
|
|
15047
|
+
return { [kind]: { ...map, [scope]: currentScope } };
|
|
15048
|
+
}
|
|
15049
|
+
const next = { ...map };
|
|
15050
|
+
for (const name of names) if (isLeaf(next[name])) delete next[name];
|
|
15051
|
+
return { [kind]: next };
|
|
15052
|
+
});
|
|
15053
|
+
}
|
|
15054
|
+
function clearResourceEntries(store, kind, scope, isLeaf) {
|
|
15055
|
+
const registry = getKindRegistry(store, kind);
|
|
15056
|
+
if (scope && scope !== "root") registry.staged.delete(scope);
|
|
15057
|
+
else if (scope === "root") registry.staged.delete(ROOT_SCOPE);
|
|
15058
|
+
else registry.staged.clear();
|
|
15059
|
+
store.setState((state) => {
|
|
15060
|
+
const map = state[kind];
|
|
15061
|
+
if (scope && scope !== "root") {
|
|
15062
|
+
const { [scope]: _, ...rest } = map;
|
|
15063
|
+
return { [kind]: rest };
|
|
15064
|
+
}
|
|
15065
|
+
if (scope === "root") {
|
|
15066
|
+
const next = {};
|
|
15067
|
+
for (const [key, value] of Object.entries(map)) if (!isLeaf(value)) next[key] = value;
|
|
15068
|
+
return { [kind]: next };
|
|
15069
|
+
}
|
|
15070
|
+
return { [kind]: {} };
|
|
15071
|
+
});
|
|
15072
|
+
}
|
|
15073
|
+
|
|
15074
|
+
function clearHmrCaches(store) {
|
|
15075
|
+
const primary = store.getState().primaryStore ?? store;
|
|
15076
|
+
invalidateAllResources(primary);
|
|
15077
|
+
primary.setState((state) => ({ _hmrVersion: state._hmrVersion + 1 }));
|
|
15078
|
+
}
|
|
15079
|
+
|
|
15515
15080
|
function CanvasImpl({
|
|
15516
15081
|
ref,
|
|
15517
15082
|
children,
|
|
@@ -15532,6 +15097,8 @@ function CanvasImpl({
|
|
|
15532
15097
|
raycaster,
|
|
15533
15098
|
camera,
|
|
15534
15099
|
scene,
|
|
15100
|
+
autoUpdateFrustum,
|
|
15101
|
+
occlusion,
|
|
15535
15102
|
onPointerMissed,
|
|
15536
15103
|
onDragOverMissed,
|
|
15537
15104
|
onDropMissed,
|
|
@@ -15543,39 +15110,10 @@ function CanvasImpl({
|
|
|
15543
15110
|
forceEven,
|
|
15544
15111
|
...props
|
|
15545
15112
|
}) {
|
|
15546
|
-
const { primaryCanvas, scheduler,
|
|
15547
|
-
const renderer = Object.keys(rendererConfig).length > 0 ? rendererConfig : rendererProp;
|
|
15113
|
+
const { primaryCanvas, scheduler, renderer } = parseRendererConfig(rendererProp);
|
|
15548
15114
|
React__namespace.useMemo(() => extend(THREE), []);
|
|
15549
15115
|
const Bridge = useBridge();
|
|
15550
|
-
const backgroundProps = React__namespace.useMemo(() =>
|
|
15551
|
-
if (!background) return null;
|
|
15552
|
-
if (typeof background === "object" && !background.isColor) {
|
|
15553
|
-
const { backgroundMap, envMap, files, preset, ...rest } = background;
|
|
15554
|
-
return {
|
|
15555
|
-
...rest,
|
|
15556
|
-
preset,
|
|
15557
|
-
files: envMap || files,
|
|
15558
|
-
backgroundFiles: backgroundMap,
|
|
15559
|
-
background: true
|
|
15560
|
-
};
|
|
15561
|
-
}
|
|
15562
|
-
if (typeof background === "number") {
|
|
15563
|
-
return { color: background, background: true };
|
|
15564
|
-
}
|
|
15565
|
-
if (typeof background === "string") {
|
|
15566
|
-
if (background in presetsObj) {
|
|
15567
|
-
return { preset: background, background: true };
|
|
15568
|
-
}
|
|
15569
|
-
if (/^(https?:\/\/|\/|\.\/|\.\.\/)|\\.(hdr|exr|jpg|jpeg|png|webp|gif)$/i.test(background)) {
|
|
15570
|
-
return { files: background, background: true };
|
|
15571
|
-
}
|
|
15572
|
-
return { color: background, background: true };
|
|
15573
|
-
}
|
|
15574
|
-
if (background.isColor) {
|
|
15575
|
-
return { color: background, background: true };
|
|
15576
|
-
}
|
|
15577
|
-
return null;
|
|
15578
|
-
}, [background]);
|
|
15116
|
+
const backgroundProps = React__namespace.useMemo(() => parseBackground(background), [background]);
|
|
15579
15117
|
const hasInitialSizeRef = React__namespace.useRef(false);
|
|
15580
15118
|
const measureConfig = React__namespace.useMemo(() => {
|
|
15581
15119
|
if (!hasInitialSizeRef.current) {
|
|
@@ -15617,6 +15155,7 @@ function CanvasImpl({
|
|
|
15617
15155
|
const handleDropMissed = useMutableCallback(onDropMissed);
|
|
15618
15156
|
const [block, setBlock] = React__namespace.useState(false);
|
|
15619
15157
|
const [error, setError] = React__namespace.useState(false);
|
|
15158
|
+
const [fallbackVisible, setFallbackVisible] = React__namespace.useState(false);
|
|
15620
15159
|
if (block) throw block;
|
|
15621
15160
|
if (error) throw error;
|
|
15622
15161
|
const root = React__namespace.useRef(null);
|
|
@@ -15624,6 +15163,7 @@ function CanvasImpl({
|
|
|
15624
15163
|
const unsubscribeErrorRef = React__namespace.useRef(null);
|
|
15625
15164
|
useIsomorphicLayoutEffect(() => {
|
|
15626
15165
|
effectActiveRef.current = true;
|
|
15166
|
+
if (fallbackVisible) return;
|
|
15627
15167
|
const canvas = canvasRef.current;
|
|
15628
15168
|
if (effectiveSize.width > 0 && effectiveSize.height > 0 && canvas) {
|
|
15629
15169
|
if (!root.current) {
|
|
@@ -15644,7 +15184,7 @@ function CanvasImpl({
|
|
|
15644
15184
|
}
|
|
15645
15185
|
async function run() {
|
|
15646
15186
|
if (!effectActiveRef.current || !root.current) return;
|
|
15647
|
-
await root.current.configure({
|
|
15187
|
+
const configured = await root.current.configure({
|
|
15648
15188
|
id,
|
|
15649
15189
|
primaryCanvas,
|
|
15650
15190
|
scheduler,
|
|
@@ -15659,6 +15199,8 @@ function CanvasImpl({
|
|
|
15659
15199
|
performance,
|
|
15660
15200
|
raycaster,
|
|
15661
15201
|
camera,
|
|
15202
|
+
autoUpdateFrustum,
|
|
15203
|
+
occlusion,
|
|
15662
15204
|
size: effectiveSize,
|
|
15663
15205
|
// Store size props for reset functionality
|
|
15664
15206
|
_sizeProps: width !== void 0 || height !== void 0 ? { width, height } : null,
|
|
@@ -15683,8 +15225,20 @@ function CanvasImpl({
|
|
|
15683
15225
|
}
|
|
15684
15226
|
onCreated?.(state);
|
|
15685
15227
|
}
|
|
15686
|
-
})
|
|
15687
|
-
|
|
15228
|
+
}).then(
|
|
15229
|
+
() => true,
|
|
15230
|
+
// Renderer setup failed (e.g. no WebGL/WebGPU support). The `fallback` prop lives
|
|
15231
|
+
// inside <canvas>, which browsers don't display, so surface it as visible DOM
|
|
15232
|
+
// instead; with no fallback, rethrow to an external error boundary. (#3757)
|
|
15233
|
+
(setupError) => {
|
|
15234
|
+
if (effectActiveRef.current) {
|
|
15235
|
+
if (fallback != null) setFallbackVisible(true);
|
|
15236
|
+
else setError(setupError);
|
|
15237
|
+
}
|
|
15238
|
+
return false;
|
|
15239
|
+
}
|
|
15240
|
+
);
|
|
15241
|
+
if (!configured || !effectActiveRef.current || !root.current) return;
|
|
15688
15242
|
root.current.render(
|
|
15689
15243
|
/* @__PURE__ */ jsxRuntime.jsx(Bridge, { children: /* @__PURE__ */ jsxRuntime.jsx(ErrorBoundary, { set: setError, children: /* @__PURE__ */ jsxRuntime.jsxs(React__namespace.Suspense, { fallback: /* @__PURE__ */ jsxRuntime.jsx(Block, { set: setBlock }), children: [
|
|
15690
15244
|
backgroundProps && /* @__PURE__ */ jsxRuntime.jsx(Environment, { ...backgroundProps }),
|
|
@@ -15718,20 +15272,13 @@ function CanvasImpl({
|
|
|
15718
15272
|
const handleHMR = () => {
|
|
15719
15273
|
queueMicrotask(() => {
|
|
15720
15274
|
const rootEntry = _roots.get(canvas);
|
|
15721
|
-
if (rootEntry?.store)
|
|
15722
|
-
rootEntry.store.setState((state) => ({
|
|
15723
|
-
nodes: {},
|
|
15724
|
-
uniforms: {},
|
|
15725
|
-
_hmrVersion: state._hmrVersion + 1
|
|
15726
|
-
}));
|
|
15727
|
-
}
|
|
15275
|
+
if (rootEntry?.store) clearHmrCaches(rootEntry.store);
|
|
15728
15276
|
});
|
|
15729
15277
|
};
|
|
15730
15278
|
if (typeof ({ url: (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)) }) !== "undefined" && undefined) {
|
|
15731
15279
|
const hot = undefined;
|
|
15732
15280
|
hot.on("vite:afterUpdate", handleHMR);
|
|
15733
|
-
return () => hot.
|
|
15734
|
-
});
|
|
15281
|
+
return () => hot.off?.("vite:afterUpdate", handleHMR);
|
|
15735
15282
|
}
|
|
15736
15283
|
if (typeof module !== "undefined" && module.hot) {
|
|
15737
15284
|
const hot = module.hot;
|
|
@@ -15754,7 +15301,20 @@ function CanvasImpl({
|
|
|
15754
15301
|
...style
|
|
15755
15302
|
},
|
|
15756
15303
|
...props,
|
|
15757
|
-
children:
|
|
15304
|
+
children: fallbackVisible ? (
|
|
15305
|
+
// Renderer setup failed: render the fallback as visible DOM. Inside <canvas> (below)
|
|
15306
|
+
// it exists in the tree but browsers never paint it, which is the whole bug (#3757).
|
|
15307
|
+
fallback
|
|
15308
|
+
) : /* @__PURE__ */ jsxRuntime.jsx("div", { ref: containerRef, className: "r3f-canvas-container", style: { width: "100%", height: "100%" }, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
15309
|
+
"canvas",
|
|
15310
|
+
{
|
|
15311
|
+
ref: canvasRef,
|
|
15312
|
+
id,
|
|
15313
|
+
className: "r3f-canvas",
|
|
15314
|
+
style: { display: "block", width: "100%", height: "100%" },
|
|
15315
|
+
children: fallback
|
|
15316
|
+
}
|
|
15317
|
+
) })
|
|
15758
15318
|
}
|
|
15759
15319
|
);
|
|
15760
15320
|
}
|
|
@@ -15827,23 +15387,50 @@ let ScopedStore = _ScopedStore;
|
|
|
15827
15387
|
function createScopedStore(data) {
|
|
15828
15388
|
return new ScopedStore(data);
|
|
15829
15389
|
}
|
|
15830
|
-
function createLazyCreatorState(state) {
|
|
15390
|
+
function createLazyCreatorState(state, store) {
|
|
15831
15391
|
let _uniforms = null;
|
|
15832
15392
|
let _nodes = null;
|
|
15393
|
+
let _buffers = null;
|
|
15394
|
+
let _gpuStorage = null;
|
|
15395
|
+
const view = (kind) => store ? withStagedOverlay(store, kind, state[kind]) : state[kind];
|
|
15833
15396
|
return Object.create(state, {
|
|
15834
15397
|
uniforms: {
|
|
15835
15398
|
get() {
|
|
15836
|
-
return _uniforms ?? (_uniforms = createScopedStore(
|
|
15399
|
+
return _uniforms ?? (_uniforms = createScopedStore(view("uniforms")));
|
|
15837
15400
|
}
|
|
15838
15401
|
},
|
|
15839
15402
|
nodes: {
|
|
15840
15403
|
get() {
|
|
15841
|
-
return _nodes ?? (_nodes = createScopedStore(
|
|
15404
|
+
return _nodes ?? (_nodes = createScopedStore(view("nodes")));
|
|
15405
|
+
}
|
|
15406
|
+
},
|
|
15407
|
+
buffers: {
|
|
15408
|
+
get() {
|
|
15409
|
+
return _buffers ?? (_buffers = createScopedStore(view("buffers")));
|
|
15410
|
+
}
|
|
15411
|
+
},
|
|
15412
|
+
gpuStorage: {
|
|
15413
|
+
get() {
|
|
15414
|
+
return _gpuStorage ?? (_gpuStorage = createScopedStore(view("gpuStorage")));
|
|
15842
15415
|
}
|
|
15843
15416
|
}
|
|
15844
15417
|
});
|
|
15845
15418
|
}
|
|
15846
15419
|
|
|
15420
|
+
function resolvePrimary(local) {
|
|
15421
|
+
return local.getState().primaryStore ?? local;
|
|
15422
|
+
}
|
|
15423
|
+
function usePrimaryStore() {
|
|
15424
|
+
const local = useStore();
|
|
15425
|
+
const primary = local.getState().primaryStore;
|
|
15426
|
+
return React.useMemo(() => primary ?? local, [primary, local]);
|
|
15427
|
+
}
|
|
15428
|
+
function usePrimaryThree(selector = (state) => state, equalityFn) {
|
|
15429
|
+
const local = useStore();
|
|
15430
|
+
const primary = resolvePrimary(local);
|
|
15431
|
+
return primary(selector, equalityFn);
|
|
15432
|
+
}
|
|
15433
|
+
|
|
15847
15434
|
function addTexture(set, key, value) {
|
|
15848
15435
|
set((state) => {
|
|
15849
15436
|
const newMap = new Map(state.textures);
|
|
@@ -15922,6 +15509,10 @@ function vectorize(inObject) {
|
|
|
15922
15509
|
if (obj.isVector2 || obj.isVector3 || obj.isVector4) return inObject;
|
|
15923
15510
|
if (obj.isMatrix3 || obj.isMatrix4) return inObject;
|
|
15924
15511
|
if (obj.isColor || obj.isEuler || obj.isQuaternion || obj.isSpherical) return inObject;
|
|
15512
|
+
if ("r" in obj && "g" in obj && "b" in obj && typeof obj.r === "number" && typeof obj.g === "number" && typeof obj.b === "number") {
|
|
15513
|
+
const scale = obj.r > 1 || obj.g > 1 || obj.b > 1 ? 1 / 255 : 1;
|
|
15514
|
+
return new webgpu.Color(obj.r * scale, obj.g * scale, obj.b * scale);
|
|
15515
|
+
}
|
|
15925
15516
|
if ("x" in obj && "y" in obj && typeof obj.x === "number" && typeof obj.y === "number") {
|
|
15926
15517
|
if ("w" in obj && typeof obj.w === "number" && "z" in obj && typeof obj.z === "number") {
|
|
15927
15518
|
return new webgpu.Vector4(obj.x, obj.y, obj.z, obj.w);
|
|
@@ -15943,65 +15534,56 @@ function useCompareMemoize(value, deep) {
|
|
|
15943
15534
|
return ref.current;
|
|
15944
15535
|
}
|
|
15945
15536
|
|
|
15537
|
+
function useScopedResource(options) {
|
|
15538
|
+
const { store, kind, scope, isLeaf, create, prepare, reconcile, input } = options;
|
|
15539
|
+
const scopeKey = scope ?? ROOT_SCOPE;
|
|
15540
|
+
const version = store((s) => s._hmrVersion);
|
|
15541
|
+
const entries = React.useMemo(() => {
|
|
15542
|
+
const staged = peekStaged(store, kind, scopeKey);
|
|
15543
|
+
const reusable = isScopeValid(store, kind, scopeKey);
|
|
15544
|
+
const map = store.getState()[kind];
|
|
15545
|
+
const committed = reusable ? readCommittedScope(map, scopeKey, isLeaf) : void 0;
|
|
15546
|
+
const result = {};
|
|
15547
|
+
let fresh = null;
|
|
15548
|
+
for (const [name, candidate] of Object.entries(create())) {
|
|
15549
|
+
const existing = staged?.[name] ?? committed?.[name];
|
|
15550
|
+
if (existing !== void 0) {
|
|
15551
|
+
result[name] = existing;
|
|
15552
|
+
reconcile?.(existing, candidate);
|
|
15553
|
+
} else {
|
|
15554
|
+
const value = prepare ? prepare(name, candidate) : candidate;
|
|
15555
|
+
result[name] = value;
|
|
15556
|
+
(fresh ?? (fresh = {}))[name] = value;
|
|
15557
|
+
}
|
|
15558
|
+
}
|
|
15559
|
+
if (fresh) stageEntries(store, kind, scopeKey, fresh);
|
|
15560
|
+
return result;
|
|
15561
|
+
}, [store, kind, scopeKey, version, input]);
|
|
15562
|
+
useIsomorphicLayoutEffect(() => {
|
|
15563
|
+
flushStagedScope(store, kind, scopeKey, isLeaf);
|
|
15564
|
+
});
|
|
15565
|
+
return entries;
|
|
15566
|
+
}
|
|
15567
|
+
|
|
15946
15568
|
const isUniformNode$1 = (value) => value !== null && typeof value === "object" && "value" in value && "uuid" in value;
|
|
15947
15569
|
function useUniforms(creatorOrScope, scope) {
|
|
15948
|
-
const store =
|
|
15570
|
+
const store = usePrimaryStore();
|
|
15949
15571
|
const removeUniforms2 = React.useCallback(
|
|
15950
|
-
(names, targetScope) =>
|
|
15951
|
-
const nameArray = Array.isArray(names) ? names : [names];
|
|
15952
|
-
store.setState((state) => {
|
|
15953
|
-
if (targetScope) {
|
|
15954
|
-
const currentScope = { ...state.uniforms[targetScope] };
|
|
15955
|
-
for (const name of nameArray) delete currentScope[name];
|
|
15956
|
-
return { uniforms: { ...state.uniforms, [targetScope]: currentScope } };
|
|
15957
|
-
}
|
|
15958
|
-
const uniforms2 = { ...state.uniforms };
|
|
15959
|
-
for (const name of nameArray) if (isUniformNode$1(uniforms2[name])) delete uniforms2[name];
|
|
15960
|
-
return { uniforms: uniforms2 };
|
|
15961
|
-
});
|
|
15962
|
-
},
|
|
15572
|
+
(names, targetScope) => removeResourceEntries(store, "uniforms", Array.isArray(names) ? names : [names], targetScope, isUniformNode$1),
|
|
15963
15573
|
[store]
|
|
15964
15574
|
);
|
|
15965
15575
|
const clearUniforms = React.useCallback(
|
|
15966
|
-
(targetScope) =>
|
|
15967
|
-
store.setState((state) => {
|
|
15968
|
-
if (targetScope && targetScope !== "root") {
|
|
15969
|
-
const { [targetScope]: _, ...rest } = state.uniforms;
|
|
15970
|
-
return { uniforms: rest };
|
|
15971
|
-
}
|
|
15972
|
-
if (targetScope === "root") {
|
|
15973
|
-
const uniforms2 = {};
|
|
15974
|
-
for (const [key, value] of Object.entries(state.uniforms)) {
|
|
15975
|
-
if (!isUniformNode$1(value)) uniforms2[key] = value;
|
|
15976
|
-
}
|
|
15977
|
-
return { uniforms: uniforms2 };
|
|
15978
|
-
}
|
|
15979
|
-
return { uniforms: {} };
|
|
15980
|
-
});
|
|
15981
|
-
},
|
|
15576
|
+
(targetScope) => clearResourceEntries(store, "uniforms", targetScope, isUniformNode$1),
|
|
15982
15577
|
[store]
|
|
15983
15578
|
);
|
|
15984
15579
|
const rebuildUniforms = React.useCallback(
|
|
15985
|
-
(targetScope) =>
|
|
15986
|
-
store.setState((state) => {
|
|
15987
|
-
let newUniforms = {};
|
|
15988
|
-
if (targetScope && targetScope !== "root") {
|
|
15989
|
-
const { [targetScope]: _, ...rest } = state.uniforms;
|
|
15990
|
-
newUniforms = rest;
|
|
15991
|
-
} else if (targetScope === "root") {
|
|
15992
|
-
for (const [key, value] of Object.entries(state.uniforms)) {
|
|
15993
|
-
if (!isUniformNode$1(value)) newUniforms[key] = value;
|
|
15994
|
-
}
|
|
15995
|
-
}
|
|
15996
|
-
return { uniforms: newUniforms, _hmrVersion: state._hmrVersion + 1 };
|
|
15997
|
-
});
|
|
15998
|
-
},
|
|
15580
|
+
(targetScope) => rebuildResource(store, "uniforms", targetScope),
|
|
15999
15581
|
[store]
|
|
16000
15582
|
);
|
|
16001
15583
|
const inputForMemoization = React.useMemo(() => {
|
|
16002
15584
|
let raw = creatorOrScope;
|
|
16003
15585
|
if (is.fun(creatorOrScope)) {
|
|
16004
|
-
const wrappedState = createLazyCreatorState(store.getState());
|
|
15586
|
+
const wrappedState = createLazyCreatorState(store.getState(), store);
|
|
16005
15587
|
raw = creatorOrScope(wrappedState);
|
|
16006
15588
|
}
|
|
16007
15589
|
if (raw && typeof raw === "object" && !Array.isArray(raw)) {
|
|
@@ -16015,80 +15597,44 @@ function useUniforms(creatorOrScope, scope) {
|
|
|
16015
15597
|
}, [creatorOrScope, store]);
|
|
16016
15598
|
const memoizedInput = useCompareMemoize(inputForMemoization);
|
|
16017
15599
|
const isReader = memoizedInput === void 0 || typeof memoizedInput === "string";
|
|
16018
|
-
const storeUniforms =
|
|
16019
|
-
const
|
|
16020
|
-
|
|
16021
|
-
|
|
16022
|
-
|
|
16023
|
-
|
|
16024
|
-
|
|
16025
|
-
|
|
16026
|
-
|
|
16027
|
-
|
|
16028
|
-
|
|
16029
|
-
|
|
16030
|
-
|
|
16031
|
-
|
|
16032
|
-
|
|
16033
|
-
|
|
16034
|
-
|
|
16035
|
-
|
|
16036
|
-
|
|
16037
|
-
|
|
16038
|
-
|
|
16039
|
-
|
|
16040
|
-
if (scope) {
|
|
16041
|
-
if (!state.uniforms[scope]) state.uniforms[scope] = {};
|
|
16042
|
-
targetRecord = state.uniforms[scope];
|
|
16043
|
-
}
|
|
16044
|
-
for (const [name, node] of Object.entries(created)) {
|
|
16045
|
-
if (targetRecord[name]) {
|
|
16046
|
-
result[name] = targetRecord[name];
|
|
16047
|
-
const existingVal = result[name].value;
|
|
16048
|
-
const newVal = vectorize(node);
|
|
16049
|
-
let equals = newVal === existingVal;
|
|
16050
|
-
if (!equals && hasEqualsMethod(existingVal) && hasEqualsMethod(newVal)) {
|
|
16051
|
-
if (isThreeVector(existingVal) && isThreeVector(newVal)) {
|
|
16052
|
-
equals = vectorEquals(existingVal, newVal);
|
|
16053
|
-
} else if (isSameThreeType(existingVal, newVal)) {
|
|
16054
|
-
equals = existingVal.equals(newVal);
|
|
16055
|
-
}
|
|
15600
|
+
const storeUniforms = usePrimaryThree((s) => s.uniforms);
|
|
15601
|
+
const created = useScopedResource({
|
|
15602
|
+
store,
|
|
15603
|
+
kind: "uniforms",
|
|
15604
|
+
scope: isReader ? void 0 : scope,
|
|
15605
|
+
isLeaf: isUniformNode$1,
|
|
15606
|
+
input: memoizedInput,
|
|
15607
|
+
create: () => {
|
|
15608
|
+
if (isReader) return {};
|
|
15609
|
+
if (typeof memoizedInput !== "object" || memoizedInput === null) throw new Error("Invalid uniform input");
|
|
15610
|
+
return memoizedInput;
|
|
15611
|
+
},
|
|
15612
|
+
prepare: (name, candidate) => createUniform(name, candidate, scope),
|
|
15613
|
+
reconcile: (existing, candidate) => {
|
|
15614
|
+
const existingVal = existing.value;
|
|
15615
|
+
const newVal = vectorize(candidate);
|
|
15616
|
+
let equals = newVal === existingVal;
|
|
15617
|
+
if (!equals && hasEqualsMethod(existingVal) && hasEqualsMethod(newVal)) {
|
|
15618
|
+
if (isThreeVector(existingVal) && isThreeVector(newVal)) {
|
|
15619
|
+
equals = vectorEquals(existingVal, newVal);
|
|
15620
|
+
} else if (isSameThreeType(existingVal, newVal)) {
|
|
15621
|
+
equals = existingVal.equals(newVal);
|
|
16056
15622
|
}
|
|
16057
|
-
if (!equals) result[name].value = newVal;
|
|
16058
|
-
} else {
|
|
16059
|
-
result[name] = createUniform(name, node, scope);
|
|
16060
|
-
hasNewUniforms = true;
|
|
16061
15623
|
}
|
|
15624
|
+
if (!equals) existing.value = newVal;
|
|
16062
15625
|
}
|
|
16063
|
-
|
|
16064
|
-
|
|
16065
|
-
|
|
16066
|
-
|
|
16067
|
-
|
|
16068
|
-
|
|
16069
|
-
|
|
16070
|
-
|
|
16071
|
-
} else {
|
|
16072
|
-
set((s) => ({ uniforms: { ...s.uniforms, ...result } }));
|
|
16073
|
-
}
|
|
16074
|
-
}
|
|
16075
|
-
return result;
|
|
16076
|
-
}, [store, memoizedInput, scope, readerDep, creatorDep]);
|
|
15626
|
+
});
|
|
15627
|
+
let uniforms = created;
|
|
15628
|
+
if (memoizedInput === void 0) {
|
|
15629
|
+
uniforms = storeUniforms;
|
|
15630
|
+
} else if (typeof memoizedInput === "string") {
|
|
15631
|
+
const scopeData = storeUniforms[memoizedInput];
|
|
15632
|
+
uniforms = scopeData && !isUniformNode$1(scopeData) ? scopeData : {};
|
|
15633
|
+
}
|
|
16077
15634
|
return { ...uniforms, removeUniforms: removeUniforms2, clearUniforms, rebuildUniforms };
|
|
16078
15635
|
}
|
|
16079
15636
|
function rebuildAllUniforms(store, scope) {
|
|
16080
|
-
store
|
|
16081
|
-
let newUniforms = {};
|
|
16082
|
-
if (scope && scope !== "root") {
|
|
16083
|
-
const { [scope]: _, ...rest } = state.uniforms;
|
|
16084
|
-
newUniforms = rest;
|
|
16085
|
-
} else if (scope === "root") {
|
|
16086
|
-
for (const [key, value] of Object.entries(state.uniforms)) {
|
|
16087
|
-
if (!isUniformNode$1(value)) newUniforms[key] = value;
|
|
16088
|
-
}
|
|
16089
|
-
}
|
|
16090
|
-
return { uniforms: newUniforms, _hmrVersion: state._hmrVersion + 1 };
|
|
16091
|
-
});
|
|
15637
|
+
rebuildResource(store, "uniforms", scope);
|
|
16092
15638
|
}
|
|
16093
15639
|
function removeUniforms(set, names, scope) {
|
|
16094
15640
|
set((state) => {
|
|
@@ -16152,191 +15698,91 @@ function isSameThreeType(a, b) {
|
|
|
16152
15698
|
return obj_a.isColor && obj_b.isColor || obj_a.isMatrix3 && obj_b.isMatrix3 || obj_a.isMatrix4 && obj_b.isMatrix4 || obj_a.isEuler && obj_b.isEuler || obj_a.isQuaternion && obj_b.isQuaternion;
|
|
16153
15699
|
}
|
|
16154
15700
|
|
|
15701
|
+
const uniformOf = tsl.uniform;
|
|
16155
15702
|
const isUniformNode = (value) => value !== null && typeof value === "object" && "value" in value && "uuid" in value;
|
|
16156
15703
|
const isTSLNode$1 = (value) => value !== null && typeof value === "object" && "uuid" in value && "nodeType" in value;
|
|
16157
15704
|
function useUniform(name, value) {
|
|
16158
15705
|
const store = useStore();
|
|
16159
|
-
const
|
|
16160
|
-
|
|
16161
|
-
|
|
16162
|
-
|
|
16163
|
-
|
|
16164
|
-
|
|
15706
|
+
const registered = useScopedResource({
|
|
15707
|
+
store,
|
|
15708
|
+
kind: "uniforms",
|
|
15709
|
+
scope: void 0,
|
|
15710
|
+
isLeaf: isUniformNode,
|
|
15711
|
+
input: name,
|
|
15712
|
+
create: () => value === void 0 ? {} : { [name]: value },
|
|
15713
|
+
prepare: createNamedUniform,
|
|
15714
|
+
reconcile: (existing2) => {
|
|
16165
15715
|
if (value !== void 0 && !isTSLNode$1(value) && !isUniformNode(value)) {
|
|
16166
|
-
|
|
15716
|
+
existing2.value = typeof value === "string" ? new webgpu.Color(value) : value;
|
|
16167
15717
|
}
|
|
16168
|
-
return existing;
|
|
16169
|
-
}
|
|
16170
|
-
if (value === void 0) {
|
|
16171
|
-
throw new Error(
|
|
16172
|
-
`[useUniform] Uniform "${name}" not found. Create it first with: useUniform('${name}', initialValue)`
|
|
16173
|
-
);
|
|
16174
|
-
}
|
|
16175
|
-
if (isUniformNode(value)) {
|
|
16176
|
-
const node2 = value;
|
|
16177
|
-
if (typeof node2.setName === "function") {
|
|
16178
|
-
node2.setName(name);
|
|
16179
|
-
}
|
|
16180
|
-
set((s) => ({
|
|
16181
|
-
uniforms: { ...s.uniforms, [name]: node2 }
|
|
16182
|
-
}));
|
|
16183
|
-
return node2;
|
|
16184
|
-
}
|
|
16185
|
-
let node;
|
|
16186
|
-
if (isTSLNode$1(value)) {
|
|
16187
|
-
node = tsl.uniform(value);
|
|
16188
|
-
} else if (typeof value === "string") {
|
|
16189
|
-
node = tsl.uniform(new webgpu.Color(value));
|
|
16190
|
-
} else {
|
|
16191
|
-
node = tsl.uniform(value);
|
|
16192
15718
|
}
|
|
16193
|
-
|
|
16194
|
-
|
|
16195
|
-
|
|
16196
|
-
|
|
16197
|
-
|
|
16198
|
-
|
|
16199
|
-
|
|
16200
|
-
|
|
16201
|
-
|
|
16202
|
-
|
|
16203
|
-
|
|
15719
|
+
});
|
|
15720
|
+
if (registered[name]) return registered[name];
|
|
15721
|
+
const existing = peekStaged(store, "uniforms", ROOT_SCOPE)?.[name] ?? store.getState().uniforms[name];
|
|
15722
|
+
if (existing && isUniformNode(existing)) return existing;
|
|
15723
|
+
throw new Error(
|
|
15724
|
+
`[useUniform] Uniform "${name}" not found. Create it first with: useUniform('${name}', initialValue)`
|
|
15725
|
+
);
|
|
15726
|
+
}
|
|
15727
|
+
function createNamedUniform(name, value) {
|
|
15728
|
+
let node;
|
|
15729
|
+
if (isUniformNode(value)) {
|
|
15730
|
+
node = value;
|
|
15731
|
+
} else if (isTSLNode$1(value)) {
|
|
15732
|
+
node = uniformOf(value);
|
|
15733
|
+
} else if (typeof value === "string") {
|
|
15734
|
+
node = tsl.uniform(new webgpu.Color(value));
|
|
15735
|
+
} else {
|
|
15736
|
+
node = uniformOf(value);
|
|
15737
|
+
}
|
|
15738
|
+
if (typeof node.setName === "function") {
|
|
15739
|
+
node.setName(name);
|
|
15740
|
+
}
|
|
15741
|
+
return node;
|
|
16204
15742
|
}
|
|
16205
15743
|
|
|
16206
15744
|
const isTSLNode = (value) => value !== null && typeof value === "object" && ("uuid" in value || "nodeType" in value);
|
|
16207
15745
|
function useNodes(creatorOrScope, scope) {
|
|
16208
|
-
const store =
|
|
15746
|
+
const store = usePrimaryStore();
|
|
16209
15747
|
const removeNodes2 = React.useCallback(
|
|
16210
|
-
(names, targetScope) =>
|
|
16211
|
-
const nameArray = Array.isArray(names) ? names : [names];
|
|
16212
|
-
store.setState((state) => {
|
|
16213
|
-
if (targetScope) {
|
|
16214
|
-
const currentScope = { ...state.nodes[targetScope] };
|
|
16215
|
-
for (const name of nameArray) delete currentScope[name];
|
|
16216
|
-
return { nodes: { ...state.nodes, [targetScope]: currentScope } };
|
|
16217
|
-
}
|
|
16218
|
-
const nodes2 = { ...state.nodes };
|
|
16219
|
-
for (const name of nameArray) if (isTSLNode(nodes2[name])) delete nodes2[name];
|
|
16220
|
-
return { nodes: nodes2 };
|
|
16221
|
-
});
|
|
16222
|
-
},
|
|
15748
|
+
(names, targetScope) => removeResourceEntries(store, "nodes", Array.isArray(names) ? names : [names], targetScope, isTSLNode),
|
|
16223
15749
|
[store]
|
|
16224
15750
|
);
|
|
16225
15751
|
const clearNodes = React.useCallback(
|
|
16226
|
-
(targetScope) =>
|
|
16227
|
-
store.setState((state) => {
|
|
16228
|
-
if (targetScope && targetScope !== "root") {
|
|
16229
|
-
const { [targetScope]: _, ...rest } = state.nodes;
|
|
16230
|
-
return { nodes: rest };
|
|
16231
|
-
}
|
|
16232
|
-
if (targetScope === "root") {
|
|
16233
|
-
const nodes2 = {};
|
|
16234
|
-
for (const [key, value] of Object.entries(state.nodes)) {
|
|
16235
|
-
if (!isTSLNode(value)) nodes2[key] = value;
|
|
16236
|
-
}
|
|
16237
|
-
return { nodes: nodes2 };
|
|
16238
|
-
}
|
|
16239
|
-
return { nodes: {} };
|
|
16240
|
-
});
|
|
16241
|
-
},
|
|
15752
|
+
(targetScope) => clearResourceEntries(store, "nodes", targetScope, isTSLNode),
|
|
16242
15753
|
[store]
|
|
16243
15754
|
);
|
|
16244
15755
|
const rebuildNodes = React.useCallback(
|
|
16245
|
-
(targetScope) =>
|
|
16246
|
-
store.setState((state) => {
|
|
16247
|
-
let newNodes = state.nodes;
|
|
16248
|
-
if (targetScope && targetScope !== "root") {
|
|
16249
|
-
const { [targetScope]: _, ...rest } = state.nodes;
|
|
16250
|
-
newNodes = rest;
|
|
16251
|
-
} else if (targetScope === "root") {
|
|
16252
|
-
newNodes = {};
|
|
16253
|
-
for (const [key, value] of Object.entries(state.nodes)) {
|
|
16254
|
-
if (!isTSLNode(value)) newNodes[key] = value;
|
|
16255
|
-
}
|
|
16256
|
-
} else {
|
|
16257
|
-
newNodes = {};
|
|
16258
|
-
}
|
|
16259
|
-
return { nodes: newNodes, _hmrVersion: state._hmrVersion + 1 };
|
|
16260
|
-
});
|
|
16261
|
-
},
|
|
15756
|
+
(targetScope) => rebuildResource(store, "nodes", targetScope),
|
|
16262
15757
|
[store]
|
|
16263
15758
|
);
|
|
16264
15759
|
const isReader = creatorOrScope === void 0 || typeof creatorOrScope === "string";
|
|
16265
|
-
const storeNodes =
|
|
16266
|
-
const
|
|
16267
|
-
|
|
16268
|
-
|
|
16269
|
-
|
|
16270
|
-
|
|
16271
|
-
|
|
16272
|
-
return
|
|
16273
|
-
|
|
16274
|
-
|
|
16275
|
-
|
|
16276
|
-
|
|
16277
|
-
return
|
|
16278
|
-
}
|
|
16279
|
-
const state = store.getState();
|
|
16280
|
-
const set = store.setState;
|
|
16281
|
-
const creator = creatorOrScope;
|
|
16282
|
-
const wrappedState = createLazyCreatorState(state);
|
|
16283
|
-
const created = creator(wrappedState);
|
|
16284
|
-
const result = {};
|
|
16285
|
-
let hasNewNodes = false;
|
|
16286
|
-
if (scope) {
|
|
16287
|
-
const currentScope = state.nodes[scope] ?? {};
|
|
16288
|
-
for (const [name, node] of Object.entries(created)) {
|
|
16289
|
-
if (currentScope[name]) {
|
|
16290
|
-
result[name] = currentScope[name];
|
|
16291
|
-
} else {
|
|
16292
|
-
node.setName?.(`${scope}.${name}`);
|
|
16293
|
-
result[name] = node;
|
|
16294
|
-
hasNewNodes = true;
|
|
16295
|
-
}
|
|
16296
|
-
}
|
|
16297
|
-
if (hasNewNodes) {
|
|
16298
|
-
set((s) => ({
|
|
16299
|
-
nodes: {
|
|
16300
|
-
...s.nodes,
|
|
16301
|
-
[scope]: { ...s.nodes[scope], ...result }
|
|
16302
|
-
}
|
|
16303
|
-
}));
|
|
16304
|
-
}
|
|
16305
|
-
return result;
|
|
16306
|
-
}
|
|
16307
|
-
for (const [name, node] of Object.entries(created)) {
|
|
16308
|
-
const existing = state.nodes[name];
|
|
16309
|
-
if (existing && isTSLNode(existing)) {
|
|
16310
|
-
result[name] = existing;
|
|
16311
|
-
} else {
|
|
16312
|
-
node.setName?.(name);
|
|
16313
|
-
result[name] = node;
|
|
16314
|
-
hasNewNodes = true;
|
|
16315
|
-
}
|
|
16316
|
-
}
|
|
16317
|
-
if (hasNewNodes) {
|
|
16318
|
-
set((s) => ({ nodes: { ...s.nodes, ...result } }));
|
|
15760
|
+
const storeNodes = usePrimaryThree((s) => s.nodes);
|
|
15761
|
+
const created = useScopedResource({
|
|
15762
|
+
store,
|
|
15763
|
+
kind: "nodes",
|
|
15764
|
+
scope: isReader ? void 0 : scope,
|
|
15765
|
+
isLeaf: isTSLNode,
|
|
15766
|
+
create: () => {
|
|
15767
|
+
if (isReader) return {};
|
|
15768
|
+
return creatorOrScope(createLazyCreatorState(store.getState(), store));
|
|
15769
|
+
},
|
|
15770
|
+
prepare: (name, node) => {
|
|
15771
|
+
node.setName?.(scope ? `${scope}.${name}` : name);
|
|
15772
|
+
return node;
|
|
16319
15773
|
}
|
|
16320
|
-
|
|
16321
|
-
|
|
15774
|
+
});
|
|
15775
|
+
let nodes = created;
|
|
15776
|
+
if (creatorOrScope === void 0) {
|
|
15777
|
+
nodes = storeNodes;
|
|
15778
|
+
} else if (typeof creatorOrScope === "string") {
|
|
15779
|
+
const scopeData = storeNodes[creatorOrScope];
|
|
15780
|
+
nodes = scopeData && !isTSLNode(scopeData) ? scopeData : {};
|
|
15781
|
+
}
|
|
16322
15782
|
return { ...nodes, removeNodes: removeNodes2, clearNodes, rebuildNodes };
|
|
16323
15783
|
}
|
|
16324
15784
|
function rebuildAllNodes(store, scope) {
|
|
16325
|
-
store
|
|
16326
|
-
let newNodes = state.nodes;
|
|
16327
|
-
if (scope && scope !== "root") {
|
|
16328
|
-
const { [scope]: _, ...rest } = state.nodes;
|
|
16329
|
-
newNodes = rest;
|
|
16330
|
-
} else if (scope === "root") {
|
|
16331
|
-
newNodes = {};
|
|
16332
|
-
for (const [key, value] of Object.entries(state.nodes)) {
|
|
16333
|
-
if (!isTSLNode(value)) newNodes[key] = value;
|
|
16334
|
-
}
|
|
16335
|
-
} else {
|
|
16336
|
-
newNodes = {};
|
|
16337
|
-
}
|
|
16338
|
-
return { nodes: newNodes, _hmrVersion: state._hmrVersion + 1 };
|
|
16339
|
-
});
|
|
15785
|
+
rebuildResource(store, "nodes", scope);
|
|
16340
15786
|
}
|
|
16341
15787
|
function removeNodes(set, names, scope) {
|
|
16342
15788
|
set((state) => {
|
|
@@ -16366,18 +15812,172 @@ function clearRootNodes(set) {
|
|
|
16366
15812
|
});
|
|
16367
15813
|
}
|
|
16368
15814
|
function useLocalNodes(creator) {
|
|
16369
|
-
const store =
|
|
16370
|
-
const uniforms =
|
|
16371
|
-
const nodes =
|
|
16372
|
-
const textures =
|
|
16373
|
-
const hmrVersion =
|
|
15815
|
+
const store = usePrimaryStore();
|
|
15816
|
+
const uniforms = usePrimaryThree((s) => s.uniforms);
|
|
15817
|
+
const nodes = usePrimaryThree((s) => s.nodes);
|
|
15818
|
+
const textures = usePrimaryThree((s) => s.textures);
|
|
15819
|
+
const hmrVersion = usePrimaryThree((s) => s._hmrVersion);
|
|
16374
15820
|
return React.useMemo(() => {
|
|
16375
|
-
const wrappedState = createLazyCreatorState(store.getState());
|
|
15821
|
+
const wrappedState = createLazyCreatorState(store.getState(), store);
|
|
16376
15822
|
return creator(wrappedState);
|
|
16377
15823
|
}, [store, creator, uniforms, nodes, textures, hmrVersion]);
|
|
16378
15824
|
}
|
|
16379
15825
|
|
|
16380
|
-
|
|
15826
|
+
const isBufferLike = (value) => {
|
|
15827
|
+
if (value === null || typeof value !== "object") return false;
|
|
15828
|
+
if (ArrayBuffer.isView(value)) return true;
|
|
15829
|
+
if ("isBufferAttribute" in value) return true;
|
|
15830
|
+
if ("uuid" in value || "nodeType" in value) return true;
|
|
15831
|
+
return false;
|
|
15832
|
+
};
|
|
15833
|
+
const disposeBuffer = (buffer) => {
|
|
15834
|
+
if (buffer === null || typeof buffer !== "object") return;
|
|
15835
|
+
if ("dispose" in buffer && typeof buffer.dispose === "function") {
|
|
15836
|
+
buffer.dispose();
|
|
15837
|
+
}
|
|
15838
|
+
};
|
|
15839
|
+
function useBuffers(creatorOrScope, scope) {
|
|
15840
|
+
const store = usePrimaryStore();
|
|
15841
|
+
const removeBuffers = React.useCallback(
|
|
15842
|
+
(names, targetScope) => removeResourceEntries(store, "buffers", Array.isArray(names) ? names : [names], targetScope, isBufferLike),
|
|
15843
|
+
[store]
|
|
15844
|
+
);
|
|
15845
|
+
const clearBuffers = React.useCallback(
|
|
15846
|
+
(targetScope) => clearResourceEntries(store, "buffers", targetScope, isBufferLike),
|
|
15847
|
+
[store]
|
|
15848
|
+
);
|
|
15849
|
+
const rebuildBuffers = React.useCallback(
|
|
15850
|
+
(targetScope) => rebuildResource(store, "buffers", targetScope),
|
|
15851
|
+
[store]
|
|
15852
|
+
);
|
|
15853
|
+
const disposeBuffers = React.useCallback(
|
|
15854
|
+
(names, targetScope) => {
|
|
15855
|
+
const nameArray = Array.isArray(names) ? names : [names];
|
|
15856
|
+
const state = store.getState();
|
|
15857
|
+
for (const name of nameArray) {
|
|
15858
|
+
const buffer = targetScope ? state.buffers[targetScope]?.[name] : state.buffers[name];
|
|
15859
|
+
if (buffer && isBufferLike(buffer)) {
|
|
15860
|
+
disposeBuffer(buffer);
|
|
15861
|
+
}
|
|
15862
|
+
}
|
|
15863
|
+
removeBuffers(names, targetScope);
|
|
15864
|
+
},
|
|
15865
|
+
[store, removeBuffers]
|
|
15866
|
+
);
|
|
15867
|
+
const isReader = creatorOrScope === void 0 || typeof creatorOrScope === "string";
|
|
15868
|
+
const storeBuffers = usePrimaryThree((s) => s.buffers);
|
|
15869
|
+
const created = useScopedResource({
|
|
15870
|
+
store,
|
|
15871
|
+
kind: "buffers",
|
|
15872
|
+
scope: isReader ? void 0 : scope,
|
|
15873
|
+
isLeaf: isBufferLike,
|
|
15874
|
+
create: () => {
|
|
15875
|
+
if (isReader) return {};
|
|
15876
|
+
return creatorOrScope(createLazyCreatorState(store.getState(), store));
|
|
15877
|
+
},
|
|
15878
|
+
prepare: (name, buffer) => {
|
|
15879
|
+
if ("setName" in buffer && typeof buffer.setName === "function") {
|
|
15880
|
+
buffer.setName(scope ? `${scope}.${name}` : name);
|
|
15881
|
+
}
|
|
15882
|
+
return buffer;
|
|
15883
|
+
}
|
|
15884
|
+
});
|
|
15885
|
+
let buffers = created;
|
|
15886
|
+
if (creatorOrScope === void 0) {
|
|
15887
|
+
buffers = storeBuffers;
|
|
15888
|
+
} else if (typeof creatorOrScope === "string") {
|
|
15889
|
+
const scopeData = storeBuffers[creatorOrScope];
|
|
15890
|
+
buffers = scopeData && !isBufferLike(scopeData) ? scopeData : {};
|
|
15891
|
+
}
|
|
15892
|
+
return { ...buffers, removeBuffers, clearBuffers, rebuildBuffers, disposeBuffers };
|
|
15893
|
+
}
|
|
15894
|
+
function rebuildAllBuffers(store, scope) {
|
|
15895
|
+
rebuildResource(store, "buffers", scope);
|
|
15896
|
+
}
|
|
15897
|
+
|
|
15898
|
+
const isStorageLike = (value) => {
|
|
15899
|
+
if (value === null || typeof value !== "object") return false;
|
|
15900
|
+
if ("isTexture" in value) return true;
|
|
15901
|
+
if ("isData3DTexture" in value) return true;
|
|
15902
|
+
if ("uuid" in value || "nodeType" in value) return true;
|
|
15903
|
+
return false;
|
|
15904
|
+
};
|
|
15905
|
+
const disposeStorage = (storage) => {
|
|
15906
|
+
if (storage === null || typeof storage !== "object") return;
|
|
15907
|
+
if ("dispose" in storage && typeof storage.dispose === "function") {
|
|
15908
|
+
storage.dispose();
|
|
15909
|
+
}
|
|
15910
|
+
};
|
|
15911
|
+
function useGPUStorage(creatorOrScope, scope) {
|
|
15912
|
+
const store = usePrimaryStore();
|
|
15913
|
+
const removeStorage = React.useCallback(
|
|
15914
|
+
(names, targetScope) => removeResourceEntries(store, "gpuStorage", Array.isArray(names) ? names : [names], targetScope, isStorageLike),
|
|
15915
|
+
[store]
|
|
15916
|
+
);
|
|
15917
|
+
const clearStorage = React.useCallback(
|
|
15918
|
+
(targetScope) => clearResourceEntries(store, "gpuStorage", targetScope, isStorageLike),
|
|
15919
|
+
[store]
|
|
15920
|
+
);
|
|
15921
|
+
const rebuildStorage = React.useCallback(
|
|
15922
|
+
(targetScope) => rebuildResource(store, "gpuStorage", targetScope),
|
|
15923
|
+
[store]
|
|
15924
|
+
);
|
|
15925
|
+
const disposeStorageFn = React.useCallback(
|
|
15926
|
+
(names, targetScope) => {
|
|
15927
|
+
const nameArray = Array.isArray(names) ? names : [names];
|
|
15928
|
+
const state = store.getState();
|
|
15929
|
+
for (const name of nameArray) {
|
|
15930
|
+
const storage = targetScope ? state.gpuStorage[targetScope]?.[name] : state.gpuStorage[name];
|
|
15931
|
+
if (storage && isStorageLike(storage)) {
|
|
15932
|
+
disposeStorage(storage);
|
|
15933
|
+
}
|
|
15934
|
+
}
|
|
15935
|
+
removeStorage(names, targetScope);
|
|
15936
|
+
},
|
|
15937
|
+
[store, removeStorage]
|
|
15938
|
+
);
|
|
15939
|
+
const isReader = creatorOrScope === void 0 || typeof creatorOrScope === "string";
|
|
15940
|
+
const storeStorage = usePrimaryThree((s) => s.gpuStorage);
|
|
15941
|
+
const created = useScopedResource({
|
|
15942
|
+
store,
|
|
15943
|
+
kind: "gpuStorage",
|
|
15944
|
+
scope: isReader ? void 0 : scope,
|
|
15945
|
+
isLeaf: isStorageLike,
|
|
15946
|
+
create: () => {
|
|
15947
|
+
if (isReader) return {};
|
|
15948
|
+
return creatorOrScope(createLazyCreatorState(store.getState(), store));
|
|
15949
|
+
},
|
|
15950
|
+
prepare: (name, storage) => {
|
|
15951
|
+
const label = scope ? `${scope}.${name}` : name;
|
|
15952
|
+
if ("setName" in storage && typeof storage.setName === "function") {
|
|
15953
|
+
storage.setName(label);
|
|
15954
|
+
}
|
|
15955
|
+
if ("name" in storage && typeof storage.name === "string") {
|
|
15956
|
+
storage.name = label;
|
|
15957
|
+
}
|
|
15958
|
+
return storage;
|
|
15959
|
+
}
|
|
15960
|
+
});
|
|
15961
|
+
let gpuStorage = created;
|
|
15962
|
+
if (creatorOrScope === void 0) {
|
|
15963
|
+
gpuStorage = storeStorage;
|
|
15964
|
+
} else if (typeof creatorOrScope === "string") {
|
|
15965
|
+
const scopeData = storeStorage[creatorOrScope];
|
|
15966
|
+
gpuStorage = scopeData && !isStorageLike(scopeData) ? scopeData : {};
|
|
15967
|
+
}
|
|
15968
|
+
return {
|
|
15969
|
+
...gpuStorage,
|
|
15970
|
+
removeStorage,
|
|
15971
|
+
clearStorage,
|
|
15972
|
+
rebuildStorage,
|
|
15973
|
+
disposeStorage: disposeStorageFn
|
|
15974
|
+
};
|
|
15975
|
+
}
|
|
15976
|
+
function rebuildAllStorage(store, scope) {
|
|
15977
|
+
rebuildResource(store, "gpuStorage", scope);
|
|
15978
|
+
}
|
|
15979
|
+
|
|
15980
|
+
function useRenderPipeline(mainCB, setupCB) {
|
|
16381
15981
|
const store = useStore();
|
|
16382
15982
|
const { scene, camera, renderer, isLegacy } = useThree();
|
|
16383
15983
|
const callbacksRanRef = React.useRef(false);
|
|
@@ -16396,7 +15996,7 @@ function usePostProcessing(mainCB, setupCB) {
|
|
|
16396
15996
|
}, [store]);
|
|
16397
15997
|
const reset = React.useCallback(() => {
|
|
16398
15998
|
store.setState({
|
|
16399
|
-
|
|
15999
|
+
renderPipeline: null,
|
|
16400
16000
|
passes: {}
|
|
16401
16001
|
});
|
|
16402
16002
|
callbacksRanRef.current = false;
|
|
@@ -16409,18 +16009,18 @@ function usePostProcessing(mainCB, setupCB) {
|
|
|
16409
16009
|
}, []);
|
|
16410
16010
|
React.useLayoutEffect(() => {
|
|
16411
16011
|
if (isLegacy) {
|
|
16412
|
-
throw new Error("
|
|
16012
|
+
throw new Error("useRenderPipeline is only available with WebGPU renderer. Set renderer prop on Canvas.");
|
|
16413
16013
|
}
|
|
16414
16014
|
if (!renderer || !scene || !camera) return;
|
|
16415
16015
|
const state = store.getState();
|
|
16416
16016
|
const set = store.setState;
|
|
16417
16017
|
try {
|
|
16418
|
-
let
|
|
16018
|
+
let pipeline = state.renderPipeline;
|
|
16419
16019
|
let currentPasses = { ...state.passes };
|
|
16420
|
-
let
|
|
16421
|
-
if (!
|
|
16422
|
-
|
|
16423
|
-
|
|
16020
|
+
let justCreatedPipeline = false;
|
|
16021
|
+
if (!pipeline) {
|
|
16022
|
+
pipeline = new webgpu.RenderPipeline(renderer);
|
|
16023
|
+
justCreatedPipeline = true;
|
|
16424
16024
|
}
|
|
16425
16025
|
const cacheValid = scenePassCacheRef.current && scenePassCacheRef.current.sceneUuid === scene.uuid && scenePassCacheRef.current.cameraUuid === camera.uuid;
|
|
16426
16026
|
let scenePass;
|
|
@@ -16431,9 +16031,9 @@ function usePostProcessing(mainCB, setupCB) {
|
|
|
16431
16031
|
scenePassCacheRef.current = { sceneUuid: scene.uuid, cameraUuid: camera.uuid, scenePass };
|
|
16432
16032
|
}
|
|
16433
16033
|
currentPasses.scenePass = scenePass;
|
|
16434
|
-
if (!
|
|
16435
|
-
set({
|
|
16436
|
-
const shouldRunCallbacks =
|
|
16034
|
+
if (!pipeline.outputNode || justCreatedPipeline) pipeline.outputNode = scenePass;
|
|
16035
|
+
set({ renderPipeline: pipeline, passes: currentPasses });
|
|
16036
|
+
const shouldRunCallbacks = justCreatedPipeline || !callbacksRanRef.current || !cacheValid;
|
|
16437
16037
|
if (shouldRunCallbacks) {
|
|
16438
16038
|
if (setupCBRef.current) {
|
|
16439
16039
|
const freshState = store.getState();
|
|
@@ -16454,24 +16054,26 @@ function usePostProcessing(mainCB, setupCB) {
|
|
|
16454
16054
|
callbacksRanRef.current = true;
|
|
16455
16055
|
}
|
|
16456
16056
|
} catch (error) {
|
|
16457
|
-
console.
|
|
16057
|
+
console.warn("[useRenderPipeline] Setup failed; the render pipeline was not configured:", error);
|
|
16458
16058
|
}
|
|
16459
16059
|
}, [store, renderer, scene, camera, isLegacy, rebuildVersion]);
|
|
16460
16060
|
const passes = useThree((s) => s.passes);
|
|
16461
|
-
const
|
|
16061
|
+
const renderPipeline = useThree((s) => s.renderPipeline);
|
|
16462
16062
|
return {
|
|
16463
16063
|
passes,
|
|
16464
|
-
|
|
16064
|
+
renderPipeline,
|
|
16465
16065
|
clearPasses,
|
|
16466
16066
|
reset,
|
|
16467
16067
|
rebuild,
|
|
16468
|
-
// isReady indicates if
|
|
16469
|
-
isReady:
|
|
16068
|
+
// isReady indicates if RenderPipeline is configured and ready for rendering
|
|
16069
|
+
isReady: renderPipeline !== null
|
|
16470
16070
|
};
|
|
16471
16071
|
}
|
|
16472
16072
|
|
|
16473
16073
|
extend(THREE);
|
|
16474
16074
|
|
|
16075
|
+
exports.Scheduler = scheduler.Scheduler;
|
|
16076
|
+
exports.getScheduler = scheduler.getScheduler;
|
|
16475
16077
|
exports.Block = Block;
|
|
16476
16078
|
exports.Canvas = Canvas;
|
|
16477
16079
|
exports.Environment = Environment;
|
|
@@ -16487,10 +16089,8 @@ exports.R3F_BUILD_LEGACY = R3F_BUILD_LEGACY;
|
|
|
16487
16089
|
exports.R3F_BUILD_WEBGPU = R3F_BUILD_WEBGPU;
|
|
16488
16090
|
exports.REACT_INTERNAL_PROPS = REACT_INTERNAL_PROPS;
|
|
16489
16091
|
exports.RESERVED_PROPS = RESERVED_PROPS;
|
|
16490
|
-
exports.Scheduler = Scheduler;
|
|
16491
16092
|
exports.Texture = Texture;
|
|
16492
16093
|
exports._roots = _roots;
|
|
16493
|
-
exports.act = act;
|
|
16494
16094
|
exports.addAfterEffect = addAfterEffect;
|
|
16495
16095
|
exports.addEffect = addEffect;
|
|
16496
16096
|
exports.addTail = addTail;
|
|
@@ -16523,7 +16123,6 @@ exports.getInstanceProps = getInstanceProps;
|
|
|
16523
16123
|
exports.getPrimary = getPrimary;
|
|
16524
16124
|
exports.getPrimaryIds = getPrimaryIds;
|
|
16525
16125
|
exports.getRootState = getRootState;
|
|
16526
|
-
exports.getScheduler = getScheduler;
|
|
16527
16126
|
exports.getUuidPrefix = getUuidPrefix;
|
|
16528
16127
|
exports.hasConstructor = hasConstructor;
|
|
16529
16128
|
exports.hasPrimary = hasPrimary;
|
|
@@ -16543,7 +16142,9 @@ exports.isVectorLike = isVectorLike;
|
|
|
16543
16142
|
exports.once = once;
|
|
16544
16143
|
exports.prepare = prepare;
|
|
16545
16144
|
exports.presetsObj = presetsObj;
|
|
16145
|
+
exports.rebuildAllBuffers = rebuildAllBuffers;
|
|
16546
16146
|
exports.rebuildAllNodes = rebuildAllNodes;
|
|
16147
|
+
exports.rebuildAllStorage = rebuildAllStorage;
|
|
16547
16148
|
exports.rebuildAllUniforms = rebuildAllUniforms;
|
|
16548
16149
|
exports.reconciler = reconciler;
|
|
16549
16150
|
exports.registerPrimary = registerPrimary;
|
|
@@ -16551,13 +16152,16 @@ exports.removeInteractivity = removeInteractivity;
|
|
|
16551
16152
|
exports.removeNodes = removeNodes;
|
|
16552
16153
|
exports.removeUniforms = removeUniforms;
|
|
16553
16154
|
exports.resolve = resolve;
|
|
16155
|
+
exports.swapInteractivity = swapInteractivity;
|
|
16554
16156
|
exports.unmountComponentAtNode = unmountComponentAtNode;
|
|
16555
16157
|
exports.unregisterPrimary = unregisterPrimary;
|
|
16556
16158
|
exports.updateCamera = updateCamera;
|
|
16557
16159
|
exports.updateFrustum = updateFrustum;
|
|
16558
16160
|
exports.useBridge = useBridge;
|
|
16161
|
+
exports.useBuffers = useBuffers;
|
|
16559
16162
|
exports.useEnvironment = useEnvironment;
|
|
16560
16163
|
exports.useFrame = useFrame;
|
|
16164
|
+
exports.useGPUStorage = useGPUStorage;
|
|
16561
16165
|
exports.useGraph = useGraph;
|
|
16562
16166
|
exports.useInstanceHandle = useInstanceHandle;
|
|
16563
16167
|
exports.useIsomorphicLayoutEffect = useIsomorphicLayoutEffect;
|
|
@@ -16565,7 +16169,7 @@ exports.useLoader = useLoader;
|
|
|
16565
16169
|
exports.useLocalNodes = useLocalNodes;
|
|
16566
16170
|
exports.useMutableCallback = useMutableCallback;
|
|
16567
16171
|
exports.useNodes = useNodes;
|
|
16568
|
-
exports.
|
|
16172
|
+
exports.useRenderPipeline = useRenderPipeline;
|
|
16569
16173
|
exports.useRenderTarget = useRenderTarget;
|
|
16570
16174
|
exports.useStore = useStore;
|
|
16571
16175
|
exports.useTexture = useTexture;
|