@react-three/fiber 10.0.0-canary.aecbafb → 10.0.0-canary.b6c54f2
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 +433 -1319
- package/dist/index.d.cts +169 -801
- package/dist/index.d.mts +169 -801
- package/dist/index.d.ts +169 -801
- package/dist/index.mjs +407 -1292
- package/dist/legacy.cjs +388 -1274
- package/dist/legacy.d.cts +169 -800
- package/dist/legacy.d.mts +169 -800
- package/dist/legacy.d.ts +169 -800
- package/dist/legacy.mjs +361 -1246
- package/dist/webgpu/index.cjs +953 -2062
- package/dist/webgpu/index.d.cts +202 -804
- package/dist/webgpu/index.d.mts +202 -804
- package/dist/webgpu/index.d.ts +202 -804
- package/dist/webgpu/index.mjs +928 -2036
- 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,9 +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$
|
|
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);
|
|
447
467
|
const act = React__namespace["act"];
|
|
448
468
|
const useIsomorphicLayoutEffect = /* @__PURE__ */ (() => typeof window !== "undefined" && (window.document?.createElement || window.navigator?.product === "ReactNative"))() ? React__namespace.useLayoutEffect : React__namespace.useEffect;
|
|
449
469
|
function useMutableCallback(fn) {
|
|
@@ -475,7 +495,7 @@ const ErrorBoundary = /* @__PURE__ */ (() => {
|
|
|
475
495
|
return _a = class extends React__namespace.Component {
|
|
476
496
|
constructor() {
|
|
477
497
|
super(...arguments);
|
|
478
|
-
__publicField$
|
|
498
|
+
__publicField$1(this, "state", { error: false });
|
|
479
499
|
}
|
|
480
500
|
componentDidCatch(err) {
|
|
481
501
|
this.props.set(err);
|
|
@@ -483,7 +503,7 @@ const ErrorBoundary = /* @__PURE__ */ (() => {
|
|
|
483
503
|
render() {
|
|
484
504
|
return this.state.error ? null : this.props.children;
|
|
485
505
|
}
|
|
486
|
-
}, __publicField$
|
|
506
|
+
}, __publicField$1(_a, "getDerivedStateFromError", () => ({ error: true })), _a;
|
|
487
507
|
})();
|
|
488
508
|
|
|
489
509
|
const is = {
|
|
@@ -620,8 +640,7 @@ function prepare(target, root, type, props) {
|
|
|
620
640
|
object,
|
|
621
641
|
eventCount: 0,
|
|
622
642
|
handlers: {},
|
|
623
|
-
isHidden: false
|
|
624
|
-
deferredRefs: []
|
|
643
|
+
isHidden: false
|
|
625
644
|
};
|
|
626
645
|
if (object) object.__r3f = instance;
|
|
627
646
|
}
|
|
@@ -778,6 +797,17 @@ function unregisterVisibility(store, object) {
|
|
|
778
797
|
internal.visibilityRegistry.delete(object.uuid);
|
|
779
798
|
internal.occlusionCache.delete(object);
|
|
780
799
|
}
|
|
800
|
+
function updateVisibilityHandlers(store, object, handlers) {
|
|
801
|
+
const { internal } = store.getState();
|
|
802
|
+
const entry = internal.visibilityRegistry.get(object.uuid);
|
|
803
|
+
if (!entry) return false;
|
|
804
|
+
entry.handlers = handlers;
|
|
805
|
+
if ((handlers.onOccluded || handlers.onVisible) && !internal.occlusionEnabled) {
|
|
806
|
+
object.occlusionTest = true;
|
|
807
|
+
enableOcclusion(store);
|
|
808
|
+
}
|
|
809
|
+
return true;
|
|
810
|
+
}
|
|
781
811
|
function checkVisibility(state) {
|
|
782
812
|
const { internal, camera } = state;
|
|
783
813
|
const registry = internal.visibilityRegistry;
|
|
@@ -965,10 +995,7 @@ function applyProps(object, props) {
|
|
|
965
995
|
continue;
|
|
966
996
|
}
|
|
967
997
|
if (value === void 0) continue;
|
|
968
|
-
if (isFromRef(value))
|
|
969
|
-
instance?.deferredRefs?.push({ prop, ref: value[FROM_REF] });
|
|
970
|
-
continue;
|
|
971
|
-
}
|
|
998
|
+
if (isFromRef(value)) continue;
|
|
972
999
|
if (isOnce(value)) {
|
|
973
1000
|
if (instance?.appliedOnce?.has(prop)) continue;
|
|
974
1001
|
if (instance) {
|
|
@@ -1004,6 +1031,19 @@ function applyProps(object, props) {
|
|
|
1004
1031
|
} else if (isVectorLike(target) && is.num(value)) {
|
|
1005
1032
|
if ("setScalar" in target && typeof target.setScalar === "function") target.setScalar(value);
|
|
1006
1033
|
else target.set(value);
|
|
1034
|
+
} else if (root instanceof webgpu.ShaderMaterial && key === "uniforms" && is.obj(value)) {
|
|
1035
|
+
if (!is.obj(root.uniforms)) root.uniforms = {};
|
|
1036
|
+
const uniforms = root.uniforms;
|
|
1037
|
+
const nextUniforms = value;
|
|
1038
|
+
for (const name in nextUniforms) {
|
|
1039
|
+
const uniform = nextUniforms[name];
|
|
1040
|
+
const targetUniform = uniforms[name];
|
|
1041
|
+
if (targetUniform) Object.assign(targetUniform, uniform);
|
|
1042
|
+
else {
|
|
1043
|
+
const nextUniform = uniform instanceof webgpu.Uniform ? uniform.clone() : new webgpu.Uniform(uniform.value);
|
|
1044
|
+
uniforms[name] = nextUniform;
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1007
1047
|
} else {
|
|
1008
1048
|
root[key] = value;
|
|
1009
1049
|
if (key.endsWith("Node") && root.isMaterial) {
|
|
@@ -1022,6 +1062,9 @@ function applyProps(object, props) {
|
|
|
1022
1062
|
if (instance.eventCount && object2.raycast !== null) {
|
|
1023
1063
|
rootState.internal.interaction.push(object2);
|
|
1024
1064
|
}
|
|
1065
|
+
}
|
|
1066
|
+
if (instance?.parent && rootState?.internal && instance.object?.isObject3D) {
|
|
1067
|
+
const object2 = instance.object;
|
|
1025
1068
|
const root = findInitialRoot(instance);
|
|
1026
1069
|
const visibilityHandlers = {
|
|
1027
1070
|
onFramed: instance.handlers.onFramed,
|
|
@@ -1029,7 +1072,9 @@ function applyProps(object, props) {
|
|
|
1029
1072
|
onVisible: instance.handlers.onVisible
|
|
1030
1073
|
};
|
|
1031
1074
|
if (hasVisibilityHandlers(visibilityHandlers)) {
|
|
1032
|
-
|
|
1075
|
+
if (!updateVisibilityHandlers(root, object2, visibilityHandlers)) {
|
|
1076
|
+
registerVisibility(root, object2, visibilityHandlers);
|
|
1077
|
+
}
|
|
1033
1078
|
} else {
|
|
1034
1079
|
unregisterVisibility(root, object2);
|
|
1035
1080
|
}
|
|
@@ -1297,7 +1342,7 @@ function createEvents(store) {
|
|
|
1297
1342
|
}
|
|
1298
1343
|
function processDeferredPointer(event, pointerId) {
|
|
1299
1344
|
const state = store.getState();
|
|
1300
|
-
const {
|
|
1345
|
+
const { internal } = state;
|
|
1301
1346
|
if (!state.events.enabled) return;
|
|
1302
1347
|
const filter = filterPointerEvents;
|
|
1303
1348
|
const hits = intersect(event, filter);
|
|
@@ -1532,6 +1577,7 @@ function createPointerEvents(store) {
|
|
|
1532
1577
|
}
|
|
1533
1578
|
},
|
|
1534
1579
|
connect: (target) => {
|
|
1580
|
+
if (!target) return;
|
|
1535
1581
|
const { set, events } = store.getState();
|
|
1536
1582
|
events.disconnect?.();
|
|
1537
1583
|
set((state) => ({ events: { ...state.events, connected: target } }));
|
|
@@ -1556,6 +1602,7 @@ function createPointerEvents(store) {
|
|
|
1556
1602
|
set((state) => ({ events: { ...state.events, connected: void 0 } }));
|
|
1557
1603
|
}
|
|
1558
1604
|
},
|
|
1605
|
+
// PREVIEW (A8): the XR pointer registration API may change before stable.
|
|
1559
1606
|
registerPointer: (config) => {
|
|
1560
1607
|
const pointerId = nextXRPointerId++;
|
|
1561
1608
|
xrPointers.set(pointerId, config);
|
|
@@ -1642,1229 +1689,206 @@ function notifyAlpha({ message, link }) {
|
|
|
1642
1689
|
}
|
|
1643
1690
|
}
|
|
1644
1691
|
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
const
|
|
1649
|
-
|
|
1650
|
-
constructor() {
|
|
1651
|
-
/** Ordered list of phase nodes */
|
|
1652
|
-
__publicField$2(this, "phases", []);
|
|
1653
|
-
/** Quick lookup by name */
|
|
1654
|
-
__publicField$2(this, "phaseMap", /* @__PURE__ */ new Map());
|
|
1655
|
-
/** Cached ordered names (invalidated on changes) */
|
|
1656
|
-
__publicField$2(this, "orderedNamesCache", null);
|
|
1657
|
-
this.initializeDefaultPhases();
|
|
1658
|
-
}
|
|
1659
|
-
//* Initialization --------------------------------
|
|
1660
|
-
initializeDefaultPhases() {
|
|
1661
|
-
for (const name of DEFAULT_PHASES) {
|
|
1662
|
-
const node = { name, isAutoGenerated: false };
|
|
1663
|
-
this.phases.push(node);
|
|
1664
|
-
this.phaseMap.set(name, node);
|
|
1665
|
-
}
|
|
1666
|
-
this.invalidateCache();
|
|
1667
|
-
}
|
|
1668
|
-
//* Public API --------------------------------
|
|
1669
|
-
/**
|
|
1670
|
-
* Add a named phase to the graph
|
|
1671
|
-
* @param name - Phase name (must be unique)
|
|
1672
|
-
* @param options - Position options (before or after another phase)
|
|
1673
|
-
*/
|
|
1674
|
-
addPhase(name, options = {}) {
|
|
1675
|
-
if (this.phaseMap.has(name)) {
|
|
1676
|
-
console.warn(`[useFrame] Phase "${name}" already exists`);
|
|
1677
|
-
return;
|
|
1678
|
-
}
|
|
1679
|
-
const { before, after } = options;
|
|
1680
|
-
const node = { name, isAutoGenerated: false };
|
|
1681
|
-
let insertIndex = this.phases.length;
|
|
1682
|
-
const targetIndex = this.getPhaseIndex(before ?? after);
|
|
1683
|
-
if (targetIndex !== -1) {
|
|
1684
|
-
insertIndex = before ? targetIndex : targetIndex + 1;
|
|
1685
|
-
} else {
|
|
1686
|
-
const constraintType = before ? "before" : "after";
|
|
1687
|
-
console.warn(`[useFrame] Phase "${before ?? after}" not found for '${constraintType}' constraint`);
|
|
1688
|
-
}
|
|
1689
|
-
this.phases.splice(insertIndex, 0, node);
|
|
1690
|
-
this.phaseMap.set(name, node);
|
|
1691
|
-
this.invalidateCache();
|
|
1692
|
-
}
|
|
1693
|
-
/**
|
|
1694
|
-
* Get ordered list of phase names
|
|
1695
|
-
*/
|
|
1696
|
-
getOrderedPhases() {
|
|
1697
|
-
if (this.orderedNamesCache === null) this.orderedNamesCache = this.phases.map((p) => p.name);
|
|
1698
|
-
return this.orderedNamesCache;
|
|
1699
|
-
}
|
|
1700
|
-
/**
|
|
1701
|
-
* Check if a phase exists
|
|
1702
|
-
*/
|
|
1703
|
-
hasPhase(name) {
|
|
1704
|
-
return this.phaseMap.has(name);
|
|
1705
|
-
}
|
|
1706
|
-
/**
|
|
1707
|
-
* Get the index of a phase (-1 if not found)
|
|
1708
|
-
*/
|
|
1709
|
-
getPhaseIndex(name) {
|
|
1710
|
-
if (!name) return -1;
|
|
1711
|
-
return this.phases.findIndex((p) => p.name === name);
|
|
1712
|
-
}
|
|
1713
|
-
/**
|
|
1714
|
-
* Ensure a phase exists, creating an auto-generated one if needed.
|
|
1715
|
-
* Used for resolving before/after constraints.
|
|
1716
|
-
*
|
|
1717
|
-
* @param name - The phase name to ensure exists
|
|
1718
|
-
* @returns The phase name (may be auto-generated like 'before:render')
|
|
1719
|
-
*/
|
|
1720
|
-
ensurePhase(name) {
|
|
1721
|
-
if (this.phaseMap.has(name)) return name;
|
|
1722
|
-
const node = { name, isAutoGenerated: true };
|
|
1723
|
-
this.phases.push(node);
|
|
1724
|
-
this.phaseMap.set(name, node);
|
|
1725
|
-
this.invalidateCache();
|
|
1726
|
-
return name;
|
|
1727
|
-
}
|
|
1728
|
-
/**
|
|
1729
|
-
* Resolve where a job with before/after constraints should go.
|
|
1730
|
-
* Creates auto-generated phases if needed.
|
|
1731
|
-
*
|
|
1732
|
-
* @param before - Phase(s) to run before
|
|
1733
|
-
* @param after - Phase(s) to run after
|
|
1734
|
-
* @returns The resolved phase name
|
|
1735
|
-
*/
|
|
1736
|
-
resolveConstraintPhase(before, after) {
|
|
1737
|
-
const beforeArr = before ? Array.isArray(before) ? before : [before] : [];
|
|
1738
|
-
const afterArr = after ? Array.isArray(after) ? after : [after] : [];
|
|
1739
|
-
if (beforeArr.length > 0) {
|
|
1740
|
-
return this.ensureAutoPhase(beforeArr[0], "before", 0);
|
|
1741
|
-
}
|
|
1742
|
-
if (afterArr.length > 0) {
|
|
1743
|
-
return this.ensureAutoPhase(afterArr[0], "after", 1);
|
|
1744
|
-
}
|
|
1745
|
-
return "update";
|
|
1746
|
-
}
|
|
1747
|
-
/**
|
|
1748
|
-
* Ensure an auto-generated phase exists relative to a target phase.
|
|
1749
|
-
* Creates the phase if it doesn't exist, inserting it at the correct position.
|
|
1750
|
-
*
|
|
1751
|
-
* @param target - The target phase name to position relative to
|
|
1752
|
-
* @param prefix - Prefix for auto-generated phase name ('before' or 'after')
|
|
1753
|
-
* @param offset - Insertion offset (0 for before, 1 for after)
|
|
1754
|
-
* @returns The auto-generated phase name
|
|
1755
|
-
*/
|
|
1756
|
-
ensureAutoPhase(target, prefix, offset) {
|
|
1757
|
-
const autoName = `${prefix}:${target}`;
|
|
1758
|
-
if (this.phaseMap.has(autoName)) return autoName;
|
|
1759
|
-
const node = { name: autoName, isAutoGenerated: true };
|
|
1760
|
-
const targetIndex = this.getPhaseIndex(target);
|
|
1761
|
-
if (targetIndex !== -1) this.phases.splice(targetIndex + offset, 0, node);
|
|
1762
|
-
else this.phases.push(node);
|
|
1763
|
-
this.phaseMap.set(autoName, node);
|
|
1764
|
-
this.invalidateCache();
|
|
1765
|
-
return autoName;
|
|
1766
|
-
}
|
|
1767
|
-
// Internal --------------------------------
|
|
1768
|
-
invalidateCache() {
|
|
1769
|
-
this.orderedNamesCache = null;
|
|
1770
|
-
}
|
|
1692
|
+
const INTERNAL_LIB_MARKERS = ["zustand", "setState", "Object.assign"];
|
|
1693
|
+
function isInternalRendererAccess(stack, callerDepth = 2) {
|
|
1694
|
+
if (!stack) return false;
|
|
1695
|
+
const caller = stack.split("\n")[callerDepth] ?? "";
|
|
1696
|
+
return INTERNAL_LIB_MARKERS.some((m) => caller.includes(m));
|
|
1771
1697
|
}
|
|
1772
1698
|
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
if (a.priority !== b.priority) return b.priority - a.priority;
|
|
1794
|
-
return a.index - b.index;
|
|
1795
|
-
});
|
|
1796
|
-
sortedBuckets.push(hasCrossJobConstraints(bucket) ? topologicalSort(bucket) : bucket);
|
|
1797
|
-
}
|
|
1798
|
-
for (const [phase, bucket] of buckets) {
|
|
1799
|
-
if (!orderedPhases.includes(phase) && bucket.length > 0) {
|
|
1800
|
-
bucket.sort((a, b) => {
|
|
1801
|
-
if (a.priority !== b.priority) return b.priority - a.priority;
|
|
1802
|
-
return a.index - b.index;
|
|
1803
|
-
});
|
|
1804
|
-
sortedBuckets.push(bucket);
|
|
1805
|
-
}
|
|
1806
|
-
}
|
|
1807
|
-
return sortedBuckets.flat();
|
|
1808
|
-
}
|
|
1809
|
-
function hasCrossJobConstraints(bucket) {
|
|
1810
|
-
const jobIds = new Set(bucket.map((j) => j.id));
|
|
1811
|
-
for (const job of bucket) {
|
|
1812
|
-
for (const ref of job.before) {
|
|
1813
|
-
if (jobIds.has(ref)) return true;
|
|
1814
|
-
}
|
|
1815
|
-
for (const ref of job.after) {
|
|
1816
|
-
if (jobIds.has(ref)) return true;
|
|
1817
|
-
}
|
|
1818
|
-
}
|
|
1819
|
-
return false;
|
|
1820
|
-
}
|
|
1821
|
-
function topologicalSort(jobs) {
|
|
1822
|
-
const n = jobs.length;
|
|
1823
|
-
if (n <= 1) return jobs;
|
|
1824
|
-
const jobMap = /* @__PURE__ */ new Map();
|
|
1825
|
-
const inDegree = /* @__PURE__ */ new Map();
|
|
1826
|
-
const adjacency = /* @__PURE__ */ new Map();
|
|
1827
|
-
for (const job of jobs) {
|
|
1828
|
-
jobMap.set(job.id, job);
|
|
1829
|
-
inDegree.set(job.id, 0);
|
|
1830
|
-
adjacency.set(job.id, []);
|
|
1831
|
-
}
|
|
1832
|
-
for (const job of jobs) {
|
|
1833
|
-
for (const ref of job.before) {
|
|
1834
|
-
if (jobMap.has(ref)) {
|
|
1835
|
-
adjacency.get(job.id).push(ref);
|
|
1836
|
-
inDegree.set(ref, inDegree.get(ref) + 1);
|
|
1699
|
+
const R3F_CONTEXT = Symbol.for("@react-three/fiber.context");
|
|
1700
|
+
const context = globalThis[R3F_CONTEXT] ?? (globalThis[R3F_CONTEXT] = React__namespace.createContext(null));
|
|
1701
|
+
const createStore = (invalidate, advance) => {
|
|
1702
|
+
const rootStore = traditional.createWithEqualityFn((set, get) => {
|
|
1703
|
+
const position = new webgpu.Vector3();
|
|
1704
|
+
const defaultTarget = new webgpu.Vector3();
|
|
1705
|
+
const tempTarget = new webgpu.Vector3();
|
|
1706
|
+
function getCurrentViewport(camera = get().camera, target = defaultTarget, size = get().size) {
|
|
1707
|
+
const { width, height, top, left } = size;
|
|
1708
|
+
const aspect = width / height;
|
|
1709
|
+
if (target.isVector3) tempTarget.copy(target);
|
|
1710
|
+
else tempTarget.set(...target);
|
|
1711
|
+
const distance = camera.getWorldPosition(position).distanceTo(tempTarget);
|
|
1712
|
+
if (isOrthographicCamera(camera)) {
|
|
1713
|
+
return { width: width / camera.zoom, height: height / camera.zoom, top, left, factor: 1, distance, aspect };
|
|
1714
|
+
} else {
|
|
1715
|
+
const fov = camera.fov * Math.PI / 180;
|
|
1716
|
+
const h = 2 * Math.tan(fov / 2) * distance;
|
|
1717
|
+
const w = h * (width / height);
|
|
1718
|
+
return { width: w, height: h, top, left, factor: width / w, distance, aspect };
|
|
1837
1719
|
}
|
|
1838
1720
|
}
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1721
|
+
let performanceTimeout = void 0;
|
|
1722
|
+
const setPerformanceCurrent = (current) => set((state2) => ({ performance: { ...state2.performance, current } }));
|
|
1723
|
+
const pointer = new webgpu.Vector2();
|
|
1724
|
+
const rootState = {
|
|
1725
|
+
set,
|
|
1726
|
+
get,
|
|
1727
|
+
// Mock objects that have to be configured
|
|
1728
|
+
// primaryStore is set after store creation (self-reference for primary, primary's store for secondary)
|
|
1729
|
+
primaryStore: null,
|
|
1730
|
+
gl: null,
|
|
1731
|
+
renderer: null,
|
|
1732
|
+
camera: null,
|
|
1733
|
+
frustum: new webgpu.Frustum(),
|
|
1734
|
+
autoUpdateFrustum: true,
|
|
1735
|
+
raycaster: null,
|
|
1736
|
+
events: {
|
|
1737
|
+
priority: 1,
|
|
1738
|
+
enabled: true,
|
|
1739
|
+
connected: false,
|
|
1740
|
+
frameTimedRaycasts: true,
|
|
1741
|
+
alwaysFireOnScroll: true,
|
|
1742
|
+
updateOnFrame: false
|
|
1743
|
+
},
|
|
1744
|
+
scene: null,
|
|
1745
|
+
rootScene: null,
|
|
1746
|
+
xr: null,
|
|
1747
|
+
inspector: null,
|
|
1748
|
+
invalidate: (frames = 1, stackFrames = false) => invalidate(get(), frames, stackFrames),
|
|
1749
|
+
advance: (timestamp, runGlobalEffects) => advance(timestamp, runGlobalEffects, get()),
|
|
1750
|
+
textureColorSpace: webgpu.SRGBColorSpace,
|
|
1751
|
+
isLegacy: false,
|
|
1752
|
+
webGPUSupported: false,
|
|
1753
|
+
isNative: false,
|
|
1754
|
+
controls: null,
|
|
1755
|
+
pointer,
|
|
1756
|
+
mouse: pointer,
|
|
1757
|
+
frameloop: "always",
|
|
1758
|
+
onPointerMissed: void 0,
|
|
1759
|
+
onDragOverMissed: void 0,
|
|
1760
|
+
onDropMissed: void 0,
|
|
1761
|
+
performance: {
|
|
1762
|
+
current: 1,
|
|
1763
|
+
min: 0.5,
|
|
1764
|
+
max: 1,
|
|
1765
|
+
debounce: 200,
|
|
1766
|
+
regress: () => {
|
|
1767
|
+
const state2 = get();
|
|
1768
|
+
if (performanceTimeout) clearTimeout(performanceTimeout);
|
|
1769
|
+
if (state2.performance.current !== state2.performance.min) setPerformanceCurrent(state2.performance.min);
|
|
1770
|
+
performanceTimeout = setTimeout(
|
|
1771
|
+
() => setPerformanceCurrent(get().performance.max),
|
|
1772
|
+
state2.performance.debounce
|
|
1773
|
+
);
|
|
1774
|
+
}
|
|
1775
|
+
},
|
|
1776
|
+
size: { width: 0, height: 0, top: 0, left: 0 },
|
|
1777
|
+
viewport: {
|
|
1778
|
+
initialDpr: 0,
|
|
1779
|
+
dpr: 0,
|
|
1780
|
+
width: 0,
|
|
1781
|
+
height: 0,
|
|
1782
|
+
top: 0,
|
|
1783
|
+
left: 0,
|
|
1784
|
+
aspect: 0,
|
|
1785
|
+
distance: 0,
|
|
1786
|
+
factor: 0,
|
|
1787
|
+
getCurrentViewport
|
|
1788
|
+
},
|
|
1789
|
+
setEvents: (events) => set((state2) => ({ ...state2, events: { ...state2.events, ...events } })),
|
|
1790
|
+
setSize: (width, height, top, left) => {
|
|
1791
|
+
const state2 = get();
|
|
1792
|
+
if (width === void 0) {
|
|
1793
|
+
set({ _sizeImperative: false });
|
|
1794
|
+
if (state2._sizeProps) {
|
|
1795
|
+
const { width: propW, height: propH } = state2._sizeProps;
|
|
1796
|
+
if (propW !== void 0 || propH !== void 0) {
|
|
1797
|
+
const currentSize = state2.size;
|
|
1798
|
+
const newSize = {
|
|
1799
|
+
width: propW ?? currentSize.width,
|
|
1800
|
+
height: propH ?? currentSize.height,
|
|
1801
|
+
top: currentSize.top,
|
|
1802
|
+
left: currentSize.left
|
|
1803
|
+
};
|
|
1804
|
+
set((s) => ({
|
|
1805
|
+
size: newSize,
|
|
1806
|
+
viewport: { ...s.viewport, ...getCurrentViewport(state2.camera, defaultTarget, newSize) }
|
|
1807
|
+
}));
|
|
1808
|
+
scheduler.getScheduler().invalidate();
|
|
1809
|
+
}
|
|
1810
|
+
}
|
|
1811
|
+
return;
|
|
1812
|
+
}
|
|
1813
|
+
const w = width;
|
|
1814
|
+
const h = height ?? width;
|
|
1815
|
+
const t = top ?? state2.size.top;
|
|
1816
|
+
const l = left ?? state2.size.left;
|
|
1817
|
+
const size = { width: w, height: h, top: t, left: l };
|
|
1818
|
+
set((s) => ({
|
|
1819
|
+
size,
|
|
1820
|
+
viewport: { ...s.viewport, ...getCurrentViewport(state2.camera, defaultTarget, size) },
|
|
1821
|
+
_sizeImperative: true
|
|
1822
|
+
}));
|
|
1823
|
+
scheduler.getScheduler().invalidate();
|
|
1824
|
+
},
|
|
1825
|
+
setDpr: (dpr) => set((state2) => {
|
|
1826
|
+
const resolved = calculateDpr(dpr);
|
|
1827
|
+
return { viewport: { ...state2.viewport, dpr: resolved, initialDpr: state2.viewport.initialDpr || resolved } };
|
|
1828
|
+
}),
|
|
1829
|
+
setFrameloop: (frameloop = "always") => {
|
|
1830
|
+
set(() => ({ frameloop }));
|
|
1831
|
+
scheduler.getScheduler().frameloop = frameloop;
|
|
1832
|
+
},
|
|
1833
|
+
setError: (error) => set(() => ({ error })),
|
|
1834
|
+
error: null,
|
|
1835
|
+
//* TSL State (managed via hooks: useUniforms, useNodes, useBuffers, useGPUStorage, useTextures, useRenderPipeline) ==============================
|
|
1836
|
+
uniforms: {},
|
|
1837
|
+
nodes: {},
|
|
1838
|
+
buffers: {},
|
|
1839
|
+
gpuStorage: {},
|
|
1840
|
+
textures: /* @__PURE__ */ new Map(),
|
|
1841
|
+
_textureRefs: /* @__PURE__ */ new Map(),
|
|
1842
|
+
renderPipeline: null,
|
|
1843
|
+
passes: {},
|
|
1844
|
+
_hmrVersion: 0,
|
|
1845
|
+
_sizeImperative: false,
|
|
1846
|
+
_sizeProps: null,
|
|
1847
|
+
previousRoot: void 0,
|
|
1848
|
+
internal: {
|
|
1849
|
+
// Events
|
|
1850
|
+
interaction: [],
|
|
1851
|
+
subscribers: [],
|
|
1852
|
+
// Per-pointer state (new unified structure)
|
|
1853
|
+
pointerMap: /* @__PURE__ */ new Map(),
|
|
1854
|
+
pointerDirty: /* @__PURE__ */ new Map(),
|
|
1855
|
+
lastEvent: React__namespace.createRef(),
|
|
1856
|
+
// Deprecated but kept for backwards compatibility
|
|
1857
|
+
hovered: /* @__PURE__ */ new Map(),
|
|
1858
|
+
initialClick: [0, 0],
|
|
1859
|
+
initialHits: [],
|
|
1860
|
+
capturedMap: /* @__PURE__ */ new Map(),
|
|
1861
|
+
// Visibility tracking (onFramed, onOccluded, onVisible)
|
|
1862
|
+
visibilityRegistry: /* @__PURE__ */ new Map(),
|
|
1863
|
+
// Occlusion system (WebGPU only)
|
|
1864
|
+
occlusionEnabled: false,
|
|
1865
|
+
occlusionObserver: null,
|
|
1866
|
+
occlusionCache: /* @__PURE__ */ new Map(),
|
|
1867
|
+
helperGroup: null,
|
|
1868
|
+
// Updates
|
|
1869
|
+
active: false,
|
|
1870
|
+
frames: 0,
|
|
1871
|
+
priority: 0,
|
|
1872
|
+
subscribe: (ref, priority, store) => {
|
|
1873
|
+
const internal = get().internal;
|
|
1874
|
+
internal.priority = internal.priority + (priority > 0 ? 1 : 0);
|
|
1875
|
+
internal.subscribers.push({ ref, priority, store });
|
|
1876
|
+
internal.subscribers = internal.subscribers.sort((a, b) => a.priority - b.priority);
|
|
1877
|
+
return () => {
|
|
1878
|
+
const internal2 = get().internal;
|
|
1879
|
+
if (internal2?.subscribers) {
|
|
1880
|
+
internal2.priority = internal2.priority - (priority > 0 ? 1 : 0);
|
|
1881
|
+
internal2.subscribers = internal2.subscribers.filter((s) => s.ref !== ref);
|
|
1882
|
+
}
|
|
1883
|
+
};
|
|
1884
|
+
},
|
|
1885
|
+
// Renderer Storage (single source of truth)
|
|
1886
|
+
actualRenderer: null,
|
|
1887
|
+
// Scheduler for useFrameNext (initialized in renderer.tsx)
|
|
1888
|
+
scheduler: null
|
|
1843
1889
|
}
|
|
1844
|
-
}
|
|
1845
|
-
|
|
1846
|
-
const queue = [];
|
|
1847
|
-
for (const job of jobs) {
|
|
1848
|
-
if (inDegree.get(job.id) === 0) {
|
|
1849
|
-
queue.push(job);
|
|
1850
|
-
}
|
|
1851
|
-
}
|
|
1852
|
-
queue.sort((a, b) => {
|
|
1853
|
-
if (a.priority !== b.priority) return b.priority - a.priority;
|
|
1854
|
-
return a.index - b.index;
|
|
1855
|
-
});
|
|
1856
|
-
const result = [];
|
|
1857
|
-
while (queue.length > 0) {
|
|
1858
|
-
const job = queue.shift();
|
|
1859
|
-
result.push(job);
|
|
1860
|
-
const neighbors = adjacency.get(job.id) || [];
|
|
1861
|
-
for (const neighborId of neighbors) {
|
|
1862
|
-
const newDegree = inDegree.get(neighborId) - 1;
|
|
1863
|
-
inDegree.set(neighborId, newDegree);
|
|
1864
|
-
if (newDegree === 0) {
|
|
1865
|
-
const neighbor = jobMap.get(neighborId);
|
|
1866
|
-
insertSorted(queue, neighbor);
|
|
1867
|
-
}
|
|
1868
|
-
}
|
|
1869
|
-
}
|
|
1870
|
-
if (result.length !== n) {
|
|
1871
|
-
console.warn("[useFrame] Circular dependency detected in job constraints");
|
|
1872
|
-
const resultIds = new Set(result.map((j) => j.id));
|
|
1873
|
-
for (const job of jobs) {
|
|
1874
|
-
if (!resultIds.has(job.id)) result.push(job);
|
|
1875
|
-
}
|
|
1876
|
-
}
|
|
1877
|
-
return result;
|
|
1878
|
-
}
|
|
1879
|
-
function insertSorted(arr, job) {
|
|
1880
|
-
let i = 0;
|
|
1881
|
-
while (i < arr.length) {
|
|
1882
|
-
const cmp = arr[i];
|
|
1883
|
-
if (job.priority > cmp.priority || job.priority === cmp.priority && job.index < cmp.index) {
|
|
1884
|
-
break;
|
|
1885
|
-
}
|
|
1886
|
-
i++;
|
|
1887
|
-
}
|
|
1888
|
-
arr.splice(i, 0, job);
|
|
1889
|
-
}
|
|
1890
|
-
|
|
1891
|
-
function shouldRun(job, now) {
|
|
1892
|
-
if (!job.enabled) return false;
|
|
1893
|
-
if (!job.fps) return true;
|
|
1894
|
-
const minInterval = 1e3 / job.fps;
|
|
1895
|
-
const lastRun = job.lastRun ?? 0;
|
|
1896
|
-
const elapsed = now - lastRun;
|
|
1897
|
-
if (elapsed < minInterval) return false;
|
|
1898
|
-
if (job.drop) {
|
|
1899
|
-
job.lastRun = now;
|
|
1900
|
-
} else {
|
|
1901
|
-
const steps = Math.floor(elapsed / minInterval);
|
|
1902
|
-
job.lastRun = lastRun + steps * minInterval;
|
|
1903
|
-
if (job.lastRun < now - minInterval) {
|
|
1904
|
-
job.lastRun = now;
|
|
1905
|
-
}
|
|
1906
|
-
}
|
|
1907
|
-
return true;
|
|
1908
|
-
}
|
|
1909
|
-
function resetJobTiming(job) {
|
|
1910
|
-
job.lastRun = void 0;
|
|
1911
|
-
}
|
|
1912
|
-
|
|
1913
|
-
var __defProp$1 = Object.defineProperty;
|
|
1914
|
-
var __defNormalProp$1 = (obj, key, value) => key in obj ? __defProp$1(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
1915
|
-
var __publicField$1 = (obj, key, value) => __defNormalProp$1(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
1916
|
-
const hmrData = (() => {
|
|
1917
|
-
if (typeof process !== "undefined" && process.env.NODE_ENV === "test") return void 0;
|
|
1918
|
-
if (typeof import_meta_hot !== "undefined") return import_meta_hot;
|
|
1919
|
-
try {
|
|
1920
|
-
return (0, eval)("import.meta.hot");
|
|
1921
|
-
} catch {
|
|
1922
|
-
return void 0;
|
|
1923
|
-
}
|
|
1924
|
-
})();
|
|
1925
|
-
const _Scheduler = class _Scheduler {
|
|
1926
|
-
//* Constructor ================================
|
|
1927
|
-
constructor() {
|
|
1928
|
-
//* Critical State ================================
|
|
1929
|
-
__publicField$1(this, "roots", /* @__PURE__ */ new Map());
|
|
1930
|
-
__publicField$1(this, "phaseGraph");
|
|
1931
|
-
__publicField$1(this, "loopState", {
|
|
1932
|
-
running: false,
|
|
1933
|
-
rafHandle: null,
|
|
1934
|
-
lastTime: null,
|
|
1935
|
-
// null = uninitialized, 0+ = valid timestamp
|
|
1936
|
-
frameCount: 0,
|
|
1937
|
-
elapsedTime: 0,
|
|
1938
|
-
createdAt: performance.now()
|
|
1939
|
-
});
|
|
1940
|
-
__publicField$1(this, "stoppedTime", 0);
|
|
1941
|
-
//* Private State ================================
|
|
1942
|
-
__publicField$1(this, "nextRootIndex", 0);
|
|
1943
|
-
__publicField$1(this, "globalBeforeJobs", /* @__PURE__ */ new Map());
|
|
1944
|
-
__publicField$1(this, "globalAfterJobs", /* @__PURE__ */ new Map());
|
|
1945
|
-
__publicField$1(this, "nextGlobalIndex", 0);
|
|
1946
|
-
__publicField$1(this, "idleCallbacks", /* @__PURE__ */ new Set());
|
|
1947
|
-
__publicField$1(this, "nextJobIndex", 0);
|
|
1948
|
-
__publicField$1(this, "jobStateListeners", /* @__PURE__ */ new Map());
|
|
1949
|
-
__publicField$1(this, "pendingFrames", 0);
|
|
1950
|
-
__publicField$1(this, "_frameloop", "always");
|
|
1951
|
-
//* Independent Mode & Error Handling State ================================
|
|
1952
|
-
__publicField$1(this, "_independent", false);
|
|
1953
|
-
__publicField$1(this, "errorHandler", null);
|
|
1954
|
-
__publicField$1(this, "rootReadyCallbacks", /* @__PURE__ */ new Set());
|
|
1955
|
-
//* Core Loop Execution Methods ================================
|
|
1956
|
-
/**
|
|
1957
|
-
* Main RAF loop callback.
|
|
1958
|
-
* Executes frame, handles demand mode, and schedules next frame.
|
|
1959
|
-
* @param {number} timestamp - RAF timestamp in milliseconds
|
|
1960
|
-
* @returns {void}
|
|
1961
|
-
* @private
|
|
1962
|
-
*/
|
|
1963
|
-
__publicField$1(this, "loop", (timestamp) => {
|
|
1964
|
-
if (!this.loopState.running) return;
|
|
1965
|
-
this.executeFrame(timestamp);
|
|
1966
|
-
if (this._frameloop === "demand") {
|
|
1967
|
-
this.pendingFrames = Math.max(0, this.pendingFrames - 1);
|
|
1968
|
-
if (this.pendingFrames === 0) {
|
|
1969
|
-
this.notifyIdle(timestamp);
|
|
1970
|
-
return this.stop();
|
|
1971
|
-
}
|
|
1972
|
-
}
|
|
1973
|
-
this.loopState.rafHandle = requestAnimationFrame(this.loop);
|
|
1974
|
-
});
|
|
1975
|
-
this.phaseGraph = new PhaseGraph();
|
|
1976
|
-
}
|
|
1977
|
-
static get instance() {
|
|
1978
|
-
return globalThis[_Scheduler.INSTANCE_KEY] ?? null;
|
|
1979
|
-
}
|
|
1980
|
-
static set instance(value) {
|
|
1981
|
-
globalThis[_Scheduler.INSTANCE_KEY] = value;
|
|
1982
|
-
}
|
|
1983
|
-
/**
|
|
1984
|
-
* Get the global scheduler instance (creates if doesn't exist).
|
|
1985
|
-
* Uses HMR data to preserve instance across hot reloads.
|
|
1986
|
-
* @returns {Scheduler} The singleton scheduler instance
|
|
1987
|
-
*/
|
|
1988
|
-
static get() {
|
|
1989
|
-
if (!_Scheduler.instance && hmrData?.data?.scheduler) {
|
|
1990
|
-
_Scheduler.instance = hmrData.data.scheduler;
|
|
1991
|
-
}
|
|
1992
|
-
if (!_Scheduler.instance) {
|
|
1993
|
-
_Scheduler.instance = new _Scheduler();
|
|
1994
|
-
if (hmrData?.data) {
|
|
1995
|
-
hmrData.data.scheduler = _Scheduler.instance;
|
|
1996
|
-
}
|
|
1997
|
-
}
|
|
1998
|
-
return _Scheduler.instance;
|
|
1999
|
-
}
|
|
2000
|
-
/**
|
|
2001
|
-
* Reset the singleton instance. Stops the loop and clears all state.
|
|
2002
|
-
* Primarily used for testing to ensure clean state between tests.
|
|
2003
|
-
* @returns {void}
|
|
2004
|
-
*/
|
|
2005
|
-
static reset() {
|
|
2006
|
-
if (_Scheduler.instance) {
|
|
2007
|
-
_Scheduler.instance.stop();
|
|
2008
|
-
_Scheduler.instance = null;
|
|
2009
|
-
}
|
|
2010
|
-
if (hmrData?.data) {
|
|
2011
|
-
hmrData.data.scheduler = null;
|
|
2012
|
-
}
|
|
2013
|
-
}
|
|
2014
|
-
//* Getters & Setters ================================
|
|
2015
|
-
get phases() {
|
|
2016
|
-
return this.phaseGraph.getOrderedPhases();
|
|
2017
|
-
}
|
|
2018
|
-
get frameloop() {
|
|
2019
|
-
return this._frameloop;
|
|
2020
|
-
}
|
|
2021
|
-
set frameloop(mode) {
|
|
2022
|
-
if (this._frameloop === mode) return;
|
|
2023
|
-
const wasAlways = this._frameloop === "always";
|
|
2024
|
-
this._frameloop = mode;
|
|
2025
|
-
if (mode === "always" && !this.loopState.running && this.roots.size > 0) this.start();
|
|
2026
|
-
else if (mode !== "always" && wasAlways) this.stop();
|
|
2027
|
-
}
|
|
2028
|
-
get isRunning() {
|
|
2029
|
-
return this.loopState.running;
|
|
2030
|
-
}
|
|
2031
|
-
get isReady() {
|
|
2032
|
-
return this.roots.size > 0;
|
|
2033
|
-
}
|
|
2034
|
-
get independent() {
|
|
2035
|
-
return this._independent;
|
|
2036
|
-
}
|
|
2037
|
-
set independent(value) {
|
|
2038
|
-
this._independent = value;
|
|
2039
|
-
if (value) this.ensureDefaultRoot();
|
|
2040
|
-
}
|
|
2041
|
-
//* Root Management Methods ================================
|
|
2042
|
-
/**
|
|
2043
|
-
* Register a root (Canvas) with the scheduler.
|
|
2044
|
-
* The first root to register starts the RAF loop (if frameloop='always').
|
|
2045
|
-
* @param {string} id - Unique identifier for this root
|
|
2046
|
-
* @param {RootOptions} [options] - Optional configuration with getState and onError callbacks
|
|
2047
|
-
* @returns {() => void} Unsubscribe function to remove this root
|
|
2048
|
-
*/
|
|
2049
|
-
registerRoot(id, options = {}) {
|
|
2050
|
-
if (this.roots.has(id)) {
|
|
2051
|
-
console.warn(`[Scheduler] Root "${id}" already registered`);
|
|
2052
|
-
return () => this.unregisterRoot(id);
|
|
2053
|
-
}
|
|
2054
|
-
const entry = {
|
|
2055
|
-
id,
|
|
2056
|
-
getState: options.getState ?? (() => ({})),
|
|
2057
|
-
jobs: /* @__PURE__ */ new Map(),
|
|
2058
|
-
sortedJobs: [],
|
|
2059
|
-
needsRebuild: false
|
|
2060
|
-
};
|
|
2061
|
-
if (options.onError) {
|
|
2062
|
-
this.errorHandler = options.onError;
|
|
2063
|
-
}
|
|
2064
|
-
this.roots.set(id, entry);
|
|
2065
|
-
if (this.roots.size === 1) {
|
|
2066
|
-
this.notifyRootReady();
|
|
2067
|
-
if (this._frameloop === "always") this.start();
|
|
2068
|
-
}
|
|
2069
|
-
return () => this.unregisterRoot(id);
|
|
2070
|
-
}
|
|
2071
|
-
/**
|
|
2072
|
-
* Unregister a root from the scheduler.
|
|
2073
|
-
* Cleans up all job state listeners for this root's jobs.
|
|
2074
|
-
* The last root to unregister stops the RAF loop.
|
|
2075
|
-
* @param {string} id - The root ID to unregister
|
|
2076
|
-
* @returns {void}
|
|
2077
|
-
*/
|
|
2078
|
-
unregisterRoot(id) {
|
|
2079
|
-
const root = this.roots.get(id);
|
|
2080
|
-
if (!root) return;
|
|
2081
|
-
for (const jobId of root.jobs.keys()) {
|
|
2082
|
-
this.jobStateListeners.delete(jobId);
|
|
2083
|
-
}
|
|
2084
|
-
this.roots.delete(id);
|
|
2085
|
-
if (this.roots.size === 0) {
|
|
2086
|
-
this.stop();
|
|
2087
|
-
this.errorHandler = null;
|
|
2088
|
-
}
|
|
2089
|
-
}
|
|
2090
|
-
/**
|
|
2091
|
-
* Subscribe to be notified when a root becomes available.
|
|
2092
|
-
* Fires immediately if a root already exists.
|
|
2093
|
-
* @param {() => void} callback - Function called when first root registers
|
|
2094
|
-
* @returns {() => void} Unsubscribe function
|
|
2095
|
-
*/
|
|
2096
|
-
onRootReady(callback) {
|
|
2097
|
-
if (this.roots.size > 0) {
|
|
2098
|
-
callback();
|
|
2099
|
-
return () => {
|
|
2100
|
-
};
|
|
2101
|
-
}
|
|
2102
|
-
this.rootReadyCallbacks.add(callback);
|
|
2103
|
-
return () => this.rootReadyCallbacks.delete(callback);
|
|
2104
|
-
}
|
|
2105
|
-
/**
|
|
2106
|
-
* Notify all registered root-ready callbacks.
|
|
2107
|
-
* Called when the first root registers.
|
|
2108
|
-
* @returns {void}
|
|
2109
|
-
* @private
|
|
2110
|
-
*/
|
|
2111
|
-
notifyRootReady() {
|
|
2112
|
-
for (const cb of this.rootReadyCallbacks) {
|
|
2113
|
-
try {
|
|
2114
|
-
cb();
|
|
2115
|
-
} catch (error) {
|
|
2116
|
-
console.error("[Scheduler] Error in root-ready callback:", error);
|
|
2117
|
-
}
|
|
2118
|
-
}
|
|
2119
|
-
this.rootReadyCallbacks.clear();
|
|
2120
|
-
}
|
|
2121
|
-
/**
|
|
2122
|
-
* Ensure a default root exists for independent mode.
|
|
2123
|
-
* Creates a minimal root with no state provider.
|
|
2124
|
-
* @returns {void}
|
|
2125
|
-
* @private
|
|
2126
|
-
*/
|
|
2127
|
-
ensureDefaultRoot() {
|
|
2128
|
-
if (!this.roots.has("__default__")) {
|
|
2129
|
-
this.registerRoot("__default__");
|
|
2130
|
-
}
|
|
2131
|
-
}
|
|
2132
|
-
/**
|
|
2133
|
-
* Trigger error handling for job errors.
|
|
2134
|
-
* Uses the bound error handler if available, otherwise logs to console.
|
|
2135
|
-
* @param {Error} error - The error to handle
|
|
2136
|
-
* @returns {void}
|
|
2137
|
-
*/
|
|
2138
|
-
triggerError(error) {
|
|
2139
|
-
if (this.errorHandler) this.errorHandler(error);
|
|
2140
|
-
else console.error("[Scheduler]", error);
|
|
2141
|
-
}
|
|
2142
|
-
//* Phase Management Methods ================================
|
|
2143
|
-
/**
|
|
2144
|
-
* Add a named phase to the scheduler's execution order.
|
|
2145
|
-
* Marks all roots for rebuild to incorporate the new phase.
|
|
2146
|
-
* @param {string} name - The phase name (e.g., 'physics', 'postprocess')
|
|
2147
|
-
* @param {AddPhaseOptions} [options] - Positioning options (before/after other phases)
|
|
2148
|
-
* @returns {void}
|
|
2149
|
-
* @example
|
|
2150
|
-
* scheduler.addPhase('physics', { before: 'update' });
|
|
2151
|
-
* scheduler.addPhase('postprocess', { after: 'render' });
|
|
2152
|
-
*/
|
|
2153
|
-
addPhase(name, options) {
|
|
2154
|
-
this.phaseGraph.addPhase(name, options);
|
|
2155
|
-
for (const root of this.roots.values()) {
|
|
2156
|
-
root.needsRebuild = true;
|
|
2157
|
-
}
|
|
2158
|
-
}
|
|
2159
|
-
/**
|
|
2160
|
-
* Check if a phase exists in the scheduler.
|
|
2161
|
-
* @param {string} name - The phase name to check
|
|
2162
|
-
* @returns {boolean} True if the phase exists
|
|
2163
|
-
*/
|
|
2164
|
-
hasPhase(name) {
|
|
2165
|
-
return this.phaseGraph.hasPhase(name);
|
|
2166
|
-
}
|
|
2167
|
-
//* Global Job Registration Methods (Deprecated APIs) ================================
|
|
2168
|
-
/**
|
|
2169
|
-
* Register a global job that runs once per frame (not per-root).
|
|
2170
|
-
* Used internally by deprecated addEffect/addAfterEffect APIs.
|
|
2171
|
-
* @param {'before' | 'after'} phase - When to run: 'before' all roots or 'after' all roots
|
|
2172
|
-
* @param {string} id - Unique identifier for this global job
|
|
2173
|
-
* @param {(timestamp: number) => void} callback - Function called each frame with RAF timestamp
|
|
2174
|
-
* @returns {() => void} Unsubscribe function to remove this global job
|
|
2175
|
-
* @deprecated Use useFrame with phases instead
|
|
2176
|
-
*/
|
|
2177
|
-
registerGlobal(phase, id, callback) {
|
|
2178
|
-
const job = { id, callback };
|
|
2179
|
-
if (phase === "before") {
|
|
2180
|
-
this.globalBeforeJobs.set(id, job);
|
|
2181
|
-
} else {
|
|
2182
|
-
this.globalAfterJobs.set(id, job);
|
|
2183
|
-
}
|
|
2184
|
-
return () => {
|
|
2185
|
-
if (phase === "before") this.globalBeforeJobs.delete(id);
|
|
2186
|
-
else this.globalAfterJobs.delete(id);
|
|
2187
|
-
};
|
|
2188
|
-
}
|
|
2189
|
-
//* Idle Callback Methods (Deprecated API) ================================
|
|
2190
|
-
/**
|
|
2191
|
-
* Register an idle callback that fires when the loop stops.
|
|
2192
|
-
* Used internally by deprecated addTail API.
|
|
2193
|
-
* @param {(timestamp: number) => void} callback - Function called when loop becomes idle
|
|
2194
|
-
* @returns {() => void} Unsubscribe function to remove this idle callback
|
|
2195
|
-
* @deprecated Use demand mode with invalidate() instead
|
|
2196
|
-
*/
|
|
2197
|
-
onIdle(callback) {
|
|
2198
|
-
this.idleCallbacks.add(callback);
|
|
2199
|
-
return () => this.idleCallbacks.delete(callback);
|
|
2200
|
-
}
|
|
2201
|
-
/**
|
|
2202
|
-
* Notify all registered idle callbacks.
|
|
2203
|
-
* Called when the loop stops in demand mode.
|
|
2204
|
-
* @param {number} timestamp - The RAF timestamp when idle occurred
|
|
2205
|
-
* @returns {void}
|
|
2206
|
-
* @private
|
|
2207
|
-
*/
|
|
2208
|
-
notifyIdle(timestamp) {
|
|
2209
|
-
for (const cb of this.idleCallbacks) {
|
|
2210
|
-
try {
|
|
2211
|
-
cb(timestamp);
|
|
2212
|
-
} catch (error) {
|
|
2213
|
-
console.error("[Scheduler] Error in idle callback:", error);
|
|
2214
|
-
}
|
|
2215
|
-
}
|
|
2216
|
-
}
|
|
2217
|
-
//* Job Registration & Management Methods ================================
|
|
2218
|
-
/**
|
|
2219
|
-
* Register a job (frame callback) with a specific root.
|
|
2220
|
-
* This is the core registration method used by useFrame internally.
|
|
2221
|
-
* @param {FrameNextCallback} callback - The function to call each frame
|
|
2222
|
-
* @param {JobOptions & { rootId?: string; system?: boolean }} [options] - Job configuration
|
|
2223
|
-
* @param {string} [options.rootId] - Target root ID (defaults to first registered root)
|
|
2224
|
-
* @param {string} [options.id] - Unique job ID (auto-generated if not provided)
|
|
2225
|
-
* @param {string} [options.phase] - Execution phase (defaults to 'update')
|
|
2226
|
-
* @param {number} [options.priority] - Priority within phase (higher = earlier, default 0)
|
|
2227
|
-
* @param {number} [options.fps] - FPS throttle limit
|
|
2228
|
-
* @param {boolean} [options.drop] - Drop frames when behind (default true)
|
|
2229
|
-
* @param {boolean} [options.enabled] - Whether job is active (default true)
|
|
2230
|
-
* @param {boolean} [options.system] - Internal flag for system jobs (not user-facing)
|
|
2231
|
-
* @returns {() => void} Unsubscribe function to remove this job
|
|
2232
|
-
*/
|
|
2233
|
-
register(callback, options = {}) {
|
|
2234
|
-
const rootId = options.rootId;
|
|
2235
|
-
const root = rootId ? this.roots.get(rootId) : this.roots.values().next().value;
|
|
2236
|
-
if (!root) {
|
|
2237
|
-
console.warn("[Scheduler] No root registered. Is this inside a Canvas?");
|
|
2238
|
-
return () => {
|
|
2239
|
-
};
|
|
2240
|
-
}
|
|
2241
|
-
const id = options.id ?? this.generateJobId();
|
|
2242
|
-
let phase = options.phase ?? "update";
|
|
2243
|
-
if (!options.phase && (options.before || options.after)) {
|
|
2244
|
-
phase = this.phaseGraph.resolveConstraintPhase(options.before, options.after);
|
|
2245
|
-
}
|
|
2246
|
-
const before = this.normalizeConstraints(options.before);
|
|
2247
|
-
const after = this.normalizeConstraints(options.after);
|
|
2248
|
-
const job = {
|
|
2249
|
-
id,
|
|
2250
|
-
callback,
|
|
2251
|
-
phase,
|
|
2252
|
-
before,
|
|
2253
|
-
after,
|
|
2254
|
-
priority: options.priority ?? 0,
|
|
2255
|
-
index: this.nextJobIndex++,
|
|
2256
|
-
fps: options.fps,
|
|
2257
|
-
drop: options.drop ?? true,
|
|
2258
|
-
enabled: options.enabled ?? true,
|
|
2259
|
-
system: options.system ?? false
|
|
2260
|
-
};
|
|
2261
|
-
if (root.jobs.has(id)) {
|
|
2262
|
-
console.warn(`[useFrame] Job with id "${id}" already exists, replacing`);
|
|
2263
|
-
}
|
|
2264
|
-
root.jobs.set(id, job);
|
|
2265
|
-
root.needsRebuild = true;
|
|
2266
|
-
return () => this.unregister(id, root.id);
|
|
2267
|
-
}
|
|
2268
|
-
/**
|
|
2269
|
-
* Unregister a job by its ID.
|
|
2270
|
-
* Searches all roots if rootId is not provided.
|
|
2271
|
-
* @param {string} id - The job ID to unregister
|
|
2272
|
-
* @param {string} [rootId] - Optional root ID to search (searches all if not provided)
|
|
2273
|
-
* @returns {void}
|
|
2274
|
-
*/
|
|
2275
|
-
unregister(id, rootId) {
|
|
2276
|
-
const root = rootId ? this.roots.get(rootId) : Array.from(this.roots.values()).find((r) => r.jobs.has(id));
|
|
2277
|
-
if (root?.jobs.delete(id)) {
|
|
2278
|
-
root.needsRebuild = true;
|
|
2279
|
-
this.jobStateListeners.delete(id);
|
|
2280
|
-
}
|
|
2281
|
-
}
|
|
2282
|
-
/**
|
|
2283
|
-
* Update a job's options dynamically.
|
|
2284
|
-
* Searches all roots to find the job by ID.
|
|
2285
|
-
* Phase/constraint changes trigger a rebuild of the sorted job list.
|
|
2286
|
-
* @param {string} id - The job ID to update
|
|
2287
|
-
* @param {Partial<JobOptions>} options - The options to update
|
|
2288
|
-
* @returns {void}
|
|
2289
|
-
*/
|
|
2290
|
-
updateJob(id, options) {
|
|
2291
|
-
let job;
|
|
2292
|
-
let root;
|
|
2293
|
-
for (const r of this.roots.values()) {
|
|
2294
|
-
job = r.jobs.get(id);
|
|
2295
|
-
if (job) {
|
|
2296
|
-
root = r;
|
|
2297
|
-
break;
|
|
2298
|
-
}
|
|
2299
|
-
}
|
|
2300
|
-
if (!job || !root) return;
|
|
2301
|
-
if (options.priority !== void 0) job.priority = options.priority;
|
|
2302
|
-
if (options.fps !== void 0) job.fps = options.fps;
|
|
2303
|
-
if (options.drop !== void 0) job.drop = options.drop;
|
|
2304
|
-
if (options.enabled !== void 0) {
|
|
2305
|
-
const wasEnabled = job.enabled;
|
|
2306
|
-
job.enabled = options.enabled;
|
|
2307
|
-
if (!wasEnabled && job.enabled) resetJobTiming(job);
|
|
2308
|
-
if (wasEnabled !== job.enabled) root.needsRebuild = true;
|
|
2309
|
-
}
|
|
2310
|
-
if (options.phase !== void 0 || options.before !== void 0 || options.after !== void 0) {
|
|
2311
|
-
if (options.phase) job.phase = options.phase;
|
|
2312
|
-
if (options.before !== void 0) job.before = this.normalizeConstraints(options.before);
|
|
2313
|
-
if (options.after !== void 0) job.after = this.normalizeConstraints(options.after);
|
|
2314
|
-
root.needsRebuild = true;
|
|
2315
|
-
}
|
|
2316
|
-
}
|
|
2317
|
-
//* Job State Management Methods ================================
|
|
2318
|
-
/**
|
|
2319
|
-
* Check if a job is currently paused (disabled).
|
|
2320
|
-
* @param {string} id - The job ID to check
|
|
2321
|
-
* @returns {boolean} True if the job exists and is paused
|
|
2322
|
-
*/
|
|
2323
|
-
isJobPaused(id) {
|
|
2324
|
-
for (const root of this.roots.values()) {
|
|
2325
|
-
const job = root.jobs.get(id);
|
|
2326
|
-
if (job) return !job.enabled;
|
|
2327
|
-
}
|
|
2328
|
-
return false;
|
|
2329
|
-
}
|
|
2330
|
-
/**
|
|
2331
|
-
* Subscribe to state changes for a specific job.
|
|
2332
|
-
* Listener is called when job is paused or resumed.
|
|
2333
|
-
* @param {string} id - The job ID to subscribe to
|
|
2334
|
-
* @param {() => void} listener - Callback invoked on state changes
|
|
2335
|
-
* @returns {() => void} Unsubscribe function
|
|
2336
|
-
*/
|
|
2337
|
-
subscribeJobState(id, listener) {
|
|
2338
|
-
if (!this.jobStateListeners.has(id)) {
|
|
2339
|
-
this.jobStateListeners.set(id, /* @__PURE__ */ new Set());
|
|
2340
|
-
}
|
|
2341
|
-
this.jobStateListeners.get(id).add(listener);
|
|
2342
|
-
return () => {
|
|
2343
|
-
this.jobStateListeners.get(id)?.delete(listener);
|
|
2344
|
-
if (this.jobStateListeners.get(id)?.size === 0) {
|
|
2345
|
-
this.jobStateListeners.delete(id);
|
|
2346
|
-
}
|
|
2347
|
-
};
|
|
2348
|
-
}
|
|
2349
|
-
/**
|
|
2350
|
-
* Notify all listeners that a job's state has changed.
|
|
2351
|
-
* @param {string} id - The job ID that changed
|
|
2352
|
-
* @returns {void}
|
|
2353
|
-
* @private
|
|
2354
|
-
*/
|
|
2355
|
-
notifyJobStateChange(id) {
|
|
2356
|
-
this.jobStateListeners.get(id)?.forEach((listener) => listener());
|
|
2357
|
-
}
|
|
2358
|
-
/**
|
|
2359
|
-
* Pause a job by ID (sets enabled=false).
|
|
2360
|
-
* Notifies any subscribed state listeners.
|
|
2361
|
-
* @param {string} id - The job ID to pause
|
|
2362
|
-
* @returns {void}
|
|
2363
|
-
*/
|
|
2364
|
-
pauseJob(id) {
|
|
2365
|
-
this.updateJob(id, { enabled: false });
|
|
2366
|
-
this.notifyJobStateChange(id);
|
|
2367
|
-
}
|
|
2368
|
-
/**
|
|
2369
|
-
* Resume a paused job by ID (sets enabled=true).
|
|
2370
|
-
* Resets job timing to prevent frame accumulation.
|
|
2371
|
-
* Notifies any subscribed state listeners.
|
|
2372
|
-
* @param {string} id - The job ID to resume
|
|
2373
|
-
* @returns {void}
|
|
2374
|
-
*/
|
|
2375
|
-
resumeJob(id) {
|
|
2376
|
-
this.updateJob(id, { enabled: true });
|
|
2377
|
-
this.notifyJobStateChange(id);
|
|
2378
|
-
}
|
|
2379
|
-
//* Frame Loop Control Methods ================================
|
|
2380
|
-
/**
|
|
2381
|
-
* Start the requestAnimationFrame loop.
|
|
2382
|
-
* Resets timing state (elapsedTime, frameCount) on start.
|
|
2383
|
-
* No-op if already running.
|
|
2384
|
-
* @returns {void}
|
|
2385
|
-
*/
|
|
2386
|
-
start() {
|
|
2387
|
-
if (this.loopState.running) return;
|
|
2388
|
-
const { elapsedTime, createdAt } = this.loopState;
|
|
2389
|
-
let adjustedCreated = 0;
|
|
2390
|
-
if (this.stoppedTime > 0) {
|
|
2391
|
-
adjustedCreated = createdAt - (performance.now() - this.stoppedTime);
|
|
2392
|
-
this.stoppedTime = 0;
|
|
2393
|
-
}
|
|
2394
|
-
Object.assign(this.loopState, {
|
|
2395
|
-
running: true,
|
|
2396
|
-
elapsedTime: elapsedTime ?? 0,
|
|
2397
|
-
lastTime: performance.now(),
|
|
2398
|
-
createdAt: adjustedCreated > 0 ? adjustedCreated : performance.now(),
|
|
2399
|
-
frameCount: 0,
|
|
2400
|
-
rafHandle: requestAnimationFrame(this.loop)
|
|
2401
|
-
});
|
|
2402
|
-
}
|
|
2403
|
-
/**
|
|
2404
|
-
* Stop the requestAnimationFrame loop.
|
|
2405
|
-
* Cancels any pending RAF callback.
|
|
2406
|
-
* No-op if not running.
|
|
2407
|
-
* @returns {void}
|
|
2408
|
-
*/
|
|
2409
|
-
stop() {
|
|
2410
|
-
if (!this.loopState.running) return;
|
|
2411
|
-
this.loopState.running = false;
|
|
2412
|
-
if (this.loopState.rafHandle !== null) {
|
|
2413
|
-
cancelAnimationFrame(this.loopState.rafHandle);
|
|
2414
|
-
this.loopState.rafHandle = null;
|
|
2415
|
-
}
|
|
2416
|
-
this.stoppedTime = performance.now();
|
|
2417
|
-
}
|
|
2418
|
-
/**
|
|
2419
|
-
* Request frames to be rendered in demand mode.
|
|
2420
|
-
* Accumulates pending frames (capped at 60) and starts the loop if not running.
|
|
2421
|
-
* No-op if frameloop is not 'demand'.
|
|
2422
|
-
* @param {number} [frames=1] - Number of frames to request
|
|
2423
|
-
* @param {boolean} [stackFrames=false] - Whether to add frames to existing pending count
|
|
2424
|
-
* - `false` (default): Sets pending frames to the specified value (replaces existing count)
|
|
2425
|
-
* - `true`: Adds frames to existing pending count (useful for accumulating invalidations)
|
|
2426
|
-
* @returns {void}
|
|
2427
|
-
* @example
|
|
2428
|
-
* // Request a single frame render
|
|
2429
|
-
* scheduler.invalidate();
|
|
2430
|
-
*
|
|
2431
|
-
* @example
|
|
2432
|
-
* // Request 5 frames (e.g., for animations)
|
|
2433
|
-
* scheduler.invalidate(5);
|
|
2434
|
-
*
|
|
2435
|
-
* @example
|
|
2436
|
-
* // Set pending frames to exactly 3 (don't stack with existing)
|
|
2437
|
-
* scheduler.invalidate(3, false);
|
|
2438
|
-
*
|
|
2439
|
-
* @example
|
|
2440
|
-
* // Add 2 more frames to existing pending count
|
|
2441
|
-
* scheduler.invalidate(2, true);
|
|
2442
|
-
*/
|
|
2443
|
-
invalidate(frames = 1, stackFrames = false) {
|
|
2444
|
-
if (this._frameloop !== "demand") return;
|
|
2445
|
-
const baseFrames = stackFrames ? this.pendingFrames : 0;
|
|
2446
|
-
this.pendingFrames = Math.min(60, baseFrames + frames);
|
|
2447
|
-
if (!this.loopState.running && this.pendingFrames > 0) this.start();
|
|
2448
|
-
}
|
|
2449
|
-
/**
|
|
2450
|
-
* Reset timing state for deterministic testing.
|
|
2451
|
-
* Preserves jobs and roots but resets lastTime, frameCount, elapsedTime, etc.
|
|
2452
|
-
* @returns {void}
|
|
2453
|
-
*/
|
|
2454
|
-
resetTiming() {
|
|
2455
|
-
this.loopState.lastTime = null;
|
|
2456
|
-
this.loopState.frameCount = 0;
|
|
2457
|
-
this.loopState.elapsedTime = 0;
|
|
2458
|
-
this.loopState.createdAt = performance.now();
|
|
2459
|
-
}
|
|
2460
|
-
//* Manual Stepping Methods ================================
|
|
2461
|
-
/**
|
|
2462
|
-
* Manually execute a single frame for all roots.
|
|
2463
|
-
* Useful for frameloop='never' mode or testing scenarios.
|
|
2464
|
-
* @param {number} [timestamp] - Optional timestamp (defaults to performance.now())
|
|
2465
|
-
* @returns {void}
|
|
2466
|
-
* @example
|
|
2467
|
-
* // Manual control mode
|
|
2468
|
-
* scheduler.frameloop = 'never';
|
|
2469
|
-
* scheduler.step(); // Execute one frame
|
|
2470
|
-
*/
|
|
2471
|
-
step(timestamp) {
|
|
2472
|
-
const now = timestamp ?? performance.now();
|
|
2473
|
-
this.executeFrame(now);
|
|
2474
|
-
}
|
|
2475
|
-
/**
|
|
2476
|
-
* Manually execute a single job by its ID.
|
|
2477
|
-
* Useful for testing individual job callbacks in isolation.
|
|
2478
|
-
* @param {string} id - The job ID to step
|
|
2479
|
-
* @param {number} [timestamp] - Optional timestamp (defaults to performance.now())
|
|
2480
|
-
* @returns {void}
|
|
2481
|
-
*/
|
|
2482
|
-
stepJob(id, timestamp) {
|
|
2483
|
-
let job;
|
|
2484
|
-
let root;
|
|
2485
|
-
for (const r of this.roots.values()) {
|
|
2486
|
-
job = r.jobs.get(id);
|
|
2487
|
-
if (job) {
|
|
2488
|
-
root = r;
|
|
2489
|
-
break;
|
|
2490
|
-
}
|
|
2491
|
-
}
|
|
2492
|
-
if (!job || !root) {
|
|
2493
|
-
console.warn(`[Scheduler] Job "${id}" not found`);
|
|
2494
|
-
return;
|
|
2495
|
-
}
|
|
2496
|
-
const now = timestamp ?? performance.now();
|
|
2497
|
-
const deltaMs = this.loopState.lastTime !== null ? now - this.loopState.lastTime : 0;
|
|
2498
|
-
const delta = deltaMs / 1e3;
|
|
2499
|
-
const elapsed = now - this.loopState.createdAt;
|
|
2500
|
-
const providedState = root.getState?.() ?? {};
|
|
2501
|
-
const frameState = {
|
|
2502
|
-
...providedState,
|
|
2503
|
-
time: now,
|
|
2504
|
-
delta,
|
|
2505
|
-
elapsed,
|
|
2506
|
-
frame: this.loopState.frameCount
|
|
2507
|
-
};
|
|
2508
|
-
try {
|
|
2509
|
-
job.callback(frameState, delta);
|
|
2510
|
-
} catch (error) {
|
|
2511
|
-
console.error(`[Scheduler] Error in job "${job.id}":`, error);
|
|
2512
|
-
this.triggerError(error instanceof Error ? error : new Error(String(error)));
|
|
2513
|
-
}
|
|
2514
|
-
}
|
|
2515
|
-
/**
|
|
2516
|
-
* Execute a single frame across all roots.
|
|
2517
|
-
* Order: globalBefore → each root's jobs → globalAfter
|
|
2518
|
-
* @param {number} timestamp - RAF timestamp in milliseconds
|
|
2519
|
-
* @returns {void}
|
|
2520
|
-
* @private
|
|
2521
|
-
*/
|
|
2522
|
-
executeFrame(timestamp) {
|
|
2523
|
-
const deltaMs = this.loopState.lastTime !== null ? timestamp - this.loopState.lastTime : 0;
|
|
2524
|
-
const delta = deltaMs / 1e3;
|
|
2525
|
-
this.loopState.lastTime = timestamp;
|
|
2526
|
-
this.loopState.frameCount++;
|
|
2527
|
-
this.loopState.elapsedTime += deltaMs;
|
|
2528
|
-
this.runGlobalJobs(this.globalBeforeJobs, timestamp);
|
|
2529
|
-
for (const root of this.roots.values()) {
|
|
2530
|
-
this.tickRoot(root, timestamp, delta);
|
|
2531
|
-
}
|
|
2532
|
-
this.runGlobalJobs(this.globalAfterJobs, timestamp);
|
|
2533
|
-
}
|
|
2534
|
-
/**
|
|
2535
|
-
* Run all global jobs from a job map.
|
|
2536
|
-
* Catches and logs errors without stopping execution.
|
|
2537
|
-
* @param {Map<string, GlobalJob>} jobs - The global jobs map to execute
|
|
2538
|
-
* @param {number} timestamp - RAF timestamp in milliseconds
|
|
2539
|
-
* @returns {void}
|
|
2540
|
-
* @private
|
|
2541
|
-
*/
|
|
2542
|
-
runGlobalJobs(jobs, timestamp) {
|
|
2543
|
-
for (const job of jobs.values()) {
|
|
2544
|
-
try {
|
|
2545
|
-
job.callback(timestamp);
|
|
2546
|
-
} catch (error) {
|
|
2547
|
-
console.error(`[Scheduler] Error in global job "${job.id}":`, error);
|
|
2548
|
-
}
|
|
2549
|
-
}
|
|
2550
|
-
}
|
|
2551
|
-
/**
|
|
2552
|
-
* Execute all jobs for a single root in sorted order.
|
|
2553
|
-
* Rebuilds sorted job list if needed, then dispatches each job.
|
|
2554
|
-
* Errors are caught and propagated via triggerError.
|
|
2555
|
-
* @param {RootEntry} root - The root entry to tick
|
|
2556
|
-
* @param {number} timestamp - RAF timestamp in milliseconds
|
|
2557
|
-
* @param {number} delta - Time since last frame in seconds
|
|
2558
|
-
* @returns {void}
|
|
2559
|
-
* @private
|
|
2560
|
-
*/
|
|
2561
|
-
tickRoot(root, timestamp, delta) {
|
|
2562
|
-
if (root.needsRebuild) {
|
|
2563
|
-
root.sortedJobs = rebuildSortedJobs(root.jobs, this.phaseGraph);
|
|
2564
|
-
root.needsRebuild = false;
|
|
2565
|
-
}
|
|
2566
|
-
const providedState = root.getState?.() ?? {};
|
|
2567
|
-
const frameState = {
|
|
2568
|
-
...providedState,
|
|
2569
|
-
time: timestamp,
|
|
2570
|
-
delta,
|
|
2571
|
-
elapsed: this.loopState.elapsedTime / 1e3,
|
|
2572
|
-
// Convert ms to seconds
|
|
2573
|
-
frame: this.loopState.frameCount
|
|
2574
|
-
};
|
|
2575
|
-
for (const job of root.sortedJobs) {
|
|
2576
|
-
if (!shouldRun(job, timestamp)) continue;
|
|
2577
|
-
try {
|
|
2578
|
-
job.callback(frameState, delta);
|
|
2579
|
-
} catch (error) {
|
|
2580
|
-
console.error(`[Scheduler] Error in job "${job.id}":`, error);
|
|
2581
|
-
this.triggerError(error instanceof Error ? error : new Error(String(error)));
|
|
2582
|
-
}
|
|
2583
|
-
}
|
|
2584
|
-
}
|
|
2585
|
-
//* Debug & Inspection Methods ================================
|
|
2586
|
-
/**
|
|
2587
|
-
* Get the total number of registered jobs across all roots.
|
|
2588
|
-
* Includes both per-root jobs and global before/after jobs.
|
|
2589
|
-
* @returns {number} Total job count
|
|
2590
|
-
*/
|
|
2591
|
-
getJobCount() {
|
|
2592
|
-
let count = 0;
|
|
2593
|
-
for (const root of this.roots.values()) {
|
|
2594
|
-
count += root.jobs.size;
|
|
2595
|
-
}
|
|
2596
|
-
return count + this.globalBeforeJobs.size + this.globalAfterJobs.size;
|
|
2597
|
-
}
|
|
2598
|
-
/**
|
|
2599
|
-
* Get all registered job IDs across all roots.
|
|
2600
|
-
* Includes both per-root jobs and global before/after jobs.
|
|
2601
|
-
* @returns {string[]} Array of all job IDs
|
|
2602
|
-
*/
|
|
2603
|
-
getJobIds() {
|
|
2604
|
-
const ids = [];
|
|
2605
|
-
for (const root of this.roots.values()) {
|
|
2606
|
-
ids.push(...root.jobs.keys());
|
|
2607
|
-
}
|
|
2608
|
-
ids.push(...this.globalBeforeJobs.keys());
|
|
2609
|
-
ids.push(...this.globalAfterJobs.keys());
|
|
2610
|
-
return ids;
|
|
2611
|
-
}
|
|
2612
|
-
/**
|
|
2613
|
-
* Get the number of registered roots (Canvas instances).
|
|
2614
|
-
* @returns {number} Number of registered roots
|
|
2615
|
-
*/
|
|
2616
|
-
getRootCount() {
|
|
2617
|
-
return this.roots.size;
|
|
2618
|
-
}
|
|
2619
|
-
/**
|
|
2620
|
-
* Check if any user (non-system) jobs are registered in a specific phase.
|
|
2621
|
-
* Used by the default render job to know if a user has taken over rendering.
|
|
2622
|
-
*
|
|
2623
|
-
* @param phase The phase to check
|
|
2624
|
-
* @param rootId Optional root ID to check (checks all roots if not provided)
|
|
2625
|
-
* @returns true if any user jobs exist in the phase
|
|
2626
|
-
*/
|
|
2627
|
-
hasUserJobsInPhase(phase, rootId) {
|
|
2628
|
-
const rootsToCheck = rootId ? [this.roots.get(rootId)].filter(Boolean) : Array.from(this.roots.values());
|
|
2629
|
-
return rootsToCheck.some((root) => {
|
|
2630
|
-
if (!root) return false;
|
|
2631
|
-
for (const job of root.jobs.values()) {
|
|
2632
|
-
if (job.phase === phase && !job.system && job.enabled) return true;
|
|
2633
|
-
}
|
|
2634
|
-
return false;
|
|
2635
|
-
});
|
|
2636
|
-
}
|
|
2637
|
-
//* Utility Methods ================================
|
|
2638
|
-
/**
|
|
2639
|
-
* Generate a unique root ID for automatic root registration.
|
|
2640
|
-
* @returns {string} A unique root ID in the format 'root_N'
|
|
2641
|
-
*/
|
|
2642
|
-
generateRootId() {
|
|
2643
|
-
return `root_${this.nextRootIndex++}`;
|
|
2644
|
-
}
|
|
2645
|
-
/**
|
|
2646
|
-
* Generate a unique job ID.
|
|
2647
|
-
* @returns {string} A unique job ID in the format 'job_N'
|
|
2648
|
-
* @private
|
|
2649
|
-
*/
|
|
2650
|
-
generateJobId() {
|
|
2651
|
-
return `job_${this.nextJobIndex}`;
|
|
2652
|
-
}
|
|
2653
|
-
/**
|
|
2654
|
-
* Normalize before/after constraints to a Set.
|
|
2655
|
-
* Handles undefined, single string, or array inputs.
|
|
2656
|
-
* @param {string | string[] | undefined} value - The constraint value(s)
|
|
2657
|
-
* @returns {Set<string>} Normalized Set of constraint strings
|
|
2658
|
-
* @private
|
|
2659
|
-
*/
|
|
2660
|
-
normalizeConstraints(value) {
|
|
2661
|
-
if (!value) return /* @__PURE__ */ new Set();
|
|
2662
|
-
if (Array.isArray(value)) return new Set(value);
|
|
2663
|
-
return /* @__PURE__ */ new Set([value]);
|
|
2664
|
-
}
|
|
2665
|
-
};
|
|
2666
|
-
//* Static State & Methods (Singleton Usage) ================================
|
|
2667
|
-
//* Cross-Bundle Singleton Key ==============================
|
|
2668
|
-
// Use Symbol.for() to ensure scheduler is shared across bundle boundaries
|
|
2669
|
-
// This prevents issues when mixing imports from @react-three/fiber and @react-three/fiber/webgpu
|
|
2670
|
-
__publicField$1(_Scheduler, "INSTANCE_KEY", Symbol.for("@react-three/fiber.scheduler"));
|
|
2671
|
-
let Scheduler = _Scheduler;
|
|
2672
|
-
const getScheduler = () => Scheduler.get();
|
|
2673
|
-
if (hmrData) {
|
|
2674
|
-
hmrData.accept?.();
|
|
2675
|
-
}
|
|
2676
|
-
|
|
2677
|
-
const R3F_CONTEXT = Symbol.for("@react-three/fiber.context");
|
|
2678
|
-
const context = globalThis[R3F_CONTEXT] ?? (globalThis[R3F_CONTEXT] = React__namespace.createContext(null));
|
|
2679
|
-
const createStore = (invalidate, advance) => {
|
|
2680
|
-
const rootStore = traditional.createWithEqualityFn((set, get) => {
|
|
2681
|
-
const position = new webgpu.Vector3();
|
|
2682
|
-
const defaultTarget = new webgpu.Vector3();
|
|
2683
|
-
const tempTarget = new webgpu.Vector3();
|
|
2684
|
-
function getCurrentViewport(camera = get().camera, target = defaultTarget, size = get().size) {
|
|
2685
|
-
const { width, height, top, left } = size;
|
|
2686
|
-
const aspect = width / height;
|
|
2687
|
-
if (target.isVector3) tempTarget.copy(target);
|
|
2688
|
-
else tempTarget.set(...target);
|
|
2689
|
-
const distance = camera.getWorldPosition(position).distanceTo(tempTarget);
|
|
2690
|
-
if (isOrthographicCamera(camera)) {
|
|
2691
|
-
return { width: width / camera.zoom, height: height / camera.zoom, top, left, factor: 1, distance, aspect };
|
|
2692
|
-
} else {
|
|
2693
|
-
const fov = camera.fov * Math.PI / 180;
|
|
2694
|
-
const h = 2 * Math.tan(fov / 2) * distance;
|
|
2695
|
-
const w = h * (width / height);
|
|
2696
|
-
return { width: w, height: h, top, left, factor: width / w, distance, aspect };
|
|
2697
|
-
}
|
|
2698
|
-
}
|
|
2699
|
-
let performanceTimeout = void 0;
|
|
2700
|
-
const setPerformanceCurrent = (current) => set((state2) => ({ performance: { ...state2.performance, current } }));
|
|
2701
|
-
const pointer = new webgpu.Vector2();
|
|
2702
|
-
const rootState = {
|
|
2703
|
-
set,
|
|
2704
|
-
get,
|
|
2705
|
-
// Mock objects that have to be configured
|
|
2706
|
-
// primaryStore is set after store creation (self-reference for primary, primary's store for secondary)
|
|
2707
|
-
primaryStore: null,
|
|
2708
|
-
gl: null,
|
|
2709
|
-
renderer: null,
|
|
2710
|
-
camera: null,
|
|
2711
|
-
frustum: new webgpu.Frustum(),
|
|
2712
|
-
autoUpdateFrustum: true,
|
|
2713
|
-
raycaster: null,
|
|
2714
|
-
events: {
|
|
2715
|
-
priority: 1,
|
|
2716
|
-
enabled: true,
|
|
2717
|
-
connected: false,
|
|
2718
|
-
frameTimedRaycasts: true,
|
|
2719
|
-
alwaysFireOnScroll: true,
|
|
2720
|
-
updateOnFrame: false
|
|
2721
|
-
},
|
|
2722
|
-
scene: null,
|
|
2723
|
-
rootScene: null,
|
|
2724
|
-
xr: null,
|
|
2725
|
-
inspector: null,
|
|
2726
|
-
invalidate: (frames = 1, stackFrames = false) => invalidate(get(), frames, stackFrames),
|
|
2727
|
-
advance: (timestamp, runGlobalEffects) => advance(timestamp, runGlobalEffects, get()),
|
|
2728
|
-
textureColorSpace: webgpu.SRGBColorSpace,
|
|
2729
|
-
isLegacy: false,
|
|
2730
|
-
webGPUSupported: false,
|
|
2731
|
-
isNative: false,
|
|
2732
|
-
controls: null,
|
|
2733
|
-
pointer,
|
|
2734
|
-
mouse: pointer,
|
|
2735
|
-
frameloop: "always",
|
|
2736
|
-
onPointerMissed: void 0,
|
|
2737
|
-
onDragOverMissed: void 0,
|
|
2738
|
-
onDropMissed: void 0,
|
|
2739
|
-
performance: {
|
|
2740
|
-
current: 1,
|
|
2741
|
-
min: 0.5,
|
|
2742
|
-
max: 1,
|
|
2743
|
-
debounce: 200,
|
|
2744
|
-
regress: () => {
|
|
2745
|
-
const state2 = get();
|
|
2746
|
-
if (performanceTimeout) clearTimeout(performanceTimeout);
|
|
2747
|
-
if (state2.performance.current !== state2.performance.min) setPerformanceCurrent(state2.performance.min);
|
|
2748
|
-
performanceTimeout = setTimeout(
|
|
2749
|
-
() => setPerformanceCurrent(get().performance.max),
|
|
2750
|
-
state2.performance.debounce
|
|
2751
|
-
);
|
|
2752
|
-
}
|
|
2753
|
-
},
|
|
2754
|
-
size: { width: 0, height: 0, top: 0, left: 0 },
|
|
2755
|
-
viewport: {
|
|
2756
|
-
initialDpr: 0,
|
|
2757
|
-
dpr: 0,
|
|
2758
|
-
width: 0,
|
|
2759
|
-
height: 0,
|
|
2760
|
-
top: 0,
|
|
2761
|
-
left: 0,
|
|
2762
|
-
aspect: 0,
|
|
2763
|
-
distance: 0,
|
|
2764
|
-
factor: 0,
|
|
2765
|
-
getCurrentViewport
|
|
2766
|
-
},
|
|
2767
|
-
setEvents: (events) => set((state2) => ({ ...state2, events: { ...state2.events, ...events } })),
|
|
2768
|
-
setSize: (width, height, top, left) => {
|
|
2769
|
-
const state2 = get();
|
|
2770
|
-
if (width === void 0) {
|
|
2771
|
-
set({ _sizeImperative: false });
|
|
2772
|
-
if (state2._sizeProps) {
|
|
2773
|
-
const { width: propW, height: propH } = state2._sizeProps;
|
|
2774
|
-
if (propW !== void 0 || propH !== void 0) {
|
|
2775
|
-
const currentSize = state2.size;
|
|
2776
|
-
const newSize = {
|
|
2777
|
-
width: propW ?? currentSize.width,
|
|
2778
|
-
height: propH ?? currentSize.height,
|
|
2779
|
-
top: currentSize.top,
|
|
2780
|
-
left: currentSize.left
|
|
2781
|
-
};
|
|
2782
|
-
set((s) => ({
|
|
2783
|
-
size: newSize,
|
|
2784
|
-
viewport: { ...s.viewport, ...getCurrentViewport(state2.camera, defaultTarget, newSize) }
|
|
2785
|
-
}));
|
|
2786
|
-
getScheduler().invalidate();
|
|
2787
|
-
}
|
|
2788
|
-
}
|
|
2789
|
-
return;
|
|
2790
|
-
}
|
|
2791
|
-
const w = width;
|
|
2792
|
-
const h = height ?? width;
|
|
2793
|
-
const t = top ?? state2.size.top;
|
|
2794
|
-
const l = left ?? state2.size.left;
|
|
2795
|
-
const size = { width: w, height: h, top: t, left: l };
|
|
2796
|
-
set((s) => ({
|
|
2797
|
-
size,
|
|
2798
|
-
viewport: { ...s.viewport, ...getCurrentViewport(state2.camera, defaultTarget, size) },
|
|
2799
|
-
_sizeImperative: true
|
|
2800
|
-
}));
|
|
2801
|
-
getScheduler().invalidate();
|
|
2802
|
-
},
|
|
2803
|
-
setDpr: (dpr) => set((state2) => {
|
|
2804
|
-
const resolved = calculateDpr(dpr);
|
|
2805
|
-
return { viewport: { ...state2.viewport, dpr: resolved, initialDpr: state2.viewport.initialDpr || resolved } };
|
|
2806
|
-
}),
|
|
2807
|
-
setFrameloop: (frameloop = "always") => {
|
|
2808
|
-
set(() => ({ frameloop }));
|
|
2809
|
-
},
|
|
2810
|
-
setError: (error) => set(() => ({ error })),
|
|
2811
|
-
error: null,
|
|
2812
|
-
//* TSL State (managed via hooks: useUniforms, useNodes, useBuffers, useGPUStorage, useTextures, useRenderPipeline) ==============================
|
|
2813
|
-
uniforms: {},
|
|
2814
|
-
nodes: {},
|
|
2815
|
-
buffers: {},
|
|
2816
|
-
gpuStorage: {},
|
|
2817
|
-
textures: /* @__PURE__ */ new Map(),
|
|
2818
|
-
renderPipeline: null,
|
|
2819
|
-
passes: {},
|
|
2820
|
-
_hmrVersion: 0,
|
|
2821
|
-
_sizeImperative: false,
|
|
2822
|
-
_sizeProps: null,
|
|
2823
|
-
previousRoot: void 0,
|
|
2824
|
-
internal: {
|
|
2825
|
-
// Events
|
|
2826
|
-
interaction: [],
|
|
2827
|
-
subscribers: [],
|
|
2828
|
-
// Per-pointer state (new unified structure)
|
|
2829
|
-
pointerMap: /* @__PURE__ */ new Map(),
|
|
2830
|
-
pointerDirty: /* @__PURE__ */ new Map(),
|
|
2831
|
-
lastEvent: React__namespace.createRef(),
|
|
2832
|
-
// Deprecated but kept for backwards compatibility
|
|
2833
|
-
hovered: /* @__PURE__ */ new Map(),
|
|
2834
|
-
initialClick: [0, 0],
|
|
2835
|
-
initialHits: [],
|
|
2836
|
-
capturedMap: /* @__PURE__ */ new Map(),
|
|
2837
|
-
// Visibility tracking (onFramed, onOccluded, onVisible)
|
|
2838
|
-
visibilityRegistry: /* @__PURE__ */ new Map(),
|
|
2839
|
-
// Occlusion system (WebGPU only)
|
|
2840
|
-
occlusionEnabled: false,
|
|
2841
|
-
occlusionObserver: null,
|
|
2842
|
-
occlusionCache: /* @__PURE__ */ new Map(),
|
|
2843
|
-
helperGroup: null,
|
|
2844
|
-
// Updates
|
|
2845
|
-
active: false,
|
|
2846
|
-
frames: 0,
|
|
2847
|
-
priority: 0,
|
|
2848
|
-
subscribe: (ref, priority, store) => {
|
|
2849
|
-
const internal = get().internal;
|
|
2850
|
-
internal.priority = internal.priority + (priority > 0 ? 1 : 0);
|
|
2851
|
-
internal.subscribers.push({ ref, priority, store });
|
|
2852
|
-
internal.subscribers = internal.subscribers.sort((a, b) => a.priority - b.priority);
|
|
2853
|
-
return () => {
|
|
2854
|
-
const internal2 = get().internal;
|
|
2855
|
-
if (internal2?.subscribers) {
|
|
2856
|
-
internal2.priority = internal2.priority - (priority > 0 ? 1 : 0);
|
|
2857
|
-
internal2.subscribers = internal2.subscribers.filter((s) => s.ref !== ref);
|
|
2858
|
-
}
|
|
2859
|
-
};
|
|
2860
|
-
},
|
|
2861
|
-
// Renderer Storage (single source of truth)
|
|
2862
|
-
actualRenderer: null,
|
|
2863
|
-
// Scheduler for useFrameNext (initialized in renderer.tsx)
|
|
2864
|
-
scheduler: null
|
|
2865
|
-
}
|
|
2866
|
-
};
|
|
2867
|
-
return rootState;
|
|
1890
|
+
};
|
|
1891
|
+
return rootState;
|
|
2868
1892
|
});
|
|
2869
1893
|
const state = rootStore.getState();
|
|
2870
1894
|
Object.defineProperty(state, "gl", {
|
|
@@ -2872,8 +1896,7 @@ const createStore = (invalidate, advance) => {
|
|
|
2872
1896
|
const currentState = rootStore.getState();
|
|
2873
1897
|
if (!currentState.isLegacy && currentState.internal.actualRenderer) {
|
|
2874
1898
|
const stack = new Error().stack || "";
|
|
2875
|
-
|
|
2876
|
-
if (!isInternalAccess) {
|
|
1899
|
+
if (!isInternalRendererAccess(stack)) {
|
|
2877
1900
|
const cleanedStack = stack.split("\n").slice(2).join("\n") || "Stack trace unavailable";
|
|
2878
1901
|
notifyDepreciated({
|
|
2879
1902
|
heading: "Accessing state.gl in WebGPU mode",
|
|
@@ -2921,14 +1944,16 @@ const createStore = (invalidate, advance) => {
|
|
|
2921
1944
|
oldSize = size;
|
|
2922
1945
|
oldDpr = viewport.dpr;
|
|
2923
1946
|
updateCamera(camera, size);
|
|
2924
|
-
if (canvasTarget) {
|
|
1947
|
+
if (internal.isSecondary && canvasTarget) {
|
|
2925
1948
|
if (viewport.dpr > 0) canvasTarget.setPixelRatio(viewport.dpr);
|
|
2926
|
-
|
|
2927
|
-
canvasTarget.setSize(size.width, size.height, updateStyle);
|
|
1949
|
+
canvasTarget.setSize(size.width, size.height, false);
|
|
2928
1950
|
} else {
|
|
2929
1951
|
if (viewport.dpr > 0) actualRenderer.setPixelRatio(viewport.dpr);
|
|
2930
|
-
|
|
2931
|
-
|
|
1952
|
+
actualRenderer.setSize(size.width, size.height, false);
|
|
1953
|
+
if (canvasTarget) {
|
|
1954
|
+
if (viewport.dpr > 0) canvasTarget.setPixelRatio(viewport.dpr);
|
|
1955
|
+
canvasTarget.setSize(size.width, size.height, false);
|
|
1956
|
+
}
|
|
2932
1957
|
}
|
|
2933
1958
|
}
|
|
2934
1959
|
if (camera !== oldCamera) {
|
|
@@ -2984,26 +2009,32 @@ function loadingFn(extensions, onProgress) {
|
|
|
2984
2009
|
);
|
|
2985
2010
|
};
|
|
2986
2011
|
}
|
|
2987
|
-
function useLoader(loader, input, extensions, onProgress) {
|
|
2988
|
-
const
|
|
2012
|
+
function useLoader(loader, input, extensions, onProgress, cacheKey) {
|
|
2013
|
+
const inputs = Array.isArray(input) ? input : [input];
|
|
2014
|
+
const cacheKeys = Array.isArray(cacheKey ?? input) ? cacheKey ?? input : [cacheKey ?? input];
|
|
2989
2015
|
const fn = loadingFn(extensions, onProgress);
|
|
2990
|
-
const results =
|
|
2016
|
+
const results = cacheKeys.map(
|
|
2017
|
+
(key, index) => suspendReact.suspend(() => fn(loader, inputs[index]), [loader, key], { equal: is.equ })
|
|
2018
|
+
);
|
|
2991
2019
|
return Array.isArray(input) ? results : results[0];
|
|
2992
2020
|
}
|
|
2993
|
-
useLoader.preload = function(loader, input, extensions, onProgress) {
|
|
2994
|
-
const
|
|
2995
|
-
|
|
2021
|
+
useLoader.preload = function(loader, input, extensions, onProgress, cacheKey) {
|
|
2022
|
+
const inputs = Array.isArray(input) ? input : [input];
|
|
2023
|
+
const cacheKeys = Array.isArray(cacheKey ?? input) ? cacheKey ?? input : [cacheKey ?? input];
|
|
2024
|
+
cacheKeys.forEach(
|
|
2025
|
+
(key, index) => suspendReact.preload(() => loadingFn(extensions, onProgress)(loader, inputs[index]), [loader, key])
|
|
2026
|
+
);
|
|
2996
2027
|
};
|
|
2997
|
-
useLoader.clear = function(loader, input) {
|
|
2998
|
-
const
|
|
2999
|
-
|
|
2028
|
+
useLoader.clear = function(loader, input, cacheKey) {
|
|
2029
|
+
const cacheKeys = Array.isArray(cacheKey ?? input) ? cacheKey ?? input : [cacheKey ?? input];
|
|
2030
|
+
cacheKeys.forEach((key) => suspendReact.clear([loader, key]));
|
|
3000
2031
|
};
|
|
3001
2032
|
useLoader.loader = getLoader;
|
|
3002
2033
|
|
|
3003
2034
|
function useFrame(callback, priorityOrOptions) {
|
|
3004
2035
|
const store = React__namespace.useContext(context);
|
|
3005
2036
|
const isInsideCanvas = store !== null;
|
|
3006
|
-
const scheduler = getScheduler();
|
|
2037
|
+
const scheduler$1 = scheduler.getScheduler();
|
|
3007
2038
|
const optionsKey = typeof priorityOrOptions === "number" ? `p:${priorityOrOptions}` : priorityOrOptions ? JSON.stringify({
|
|
3008
2039
|
id: priorityOrOptions.id,
|
|
3009
2040
|
phase: priorityOrOptions.phase,
|
|
@@ -3051,7 +2082,7 @@ function useFrame(callback, priorityOrOptions) {
|
|
|
3051
2082
|
};
|
|
3052
2083
|
callbackRef.current?.(mergedState, delta);
|
|
3053
2084
|
};
|
|
3054
|
-
const unregister = scheduler.register(wrappedCallback, {
|
|
2085
|
+
const unregister = scheduler$1.register(wrappedCallback, {
|
|
3055
2086
|
id,
|
|
3056
2087
|
rootId,
|
|
3057
2088
|
...options
|
|
@@ -3072,37 +2103,31 @@ function useFrame(callback, priorityOrOptions) {
|
|
|
3072
2103
|
}
|
|
3073
2104
|
};
|
|
3074
2105
|
} else {
|
|
3075
|
-
|
|
3076
|
-
|
|
3077
|
-
|
|
3078
|
-
|
|
3079
|
-
|
|
3080
|
-
|
|
3081
|
-
|
|
3082
|
-
|
|
3083
|
-
unregisterJob = registerOutside();
|
|
3084
|
-
});
|
|
3085
|
-
return () => {
|
|
3086
|
-
unsubReady();
|
|
3087
|
-
unregisterJob?.();
|
|
3088
|
-
};
|
|
2106
|
+
return scheduler$1.register(
|
|
2107
|
+
(state, delta) => {
|
|
2108
|
+
const frameState = state;
|
|
2109
|
+
if (!frameState.renderer) return;
|
|
2110
|
+
callbackRef.current?.(frameState, delta);
|
|
2111
|
+
},
|
|
2112
|
+
{ id, ...options }
|
|
2113
|
+
);
|
|
3089
2114
|
}
|
|
3090
|
-
}, [store, scheduler, id, optionsKey, isLegacyPriority, isInsideCanvas]);
|
|
2115
|
+
}, [store, scheduler$1, id, optionsKey, isLegacyPriority, isInsideCanvas]);
|
|
3091
2116
|
const isPaused = React__namespace.useSyncExternalStore(
|
|
3092
2117
|
// Subscribe function
|
|
3093
2118
|
React__namespace.useCallback(
|
|
3094
2119
|
(onStoreChange) => {
|
|
3095
|
-
return getScheduler().subscribeJobState(id, onStoreChange);
|
|
2120
|
+
return scheduler.getScheduler().subscribeJobState(id, onStoreChange);
|
|
3096
2121
|
},
|
|
3097
2122
|
[id]
|
|
3098
2123
|
),
|
|
3099
2124
|
// getSnapshot function
|
|
3100
|
-
React__namespace.useCallback(() => getScheduler().isJobPaused(id), [id]),
|
|
2125
|
+
React__namespace.useCallback(() => scheduler.getScheduler().isJobPaused(id), [id]),
|
|
3101
2126
|
// getServerSnapshot function (SSR)
|
|
3102
2127
|
React__namespace.useCallback(() => false, [])
|
|
3103
2128
|
);
|
|
3104
2129
|
const controls = React__namespace.useMemo(() => {
|
|
3105
|
-
const scheduler2 = getScheduler();
|
|
2130
|
+
const scheduler2 = scheduler.getScheduler();
|
|
3106
2131
|
return {
|
|
3107
2132
|
/** The job's unique ID */
|
|
3108
2133
|
id,
|
|
@@ -3117,7 +2142,7 @@ function useFrame(callback, priorityOrOptions) {
|
|
|
3117
2142
|
* @param timestamp Optional timestamp (defaults to performance.now())
|
|
3118
2143
|
*/
|
|
3119
2144
|
step: (timestamp) => {
|
|
3120
|
-
getScheduler().stepJob(id, timestamp);
|
|
2145
|
+
scheduler.getScheduler().stepJob(id, timestamp);
|
|
3121
2146
|
},
|
|
3122
2147
|
/**
|
|
3123
2148
|
* Manually step ALL jobs in the scheduler.
|
|
@@ -3125,20 +2150,20 @@ function useFrame(callback, priorityOrOptions) {
|
|
|
3125
2150
|
* @param timestamp Optional timestamp (defaults to performance.now())
|
|
3126
2151
|
*/
|
|
3127
2152
|
stepAll: (timestamp) => {
|
|
3128
|
-
getScheduler().step(timestamp);
|
|
2153
|
+
scheduler.getScheduler().step(timestamp);
|
|
3129
2154
|
},
|
|
3130
2155
|
/**
|
|
3131
2156
|
* Pause this job (set enabled=false).
|
|
3132
2157
|
* Job remains registered but won't run.
|
|
3133
2158
|
*/
|
|
3134
2159
|
pause: () => {
|
|
3135
|
-
getScheduler().pauseJob(id);
|
|
2160
|
+
scheduler.getScheduler().pauseJob(id);
|
|
3136
2161
|
},
|
|
3137
2162
|
/**
|
|
3138
2163
|
* Resume this job (set enabled=true).
|
|
3139
2164
|
*/
|
|
3140
2165
|
resume: () => {
|
|
3141
|
-
getScheduler().resumeJob(id);
|
|
2166
|
+
scheduler.getScheduler().resumeJob(id);
|
|
3142
2167
|
},
|
|
3143
2168
|
/**
|
|
3144
2169
|
* Reactive paused state - automatically updates when pause/resume is called.
|
|
@@ -3176,18 +2201,18 @@ function buildFromCache(input, textureCache) {
|
|
|
3176
2201
|
function useTexture(input, optionsOrOnLoad) {
|
|
3177
2202
|
const renderer = useThree((state) => state.internal.actualRenderer);
|
|
3178
2203
|
const store = useStore();
|
|
3179
|
-
const textureCache = useThree((state) => state.textures);
|
|
3180
2204
|
const options = typeof optionsOrOnLoad === "function" ? { onLoad: optionsOrOnLoad } : optionsOrOnLoad ?? {};
|
|
3181
|
-
const { onLoad, cache =
|
|
2205
|
+
const { onLoad, cache = true } = options;
|
|
3182
2206
|
const onLoadRef = React.useRef(onLoad);
|
|
3183
2207
|
onLoadRef.current = onLoad;
|
|
3184
2208
|
const onLoadCalledForRef = React.useRef(null);
|
|
3185
2209
|
const urls = React.useMemo(() => getUrls(input), [input]);
|
|
3186
2210
|
const cachedResult = React.useMemo(() => {
|
|
3187
2211
|
if (!cache) return null;
|
|
3188
|
-
|
|
3189
|
-
|
|
3190
|
-
|
|
2212
|
+
const textures = store.getState().textures;
|
|
2213
|
+
if (!allUrlsCached(urls, textures)) return null;
|
|
2214
|
+
return buildFromCache(input, textures);
|
|
2215
|
+
}, [cache, urls, input, store]);
|
|
3191
2216
|
const loadedTextures = useLoader(
|
|
3192
2217
|
webgpu.TextureLoader,
|
|
3193
2218
|
IsObject(input) ? Object.values(input) : input
|
|
@@ -3231,8 +2256,6 @@ function useTexture(input, optionsOrOnLoad) {
|
|
|
3231
2256
|
}, [input, loadedTextures, cachedResult]);
|
|
3232
2257
|
React.useEffect(() => {
|
|
3233
2258
|
if (!cache) return;
|
|
3234
|
-
if (cachedResult) return;
|
|
3235
|
-
const set = store.setState;
|
|
3236
2259
|
const urlTextureMap = [];
|
|
3237
2260
|
if (typeof input === "string") {
|
|
3238
2261
|
urlTextureMap.push([input, mappedTextures]);
|
|
@@ -3246,18 +2269,32 @@ function useTexture(input, optionsOrOnLoad) {
|
|
|
3246
2269
|
urlTextureMap.push([url, textureRecord[key]]);
|
|
3247
2270
|
}
|
|
3248
2271
|
}
|
|
3249
|
-
|
|
3250
|
-
const
|
|
3251
|
-
let
|
|
2272
|
+
store.setState((state) => {
|
|
2273
|
+
const refs = new Map(state._textureRefs);
|
|
2274
|
+
let textures = state.textures;
|
|
2275
|
+
let added = false;
|
|
3252
2276
|
for (const [url, texture] of urlTextureMap) {
|
|
3253
|
-
if (!
|
|
3254
|
-
|
|
3255
|
-
|
|
2277
|
+
if (!textures.has(url)) {
|
|
2278
|
+
if (!added) {
|
|
2279
|
+
textures = new Map(textures);
|
|
2280
|
+
added = true;
|
|
2281
|
+
}
|
|
2282
|
+
textures.set(url, texture);
|
|
3256
2283
|
}
|
|
2284
|
+
refs.set(url, (refs.get(url) ?? 0) + 1);
|
|
3257
2285
|
}
|
|
3258
|
-
return
|
|
2286
|
+
return added ? { textures, _textureRefs: refs } : { _textureRefs: refs };
|
|
2287
|
+
});
|
|
2288
|
+
return () => store.setState((state) => {
|
|
2289
|
+
const refs = new Map(state._textureRefs);
|
|
2290
|
+
for (const [url] of urlTextureMap) {
|
|
2291
|
+
const next = (refs.get(url) ?? 0) - 1;
|
|
2292
|
+
if (next <= 0) refs.delete(url);
|
|
2293
|
+
else refs.set(url, next);
|
|
2294
|
+
}
|
|
2295
|
+
return { _textureRefs: refs };
|
|
3259
2296
|
});
|
|
3260
|
-
}, [cache, input, mappedTextures, store
|
|
2297
|
+
}, [cache, input, mappedTextures, store]);
|
|
3261
2298
|
return mappedTextures;
|
|
3262
2299
|
}
|
|
3263
2300
|
useTexture.preload = (url) => useLoader.preload(webgpu.TextureLoader, url);
|
|
@@ -3273,96 +2310,63 @@ const Texture = ({
|
|
|
3273
2310
|
return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: children?.(ret) });
|
|
3274
2311
|
};
|
|
3275
2312
|
|
|
3276
|
-
function
|
|
3277
|
-
if (entry instanceof webgpu.Texture) return entry;
|
|
3278
|
-
if (entry && typeof entry === "object" && "value" in entry && entry.value instanceof webgpu.Texture) {
|
|
3279
|
-
return entry.value;
|
|
3280
|
-
}
|
|
3281
|
-
return null;
|
|
3282
|
-
}
|
|
3283
|
-
function useTextures() {
|
|
2313
|
+
function useTextures(selector) {
|
|
3284
2314
|
const store = useStore();
|
|
3285
|
-
|
|
3286
|
-
const set = store.setState;
|
|
2315
|
+
const registry = React.useMemo(() => {
|
|
3287
2316
|
const getState = store.getState;
|
|
3288
|
-
const
|
|
3289
|
-
|
|
3290
|
-
const newMap = new Map(state.textures);
|
|
3291
|
-
newMap.set(key, value);
|
|
3292
|
-
return { textures: newMap };
|
|
3293
|
-
});
|
|
3294
|
-
};
|
|
3295
|
-
const addMultiple = (items) => {
|
|
3296
|
-
set((state) => {
|
|
3297
|
-
const newMap = new Map(state.textures);
|
|
3298
|
-
const entries = items instanceof Map ? items.entries() : Object.entries(items);
|
|
3299
|
-
for (const [key, value] of entries) {
|
|
3300
|
-
newMap.set(key, value);
|
|
3301
|
-
}
|
|
3302
|
-
return { textures: newMap };
|
|
3303
|
-
});
|
|
3304
|
-
};
|
|
3305
|
-
const remove = (key) => {
|
|
3306
|
-
set((state) => {
|
|
3307
|
-
const newMap = new Map(state.textures);
|
|
3308
|
-
newMap.delete(key);
|
|
3309
|
-
return { textures: newMap };
|
|
3310
|
-
});
|
|
3311
|
-
};
|
|
3312
|
-
const removeMultiple = (keys) => {
|
|
3313
|
-
set((state) => {
|
|
3314
|
-
const newMap = new Map(state.textures);
|
|
3315
|
-
for (const key of keys) newMap.delete(key);
|
|
3316
|
-
return { textures: newMap };
|
|
3317
|
-
});
|
|
3318
|
-
};
|
|
3319
|
-
const dispose = (key) => {
|
|
3320
|
-
const entry = getState().textures.get(key);
|
|
3321
|
-
if (entry) {
|
|
3322
|
-
const tex = getTextureValue(entry);
|
|
3323
|
-
tex?.dispose();
|
|
3324
|
-
}
|
|
3325
|
-
remove(key);
|
|
3326
|
-
};
|
|
3327
|
-
const disposeMultiple = (keys) => {
|
|
3328
|
-
const textures = getState().textures;
|
|
3329
|
-
for (const key of keys) {
|
|
3330
|
-
const entry = textures.get(key);
|
|
3331
|
-
if (entry) {
|
|
3332
|
-
const tex = getTextureValue(entry);
|
|
3333
|
-
tex?.dispose();
|
|
3334
|
-
}
|
|
3335
|
-
}
|
|
3336
|
-
removeMultiple(keys);
|
|
3337
|
-
};
|
|
3338
|
-
const disposeAll = () => {
|
|
3339
|
-
const textures = getState().textures;
|
|
3340
|
-
for (const entry of textures.values()) {
|
|
3341
|
-
const tex = getTextureValue(entry);
|
|
3342
|
-
tex?.dispose();
|
|
3343
|
-
}
|
|
3344
|
-
set({ textures: /* @__PURE__ */ new Map() });
|
|
3345
|
-
};
|
|
2317
|
+
const setState = store.setState;
|
|
2318
|
+
const getOne = (key) => getState().textures.get(key);
|
|
3346
2319
|
return {
|
|
3347
|
-
|
|
3348
|
-
get textures() {
|
|
2320
|
+
get all() {
|
|
3349
2321
|
return getState().textures;
|
|
3350
2322
|
},
|
|
3351
|
-
|
|
3352
|
-
|
|
2323
|
+
get(input) {
|
|
2324
|
+
if (typeof input === "string") return getOne(input);
|
|
2325
|
+
if (Array.isArray(input)) return input.map(getOne);
|
|
2326
|
+
const out = {};
|
|
2327
|
+
for (const name in input) out[name] = getOne(input[name]);
|
|
2328
|
+
return out;
|
|
2329
|
+
},
|
|
3353
2330
|
has: (key) => getState().textures.has(key),
|
|
3354
|
-
|
|
3355
|
-
|
|
3356
|
-
|
|
3357
|
-
|
|
3358
|
-
|
|
3359
|
-
|
|
3360
|
-
|
|
3361
|
-
|
|
3362
|
-
|
|
3363
|
-
|
|
2331
|
+
add(keyOrRecord, texture) {
|
|
2332
|
+
setState((state) => {
|
|
2333
|
+
const textures = new Map(state.textures);
|
|
2334
|
+
if (typeof keyOrRecord === "string") {
|
|
2335
|
+
textures.set(keyOrRecord, texture);
|
|
2336
|
+
} else {
|
|
2337
|
+
for (const key in keyOrRecord) textures.set(key, keyOrRecord[key]);
|
|
2338
|
+
}
|
|
2339
|
+
return { textures };
|
|
2340
|
+
});
|
|
2341
|
+
},
|
|
2342
|
+
dispose(key, options) {
|
|
2343
|
+
const state = getState();
|
|
2344
|
+
const refs = state._textureRefs.get(key) ?? 0;
|
|
2345
|
+
if (refs > 0 && !options?.force) {
|
|
2346
|
+
console.warn(
|
|
2347
|
+
`[useTextures] "${key}" still has ${refs} active reference(s); skipping dispose. Pass { force: true } to override.`
|
|
2348
|
+
);
|
|
2349
|
+
return false;
|
|
2350
|
+
}
|
|
2351
|
+
state.textures.get(key)?.dispose();
|
|
2352
|
+
setState((s) => {
|
|
2353
|
+
const textures = new Map(s.textures);
|
|
2354
|
+
textures.delete(key);
|
|
2355
|
+
const nextRefs = new Map(s._textureRefs);
|
|
2356
|
+
nextRefs.delete(key);
|
|
2357
|
+
return { textures, _textureRefs: nextRefs };
|
|
2358
|
+
});
|
|
2359
|
+
return true;
|
|
2360
|
+
},
|
|
2361
|
+
disposeAll() {
|
|
2362
|
+
for (const texture of getState().textures.values()) texture.dispose();
|
|
2363
|
+
setState({ textures: /* @__PURE__ */ new Map(), _textureRefs: /* @__PURE__ */ new Map() });
|
|
2364
|
+
}
|
|
3364
2365
|
};
|
|
3365
2366
|
}, [store]);
|
|
2367
|
+
const subscribe = selector ? () => selector(registry) : (state) => state.textures;
|
|
2368
|
+
const selected = useThree(subscribe);
|
|
2369
|
+
return selector ? selected : registry;
|
|
3366
2370
|
}
|
|
3367
2371
|
|
|
3368
2372
|
function useRenderTarget(widthOrOptions, heightOrOptions, options) {
|
|
@@ -3417,7 +2421,7 @@ function addEffect(callback) {
|
|
|
3417
2421
|
link: "https://docs.pmnd.rs/react-three-fiber/api/hooks#useframe"
|
|
3418
2422
|
});
|
|
3419
2423
|
const id = `legacy_effect_${effectId++}`;
|
|
3420
|
-
return getScheduler().registerGlobal("before", id, callback);
|
|
2424
|
+
return scheduler.getScheduler().registerGlobal("before", id, callback);
|
|
3421
2425
|
}
|
|
3422
2426
|
function addAfterEffect(callback) {
|
|
3423
2427
|
notifyDepreciated({
|
|
@@ -3426,7 +2430,7 @@ function addAfterEffect(callback) {
|
|
|
3426
2430
|
link: "https://docs.pmnd.rs/react-three-fiber/api/hooks#useframe"
|
|
3427
2431
|
});
|
|
3428
2432
|
const id = `legacy_afterEffect_${effectId++}`;
|
|
3429
|
-
return getScheduler().registerGlobal("after", id, callback);
|
|
2433
|
+
return scheduler.getScheduler().registerGlobal("after", id, callback);
|
|
3430
2434
|
}
|
|
3431
2435
|
function addTail(callback) {
|
|
3432
2436
|
notifyDepreciated({
|
|
@@ -3434,16 +2438,16 @@ function addTail(callback) {
|
|
|
3434
2438
|
body: "Use scheduler.onIdle(callback) instead.\naddTail will be removed in a future version.",
|
|
3435
2439
|
link: "https://docs.pmnd.rs/react-three-fiber/api/hooks#useframe"
|
|
3436
2440
|
});
|
|
3437
|
-
return getScheduler().onIdle(callback);
|
|
2441
|
+
return scheduler.getScheduler().onIdle(callback);
|
|
3438
2442
|
}
|
|
3439
2443
|
function invalidate(state, frames = 1, stackFrames = false) {
|
|
3440
|
-
getScheduler().invalidate(frames, stackFrames);
|
|
2444
|
+
scheduler.getScheduler().invalidate(frames, stackFrames);
|
|
3441
2445
|
}
|
|
3442
2446
|
function advance(timestamp) {
|
|
3443
|
-
getScheduler().step(timestamp);
|
|
2447
|
+
scheduler.getScheduler().step(timestamp);
|
|
3444
2448
|
}
|
|
3445
2449
|
|
|
3446
|
-
const version = "10.0.0-alpha.
|
|
2450
|
+
const version = "10.0.0-alpha.3";
|
|
3447
2451
|
const packageData = {
|
|
3448
2452
|
version: version};
|
|
3449
2453
|
|
|
@@ -14882,6 +13886,7 @@ function swapInstances() {
|
|
|
14882
13886
|
}
|
|
14883
13887
|
}
|
|
14884
13888
|
if (instance.isHidden) unhideInstance(instance);
|
|
13889
|
+
if (isObject3D(instance.object)) removeInteractivity(findInitialRoot(instance), instance.object);
|
|
14885
13890
|
if (instance.object.__r3f) delete instance.object.__r3f;
|
|
14886
13891
|
if (instance.type !== "primitive") disposeOnIdle(instance.object);
|
|
14887
13892
|
}
|
|
@@ -14894,6 +13899,8 @@ function swapInstances() {
|
|
|
14894
13899
|
instance.object.__r3f = instance;
|
|
14895
13900
|
setFiberRef(fiber, instance.object);
|
|
14896
13901
|
delete instance.appliedOnce;
|
|
13902
|
+
instance.eventCount = 0;
|
|
13903
|
+
instance.handlers = {};
|
|
14897
13904
|
applyProps(instance.object, instance.props);
|
|
14898
13905
|
if (instance.props.attach) {
|
|
14899
13906
|
attach(parent, instance);
|
|
@@ -15109,11 +14116,15 @@ const reconciler = /* @__PURE__ */ createReconciler({
|
|
|
15109
14116
|
cloneMutableTextInstance(textInstance) {
|
|
15110
14117
|
return textInstance;
|
|
15111
14118
|
},
|
|
14119
|
+
// React 19.2 added these for root-level view transitions. They only run for hosts that opt into
|
|
14120
|
+
// view transitions by returning a container from cloneRootViewTransitionContainer; R3F renders
|
|
14121
|
+
// into a WebGL/WebGPU canvas, where cloning the root has no meaning, so we never opt in and
|
|
14122
|
+
// React never calls them. They stay as no-ops rather than throws: the host config is shared by
|
|
14123
|
+
// every root, and a throw here would turn a React-internal probe into a hard crash for users.
|
|
14124
|
+
// Guarded by a test asserting a full render + unmount never reaches them.
|
|
15112
14125
|
cloneRootViewTransitionContainer(_rootContainer) {
|
|
15113
|
-
throw new Error("Not implemented.");
|
|
15114
14126
|
},
|
|
15115
14127
|
removeRootViewTransitionClone(_rootContainer, _clone) {
|
|
15116
|
-
throw new Error("Not implemented.");
|
|
15117
14128
|
},
|
|
15118
14129
|
// https://github.com/facebook/react/pull/32465
|
|
15119
14130
|
createFragmentInstance: (_fiber) => null,
|
|
@@ -15198,6 +14209,7 @@ function createRoot(canvas) {
|
|
|
15198
14209
|
const lastConfiguredProps = {};
|
|
15199
14210
|
let configured = false;
|
|
15200
14211
|
let pending = null;
|
|
14212
|
+
let rendererSetup = null;
|
|
15201
14213
|
return {
|
|
15202
14214
|
async configure(props = {}) {
|
|
15203
14215
|
let resolve;
|
|
@@ -15213,7 +14225,6 @@ function createRoot(canvas) {
|
|
|
15213
14225
|
events,
|
|
15214
14226
|
onCreated: onCreatedCallback,
|
|
15215
14227
|
shadows = false,
|
|
15216
|
-
textureColorSpace = webgpu.SRGBColorSpace,
|
|
15217
14228
|
orthographic = false,
|
|
15218
14229
|
frameloop = "always",
|
|
15219
14230
|
dpr = [1, 2],
|
|
@@ -15228,7 +14239,8 @@ function createRoot(canvas) {
|
|
|
15228
14239
|
_sizeProps,
|
|
15229
14240
|
forceEven
|
|
15230
14241
|
} = props;
|
|
15231
|
-
const
|
|
14242
|
+
const textureColorSpace = is.obj(glConfig) && !is.fun(glConfig) && !isRenderer(glConfig) && glConfig.textureColorSpace || is.obj(rendererConfig) && !is.fun(rendererConfig) && !isRenderer(rendererConfig) && rendererConfig.textureColorSpace || webgpu.SRGBColorSpace;
|
|
14243
|
+
let state = store.getState();
|
|
15232
14244
|
const defaultGPUProps = {
|
|
15233
14245
|
canvas,
|
|
15234
14246
|
antialias: true
|
|
@@ -15242,52 +14254,64 @@ function createRoot(canvas) {
|
|
|
15242
14254
|
throw new Error("Cannot use both gl and renderer props at the same time");
|
|
15243
14255
|
}
|
|
15244
14256
|
let renderer = state.internal.actualRenderer;
|
|
15245
|
-
if (
|
|
15246
|
-
|
|
15247
|
-
|
|
15248
|
-
|
|
15249
|
-
|
|
15250
|
-
|
|
15251
|
-
|
|
15252
|
-
|
|
15253
|
-
|
|
15254
|
-
|
|
15255
|
-
|
|
15256
|
-
|
|
15257
|
-
|
|
15258
|
-
|
|
15259
|
-
|
|
15260
|
-
|
|
15261
|
-
|
|
15262
|
-
|
|
15263
|
-
|
|
15264
|
-
|
|
15265
|
-
|
|
15266
|
-
|
|
15267
|
-
|
|
15268
|
-
|
|
15269
|
-
|
|
15270
|
-
|
|
15271
|
-
|
|
15272
|
-
|
|
15273
|
-
|
|
15274
|
-
|
|
15275
|
-
|
|
15276
|
-
|
|
15277
|
-
|
|
15278
|
-
|
|
15279
|
-
|
|
15280
|
-
|
|
15281
|
-
|
|
15282
|
-
|
|
15283
|
-
|
|
15284
|
-
|
|
15285
|
-
|
|
15286
|
-
|
|
15287
|
-
|
|
14257
|
+
if (!state.internal.actualRenderer) {
|
|
14258
|
+
if (!rendererSetup) {
|
|
14259
|
+
rendererSetup = (async () => {
|
|
14260
|
+
if (primaryCanvas) {
|
|
14261
|
+
const primary = await waitForPrimary(primaryCanvas);
|
|
14262
|
+
renderer = primary.renderer;
|
|
14263
|
+
state.internal.actualRenderer = renderer;
|
|
14264
|
+
const canvasTarget = new webgpu.CanvasTarget(canvas);
|
|
14265
|
+
primary.store.setState((prev) => ({
|
|
14266
|
+
internal: { ...prev.internal, isMultiCanvas: true }
|
|
14267
|
+
}));
|
|
14268
|
+
state.set((prev) => ({
|
|
14269
|
+
webGPUSupported: primary.store.getState().webGPUSupported,
|
|
14270
|
+
renderer,
|
|
14271
|
+
primaryStore: primary.store,
|
|
14272
|
+
internal: {
|
|
14273
|
+
...prev.internal,
|
|
14274
|
+
canvasTarget,
|
|
14275
|
+
isMultiCanvas: true,
|
|
14276
|
+
isSecondary: true,
|
|
14277
|
+
targetId: primaryCanvas
|
|
14278
|
+
}
|
|
14279
|
+
}));
|
|
14280
|
+
} else {
|
|
14281
|
+
renderer = await resolveRenderer(rendererConfig, defaultGPUProps, webgpu.WebGPURenderer);
|
|
14282
|
+
if (!renderer.hasInitialized?.()) {
|
|
14283
|
+
const size2 = computeInitialSize(canvas, propsSize);
|
|
14284
|
+
if (size2.width > 0 && size2.height > 0) {
|
|
14285
|
+
const pixelRatio = calculateDpr(dpr);
|
|
14286
|
+
canvas.width = size2.width * pixelRatio;
|
|
14287
|
+
canvas.height = size2.height * pixelRatio;
|
|
14288
|
+
}
|
|
14289
|
+
await renderer.init();
|
|
14290
|
+
}
|
|
14291
|
+
const backend = renderer.backend;
|
|
14292
|
+
const isWebGPUBackend = backend && "isWebGPUBackend" in backend;
|
|
14293
|
+
state.internal.actualRenderer = renderer;
|
|
14294
|
+
state.set({ webGPUSupported: isWebGPUBackend, renderer, primaryStore: store });
|
|
14295
|
+
if (canvasId && !state.internal.isSecondary) {
|
|
14296
|
+
const canvasTarget = new webgpu.CanvasTarget(canvas);
|
|
14297
|
+
const unregisterPrimary = registerPrimary(canvasId, renderer, store);
|
|
14298
|
+
state.set((prev) => ({
|
|
14299
|
+
internal: {
|
|
14300
|
+
...prev.internal,
|
|
14301
|
+
canvasTarget,
|
|
14302
|
+
unregisterPrimary
|
|
14303
|
+
}
|
|
14304
|
+
}));
|
|
14305
|
+
}
|
|
15288
14306
|
}
|
|
15289
|
-
}))
|
|
14307
|
+
})().catch((err) => {
|
|
14308
|
+
rendererSetup = null;
|
|
14309
|
+
throw err;
|
|
14310
|
+
});
|
|
15290
14311
|
}
|
|
14312
|
+
await rendererSetup;
|
|
14313
|
+
state = store.getState();
|
|
14314
|
+
renderer = state.internal.actualRenderer;
|
|
15291
14315
|
}
|
|
15292
14316
|
let raycaster = state.raycaster;
|
|
15293
14317
|
if (!raycaster) state.set({ raycaster: raycaster = new webgpu.Raycaster() });
|
|
@@ -15380,7 +14404,7 @@ function createRoot(canvas) {
|
|
|
15380
14404
|
lastConfiguredProps.performance = performance;
|
|
15381
14405
|
}
|
|
15382
14406
|
if (!state.xr) {
|
|
15383
|
-
const handleXRFrame = (timestamp,
|
|
14407
|
+
const handleXRFrame = (timestamp, _frame) => {
|
|
15384
14408
|
const state2 = store.getState();
|
|
15385
14409
|
if (state2.frameloop === "never") return;
|
|
15386
14410
|
advance(timestamp);
|
|
@@ -15416,15 +14440,22 @@ function createRoot(canvas) {
|
|
|
15416
14440
|
const oldType = renderer.shadowMap.type;
|
|
15417
14441
|
renderer.shadowMap.enabled = !!shadows;
|
|
15418
14442
|
if (is.boo(shadows)) {
|
|
15419
|
-
renderer.shadowMap.type = webgpu.
|
|
14443
|
+
renderer.shadowMap.type = webgpu.PCFShadowMap;
|
|
15420
14444
|
} else if (is.str(shadows)) {
|
|
14445
|
+
if (shadows === "soft") {
|
|
14446
|
+
notifyDepreciated({
|
|
14447
|
+
heading: 'shadows="soft" is deprecated',
|
|
14448
|
+
body: "Three has depreciated soft and improved basic PCFShadows, we converted for you.",
|
|
14449
|
+
link: "https://github.com/mrdoob/three.js/wiki/Migration-Guide?utm_source=chatgpt.com#181--182"
|
|
14450
|
+
});
|
|
14451
|
+
}
|
|
15421
14452
|
const types = {
|
|
15422
14453
|
basic: webgpu.BasicShadowMap,
|
|
15423
14454
|
percentage: webgpu.PCFShadowMap,
|
|
15424
|
-
soft: webgpu.
|
|
14455
|
+
soft: webgpu.PCFShadowMap,
|
|
15425
14456
|
variance: webgpu.VSMShadowMap
|
|
15426
14457
|
};
|
|
15427
|
-
renderer.shadowMap.type = types[shadows] ?? webgpu.
|
|
14458
|
+
renderer.shadowMap.type = types[shadows] ?? webgpu.PCFShadowMap;
|
|
15428
14459
|
} else if (is.obj(shadows)) {
|
|
15429
14460
|
Object.assign(renderer.shadowMap, shadows);
|
|
15430
14461
|
}
|
|
@@ -15440,24 +14471,35 @@ function createRoot(canvas) {
|
|
|
15440
14471
|
if (state.textureColorSpace !== textureColorSpace) state.set(() => ({ textureColorSpace }));
|
|
15441
14472
|
lastConfiguredProps.textureColorSpace = textureColorSpace;
|
|
15442
14473
|
}
|
|
14474
|
+
const r3fProps = ["textureColorSpace"];
|
|
14475
|
+
const constructorOnlyProps = ["samples", "antialias", "alpha", "canvas", "powerPreference"];
|
|
14476
|
+
const nonApplyProps = [...r3fProps, ...constructorOnlyProps];
|
|
15443
14477
|
if (glConfig && !is.fun(glConfig) && !isRenderer(glConfig) && !is.equ(glConfig, renderer, shallowLoose)) {
|
|
15444
|
-
|
|
14478
|
+
const glProps = {};
|
|
14479
|
+
for (const key in glConfig) {
|
|
14480
|
+
if (!nonApplyProps.includes(key)) glProps[key] = glConfig[key];
|
|
14481
|
+
}
|
|
14482
|
+
applyProps(renderer, glProps);
|
|
15445
14483
|
}
|
|
15446
14484
|
if (rendererConfig && !is.fun(rendererConfig) && !isRenderer(rendererConfig) && state.renderer) {
|
|
15447
14485
|
const currentRenderer = state.renderer;
|
|
15448
14486
|
if (!is.equ(rendererConfig, currentRenderer, shallowLoose)) {
|
|
15449
|
-
|
|
14487
|
+
const rendererProps = {};
|
|
14488
|
+
for (const key in rendererConfig) {
|
|
14489
|
+
if (!nonApplyProps.includes(key)) rendererProps[key] = rendererConfig[key];
|
|
14490
|
+
}
|
|
14491
|
+
applyProps(currentRenderer, rendererProps);
|
|
15450
14492
|
}
|
|
15451
14493
|
}
|
|
15452
|
-
const scheduler = getScheduler();
|
|
14494
|
+
const scheduler$1 = scheduler.getScheduler();
|
|
15453
14495
|
const rootId = state.internal.rootId;
|
|
15454
14496
|
if (!rootId) {
|
|
15455
|
-
const newRootId = canvasId || scheduler.generateRootId();
|
|
15456
|
-
const unregisterRoot = scheduler.registerRoot(newRootId, {
|
|
14497
|
+
const newRootId = canvasId || scheduler$1.generateRootId();
|
|
14498
|
+
const unregisterRoot = scheduler$1.registerRoot(newRootId, {
|
|
15457
14499
|
getState: () => store.getState(),
|
|
15458
14500
|
onError: (err) => store.getState().setError(err)
|
|
15459
14501
|
});
|
|
15460
|
-
const unregisterCanvasTarget = scheduler.register(
|
|
14502
|
+
const unregisterCanvasTarget = scheduler$1.register(
|
|
15461
14503
|
() => {
|
|
15462
14504
|
const state2 = store.getState();
|
|
15463
14505
|
if (state2.internal.isMultiCanvas && state2.internal.canvasTarget) {
|
|
@@ -15472,7 +14514,7 @@ function createRoot(canvas) {
|
|
|
15472
14514
|
system: true
|
|
15473
14515
|
}
|
|
15474
14516
|
);
|
|
15475
|
-
const unregisterEventsFlush = scheduler.register(
|
|
14517
|
+
const unregisterEventsFlush = scheduler$1.register(
|
|
15476
14518
|
() => {
|
|
15477
14519
|
const state2 = store.getState();
|
|
15478
14520
|
state2.events.flush?.();
|
|
@@ -15484,7 +14526,7 @@ function createRoot(canvas) {
|
|
|
15484
14526
|
system: true
|
|
15485
14527
|
}
|
|
15486
14528
|
);
|
|
15487
|
-
const unregisterFrustum = scheduler.register(
|
|
14529
|
+
const unregisterFrustum = scheduler$1.register(
|
|
15488
14530
|
() => {
|
|
15489
14531
|
const state2 = store.getState();
|
|
15490
14532
|
if (state2.autoUpdateFrustum && state2.camera) {
|
|
@@ -15494,11 +14536,11 @@ function createRoot(canvas) {
|
|
|
15494
14536
|
{
|
|
15495
14537
|
id: `${newRootId}_frustum`,
|
|
15496
14538
|
rootId: newRootId,
|
|
15497
|
-
|
|
14539
|
+
before: "render",
|
|
15498
14540
|
system: true
|
|
15499
14541
|
}
|
|
15500
14542
|
);
|
|
15501
|
-
const unregisterVisibility = scheduler.register(
|
|
14543
|
+
const unregisterVisibility = scheduler$1.register(
|
|
15502
14544
|
() => {
|
|
15503
14545
|
const state2 = store.getState();
|
|
15504
14546
|
checkVisibility(state2);
|
|
@@ -15506,16 +14548,16 @@ function createRoot(canvas) {
|
|
|
15506
14548
|
{
|
|
15507
14549
|
id: `${newRootId}_visibility`,
|
|
15508
14550
|
rootId: newRootId,
|
|
15509
|
-
|
|
14551
|
+
before: "render",
|
|
15510
14552
|
system: true,
|
|
15511
14553
|
after: `${newRootId}_frustum`
|
|
15512
14554
|
}
|
|
15513
14555
|
);
|
|
15514
|
-
const unregisterRender = scheduler.register(
|
|
14556
|
+
const unregisterRender = scheduler$1.register(
|
|
15515
14557
|
() => {
|
|
15516
14558
|
const state2 = store.getState();
|
|
15517
14559
|
const renderer2 = state2.internal.actualRenderer;
|
|
15518
|
-
const userHandlesRender = scheduler.hasUserJobsInPhase("render", newRootId);
|
|
14560
|
+
const userHandlesRender = scheduler$1.hasUserJobsInPhase("render", newRootId);
|
|
15519
14561
|
if (userHandlesRender || state2.internal.priority) return;
|
|
15520
14562
|
try {
|
|
15521
14563
|
if (state2.renderPipeline?.render) state2.renderPipeline.render();
|
|
@@ -15548,11 +14590,11 @@ function createRoot(canvas) {
|
|
|
15548
14590
|
unregisterVisibility();
|
|
15549
14591
|
unregisterRender();
|
|
15550
14592
|
},
|
|
15551
|
-
scheduler
|
|
14593
|
+
scheduler: scheduler$1
|
|
15552
14594
|
}
|
|
15553
14595
|
}));
|
|
15554
14596
|
}
|
|
15555
|
-
scheduler.frameloop = frameloop;
|
|
14597
|
+
scheduler$1.frameloop = frameloop;
|
|
15556
14598
|
onCreated = onCreatedCallback;
|
|
15557
14599
|
configured = true;
|
|
15558
14600
|
resolve();
|
|
@@ -15619,7 +14661,8 @@ function unmountComponentAtNode(canvas, callback) {
|
|
|
15619
14661
|
dispose(state.scene);
|
|
15620
14662
|
_roots.delete(canvas);
|
|
15621
14663
|
if (callback) callback(canvas);
|
|
15622
|
-
} catch {
|
|
14664
|
+
} catch (error) {
|
|
14665
|
+
console.warn("[R3F] Error while unmounting root; teardown may be incomplete:", error);
|
|
15623
14666
|
}
|
|
15624
14667
|
}, 500);
|
|
15625
14668
|
}
|
|
@@ -15682,6 +14725,7 @@ function PortalInner({ state = {}, children, container }) {
|
|
|
15682
14725
|
viewport = rootState.viewport.getCurrentViewport(camera, new webgpu.Vector3(), resolvedSize);
|
|
15683
14726
|
if (camera !== rootState.camera) updateCamera(camera, resolvedSize);
|
|
15684
14727
|
}
|
|
14728
|
+
const set = injectState.set;
|
|
15685
14729
|
return {
|
|
15686
14730
|
// The intersect consists of the previous root state
|
|
15687
14731
|
...rootState,
|
|
@@ -15700,33 +14744,205 @@ function PortalInner({ state = {}, children, container }) {
|
|
|
15700
14744
|
size: resolvedSize,
|
|
15701
14745
|
viewport: { ...rootState.viewport, ...viewport },
|
|
15702
14746
|
// Layers are allowed to override events
|
|
15703
|
-
setEvents: (events2) =>
|
|
14747
|
+
setEvents: (events2) => set((state2) => ({ ...state2, events: { ...state2.events, ...events2 } })),
|
|
15704
14748
|
// Container for child attachment - the portalScene (injected or container itself)
|
|
15705
14749
|
internal: { ...rootState.internal, ...injectState.internal, container: portalScene }
|
|
15706
14750
|
};
|
|
15707
14751
|
});
|
|
15708
|
-
const usePortalStore = React.useMemo(() => {
|
|
15709
|
-
const store = traditional.createWithEqualityFn((set, get) => ({ ...rest, set, get }));
|
|
15710
|
-
const onMutate = (prev) => store.setState((state2) => inject.current(prev, state2));
|
|
15711
|
-
onMutate(previousRoot.getState());
|
|
15712
|
-
return store;
|
|
15713
|
-
}, [previousRoot, container]);
|
|
15714
|
-
useIsomorphicLayoutEffect(() => {
|
|
15715
|
-
const onMutate = (prev) => usePortalStore.setState((state2) => inject.current(prev, state2));
|
|
15716
|
-
const unsubscribe = previousRoot.subscribe(onMutate);
|
|
15717
|
-
return unsubscribe;
|
|
15718
|
-
}, [previousRoot, usePortalStore]);
|
|
15719
|
-
return (
|
|
15720
|
-
// @ts-ignore, reconciler types are not maintained
|
|
15721
|
-
/* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: reconciler.createPortal(
|
|
15722
|
-
/* @__PURE__ */ jsxRuntime.jsx(context.Provider, { value: usePortalStore, children }),
|
|
15723
|
-
usePortalStore,
|
|
15724
|
-
null
|
|
15725
|
-
) })
|
|
15726
|
-
);
|
|
14752
|
+
const usePortalStore = React.useMemo(() => {
|
|
14753
|
+
const store = traditional.createWithEqualityFn((set, get) => ({ ...rest, set, get }));
|
|
14754
|
+
const onMutate = (prev) => store.setState((state2) => inject.current(prev, state2));
|
|
14755
|
+
onMutate(previousRoot.getState());
|
|
14756
|
+
return store;
|
|
14757
|
+
}, [previousRoot, container]);
|
|
14758
|
+
useIsomorphicLayoutEffect(() => {
|
|
14759
|
+
const onMutate = (prev) => usePortalStore.setState((state2) => inject.current(prev, state2));
|
|
14760
|
+
const unsubscribe = previousRoot.subscribe(onMutate);
|
|
14761
|
+
return unsubscribe;
|
|
14762
|
+
}, [previousRoot, usePortalStore]);
|
|
14763
|
+
return (
|
|
14764
|
+
// @ts-ignore, reconciler types are not maintained
|
|
14765
|
+
/* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: reconciler.createPortal(
|
|
14766
|
+
/* @__PURE__ */ jsxRuntime.jsx(context.Provider, { value: usePortalStore, children }),
|
|
14767
|
+
usePortalStore,
|
|
14768
|
+
null
|
|
14769
|
+
) })
|
|
14770
|
+
);
|
|
14771
|
+
}
|
|
14772
|
+
function flushSync(fn) {
|
|
14773
|
+
return reconciler.flushSyncFromReconciler(fn);
|
|
14774
|
+
}
|
|
14775
|
+
|
|
14776
|
+
function parseBackground(background) {
|
|
14777
|
+
if (!background) return null;
|
|
14778
|
+
if (typeof background === "object" && !background.isColor) {
|
|
14779
|
+
const { backgroundMap, envMap, files, preset, ...rest } = background;
|
|
14780
|
+
return {
|
|
14781
|
+
...rest,
|
|
14782
|
+
preset,
|
|
14783
|
+
files: envMap || files,
|
|
14784
|
+
backgroundFiles: backgroundMap,
|
|
14785
|
+
background: true
|
|
14786
|
+
};
|
|
14787
|
+
}
|
|
14788
|
+
if (typeof background === "number") {
|
|
14789
|
+
return { color: background, background: true };
|
|
14790
|
+
}
|
|
14791
|
+
if (typeof background === "string") {
|
|
14792
|
+
if (background in presetsObj) {
|
|
14793
|
+
return { preset: background, background: true };
|
|
14794
|
+
}
|
|
14795
|
+
if (/^(https?:\/\/|\/|\.\/|\.\.\/)|\.(hdr|exr|jpg|jpeg|png|webp|gif)$/i.test(background)) {
|
|
14796
|
+
return { files: background, background: true };
|
|
14797
|
+
}
|
|
14798
|
+
return { color: background, background: true };
|
|
14799
|
+
}
|
|
14800
|
+
if (background.isColor) {
|
|
14801
|
+
return { color: background, background: true };
|
|
14802
|
+
}
|
|
14803
|
+
return null;
|
|
14804
|
+
}
|
|
14805
|
+
|
|
14806
|
+
function parseRendererConfig(rendererProp) {
|
|
14807
|
+
const isRendererConfig = typeof rendererProp === "object" && rendererProp !== null && !("render" in rendererProp) && ("primaryCanvas" in rendererProp || "scheduler" in rendererProp);
|
|
14808
|
+
if (isRendererConfig) {
|
|
14809
|
+
const { primaryCanvas: pc, scheduler: sc, ...rest } = rendererProp;
|
|
14810
|
+
return {
|
|
14811
|
+
primaryCanvas: pc,
|
|
14812
|
+
scheduler: sc,
|
|
14813
|
+
renderer: Object.keys(rest).length > 0 ? rest : rendererProp
|
|
14814
|
+
};
|
|
14815
|
+
}
|
|
14816
|
+
return { primaryCanvas: void 0, scheduler: void 0, renderer: rendererProp };
|
|
14817
|
+
}
|
|
14818
|
+
|
|
14819
|
+
const RESOURCE_KINDS = ["nodes", "uniforms", "buffers", "gpuStorage"];
|
|
14820
|
+
const ROOT_SCOPE = "";
|
|
14821
|
+
const registries = /* @__PURE__ */ new WeakMap();
|
|
14822
|
+
function getKindRegistry(store, kind) {
|
|
14823
|
+
let registry = registries.get(store);
|
|
14824
|
+
if (!registry) {
|
|
14825
|
+
registry = {
|
|
14826
|
+
nodes: { valid: /* @__PURE__ */ new Set(), staged: /* @__PURE__ */ new Map() },
|
|
14827
|
+
uniforms: { valid: /* @__PURE__ */ new Set(), staged: /* @__PURE__ */ new Map() },
|
|
14828
|
+
buffers: { valid: /* @__PURE__ */ new Set(), staged: /* @__PURE__ */ new Map() },
|
|
14829
|
+
gpuStorage: { valid: /* @__PURE__ */ new Set(), staged: /* @__PURE__ */ new Map() }
|
|
14830
|
+
};
|
|
14831
|
+
registries.set(store, registry);
|
|
14832
|
+
}
|
|
14833
|
+
return registry[kind];
|
|
14834
|
+
}
|
|
14835
|
+
function resolvePrimary$1(store) {
|
|
14836
|
+
return store.getState().primaryStore ?? store;
|
|
14837
|
+
}
|
|
14838
|
+
function isScopeValid(store, kind, scopeKey) {
|
|
14839
|
+
return getKindRegistry(store, kind).valid.has(scopeKey);
|
|
14840
|
+
}
|
|
14841
|
+
function peekStaged(store, kind, scopeKey) {
|
|
14842
|
+
return getKindRegistry(store, kind).staged.get(scopeKey);
|
|
14843
|
+
}
|
|
14844
|
+
function stageEntries(store, kind, scopeKey, entries) {
|
|
14845
|
+
const { staged } = getKindRegistry(store, kind);
|
|
14846
|
+
const current = staged.get(scopeKey);
|
|
14847
|
+
if (current) Object.assign(current, entries);
|
|
14848
|
+
else staged.set(scopeKey, { ...entries });
|
|
14849
|
+
}
|
|
14850
|
+
function readCommittedScope(map, scopeKey, isLeaf) {
|
|
14851
|
+
if (scopeKey === ROOT_SCOPE) {
|
|
14852
|
+
const leaves = {};
|
|
14853
|
+
for (const [key, value] of Object.entries(map)) if (isLeaf(value)) leaves[key] = value;
|
|
14854
|
+
return leaves;
|
|
14855
|
+
}
|
|
14856
|
+
const scopeData = map[scopeKey];
|
|
14857
|
+
if (scopeData && typeof scopeData === "object" && !isLeaf(scopeData)) return scopeData;
|
|
14858
|
+
return {};
|
|
14859
|
+
}
|
|
14860
|
+
function withStagedOverlay(store, kind, committed) {
|
|
14861
|
+
const { staged } = getKindRegistry(store, kind);
|
|
14862
|
+
if (staged.size === 0) return committed;
|
|
14863
|
+
const view = { ...committed };
|
|
14864
|
+
for (const [scopeKey, entries] of staged) {
|
|
14865
|
+
if (scopeKey === ROOT_SCOPE) Object.assign(view, entries);
|
|
14866
|
+
else view[scopeKey] = { ...view[scopeKey], ...entries };
|
|
14867
|
+
}
|
|
14868
|
+
return view;
|
|
14869
|
+
}
|
|
14870
|
+
function flushStagedScope(store, kind, scopeKey, isLeaf) {
|
|
14871
|
+
const registry = getKindRegistry(store, kind);
|
|
14872
|
+
const staged = registry.staged.get(scopeKey);
|
|
14873
|
+
if (!staged) return;
|
|
14874
|
+
registry.staged.delete(scopeKey);
|
|
14875
|
+
const replace = !registry.valid.has(scopeKey);
|
|
14876
|
+
store.setState((state) => {
|
|
14877
|
+
const map = state[kind];
|
|
14878
|
+
if (scopeKey === ROOT_SCOPE) {
|
|
14879
|
+
if (!replace) return { [kind]: { ...map, ...staged } };
|
|
14880
|
+
const next = {};
|
|
14881
|
+
for (const [key, value] of Object.entries(map)) if (!isLeaf(value)) next[key] = value;
|
|
14882
|
+
return { [kind]: { ...next, ...staged } };
|
|
14883
|
+
}
|
|
14884
|
+
const current = replace ? {} : map[scopeKey] ?? {};
|
|
14885
|
+
return { [kind]: { ...map, [scopeKey]: { ...current, ...staged } } };
|
|
14886
|
+
});
|
|
14887
|
+
registry.valid.add(scopeKey);
|
|
14888
|
+
}
|
|
14889
|
+
function invalidateResource(store, kind, scopeKey) {
|
|
14890
|
+
const registry = getKindRegistry(store, kind);
|
|
14891
|
+
if (scopeKey === void 0) {
|
|
14892
|
+
registry.valid.clear();
|
|
14893
|
+
registry.staged.clear();
|
|
14894
|
+
} else {
|
|
14895
|
+
registry.valid.delete(scopeKey);
|
|
14896
|
+
registry.staged.delete(scopeKey);
|
|
14897
|
+
}
|
|
14898
|
+
}
|
|
14899
|
+
function rebuildResource(store, kind, userScope) {
|
|
14900
|
+
const primary = resolvePrimary$1(store);
|
|
14901
|
+
invalidateResource(primary, kind, userScope === "root" ? ROOT_SCOPE : userScope);
|
|
14902
|
+
primary.setState((state) => ({ _hmrVersion: state._hmrVersion + 1 }));
|
|
14903
|
+
}
|
|
14904
|
+
function invalidateAllResources(store) {
|
|
14905
|
+
for (const kind of RESOURCE_KINDS) invalidateResource(store, kind);
|
|
14906
|
+
}
|
|
14907
|
+
function removeResourceEntries(store, kind, names, scope, isLeaf) {
|
|
14908
|
+
const staged = peekStaged(store, kind, scope ?? ROOT_SCOPE);
|
|
14909
|
+
if (staged) for (const name of names) delete staged[name];
|
|
14910
|
+
store.setState((state) => {
|
|
14911
|
+
const map = state[kind];
|
|
14912
|
+
if (scope) {
|
|
14913
|
+
const currentScope = { ...map[scope] };
|
|
14914
|
+
for (const name of names) delete currentScope[name];
|
|
14915
|
+
return { [kind]: { ...map, [scope]: currentScope } };
|
|
14916
|
+
}
|
|
14917
|
+
const next = { ...map };
|
|
14918
|
+
for (const name of names) if (isLeaf(next[name])) delete next[name];
|
|
14919
|
+
return { [kind]: next };
|
|
14920
|
+
});
|
|
15727
14921
|
}
|
|
15728
|
-
function
|
|
15729
|
-
|
|
14922
|
+
function clearResourceEntries(store, kind, scope, isLeaf) {
|
|
14923
|
+
const registry = getKindRegistry(store, kind);
|
|
14924
|
+
if (scope && scope !== "root") registry.staged.delete(scope);
|
|
14925
|
+
else if (scope === "root") registry.staged.delete(ROOT_SCOPE);
|
|
14926
|
+
else registry.staged.clear();
|
|
14927
|
+
store.setState((state) => {
|
|
14928
|
+
const map = state[kind];
|
|
14929
|
+
if (scope && scope !== "root") {
|
|
14930
|
+
const { [scope]: _, ...rest } = map;
|
|
14931
|
+
return { [kind]: rest };
|
|
14932
|
+
}
|
|
14933
|
+
if (scope === "root") {
|
|
14934
|
+
const next = {};
|
|
14935
|
+
for (const [key, value] of Object.entries(map)) if (!isLeaf(value)) next[key] = value;
|
|
14936
|
+
return { [kind]: next };
|
|
14937
|
+
}
|
|
14938
|
+
return { [kind]: {} };
|
|
14939
|
+
});
|
|
14940
|
+
}
|
|
14941
|
+
|
|
14942
|
+
function clearHmrCaches(store) {
|
|
14943
|
+
const primary = store.getState().primaryStore ?? store;
|
|
14944
|
+
invalidateAllResources(primary);
|
|
14945
|
+
primary.setState((state) => ({ _hmrVersion: state._hmrVersion + 1 }));
|
|
15730
14946
|
}
|
|
15731
14947
|
|
|
15732
14948
|
function CanvasImpl({
|
|
@@ -15749,6 +14965,8 @@ function CanvasImpl({
|
|
|
15749
14965
|
raycaster,
|
|
15750
14966
|
camera,
|
|
15751
14967
|
scene,
|
|
14968
|
+
autoUpdateFrustum,
|
|
14969
|
+
occlusion,
|
|
15752
14970
|
onPointerMissed,
|
|
15753
14971
|
onDragOverMissed,
|
|
15754
14972
|
onDropMissed,
|
|
@@ -15760,39 +14978,10 @@ function CanvasImpl({
|
|
|
15760
14978
|
forceEven,
|
|
15761
14979
|
...props
|
|
15762
14980
|
}) {
|
|
15763
|
-
const { primaryCanvas, scheduler,
|
|
15764
|
-
const renderer = Object.keys(rendererConfig).length > 0 ? rendererConfig : rendererProp;
|
|
14981
|
+
const { primaryCanvas, scheduler, renderer } = parseRendererConfig(rendererProp);
|
|
15765
14982
|
React__namespace.useMemo(() => extend(THREE), []);
|
|
15766
14983
|
const Bridge = useBridge();
|
|
15767
|
-
const backgroundProps = React__namespace.useMemo(() =>
|
|
15768
|
-
if (!background) return null;
|
|
15769
|
-
if (typeof background === "object" && !background.isColor) {
|
|
15770
|
-
const { backgroundMap, envMap, files, preset, ...rest } = background;
|
|
15771
|
-
return {
|
|
15772
|
-
...rest,
|
|
15773
|
-
preset,
|
|
15774
|
-
files: envMap || files,
|
|
15775
|
-
backgroundFiles: backgroundMap,
|
|
15776
|
-
background: true
|
|
15777
|
-
};
|
|
15778
|
-
}
|
|
15779
|
-
if (typeof background === "number") {
|
|
15780
|
-
return { color: background, background: true };
|
|
15781
|
-
}
|
|
15782
|
-
if (typeof background === "string") {
|
|
15783
|
-
if (background in presetsObj) {
|
|
15784
|
-
return { preset: background, background: true };
|
|
15785
|
-
}
|
|
15786
|
-
if (/^(https?:\/\/|\/|\.\/|\.\.\/)|\\.(hdr|exr|jpg|jpeg|png|webp|gif)$/i.test(background)) {
|
|
15787
|
-
return { files: background, background: true };
|
|
15788
|
-
}
|
|
15789
|
-
return { color: background, background: true };
|
|
15790
|
-
}
|
|
15791
|
-
if (background.isColor) {
|
|
15792
|
-
return { color: background, background: true };
|
|
15793
|
-
}
|
|
15794
|
-
return null;
|
|
15795
|
-
}, [background]);
|
|
14984
|
+
const backgroundProps = React__namespace.useMemo(() => parseBackground(background), [background]);
|
|
15796
14985
|
const hasInitialSizeRef = React__namespace.useRef(false);
|
|
15797
14986
|
const measureConfig = React__namespace.useMemo(() => {
|
|
15798
14987
|
if (!hasInitialSizeRef.current) {
|
|
@@ -15834,6 +15023,7 @@ function CanvasImpl({
|
|
|
15834
15023
|
const handleDropMissed = useMutableCallback(onDropMissed);
|
|
15835
15024
|
const [block, setBlock] = React__namespace.useState(false);
|
|
15836
15025
|
const [error, setError] = React__namespace.useState(false);
|
|
15026
|
+
const [fallbackVisible, setFallbackVisible] = React__namespace.useState(false);
|
|
15837
15027
|
if (block) throw block;
|
|
15838
15028
|
if (error) throw error;
|
|
15839
15029
|
const root = React__namespace.useRef(null);
|
|
@@ -15841,6 +15031,7 @@ function CanvasImpl({
|
|
|
15841
15031
|
const unsubscribeErrorRef = React__namespace.useRef(null);
|
|
15842
15032
|
useIsomorphicLayoutEffect(() => {
|
|
15843
15033
|
effectActiveRef.current = true;
|
|
15034
|
+
if (fallbackVisible) return;
|
|
15844
15035
|
const canvas = canvasRef.current;
|
|
15845
15036
|
if (effectiveSize.width > 0 && effectiveSize.height > 0 && canvas) {
|
|
15846
15037
|
if (!root.current) {
|
|
@@ -15861,7 +15052,7 @@ function CanvasImpl({
|
|
|
15861
15052
|
}
|
|
15862
15053
|
async function run() {
|
|
15863
15054
|
if (!effectActiveRef.current || !root.current) return;
|
|
15864
|
-
await root.current.configure({
|
|
15055
|
+
const configured = await root.current.configure({
|
|
15865
15056
|
id,
|
|
15866
15057
|
primaryCanvas,
|
|
15867
15058
|
scheduler,
|
|
@@ -15876,6 +15067,8 @@ function CanvasImpl({
|
|
|
15876
15067
|
performance,
|
|
15877
15068
|
raycaster,
|
|
15878
15069
|
camera,
|
|
15070
|
+
autoUpdateFrustum,
|
|
15071
|
+
occlusion,
|
|
15879
15072
|
size: effectiveSize,
|
|
15880
15073
|
// Store size props for reset functionality
|
|
15881
15074
|
_sizeProps: width !== void 0 || height !== void 0 ? { width, height } : null,
|
|
@@ -15900,8 +15093,20 @@ function CanvasImpl({
|
|
|
15900
15093
|
}
|
|
15901
15094
|
onCreated?.(state);
|
|
15902
15095
|
}
|
|
15903
|
-
})
|
|
15904
|
-
|
|
15096
|
+
}).then(
|
|
15097
|
+
() => true,
|
|
15098
|
+
// Renderer setup failed (e.g. no WebGL/WebGPU support). The `fallback` prop lives
|
|
15099
|
+
// inside <canvas>, which browsers don't display, so surface it as visible DOM
|
|
15100
|
+
// instead; with no fallback, rethrow to an external error boundary. (#3757)
|
|
15101
|
+
(setupError) => {
|
|
15102
|
+
if (effectActiveRef.current) {
|
|
15103
|
+
if (fallback != null) setFallbackVisible(true);
|
|
15104
|
+
else setError(setupError);
|
|
15105
|
+
}
|
|
15106
|
+
return false;
|
|
15107
|
+
}
|
|
15108
|
+
);
|
|
15109
|
+
if (!configured || !effectActiveRef.current || !root.current) return;
|
|
15905
15110
|
root.current.render(
|
|
15906
15111
|
/* @__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: [
|
|
15907
15112
|
backgroundProps && /* @__PURE__ */ jsxRuntime.jsx(Environment, { ...backgroundProps }),
|
|
@@ -15935,14 +15140,7 @@ function CanvasImpl({
|
|
|
15935
15140
|
const handleHMR = () => {
|
|
15936
15141
|
queueMicrotask(() => {
|
|
15937
15142
|
const rootEntry = _roots.get(canvas);
|
|
15938
|
-
if (rootEntry?.store)
|
|
15939
|
-
console.log("[R3F] HMR detected \u2014 rebuilding nodes/uniforms");
|
|
15940
|
-
rootEntry.store.setState((state) => ({
|
|
15941
|
-
nodes: {},
|
|
15942
|
-
uniforms: {},
|
|
15943
|
-
_hmrVersion: state._hmrVersion + 1
|
|
15944
|
-
}));
|
|
15945
|
-
}
|
|
15143
|
+
if (rootEntry?.store) clearHmrCaches(rootEntry.store);
|
|
15946
15144
|
});
|
|
15947
15145
|
};
|
|
15948
15146
|
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) {
|
|
@@ -15971,7 +15169,20 @@ function CanvasImpl({
|
|
|
15971
15169
|
...style
|
|
15972
15170
|
},
|
|
15973
15171
|
...props,
|
|
15974
|
-
children:
|
|
15172
|
+
children: fallbackVisible ? (
|
|
15173
|
+
// Renderer setup failed: render the fallback as visible DOM. Inside <canvas> (below)
|
|
15174
|
+
// it exists in the tree but browsers never paint it, which is the whole bug (#3757).
|
|
15175
|
+
fallback
|
|
15176
|
+
) : /* @__PURE__ */ jsxRuntime.jsx("div", { ref: containerRef, className: "r3f-canvas-container", style: { width: "100%", height: "100%" }, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
15177
|
+
"canvas",
|
|
15178
|
+
{
|
|
15179
|
+
ref: canvasRef,
|
|
15180
|
+
id,
|
|
15181
|
+
className: "r3f-canvas",
|
|
15182
|
+
style: { display: "block", width: "100%", height: "100%" },
|
|
15183
|
+
children: fallback
|
|
15184
|
+
}
|
|
15185
|
+
) })
|
|
15975
15186
|
}
|
|
15976
15187
|
);
|
|
15977
15188
|
}
|
|
@@ -16044,35 +15255,50 @@ let ScopedStore = _ScopedStore;
|
|
|
16044
15255
|
function createScopedStore(data) {
|
|
16045
15256
|
return new ScopedStore(data);
|
|
16046
15257
|
}
|
|
16047
|
-
function createLazyCreatorState(state) {
|
|
15258
|
+
function createLazyCreatorState(state, store) {
|
|
16048
15259
|
let _uniforms = null;
|
|
16049
15260
|
let _nodes = null;
|
|
16050
15261
|
let _buffers = null;
|
|
16051
15262
|
let _gpuStorage = null;
|
|
15263
|
+
const view = (kind) => store ? withStagedOverlay(store, kind, state[kind]) : state[kind];
|
|
16052
15264
|
return Object.create(state, {
|
|
16053
15265
|
uniforms: {
|
|
16054
15266
|
get() {
|
|
16055
|
-
return _uniforms ?? (_uniforms = createScopedStore(
|
|
15267
|
+
return _uniforms ?? (_uniforms = createScopedStore(view("uniforms")));
|
|
16056
15268
|
}
|
|
16057
15269
|
},
|
|
16058
15270
|
nodes: {
|
|
16059
15271
|
get() {
|
|
16060
|
-
return _nodes ?? (_nodes = createScopedStore(
|
|
15272
|
+
return _nodes ?? (_nodes = createScopedStore(view("nodes")));
|
|
16061
15273
|
}
|
|
16062
15274
|
},
|
|
16063
15275
|
buffers: {
|
|
16064
15276
|
get() {
|
|
16065
|
-
return _buffers ?? (_buffers = createScopedStore(
|
|
15277
|
+
return _buffers ?? (_buffers = createScopedStore(view("buffers")));
|
|
16066
15278
|
}
|
|
16067
15279
|
},
|
|
16068
15280
|
gpuStorage: {
|
|
16069
15281
|
get() {
|
|
16070
|
-
return _gpuStorage ?? (_gpuStorage = createScopedStore(
|
|
15282
|
+
return _gpuStorage ?? (_gpuStorage = createScopedStore(view("gpuStorage")));
|
|
16071
15283
|
}
|
|
16072
15284
|
}
|
|
16073
15285
|
});
|
|
16074
15286
|
}
|
|
16075
15287
|
|
|
15288
|
+
function resolvePrimary(local) {
|
|
15289
|
+
return local.getState().primaryStore ?? local;
|
|
15290
|
+
}
|
|
15291
|
+
function usePrimaryStore() {
|
|
15292
|
+
const local = useStore();
|
|
15293
|
+
const primary = local.getState().primaryStore;
|
|
15294
|
+
return React.useMemo(() => primary ?? local, [primary, local]);
|
|
15295
|
+
}
|
|
15296
|
+
function usePrimaryThree(selector = (state) => state, equalityFn) {
|
|
15297
|
+
const local = useStore();
|
|
15298
|
+
const primary = resolvePrimary(local);
|
|
15299
|
+
return primary(selector, equalityFn);
|
|
15300
|
+
}
|
|
15301
|
+
|
|
16076
15302
|
function addTexture(set, key, value) {
|
|
16077
15303
|
set((state) => {
|
|
16078
15304
|
const newMap = new Map(state.textures);
|
|
@@ -16176,65 +15402,56 @@ function useCompareMemoize(value, deep) {
|
|
|
16176
15402
|
return ref.current;
|
|
16177
15403
|
}
|
|
16178
15404
|
|
|
15405
|
+
function useScopedResource(options) {
|
|
15406
|
+
const { store, kind, scope, isLeaf, create, prepare, reconcile, input } = options;
|
|
15407
|
+
const scopeKey = scope ?? ROOT_SCOPE;
|
|
15408
|
+
const version = store((s) => s._hmrVersion);
|
|
15409
|
+
const entries = React.useMemo(() => {
|
|
15410
|
+
const staged = peekStaged(store, kind, scopeKey);
|
|
15411
|
+
const reusable = isScopeValid(store, kind, scopeKey);
|
|
15412
|
+
const map = store.getState()[kind];
|
|
15413
|
+
const committed = reusable ? readCommittedScope(map, scopeKey, isLeaf) : void 0;
|
|
15414
|
+
const result = {};
|
|
15415
|
+
let fresh = null;
|
|
15416
|
+
for (const [name, candidate] of Object.entries(create())) {
|
|
15417
|
+
const existing = staged?.[name] ?? committed?.[name];
|
|
15418
|
+
if (existing !== void 0) {
|
|
15419
|
+
result[name] = existing;
|
|
15420
|
+
reconcile?.(existing, candidate);
|
|
15421
|
+
} else {
|
|
15422
|
+
const value = prepare ? prepare(name, candidate) : candidate;
|
|
15423
|
+
result[name] = value;
|
|
15424
|
+
(fresh ?? (fresh = {}))[name] = value;
|
|
15425
|
+
}
|
|
15426
|
+
}
|
|
15427
|
+
if (fresh) stageEntries(store, kind, scopeKey, fresh);
|
|
15428
|
+
return result;
|
|
15429
|
+
}, [store, kind, scopeKey, version, input]);
|
|
15430
|
+
useIsomorphicLayoutEffect(() => {
|
|
15431
|
+
flushStagedScope(store, kind, scopeKey, isLeaf);
|
|
15432
|
+
});
|
|
15433
|
+
return entries;
|
|
15434
|
+
}
|
|
15435
|
+
|
|
16179
15436
|
const isUniformNode$1 = (value) => value !== null && typeof value === "object" && "value" in value && "uuid" in value;
|
|
16180
15437
|
function useUniforms(creatorOrScope, scope) {
|
|
16181
|
-
const store =
|
|
15438
|
+
const store = usePrimaryStore();
|
|
16182
15439
|
const removeUniforms2 = React.useCallback(
|
|
16183
|
-
(names, targetScope) =>
|
|
16184
|
-
const nameArray = Array.isArray(names) ? names : [names];
|
|
16185
|
-
store.setState((state) => {
|
|
16186
|
-
if (targetScope) {
|
|
16187
|
-
const currentScope = { ...state.uniforms[targetScope] };
|
|
16188
|
-
for (const name of nameArray) delete currentScope[name];
|
|
16189
|
-
return { uniforms: { ...state.uniforms, [targetScope]: currentScope } };
|
|
16190
|
-
}
|
|
16191
|
-
const uniforms2 = { ...state.uniforms };
|
|
16192
|
-
for (const name of nameArray) if (isUniformNode$1(uniforms2[name])) delete uniforms2[name];
|
|
16193
|
-
return { uniforms: uniforms2 };
|
|
16194
|
-
});
|
|
16195
|
-
},
|
|
15440
|
+
(names, targetScope) => removeResourceEntries(store, "uniforms", Array.isArray(names) ? names : [names], targetScope, isUniformNode$1),
|
|
16196
15441
|
[store]
|
|
16197
15442
|
);
|
|
16198
15443
|
const clearUniforms = React.useCallback(
|
|
16199
|
-
(targetScope) =>
|
|
16200
|
-
store.setState((state) => {
|
|
16201
|
-
if (targetScope && targetScope !== "root") {
|
|
16202
|
-
const { [targetScope]: _, ...rest } = state.uniforms;
|
|
16203
|
-
return { uniforms: rest };
|
|
16204
|
-
}
|
|
16205
|
-
if (targetScope === "root") {
|
|
16206
|
-
const uniforms2 = {};
|
|
16207
|
-
for (const [key, value] of Object.entries(state.uniforms)) {
|
|
16208
|
-
if (!isUniformNode$1(value)) uniforms2[key] = value;
|
|
16209
|
-
}
|
|
16210
|
-
return { uniforms: uniforms2 };
|
|
16211
|
-
}
|
|
16212
|
-
return { uniforms: {} };
|
|
16213
|
-
});
|
|
16214
|
-
},
|
|
15444
|
+
(targetScope) => clearResourceEntries(store, "uniforms", targetScope, isUniformNode$1),
|
|
16215
15445
|
[store]
|
|
16216
15446
|
);
|
|
16217
15447
|
const rebuildUniforms = React.useCallback(
|
|
16218
|
-
(targetScope) =>
|
|
16219
|
-
store.setState((state) => {
|
|
16220
|
-
let newUniforms = {};
|
|
16221
|
-
if (targetScope && targetScope !== "root") {
|
|
16222
|
-
const { [targetScope]: _, ...rest } = state.uniforms;
|
|
16223
|
-
newUniforms = rest;
|
|
16224
|
-
} else if (targetScope === "root") {
|
|
16225
|
-
for (const [key, value] of Object.entries(state.uniforms)) {
|
|
16226
|
-
if (!isUniformNode$1(value)) newUniforms[key] = value;
|
|
16227
|
-
}
|
|
16228
|
-
}
|
|
16229
|
-
return { uniforms: newUniforms, _hmrVersion: state._hmrVersion + 1 };
|
|
16230
|
-
});
|
|
16231
|
-
},
|
|
15448
|
+
(targetScope) => rebuildResource(store, "uniforms", targetScope),
|
|
16232
15449
|
[store]
|
|
16233
15450
|
);
|
|
16234
15451
|
const inputForMemoization = React.useMemo(() => {
|
|
16235
15452
|
let raw = creatorOrScope;
|
|
16236
15453
|
if (is.fun(creatorOrScope)) {
|
|
16237
|
-
const wrappedState = createLazyCreatorState(store.getState());
|
|
15454
|
+
const wrappedState = createLazyCreatorState(store.getState(), store);
|
|
16238
15455
|
raw = creatorOrScope(wrappedState);
|
|
16239
15456
|
}
|
|
16240
15457
|
if (raw && typeof raw === "object" && !Array.isArray(raw)) {
|
|
@@ -16248,80 +15465,44 @@ function useUniforms(creatorOrScope, scope) {
|
|
|
16248
15465
|
}, [creatorOrScope, store]);
|
|
16249
15466
|
const memoizedInput = useCompareMemoize(inputForMemoization);
|
|
16250
15467
|
const isReader = memoizedInput === void 0 || typeof memoizedInput === "string";
|
|
16251
|
-
const storeUniforms =
|
|
16252
|
-
const
|
|
16253
|
-
|
|
16254
|
-
|
|
16255
|
-
|
|
16256
|
-
|
|
16257
|
-
|
|
16258
|
-
|
|
16259
|
-
|
|
16260
|
-
|
|
16261
|
-
|
|
16262
|
-
|
|
16263
|
-
|
|
16264
|
-
|
|
16265
|
-
|
|
16266
|
-
|
|
16267
|
-
|
|
16268
|
-
|
|
16269
|
-
|
|
16270
|
-
|
|
16271
|
-
|
|
16272
|
-
|
|
16273
|
-
if (scope) {
|
|
16274
|
-
if (!state.uniforms[scope]) state.uniforms[scope] = {};
|
|
16275
|
-
targetRecord = state.uniforms[scope];
|
|
16276
|
-
}
|
|
16277
|
-
for (const [name, node] of Object.entries(created)) {
|
|
16278
|
-
if (targetRecord[name]) {
|
|
16279
|
-
result[name] = targetRecord[name];
|
|
16280
|
-
const existingVal = result[name].value;
|
|
16281
|
-
const newVal = vectorize(node);
|
|
16282
|
-
let equals = newVal === existingVal;
|
|
16283
|
-
if (!equals && hasEqualsMethod(existingVal) && hasEqualsMethod(newVal)) {
|
|
16284
|
-
if (isThreeVector(existingVal) && isThreeVector(newVal)) {
|
|
16285
|
-
equals = vectorEquals(existingVal, newVal);
|
|
16286
|
-
} else if (isSameThreeType(existingVal, newVal)) {
|
|
16287
|
-
equals = existingVal.equals(newVal);
|
|
16288
|
-
}
|
|
15468
|
+
const storeUniforms = usePrimaryThree((s) => s.uniforms);
|
|
15469
|
+
const created = useScopedResource({
|
|
15470
|
+
store,
|
|
15471
|
+
kind: "uniforms",
|
|
15472
|
+
scope: isReader ? void 0 : scope,
|
|
15473
|
+
isLeaf: isUniformNode$1,
|
|
15474
|
+
input: memoizedInput,
|
|
15475
|
+
create: () => {
|
|
15476
|
+
if (isReader) return {};
|
|
15477
|
+
if (typeof memoizedInput !== "object" || memoizedInput === null) throw new Error("Invalid uniform input");
|
|
15478
|
+
return memoizedInput;
|
|
15479
|
+
},
|
|
15480
|
+
prepare: (name, candidate) => createUniform(name, candidate, scope),
|
|
15481
|
+
reconcile: (existing, candidate) => {
|
|
15482
|
+
const existingVal = existing.value;
|
|
15483
|
+
const newVal = vectorize(candidate);
|
|
15484
|
+
let equals = newVal === existingVal;
|
|
15485
|
+
if (!equals && hasEqualsMethod(existingVal) && hasEqualsMethod(newVal)) {
|
|
15486
|
+
if (isThreeVector(existingVal) && isThreeVector(newVal)) {
|
|
15487
|
+
equals = vectorEquals(existingVal, newVal);
|
|
15488
|
+
} else if (isSameThreeType(existingVal, newVal)) {
|
|
15489
|
+
equals = existingVal.equals(newVal);
|
|
16289
15490
|
}
|
|
16290
|
-
if (!equals) result[name].value = newVal;
|
|
16291
|
-
} else {
|
|
16292
|
-
result[name] = createUniform(name, node, scope);
|
|
16293
|
-
hasNewUniforms = true;
|
|
16294
|
-
}
|
|
16295
|
-
}
|
|
16296
|
-
if (hasNewUniforms) {
|
|
16297
|
-
if (scope) {
|
|
16298
|
-
set((s) => ({
|
|
16299
|
-
uniforms: {
|
|
16300
|
-
...s.uniforms,
|
|
16301
|
-
[scope]: { ...s.uniforms[scope], ...result }
|
|
16302
|
-
}
|
|
16303
|
-
}));
|
|
16304
|
-
} else {
|
|
16305
|
-
set((s) => ({ uniforms: { ...s.uniforms, ...result } }));
|
|
16306
15491
|
}
|
|
15492
|
+
if (!equals) existing.value = newVal;
|
|
16307
15493
|
}
|
|
16308
|
-
|
|
16309
|
-
|
|
15494
|
+
});
|
|
15495
|
+
let uniforms = created;
|
|
15496
|
+
if (memoizedInput === void 0) {
|
|
15497
|
+
uniforms = storeUniforms;
|
|
15498
|
+
} else if (typeof memoizedInput === "string") {
|
|
15499
|
+
const scopeData = storeUniforms[memoizedInput];
|
|
15500
|
+
uniforms = scopeData && !isUniformNode$1(scopeData) ? scopeData : {};
|
|
15501
|
+
}
|
|
16310
15502
|
return { ...uniforms, removeUniforms: removeUniforms2, clearUniforms, rebuildUniforms };
|
|
16311
15503
|
}
|
|
16312
15504
|
function rebuildAllUniforms(store, scope) {
|
|
16313
|
-
store
|
|
16314
|
-
let newUniforms = {};
|
|
16315
|
-
if (scope && scope !== "root") {
|
|
16316
|
-
const { [scope]: _, ...rest } = state.uniforms;
|
|
16317
|
-
newUniforms = rest;
|
|
16318
|
-
} else if (scope === "root") {
|
|
16319
|
-
for (const [key, value] of Object.entries(state.uniforms)) {
|
|
16320
|
-
if (!isUniformNode$1(value)) newUniforms[key] = value;
|
|
16321
|
-
}
|
|
16322
|
-
}
|
|
16323
|
-
return { uniforms: newUniforms, _hmrVersion: state._hmrVersion + 1 };
|
|
16324
|
-
});
|
|
15505
|
+
rebuildResource(store, "uniforms", scope);
|
|
16325
15506
|
}
|
|
16326
15507
|
function removeUniforms(set, names, scope) {
|
|
16327
15508
|
set((state) => {
|
|
@@ -16385,191 +15566,91 @@ function isSameThreeType(a, b) {
|
|
|
16385
15566
|
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;
|
|
16386
15567
|
}
|
|
16387
15568
|
|
|
15569
|
+
const uniformOf = tsl.uniform;
|
|
16388
15570
|
const isUniformNode = (value) => value !== null && typeof value === "object" && "value" in value && "uuid" in value;
|
|
16389
15571
|
const isTSLNode$1 = (value) => value !== null && typeof value === "object" && "uuid" in value && "nodeType" in value;
|
|
16390
15572
|
function useUniform(name, value) {
|
|
16391
15573
|
const store = useStore();
|
|
16392
|
-
const
|
|
16393
|
-
|
|
16394
|
-
|
|
16395
|
-
|
|
16396
|
-
|
|
16397
|
-
|
|
15574
|
+
const registered = useScopedResource({
|
|
15575
|
+
store,
|
|
15576
|
+
kind: "uniforms",
|
|
15577
|
+
scope: void 0,
|
|
15578
|
+
isLeaf: isUniformNode,
|
|
15579
|
+
input: name,
|
|
15580
|
+
create: () => value === void 0 ? {} : { [name]: value },
|
|
15581
|
+
prepare: createNamedUniform,
|
|
15582
|
+
reconcile: (existing2) => {
|
|
16398
15583
|
if (value !== void 0 && !isTSLNode$1(value) && !isUniformNode(value)) {
|
|
16399
|
-
|
|
15584
|
+
existing2.value = typeof value === "string" ? new webgpu.Color(value) : value;
|
|
16400
15585
|
}
|
|
16401
|
-
return existing;
|
|
16402
|
-
}
|
|
16403
|
-
if (value === void 0) {
|
|
16404
|
-
throw new Error(
|
|
16405
|
-
`[useUniform] Uniform "${name}" not found. Create it first with: useUniform('${name}', initialValue)`
|
|
16406
|
-
);
|
|
16407
|
-
}
|
|
16408
|
-
if (isUniformNode(value)) {
|
|
16409
|
-
const node2 = value;
|
|
16410
|
-
if (typeof node2.setName === "function") {
|
|
16411
|
-
node2.setName(name);
|
|
16412
|
-
}
|
|
16413
|
-
set((s) => ({
|
|
16414
|
-
uniforms: { ...s.uniforms, [name]: node2 }
|
|
16415
|
-
}));
|
|
16416
|
-
return node2;
|
|
16417
|
-
}
|
|
16418
|
-
let node;
|
|
16419
|
-
if (isTSLNode$1(value)) {
|
|
16420
|
-
node = tsl.uniform(value);
|
|
16421
|
-
} else if (typeof value === "string") {
|
|
16422
|
-
node = tsl.uniform(new webgpu.Color(value));
|
|
16423
|
-
} else {
|
|
16424
|
-
node = tsl.uniform(value);
|
|
16425
|
-
}
|
|
16426
|
-
if (typeof node.setName === "function") {
|
|
16427
|
-
node.setName(name);
|
|
16428
15586
|
}
|
|
16429
|
-
|
|
16430
|
-
|
|
16431
|
-
|
|
16432
|
-
|
|
16433
|
-
|
|
16434
|
-
})
|
|
16435
|
-
|
|
16436
|
-
|
|
15587
|
+
});
|
|
15588
|
+
if (registered[name]) return registered[name];
|
|
15589
|
+
const existing = peekStaged(store, "uniforms", ROOT_SCOPE)?.[name] ?? store.getState().uniforms[name];
|
|
15590
|
+
if (existing && isUniformNode(existing)) return existing;
|
|
15591
|
+
throw new Error(
|
|
15592
|
+
`[useUniform] Uniform "${name}" not found. Create it first with: useUniform('${name}', initialValue)`
|
|
15593
|
+
);
|
|
15594
|
+
}
|
|
15595
|
+
function createNamedUniform(name, value) {
|
|
15596
|
+
let node;
|
|
15597
|
+
if (isUniformNode(value)) {
|
|
15598
|
+
node = value;
|
|
15599
|
+
} else if (isTSLNode$1(value)) {
|
|
15600
|
+
node = uniformOf(value);
|
|
15601
|
+
} else if (typeof value === "string") {
|
|
15602
|
+
node = tsl.uniform(new webgpu.Color(value));
|
|
15603
|
+
} else {
|
|
15604
|
+
node = uniformOf(value);
|
|
15605
|
+
}
|
|
15606
|
+
if (typeof node.setName === "function") {
|
|
15607
|
+
node.setName(name);
|
|
15608
|
+
}
|
|
15609
|
+
return node;
|
|
16437
15610
|
}
|
|
16438
15611
|
|
|
16439
15612
|
const isTSLNode = (value) => value !== null && typeof value === "object" && ("uuid" in value || "nodeType" in value);
|
|
16440
15613
|
function useNodes(creatorOrScope, scope) {
|
|
16441
|
-
const store =
|
|
15614
|
+
const store = usePrimaryStore();
|
|
16442
15615
|
const removeNodes2 = React.useCallback(
|
|
16443
|
-
(names, targetScope) =>
|
|
16444
|
-
const nameArray = Array.isArray(names) ? names : [names];
|
|
16445
|
-
store.setState((state) => {
|
|
16446
|
-
if (targetScope) {
|
|
16447
|
-
const currentScope = { ...state.nodes[targetScope] };
|
|
16448
|
-
for (const name of nameArray) delete currentScope[name];
|
|
16449
|
-
return { nodes: { ...state.nodes, [targetScope]: currentScope } };
|
|
16450
|
-
}
|
|
16451
|
-
const nodes2 = { ...state.nodes };
|
|
16452
|
-
for (const name of nameArray) if (isTSLNode(nodes2[name])) delete nodes2[name];
|
|
16453
|
-
return { nodes: nodes2 };
|
|
16454
|
-
});
|
|
16455
|
-
},
|
|
15616
|
+
(names, targetScope) => removeResourceEntries(store, "nodes", Array.isArray(names) ? names : [names], targetScope, isTSLNode),
|
|
16456
15617
|
[store]
|
|
16457
15618
|
);
|
|
16458
15619
|
const clearNodes = React.useCallback(
|
|
16459
|
-
(targetScope) =>
|
|
16460
|
-
store.setState((state) => {
|
|
16461
|
-
if (targetScope && targetScope !== "root") {
|
|
16462
|
-
const { [targetScope]: _, ...rest } = state.nodes;
|
|
16463
|
-
return { nodes: rest };
|
|
16464
|
-
}
|
|
16465
|
-
if (targetScope === "root") {
|
|
16466
|
-
const nodes2 = {};
|
|
16467
|
-
for (const [key, value] of Object.entries(state.nodes)) {
|
|
16468
|
-
if (!isTSLNode(value)) nodes2[key] = value;
|
|
16469
|
-
}
|
|
16470
|
-
return { nodes: nodes2 };
|
|
16471
|
-
}
|
|
16472
|
-
return { nodes: {} };
|
|
16473
|
-
});
|
|
16474
|
-
},
|
|
15620
|
+
(targetScope) => clearResourceEntries(store, "nodes", targetScope, isTSLNode),
|
|
16475
15621
|
[store]
|
|
16476
15622
|
);
|
|
16477
15623
|
const rebuildNodes = React.useCallback(
|
|
16478
|
-
(targetScope) =>
|
|
16479
|
-
store.setState((state) => {
|
|
16480
|
-
let newNodes = state.nodes;
|
|
16481
|
-
if (targetScope && targetScope !== "root") {
|
|
16482
|
-
const { [targetScope]: _, ...rest } = state.nodes;
|
|
16483
|
-
newNodes = rest;
|
|
16484
|
-
} else if (targetScope === "root") {
|
|
16485
|
-
newNodes = {};
|
|
16486
|
-
for (const [key, value] of Object.entries(state.nodes)) {
|
|
16487
|
-
if (!isTSLNode(value)) newNodes[key] = value;
|
|
16488
|
-
}
|
|
16489
|
-
} else {
|
|
16490
|
-
newNodes = {};
|
|
16491
|
-
}
|
|
16492
|
-
return { nodes: newNodes, _hmrVersion: state._hmrVersion + 1 };
|
|
16493
|
-
});
|
|
16494
|
-
},
|
|
15624
|
+
(targetScope) => rebuildResource(store, "nodes", targetScope),
|
|
16495
15625
|
[store]
|
|
16496
15626
|
);
|
|
16497
15627
|
const isReader = creatorOrScope === void 0 || typeof creatorOrScope === "string";
|
|
16498
|
-
const storeNodes =
|
|
16499
|
-
const
|
|
16500
|
-
|
|
16501
|
-
|
|
16502
|
-
|
|
16503
|
-
|
|
16504
|
-
|
|
16505
|
-
return
|
|
16506
|
-
|
|
16507
|
-
|
|
16508
|
-
|
|
16509
|
-
|
|
16510
|
-
return
|
|
16511
|
-
}
|
|
16512
|
-
const state = store.getState();
|
|
16513
|
-
const set = store.setState;
|
|
16514
|
-
const creator = creatorOrScope;
|
|
16515
|
-
const wrappedState = createLazyCreatorState(state);
|
|
16516
|
-
const created = creator(wrappedState);
|
|
16517
|
-
const result = {};
|
|
16518
|
-
let hasNewNodes = false;
|
|
16519
|
-
if (scope) {
|
|
16520
|
-
const currentScope = state.nodes[scope] ?? {};
|
|
16521
|
-
for (const [name, node] of Object.entries(created)) {
|
|
16522
|
-
if (currentScope[name]) {
|
|
16523
|
-
result[name] = currentScope[name];
|
|
16524
|
-
} else {
|
|
16525
|
-
node.setName?.(`${scope}.${name}`);
|
|
16526
|
-
result[name] = node;
|
|
16527
|
-
hasNewNodes = true;
|
|
16528
|
-
}
|
|
16529
|
-
}
|
|
16530
|
-
if (hasNewNodes) {
|
|
16531
|
-
set((s) => ({
|
|
16532
|
-
nodes: {
|
|
16533
|
-
...s.nodes,
|
|
16534
|
-
[scope]: { ...s.nodes[scope], ...result }
|
|
16535
|
-
}
|
|
16536
|
-
}));
|
|
16537
|
-
}
|
|
16538
|
-
return result;
|
|
16539
|
-
}
|
|
16540
|
-
for (const [name, node] of Object.entries(created)) {
|
|
16541
|
-
const existing = state.nodes[name];
|
|
16542
|
-
if (existing && isTSLNode(existing)) {
|
|
16543
|
-
result[name] = existing;
|
|
16544
|
-
} else {
|
|
16545
|
-
node.setName?.(name);
|
|
16546
|
-
result[name] = node;
|
|
16547
|
-
hasNewNodes = true;
|
|
16548
|
-
}
|
|
16549
|
-
}
|
|
16550
|
-
if (hasNewNodes) {
|
|
16551
|
-
set((s) => ({ nodes: { ...s.nodes, ...result } }));
|
|
15628
|
+
const storeNodes = usePrimaryThree((s) => s.nodes);
|
|
15629
|
+
const created = useScopedResource({
|
|
15630
|
+
store,
|
|
15631
|
+
kind: "nodes",
|
|
15632
|
+
scope: isReader ? void 0 : scope,
|
|
15633
|
+
isLeaf: isTSLNode,
|
|
15634
|
+
create: () => {
|
|
15635
|
+
if (isReader) return {};
|
|
15636
|
+
return creatorOrScope(createLazyCreatorState(store.getState(), store));
|
|
15637
|
+
},
|
|
15638
|
+
prepare: (name, node) => {
|
|
15639
|
+
node.setName?.(scope ? `${scope}.${name}` : name);
|
|
15640
|
+
return node;
|
|
16552
15641
|
}
|
|
16553
|
-
|
|
16554
|
-
|
|
15642
|
+
});
|
|
15643
|
+
let nodes = created;
|
|
15644
|
+
if (creatorOrScope === void 0) {
|
|
15645
|
+
nodes = storeNodes;
|
|
15646
|
+
} else if (typeof creatorOrScope === "string") {
|
|
15647
|
+
const scopeData = storeNodes[creatorOrScope];
|
|
15648
|
+
nodes = scopeData && !isTSLNode(scopeData) ? scopeData : {};
|
|
15649
|
+
}
|
|
16555
15650
|
return { ...nodes, removeNodes: removeNodes2, clearNodes, rebuildNodes };
|
|
16556
15651
|
}
|
|
16557
15652
|
function rebuildAllNodes(store, scope) {
|
|
16558
|
-
store
|
|
16559
|
-
let newNodes = state.nodes;
|
|
16560
|
-
if (scope && scope !== "root") {
|
|
16561
|
-
const { [scope]: _, ...rest } = state.nodes;
|
|
16562
|
-
newNodes = rest;
|
|
16563
|
-
} else if (scope === "root") {
|
|
16564
|
-
newNodes = {};
|
|
16565
|
-
for (const [key, value] of Object.entries(state.nodes)) {
|
|
16566
|
-
if (!isTSLNode(value)) newNodes[key] = value;
|
|
16567
|
-
}
|
|
16568
|
-
} else {
|
|
16569
|
-
newNodes = {};
|
|
16570
|
-
}
|
|
16571
|
-
return { nodes: newNodes, _hmrVersion: state._hmrVersion + 1 };
|
|
16572
|
-
});
|
|
15653
|
+
rebuildResource(store, "nodes", scope);
|
|
16573
15654
|
}
|
|
16574
15655
|
function removeNodes(set, names, scope) {
|
|
16575
15656
|
set((state) => {
|
|
@@ -16599,13 +15680,13 @@ function clearRootNodes(set) {
|
|
|
16599
15680
|
});
|
|
16600
15681
|
}
|
|
16601
15682
|
function useLocalNodes(creator) {
|
|
16602
|
-
const store =
|
|
16603
|
-
const uniforms =
|
|
16604
|
-
const nodes =
|
|
16605
|
-
const textures =
|
|
16606
|
-
const hmrVersion =
|
|
15683
|
+
const store = usePrimaryStore();
|
|
15684
|
+
const uniforms = usePrimaryThree((s) => s.uniforms);
|
|
15685
|
+
const nodes = usePrimaryThree((s) => s.nodes);
|
|
15686
|
+
const textures = usePrimaryThree((s) => s.textures);
|
|
15687
|
+
const hmrVersion = usePrimaryThree((s) => s._hmrVersion);
|
|
16607
15688
|
return React.useMemo(() => {
|
|
16608
|
-
const wrappedState = createLazyCreatorState(store.getState());
|
|
15689
|
+
const wrappedState = createLazyCreatorState(store.getState(), store);
|
|
16609
15690
|
return creator(wrappedState);
|
|
16610
15691
|
}, [store, creator, uniforms, nodes, textures, hmrVersion]);
|
|
16611
15692
|
}
|
|
@@ -16624,60 +15705,17 @@ const disposeBuffer = (buffer) => {
|
|
|
16624
15705
|
}
|
|
16625
15706
|
};
|
|
16626
15707
|
function useBuffers(creatorOrScope, scope) {
|
|
16627
|
-
const store =
|
|
15708
|
+
const store = usePrimaryStore();
|
|
16628
15709
|
const removeBuffers = React.useCallback(
|
|
16629
|
-
(names, targetScope) =>
|
|
16630
|
-
const nameArray = Array.isArray(names) ? names : [names];
|
|
16631
|
-
store.setState((state) => {
|
|
16632
|
-
if (targetScope) {
|
|
16633
|
-
const currentScope = { ...state.buffers[targetScope] };
|
|
16634
|
-
for (const name of nameArray) delete currentScope[name];
|
|
16635
|
-
return { buffers: { ...state.buffers, [targetScope]: currentScope } };
|
|
16636
|
-
}
|
|
16637
|
-
const buffers2 = { ...state.buffers };
|
|
16638
|
-
for (const name of nameArray) if (isBufferLike(buffers2[name])) delete buffers2[name];
|
|
16639
|
-
return { buffers: buffers2 };
|
|
16640
|
-
});
|
|
16641
|
-
},
|
|
15710
|
+
(names, targetScope) => removeResourceEntries(store, "buffers", Array.isArray(names) ? names : [names], targetScope, isBufferLike),
|
|
16642
15711
|
[store]
|
|
16643
15712
|
);
|
|
16644
15713
|
const clearBuffers = React.useCallback(
|
|
16645
|
-
(targetScope) =>
|
|
16646
|
-
store.setState((state) => {
|
|
16647
|
-
if (targetScope && targetScope !== "root") {
|
|
16648
|
-
const { [targetScope]: _, ...rest } = state.buffers;
|
|
16649
|
-
return { buffers: rest };
|
|
16650
|
-
}
|
|
16651
|
-
if (targetScope === "root") {
|
|
16652
|
-
const buffers2 = {};
|
|
16653
|
-
for (const [key, value] of Object.entries(state.buffers)) {
|
|
16654
|
-
if (!isBufferLike(value)) buffers2[key] = value;
|
|
16655
|
-
}
|
|
16656
|
-
return { buffers: buffers2 };
|
|
16657
|
-
}
|
|
16658
|
-
return { buffers: {} };
|
|
16659
|
-
});
|
|
16660
|
-
},
|
|
15714
|
+
(targetScope) => clearResourceEntries(store, "buffers", targetScope, isBufferLike),
|
|
16661
15715
|
[store]
|
|
16662
15716
|
);
|
|
16663
15717
|
const rebuildBuffers = React.useCallback(
|
|
16664
|
-
(targetScope) =>
|
|
16665
|
-
store.setState((state) => {
|
|
16666
|
-
let newBuffers = state.buffers;
|
|
16667
|
-
if (targetScope && targetScope !== "root") {
|
|
16668
|
-
const { [targetScope]: _, ...rest } = state.buffers;
|
|
16669
|
-
newBuffers = rest;
|
|
16670
|
-
} else if (targetScope === "root") {
|
|
16671
|
-
newBuffers = {};
|
|
16672
|
-
for (const [key, value] of Object.entries(state.buffers)) {
|
|
16673
|
-
if (!isBufferLike(value)) newBuffers[key] = value;
|
|
16674
|
-
}
|
|
16675
|
-
} else {
|
|
16676
|
-
newBuffers = {};
|
|
16677
|
-
}
|
|
16678
|
-
return { buffers: newBuffers, _hmrVersion: state._hmrVersion + 1 };
|
|
16679
|
-
});
|
|
16680
|
-
},
|
|
15718
|
+
(targetScope) => rebuildResource(store, "buffers", targetScope),
|
|
16681
15719
|
[store]
|
|
16682
15720
|
);
|
|
16683
15721
|
const disposeBuffers = React.useCallback(
|
|
@@ -16695,85 +15733,34 @@ function useBuffers(creatorOrScope, scope) {
|
|
|
16695
15733
|
[store, removeBuffers]
|
|
16696
15734
|
);
|
|
16697
15735
|
const isReader = creatorOrScope === void 0 || typeof creatorOrScope === "string";
|
|
16698
|
-
const storeBuffers =
|
|
16699
|
-
const
|
|
16700
|
-
|
|
16701
|
-
|
|
16702
|
-
|
|
16703
|
-
|
|
16704
|
-
|
|
16705
|
-
return
|
|
16706
|
-
|
|
16707
|
-
|
|
16708
|
-
|
|
16709
|
-
if (
|
|
16710
|
-
|
|
16711
|
-
}
|
|
16712
|
-
const state = store.getState();
|
|
16713
|
-
const set = store.setState;
|
|
16714
|
-
const creator = creatorOrScope;
|
|
16715
|
-
const wrappedState = createLazyCreatorState(state);
|
|
16716
|
-
const created = creator(wrappedState);
|
|
16717
|
-
const result = {};
|
|
16718
|
-
let hasNewBuffers = false;
|
|
16719
|
-
if (scope) {
|
|
16720
|
-
const currentScope = state.buffers[scope] ?? {};
|
|
16721
|
-
for (const [name, buffer] of Object.entries(created)) {
|
|
16722
|
-
if (currentScope[name]) {
|
|
16723
|
-
result[name] = currentScope[name];
|
|
16724
|
-
} else {
|
|
16725
|
-
if ("setName" in buffer && typeof buffer.setName === "function") {
|
|
16726
|
-
buffer.setName(`${scope}.${name}`);
|
|
16727
|
-
}
|
|
16728
|
-
result[name] = buffer;
|
|
16729
|
-
hasNewBuffers = true;
|
|
16730
|
-
}
|
|
16731
|
-
}
|
|
16732
|
-
if (hasNewBuffers) {
|
|
16733
|
-
set((s) => ({
|
|
16734
|
-
buffers: {
|
|
16735
|
-
...s.buffers,
|
|
16736
|
-
[scope]: { ...s.buffers[scope], ...result }
|
|
16737
|
-
}
|
|
16738
|
-
}));
|
|
16739
|
-
}
|
|
16740
|
-
return result;
|
|
16741
|
-
}
|
|
16742
|
-
for (const [name, buffer] of Object.entries(created)) {
|
|
16743
|
-
const existing = state.buffers[name];
|
|
16744
|
-
if (existing && isBufferLike(existing)) {
|
|
16745
|
-
result[name] = existing;
|
|
16746
|
-
} else {
|
|
16747
|
-
if ("setName" in buffer && typeof buffer.setName === "function") {
|
|
16748
|
-
buffer.setName(name);
|
|
16749
|
-
}
|
|
16750
|
-
result[name] = buffer;
|
|
16751
|
-
hasNewBuffers = true;
|
|
15736
|
+
const storeBuffers = usePrimaryThree((s) => s.buffers);
|
|
15737
|
+
const created = useScopedResource({
|
|
15738
|
+
store,
|
|
15739
|
+
kind: "buffers",
|
|
15740
|
+
scope: isReader ? void 0 : scope,
|
|
15741
|
+
isLeaf: isBufferLike,
|
|
15742
|
+
create: () => {
|
|
15743
|
+
if (isReader) return {};
|
|
15744
|
+
return creatorOrScope(createLazyCreatorState(store.getState(), store));
|
|
15745
|
+
},
|
|
15746
|
+
prepare: (name, buffer) => {
|
|
15747
|
+
if ("setName" in buffer && typeof buffer.setName === "function") {
|
|
15748
|
+
buffer.setName(scope ? `${scope}.${name}` : name);
|
|
16752
15749
|
}
|
|
15750
|
+
return buffer;
|
|
16753
15751
|
}
|
|
16754
|
-
|
|
16755
|
-
|
|
16756
|
-
|
|
16757
|
-
|
|
16758
|
-
}
|
|
15752
|
+
});
|
|
15753
|
+
let buffers = created;
|
|
15754
|
+
if (creatorOrScope === void 0) {
|
|
15755
|
+
buffers = storeBuffers;
|
|
15756
|
+
} else if (typeof creatorOrScope === "string") {
|
|
15757
|
+
const scopeData = storeBuffers[creatorOrScope];
|
|
15758
|
+
buffers = scopeData && !isBufferLike(scopeData) ? scopeData : {};
|
|
15759
|
+
}
|
|
16759
15760
|
return { ...buffers, removeBuffers, clearBuffers, rebuildBuffers, disposeBuffers };
|
|
16760
15761
|
}
|
|
16761
15762
|
function rebuildAllBuffers(store, scope) {
|
|
16762
|
-
store
|
|
16763
|
-
let newBuffers = state.buffers;
|
|
16764
|
-
if (scope && scope !== "root") {
|
|
16765
|
-
const { [scope]: _, ...rest } = state.buffers;
|
|
16766
|
-
newBuffers = rest;
|
|
16767
|
-
} else if (scope === "root") {
|
|
16768
|
-
newBuffers = {};
|
|
16769
|
-
for (const [key, value] of Object.entries(state.buffers)) {
|
|
16770
|
-
if (!isBufferLike(value)) newBuffers[key] = value;
|
|
16771
|
-
}
|
|
16772
|
-
} else {
|
|
16773
|
-
newBuffers = {};
|
|
16774
|
-
}
|
|
16775
|
-
return { buffers: newBuffers, _hmrVersion: state._hmrVersion + 1 };
|
|
16776
|
-
});
|
|
15763
|
+
rebuildResource(store, "buffers", scope);
|
|
16777
15764
|
}
|
|
16778
15765
|
|
|
16779
15766
|
const isStorageLike = (value) => {
|
|
@@ -16790,60 +15777,17 @@ const disposeStorage = (storage) => {
|
|
|
16790
15777
|
}
|
|
16791
15778
|
};
|
|
16792
15779
|
function useGPUStorage(creatorOrScope, scope) {
|
|
16793
|
-
const store =
|
|
15780
|
+
const store = usePrimaryStore();
|
|
16794
15781
|
const removeStorage = React.useCallback(
|
|
16795
|
-
(names, targetScope) =>
|
|
16796
|
-
const nameArray = Array.isArray(names) ? names : [names];
|
|
16797
|
-
store.setState((state) => {
|
|
16798
|
-
if (targetScope) {
|
|
16799
|
-
const currentScope = { ...state.gpuStorage[targetScope] };
|
|
16800
|
-
for (const name of nameArray) delete currentScope[name];
|
|
16801
|
-
return { gpuStorage: { ...state.gpuStorage, [targetScope]: currentScope } };
|
|
16802
|
-
}
|
|
16803
|
-
const gpuStorage2 = { ...state.gpuStorage };
|
|
16804
|
-
for (const name of nameArray) if (isStorageLike(gpuStorage2[name])) delete gpuStorage2[name];
|
|
16805
|
-
return { gpuStorage: gpuStorage2 };
|
|
16806
|
-
});
|
|
16807
|
-
},
|
|
15782
|
+
(names, targetScope) => removeResourceEntries(store, "gpuStorage", Array.isArray(names) ? names : [names], targetScope, isStorageLike),
|
|
16808
15783
|
[store]
|
|
16809
15784
|
);
|
|
16810
15785
|
const clearStorage = React.useCallback(
|
|
16811
|
-
(targetScope) =>
|
|
16812
|
-
store.setState((state) => {
|
|
16813
|
-
if (targetScope && targetScope !== "root") {
|
|
16814
|
-
const { [targetScope]: _, ...rest } = state.gpuStorage;
|
|
16815
|
-
return { gpuStorage: rest };
|
|
16816
|
-
}
|
|
16817
|
-
if (targetScope === "root") {
|
|
16818
|
-
const gpuStorage2 = {};
|
|
16819
|
-
for (const [key, value] of Object.entries(state.gpuStorage)) {
|
|
16820
|
-
if (!isStorageLike(value)) gpuStorage2[key] = value;
|
|
16821
|
-
}
|
|
16822
|
-
return { gpuStorage: gpuStorage2 };
|
|
16823
|
-
}
|
|
16824
|
-
return { gpuStorage: {} };
|
|
16825
|
-
});
|
|
16826
|
-
},
|
|
15786
|
+
(targetScope) => clearResourceEntries(store, "gpuStorage", targetScope, isStorageLike),
|
|
16827
15787
|
[store]
|
|
16828
15788
|
);
|
|
16829
15789
|
const rebuildStorage = React.useCallback(
|
|
16830
|
-
(targetScope) =>
|
|
16831
|
-
store.setState((state) => {
|
|
16832
|
-
let newStorage = state.gpuStorage;
|
|
16833
|
-
if (targetScope && targetScope !== "root") {
|
|
16834
|
-
const { [targetScope]: _, ...rest } = state.gpuStorage;
|
|
16835
|
-
newStorage = rest;
|
|
16836
|
-
} else if (targetScope === "root") {
|
|
16837
|
-
newStorage = {};
|
|
16838
|
-
for (const [key, value] of Object.entries(state.gpuStorage)) {
|
|
16839
|
-
if (!isStorageLike(value)) newStorage[key] = value;
|
|
16840
|
-
}
|
|
16841
|
-
} else {
|
|
16842
|
-
newStorage = {};
|
|
16843
|
-
}
|
|
16844
|
-
return { gpuStorage: newStorage, _hmrVersion: state._hmrVersion + 1 };
|
|
16845
|
-
});
|
|
16846
|
-
},
|
|
15790
|
+
(targetScope) => rebuildResource(store, "gpuStorage", targetScope),
|
|
16847
15791
|
[store]
|
|
16848
15792
|
);
|
|
16849
15793
|
const disposeStorageFn = React.useCallback(
|
|
@@ -16861,73 +15805,34 @@ function useGPUStorage(creatorOrScope, scope) {
|
|
|
16861
15805
|
[store, removeStorage]
|
|
16862
15806
|
);
|
|
16863
15807
|
const isReader = creatorOrScope === void 0 || typeof creatorOrScope === "string";
|
|
16864
|
-
const storeStorage =
|
|
16865
|
-
const
|
|
16866
|
-
|
|
16867
|
-
|
|
16868
|
-
|
|
16869
|
-
|
|
16870
|
-
|
|
16871
|
-
return
|
|
16872
|
-
|
|
16873
|
-
|
|
16874
|
-
|
|
16875
|
-
|
|
16876
|
-
|
|
16877
|
-
|
|
16878
|
-
const state = store.getState();
|
|
16879
|
-
const set = store.setState;
|
|
16880
|
-
const creator = creatorOrScope;
|
|
16881
|
-
const wrappedState = createLazyCreatorState(state);
|
|
16882
|
-
const created = creator(wrappedState);
|
|
16883
|
-
const result = {};
|
|
16884
|
-
let hasNewStorage = false;
|
|
16885
|
-
if (scope) {
|
|
16886
|
-
const currentScope = state.gpuStorage[scope] ?? {};
|
|
16887
|
-
for (const [name, storage] of Object.entries(created)) {
|
|
16888
|
-
if (currentScope[name]) {
|
|
16889
|
-
result[name] = currentScope[name];
|
|
16890
|
-
} else {
|
|
16891
|
-
if ("setName" in storage && typeof storage.setName === "function") {
|
|
16892
|
-
storage.setName(`${scope}.${name}`);
|
|
16893
|
-
}
|
|
16894
|
-
if ("name" in storage && typeof storage.name === "string") {
|
|
16895
|
-
storage.name = `${scope}.${name}`;
|
|
16896
|
-
}
|
|
16897
|
-
result[name] = storage;
|
|
16898
|
-
hasNewStorage = true;
|
|
16899
|
-
}
|
|
16900
|
-
}
|
|
16901
|
-
if (hasNewStorage) {
|
|
16902
|
-
set((s) => ({
|
|
16903
|
-
gpuStorage: {
|
|
16904
|
-
...s.gpuStorage,
|
|
16905
|
-
[scope]: { ...s.gpuStorage[scope], ...result }
|
|
16906
|
-
}
|
|
16907
|
-
}));
|
|
15808
|
+
const storeStorage = usePrimaryThree((s) => s.gpuStorage);
|
|
15809
|
+
const created = useScopedResource({
|
|
15810
|
+
store,
|
|
15811
|
+
kind: "gpuStorage",
|
|
15812
|
+
scope: isReader ? void 0 : scope,
|
|
15813
|
+
isLeaf: isStorageLike,
|
|
15814
|
+
create: () => {
|
|
15815
|
+
if (isReader) return {};
|
|
15816
|
+
return creatorOrScope(createLazyCreatorState(store.getState(), store));
|
|
15817
|
+
},
|
|
15818
|
+
prepare: (name, storage) => {
|
|
15819
|
+
const label = scope ? `${scope}.${name}` : name;
|
|
15820
|
+
if ("setName" in storage && typeof storage.setName === "function") {
|
|
15821
|
+
storage.setName(label);
|
|
16908
15822
|
}
|
|
16909
|
-
|
|
16910
|
-
|
|
16911
|
-
for (const [name, storage] of Object.entries(created)) {
|
|
16912
|
-
const existing = state.gpuStorage[name];
|
|
16913
|
-
if (existing && isStorageLike(existing)) {
|
|
16914
|
-
result[name] = existing;
|
|
16915
|
-
} else {
|
|
16916
|
-
if ("setName" in storage && typeof storage.setName === "function") {
|
|
16917
|
-
storage.setName(name);
|
|
16918
|
-
}
|
|
16919
|
-
if ("name" in storage && typeof storage.name === "string") {
|
|
16920
|
-
storage.name = name;
|
|
16921
|
-
}
|
|
16922
|
-
result[name] = storage;
|
|
16923
|
-
hasNewStorage = true;
|
|
15823
|
+
if ("name" in storage && typeof storage.name === "string") {
|
|
15824
|
+
storage.name = label;
|
|
16924
15825
|
}
|
|
15826
|
+
return storage;
|
|
16925
15827
|
}
|
|
16926
|
-
|
|
16927
|
-
|
|
16928
|
-
|
|
16929
|
-
|
|
16930
|
-
}
|
|
15828
|
+
});
|
|
15829
|
+
let gpuStorage = created;
|
|
15830
|
+
if (creatorOrScope === void 0) {
|
|
15831
|
+
gpuStorage = storeStorage;
|
|
15832
|
+
} else if (typeof creatorOrScope === "string") {
|
|
15833
|
+
const scopeData = storeStorage[creatorOrScope];
|
|
15834
|
+
gpuStorage = scopeData && !isStorageLike(scopeData) ? scopeData : {};
|
|
15835
|
+
}
|
|
16931
15836
|
return {
|
|
16932
15837
|
...gpuStorage,
|
|
16933
15838
|
removeStorage,
|
|
@@ -16937,21 +15842,7 @@ function useGPUStorage(creatorOrScope, scope) {
|
|
|
16937
15842
|
};
|
|
16938
15843
|
}
|
|
16939
15844
|
function rebuildAllStorage(store, scope) {
|
|
16940
|
-
store
|
|
16941
|
-
let newStorage = state.gpuStorage;
|
|
16942
|
-
if (scope && scope !== "root") {
|
|
16943
|
-
const { [scope]: _, ...rest } = state.gpuStorage;
|
|
16944
|
-
newStorage = rest;
|
|
16945
|
-
} else if (scope === "root") {
|
|
16946
|
-
newStorage = {};
|
|
16947
|
-
for (const [key, value] of Object.entries(state.gpuStorage)) {
|
|
16948
|
-
if (!isStorageLike(value)) newStorage[key] = value;
|
|
16949
|
-
}
|
|
16950
|
-
} else {
|
|
16951
|
-
newStorage = {};
|
|
16952
|
-
}
|
|
16953
|
-
return { gpuStorage: newStorage, _hmrVersion: state._hmrVersion + 1 };
|
|
16954
|
-
});
|
|
15845
|
+
rebuildResource(store, "gpuStorage", scope);
|
|
16955
15846
|
}
|
|
16956
15847
|
|
|
16957
15848
|
function useRenderPipeline(mainCB, setupCB) {
|
|
@@ -16992,12 +15883,12 @@ function useRenderPipeline(mainCB, setupCB) {
|
|
|
16992
15883
|
const state = store.getState();
|
|
16993
15884
|
const set = store.setState;
|
|
16994
15885
|
try {
|
|
16995
|
-
let
|
|
15886
|
+
let pipeline = state.renderPipeline;
|
|
16996
15887
|
let currentPasses = { ...state.passes };
|
|
16997
|
-
let
|
|
16998
|
-
if (!
|
|
16999
|
-
|
|
17000
|
-
|
|
15888
|
+
let justCreatedPipeline = false;
|
|
15889
|
+
if (!pipeline) {
|
|
15890
|
+
pipeline = new webgpu.RenderPipeline(renderer);
|
|
15891
|
+
justCreatedPipeline = true;
|
|
17001
15892
|
}
|
|
17002
15893
|
const cacheValid = scenePassCacheRef.current && scenePassCacheRef.current.sceneUuid === scene.uuid && scenePassCacheRef.current.cameraUuid === camera.uuid;
|
|
17003
15894
|
let scenePass;
|
|
@@ -17008,9 +15899,9 @@ function useRenderPipeline(mainCB, setupCB) {
|
|
|
17008
15899
|
scenePassCacheRef.current = { sceneUuid: scene.uuid, cameraUuid: camera.uuid, scenePass };
|
|
17009
15900
|
}
|
|
17010
15901
|
currentPasses.scenePass = scenePass;
|
|
17011
|
-
if (!
|
|
17012
|
-
set({ renderPipeline:
|
|
17013
|
-
const shouldRunCallbacks =
|
|
15902
|
+
if (!pipeline.outputNode || justCreatedPipeline) pipeline.outputNode = scenePass;
|
|
15903
|
+
set({ renderPipeline: pipeline, passes: currentPasses });
|
|
15904
|
+
const shouldRunCallbacks = justCreatedPipeline || !callbacksRanRef.current || !cacheValid;
|
|
17014
15905
|
if (shouldRunCallbacks) {
|
|
17015
15906
|
if (setupCBRef.current) {
|
|
17016
15907
|
const freshState = store.getState();
|
|
@@ -17031,7 +15922,7 @@ function useRenderPipeline(mainCB, setupCB) {
|
|
|
17031
15922
|
callbacksRanRef.current = true;
|
|
17032
15923
|
}
|
|
17033
15924
|
} catch (error) {
|
|
17034
|
-
console.
|
|
15925
|
+
console.warn("[useRenderPipeline] Setup failed; the render pipeline was not configured:", error);
|
|
17035
15926
|
}
|
|
17036
15927
|
}, [store, renderer, scene, camera, isLegacy, rebuildVersion]);
|
|
17037
15928
|
const passes = useThree((s) => s.passes);
|
|
@@ -17049,6 +15940,8 @@ function useRenderPipeline(mainCB, setupCB) {
|
|
|
17049
15940
|
|
|
17050
15941
|
extend(THREE);
|
|
17051
15942
|
|
|
15943
|
+
exports.Scheduler = scheduler.Scheduler;
|
|
15944
|
+
exports.getScheduler = scheduler.getScheduler;
|
|
17052
15945
|
exports.Block = Block;
|
|
17053
15946
|
exports.Canvas = Canvas;
|
|
17054
15947
|
exports.Environment = Environment;
|
|
@@ -17064,7 +15957,6 @@ exports.R3F_BUILD_LEGACY = R3F_BUILD_LEGACY;
|
|
|
17064
15957
|
exports.R3F_BUILD_WEBGPU = R3F_BUILD_WEBGPU;
|
|
17065
15958
|
exports.REACT_INTERNAL_PROPS = REACT_INTERNAL_PROPS;
|
|
17066
15959
|
exports.RESERVED_PROPS = RESERVED_PROPS;
|
|
17067
|
-
exports.Scheduler = Scheduler;
|
|
17068
15960
|
exports.Texture = Texture;
|
|
17069
15961
|
exports._roots = _roots;
|
|
17070
15962
|
exports.act = act;
|
|
@@ -17100,7 +15992,6 @@ exports.getInstanceProps = getInstanceProps;
|
|
|
17100
15992
|
exports.getPrimary = getPrimary;
|
|
17101
15993
|
exports.getPrimaryIds = getPrimaryIds;
|
|
17102
15994
|
exports.getRootState = getRootState;
|
|
17103
|
-
exports.getScheduler = getScheduler;
|
|
17104
15995
|
exports.getUuidPrefix = getUuidPrefix;
|
|
17105
15996
|
exports.hasConstructor = hasConstructor;
|
|
17106
15997
|
exports.hasPrimary = hasPrimary;
|