@react-three/fiber 10.0.0-canary.604355a → 10.0.0-canary.76d2134
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +885 -1422
- package/dist/index.d.cts +309 -811
- package/dist/index.d.mts +309 -811
- package/dist/index.d.ts +309 -811
- package/dist/index.mjs +860 -1396
- package/dist/legacy.cjs +840 -1383
- package/dist/legacy.d.cts +310 -811
- package/dist/legacy.d.mts +310 -811
- package/dist/legacy.d.ts +310 -811
- package/dist/legacy.mjs +814 -1356
- package/dist/webgpu/index.cjs +1341 -1737
- package/dist/webgpu/index.d.cts +425 -825
- package/dist/webgpu/index.d.mts +425 -825
- package/dist/webgpu/index.d.ts +425 -825
- package/dist/webgpu/index.mjs +1310 -1709
- package/package.json +4 -3
- package/readme.md +2 -3
package/dist/webgpu/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as webgpu from 'three/webgpu';
|
|
2
|
-
import { RenderTarget, CubeReflectionMapping, EquirectangularReflectionMapping, CubeTextureLoader, Scene,
|
|
2
|
+
import { CubeRenderTarget, RenderTarget, CubeReflectionMapping, EquirectangularReflectionMapping, CubeTextureLoader, Scene, HalfFloatType, Color, Frustum, Matrix4, Group, BoxGeometry, MeshBasicNodeMaterial, Mesh, Node, NodeUpdateType, Layers, ShaderMaterial, Uniform, SRGBColorSpace, RGBAFormat, UnsignedByteType, Vector3, Vector2, TextureLoader, Texture as Texture$1, CanvasTarget, Raycaster, OrthographicCamera, PerspectiveCamera, PCFShadowMap, VSMShadowMap, BasicShadowMap, ACESFilmicToneMapping, WebGPURenderer, Vector4, RenderPipeline } from 'three/webgpu';
|
|
3
3
|
import { Inspector } from 'three/addons/inspector/Inspector.js';
|
|
4
4
|
import { jsx, Fragment, jsxs } from 'react/jsx-runtime';
|
|
5
5
|
import * as React from 'react';
|
|
@@ -12,8 +12,10 @@ import { HDRLoader } from 'three/examples/jsm/loaders/HDRLoader.js';
|
|
|
12
12
|
import { EXRLoader } from 'three/examples/jsm/loaders/EXRLoader.js';
|
|
13
13
|
import { UltraHDRLoader } from 'three/examples/jsm/loaders/UltraHDRLoader.js';
|
|
14
14
|
import { GainMapLoader } from '@monogrid/gainmap-js';
|
|
15
|
-
import Tb, {
|
|
15
|
+
import Tb, { unstable_getCurrentPriorityLevel, unstable_IdlePriority, unstable_LowPriority, unstable_NormalPriority, unstable_UserBlockingPriority, unstable_ImmediatePriority, unstable_scheduleCallback } from 'scheduler';
|
|
16
16
|
import { createWithEqualityFn } from 'zustand/traditional';
|
|
17
|
+
import { getScheduler } from '@pmndrs/scheduler';
|
|
18
|
+
export { Scheduler, getScheduler } from '@pmndrs/scheduler';
|
|
17
19
|
import { suspend, preload, clear } from 'suspend-react';
|
|
18
20
|
import { dequal } from 'dequal/lite';
|
|
19
21
|
import 'zustand/shallow';
|
|
@@ -41,13 +43,16 @@ const WebGLRenderer = class WebGLRenderer2 {
|
|
|
41
43
|
}
|
|
42
44
|
};
|
|
43
45
|
const WebGLRenderTarget = null;
|
|
46
|
+
const WebGLCubeRenderTarget = null;
|
|
44
47
|
|
|
45
48
|
const THREE = /*#__PURE__*/_mergeNamespaces({
|
|
46
49
|
__proto__: null,
|
|
50
|
+
CubeRenderTargetCompat: CubeRenderTarget,
|
|
47
51
|
Inspector: Inspector,
|
|
48
52
|
R3F_BUILD_LEGACY: R3F_BUILD_LEGACY,
|
|
49
53
|
R3F_BUILD_WEBGPU: R3F_BUILD_WEBGPU,
|
|
50
54
|
RenderTargetCompat: RenderTarget,
|
|
55
|
+
WebGLCubeRenderTarget: WebGLCubeRenderTarget,
|
|
51
56
|
WebGLRenderTarget: WebGLRenderTarget,
|
|
52
57
|
WebGLRenderer: WebGLRenderer
|
|
53
58
|
}, [webgpu]);
|
|
@@ -149,7 +154,7 @@ function useEnvironment({
|
|
|
149
154
|
useLoader$1.clear(loader, multiFile ? [files] : files);
|
|
150
155
|
}
|
|
151
156
|
renderer.domElement.addEventListener("webglcontextlost", clearGainmapTexture, { once: true });
|
|
152
|
-
}, [files, renderer.domElement]);
|
|
157
|
+
}, [extension, files, loader, multiFile, renderer.domElement]);
|
|
153
158
|
const loaderResult = useLoader$1(
|
|
154
159
|
loader,
|
|
155
160
|
multiFile ? [files] : files,
|
|
@@ -321,15 +326,16 @@ function EnvironmentPortal({
|
|
|
321
326
|
preset = void 0,
|
|
322
327
|
extensions
|
|
323
328
|
}) {
|
|
324
|
-
const
|
|
329
|
+
const renderer = useThree$1((state) => state.renderer);
|
|
330
|
+
const isLegacy = useThree$1((state) => state.isLegacy);
|
|
325
331
|
const defaultScene = useThree$1((state) => state.scene);
|
|
326
332
|
const camera = React.useRef(null);
|
|
327
333
|
const [virtualScene] = React.useState(() => new Scene());
|
|
328
334
|
const fbo = React.useMemo(() => {
|
|
329
|
-
const fbo2 = new WebGLCubeRenderTarget(resolution);
|
|
335
|
+
const fbo2 = isLegacy ? new WebGLCubeRenderTarget(resolution) : new CubeRenderTarget(resolution);
|
|
330
336
|
fbo2.texture.type = HalfFloatType;
|
|
331
337
|
return fbo2;
|
|
332
|
-
}, [resolution]);
|
|
338
|
+
}, [resolution, isLegacy]);
|
|
333
339
|
React.useEffect(() => {
|
|
334
340
|
return () => {
|
|
335
341
|
fbo.dispose();
|
|
@@ -337,10 +343,10 @@ function EnvironmentPortal({
|
|
|
337
343
|
}, [fbo]);
|
|
338
344
|
React.useLayoutEffect(() => {
|
|
339
345
|
if (frames === 1) {
|
|
340
|
-
const autoClear =
|
|
341
|
-
|
|
342
|
-
camera.current.update(
|
|
343
|
-
|
|
346
|
+
const autoClear = renderer.autoClear;
|
|
347
|
+
renderer.autoClear = true;
|
|
348
|
+
camera.current.update(renderer, virtualScene);
|
|
349
|
+
renderer.autoClear = autoClear;
|
|
344
350
|
}
|
|
345
351
|
return setEnvProps(background, scene, defaultScene, fbo.texture, {
|
|
346
352
|
backgroundBlurriness: blur ?? backgroundBlurriness,
|
|
@@ -349,14 +355,29 @@ function EnvironmentPortal({
|
|
|
349
355
|
environmentIntensity,
|
|
350
356
|
environmentRotation
|
|
351
357
|
});
|
|
352
|
-
}, [
|
|
358
|
+
}, [
|
|
359
|
+
children,
|
|
360
|
+
virtualScene,
|
|
361
|
+
fbo.texture,
|
|
362
|
+
scene,
|
|
363
|
+
defaultScene,
|
|
364
|
+
background,
|
|
365
|
+
frames,
|
|
366
|
+
renderer,
|
|
367
|
+
blur,
|
|
368
|
+
backgroundBlurriness,
|
|
369
|
+
backgroundIntensity,
|
|
370
|
+
backgroundRotation,
|
|
371
|
+
environmentIntensity,
|
|
372
|
+
environmentRotation
|
|
373
|
+
]);
|
|
353
374
|
let count = 1;
|
|
354
375
|
useFrame$1(() => {
|
|
355
376
|
if (frames === Infinity || count < frames) {
|
|
356
|
-
const autoClear =
|
|
357
|
-
|
|
358
|
-
camera.current.update(
|
|
359
|
-
|
|
377
|
+
const autoClear = renderer.autoClear;
|
|
378
|
+
renderer.autoClear = true;
|
|
379
|
+
camera.current.update(renderer, virtualScene);
|
|
380
|
+
renderer.autoClear = autoClear;
|
|
360
381
|
count++;
|
|
361
382
|
}
|
|
362
383
|
});
|
|
@@ -420,10 +441,9 @@ function Environment(props) {
|
|
|
420
441
|
return props.ground ? /* @__PURE__ */ jsx(EnvironmentGround, { ...props }) : props.map ? /* @__PURE__ */ jsx(EnvironmentMap, { ...props }) : props.children ? /* @__PURE__ */ jsx(EnvironmentPortal, { ...props }) : /* @__PURE__ */ jsx(EnvironmentCube, { ...props });
|
|
421
442
|
}
|
|
422
443
|
|
|
423
|
-
var __defProp$
|
|
424
|
-
var __defNormalProp$
|
|
425
|
-
var __publicField$
|
|
426
|
-
const act = React["act"];
|
|
444
|
+
var __defProp$1 = Object.defineProperty;
|
|
445
|
+
var __defNormalProp$1 = (obj, key, value) => key in obj ? __defProp$1(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
446
|
+
var __publicField$1 = (obj, key, value) => __defNormalProp$1(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
427
447
|
const useIsomorphicLayoutEffect = /* @__PURE__ */ (() => typeof window !== "undefined" && (window.document?.createElement || window.navigator?.product === "ReactNative"))() ? React.useLayoutEffect : React.useEffect;
|
|
428
448
|
function useMutableCallback(fn) {
|
|
429
449
|
const ref = React.useRef(fn);
|
|
@@ -454,7 +474,7 @@ const ErrorBoundary = /* @__PURE__ */ (() => {
|
|
|
454
474
|
return _a = class extends React.Component {
|
|
455
475
|
constructor() {
|
|
456
476
|
super(...arguments);
|
|
457
|
-
__publicField$
|
|
477
|
+
__publicField$1(this, "state", { error: false });
|
|
458
478
|
}
|
|
459
479
|
componentDidCatch(err) {
|
|
460
480
|
this.props.set(err);
|
|
@@ -462,7 +482,7 @@ const ErrorBoundary = /* @__PURE__ */ (() => {
|
|
|
462
482
|
render() {
|
|
463
483
|
return this.state.error ? null : this.props.children;
|
|
464
484
|
}
|
|
465
|
-
}, __publicField$
|
|
485
|
+
}, __publicField$1(_a, "getDerivedStateFromError", () => ({ error: true })), _a;
|
|
466
486
|
})();
|
|
467
487
|
|
|
468
488
|
const is = {
|
|
@@ -599,8 +619,7 @@ function prepare(target, root, type, props) {
|
|
|
599
619
|
object,
|
|
600
620
|
eventCount: 0,
|
|
601
621
|
handlers: {},
|
|
602
|
-
isHidden: false
|
|
603
|
-
deferredRefs: []
|
|
622
|
+
isHidden: false
|
|
604
623
|
};
|
|
605
624
|
if (object) object.__r3f = instance;
|
|
606
625
|
}
|
|
@@ -757,6 +776,17 @@ function unregisterVisibility(store, object) {
|
|
|
757
776
|
internal.visibilityRegistry.delete(object.uuid);
|
|
758
777
|
internal.occlusionCache.delete(object);
|
|
759
778
|
}
|
|
779
|
+
function updateVisibilityHandlers(store, object, handlers) {
|
|
780
|
+
const { internal } = store.getState();
|
|
781
|
+
const entry = internal.visibilityRegistry.get(object.uuid);
|
|
782
|
+
if (!entry) return false;
|
|
783
|
+
entry.handlers = handlers;
|
|
784
|
+
if ((handlers.onOccluded || handlers.onVisible) && !internal.occlusionEnabled) {
|
|
785
|
+
object.occlusionTest = true;
|
|
786
|
+
enableOcclusion(store);
|
|
787
|
+
}
|
|
788
|
+
return true;
|
|
789
|
+
}
|
|
760
790
|
function checkVisibility(state) {
|
|
761
791
|
const { internal, camera } = state;
|
|
762
792
|
const registry = internal.visibilityRegistry;
|
|
@@ -917,9 +947,9 @@ function diffProps(instance, newProps) {
|
|
|
917
947
|
const { root, key } = resolve(instance.object, prop);
|
|
918
948
|
if (root.constructor && root.constructor.length === 0) {
|
|
919
949
|
const ctor = getMemoizedPrototype(root);
|
|
920
|
-
if (!is.und(ctor)) changedProps[
|
|
950
|
+
if (!is.und(ctor)) changedProps[prop] = ctor[key];
|
|
921
951
|
} else {
|
|
922
|
-
changedProps[
|
|
952
|
+
changedProps[prop] = 0;
|
|
923
953
|
}
|
|
924
954
|
}
|
|
925
955
|
return changedProps;
|
|
@@ -944,10 +974,7 @@ function applyProps(object, props) {
|
|
|
944
974
|
continue;
|
|
945
975
|
}
|
|
946
976
|
if (value === void 0) continue;
|
|
947
|
-
if (isFromRef(value))
|
|
948
|
-
instance?.deferredRefs?.push({ prop, ref: value[FROM_REF] });
|
|
949
|
-
continue;
|
|
950
|
-
}
|
|
977
|
+
if (isFromRef(value)) continue;
|
|
951
978
|
if (isOnce(value)) {
|
|
952
979
|
if (instance?.appliedOnce?.has(prop)) continue;
|
|
953
980
|
if (instance) {
|
|
@@ -983,8 +1010,24 @@ function applyProps(object, props) {
|
|
|
983
1010
|
} else if (isVectorLike(target) && is.num(value)) {
|
|
984
1011
|
if ("setScalar" in target && typeof target.setScalar === "function") target.setScalar(value);
|
|
985
1012
|
else target.set(value);
|
|
1013
|
+
} else if (root instanceof ShaderMaterial && key === "uniforms" && is.obj(value)) {
|
|
1014
|
+
if (!is.obj(root.uniforms)) root.uniforms = {};
|
|
1015
|
+
const uniforms = root.uniforms;
|
|
1016
|
+
const nextUniforms = value;
|
|
1017
|
+
for (const name in nextUniforms) {
|
|
1018
|
+
const uniform = nextUniforms[name];
|
|
1019
|
+
const targetUniform = uniforms[name];
|
|
1020
|
+
if (targetUniform) Object.assign(targetUniform, uniform);
|
|
1021
|
+
else {
|
|
1022
|
+
const nextUniform = uniform instanceof Uniform ? uniform.clone() : new Uniform(uniform.value);
|
|
1023
|
+
uniforms[name] = nextUniform;
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
986
1026
|
} else {
|
|
987
1027
|
root[key] = value;
|
|
1028
|
+
if (key.endsWith("Node") && root.isMaterial) {
|
|
1029
|
+
root.needsUpdate = true;
|
|
1030
|
+
}
|
|
988
1031
|
if (rootState && rootState.renderer?.outputColorSpace === SRGBColorSpace && colorMaps.includes(key) && isTexture(value) && root[key]?.isTexture && // sRGB textures must be RGBA8 since r137 https://github.com/mrdoob/three.js/pull/23129
|
|
989
1032
|
root[key].format === RGBAFormat && root[key].type === UnsignedByteType) {
|
|
990
1033
|
root[key].colorSpace = rootState.textureColorSpace;
|
|
@@ -998,6 +1041,9 @@ function applyProps(object, props) {
|
|
|
998
1041
|
if (instance.eventCount && object2.raycast !== null) {
|
|
999
1042
|
rootState.internal.interaction.push(object2);
|
|
1000
1043
|
}
|
|
1044
|
+
}
|
|
1045
|
+
if (instance?.parent && rootState?.internal && instance.object?.isObject3D) {
|
|
1046
|
+
const object2 = instance.object;
|
|
1001
1047
|
const root = findInitialRoot(instance);
|
|
1002
1048
|
const visibilityHandlers = {
|
|
1003
1049
|
onFramed: instance.handlers.onFramed,
|
|
@@ -1005,7 +1051,9 @@ function applyProps(object, props) {
|
|
|
1005
1051
|
onVisible: instance.handlers.onVisible
|
|
1006
1052
|
};
|
|
1007
1053
|
if (hasVisibilityHandlers(visibilityHandlers)) {
|
|
1008
|
-
|
|
1054
|
+
if (!updateVisibilityHandlers(root, object2, visibilityHandlers)) {
|
|
1055
|
+
registerVisibility(root, object2, visibilityHandlers);
|
|
1056
|
+
}
|
|
1009
1057
|
} else {
|
|
1010
1058
|
unregisterVisibility(root, object2);
|
|
1011
1059
|
}
|
|
@@ -1018,38 +1066,95 @@ function applyProps(object, props) {
|
|
|
1018
1066
|
return object;
|
|
1019
1067
|
}
|
|
1020
1068
|
|
|
1069
|
+
const DEFAULT_POINTER_ID = 0;
|
|
1070
|
+
const XR_POINTER_ID_START = 1e3;
|
|
1071
|
+
function getPointerState(internal, pointerId) {
|
|
1072
|
+
let state = internal.pointerMap.get(pointerId);
|
|
1073
|
+
if (!state) {
|
|
1074
|
+
state = {
|
|
1075
|
+
hovered: /* @__PURE__ */ new Map(),
|
|
1076
|
+
captured: /* @__PURE__ */ new Map(),
|
|
1077
|
+
initialClick: [0, 0],
|
|
1078
|
+
initialHits: []
|
|
1079
|
+
};
|
|
1080
|
+
internal.pointerMap.set(pointerId, state);
|
|
1081
|
+
}
|
|
1082
|
+
return state;
|
|
1083
|
+
}
|
|
1084
|
+
function getPointerId(event) {
|
|
1085
|
+
return "pointerId" in event ? event.pointerId : DEFAULT_POINTER_ID;
|
|
1086
|
+
}
|
|
1021
1087
|
function makeId(event) {
|
|
1022
1088
|
return (event.eventObject || event.object).uuid + "/" + event.index + event.instanceId;
|
|
1023
1089
|
}
|
|
1024
|
-
function releaseInternalPointerCapture(
|
|
1025
|
-
const
|
|
1090
|
+
function releaseInternalPointerCapture(internal, obj, pointerId) {
|
|
1091
|
+
const pointerState = internal.pointerMap.get(pointerId);
|
|
1092
|
+
if (!pointerState) return;
|
|
1093
|
+
const captureData = pointerState.captured.get(obj);
|
|
1026
1094
|
if (captureData) {
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1095
|
+
pointerState.captured.delete(obj);
|
|
1096
|
+
captureData.target.releasePointerCapture(pointerId);
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
function swapInteractivity(store, object, newObject) {
|
|
1100
|
+
const { internal } = store.getState();
|
|
1101
|
+
for (let i = 0; i < internal.interaction.length; i++) {
|
|
1102
|
+
if (internal.interaction[i] === object) internal.interaction[i] = newObject;
|
|
1103
|
+
}
|
|
1104
|
+
for (const pointerState of internal.pointerMap.values()) {
|
|
1105
|
+
for (let i = 0; i < pointerState.initialHits.length; i++) {
|
|
1106
|
+
if (pointerState.initialHits[i] === object) pointerState.initialHits[i] = newObject;
|
|
1107
|
+
}
|
|
1108
|
+
pointerState.hovered.forEach((value, key) => {
|
|
1109
|
+
if (value.eventObject === object || value.object === object) {
|
|
1110
|
+
pointerState.hovered.delete(key);
|
|
1111
|
+
const next = {
|
|
1112
|
+
...value,
|
|
1113
|
+
eventObject: value.eventObject === object ? newObject : value.eventObject,
|
|
1114
|
+
object: value.object === object ? newObject : value.object
|
|
1115
|
+
};
|
|
1116
|
+
pointerState.hovered.set(makeId(next), next);
|
|
1117
|
+
}
|
|
1118
|
+
});
|
|
1119
|
+
const captureData = pointerState.captured.get(object);
|
|
1120
|
+
if (captureData) {
|
|
1121
|
+
pointerState.captured.delete(object);
|
|
1122
|
+
pointerState.captured.set(newObject, {
|
|
1123
|
+
...captureData,
|
|
1124
|
+
intersection: {
|
|
1125
|
+
...captureData.intersection,
|
|
1126
|
+
object: captureData.intersection.object === object ? newObject : captureData.intersection.object,
|
|
1127
|
+
eventObject: captureData.intersection.eventObject === object ? newObject : captureData.intersection.eventObject
|
|
1128
|
+
}
|
|
1129
|
+
});
|
|
1031
1130
|
}
|
|
1032
1131
|
}
|
|
1132
|
+
unregisterVisibility(store, object);
|
|
1033
1133
|
}
|
|
1034
1134
|
function removeInteractivity(store, object) {
|
|
1035
1135
|
const { internal } = store.getState();
|
|
1036
1136
|
internal.interaction = internal.interaction.filter((o) => o !== object);
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1137
|
+
for (const [pointerId, pointerState] of internal.pointerMap) {
|
|
1138
|
+
pointerState.initialHits = pointerState.initialHits.filter((o) => o !== object);
|
|
1139
|
+
pointerState.hovered.forEach((value, key) => {
|
|
1140
|
+
if (value.eventObject === object || value.object === object) {
|
|
1141
|
+
pointerState.hovered.delete(key);
|
|
1142
|
+
}
|
|
1143
|
+
});
|
|
1144
|
+
if (pointerState.captured.has(object)) {
|
|
1145
|
+
releaseInternalPointerCapture(internal, object, pointerId);
|
|
1041
1146
|
}
|
|
1042
|
-
}
|
|
1043
|
-
internal.capturedMap.forEach((captures, pointerId) => {
|
|
1044
|
-
releaseInternalPointerCapture(internal.capturedMap, object, captures, pointerId);
|
|
1045
|
-
});
|
|
1147
|
+
}
|
|
1046
1148
|
unregisterVisibility(store, object);
|
|
1047
1149
|
}
|
|
1048
1150
|
function createEvents(store) {
|
|
1049
|
-
function calculateDistance(event) {
|
|
1151
|
+
function calculateDistance(event, pointerId) {
|
|
1050
1152
|
const { internal } = store.getState();
|
|
1051
|
-
const
|
|
1052
|
-
|
|
1153
|
+
const pointerState = internal.pointerMap.get(pointerId);
|
|
1154
|
+
if (!pointerState) return 0;
|
|
1155
|
+
const [initialX, initialY] = pointerState.initialClick;
|
|
1156
|
+
const dx = event.offsetX - initialX;
|
|
1157
|
+
const dy = event.offsetY - initialY;
|
|
1053
1158
|
return Math.round(Math.sqrt(dx * dx + dy * dy));
|
|
1054
1159
|
}
|
|
1055
1160
|
function filterPointerEvents(objects) {
|
|
@@ -1085,6 +1190,15 @@ function createEvents(store) {
|
|
|
1085
1190
|
return state2.raycaster.camera ? state2.raycaster.intersectObject(obj, true) : [];
|
|
1086
1191
|
}
|
|
1087
1192
|
let hits = eventsObjects.flatMap(handleRaycast).sort((a, b) => {
|
|
1193
|
+
const aInteractivePriority = a.object.userData?.interactivePriority;
|
|
1194
|
+
const bInteractivePriority = b.object.userData?.interactivePriority;
|
|
1195
|
+
if (aInteractivePriority !== void 0 || bInteractivePriority !== void 0) {
|
|
1196
|
+
if (aInteractivePriority !== void 0 && bInteractivePriority === void 0) return -1;
|
|
1197
|
+
if (bInteractivePriority !== void 0 && aInteractivePriority === void 0) return 1;
|
|
1198
|
+
if (aInteractivePriority !== bInteractivePriority) {
|
|
1199
|
+
return (bInteractivePriority ?? 0) - (aInteractivePriority ?? 0);
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1088
1202
|
const aState = getRootState(a.object);
|
|
1089
1203
|
const bState = getRootState(b.object);
|
|
1090
1204
|
const aPriority = aState?.events?.priority ?? 1;
|
|
@@ -1106,9 +1220,13 @@ function createEvents(store) {
|
|
|
1106
1220
|
eventObject = eventObject.parent;
|
|
1107
1221
|
}
|
|
1108
1222
|
}
|
|
1109
|
-
if ("pointerId" in event
|
|
1110
|
-
|
|
1111
|
-
|
|
1223
|
+
if ("pointerId" in event) {
|
|
1224
|
+
const pointerId = event.pointerId;
|
|
1225
|
+
const pointerState = state.internal.pointerMap.get(pointerId);
|
|
1226
|
+
if (pointerState?.captured.size) {
|
|
1227
|
+
for (const captureData of pointerState.captured.values()) {
|
|
1228
|
+
if (!duplicates.has(makeId(captureData.intersection))) intersections.push(captureData.intersection);
|
|
1229
|
+
}
|
|
1112
1230
|
}
|
|
1113
1231
|
}
|
|
1114
1232
|
return intersections;
|
|
@@ -1121,27 +1239,25 @@ function createEvents(store) {
|
|
|
1121
1239
|
if (state) {
|
|
1122
1240
|
const { raycaster, pointer, camera, internal } = state;
|
|
1123
1241
|
const unprojectedPoint = new Vector3(pointer.x, pointer.y, 0).unproject(camera);
|
|
1124
|
-
const hasPointerCapture = (id) =>
|
|
1242
|
+
const hasPointerCapture = (id) => {
|
|
1243
|
+
const pointerState = internal.pointerMap.get(id);
|
|
1244
|
+
return pointerState?.captured.has(hit.eventObject) ?? false;
|
|
1245
|
+
};
|
|
1125
1246
|
const setPointerCapture = (id) => {
|
|
1126
1247
|
const captureData = { intersection: hit, target: event.target };
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
} else {
|
|
1130
|
-
internal.capturedMap.set(id, /* @__PURE__ */ new Map([[hit.eventObject, captureData]]));
|
|
1131
|
-
}
|
|
1248
|
+
const pointerState = getPointerState(internal, id);
|
|
1249
|
+
pointerState.captured.set(hit.eventObject, captureData);
|
|
1132
1250
|
event.target.setPointerCapture(id);
|
|
1133
1251
|
};
|
|
1134
1252
|
const releasePointerCapture = (id) => {
|
|
1135
|
-
|
|
1136
|
-
if (captures) {
|
|
1137
|
-
releaseInternalPointerCapture(internal.capturedMap, hit.eventObject, captures, id);
|
|
1138
|
-
}
|
|
1253
|
+
releaseInternalPointerCapture(internal, hit.eventObject, id);
|
|
1139
1254
|
};
|
|
1140
1255
|
const extractEventProps = {};
|
|
1141
1256
|
for (const prop in event) {
|
|
1142
1257
|
const property = event[prop];
|
|
1143
1258
|
if (typeof property !== "function") extractEventProps[prop] = property;
|
|
1144
1259
|
}
|
|
1260
|
+
const eventPointerId = "pointerId" in event ? event.pointerId : void 0;
|
|
1145
1261
|
const raycastEvent = {
|
|
1146
1262
|
...hit,
|
|
1147
1263
|
...extractEventProps,
|
|
@@ -1152,18 +1268,19 @@ function createEvents(store) {
|
|
|
1152
1268
|
unprojectedPoint,
|
|
1153
1269
|
ray: raycaster.ray,
|
|
1154
1270
|
camera,
|
|
1271
|
+
pointerId: eventPointerId,
|
|
1155
1272
|
// Hijack stopPropagation, which just sets a flag
|
|
1156
1273
|
stopPropagation() {
|
|
1157
|
-
const
|
|
1274
|
+
const pointerState = eventPointerId !== void 0 ? internal.pointerMap.get(eventPointerId) : void 0;
|
|
1158
1275
|
if (
|
|
1159
1276
|
// ...if this pointer hasn't been captured
|
|
1160
|
-
!
|
|
1161
|
-
|
|
1277
|
+
!pointerState?.captured.size || // ... or if the hit object is capturing the pointer
|
|
1278
|
+
pointerState.captured.has(hit.eventObject)
|
|
1162
1279
|
) {
|
|
1163
1280
|
raycastEvent.stopped = localState.stopped = true;
|
|
1164
|
-
if (
|
|
1281
|
+
if (pointerState?.hovered.size && Array.from(pointerState.hovered.values()).find((i) => i.eventObject === hit.eventObject)) {
|
|
1165
1282
|
const higher = intersections.slice(0, intersections.indexOf(hit));
|
|
1166
|
-
cancelPointer([...higher, hit]);
|
|
1283
|
+
cancelPointer([...higher, hit], eventPointerId);
|
|
1167
1284
|
}
|
|
1168
1285
|
}
|
|
1169
1286
|
},
|
|
@@ -1179,15 +1296,18 @@ function createEvents(store) {
|
|
|
1179
1296
|
}
|
|
1180
1297
|
return intersections;
|
|
1181
1298
|
}
|
|
1182
|
-
function cancelPointer(intersections) {
|
|
1299
|
+
function cancelPointer(intersections, pointerId) {
|
|
1183
1300
|
const { internal } = store.getState();
|
|
1184
|
-
|
|
1301
|
+
const pid = pointerId ?? DEFAULT_POINTER_ID;
|
|
1302
|
+
const pointerState = internal.pointerMap.get(pid);
|
|
1303
|
+
if (!pointerState) return;
|
|
1304
|
+
for (const [hoveredId, hoveredObj] of pointerState.hovered) {
|
|
1185
1305
|
if (!intersections.length || !intersections.find(
|
|
1186
1306
|
(hit) => hit.object === hoveredObj.object && hit.index === hoveredObj.index && hit.instanceId === hoveredObj.instanceId
|
|
1187
1307
|
)) {
|
|
1188
1308
|
const eventObject = hoveredObj.eventObject;
|
|
1189
1309
|
const instance = eventObject.__r3f;
|
|
1190
|
-
|
|
1310
|
+
pointerState.hovered.delete(hoveredId);
|
|
1191
1311
|
if (instance?.eventCount) {
|
|
1192
1312
|
const handlers = instance.handlers;
|
|
1193
1313
|
const data = { ...hoveredObj, intersections };
|
|
@@ -1216,41 +1336,118 @@ function createEvents(store) {
|
|
|
1216
1336
|
instance?.handlers.onDropMissed?.(event);
|
|
1217
1337
|
}
|
|
1218
1338
|
}
|
|
1339
|
+
function cleanupPointer(pointerId) {
|
|
1340
|
+
const { internal } = store.getState();
|
|
1341
|
+
const pointerState = internal.pointerMap.get(pointerId);
|
|
1342
|
+
if (pointerState) {
|
|
1343
|
+
for (const [, hoveredObj] of pointerState.hovered) {
|
|
1344
|
+
const eventObject = hoveredObj.eventObject;
|
|
1345
|
+
const instance = eventObject.__r3f;
|
|
1346
|
+
if (instance?.eventCount) {
|
|
1347
|
+
const handlers = instance.handlers;
|
|
1348
|
+
const data = { ...hoveredObj, intersections: [] };
|
|
1349
|
+
handlers.onPointerOut?.(data);
|
|
1350
|
+
handlers.onPointerLeave?.(data);
|
|
1351
|
+
}
|
|
1352
|
+
}
|
|
1353
|
+
internal.pointerMap.delete(pointerId);
|
|
1354
|
+
}
|
|
1355
|
+
internal.pointerDirty.delete(pointerId);
|
|
1356
|
+
}
|
|
1357
|
+
function processDeferredPointer(event, pointerId) {
|
|
1358
|
+
const state = store.getState();
|
|
1359
|
+
const { internal } = state;
|
|
1360
|
+
if (!state.events.enabled) return;
|
|
1361
|
+
const filter = filterPointerEvents;
|
|
1362
|
+
const hits = intersect(event, filter);
|
|
1363
|
+
cancelPointer(hits, pointerId);
|
|
1364
|
+
function onIntersect(data) {
|
|
1365
|
+
const eventObject = data.eventObject;
|
|
1366
|
+
const instance = eventObject.__r3f;
|
|
1367
|
+
if (!instance?.eventCount) return;
|
|
1368
|
+
const handlers = instance.handlers;
|
|
1369
|
+
if (handlers.onPointerOver || handlers.onPointerEnter || handlers.onPointerOut || handlers.onPointerLeave) {
|
|
1370
|
+
const id = makeId(data);
|
|
1371
|
+
const pointerState = getPointerState(internal, pointerId);
|
|
1372
|
+
const hoveredItem = pointerState.hovered.get(id);
|
|
1373
|
+
if (!hoveredItem) {
|
|
1374
|
+
pointerState.hovered.set(id, data);
|
|
1375
|
+
handlers.onPointerOver?.(data);
|
|
1376
|
+
handlers.onPointerEnter?.(data);
|
|
1377
|
+
} else if (hoveredItem.stopped) {
|
|
1378
|
+
data.stopPropagation();
|
|
1379
|
+
}
|
|
1380
|
+
}
|
|
1381
|
+
handlers.onPointerMove?.(data);
|
|
1382
|
+
}
|
|
1383
|
+
handleIntersects(hits, event, 0, onIntersect);
|
|
1384
|
+
}
|
|
1219
1385
|
function handlePointer(name) {
|
|
1220
1386
|
switch (name) {
|
|
1221
1387
|
case "onPointerLeave":
|
|
1222
|
-
case "onPointerCancel":
|
|
1223
1388
|
case "onDragLeave":
|
|
1224
1389
|
return () => cancelPointer([]);
|
|
1390
|
+
// Global cancel of these events
|
|
1391
|
+
case "onPointerCancel":
|
|
1392
|
+
return (event) => {
|
|
1393
|
+
const pointerId = getPointerId(event);
|
|
1394
|
+
cleanupPointer(pointerId);
|
|
1395
|
+
};
|
|
1225
1396
|
case "onLostPointerCapture":
|
|
1226
1397
|
return (event) => {
|
|
1227
1398
|
const { internal } = store.getState();
|
|
1228
|
-
|
|
1399
|
+
const pointerId = getPointerId(event);
|
|
1400
|
+
const pointerState = internal.pointerMap.get(pointerId);
|
|
1401
|
+
if (pointerState?.captured.size) {
|
|
1229
1402
|
requestAnimationFrame(() => {
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1403
|
+
const pointerState2 = internal.pointerMap.get(pointerId);
|
|
1404
|
+
if (pointerState2?.captured.size) {
|
|
1405
|
+
pointerState2.captured.clear();
|
|
1233
1406
|
}
|
|
1407
|
+
cancelPointer([], pointerId);
|
|
1234
1408
|
});
|
|
1235
1409
|
}
|
|
1236
1410
|
};
|
|
1237
1411
|
}
|
|
1238
1412
|
return function handleEvent(event) {
|
|
1239
1413
|
const state = store.getState();
|
|
1240
|
-
const { onPointerMissed, onDragOverMissed, onDropMissed, internal } = state;
|
|
1414
|
+
const { onPointerMissed, onDragOverMissed, onDropMissed, internal, events } = state;
|
|
1415
|
+
const pointerId = getPointerId(event);
|
|
1241
1416
|
internal.lastEvent.current = event;
|
|
1242
|
-
if (!
|
|
1417
|
+
if (!events.enabled) return;
|
|
1243
1418
|
const isPointerMove = name === "onPointerMove";
|
|
1244
1419
|
const isDragOver = name === "onDragOver";
|
|
1245
1420
|
const isDrop = name === "onDrop";
|
|
1246
1421
|
const isClickEvent = name === "onClick" || name === "onContextMenu" || name === "onDoubleClick";
|
|
1422
|
+
const isPointerDown = name === "onPointerDown";
|
|
1423
|
+
const isPointerUp = name === "onPointerUp";
|
|
1424
|
+
const isWheel = name === "onWheel";
|
|
1425
|
+
const canDeferRaycasts = events.frameTimedRaycasts && state.frameloop === "always";
|
|
1426
|
+
if (isPointerMove && canDeferRaycasts) {
|
|
1427
|
+
events.compute?.(event, state);
|
|
1428
|
+
internal.pointerDirty.set(pointerId, event);
|
|
1429
|
+
return;
|
|
1430
|
+
}
|
|
1431
|
+
if (isWheel && canDeferRaycasts && !events.alwaysFireOnScroll) {
|
|
1432
|
+
events.compute?.(event, state);
|
|
1433
|
+
internal.pointerDirty.set(pointerId, event);
|
|
1434
|
+
return;
|
|
1435
|
+
}
|
|
1436
|
+
if ((isClickEvent || isPointerDown || isPointerUp) && internal.pointerDirty.has(pointerId)) {
|
|
1437
|
+
const deferredEvent = internal.pointerDirty.get(pointerId);
|
|
1438
|
+
internal.pointerDirty.delete(pointerId);
|
|
1439
|
+
processDeferredPointer(deferredEvent, pointerId);
|
|
1440
|
+
}
|
|
1247
1441
|
const filter = isPointerMove || isDragOver || isDrop ? filterPointerEvents : void 0;
|
|
1248
1442
|
const hits = intersect(event, filter);
|
|
1249
|
-
const delta = isClickEvent ? calculateDistance(event) : 0;
|
|
1250
|
-
if (
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1443
|
+
const delta = isClickEvent ? calculateDistance(event, pointerId) : 0;
|
|
1444
|
+
if (isPointerDown) {
|
|
1445
|
+
const pointerState2 = getPointerState(internal, pointerId);
|
|
1446
|
+
pointerState2.initialClick = [event.offsetX, event.offsetY];
|
|
1447
|
+
pointerState2.initialHits = hits.map((hit) => hit.eventObject);
|
|
1448
|
+
}
|
|
1449
|
+
const pointerState = internal.pointerMap.get(pointerId);
|
|
1450
|
+
const initialHits = pointerState?.initialHits ?? [];
|
|
1254
1451
|
if (isClickEvent && !hits.length) {
|
|
1255
1452
|
if (delta <= 2) {
|
|
1256
1453
|
pointerMissed(event, internal.interaction);
|
|
@@ -1265,7 +1462,9 @@ function createEvents(store) {
|
|
|
1265
1462
|
dropMissed(event, internal.interaction);
|
|
1266
1463
|
if (onDropMissed) onDropMissed(event);
|
|
1267
1464
|
}
|
|
1268
|
-
if (isPointerMove || isDragOver)
|
|
1465
|
+
if (isPointerMove || isDragOver) {
|
|
1466
|
+
cancelPointer(hits, pointerId);
|
|
1467
|
+
}
|
|
1269
1468
|
function onIntersect(data) {
|
|
1270
1469
|
const eventObject = data.eventObject;
|
|
1271
1470
|
const instance = eventObject.__r3f;
|
|
@@ -1274,9 +1473,10 @@ function createEvents(store) {
|
|
|
1274
1473
|
if (isPointerMove) {
|
|
1275
1474
|
if (handlers.onPointerOver || handlers.onPointerEnter || handlers.onPointerOut || handlers.onPointerLeave) {
|
|
1276
1475
|
const id = makeId(data);
|
|
1277
|
-
const
|
|
1476
|
+
const pointerState2 = getPointerState(internal, pointerId);
|
|
1477
|
+
const hoveredItem = pointerState2.hovered.get(id);
|
|
1278
1478
|
if (!hoveredItem) {
|
|
1279
|
-
|
|
1479
|
+
pointerState2.hovered.set(id, data);
|
|
1280
1480
|
handlers.onPointerOver?.(data);
|
|
1281
1481
|
handlers.onPointerEnter?.(data);
|
|
1282
1482
|
} else if (hoveredItem.stopped) {
|
|
@@ -1286,9 +1486,10 @@ function createEvents(store) {
|
|
|
1286
1486
|
handlers.onPointerMove?.(data);
|
|
1287
1487
|
} else if (isDragOver) {
|
|
1288
1488
|
const id = makeId(data);
|
|
1289
|
-
const
|
|
1489
|
+
const pointerState2 = getPointerState(internal, pointerId);
|
|
1490
|
+
const hoveredItem = pointerState2.hovered.get(id);
|
|
1290
1491
|
if (!hoveredItem) {
|
|
1291
|
-
|
|
1492
|
+
pointerState2.hovered.set(id, data);
|
|
1292
1493
|
handlers.onDragOverEnter?.(data);
|
|
1293
1494
|
} else if (hoveredItem.stopped) {
|
|
1294
1495
|
data.stopPropagation();
|
|
@@ -1299,18 +1500,18 @@ function createEvents(store) {
|
|
|
1299
1500
|
} else {
|
|
1300
1501
|
const handler = handlers[name];
|
|
1301
1502
|
if (handler) {
|
|
1302
|
-
if (!isClickEvent ||
|
|
1503
|
+
if (!isClickEvent || initialHits.includes(eventObject)) {
|
|
1303
1504
|
pointerMissed(
|
|
1304
1505
|
event,
|
|
1305
|
-
internal.interaction.filter((object) => !
|
|
1506
|
+
internal.interaction.filter((object) => !initialHits.includes(object))
|
|
1306
1507
|
);
|
|
1307
1508
|
handler(data);
|
|
1308
1509
|
}
|
|
1309
1510
|
} else {
|
|
1310
|
-
if (isClickEvent &&
|
|
1511
|
+
if (isClickEvent && initialHits.includes(eventObject)) {
|
|
1311
1512
|
pointerMissed(
|
|
1312
1513
|
event,
|
|
1313
|
-
internal.interaction.filter((object) => !
|
|
1514
|
+
internal.interaction.filter((object) => !initialHits.includes(object))
|
|
1314
1515
|
);
|
|
1315
1516
|
}
|
|
1316
1517
|
}
|
|
@@ -1319,7 +1520,15 @@ function createEvents(store) {
|
|
|
1319
1520
|
handleIntersects(hits, event, delta, onIntersect);
|
|
1320
1521
|
};
|
|
1321
1522
|
}
|
|
1322
|
-
|
|
1523
|
+
function flushDeferredPointers() {
|
|
1524
|
+
const { internal, events } = store.getState();
|
|
1525
|
+
if (!events.frameTimedRaycasts) return;
|
|
1526
|
+
for (const [pointerId, event] of internal.pointerDirty) {
|
|
1527
|
+
processDeferredPointer(event, pointerId);
|
|
1528
|
+
}
|
|
1529
|
+
internal.pointerDirty.clear();
|
|
1530
|
+
}
|
|
1531
|
+
return { handlePointer, flushDeferredPointers, processDeferredPointer };
|
|
1323
1532
|
}
|
|
1324
1533
|
const DOM_EVENTS = {
|
|
1325
1534
|
onClick: ["click", false],
|
|
@@ -1338,10 +1547,15 @@ const DOM_EVENTS = {
|
|
|
1338
1547
|
onLostPointerCapture: ["lostpointercapture", true]
|
|
1339
1548
|
};
|
|
1340
1549
|
function createPointerEvents(store) {
|
|
1341
|
-
const { handlePointer } = createEvents(store);
|
|
1550
|
+
const { handlePointer, flushDeferredPointers, processDeferredPointer } = createEvents(store);
|
|
1551
|
+
let nextXRPointerId = XR_POINTER_ID_START;
|
|
1552
|
+
const xrPointers = /* @__PURE__ */ new Map();
|
|
1342
1553
|
return {
|
|
1343
1554
|
priority: 1,
|
|
1344
1555
|
enabled: true,
|
|
1556
|
+
frameTimedRaycasts: true,
|
|
1557
|
+
alwaysFireOnScroll: true,
|
|
1558
|
+
updateOnFrame: false,
|
|
1345
1559
|
compute(event, state) {
|
|
1346
1560
|
state.pointer.set(event.offsetX / state.size.width * 2 - 1, -(event.offsetY / state.size.height) * 2 + 1);
|
|
1347
1561
|
state.raycaster.setFromCamera(state.pointer, state.camera);
|
|
@@ -1351,11 +1565,33 @@ function createPointerEvents(store) {
|
|
|
1351
1565
|
(acc, key) => ({ ...acc, [key]: handlePointer(key) }),
|
|
1352
1566
|
{}
|
|
1353
1567
|
),
|
|
1354
|
-
update: () => {
|
|
1568
|
+
update: (pointerId) => {
|
|
1569
|
+
const { events, internal } = store.getState();
|
|
1570
|
+
if (!events.handlers) return;
|
|
1571
|
+
if (pointerId !== void 0) {
|
|
1572
|
+
const event = internal.pointerDirty.get(pointerId);
|
|
1573
|
+
if (event) {
|
|
1574
|
+
internal.pointerDirty.delete(pointerId);
|
|
1575
|
+
processDeferredPointer(event, pointerId);
|
|
1576
|
+
} else if (internal.lastEvent?.current) {
|
|
1577
|
+
processDeferredPointer(internal.lastEvent.current, pointerId);
|
|
1578
|
+
}
|
|
1579
|
+
} else {
|
|
1580
|
+
flushDeferredPointers();
|
|
1581
|
+
if (internal.lastEvent?.current) {
|
|
1582
|
+
events.handlers.onPointerMove(internal.lastEvent.current);
|
|
1583
|
+
}
|
|
1584
|
+
}
|
|
1585
|
+
},
|
|
1586
|
+
flush: () => {
|
|
1355
1587
|
const { events, internal } = store.getState();
|
|
1356
|
-
|
|
1588
|
+
flushDeferredPointers();
|
|
1589
|
+
if (events.updateOnFrame && internal.lastEvent?.current && events.handlers) {
|
|
1590
|
+
events.handlers.onPointerMove(internal.lastEvent.current);
|
|
1591
|
+
}
|
|
1357
1592
|
},
|
|
1358
1593
|
connect: (target) => {
|
|
1594
|
+
if (!target) return;
|
|
1359
1595
|
const { set, events } = store.getState();
|
|
1360
1596
|
events.disconnect?.();
|
|
1361
1597
|
set((state) => ({ events: { ...state.events, connected: target } }));
|
|
@@ -1379,6 +1615,33 @@ function createPointerEvents(store) {
|
|
|
1379
1615
|
}
|
|
1380
1616
|
set((state) => ({ events: { ...state.events, connected: void 0 } }));
|
|
1381
1617
|
}
|
|
1618
|
+
},
|
|
1619
|
+
// PREVIEW (A8): the XR pointer registration API may change before stable.
|
|
1620
|
+
registerPointer: (config) => {
|
|
1621
|
+
const pointerId = nextXRPointerId++;
|
|
1622
|
+
xrPointers.set(pointerId, config);
|
|
1623
|
+
const { internal } = store.getState();
|
|
1624
|
+
getPointerState(internal, pointerId);
|
|
1625
|
+
return pointerId;
|
|
1626
|
+
},
|
|
1627
|
+
unregisterPointer: (pointerId) => {
|
|
1628
|
+
xrPointers.delete(pointerId);
|
|
1629
|
+
const { internal } = store.getState();
|
|
1630
|
+
const pointerState = internal.pointerMap.get(pointerId);
|
|
1631
|
+
if (pointerState) {
|
|
1632
|
+
for (const [, hoveredObj] of pointerState.hovered) {
|
|
1633
|
+
const eventObject = hoveredObj.eventObject;
|
|
1634
|
+
const instance = eventObject.__r3f;
|
|
1635
|
+
if (instance?.eventCount) {
|
|
1636
|
+
const handlers = instance.handlers;
|
|
1637
|
+
const data = { ...hoveredObj, intersections: [] };
|
|
1638
|
+
handlers.onPointerOut?.(data);
|
|
1639
|
+
handlers.onPointerLeave?.(data);
|
|
1640
|
+
}
|
|
1641
|
+
}
|
|
1642
|
+
internal.pointerMap.delete(pointerId);
|
|
1643
|
+
}
|
|
1644
|
+
internal.pointerDirty.delete(pointerId);
|
|
1382
1645
|
}
|
|
1383
1646
|
};
|
|
1384
1647
|
}
|
|
@@ -1440,1058 +1703,33 @@ function notifyAlpha({ message, link }) {
|
|
|
1440
1703
|
}
|
|
1441
1704
|
}
|
|
1442
1705
|
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
const
|
|
1447
|
-
|
|
1448
|
-
constructor() {
|
|
1449
|
-
/** Ordered list of phase nodes */
|
|
1450
|
-
__publicField$2(this, "phases", []);
|
|
1451
|
-
/** Quick lookup by name */
|
|
1452
|
-
__publicField$2(this, "phaseMap", /* @__PURE__ */ new Map());
|
|
1453
|
-
/** Cached ordered names (invalidated on changes) */
|
|
1454
|
-
__publicField$2(this, "orderedNamesCache", null);
|
|
1455
|
-
this.initializeDefaultPhases();
|
|
1456
|
-
}
|
|
1457
|
-
//* Initialization --------------------------------
|
|
1458
|
-
initializeDefaultPhases() {
|
|
1459
|
-
for (const name of DEFAULT_PHASES) {
|
|
1460
|
-
const node = { name, isAutoGenerated: false };
|
|
1461
|
-
this.phases.push(node);
|
|
1462
|
-
this.phaseMap.set(name, node);
|
|
1463
|
-
}
|
|
1464
|
-
this.invalidateCache();
|
|
1465
|
-
}
|
|
1466
|
-
//* Public API --------------------------------
|
|
1467
|
-
/**
|
|
1468
|
-
* Add a named phase to the graph
|
|
1469
|
-
* @param name - Phase name (must be unique)
|
|
1470
|
-
* @param options - Position options (before or after another phase)
|
|
1471
|
-
*/
|
|
1472
|
-
addPhase(name, options = {}) {
|
|
1473
|
-
if (this.phaseMap.has(name)) {
|
|
1474
|
-
console.warn(`[useFrame] Phase "${name}" already exists`);
|
|
1475
|
-
return;
|
|
1476
|
-
}
|
|
1477
|
-
const { before, after } = options;
|
|
1478
|
-
const node = { name, isAutoGenerated: false };
|
|
1479
|
-
let insertIndex = this.phases.length;
|
|
1480
|
-
const targetIndex = this.getPhaseIndex(before ?? after);
|
|
1481
|
-
if (targetIndex !== -1) {
|
|
1482
|
-
insertIndex = before ? targetIndex : targetIndex + 1;
|
|
1483
|
-
} else {
|
|
1484
|
-
const constraintType = before ? "before" : "after";
|
|
1485
|
-
console.warn(`[useFrame] Phase "${before ?? after}" not found for '${constraintType}' constraint`);
|
|
1486
|
-
}
|
|
1487
|
-
this.phases.splice(insertIndex, 0, node);
|
|
1488
|
-
this.phaseMap.set(name, node);
|
|
1489
|
-
this.invalidateCache();
|
|
1490
|
-
}
|
|
1491
|
-
/**
|
|
1492
|
-
* Get ordered list of phase names
|
|
1493
|
-
*/
|
|
1494
|
-
getOrderedPhases() {
|
|
1495
|
-
if (this.orderedNamesCache === null) this.orderedNamesCache = this.phases.map((p) => p.name);
|
|
1496
|
-
return this.orderedNamesCache;
|
|
1497
|
-
}
|
|
1498
|
-
/**
|
|
1499
|
-
* Check if a phase exists
|
|
1500
|
-
*/
|
|
1501
|
-
hasPhase(name) {
|
|
1502
|
-
return this.phaseMap.has(name);
|
|
1503
|
-
}
|
|
1504
|
-
/**
|
|
1505
|
-
* Get the index of a phase (-1 if not found)
|
|
1506
|
-
*/
|
|
1507
|
-
getPhaseIndex(name) {
|
|
1508
|
-
if (!name) return -1;
|
|
1509
|
-
return this.phases.findIndex((p) => p.name === name);
|
|
1510
|
-
}
|
|
1511
|
-
/**
|
|
1512
|
-
* Ensure a phase exists, creating an auto-generated one if needed.
|
|
1513
|
-
* Used for resolving before/after constraints.
|
|
1514
|
-
*
|
|
1515
|
-
* @param name - The phase name to ensure exists
|
|
1516
|
-
* @returns The phase name (may be auto-generated like 'before:render')
|
|
1517
|
-
*/
|
|
1518
|
-
ensurePhase(name) {
|
|
1519
|
-
if (this.phaseMap.has(name)) return name;
|
|
1520
|
-
const node = { name, isAutoGenerated: true };
|
|
1521
|
-
this.phases.push(node);
|
|
1522
|
-
this.phaseMap.set(name, node);
|
|
1523
|
-
this.invalidateCache();
|
|
1524
|
-
return name;
|
|
1525
|
-
}
|
|
1526
|
-
/**
|
|
1527
|
-
* Resolve where a job with before/after constraints should go.
|
|
1528
|
-
* Creates auto-generated phases if needed.
|
|
1529
|
-
*
|
|
1530
|
-
* @param before - Phase(s) to run before
|
|
1531
|
-
* @param after - Phase(s) to run after
|
|
1532
|
-
* @returns The resolved phase name
|
|
1533
|
-
*/
|
|
1534
|
-
resolveConstraintPhase(before, after) {
|
|
1535
|
-
const beforeArr = before ? Array.isArray(before) ? before : [before] : [];
|
|
1536
|
-
const afterArr = after ? Array.isArray(after) ? after : [after] : [];
|
|
1537
|
-
if (beforeArr.length > 0) {
|
|
1538
|
-
return this.ensureAutoPhase(beforeArr[0], "before", 0);
|
|
1539
|
-
}
|
|
1540
|
-
if (afterArr.length > 0) {
|
|
1541
|
-
return this.ensureAutoPhase(afterArr[0], "after", 1);
|
|
1542
|
-
}
|
|
1543
|
-
return "update";
|
|
1544
|
-
}
|
|
1545
|
-
/**
|
|
1546
|
-
* Ensure an auto-generated phase exists relative to a target phase.
|
|
1547
|
-
* Creates the phase if it doesn't exist, inserting it at the correct position.
|
|
1548
|
-
*
|
|
1549
|
-
* @param target - The target phase name to position relative to
|
|
1550
|
-
* @param prefix - Prefix for auto-generated phase name ('before' or 'after')
|
|
1551
|
-
* @param offset - Insertion offset (0 for before, 1 for after)
|
|
1552
|
-
* @returns The auto-generated phase name
|
|
1553
|
-
*/
|
|
1554
|
-
ensureAutoPhase(target, prefix, offset) {
|
|
1555
|
-
const autoName = `${prefix}:${target}`;
|
|
1556
|
-
if (this.phaseMap.has(autoName)) return autoName;
|
|
1557
|
-
const node = { name: autoName, isAutoGenerated: true };
|
|
1558
|
-
const targetIndex = this.getPhaseIndex(target);
|
|
1559
|
-
if (targetIndex !== -1) this.phases.splice(targetIndex + offset, 0, node);
|
|
1560
|
-
else this.phases.push(node);
|
|
1561
|
-
this.phaseMap.set(autoName, node);
|
|
1562
|
-
this.invalidateCache();
|
|
1563
|
-
return autoName;
|
|
1564
|
-
}
|
|
1565
|
-
// Internal --------------------------------
|
|
1566
|
-
invalidateCache() {
|
|
1567
|
-
this.orderedNamesCache = null;
|
|
1568
|
-
}
|
|
1569
|
-
}
|
|
1570
|
-
|
|
1571
|
-
function rebuildSortedJobs(jobs, phaseGraph) {
|
|
1572
|
-
const orderedPhases = phaseGraph.getOrderedPhases();
|
|
1573
|
-
const buckets = /* @__PURE__ */ new Map();
|
|
1574
|
-
for (const phase of orderedPhases) {
|
|
1575
|
-
buckets.set(phase, []);
|
|
1576
|
-
}
|
|
1577
|
-
for (const job of jobs.values()) {
|
|
1578
|
-
if (!job.enabled) continue;
|
|
1579
|
-
let bucket = buckets.get(job.phase);
|
|
1580
|
-
if (!bucket) {
|
|
1581
|
-
bucket = [];
|
|
1582
|
-
buckets.set(job.phase, bucket);
|
|
1583
|
-
}
|
|
1584
|
-
bucket.push(job);
|
|
1585
|
-
}
|
|
1586
|
-
const sortedBuckets = [];
|
|
1587
|
-
for (const phase of orderedPhases) {
|
|
1588
|
-
const bucket = buckets.get(phase);
|
|
1589
|
-
if (!bucket || bucket.length === 0) continue;
|
|
1590
|
-
bucket.sort((a, b) => {
|
|
1591
|
-
if (a.priority !== b.priority) return b.priority - a.priority;
|
|
1592
|
-
return a.index - b.index;
|
|
1593
|
-
});
|
|
1594
|
-
sortedBuckets.push(hasCrossJobConstraints(bucket) ? topologicalSort(bucket) : bucket);
|
|
1595
|
-
}
|
|
1596
|
-
for (const [phase, bucket] of buckets) {
|
|
1597
|
-
if (!orderedPhases.includes(phase) && bucket.length > 0) {
|
|
1598
|
-
bucket.sort((a, b) => {
|
|
1599
|
-
if (a.priority !== b.priority) return b.priority - a.priority;
|
|
1600
|
-
return a.index - b.index;
|
|
1601
|
-
});
|
|
1602
|
-
sortedBuckets.push(bucket);
|
|
1603
|
-
}
|
|
1604
|
-
}
|
|
1605
|
-
return sortedBuckets.flat();
|
|
1606
|
-
}
|
|
1607
|
-
function hasCrossJobConstraints(bucket) {
|
|
1608
|
-
const jobIds = new Set(bucket.map((j) => j.id));
|
|
1609
|
-
for (const job of bucket) {
|
|
1610
|
-
for (const ref of job.before) {
|
|
1611
|
-
if (jobIds.has(ref)) return true;
|
|
1612
|
-
}
|
|
1613
|
-
for (const ref of job.after) {
|
|
1614
|
-
if (jobIds.has(ref)) return true;
|
|
1615
|
-
}
|
|
1616
|
-
}
|
|
1617
|
-
return false;
|
|
1618
|
-
}
|
|
1619
|
-
function topologicalSort(jobs) {
|
|
1620
|
-
const n = jobs.length;
|
|
1621
|
-
if (n <= 1) return jobs;
|
|
1622
|
-
const jobMap = /* @__PURE__ */ new Map();
|
|
1623
|
-
const inDegree = /* @__PURE__ */ new Map();
|
|
1624
|
-
const adjacency = /* @__PURE__ */ new Map();
|
|
1625
|
-
for (const job of jobs) {
|
|
1626
|
-
jobMap.set(job.id, job);
|
|
1627
|
-
inDegree.set(job.id, 0);
|
|
1628
|
-
adjacency.set(job.id, []);
|
|
1629
|
-
}
|
|
1630
|
-
for (const job of jobs) {
|
|
1631
|
-
for (const ref of job.before) {
|
|
1632
|
-
if (jobMap.has(ref)) {
|
|
1633
|
-
adjacency.get(job.id).push(ref);
|
|
1634
|
-
inDegree.set(ref, inDegree.get(ref) + 1);
|
|
1635
|
-
}
|
|
1636
|
-
}
|
|
1637
|
-
for (const ref of job.after) {
|
|
1638
|
-
if (jobMap.has(ref)) {
|
|
1639
|
-
adjacency.get(ref).push(job.id);
|
|
1640
|
-
inDegree.set(job.id, inDegree.get(job.id) + 1);
|
|
1641
|
-
}
|
|
1642
|
-
}
|
|
1643
|
-
}
|
|
1644
|
-
const queue = [];
|
|
1645
|
-
for (const job of jobs) {
|
|
1646
|
-
if (inDegree.get(job.id) === 0) {
|
|
1647
|
-
queue.push(job);
|
|
1648
|
-
}
|
|
1649
|
-
}
|
|
1650
|
-
queue.sort((a, b) => {
|
|
1651
|
-
if (a.priority !== b.priority) return b.priority - a.priority;
|
|
1652
|
-
return a.index - b.index;
|
|
1653
|
-
});
|
|
1654
|
-
const result = [];
|
|
1655
|
-
while (queue.length > 0) {
|
|
1656
|
-
const job = queue.shift();
|
|
1657
|
-
result.push(job);
|
|
1658
|
-
const neighbors = adjacency.get(job.id) || [];
|
|
1659
|
-
for (const neighborId of neighbors) {
|
|
1660
|
-
const newDegree = inDegree.get(neighborId) - 1;
|
|
1661
|
-
inDegree.set(neighborId, newDegree);
|
|
1662
|
-
if (newDegree === 0) {
|
|
1663
|
-
const neighbor = jobMap.get(neighborId);
|
|
1664
|
-
insertSorted(queue, neighbor);
|
|
1665
|
-
}
|
|
1666
|
-
}
|
|
1667
|
-
}
|
|
1668
|
-
if (result.length !== n) {
|
|
1669
|
-
console.warn("[useFrame] Circular dependency detected in job constraints");
|
|
1670
|
-
const resultIds = new Set(result.map((j) => j.id));
|
|
1671
|
-
for (const job of jobs) {
|
|
1672
|
-
if (!resultIds.has(job.id)) result.push(job);
|
|
1673
|
-
}
|
|
1674
|
-
}
|
|
1675
|
-
return result;
|
|
1676
|
-
}
|
|
1677
|
-
function insertSorted(arr, job) {
|
|
1678
|
-
let i = 0;
|
|
1679
|
-
while (i < arr.length) {
|
|
1680
|
-
const cmp = arr[i];
|
|
1681
|
-
if (job.priority > cmp.priority || job.priority === cmp.priority && job.index < cmp.index) {
|
|
1682
|
-
break;
|
|
1683
|
-
}
|
|
1684
|
-
i++;
|
|
1685
|
-
}
|
|
1686
|
-
arr.splice(i, 0, job);
|
|
1687
|
-
}
|
|
1688
|
-
|
|
1689
|
-
function shouldRun(job, now) {
|
|
1690
|
-
if (!job.enabled) return false;
|
|
1691
|
-
if (!job.fps) return true;
|
|
1692
|
-
const minInterval = 1e3 / job.fps;
|
|
1693
|
-
const lastRun = job.lastRun ?? 0;
|
|
1694
|
-
const elapsed = now - lastRun;
|
|
1695
|
-
if (elapsed < minInterval) return false;
|
|
1696
|
-
if (job.drop) {
|
|
1697
|
-
job.lastRun = now;
|
|
1698
|
-
} else {
|
|
1699
|
-
const steps = Math.floor(elapsed / minInterval);
|
|
1700
|
-
job.lastRun = lastRun + steps * minInterval;
|
|
1701
|
-
if (job.lastRun < now - minInterval) {
|
|
1702
|
-
job.lastRun = now;
|
|
1703
|
-
}
|
|
1704
|
-
}
|
|
1705
|
-
return true;
|
|
1706
|
-
}
|
|
1707
|
-
function resetJobTiming(job) {
|
|
1708
|
-
job.lastRun = void 0;
|
|
1706
|
+
const INTERNAL_LIB_MARKERS = ["zustand", "setState", "Object.assign"];
|
|
1707
|
+
function isInternalRendererAccess(stack, callerDepth = 2) {
|
|
1708
|
+
if (!stack) return false;
|
|
1709
|
+
const caller = stack.split("\n")[callerDepth] ?? "";
|
|
1710
|
+
return INTERNAL_LIB_MARKERS.some((m) => caller.includes(m));
|
|
1709
1711
|
}
|
|
1710
1712
|
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
const
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
const
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
rafHandle: null,
|
|
1732
|
-
lastTime: null,
|
|
1733
|
-
// null = uninitialized, 0+ = valid timestamp
|
|
1734
|
-
frameCount: 0,
|
|
1735
|
-
elapsedTime: 0,
|
|
1736
|
-
createdAt: performance.now()
|
|
1737
|
-
});
|
|
1738
|
-
__publicField$1(this, "stoppedTime", 0);
|
|
1739
|
-
//* Private State ================================
|
|
1740
|
-
__publicField$1(this, "nextRootIndex", 0);
|
|
1741
|
-
__publicField$1(this, "globalBeforeJobs", /* @__PURE__ */ new Map());
|
|
1742
|
-
__publicField$1(this, "globalAfterJobs", /* @__PURE__ */ new Map());
|
|
1743
|
-
__publicField$1(this, "nextGlobalIndex", 0);
|
|
1744
|
-
__publicField$1(this, "idleCallbacks", /* @__PURE__ */ new Set());
|
|
1745
|
-
__publicField$1(this, "nextJobIndex", 0);
|
|
1746
|
-
__publicField$1(this, "jobStateListeners", /* @__PURE__ */ new Map());
|
|
1747
|
-
__publicField$1(this, "pendingFrames", 0);
|
|
1748
|
-
__publicField$1(this, "_frameloop", "always");
|
|
1749
|
-
//* Independent Mode & Error Handling State ================================
|
|
1750
|
-
__publicField$1(this, "_independent", false);
|
|
1751
|
-
__publicField$1(this, "errorHandler", null);
|
|
1752
|
-
__publicField$1(this, "rootReadyCallbacks", /* @__PURE__ */ new Set());
|
|
1753
|
-
//* Core Loop Execution Methods ================================
|
|
1754
|
-
/**
|
|
1755
|
-
* Main RAF loop callback.
|
|
1756
|
-
* Executes frame, handles demand mode, and schedules next frame.
|
|
1757
|
-
* @param {number} timestamp - RAF timestamp in milliseconds
|
|
1758
|
-
* @returns {void}
|
|
1759
|
-
* @private
|
|
1760
|
-
*/
|
|
1761
|
-
__publicField$1(this, "loop", (timestamp) => {
|
|
1762
|
-
if (!this.loopState.running) return;
|
|
1763
|
-
this.executeFrame(timestamp);
|
|
1764
|
-
if (this._frameloop === "demand") {
|
|
1765
|
-
this.pendingFrames = Math.max(0, this.pendingFrames - 1);
|
|
1766
|
-
if (this.pendingFrames === 0) {
|
|
1767
|
-
this.notifyIdle(timestamp);
|
|
1768
|
-
return this.stop();
|
|
1769
|
-
}
|
|
1770
|
-
}
|
|
1771
|
-
this.loopState.rafHandle = requestAnimationFrame(this.loop);
|
|
1772
|
-
});
|
|
1773
|
-
this.phaseGraph = new PhaseGraph();
|
|
1774
|
-
}
|
|
1775
|
-
static get instance() {
|
|
1776
|
-
return globalThis[_Scheduler.INSTANCE_KEY] ?? null;
|
|
1777
|
-
}
|
|
1778
|
-
static set instance(value) {
|
|
1779
|
-
globalThis[_Scheduler.INSTANCE_KEY] = value;
|
|
1780
|
-
}
|
|
1781
|
-
/**
|
|
1782
|
-
* Get the global scheduler instance (creates if doesn't exist).
|
|
1783
|
-
* Uses HMR data to preserve instance across hot reloads.
|
|
1784
|
-
* @returns {Scheduler} The singleton scheduler instance
|
|
1785
|
-
*/
|
|
1786
|
-
static get() {
|
|
1787
|
-
if (!_Scheduler.instance && hmrData?.data?.scheduler) {
|
|
1788
|
-
_Scheduler.instance = hmrData.data.scheduler;
|
|
1789
|
-
}
|
|
1790
|
-
if (!_Scheduler.instance) {
|
|
1791
|
-
_Scheduler.instance = new _Scheduler();
|
|
1792
|
-
if (hmrData?.data) {
|
|
1793
|
-
hmrData.data.scheduler = _Scheduler.instance;
|
|
1794
|
-
}
|
|
1795
|
-
}
|
|
1796
|
-
return _Scheduler.instance;
|
|
1797
|
-
}
|
|
1798
|
-
/**
|
|
1799
|
-
* Reset the singleton instance. Stops the loop and clears all state.
|
|
1800
|
-
* Primarily used for testing to ensure clean state between tests.
|
|
1801
|
-
* @returns {void}
|
|
1802
|
-
*/
|
|
1803
|
-
static reset() {
|
|
1804
|
-
if (_Scheduler.instance) {
|
|
1805
|
-
_Scheduler.instance.stop();
|
|
1806
|
-
_Scheduler.instance = null;
|
|
1807
|
-
}
|
|
1808
|
-
if (hmrData?.data) {
|
|
1809
|
-
hmrData.data.scheduler = null;
|
|
1810
|
-
}
|
|
1811
|
-
}
|
|
1812
|
-
//* Getters & Setters ================================
|
|
1813
|
-
get phases() {
|
|
1814
|
-
return this.phaseGraph.getOrderedPhases();
|
|
1815
|
-
}
|
|
1816
|
-
get frameloop() {
|
|
1817
|
-
return this._frameloop;
|
|
1818
|
-
}
|
|
1819
|
-
set frameloop(mode) {
|
|
1820
|
-
if (this._frameloop === mode) return;
|
|
1821
|
-
const wasAlways = this._frameloop === "always";
|
|
1822
|
-
this._frameloop = mode;
|
|
1823
|
-
if (mode === "always" && !this.loopState.running && this.roots.size > 0) this.start();
|
|
1824
|
-
else if (mode !== "always" && wasAlways) this.stop();
|
|
1825
|
-
}
|
|
1826
|
-
get isRunning() {
|
|
1827
|
-
return this.loopState.running;
|
|
1828
|
-
}
|
|
1829
|
-
get isReady() {
|
|
1830
|
-
return this.roots.size > 0;
|
|
1831
|
-
}
|
|
1832
|
-
get independent() {
|
|
1833
|
-
return this._independent;
|
|
1834
|
-
}
|
|
1835
|
-
set independent(value) {
|
|
1836
|
-
this._independent = value;
|
|
1837
|
-
if (value) this.ensureDefaultRoot();
|
|
1838
|
-
}
|
|
1839
|
-
//* Root Management Methods ================================
|
|
1840
|
-
/**
|
|
1841
|
-
* Register a root (Canvas) with the scheduler.
|
|
1842
|
-
* The first root to register starts the RAF loop (if frameloop='always').
|
|
1843
|
-
* @param {string} id - Unique identifier for this root
|
|
1844
|
-
* @param {RootOptions} [options] - Optional configuration with getState and onError callbacks
|
|
1845
|
-
* @returns {() => void} Unsubscribe function to remove this root
|
|
1846
|
-
*/
|
|
1847
|
-
registerRoot(id, options = {}) {
|
|
1848
|
-
if (this.roots.has(id)) {
|
|
1849
|
-
console.warn(`[Scheduler] Root "${id}" already registered`);
|
|
1850
|
-
return () => this.unregisterRoot(id);
|
|
1851
|
-
}
|
|
1852
|
-
const entry = {
|
|
1853
|
-
id,
|
|
1854
|
-
getState: options.getState ?? (() => ({})),
|
|
1855
|
-
jobs: /* @__PURE__ */ new Map(),
|
|
1856
|
-
sortedJobs: [],
|
|
1857
|
-
needsRebuild: false
|
|
1858
|
-
};
|
|
1859
|
-
if (options.onError) {
|
|
1860
|
-
this.errorHandler = options.onError;
|
|
1861
|
-
}
|
|
1862
|
-
this.roots.set(id, entry);
|
|
1863
|
-
if (this.roots.size === 1) {
|
|
1864
|
-
this.notifyRootReady();
|
|
1865
|
-
if (this._frameloop === "always") this.start();
|
|
1866
|
-
}
|
|
1867
|
-
return () => this.unregisterRoot(id);
|
|
1868
|
-
}
|
|
1869
|
-
/**
|
|
1870
|
-
* Unregister a root from the scheduler.
|
|
1871
|
-
* Cleans up all job state listeners for this root's jobs.
|
|
1872
|
-
* The last root to unregister stops the RAF loop.
|
|
1873
|
-
* @param {string} id - The root ID to unregister
|
|
1874
|
-
* @returns {void}
|
|
1875
|
-
*/
|
|
1876
|
-
unregisterRoot(id) {
|
|
1877
|
-
const root = this.roots.get(id);
|
|
1878
|
-
if (!root) return;
|
|
1879
|
-
for (const jobId of root.jobs.keys()) {
|
|
1880
|
-
this.jobStateListeners.delete(jobId);
|
|
1881
|
-
}
|
|
1882
|
-
this.roots.delete(id);
|
|
1883
|
-
if (this.roots.size === 0) {
|
|
1884
|
-
this.stop();
|
|
1885
|
-
this.errorHandler = null;
|
|
1886
|
-
}
|
|
1887
|
-
}
|
|
1888
|
-
/**
|
|
1889
|
-
* Subscribe to be notified when a root becomes available.
|
|
1890
|
-
* Fires immediately if a root already exists.
|
|
1891
|
-
* @param {() => void} callback - Function called when first root registers
|
|
1892
|
-
* @returns {() => void} Unsubscribe function
|
|
1893
|
-
*/
|
|
1894
|
-
onRootReady(callback) {
|
|
1895
|
-
if (this.roots.size > 0) {
|
|
1896
|
-
callback();
|
|
1897
|
-
return () => {
|
|
1898
|
-
};
|
|
1899
|
-
}
|
|
1900
|
-
this.rootReadyCallbacks.add(callback);
|
|
1901
|
-
return () => this.rootReadyCallbacks.delete(callback);
|
|
1902
|
-
}
|
|
1903
|
-
/**
|
|
1904
|
-
* Notify all registered root-ready callbacks.
|
|
1905
|
-
* Called when the first root registers.
|
|
1906
|
-
* @returns {void}
|
|
1907
|
-
* @private
|
|
1908
|
-
*/
|
|
1909
|
-
notifyRootReady() {
|
|
1910
|
-
for (const cb of this.rootReadyCallbacks) {
|
|
1911
|
-
try {
|
|
1912
|
-
cb();
|
|
1913
|
-
} catch (error) {
|
|
1914
|
-
console.error("[Scheduler] Error in root-ready callback:", error);
|
|
1915
|
-
}
|
|
1916
|
-
}
|
|
1917
|
-
this.rootReadyCallbacks.clear();
|
|
1918
|
-
}
|
|
1919
|
-
/**
|
|
1920
|
-
* Ensure a default root exists for independent mode.
|
|
1921
|
-
* Creates a minimal root with no state provider.
|
|
1922
|
-
* @returns {void}
|
|
1923
|
-
* @private
|
|
1924
|
-
*/
|
|
1925
|
-
ensureDefaultRoot() {
|
|
1926
|
-
if (!this.roots.has("__default__")) {
|
|
1927
|
-
this.registerRoot("__default__");
|
|
1928
|
-
}
|
|
1929
|
-
}
|
|
1930
|
-
/**
|
|
1931
|
-
* Trigger error handling for job errors.
|
|
1932
|
-
* Uses the bound error handler if available, otherwise logs to console.
|
|
1933
|
-
* @param {Error} error - The error to handle
|
|
1934
|
-
* @returns {void}
|
|
1935
|
-
*/
|
|
1936
|
-
triggerError(error) {
|
|
1937
|
-
if (this.errorHandler) this.errorHandler(error);
|
|
1938
|
-
else console.error("[Scheduler]", error);
|
|
1939
|
-
}
|
|
1940
|
-
//* Phase Management Methods ================================
|
|
1941
|
-
/**
|
|
1942
|
-
* Add a named phase to the scheduler's execution order.
|
|
1943
|
-
* Marks all roots for rebuild to incorporate the new phase.
|
|
1944
|
-
* @param {string} name - The phase name (e.g., 'physics', 'postprocess')
|
|
1945
|
-
* @param {AddPhaseOptions} [options] - Positioning options (before/after other phases)
|
|
1946
|
-
* @returns {void}
|
|
1947
|
-
* @example
|
|
1948
|
-
* scheduler.addPhase('physics', { before: 'update' });
|
|
1949
|
-
* scheduler.addPhase('postprocess', { after: 'render' });
|
|
1950
|
-
*/
|
|
1951
|
-
addPhase(name, options) {
|
|
1952
|
-
this.phaseGraph.addPhase(name, options);
|
|
1953
|
-
for (const root of this.roots.values()) {
|
|
1954
|
-
root.needsRebuild = true;
|
|
1955
|
-
}
|
|
1956
|
-
}
|
|
1957
|
-
/**
|
|
1958
|
-
* Check if a phase exists in the scheduler.
|
|
1959
|
-
* @param {string} name - The phase name to check
|
|
1960
|
-
* @returns {boolean} True if the phase exists
|
|
1961
|
-
*/
|
|
1962
|
-
hasPhase(name) {
|
|
1963
|
-
return this.phaseGraph.hasPhase(name);
|
|
1964
|
-
}
|
|
1965
|
-
//* Global Job Registration Methods (Deprecated APIs) ================================
|
|
1966
|
-
/**
|
|
1967
|
-
* Register a global job that runs once per frame (not per-root).
|
|
1968
|
-
* Used internally by deprecated addEffect/addAfterEffect APIs.
|
|
1969
|
-
* @param {'before' | 'after'} phase - When to run: 'before' all roots or 'after' all roots
|
|
1970
|
-
* @param {string} id - Unique identifier for this global job
|
|
1971
|
-
* @param {(timestamp: number) => void} callback - Function called each frame with RAF timestamp
|
|
1972
|
-
* @returns {() => void} Unsubscribe function to remove this global job
|
|
1973
|
-
* @deprecated Use useFrame with phases instead
|
|
1974
|
-
*/
|
|
1975
|
-
registerGlobal(phase, id, callback) {
|
|
1976
|
-
const job = { id, callback };
|
|
1977
|
-
if (phase === "before") {
|
|
1978
|
-
this.globalBeforeJobs.set(id, job);
|
|
1979
|
-
} else {
|
|
1980
|
-
this.globalAfterJobs.set(id, job);
|
|
1981
|
-
}
|
|
1982
|
-
return () => {
|
|
1983
|
-
if (phase === "before") this.globalBeforeJobs.delete(id);
|
|
1984
|
-
else this.globalAfterJobs.delete(id);
|
|
1985
|
-
};
|
|
1986
|
-
}
|
|
1987
|
-
//* Idle Callback Methods (Deprecated API) ================================
|
|
1988
|
-
/**
|
|
1989
|
-
* Register an idle callback that fires when the loop stops.
|
|
1990
|
-
* Used internally by deprecated addTail API.
|
|
1991
|
-
* @param {(timestamp: number) => void} callback - Function called when loop becomes idle
|
|
1992
|
-
* @returns {() => void} Unsubscribe function to remove this idle callback
|
|
1993
|
-
* @deprecated Use demand mode with invalidate() instead
|
|
1994
|
-
*/
|
|
1995
|
-
onIdle(callback) {
|
|
1996
|
-
this.idleCallbacks.add(callback);
|
|
1997
|
-
return () => this.idleCallbacks.delete(callback);
|
|
1998
|
-
}
|
|
1999
|
-
/**
|
|
2000
|
-
* Notify all registered idle callbacks.
|
|
2001
|
-
* Called when the loop stops in demand mode.
|
|
2002
|
-
* @param {number} timestamp - The RAF timestamp when idle occurred
|
|
2003
|
-
* @returns {void}
|
|
2004
|
-
* @private
|
|
2005
|
-
*/
|
|
2006
|
-
notifyIdle(timestamp) {
|
|
2007
|
-
for (const cb of this.idleCallbacks) {
|
|
2008
|
-
try {
|
|
2009
|
-
cb(timestamp);
|
|
2010
|
-
} catch (error) {
|
|
2011
|
-
console.error("[Scheduler] Error in idle callback:", error);
|
|
2012
|
-
}
|
|
2013
|
-
}
|
|
2014
|
-
}
|
|
2015
|
-
//* Job Registration & Management Methods ================================
|
|
2016
|
-
/**
|
|
2017
|
-
* Register a job (frame callback) with a specific root.
|
|
2018
|
-
* This is the core registration method used by useFrame internally.
|
|
2019
|
-
* @param {FrameNextCallback} callback - The function to call each frame
|
|
2020
|
-
* @param {JobOptions & { rootId?: string; system?: boolean }} [options] - Job configuration
|
|
2021
|
-
* @param {string} [options.rootId] - Target root ID (defaults to first registered root)
|
|
2022
|
-
* @param {string} [options.id] - Unique job ID (auto-generated if not provided)
|
|
2023
|
-
* @param {string} [options.phase] - Execution phase (defaults to 'update')
|
|
2024
|
-
* @param {number} [options.priority] - Priority within phase (higher = earlier, default 0)
|
|
2025
|
-
* @param {number} [options.fps] - FPS throttle limit
|
|
2026
|
-
* @param {boolean} [options.drop] - Drop frames when behind (default true)
|
|
2027
|
-
* @param {boolean} [options.enabled] - Whether job is active (default true)
|
|
2028
|
-
* @param {boolean} [options.system] - Internal flag for system jobs (not user-facing)
|
|
2029
|
-
* @returns {() => void} Unsubscribe function to remove this job
|
|
2030
|
-
*/
|
|
2031
|
-
register(callback, options = {}) {
|
|
2032
|
-
const rootId = options.rootId;
|
|
2033
|
-
const root = rootId ? this.roots.get(rootId) : this.roots.values().next().value;
|
|
2034
|
-
if (!root) {
|
|
2035
|
-
console.warn("[Scheduler] No root registered. Is this inside a Canvas?");
|
|
2036
|
-
return () => {
|
|
2037
|
-
};
|
|
2038
|
-
}
|
|
2039
|
-
const id = options.id ?? this.generateJobId();
|
|
2040
|
-
let phase = options.phase ?? "update";
|
|
2041
|
-
if (!options.phase && (options.before || options.after)) {
|
|
2042
|
-
phase = this.phaseGraph.resolveConstraintPhase(options.before, options.after);
|
|
2043
|
-
}
|
|
2044
|
-
const before = this.normalizeConstraints(options.before);
|
|
2045
|
-
const after = this.normalizeConstraints(options.after);
|
|
2046
|
-
const job = {
|
|
2047
|
-
id,
|
|
2048
|
-
callback,
|
|
2049
|
-
phase,
|
|
2050
|
-
before,
|
|
2051
|
-
after,
|
|
2052
|
-
priority: options.priority ?? 0,
|
|
2053
|
-
index: this.nextJobIndex++,
|
|
2054
|
-
fps: options.fps,
|
|
2055
|
-
drop: options.drop ?? true,
|
|
2056
|
-
enabled: options.enabled ?? true,
|
|
2057
|
-
system: options.system ?? false
|
|
2058
|
-
};
|
|
2059
|
-
if (root.jobs.has(id)) {
|
|
2060
|
-
console.warn(`[useFrame] Job with id "${id}" already exists, replacing`);
|
|
2061
|
-
}
|
|
2062
|
-
root.jobs.set(id, job);
|
|
2063
|
-
root.needsRebuild = true;
|
|
2064
|
-
return () => this.unregister(id, root.id);
|
|
2065
|
-
}
|
|
2066
|
-
/**
|
|
2067
|
-
* Unregister a job by its ID.
|
|
2068
|
-
* Searches all roots if rootId is not provided.
|
|
2069
|
-
* @param {string} id - The job ID to unregister
|
|
2070
|
-
* @param {string} [rootId] - Optional root ID to search (searches all if not provided)
|
|
2071
|
-
* @returns {void}
|
|
2072
|
-
*/
|
|
2073
|
-
unregister(id, rootId) {
|
|
2074
|
-
const root = rootId ? this.roots.get(rootId) : Array.from(this.roots.values()).find((r) => r.jobs.has(id));
|
|
2075
|
-
if (root?.jobs.delete(id)) {
|
|
2076
|
-
root.needsRebuild = true;
|
|
2077
|
-
this.jobStateListeners.delete(id);
|
|
2078
|
-
}
|
|
2079
|
-
}
|
|
2080
|
-
/**
|
|
2081
|
-
* Update a job's options dynamically.
|
|
2082
|
-
* Searches all roots to find the job by ID.
|
|
2083
|
-
* Phase/constraint changes trigger a rebuild of the sorted job list.
|
|
2084
|
-
* @param {string} id - The job ID to update
|
|
2085
|
-
* @param {Partial<JobOptions>} options - The options to update
|
|
2086
|
-
* @returns {void}
|
|
2087
|
-
*/
|
|
2088
|
-
updateJob(id, options) {
|
|
2089
|
-
let job;
|
|
2090
|
-
let root;
|
|
2091
|
-
for (const r of this.roots.values()) {
|
|
2092
|
-
job = r.jobs.get(id);
|
|
2093
|
-
if (job) {
|
|
2094
|
-
root = r;
|
|
2095
|
-
break;
|
|
2096
|
-
}
|
|
2097
|
-
}
|
|
2098
|
-
if (!job || !root) return;
|
|
2099
|
-
if (options.priority !== void 0) job.priority = options.priority;
|
|
2100
|
-
if (options.fps !== void 0) job.fps = options.fps;
|
|
2101
|
-
if (options.drop !== void 0) job.drop = options.drop;
|
|
2102
|
-
if (options.enabled !== void 0) {
|
|
2103
|
-
const wasEnabled = job.enabled;
|
|
2104
|
-
job.enabled = options.enabled;
|
|
2105
|
-
if (!wasEnabled && job.enabled) resetJobTiming(job);
|
|
2106
|
-
if (wasEnabled !== job.enabled) root.needsRebuild = true;
|
|
2107
|
-
}
|
|
2108
|
-
if (options.phase !== void 0 || options.before !== void 0 || options.after !== void 0) {
|
|
2109
|
-
if (options.phase) job.phase = options.phase;
|
|
2110
|
-
if (options.before !== void 0) job.before = this.normalizeConstraints(options.before);
|
|
2111
|
-
if (options.after !== void 0) job.after = this.normalizeConstraints(options.after);
|
|
2112
|
-
root.needsRebuild = true;
|
|
2113
|
-
}
|
|
2114
|
-
}
|
|
2115
|
-
//* Job State Management Methods ================================
|
|
2116
|
-
/**
|
|
2117
|
-
* Check if a job is currently paused (disabled).
|
|
2118
|
-
* @param {string} id - The job ID to check
|
|
2119
|
-
* @returns {boolean} True if the job exists and is paused
|
|
2120
|
-
*/
|
|
2121
|
-
isJobPaused(id) {
|
|
2122
|
-
for (const root of this.roots.values()) {
|
|
2123
|
-
const job = root.jobs.get(id);
|
|
2124
|
-
if (job) return !job.enabled;
|
|
2125
|
-
}
|
|
2126
|
-
return false;
|
|
2127
|
-
}
|
|
2128
|
-
/**
|
|
2129
|
-
* Subscribe to state changes for a specific job.
|
|
2130
|
-
* Listener is called when job is paused or resumed.
|
|
2131
|
-
* @param {string} id - The job ID to subscribe to
|
|
2132
|
-
* @param {() => void} listener - Callback invoked on state changes
|
|
2133
|
-
* @returns {() => void} Unsubscribe function
|
|
2134
|
-
*/
|
|
2135
|
-
subscribeJobState(id, listener) {
|
|
2136
|
-
if (!this.jobStateListeners.has(id)) {
|
|
2137
|
-
this.jobStateListeners.set(id, /* @__PURE__ */ new Set());
|
|
2138
|
-
}
|
|
2139
|
-
this.jobStateListeners.get(id).add(listener);
|
|
2140
|
-
return () => {
|
|
2141
|
-
this.jobStateListeners.get(id)?.delete(listener);
|
|
2142
|
-
if (this.jobStateListeners.get(id)?.size === 0) {
|
|
2143
|
-
this.jobStateListeners.delete(id);
|
|
2144
|
-
}
|
|
2145
|
-
};
|
|
2146
|
-
}
|
|
2147
|
-
/**
|
|
2148
|
-
* Notify all listeners that a job's state has changed.
|
|
2149
|
-
* @param {string} id - The job ID that changed
|
|
2150
|
-
* @returns {void}
|
|
2151
|
-
* @private
|
|
2152
|
-
*/
|
|
2153
|
-
notifyJobStateChange(id) {
|
|
2154
|
-
this.jobStateListeners.get(id)?.forEach((listener) => listener());
|
|
2155
|
-
}
|
|
2156
|
-
/**
|
|
2157
|
-
* Pause a job by ID (sets enabled=false).
|
|
2158
|
-
* Notifies any subscribed state listeners.
|
|
2159
|
-
* @param {string} id - The job ID to pause
|
|
2160
|
-
* @returns {void}
|
|
2161
|
-
*/
|
|
2162
|
-
pauseJob(id) {
|
|
2163
|
-
this.updateJob(id, { enabled: false });
|
|
2164
|
-
this.notifyJobStateChange(id);
|
|
2165
|
-
}
|
|
2166
|
-
/**
|
|
2167
|
-
* Resume a paused job by ID (sets enabled=true).
|
|
2168
|
-
* Resets job timing to prevent frame accumulation.
|
|
2169
|
-
* Notifies any subscribed state listeners.
|
|
2170
|
-
* @param {string} id - The job ID to resume
|
|
2171
|
-
* @returns {void}
|
|
2172
|
-
*/
|
|
2173
|
-
resumeJob(id) {
|
|
2174
|
-
this.updateJob(id, { enabled: true });
|
|
2175
|
-
this.notifyJobStateChange(id);
|
|
2176
|
-
}
|
|
2177
|
-
//* Frame Loop Control Methods ================================
|
|
2178
|
-
/**
|
|
2179
|
-
* Start the requestAnimationFrame loop.
|
|
2180
|
-
* Resets timing state (elapsedTime, frameCount) on start.
|
|
2181
|
-
* No-op if already running.
|
|
2182
|
-
* @returns {void}
|
|
2183
|
-
*/
|
|
2184
|
-
start() {
|
|
2185
|
-
if (this.loopState.running) return;
|
|
2186
|
-
const { elapsedTime, createdAt } = this.loopState;
|
|
2187
|
-
let adjustedCreated = 0;
|
|
2188
|
-
if (this.stoppedTime > 0) {
|
|
2189
|
-
adjustedCreated = createdAt - (performance.now() - this.stoppedTime);
|
|
2190
|
-
this.stoppedTime = 0;
|
|
2191
|
-
}
|
|
2192
|
-
Object.assign(this.loopState, {
|
|
2193
|
-
running: true,
|
|
2194
|
-
elapsedTime: elapsedTime ?? 0,
|
|
2195
|
-
lastTime: performance.now(),
|
|
2196
|
-
createdAt: adjustedCreated > 0 ? adjustedCreated : performance.now(),
|
|
2197
|
-
frameCount: 0,
|
|
2198
|
-
rafHandle: requestAnimationFrame(this.loop)
|
|
2199
|
-
});
|
|
2200
|
-
}
|
|
2201
|
-
/**
|
|
2202
|
-
* Stop the requestAnimationFrame loop.
|
|
2203
|
-
* Cancels any pending RAF callback.
|
|
2204
|
-
* No-op if not running.
|
|
2205
|
-
* @returns {void}
|
|
2206
|
-
*/
|
|
2207
|
-
stop() {
|
|
2208
|
-
if (!this.loopState.running) return;
|
|
2209
|
-
this.loopState.running = false;
|
|
2210
|
-
if (this.loopState.rafHandle !== null) {
|
|
2211
|
-
cancelAnimationFrame(this.loopState.rafHandle);
|
|
2212
|
-
this.loopState.rafHandle = null;
|
|
2213
|
-
}
|
|
2214
|
-
this.stoppedTime = performance.now();
|
|
2215
|
-
}
|
|
2216
|
-
/**
|
|
2217
|
-
* Request frames to be rendered in demand mode.
|
|
2218
|
-
* Accumulates pending frames (capped at 60) and starts the loop if not running.
|
|
2219
|
-
* No-op if frameloop is not 'demand'.
|
|
2220
|
-
* @param {number} [frames=1] - Number of frames to request
|
|
2221
|
-
* @param {boolean} [stackFrames=false] - Whether to add frames to existing pending count
|
|
2222
|
-
* - `false` (default): Sets pending frames to the specified value (replaces existing count)
|
|
2223
|
-
* - `true`: Adds frames to existing pending count (useful for accumulating invalidations)
|
|
2224
|
-
* @returns {void}
|
|
2225
|
-
* @example
|
|
2226
|
-
* // Request a single frame render
|
|
2227
|
-
* scheduler.invalidate();
|
|
2228
|
-
*
|
|
2229
|
-
* @example
|
|
2230
|
-
* // Request 5 frames (e.g., for animations)
|
|
2231
|
-
* scheduler.invalidate(5);
|
|
2232
|
-
*
|
|
2233
|
-
* @example
|
|
2234
|
-
* // Set pending frames to exactly 3 (don't stack with existing)
|
|
2235
|
-
* scheduler.invalidate(3, false);
|
|
2236
|
-
*
|
|
2237
|
-
* @example
|
|
2238
|
-
* // Add 2 more frames to existing pending count
|
|
2239
|
-
* scheduler.invalidate(2, true);
|
|
2240
|
-
*/
|
|
2241
|
-
invalidate(frames = 1, stackFrames = false) {
|
|
2242
|
-
if (this._frameloop !== "demand") return;
|
|
2243
|
-
const baseFrames = stackFrames ? this.pendingFrames : 0;
|
|
2244
|
-
this.pendingFrames = Math.min(60, baseFrames + frames);
|
|
2245
|
-
if (!this.loopState.running && this.pendingFrames > 0) this.start();
|
|
2246
|
-
}
|
|
2247
|
-
/**
|
|
2248
|
-
* Reset timing state for deterministic testing.
|
|
2249
|
-
* Preserves jobs and roots but resets lastTime, frameCount, elapsedTime, etc.
|
|
2250
|
-
* @returns {void}
|
|
2251
|
-
*/
|
|
2252
|
-
resetTiming() {
|
|
2253
|
-
this.loopState.lastTime = null;
|
|
2254
|
-
this.loopState.frameCount = 0;
|
|
2255
|
-
this.loopState.elapsedTime = 0;
|
|
2256
|
-
this.loopState.createdAt = performance.now();
|
|
2257
|
-
}
|
|
2258
|
-
//* Manual Stepping Methods ================================
|
|
2259
|
-
/**
|
|
2260
|
-
* Manually execute a single frame for all roots.
|
|
2261
|
-
* Useful for frameloop='never' mode or testing scenarios.
|
|
2262
|
-
* @param {number} [timestamp] - Optional timestamp (defaults to performance.now())
|
|
2263
|
-
* @returns {void}
|
|
2264
|
-
* @example
|
|
2265
|
-
* // Manual control mode
|
|
2266
|
-
* scheduler.frameloop = 'never';
|
|
2267
|
-
* scheduler.step(); // Execute one frame
|
|
2268
|
-
*/
|
|
2269
|
-
step(timestamp) {
|
|
2270
|
-
const now = timestamp ?? performance.now();
|
|
2271
|
-
this.executeFrame(now);
|
|
2272
|
-
}
|
|
2273
|
-
/**
|
|
2274
|
-
* Manually execute a single job by its ID.
|
|
2275
|
-
* Useful for testing individual job callbacks in isolation.
|
|
2276
|
-
* @param {string} id - The job ID to step
|
|
2277
|
-
* @param {number} [timestamp] - Optional timestamp (defaults to performance.now())
|
|
2278
|
-
* @returns {void}
|
|
2279
|
-
*/
|
|
2280
|
-
stepJob(id, timestamp) {
|
|
2281
|
-
let job;
|
|
2282
|
-
let root;
|
|
2283
|
-
for (const r of this.roots.values()) {
|
|
2284
|
-
job = r.jobs.get(id);
|
|
2285
|
-
if (job) {
|
|
2286
|
-
root = r;
|
|
2287
|
-
break;
|
|
2288
|
-
}
|
|
2289
|
-
}
|
|
2290
|
-
if (!job || !root) {
|
|
2291
|
-
console.warn(`[Scheduler] Job "${id}" not found`);
|
|
2292
|
-
return;
|
|
2293
|
-
}
|
|
2294
|
-
const now = timestamp ?? performance.now();
|
|
2295
|
-
const deltaMs = this.loopState.lastTime !== null ? now - this.loopState.lastTime : 0;
|
|
2296
|
-
const delta = deltaMs / 1e3;
|
|
2297
|
-
const elapsed = now - this.loopState.createdAt;
|
|
2298
|
-
const providedState = root.getState?.() ?? {};
|
|
2299
|
-
const frameState = {
|
|
2300
|
-
...providedState,
|
|
2301
|
-
time: now,
|
|
2302
|
-
delta,
|
|
2303
|
-
elapsed,
|
|
2304
|
-
frame: this.loopState.frameCount
|
|
2305
|
-
};
|
|
2306
|
-
try {
|
|
2307
|
-
job.callback(frameState, delta);
|
|
2308
|
-
} catch (error) {
|
|
2309
|
-
console.error(`[Scheduler] Error in job "${job.id}":`, error);
|
|
2310
|
-
this.triggerError(error instanceof Error ? error : new Error(String(error)));
|
|
2311
|
-
}
|
|
2312
|
-
}
|
|
2313
|
-
/**
|
|
2314
|
-
* Execute a single frame across all roots.
|
|
2315
|
-
* Order: globalBefore → each root's jobs → globalAfter
|
|
2316
|
-
* @param {number} timestamp - RAF timestamp in milliseconds
|
|
2317
|
-
* @returns {void}
|
|
2318
|
-
* @private
|
|
2319
|
-
*/
|
|
2320
|
-
executeFrame(timestamp) {
|
|
2321
|
-
const deltaMs = this.loopState.lastTime !== null ? timestamp - this.loopState.lastTime : 0;
|
|
2322
|
-
const delta = deltaMs / 1e3;
|
|
2323
|
-
this.loopState.lastTime = timestamp;
|
|
2324
|
-
this.loopState.frameCount++;
|
|
2325
|
-
this.loopState.elapsedTime += deltaMs;
|
|
2326
|
-
this.runGlobalJobs(this.globalBeforeJobs, timestamp);
|
|
2327
|
-
for (const root of this.roots.values()) {
|
|
2328
|
-
this.tickRoot(root, timestamp, delta);
|
|
2329
|
-
}
|
|
2330
|
-
this.runGlobalJobs(this.globalAfterJobs, timestamp);
|
|
2331
|
-
}
|
|
2332
|
-
/**
|
|
2333
|
-
* Run all global jobs from a job map.
|
|
2334
|
-
* Catches and logs errors without stopping execution.
|
|
2335
|
-
* @param {Map<string, GlobalJob>} jobs - The global jobs map to execute
|
|
2336
|
-
* @param {number} timestamp - RAF timestamp in milliseconds
|
|
2337
|
-
* @returns {void}
|
|
2338
|
-
* @private
|
|
2339
|
-
*/
|
|
2340
|
-
runGlobalJobs(jobs, timestamp) {
|
|
2341
|
-
for (const job of jobs.values()) {
|
|
2342
|
-
try {
|
|
2343
|
-
job.callback(timestamp);
|
|
2344
|
-
} catch (error) {
|
|
2345
|
-
console.error(`[Scheduler] Error in global job "${job.id}":`, error);
|
|
2346
|
-
}
|
|
2347
|
-
}
|
|
2348
|
-
}
|
|
2349
|
-
/**
|
|
2350
|
-
* Execute all jobs for a single root in sorted order.
|
|
2351
|
-
* Rebuilds sorted job list if needed, then dispatches each job.
|
|
2352
|
-
* Errors are caught and propagated via triggerError.
|
|
2353
|
-
* @param {RootEntry} root - The root entry to tick
|
|
2354
|
-
* @param {number} timestamp - RAF timestamp in milliseconds
|
|
2355
|
-
* @param {number} delta - Time since last frame in seconds
|
|
2356
|
-
* @returns {void}
|
|
2357
|
-
* @private
|
|
2358
|
-
*/
|
|
2359
|
-
tickRoot(root, timestamp, delta) {
|
|
2360
|
-
if (root.needsRebuild) {
|
|
2361
|
-
root.sortedJobs = rebuildSortedJobs(root.jobs, this.phaseGraph);
|
|
2362
|
-
root.needsRebuild = false;
|
|
2363
|
-
}
|
|
2364
|
-
const providedState = root.getState?.() ?? {};
|
|
2365
|
-
const frameState = {
|
|
2366
|
-
...providedState,
|
|
2367
|
-
time: timestamp,
|
|
2368
|
-
delta,
|
|
2369
|
-
elapsed: this.loopState.elapsedTime / 1e3,
|
|
2370
|
-
// Convert ms to seconds
|
|
2371
|
-
frame: this.loopState.frameCount
|
|
2372
|
-
};
|
|
2373
|
-
for (const job of root.sortedJobs) {
|
|
2374
|
-
if (!shouldRun(job, timestamp)) continue;
|
|
2375
|
-
try {
|
|
2376
|
-
job.callback(frameState, delta);
|
|
2377
|
-
} catch (error) {
|
|
2378
|
-
console.error(`[Scheduler] Error in job "${job.id}":`, error);
|
|
2379
|
-
this.triggerError(error instanceof Error ? error : new Error(String(error)));
|
|
2380
|
-
}
|
|
2381
|
-
}
|
|
2382
|
-
}
|
|
2383
|
-
//* Debug & Inspection Methods ================================
|
|
2384
|
-
/**
|
|
2385
|
-
* Get the total number of registered jobs across all roots.
|
|
2386
|
-
* Includes both per-root jobs and global before/after jobs.
|
|
2387
|
-
* @returns {number} Total job count
|
|
2388
|
-
*/
|
|
2389
|
-
getJobCount() {
|
|
2390
|
-
let count = 0;
|
|
2391
|
-
for (const root of this.roots.values()) {
|
|
2392
|
-
count += root.jobs.size;
|
|
2393
|
-
}
|
|
2394
|
-
return count + this.globalBeforeJobs.size + this.globalAfterJobs.size;
|
|
2395
|
-
}
|
|
2396
|
-
/**
|
|
2397
|
-
* Get all registered job IDs across all roots.
|
|
2398
|
-
* Includes both per-root jobs and global before/after jobs.
|
|
2399
|
-
* @returns {string[]} Array of all job IDs
|
|
2400
|
-
*/
|
|
2401
|
-
getJobIds() {
|
|
2402
|
-
const ids = [];
|
|
2403
|
-
for (const root of this.roots.values()) {
|
|
2404
|
-
ids.push(...root.jobs.keys());
|
|
2405
|
-
}
|
|
2406
|
-
ids.push(...this.globalBeforeJobs.keys());
|
|
2407
|
-
ids.push(...this.globalAfterJobs.keys());
|
|
2408
|
-
return ids;
|
|
2409
|
-
}
|
|
2410
|
-
/**
|
|
2411
|
-
* Get the number of registered roots (Canvas instances).
|
|
2412
|
-
* @returns {number} Number of registered roots
|
|
2413
|
-
*/
|
|
2414
|
-
getRootCount() {
|
|
2415
|
-
return this.roots.size;
|
|
2416
|
-
}
|
|
2417
|
-
/**
|
|
2418
|
-
* Check if any user (non-system) jobs are registered in a specific phase.
|
|
2419
|
-
* Used by the default render job to know if a user has taken over rendering.
|
|
2420
|
-
*
|
|
2421
|
-
* @param phase The phase to check
|
|
2422
|
-
* @param rootId Optional root ID to check (checks all roots if not provided)
|
|
2423
|
-
* @returns true if any user jobs exist in the phase
|
|
2424
|
-
*/
|
|
2425
|
-
hasUserJobsInPhase(phase, rootId) {
|
|
2426
|
-
const rootsToCheck = rootId ? [this.roots.get(rootId)].filter(Boolean) : Array.from(this.roots.values());
|
|
2427
|
-
return rootsToCheck.some((root) => {
|
|
2428
|
-
if (!root) return false;
|
|
2429
|
-
for (const job of root.jobs.values()) {
|
|
2430
|
-
if (job.phase === phase && !job.system && job.enabled) return true;
|
|
2431
|
-
}
|
|
2432
|
-
return false;
|
|
2433
|
-
});
|
|
2434
|
-
}
|
|
2435
|
-
//* Utility Methods ================================
|
|
2436
|
-
/**
|
|
2437
|
-
* Generate a unique root ID for automatic root registration.
|
|
2438
|
-
* @returns {string} A unique root ID in the format 'root_N'
|
|
2439
|
-
*/
|
|
2440
|
-
generateRootId() {
|
|
2441
|
-
return `root_${this.nextRootIndex++}`;
|
|
2442
|
-
}
|
|
2443
|
-
/**
|
|
2444
|
-
* Generate a unique job ID.
|
|
2445
|
-
* @returns {string} A unique job ID in the format 'job_N'
|
|
2446
|
-
* @private
|
|
2447
|
-
*/
|
|
2448
|
-
generateJobId() {
|
|
2449
|
-
return `job_${this.nextJobIndex}`;
|
|
2450
|
-
}
|
|
2451
|
-
/**
|
|
2452
|
-
* Normalize before/after constraints to a Set.
|
|
2453
|
-
* Handles undefined, single string, or array inputs.
|
|
2454
|
-
* @param {string | string[] | undefined} value - The constraint value(s)
|
|
2455
|
-
* @returns {Set<string>} Normalized Set of constraint strings
|
|
2456
|
-
* @private
|
|
2457
|
-
*/
|
|
2458
|
-
normalizeConstraints(value) {
|
|
2459
|
-
if (!value) return /* @__PURE__ */ new Set();
|
|
2460
|
-
if (Array.isArray(value)) return new Set(value);
|
|
2461
|
-
return /* @__PURE__ */ new Set([value]);
|
|
2462
|
-
}
|
|
2463
|
-
};
|
|
2464
|
-
//* Static State & Methods (Singleton Usage) ================================
|
|
2465
|
-
//* Cross-Bundle Singleton Key ==============================
|
|
2466
|
-
// Use Symbol.for() to ensure scheduler is shared across bundle boundaries
|
|
2467
|
-
// This prevents issues when mixing imports from @react-three/fiber and @react-three/fiber/webgpu
|
|
2468
|
-
__publicField$1(_Scheduler, "INSTANCE_KEY", Symbol.for("@react-three/fiber.scheduler"));
|
|
2469
|
-
let Scheduler = _Scheduler;
|
|
2470
|
-
const getScheduler = () => Scheduler.get();
|
|
2471
|
-
if (hmrData) {
|
|
2472
|
-
hmrData.accept?.();
|
|
2473
|
-
}
|
|
2474
|
-
|
|
2475
|
-
const R3F_CONTEXT = Symbol.for("@react-three/fiber.context");
|
|
2476
|
-
const context = globalThis[R3F_CONTEXT] ?? (globalThis[R3F_CONTEXT] = React.createContext(null));
|
|
2477
|
-
const createStore = (invalidate, advance) => {
|
|
2478
|
-
const rootStore = createWithEqualityFn((set, get) => {
|
|
2479
|
-
const position = new Vector3();
|
|
2480
|
-
const defaultTarget = new Vector3();
|
|
2481
|
-
const tempTarget = new Vector3();
|
|
2482
|
-
function getCurrentViewport(camera = get().camera, target = defaultTarget, size = get().size) {
|
|
2483
|
-
const { width, height, top, left } = size;
|
|
2484
|
-
const aspect = width / height;
|
|
2485
|
-
if (target.isVector3) tempTarget.copy(target);
|
|
2486
|
-
else tempTarget.set(...target);
|
|
2487
|
-
const distance = camera.getWorldPosition(position).distanceTo(tempTarget);
|
|
2488
|
-
if (isOrthographicCamera(camera)) {
|
|
2489
|
-
return { width: width / camera.zoom, height: height / camera.zoom, top, left, factor: 1, distance, aspect };
|
|
2490
|
-
} else {
|
|
2491
|
-
const fov = camera.fov * Math.PI / 180;
|
|
2492
|
-
const h = 2 * Math.tan(fov / 2) * distance;
|
|
2493
|
-
const w = h * (width / height);
|
|
2494
|
-
return { width: w, height: h, top, left, factor: width / w, distance, aspect };
|
|
1713
|
+
const R3F_CONTEXT = Symbol.for("@react-three/fiber.context");
|
|
1714
|
+
const context = globalThis[R3F_CONTEXT] ?? (globalThis[R3F_CONTEXT] = React.createContext(null));
|
|
1715
|
+
const createStore = (invalidate, advance) => {
|
|
1716
|
+
const rootStore = createWithEqualityFn((set, get) => {
|
|
1717
|
+
const position = new Vector3();
|
|
1718
|
+
const defaultTarget = new Vector3();
|
|
1719
|
+
const tempTarget = new Vector3();
|
|
1720
|
+
function getCurrentViewport(camera = get().camera, target = defaultTarget, size = get().size) {
|
|
1721
|
+
const { width, height, top, left } = size;
|
|
1722
|
+
const aspect = width / height;
|
|
1723
|
+
if (target.isVector3) tempTarget.copy(target);
|
|
1724
|
+
else tempTarget.set(...target);
|
|
1725
|
+
const distance = camera.getWorldPosition(position).distanceTo(tempTarget);
|
|
1726
|
+
if (isOrthographicCamera(camera)) {
|
|
1727
|
+
return { width: width / camera.zoom, height: height / camera.zoom, top, left, factor: 1, distance, aspect };
|
|
1728
|
+
} else {
|
|
1729
|
+
const fov = camera.fov * Math.PI / 180;
|
|
1730
|
+
const h = 2 * Math.tan(fov / 2) * distance;
|
|
1731
|
+
const w = h * (width / height);
|
|
1732
|
+
return { width: w, height: h, top, left, factor: width / w, distance, aspect };
|
|
2495
1733
|
}
|
|
2496
1734
|
}
|
|
2497
1735
|
let performanceTimeout = void 0;
|
|
@@ -2509,7 +1747,14 @@ const createStore = (invalidate, advance) => {
|
|
|
2509
1747
|
frustum: new Frustum(),
|
|
2510
1748
|
autoUpdateFrustum: true,
|
|
2511
1749
|
raycaster: null,
|
|
2512
|
-
events: {
|
|
1750
|
+
events: {
|
|
1751
|
+
priority: 1,
|
|
1752
|
+
enabled: true,
|
|
1753
|
+
connected: false,
|
|
1754
|
+
frameTimedRaycasts: true,
|
|
1755
|
+
alwaysFireOnScroll: true,
|
|
1756
|
+
updateOnFrame: false
|
|
1757
|
+
},
|
|
2513
1758
|
scene: null,
|
|
2514
1759
|
rootScene: null,
|
|
2515
1760
|
xr: null,
|
|
@@ -2597,14 +1842,18 @@ const createStore = (invalidate, advance) => {
|
|
|
2597
1842
|
}),
|
|
2598
1843
|
setFrameloop: (frameloop = "always") => {
|
|
2599
1844
|
set(() => ({ frameloop }));
|
|
1845
|
+
getScheduler().frameloop = frameloop;
|
|
2600
1846
|
},
|
|
2601
1847
|
setError: (error) => set(() => ({ error })),
|
|
2602
1848
|
error: null,
|
|
2603
|
-
//* TSL State (managed via hooks: useUniforms, useNodes, useTextures,
|
|
1849
|
+
//* TSL State (managed via hooks: useUniforms, useNodes, useBuffers, useGPUStorage, useTextures, useRenderPipeline) ==============================
|
|
2604
1850
|
uniforms: {},
|
|
2605
1851
|
nodes: {},
|
|
1852
|
+
buffers: {},
|
|
1853
|
+
gpuStorage: {},
|
|
2606
1854
|
textures: /* @__PURE__ */ new Map(),
|
|
2607
|
-
|
|
1855
|
+
_textureRefs: /* @__PURE__ */ new Map(),
|
|
1856
|
+
renderPipeline: null,
|
|
2608
1857
|
passes: {},
|
|
2609
1858
|
_hmrVersion: 0,
|
|
2610
1859
|
_sizeImperative: false,
|
|
@@ -2613,12 +1862,16 @@ const createStore = (invalidate, advance) => {
|
|
|
2613
1862
|
internal: {
|
|
2614
1863
|
// Events
|
|
2615
1864
|
interaction: [],
|
|
2616
|
-
hovered: /* @__PURE__ */ new Map(),
|
|
2617
1865
|
subscribers: [],
|
|
1866
|
+
// Per-pointer state (new unified structure)
|
|
1867
|
+
pointerMap: /* @__PURE__ */ new Map(),
|
|
1868
|
+
pointerDirty: /* @__PURE__ */ new Map(),
|
|
1869
|
+
lastEvent: React.createRef(),
|
|
1870
|
+
// Deprecated but kept for backwards compatibility
|
|
1871
|
+
hovered: /* @__PURE__ */ new Map(),
|
|
2618
1872
|
initialClick: [0, 0],
|
|
2619
1873
|
initialHits: [],
|
|
2620
1874
|
capturedMap: /* @__PURE__ */ new Map(),
|
|
2621
|
-
lastEvent: React.createRef(),
|
|
2622
1875
|
// Visibility tracking (onFramed, onOccluded, onVisible)
|
|
2623
1876
|
visibilityRegistry: /* @__PURE__ */ new Map(),
|
|
2624
1877
|
// Occlusion system (WebGPU only)
|
|
@@ -2657,8 +1910,7 @@ const createStore = (invalidate, advance) => {
|
|
|
2657
1910
|
const currentState = rootStore.getState();
|
|
2658
1911
|
if (!currentState.isLegacy && currentState.internal.actualRenderer) {
|
|
2659
1912
|
const stack = new Error().stack || "";
|
|
2660
|
-
|
|
2661
|
-
if (!isInternalAccess) {
|
|
1913
|
+
if (!isInternalRendererAccess(stack)) {
|
|
2662
1914
|
const cleanedStack = stack.split("\n").slice(2).join("\n") || "Stack trace unavailable";
|
|
2663
1915
|
notifyDepreciated({
|
|
2664
1916
|
heading: "Accessing state.gl in WebGPU mode",
|
|
@@ -2706,14 +1958,16 @@ const createStore = (invalidate, advance) => {
|
|
|
2706
1958
|
oldSize = size;
|
|
2707
1959
|
oldDpr = viewport.dpr;
|
|
2708
1960
|
updateCamera(camera, size);
|
|
2709
|
-
if (canvasTarget) {
|
|
1961
|
+
if (internal.isSecondary && canvasTarget) {
|
|
2710
1962
|
if (viewport.dpr > 0) canvasTarget.setPixelRatio(viewport.dpr);
|
|
2711
|
-
|
|
2712
|
-
canvasTarget.setSize(size.width, size.height, updateStyle);
|
|
1963
|
+
canvasTarget.setSize(size.width, size.height, false);
|
|
2713
1964
|
} else {
|
|
2714
1965
|
if (viewport.dpr > 0) actualRenderer.setPixelRatio(viewport.dpr);
|
|
2715
|
-
|
|
2716
|
-
|
|
1966
|
+
actualRenderer.setSize(size.width, size.height, false);
|
|
1967
|
+
if (canvasTarget) {
|
|
1968
|
+
if (viewport.dpr > 0) canvasTarget.setPixelRatio(viewport.dpr);
|
|
1969
|
+
canvasTarget.setSize(size.width, size.height, false);
|
|
1970
|
+
}
|
|
2717
1971
|
}
|
|
2718
1972
|
}
|
|
2719
1973
|
if (camera !== oldCamera) {
|
|
@@ -2769,19 +2023,25 @@ function loadingFn(extensions, onProgress) {
|
|
|
2769
2023
|
);
|
|
2770
2024
|
};
|
|
2771
2025
|
}
|
|
2772
|
-
function useLoader(loader, input, extensions, onProgress) {
|
|
2773
|
-
const
|
|
2026
|
+
function useLoader(loader, input, extensions, onProgress, cacheKey) {
|
|
2027
|
+
const inputs = Array.isArray(input) ? input : [input];
|
|
2028
|
+
const cacheKeys = Array.isArray(cacheKey ?? input) ? cacheKey ?? input : [cacheKey ?? input];
|
|
2774
2029
|
const fn = loadingFn(extensions, onProgress);
|
|
2775
|
-
const results =
|
|
2030
|
+
const results = cacheKeys.map(
|
|
2031
|
+
(key, index) => suspend(() => fn(loader, inputs[index]), [loader, key], { equal: is.equ })
|
|
2032
|
+
);
|
|
2776
2033
|
return Array.isArray(input) ? results : results[0];
|
|
2777
2034
|
}
|
|
2778
|
-
useLoader.preload = function(loader, input, extensions, onProgress) {
|
|
2779
|
-
const
|
|
2780
|
-
|
|
2035
|
+
useLoader.preload = function(loader, input, extensions, onProgress, cacheKey) {
|
|
2036
|
+
const inputs = Array.isArray(input) ? input : [input];
|
|
2037
|
+
const cacheKeys = Array.isArray(cacheKey ?? input) ? cacheKey ?? input : [cacheKey ?? input];
|
|
2038
|
+
cacheKeys.forEach(
|
|
2039
|
+
(key, index) => preload(() => loadingFn(extensions, onProgress)(loader, inputs[index]), [loader, key])
|
|
2040
|
+
);
|
|
2781
2041
|
};
|
|
2782
|
-
useLoader.clear = function(loader, input) {
|
|
2783
|
-
const
|
|
2784
|
-
|
|
2042
|
+
useLoader.clear = function(loader, input, cacheKey) {
|
|
2043
|
+
const cacheKeys = Array.isArray(cacheKey ?? input) ? cacheKey ?? input : [cacheKey ?? input];
|
|
2044
|
+
cacheKeys.forEach((key) => clear([loader, key]));
|
|
2785
2045
|
};
|
|
2786
2046
|
useLoader.loader = getLoader;
|
|
2787
2047
|
|
|
@@ -2857,20 +2117,14 @@ function useFrame(callback, priorityOrOptions) {
|
|
|
2857
2117
|
}
|
|
2858
2118
|
};
|
|
2859
2119
|
} else {
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
unregisterJob = registerOutside();
|
|
2869
|
-
});
|
|
2870
|
-
return () => {
|
|
2871
|
-
unsubReady();
|
|
2872
|
-
unregisterJob?.();
|
|
2873
|
-
};
|
|
2120
|
+
return scheduler.register(
|
|
2121
|
+
(state, delta) => {
|
|
2122
|
+
const frameState = state;
|
|
2123
|
+
if (!frameState.renderer) return;
|
|
2124
|
+
callbackRef.current?.(frameState, delta);
|
|
2125
|
+
},
|
|
2126
|
+
{ id, ...options }
|
|
2127
|
+
);
|
|
2874
2128
|
}
|
|
2875
2129
|
}, [store, scheduler, id, optionsKey, isLegacyPriority, isInsideCanvas]);
|
|
2876
2130
|
const isPaused = React.useSyncExternalStore(
|
|
@@ -2961,18 +2215,18 @@ function buildFromCache(input, textureCache) {
|
|
|
2961
2215
|
function useTexture(input, optionsOrOnLoad) {
|
|
2962
2216
|
const renderer = useThree((state) => state.internal.actualRenderer);
|
|
2963
2217
|
const store = useStore();
|
|
2964
|
-
const textureCache = useThree((state) => state.textures);
|
|
2965
2218
|
const options = typeof optionsOrOnLoad === "function" ? { onLoad: optionsOrOnLoad } : optionsOrOnLoad ?? {};
|
|
2966
|
-
const { onLoad, cache =
|
|
2219
|
+
const { onLoad, cache = true } = options;
|
|
2967
2220
|
const onLoadRef = useRef(onLoad);
|
|
2968
2221
|
onLoadRef.current = onLoad;
|
|
2969
2222
|
const onLoadCalledForRef = useRef(null);
|
|
2970
2223
|
const urls = useMemo(() => getUrls(input), [input]);
|
|
2971
2224
|
const cachedResult = useMemo(() => {
|
|
2972
2225
|
if (!cache) return null;
|
|
2973
|
-
|
|
2974
|
-
|
|
2975
|
-
|
|
2226
|
+
const textures = store.getState().textures;
|
|
2227
|
+
if (!allUrlsCached(urls, textures)) return null;
|
|
2228
|
+
return buildFromCache(input, textures);
|
|
2229
|
+
}, [cache, urls, input, store]);
|
|
2976
2230
|
const loadedTextures = useLoader(
|
|
2977
2231
|
TextureLoader,
|
|
2978
2232
|
IsObject(input) ? Object.values(input) : input
|
|
@@ -3016,8 +2270,6 @@ function useTexture(input, optionsOrOnLoad) {
|
|
|
3016
2270
|
}, [input, loadedTextures, cachedResult]);
|
|
3017
2271
|
useEffect(() => {
|
|
3018
2272
|
if (!cache) return;
|
|
3019
|
-
if (cachedResult) return;
|
|
3020
|
-
const set = store.setState;
|
|
3021
2273
|
const urlTextureMap = [];
|
|
3022
2274
|
if (typeof input === "string") {
|
|
3023
2275
|
urlTextureMap.push([input, mappedTextures]);
|
|
@@ -3031,18 +2283,32 @@ function useTexture(input, optionsOrOnLoad) {
|
|
|
3031
2283
|
urlTextureMap.push([url, textureRecord[key]]);
|
|
3032
2284
|
}
|
|
3033
2285
|
}
|
|
3034
|
-
|
|
3035
|
-
const
|
|
3036
|
-
let
|
|
2286
|
+
store.setState((state) => {
|
|
2287
|
+
const refs = new Map(state._textureRefs);
|
|
2288
|
+
let textures = state.textures;
|
|
2289
|
+
let added = false;
|
|
3037
2290
|
for (const [url, texture] of urlTextureMap) {
|
|
3038
|
-
if (!
|
|
3039
|
-
|
|
3040
|
-
|
|
2291
|
+
if (!textures.has(url)) {
|
|
2292
|
+
if (!added) {
|
|
2293
|
+
textures = new Map(textures);
|
|
2294
|
+
added = true;
|
|
2295
|
+
}
|
|
2296
|
+
textures.set(url, texture);
|
|
3041
2297
|
}
|
|
2298
|
+
refs.set(url, (refs.get(url) ?? 0) + 1);
|
|
3042
2299
|
}
|
|
3043
|
-
return
|
|
2300
|
+
return added ? { textures, _textureRefs: refs } : { _textureRefs: refs };
|
|
2301
|
+
});
|
|
2302
|
+
return () => store.setState((state) => {
|
|
2303
|
+
const refs = new Map(state._textureRefs);
|
|
2304
|
+
for (const [url] of urlTextureMap) {
|
|
2305
|
+
const next = (refs.get(url) ?? 0) - 1;
|
|
2306
|
+
if (next <= 0) refs.delete(url);
|
|
2307
|
+
else refs.set(url, next);
|
|
2308
|
+
}
|
|
2309
|
+
return { _textureRefs: refs };
|
|
3044
2310
|
});
|
|
3045
|
-
}, [cache, input, mappedTextures, store
|
|
2311
|
+
}, [cache, input, mappedTextures, store]);
|
|
3046
2312
|
return mappedTextures;
|
|
3047
2313
|
}
|
|
3048
2314
|
useTexture.preload = (url) => useLoader.preload(TextureLoader, url);
|
|
@@ -3058,96 +2324,63 @@ const Texture = ({
|
|
|
3058
2324
|
return /* @__PURE__ */ jsx(Fragment, { children: children?.(ret) });
|
|
3059
2325
|
};
|
|
3060
2326
|
|
|
3061
|
-
function
|
|
3062
|
-
if (entry instanceof Texture$1) return entry;
|
|
3063
|
-
if (entry && typeof entry === "object" && "value" in entry && entry.value instanceof Texture$1) {
|
|
3064
|
-
return entry.value;
|
|
3065
|
-
}
|
|
3066
|
-
return null;
|
|
3067
|
-
}
|
|
3068
|
-
function useTextures() {
|
|
2327
|
+
function useTextures(selector) {
|
|
3069
2328
|
const store = useStore();
|
|
3070
|
-
|
|
3071
|
-
const set = store.setState;
|
|
2329
|
+
const registry = useMemo(() => {
|
|
3072
2330
|
const getState = store.getState;
|
|
3073
|
-
const
|
|
3074
|
-
|
|
3075
|
-
const newMap = new Map(state.textures);
|
|
3076
|
-
newMap.set(key, value);
|
|
3077
|
-
return { textures: newMap };
|
|
3078
|
-
});
|
|
3079
|
-
};
|
|
3080
|
-
const addMultiple = (items) => {
|
|
3081
|
-
set((state) => {
|
|
3082
|
-
const newMap = new Map(state.textures);
|
|
3083
|
-
const entries = items instanceof Map ? items.entries() : Object.entries(items);
|
|
3084
|
-
for (const [key, value] of entries) {
|
|
3085
|
-
newMap.set(key, value);
|
|
3086
|
-
}
|
|
3087
|
-
return { textures: newMap };
|
|
3088
|
-
});
|
|
3089
|
-
};
|
|
3090
|
-
const remove = (key) => {
|
|
3091
|
-
set((state) => {
|
|
3092
|
-
const newMap = new Map(state.textures);
|
|
3093
|
-
newMap.delete(key);
|
|
3094
|
-
return { textures: newMap };
|
|
3095
|
-
});
|
|
3096
|
-
};
|
|
3097
|
-
const removeMultiple = (keys) => {
|
|
3098
|
-
set((state) => {
|
|
3099
|
-
const newMap = new Map(state.textures);
|
|
3100
|
-
for (const key of keys) newMap.delete(key);
|
|
3101
|
-
return { textures: newMap };
|
|
3102
|
-
});
|
|
3103
|
-
};
|
|
3104
|
-
const dispose = (key) => {
|
|
3105
|
-
const entry = getState().textures.get(key);
|
|
3106
|
-
if (entry) {
|
|
3107
|
-
const tex = getTextureValue(entry);
|
|
3108
|
-
tex?.dispose();
|
|
3109
|
-
}
|
|
3110
|
-
remove(key);
|
|
3111
|
-
};
|
|
3112
|
-
const disposeMultiple = (keys) => {
|
|
3113
|
-
const textures = getState().textures;
|
|
3114
|
-
for (const key of keys) {
|
|
3115
|
-
const entry = textures.get(key);
|
|
3116
|
-
if (entry) {
|
|
3117
|
-
const tex = getTextureValue(entry);
|
|
3118
|
-
tex?.dispose();
|
|
3119
|
-
}
|
|
3120
|
-
}
|
|
3121
|
-
removeMultiple(keys);
|
|
3122
|
-
};
|
|
3123
|
-
const disposeAll = () => {
|
|
3124
|
-
const textures = getState().textures;
|
|
3125
|
-
for (const entry of textures.values()) {
|
|
3126
|
-
const tex = getTextureValue(entry);
|
|
3127
|
-
tex?.dispose();
|
|
3128
|
-
}
|
|
3129
|
-
set({ textures: /* @__PURE__ */ new Map() });
|
|
3130
|
-
};
|
|
2331
|
+
const setState = store.setState;
|
|
2332
|
+
const getOne = (key) => getState().textures.get(key);
|
|
3131
2333
|
return {
|
|
3132
|
-
|
|
3133
|
-
get textures() {
|
|
2334
|
+
get all() {
|
|
3134
2335
|
return getState().textures;
|
|
3135
2336
|
},
|
|
3136
|
-
|
|
3137
|
-
|
|
2337
|
+
get(input) {
|
|
2338
|
+
if (typeof input === "string") return getOne(input);
|
|
2339
|
+
if (Array.isArray(input)) return input.map(getOne);
|
|
2340
|
+
const out = {};
|
|
2341
|
+
for (const name in input) out[name] = getOne(input[name]);
|
|
2342
|
+
return out;
|
|
2343
|
+
},
|
|
3138
2344
|
has: (key) => getState().textures.has(key),
|
|
3139
|
-
|
|
3140
|
-
|
|
3141
|
-
|
|
3142
|
-
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
|
|
3146
|
-
|
|
3147
|
-
|
|
3148
|
-
|
|
2345
|
+
add(keyOrRecord, texture) {
|
|
2346
|
+
setState((state) => {
|
|
2347
|
+
const textures = new Map(state.textures);
|
|
2348
|
+
if (typeof keyOrRecord === "string") {
|
|
2349
|
+
textures.set(keyOrRecord, texture);
|
|
2350
|
+
} else {
|
|
2351
|
+
for (const key in keyOrRecord) textures.set(key, keyOrRecord[key]);
|
|
2352
|
+
}
|
|
2353
|
+
return { textures };
|
|
2354
|
+
});
|
|
2355
|
+
},
|
|
2356
|
+
dispose(key, options) {
|
|
2357
|
+
const state = getState();
|
|
2358
|
+
const refs = state._textureRefs.get(key) ?? 0;
|
|
2359
|
+
if (refs > 0 && !options?.force) {
|
|
2360
|
+
console.warn(
|
|
2361
|
+
`[useTextures] "${key}" still has ${refs} active reference(s); skipping dispose. Pass { force: true } to override.`
|
|
2362
|
+
);
|
|
2363
|
+
return false;
|
|
2364
|
+
}
|
|
2365
|
+
state.textures.get(key)?.dispose();
|
|
2366
|
+
setState((s) => {
|
|
2367
|
+
const textures = new Map(s.textures);
|
|
2368
|
+
textures.delete(key);
|
|
2369
|
+
const nextRefs = new Map(s._textureRefs);
|
|
2370
|
+
nextRefs.delete(key);
|
|
2371
|
+
return { textures, _textureRefs: nextRefs };
|
|
2372
|
+
});
|
|
2373
|
+
return true;
|
|
2374
|
+
},
|
|
2375
|
+
disposeAll() {
|
|
2376
|
+
for (const texture of getState().textures.values()) texture.dispose();
|
|
2377
|
+
setState({ textures: /* @__PURE__ */ new Map(), _textureRefs: /* @__PURE__ */ new Map() });
|
|
2378
|
+
}
|
|
3149
2379
|
};
|
|
3150
2380
|
}, [store]);
|
|
2381
|
+
const subscribe = selector ? () => selector(registry) : (state) => state.textures;
|
|
2382
|
+
const selected = useThree(subscribe);
|
|
2383
|
+
return selector ? selected : registry;
|
|
3151
2384
|
}
|
|
3152
2385
|
|
|
3153
2386
|
function useRenderTarget(widthOrOptions, heightOrOptions, options) {
|
|
@@ -3228,7 +2461,7 @@ function advance(timestamp) {
|
|
|
3228
2461
|
getScheduler().step(timestamp);
|
|
3229
2462
|
}
|
|
3230
2463
|
|
|
3231
|
-
const version = "10.0.0-alpha.
|
|
2464
|
+
const version = "10.0.0-alpha.3";
|
|
3232
2465
|
const packageData = {
|
|
3233
2466
|
version: version};
|
|
3234
2467
|
|
|
@@ -14484,7 +13717,7 @@ function n0() {
|
|
|
14484
13717
|
var t0 = n0();
|
|
14485
13718
|
const r0 = Xb(t0);
|
|
14486
13719
|
|
|
14487
|
-
const t = 1, o = 8, r = 32, e = 2;
|
|
13720
|
+
const t = 1, o = 8, r = 32, e = 2, i$1 = 268435456;
|
|
14488
13721
|
|
|
14489
13722
|
function createReconciler(config) {
|
|
14490
13723
|
const reconciler2 = r0(config);
|
|
@@ -14649,7 +13882,15 @@ function setFiberRef(fiber, publicInstance) {
|
|
|
14649
13882
|
}
|
|
14650
13883
|
}
|
|
14651
13884
|
const reconstructed = [];
|
|
14652
|
-
function
|
|
13885
|
+
function flushReconstructedInstances() {
|
|
13886
|
+
if (reconstructed.length === 0) return;
|
|
13887
|
+
try {
|
|
13888
|
+
swapReconstructedInstances();
|
|
13889
|
+
} finally {
|
|
13890
|
+
reconstructed.length = 0;
|
|
13891
|
+
}
|
|
13892
|
+
}
|
|
13893
|
+
function swapReconstructedInstances() {
|
|
14653
13894
|
for (const [instance] of reconstructed) {
|
|
14654
13895
|
const parent = instance.parent;
|
|
14655
13896
|
if (parent) {
|
|
@@ -14667,6 +13908,9 @@ function swapInstances() {
|
|
|
14667
13908
|
}
|
|
14668
13909
|
}
|
|
14669
13910
|
if (instance.isHidden) unhideInstance(instance);
|
|
13911
|
+
if (isObject3D(instance.object) && !instance.parent) {
|
|
13912
|
+
removeInteractivity(findInitialRoot(instance), instance.object);
|
|
13913
|
+
}
|
|
14670
13914
|
if (instance.object.__r3f) delete instance.object.__r3f;
|
|
14671
13915
|
if (instance.type !== "primitive") disposeOnIdle(instance.object);
|
|
14672
13916
|
}
|
|
@@ -14675,10 +13919,16 @@ function swapInstances() {
|
|
|
14675
13919
|
const parent = instance.parent;
|
|
14676
13920
|
if (parent) {
|
|
14677
13921
|
const target = catalogue[toPascalCase(instance.type)];
|
|
13922
|
+
const prevObject = instance.object;
|
|
14678
13923
|
instance.object = instance.props.object ?? new target(...instance.props.args ?? []);
|
|
14679
13924
|
instance.object.__r3f = instance;
|
|
14680
13925
|
setFiberRef(fiber, instance.object);
|
|
13926
|
+
if (isObject3D(prevObject) && isObject3D(instance.object)) {
|
|
13927
|
+
swapInteractivity(findInitialRoot(instance), prevObject, instance.object);
|
|
13928
|
+
}
|
|
14681
13929
|
delete instance.appliedOnce;
|
|
13930
|
+
instance.eventCount = 0;
|
|
13931
|
+
instance.handlers = {};
|
|
14682
13932
|
applyProps(instance.object, instance.props);
|
|
14683
13933
|
if (instance.props.attach) {
|
|
14684
13934
|
attach(parent, instance);
|
|
@@ -14695,14 +13945,114 @@ function swapInstances() {
|
|
|
14695
13945
|
invalidateInstance(instance);
|
|
14696
13946
|
}
|
|
14697
13947
|
}
|
|
14698
|
-
reconstructed.length = 0;
|
|
14699
13948
|
}
|
|
14700
13949
|
const handleTextInstance = () => {
|
|
14701
13950
|
};
|
|
14702
13951
|
const NO_CONTEXT = {};
|
|
14703
13952
|
let currentUpdatePriority = NoEventPriority;
|
|
14704
|
-
|
|
14705
|
-
|
|
13953
|
+
function getEventPriority(type) {
|
|
13954
|
+
switch (type) {
|
|
13955
|
+
case "beforetoggle":
|
|
13956
|
+
case "cancel":
|
|
13957
|
+
case "click":
|
|
13958
|
+
case "close":
|
|
13959
|
+
case "contextmenu":
|
|
13960
|
+
case "copy":
|
|
13961
|
+
case "cut":
|
|
13962
|
+
case "auxclick":
|
|
13963
|
+
case "dblclick":
|
|
13964
|
+
case "dragend":
|
|
13965
|
+
case "dragstart":
|
|
13966
|
+
case "drop":
|
|
13967
|
+
case "focusin":
|
|
13968
|
+
case "focusout":
|
|
13969
|
+
case "input":
|
|
13970
|
+
case "invalid":
|
|
13971
|
+
case "keydown":
|
|
13972
|
+
case "keypress":
|
|
13973
|
+
case "keyup":
|
|
13974
|
+
case "mousedown":
|
|
13975
|
+
case "mouseup":
|
|
13976
|
+
case "paste":
|
|
13977
|
+
case "pause":
|
|
13978
|
+
case "play":
|
|
13979
|
+
case "pointercancel":
|
|
13980
|
+
case "pointerdown":
|
|
13981
|
+
case "pointerup":
|
|
13982
|
+
case "ratechange":
|
|
13983
|
+
case "reset":
|
|
13984
|
+
case "resize":
|
|
13985
|
+
case "seeked":
|
|
13986
|
+
case "submit":
|
|
13987
|
+
case "touchcancel":
|
|
13988
|
+
case "touchend":
|
|
13989
|
+
case "touchstart":
|
|
13990
|
+
case "volumechange":
|
|
13991
|
+
case "change":
|
|
13992
|
+
case "selectionchange":
|
|
13993
|
+
case "compositionstart":
|
|
13994
|
+
case "compositionend":
|
|
13995
|
+
case "compositionupdate":
|
|
13996
|
+
case "beforeinput":
|
|
13997
|
+
case "blur":
|
|
13998
|
+
case "fullscreenchange":
|
|
13999
|
+
case "focus":
|
|
14000
|
+
case "hashchange":
|
|
14001
|
+
case "popstate":
|
|
14002
|
+
case "select":
|
|
14003
|
+
case "selectstart":
|
|
14004
|
+
return e;
|
|
14005
|
+
case "drag":
|
|
14006
|
+
case "dragenter":
|
|
14007
|
+
case "dragexit":
|
|
14008
|
+
case "dragleave":
|
|
14009
|
+
case "dragover":
|
|
14010
|
+
case "mousemove":
|
|
14011
|
+
case "mouseout":
|
|
14012
|
+
case "mouseover":
|
|
14013
|
+
case "pointermove":
|
|
14014
|
+
case "pointerout":
|
|
14015
|
+
case "pointerover":
|
|
14016
|
+
case "scroll":
|
|
14017
|
+
case "touchmove":
|
|
14018
|
+
case "wheel":
|
|
14019
|
+
case "mouseenter":
|
|
14020
|
+
case "mouseleave":
|
|
14021
|
+
case "pointerenter":
|
|
14022
|
+
case "pointerleave":
|
|
14023
|
+
return o;
|
|
14024
|
+
case "message": {
|
|
14025
|
+
switch (unstable_getCurrentPriorityLevel()) {
|
|
14026
|
+
case unstable_ImmediatePriority:
|
|
14027
|
+
return e;
|
|
14028
|
+
case unstable_UserBlockingPriority:
|
|
14029
|
+
return o;
|
|
14030
|
+
case unstable_NormalPriority:
|
|
14031
|
+
case unstable_LowPriority:
|
|
14032
|
+
return r;
|
|
14033
|
+
case unstable_IdlePriority:
|
|
14034
|
+
return i$1;
|
|
14035
|
+
default:
|
|
14036
|
+
return r;
|
|
14037
|
+
}
|
|
14038
|
+
}
|
|
14039
|
+
default:
|
|
14040
|
+
return r;
|
|
14041
|
+
}
|
|
14042
|
+
}
|
|
14043
|
+
function scheduleMicrotask(callback) {
|
|
14044
|
+
if (typeof queueMicrotask === "function") {
|
|
14045
|
+
queueMicrotask(callback);
|
|
14046
|
+
} else if (typeof Promise !== "undefined") {
|
|
14047
|
+
Promise.resolve().then(callback).catch((error) => {
|
|
14048
|
+
setTimeout(() => {
|
|
14049
|
+
throw error;
|
|
14050
|
+
});
|
|
14051
|
+
});
|
|
14052
|
+
} else {
|
|
14053
|
+
setTimeout(callback);
|
|
14054
|
+
}
|
|
14055
|
+
}
|
|
14706
14056
|
const reconciler = /* @__PURE__ */ createReconciler({
|
|
14707
14057
|
isPrimaryRenderer: false,
|
|
14708
14058
|
warnsIfNotActing: false,
|
|
@@ -14741,16 +14091,15 @@ const reconciler = /* @__PURE__ */ createReconciler({
|
|
|
14741
14091
|
else if (newProps.args?.length !== oldProps.args?.length) reconstruct = true;
|
|
14742
14092
|
else if (newProps.args?.some((value, index) => value !== oldProps.args?.[index])) reconstruct = true;
|
|
14743
14093
|
if (reconstruct) {
|
|
14744
|
-
reconstructed.push([instance,
|
|
14094
|
+
reconstructed.push([instance, getInstanceProps(newProps), fiber]);
|
|
14745
14095
|
} else {
|
|
14746
14096
|
const changedProps = diffProps(instance, newProps);
|
|
14747
|
-
|
|
14748
|
-
|
|
14749
|
-
|
|
14750
|
-
|
|
14097
|
+
const attach2 = instance.props.attach;
|
|
14098
|
+
instance.props = getInstanceProps(newProps);
|
|
14099
|
+
if (attach2 !== void 0) instance.props.attach = attach2;
|
|
14100
|
+
else delete instance.props.attach;
|
|
14101
|
+
if (Object.keys(changedProps).length) applyProps(instance.object, changedProps);
|
|
14751
14102
|
}
|
|
14752
|
-
const isTailSibling = fiber.sibling === null || (fiber.flags & Update) === NoFlags;
|
|
14753
|
-
if (isTailSibling) swapInstances();
|
|
14754
14103
|
},
|
|
14755
14104
|
finalizeInitialChildren: (instance) => {
|
|
14756
14105
|
for (const prop in instance.props) {
|
|
@@ -14775,8 +14124,9 @@ const reconciler = /* @__PURE__ */ createReconciler({
|
|
|
14775
14124
|
const target = container.getState().internal.container ?? container.getState().scene;
|
|
14776
14125
|
return prepare(target, container, "", {});
|
|
14777
14126
|
},
|
|
14778
|
-
|
|
14779
|
-
|
|
14127
|
+
// Reconstructed instances are swapped once all mutations are committed,
|
|
14128
|
+
// before layout effects run so refs point to the new objects
|
|
14129
|
+
resetAfterCommit: flushReconstructedInstances,
|
|
14780
14130
|
shouldSetTextContent: () => false,
|
|
14781
14131
|
clearContainer: () => false,
|
|
14782
14132
|
hideInstance,
|
|
@@ -14784,6 +14134,9 @@ const reconciler = /* @__PURE__ */ createReconciler({
|
|
|
14784
14134
|
createTextInstance: handleTextInstance,
|
|
14785
14135
|
hideTextInstance: handleTextInstance,
|
|
14786
14136
|
unhideTextInstance: handleTextInstance,
|
|
14137
|
+
// Mirrors react-dom, which flushes discrete work in microtasks
|
|
14138
|
+
supportsMicrotasks: true,
|
|
14139
|
+
scheduleMicrotask,
|
|
14787
14140
|
scheduleTimeout: typeof setTimeout === "function" ? setTimeout : void 0,
|
|
14788
14141
|
cancelTimeout: typeof clearTimeout === "function" ? clearTimeout : void 0,
|
|
14789
14142
|
noTimeout: -1,
|
|
@@ -14824,28 +14177,9 @@ const reconciler = /* @__PURE__ */ createReconciler({
|
|
|
14824
14177
|
},
|
|
14825
14178
|
resolveUpdatePriority() {
|
|
14826
14179
|
if (currentUpdatePriority !== NoEventPriority) return currentUpdatePriority;
|
|
14827
|
-
|
|
14828
|
-
|
|
14829
|
-
|
|
14830
|
-
case "dblclick":
|
|
14831
|
-
case "dragenter":
|
|
14832
|
-
case "dragleave":
|
|
14833
|
-
case "drop":
|
|
14834
|
-
case "pointercancel":
|
|
14835
|
-
case "pointerdown":
|
|
14836
|
-
case "pointerup":
|
|
14837
|
-
return e;
|
|
14838
|
-
case "dragover":
|
|
14839
|
-
case "pointermove":
|
|
14840
|
-
case "pointerout":
|
|
14841
|
-
case "pointerover":
|
|
14842
|
-
case "pointerenter":
|
|
14843
|
-
case "pointerleave":
|
|
14844
|
-
case "wheel":
|
|
14845
|
-
return o;
|
|
14846
|
-
default:
|
|
14847
|
-
return r;
|
|
14848
|
-
}
|
|
14180
|
+
const eventType = typeof window !== "undefined" ? window.event?.type : void 0;
|
|
14181
|
+
if (eventType === void 0) return r;
|
|
14182
|
+
return getEventPriority(eventType);
|
|
14849
14183
|
},
|
|
14850
14184
|
resetFormInstance() {
|
|
14851
14185
|
},
|
|
@@ -14894,11 +14228,15 @@ const reconciler = /* @__PURE__ */ createReconciler({
|
|
|
14894
14228
|
cloneMutableTextInstance(textInstance) {
|
|
14895
14229
|
return textInstance;
|
|
14896
14230
|
},
|
|
14231
|
+
// React 19.2 added these for root-level view transitions. They only run for hosts that opt into
|
|
14232
|
+
// view transitions by returning a container from cloneRootViewTransitionContainer; R3F renders
|
|
14233
|
+
// into a WebGL/WebGPU canvas, where cloning the root has no meaning, so we never opt in and
|
|
14234
|
+
// React never calls them. They stay as no-ops rather than throws: the host config is shared by
|
|
14235
|
+
// every root, and a throw here would turn a React-internal probe into a hard crash for users.
|
|
14236
|
+
// Guarded by a test asserting a full render + unmount never reaches them.
|
|
14897
14237
|
cloneRootViewTransitionContainer(_rootContainer) {
|
|
14898
|
-
throw new Error("Not implemented.");
|
|
14899
14238
|
},
|
|
14900
14239
|
removeRootViewTransitionClone(_rootContainer, _clone) {
|
|
14901
|
-
throw new Error("Not implemented.");
|
|
14902
14240
|
},
|
|
14903
14241
|
// https://github.com/facebook/react/pull/32465
|
|
14904
14242
|
createFragmentInstance: (_fiber) => null,
|
|
@@ -14983,6 +14321,7 @@ function createRoot(canvas) {
|
|
|
14983
14321
|
const lastConfiguredProps = {};
|
|
14984
14322
|
let configured = false;
|
|
14985
14323
|
let pending = null;
|
|
14324
|
+
let rendererSetup = null;
|
|
14986
14325
|
return {
|
|
14987
14326
|
async configure(props = {}) {
|
|
14988
14327
|
let resolve;
|
|
@@ -14998,7 +14337,6 @@ function createRoot(canvas) {
|
|
|
14998
14337
|
events,
|
|
14999
14338
|
onCreated: onCreatedCallback,
|
|
15000
14339
|
shadows = false,
|
|
15001
|
-
textureColorSpace = SRGBColorSpace,
|
|
15002
14340
|
orthographic = false,
|
|
15003
14341
|
frameloop = "always",
|
|
15004
14342
|
dpr = [1, 2],
|
|
@@ -15013,7 +14351,8 @@ function createRoot(canvas) {
|
|
|
15013
14351
|
_sizeProps,
|
|
15014
14352
|
forceEven
|
|
15015
14353
|
} = props;
|
|
15016
|
-
const
|
|
14354
|
+
const textureColorSpace = is.obj(glConfig) && !is.fun(glConfig) && !isRenderer(glConfig) && glConfig.textureColorSpace || is.obj(rendererConfig) && !is.fun(rendererConfig) && !isRenderer(rendererConfig) && rendererConfig.textureColorSpace || SRGBColorSpace;
|
|
14355
|
+
let state = store.getState();
|
|
15017
14356
|
const defaultGPUProps = {
|
|
15018
14357
|
canvas,
|
|
15019
14358
|
antialias: true
|
|
@@ -15027,46 +14366,64 @@ function createRoot(canvas) {
|
|
|
15027
14366
|
throw new Error("Cannot use both gl and renderer props at the same time");
|
|
15028
14367
|
}
|
|
15029
14368
|
let renderer = state.internal.actualRenderer;
|
|
15030
|
-
if (
|
|
15031
|
-
|
|
15032
|
-
|
|
15033
|
-
|
|
15034
|
-
|
|
15035
|
-
|
|
15036
|
-
|
|
15037
|
-
|
|
15038
|
-
|
|
15039
|
-
|
|
15040
|
-
|
|
15041
|
-
|
|
15042
|
-
|
|
15043
|
-
|
|
15044
|
-
|
|
15045
|
-
|
|
15046
|
-
|
|
15047
|
-
|
|
15048
|
-
|
|
15049
|
-
|
|
15050
|
-
|
|
15051
|
-
|
|
15052
|
-
|
|
15053
|
-
|
|
15054
|
-
|
|
15055
|
-
|
|
15056
|
-
|
|
15057
|
-
|
|
15058
|
-
|
|
15059
|
-
|
|
15060
|
-
|
|
15061
|
-
|
|
15062
|
-
|
|
15063
|
-
|
|
15064
|
-
|
|
15065
|
-
|
|
15066
|
-
|
|
14369
|
+
if (!state.internal.actualRenderer) {
|
|
14370
|
+
if (!rendererSetup) {
|
|
14371
|
+
rendererSetup = (async () => {
|
|
14372
|
+
if (primaryCanvas) {
|
|
14373
|
+
const primary = await waitForPrimary(primaryCanvas);
|
|
14374
|
+
renderer = primary.renderer;
|
|
14375
|
+
state.internal.actualRenderer = renderer;
|
|
14376
|
+
const canvasTarget = new CanvasTarget(canvas);
|
|
14377
|
+
primary.store.setState((prev) => ({
|
|
14378
|
+
internal: { ...prev.internal, isMultiCanvas: true }
|
|
14379
|
+
}));
|
|
14380
|
+
state.set((prev) => ({
|
|
14381
|
+
webGPUSupported: primary.store.getState().webGPUSupported,
|
|
14382
|
+
renderer,
|
|
14383
|
+
primaryStore: primary.store,
|
|
14384
|
+
internal: {
|
|
14385
|
+
...prev.internal,
|
|
14386
|
+
canvasTarget,
|
|
14387
|
+
isMultiCanvas: true,
|
|
14388
|
+
isSecondary: true,
|
|
14389
|
+
targetId: primaryCanvas
|
|
14390
|
+
}
|
|
14391
|
+
}));
|
|
14392
|
+
} else {
|
|
14393
|
+
renderer = await resolveRenderer(rendererConfig, defaultGPUProps, WebGPURenderer);
|
|
14394
|
+
if (!renderer.hasInitialized?.()) {
|
|
14395
|
+
const size2 = computeInitialSize(canvas, propsSize);
|
|
14396
|
+
if (size2.width > 0 && size2.height > 0) {
|
|
14397
|
+
const pixelRatio = calculateDpr(dpr);
|
|
14398
|
+
canvas.width = size2.width * pixelRatio;
|
|
14399
|
+
canvas.height = size2.height * pixelRatio;
|
|
14400
|
+
}
|
|
14401
|
+
await renderer.init();
|
|
14402
|
+
}
|
|
14403
|
+
const backend = renderer.backend;
|
|
14404
|
+
const isWebGPUBackend = backend && "isWebGPUBackend" in backend;
|
|
14405
|
+
state.internal.actualRenderer = renderer;
|
|
14406
|
+
state.set({ webGPUSupported: isWebGPUBackend, renderer, primaryStore: store });
|
|
14407
|
+
if (canvasId && !state.internal.isSecondary) {
|
|
14408
|
+
const canvasTarget = new CanvasTarget(canvas);
|
|
14409
|
+
const unregisterPrimary = registerPrimary(canvasId, renderer, store);
|
|
14410
|
+
state.set((prev) => ({
|
|
14411
|
+
internal: {
|
|
14412
|
+
...prev.internal,
|
|
14413
|
+
canvasTarget,
|
|
14414
|
+
unregisterPrimary
|
|
14415
|
+
}
|
|
14416
|
+
}));
|
|
14417
|
+
}
|
|
15067
14418
|
}
|
|
15068
|
-
}))
|
|
14419
|
+
})().catch((err) => {
|
|
14420
|
+
rendererSetup = null;
|
|
14421
|
+
throw err;
|
|
14422
|
+
});
|
|
15069
14423
|
}
|
|
14424
|
+
await rendererSetup;
|
|
14425
|
+
state = store.getState();
|
|
14426
|
+
renderer = state.internal.actualRenderer;
|
|
15070
14427
|
}
|
|
15071
14428
|
let raycaster = state.raycaster;
|
|
15072
14429
|
if (!raycaster) state.set({ raycaster: raycaster = new Raycaster() });
|
|
@@ -15159,7 +14516,7 @@ function createRoot(canvas) {
|
|
|
15159
14516
|
lastConfiguredProps.performance = performance;
|
|
15160
14517
|
}
|
|
15161
14518
|
if (!state.xr) {
|
|
15162
|
-
const handleXRFrame = (timestamp,
|
|
14519
|
+
const handleXRFrame = (timestamp, _frame) => {
|
|
15163
14520
|
const state2 = store.getState();
|
|
15164
14521
|
if (state2.frameloop === "never") return;
|
|
15165
14522
|
advance(timestamp);
|
|
@@ -15195,15 +14552,22 @@ function createRoot(canvas) {
|
|
|
15195
14552
|
const oldType = renderer.shadowMap.type;
|
|
15196
14553
|
renderer.shadowMap.enabled = !!shadows;
|
|
15197
14554
|
if (is.boo(shadows)) {
|
|
15198
|
-
renderer.shadowMap.type =
|
|
14555
|
+
renderer.shadowMap.type = PCFShadowMap;
|
|
15199
14556
|
} else if (is.str(shadows)) {
|
|
14557
|
+
if (shadows === "soft") {
|
|
14558
|
+
notifyDepreciated({
|
|
14559
|
+
heading: 'shadows="soft" is deprecated',
|
|
14560
|
+
body: "Three has depreciated soft and improved basic PCFShadows, we converted for you.",
|
|
14561
|
+
link: "https://github.com/mrdoob/three.js/wiki/Migration-Guide?utm_source=chatgpt.com#181--182"
|
|
14562
|
+
});
|
|
14563
|
+
}
|
|
15200
14564
|
const types = {
|
|
15201
14565
|
basic: BasicShadowMap,
|
|
15202
14566
|
percentage: PCFShadowMap,
|
|
15203
|
-
soft:
|
|
14567
|
+
soft: PCFShadowMap,
|
|
15204
14568
|
variance: VSMShadowMap
|
|
15205
14569
|
};
|
|
15206
|
-
renderer.shadowMap.type = types[shadows] ??
|
|
14570
|
+
renderer.shadowMap.type = types[shadows] ?? PCFShadowMap;
|
|
15207
14571
|
} else if (is.obj(shadows)) {
|
|
15208
14572
|
Object.assign(renderer.shadowMap, shadows);
|
|
15209
14573
|
}
|
|
@@ -15219,13 +14583,24 @@ function createRoot(canvas) {
|
|
|
15219
14583
|
if (state.textureColorSpace !== textureColorSpace) state.set(() => ({ textureColorSpace }));
|
|
15220
14584
|
lastConfiguredProps.textureColorSpace = textureColorSpace;
|
|
15221
14585
|
}
|
|
14586
|
+
const r3fProps = ["textureColorSpace"];
|
|
14587
|
+
const constructorOnlyProps = ["samples", "antialias", "alpha", "canvas", "powerPreference"];
|
|
14588
|
+
const nonApplyProps = [...r3fProps, ...constructorOnlyProps];
|
|
15222
14589
|
if (glConfig && !is.fun(glConfig) && !isRenderer(glConfig) && !is.equ(glConfig, renderer, shallowLoose)) {
|
|
15223
|
-
|
|
14590
|
+
const glProps = {};
|
|
14591
|
+
for (const key in glConfig) {
|
|
14592
|
+
if (!nonApplyProps.includes(key)) glProps[key] = glConfig[key];
|
|
14593
|
+
}
|
|
14594
|
+
applyProps(renderer, glProps);
|
|
15224
14595
|
}
|
|
15225
14596
|
if (rendererConfig && !is.fun(rendererConfig) && !isRenderer(rendererConfig) && state.renderer) {
|
|
15226
14597
|
const currentRenderer = state.renderer;
|
|
15227
14598
|
if (!is.equ(rendererConfig, currentRenderer, shallowLoose)) {
|
|
15228
|
-
|
|
14599
|
+
const rendererProps = {};
|
|
14600
|
+
for (const key in rendererConfig) {
|
|
14601
|
+
if (!nonApplyProps.includes(key)) rendererProps[key] = rendererConfig[key];
|
|
14602
|
+
}
|
|
14603
|
+
applyProps(currentRenderer, rendererProps);
|
|
15229
14604
|
}
|
|
15230
14605
|
}
|
|
15231
14606
|
const scheduler = getScheduler();
|
|
@@ -15251,6 +14626,18 @@ function createRoot(canvas) {
|
|
|
15251
14626
|
system: true
|
|
15252
14627
|
}
|
|
15253
14628
|
);
|
|
14629
|
+
const unregisterEventsFlush = scheduler.register(
|
|
14630
|
+
() => {
|
|
14631
|
+
const state2 = store.getState();
|
|
14632
|
+
state2.events.flush?.();
|
|
14633
|
+
},
|
|
14634
|
+
{
|
|
14635
|
+
id: `${newRootId}_events`,
|
|
14636
|
+
rootId: newRootId,
|
|
14637
|
+
phase: "input",
|
|
14638
|
+
system: true
|
|
14639
|
+
}
|
|
14640
|
+
);
|
|
15254
14641
|
const unregisterFrustum = scheduler.register(
|
|
15255
14642
|
() => {
|
|
15256
14643
|
const state2 = store.getState();
|
|
@@ -15261,7 +14648,7 @@ function createRoot(canvas) {
|
|
|
15261
14648
|
{
|
|
15262
14649
|
id: `${newRootId}_frustum`,
|
|
15263
14650
|
rootId: newRootId,
|
|
15264
|
-
|
|
14651
|
+
before: "render",
|
|
15265
14652
|
system: true
|
|
15266
14653
|
}
|
|
15267
14654
|
);
|
|
@@ -15273,7 +14660,7 @@ function createRoot(canvas) {
|
|
|
15273
14660
|
{
|
|
15274
14661
|
id: `${newRootId}_visibility`,
|
|
15275
14662
|
rootId: newRootId,
|
|
15276
|
-
|
|
14663
|
+
before: "render",
|
|
15277
14664
|
system: true,
|
|
15278
14665
|
after: `${newRootId}_frustum`
|
|
15279
14666
|
}
|
|
@@ -15285,7 +14672,7 @@ function createRoot(canvas) {
|
|
|
15285
14672
|
const userHandlesRender = scheduler.hasUserJobsInPhase("render", newRootId);
|
|
15286
14673
|
if (userHandlesRender || state2.internal.priority) return;
|
|
15287
14674
|
try {
|
|
15288
|
-
if (state2.
|
|
14675
|
+
if (state2.renderPipeline?.render) state2.renderPipeline.render();
|
|
15289
14676
|
else if (renderer2?.render) renderer2.render(state2.scene, state2.camera);
|
|
15290
14677
|
} catch (error) {
|
|
15291
14678
|
state2.setError(error instanceof Error ? error : new Error(String(error)));
|
|
@@ -15310,6 +14697,7 @@ function createRoot(canvas) {
|
|
|
15310
14697
|
unregisterRoot: () => {
|
|
15311
14698
|
unregisterRoot();
|
|
15312
14699
|
unregisterCanvasTarget();
|
|
14700
|
+
unregisterEventsFlush();
|
|
15313
14701
|
unregisterFrustum();
|
|
15314
14702
|
unregisterVisibility();
|
|
15315
14703
|
unregisterRender();
|
|
@@ -15385,7 +14773,8 @@ function unmountComponentAtNode(canvas, callback) {
|
|
|
15385
14773
|
dispose(state.scene);
|
|
15386
14774
|
_roots.delete(canvas);
|
|
15387
14775
|
if (callback) callback(canvas);
|
|
15388
|
-
} catch {
|
|
14776
|
+
} catch (error) {
|
|
14777
|
+
console.warn("[R3F] Error while unmounting root; teardown may be incomplete:", error);
|
|
15389
14778
|
}
|
|
15390
14779
|
}, 500);
|
|
15391
14780
|
}
|
|
@@ -15448,6 +14837,7 @@ function PortalInner({ state = {}, children, container }) {
|
|
|
15448
14837
|
viewport = rootState.viewport.getCurrentViewport(camera, new Vector3(), resolvedSize);
|
|
15449
14838
|
if (camera !== rootState.camera) updateCamera(camera, resolvedSize);
|
|
15450
14839
|
}
|
|
14840
|
+
const set = injectState.set;
|
|
15451
14841
|
return {
|
|
15452
14842
|
// The intersect consists of the previous root state
|
|
15453
14843
|
...rootState,
|
|
@@ -15466,7 +14856,7 @@ function PortalInner({ state = {}, children, container }) {
|
|
|
15466
14856
|
size: resolvedSize,
|
|
15467
14857
|
viewport: { ...rootState.viewport, ...viewport },
|
|
15468
14858
|
// Layers are allowed to override events
|
|
15469
|
-
setEvents: (events2) =>
|
|
14859
|
+
setEvents: (events2) => set((state2) => ({ ...state2, events: { ...state2.events, ...events2 } })),
|
|
15470
14860
|
// Container for child attachment - the portalScene (injected or container itself)
|
|
15471
14861
|
internal: { ...rootState.internal, ...injectState.internal, container: portalScene }
|
|
15472
14862
|
};
|
|
@@ -15475,9 +14865,13 @@ function PortalInner({ state = {}, children, container }) {
|
|
|
15475
14865
|
const store = createWithEqualityFn((set, get) => ({ ...rest, set, get }));
|
|
15476
14866
|
const onMutate = (prev) => store.setState((state2) => inject.current(prev, state2));
|
|
15477
14867
|
onMutate(previousRoot.getState());
|
|
15478
|
-
previousRoot.subscribe(onMutate);
|
|
15479
14868
|
return store;
|
|
15480
14869
|
}, [previousRoot, container]);
|
|
14870
|
+
useIsomorphicLayoutEffect(() => {
|
|
14871
|
+
const onMutate = (prev) => usePortalStore.setState((state2) => inject.current(prev, state2));
|
|
14872
|
+
const unsubscribe = previousRoot.subscribe(onMutate);
|
|
14873
|
+
return unsubscribe;
|
|
14874
|
+
}, [previousRoot, usePortalStore]);
|
|
15481
14875
|
return (
|
|
15482
14876
|
// @ts-ignore, reconciler types are not maintained
|
|
15483
14877
|
/* @__PURE__ */ jsx(Fragment, { children: reconciler.createPortal(
|
|
@@ -15491,6 +14885,178 @@ function flushSync(fn) {
|
|
|
15491
14885
|
return reconciler.flushSyncFromReconciler(fn);
|
|
15492
14886
|
}
|
|
15493
14887
|
|
|
14888
|
+
function parseBackground(background) {
|
|
14889
|
+
if (!background) return null;
|
|
14890
|
+
if (typeof background === "object" && !background.isColor) {
|
|
14891
|
+
const { backgroundMap, envMap, files, preset, ...rest } = background;
|
|
14892
|
+
return {
|
|
14893
|
+
...rest,
|
|
14894
|
+
preset,
|
|
14895
|
+
files: envMap || files,
|
|
14896
|
+
backgroundFiles: backgroundMap,
|
|
14897
|
+
background: true
|
|
14898
|
+
};
|
|
14899
|
+
}
|
|
14900
|
+
if (typeof background === "number") {
|
|
14901
|
+
return { color: background, background: true };
|
|
14902
|
+
}
|
|
14903
|
+
if (typeof background === "string") {
|
|
14904
|
+
if (background in presetsObj) {
|
|
14905
|
+
return { preset: background, background: true };
|
|
14906
|
+
}
|
|
14907
|
+
if (/^(https?:\/\/|\/|\.\/|\.\.\/)|\.(hdr|exr|jpg|jpeg|png|webp|gif)$/i.test(background)) {
|
|
14908
|
+
return { files: background, background: true };
|
|
14909
|
+
}
|
|
14910
|
+
return { color: background, background: true };
|
|
14911
|
+
}
|
|
14912
|
+
if (background.isColor) {
|
|
14913
|
+
return { color: background, background: true };
|
|
14914
|
+
}
|
|
14915
|
+
return null;
|
|
14916
|
+
}
|
|
14917
|
+
|
|
14918
|
+
function parseRendererConfig(rendererProp) {
|
|
14919
|
+
const isRendererConfig = typeof rendererProp === "object" && rendererProp !== null && !("render" in rendererProp) && ("primaryCanvas" in rendererProp || "scheduler" in rendererProp);
|
|
14920
|
+
if (isRendererConfig) {
|
|
14921
|
+
const { primaryCanvas: pc, scheduler: sc, ...rest } = rendererProp;
|
|
14922
|
+
return {
|
|
14923
|
+
primaryCanvas: pc,
|
|
14924
|
+
scheduler: sc,
|
|
14925
|
+
renderer: Object.keys(rest).length > 0 ? rest : rendererProp
|
|
14926
|
+
};
|
|
14927
|
+
}
|
|
14928
|
+
return { primaryCanvas: void 0, scheduler: void 0, renderer: rendererProp };
|
|
14929
|
+
}
|
|
14930
|
+
|
|
14931
|
+
const RESOURCE_KINDS = ["nodes", "uniforms", "buffers", "gpuStorage"];
|
|
14932
|
+
const ROOT_SCOPE = "";
|
|
14933
|
+
const registries = /* @__PURE__ */ new WeakMap();
|
|
14934
|
+
function getKindRegistry(store, kind) {
|
|
14935
|
+
let registry = registries.get(store);
|
|
14936
|
+
if (!registry) {
|
|
14937
|
+
registry = {
|
|
14938
|
+
nodes: { valid: /* @__PURE__ */ new Set(), staged: /* @__PURE__ */ new Map() },
|
|
14939
|
+
uniforms: { valid: /* @__PURE__ */ new Set(), staged: /* @__PURE__ */ new Map() },
|
|
14940
|
+
buffers: { valid: /* @__PURE__ */ new Set(), staged: /* @__PURE__ */ new Map() },
|
|
14941
|
+
gpuStorage: { valid: /* @__PURE__ */ new Set(), staged: /* @__PURE__ */ new Map() }
|
|
14942
|
+
};
|
|
14943
|
+
registries.set(store, registry);
|
|
14944
|
+
}
|
|
14945
|
+
return registry[kind];
|
|
14946
|
+
}
|
|
14947
|
+
function resolvePrimary$1(store) {
|
|
14948
|
+
return store.getState().primaryStore ?? store;
|
|
14949
|
+
}
|
|
14950
|
+
function isScopeValid(store, kind, scopeKey) {
|
|
14951
|
+
return getKindRegistry(store, kind).valid.has(scopeKey);
|
|
14952
|
+
}
|
|
14953
|
+
function peekStaged(store, kind, scopeKey) {
|
|
14954
|
+
return getKindRegistry(store, kind).staged.get(scopeKey);
|
|
14955
|
+
}
|
|
14956
|
+
function stageEntries(store, kind, scopeKey, entries) {
|
|
14957
|
+
const { staged } = getKindRegistry(store, kind);
|
|
14958
|
+
const current = staged.get(scopeKey);
|
|
14959
|
+
if (current) Object.assign(current, entries);
|
|
14960
|
+
else staged.set(scopeKey, { ...entries });
|
|
14961
|
+
}
|
|
14962
|
+
function readCommittedScope(map, scopeKey, isLeaf) {
|
|
14963
|
+
if (scopeKey === ROOT_SCOPE) {
|
|
14964
|
+
const leaves = {};
|
|
14965
|
+
for (const [key, value] of Object.entries(map)) if (isLeaf(value)) leaves[key] = value;
|
|
14966
|
+
return leaves;
|
|
14967
|
+
}
|
|
14968
|
+
const scopeData = map[scopeKey];
|
|
14969
|
+
if (scopeData && typeof scopeData === "object" && !isLeaf(scopeData)) return scopeData;
|
|
14970
|
+
return {};
|
|
14971
|
+
}
|
|
14972
|
+
function withStagedOverlay(store, kind, committed) {
|
|
14973
|
+
const { staged } = getKindRegistry(store, kind);
|
|
14974
|
+
if (staged.size === 0) return committed;
|
|
14975
|
+
const view = { ...committed };
|
|
14976
|
+
for (const [scopeKey, entries] of staged) {
|
|
14977
|
+
if (scopeKey === ROOT_SCOPE) Object.assign(view, entries);
|
|
14978
|
+
else view[scopeKey] = { ...view[scopeKey], ...entries };
|
|
14979
|
+
}
|
|
14980
|
+
return view;
|
|
14981
|
+
}
|
|
14982
|
+
function flushStagedScope(store, kind, scopeKey, isLeaf) {
|
|
14983
|
+
const registry = getKindRegistry(store, kind);
|
|
14984
|
+
const staged = registry.staged.get(scopeKey);
|
|
14985
|
+
if (!staged) return;
|
|
14986
|
+
registry.staged.delete(scopeKey);
|
|
14987
|
+
const replace = !registry.valid.has(scopeKey);
|
|
14988
|
+
store.setState((state) => {
|
|
14989
|
+
const map = state[kind];
|
|
14990
|
+
if (scopeKey === ROOT_SCOPE) {
|
|
14991
|
+
if (!replace) return { [kind]: { ...map, ...staged } };
|
|
14992
|
+
const next = {};
|
|
14993
|
+
for (const [key, value] of Object.entries(map)) if (!isLeaf(value)) next[key] = value;
|
|
14994
|
+
return { [kind]: { ...next, ...staged } };
|
|
14995
|
+
}
|
|
14996
|
+
const current = replace ? {} : map[scopeKey] ?? {};
|
|
14997
|
+
return { [kind]: { ...map, [scopeKey]: { ...current, ...staged } } };
|
|
14998
|
+
});
|
|
14999
|
+
registry.valid.add(scopeKey);
|
|
15000
|
+
}
|
|
15001
|
+
function invalidateResource(store, kind, scopeKey) {
|
|
15002
|
+
const registry = getKindRegistry(store, kind);
|
|
15003
|
+
if (scopeKey === void 0) {
|
|
15004
|
+
registry.valid.clear();
|
|
15005
|
+
registry.staged.clear();
|
|
15006
|
+
} else {
|
|
15007
|
+
registry.valid.delete(scopeKey);
|
|
15008
|
+
registry.staged.delete(scopeKey);
|
|
15009
|
+
}
|
|
15010
|
+
}
|
|
15011
|
+
function rebuildResource(store, kind, userScope) {
|
|
15012
|
+
const primary = resolvePrimary$1(store);
|
|
15013
|
+
invalidateResource(primary, kind, userScope === "root" ? ROOT_SCOPE : userScope);
|
|
15014
|
+
primary.setState((state) => ({ _hmrVersion: state._hmrVersion + 1 }));
|
|
15015
|
+
}
|
|
15016
|
+
function invalidateAllResources(store) {
|
|
15017
|
+
for (const kind of RESOURCE_KINDS) invalidateResource(store, kind);
|
|
15018
|
+
}
|
|
15019
|
+
function removeResourceEntries(store, kind, names, scope, isLeaf) {
|
|
15020
|
+
const staged = peekStaged(store, kind, scope ?? ROOT_SCOPE);
|
|
15021
|
+
if (staged) for (const name of names) delete staged[name];
|
|
15022
|
+
store.setState((state) => {
|
|
15023
|
+
const map = state[kind];
|
|
15024
|
+
if (scope) {
|
|
15025
|
+
const currentScope = { ...map[scope] };
|
|
15026
|
+
for (const name of names) delete currentScope[name];
|
|
15027
|
+
return { [kind]: { ...map, [scope]: currentScope } };
|
|
15028
|
+
}
|
|
15029
|
+
const next = { ...map };
|
|
15030
|
+
for (const name of names) if (isLeaf(next[name])) delete next[name];
|
|
15031
|
+
return { [kind]: next };
|
|
15032
|
+
});
|
|
15033
|
+
}
|
|
15034
|
+
function clearResourceEntries(store, kind, scope, isLeaf) {
|
|
15035
|
+
const registry = getKindRegistry(store, kind);
|
|
15036
|
+
if (scope && scope !== "root") registry.staged.delete(scope);
|
|
15037
|
+
else if (scope === "root") registry.staged.delete(ROOT_SCOPE);
|
|
15038
|
+
else registry.staged.clear();
|
|
15039
|
+
store.setState((state) => {
|
|
15040
|
+
const map = state[kind];
|
|
15041
|
+
if (scope && scope !== "root") {
|
|
15042
|
+
const { [scope]: _, ...rest } = map;
|
|
15043
|
+
return { [kind]: rest };
|
|
15044
|
+
}
|
|
15045
|
+
if (scope === "root") {
|
|
15046
|
+
const next = {};
|
|
15047
|
+
for (const [key, value] of Object.entries(map)) if (!isLeaf(value)) next[key] = value;
|
|
15048
|
+
return { [kind]: next };
|
|
15049
|
+
}
|
|
15050
|
+
return { [kind]: {} };
|
|
15051
|
+
});
|
|
15052
|
+
}
|
|
15053
|
+
|
|
15054
|
+
function clearHmrCaches(store) {
|
|
15055
|
+
const primary = store.getState().primaryStore ?? store;
|
|
15056
|
+
invalidateAllResources(primary);
|
|
15057
|
+
primary.setState((state) => ({ _hmrVersion: state._hmrVersion + 1 }));
|
|
15058
|
+
}
|
|
15059
|
+
|
|
15494
15060
|
function CanvasImpl({
|
|
15495
15061
|
ref,
|
|
15496
15062
|
children,
|
|
@@ -15511,6 +15077,8 @@ function CanvasImpl({
|
|
|
15511
15077
|
raycaster,
|
|
15512
15078
|
camera,
|
|
15513
15079
|
scene,
|
|
15080
|
+
autoUpdateFrustum,
|
|
15081
|
+
occlusion,
|
|
15514
15082
|
onPointerMissed,
|
|
15515
15083
|
onDragOverMissed,
|
|
15516
15084
|
onDropMissed,
|
|
@@ -15522,39 +15090,10 @@ function CanvasImpl({
|
|
|
15522
15090
|
forceEven,
|
|
15523
15091
|
...props
|
|
15524
15092
|
}) {
|
|
15525
|
-
const { primaryCanvas, scheduler,
|
|
15526
|
-
const renderer = Object.keys(rendererConfig).length > 0 ? rendererConfig : rendererProp;
|
|
15093
|
+
const { primaryCanvas, scheduler, renderer } = parseRendererConfig(rendererProp);
|
|
15527
15094
|
React.useMemo(() => extend(THREE), []);
|
|
15528
15095
|
const Bridge = useBridge();
|
|
15529
|
-
const backgroundProps = React.useMemo(() =>
|
|
15530
|
-
if (!background) return null;
|
|
15531
|
-
if (typeof background === "object" && !background.isColor) {
|
|
15532
|
-
const { backgroundMap, envMap, files, preset, ...rest } = background;
|
|
15533
|
-
return {
|
|
15534
|
-
...rest,
|
|
15535
|
-
preset,
|
|
15536
|
-
files: envMap || files,
|
|
15537
|
-
backgroundFiles: backgroundMap,
|
|
15538
|
-
background: true
|
|
15539
|
-
};
|
|
15540
|
-
}
|
|
15541
|
-
if (typeof background === "number") {
|
|
15542
|
-
return { color: background, background: true };
|
|
15543
|
-
}
|
|
15544
|
-
if (typeof background === "string") {
|
|
15545
|
-
if (background in presetsObj) {
|
|
15546
|
-
return { preset: background, background: true };
|
|
15547
|
-
}
|
|
15548
|
-
if (/^(https?:\/\/|\/|\.\/|\.\.\/)|\\.(hdr|exr|jpg|jpeg|png|webp|gif)$/i.test(background)) {
|
|
15549
|
-
return { files: background, background: true };
|
|
15550
|
-
}
|
|
15551
|
-
return { color: background, background: true };
|
|
15552
|
-
}
|
|
15553
|
-
if (background.isColor) {
|
|
15554
|
-
return { color: background, background: true };
|
|
15555
|
-
}
|
|
15556
|
-
return null;
|
|
15557
|
-
}, [background]);
|
|
15096
|
+
const backgroundProps = React.useMemo(() => parseBackground(background), [background]);
|
|
15558
15097
|
const hasInitialSizeRef = React.useRef(false);
|
|
15559
15098
|
const measureConfig = React.useMemo(() => {
|
|
15560
15099
|
if (!hasInitialSizeRef.current) {
|
|
@@ -15596,6 +15135,7 @@ function CanvasImpl({
|
|
|
15596
15135
|
const handleDropMissed = useMutableCallback(onDropMissed);
|
|
15597
15136
|
const [block, setBlock] = React.useState(false);
|
|
15598
15137
|
const [error, setError] = React.useState(false);
|
|
15138
|
+
const [fallbackVisible, setFallbackVisible] = React.useState(false);
|
|
15599
15139
|
if (block) throw block;
|
|
15600
15140
|
if (error) throw error;
|
|
15601
15141
|
const root = React.useRef(null);
|
|
@@ -15603,6 +15143,7 @@ function CanvasImpl({
|
|
|
15603
15143
|
const unsubscribeErrorRef = React.useRef(null);
|
|
15604
15144
|
useIsomorphicLayoutEffect(() => {
|
|
15605
15145
|
effectActiveRef.current = true;
|
|
15146
|
+
if (fallbackVisible) return;
|
|
15606
15147
|
const canvas = canvasRef.current;
|
|
15607
15148
|
if (effectiveSize.width > 0 && effectiveSize.height > 0 && canvas) {
|
|
15608
15149
|
if (!root.current) {
|
|
@@ -15623,7 +15164,7 @@ function CanvasImpl({
|
|
|
15623
15164
|
}
|
|
15624
15165
|
async function run() {
|
|
15625
15166
|
if (!effectActiveRef.current || !root.current) return;
|
|
15626
|
-
await root.current.configure({
|
|
15167
|
+
const configured = await root.current.configure({
|
|
15627
15168
|
id,
|
|
15628
15169
|
primaryCanvas,
|
|
15629
15170
|
scheduler,
|
|
@@ -15638,6 +15179,8 @@ function CanvasImpl({
|
|
|
15638
15179
|
performance,
|
|
15639
15180
|
raycaster,
|
|
15640
15181
|
camera,
|
|
15182
|
+
autoUpdateFrustum,
|
|
15183
|
+
occlusion,
|
|
15641
15184
|
size: effectiveSize,
|
|
15642
15185
|
// Store size props for reset functionality
|
|
15643
15186
|
_sizeProps: width !== void 0 || height !== void 0 ? { width, height } : null,
|
|
@@ -15662,8 +15205,20 @@ function CanvasImpl({
|
|
|
15662
15205
|
}
|
|
15663
15206
|
onCreated?.(state);
|
|
15664
15207
|
}
|
|
15665
|
-
})
|
|
15666
|
-
|
|
15208
|
+
}).then(
|
|
15209
|
+
() => true,
|
|
15210
|
+
// Renderer setup failed (e.g. no WebGL/WebGPU support). The `fallback` prop lives
|
|
15211
|
+
// inside <canvas>, which browsers don't display, so surface it as visible DOM
|
|
15212
|
+
// instead; with no fallback, rethrow to an external error boundary. (#3757)
|
|
15213
|
+
(setupError) => {
|
|
15214
|
+
if (effectActiveRef.current) {
|
|
15215
|
+
if (fallback != null) setFallbackVisible(true);
|
|
15216
|
+
else setError(setupError);
|
|
15217
|
+
}
|
|
15218
|
+
return false;
|
|
15219
|
+
}
|
|
15220
|
+
);
|
|
15221
|
+
if (!configured || !effectActiveRef.current || !root.current) return;
|
|
15667
15222
|
root.current.render(
|
|
15668
15223
|
/* @__PURE__ */ jsx(Bridge, { children: /* @__PURE__ */ jsx(ErrorBoundary, { set: setError, children: /* @__PURE__ */ jsxs(React.Suspense, { fallback: /* @__PURE__ */ jsx(Block, { set: setBlock }), children: [
|
|
15669
15224
|
backgroundProps && /* @__PURE__ */ jsx(Environment, { ...backgroundProps }),
|
|
@@ -15697,20 +15252,13 @@ function CanvasImpl({
|
|
|
15697
15252
|
const handleHMR = () => {
|
|
15698
15253
|
queueMicrotask(() => {
|
|
15699
15254
|
const rootEntry = _roots.get(canvas);
|
|
15700
|
-
if (rootEntry?.store)
|
|
15701
|
-
rootEntry.store.setState((state) => ({
|
|
15702
|
-
nodes: {},
|
|
15703
|
-
uniforms: {},
|
|
15704
|
-
_hmrVersion: state._hmrVersion + 1
|
|
15705
|
-
}));
|
|
15706
|
-
}
|
|
15255
|
+
if (rootEntry?.store) clearHmrCaches(rootEntry.store);
|
|
15707
15256
|
});
|
|
15708
15257
|
};
|
|
15709
15258
|
if (typeof import.meta !== "undefined" && import.meta.hot) {
|
|
15710
15259
|
const hot = import.meta.hot;
|
|
15711
15260
|
hot.on("vite:afterUpdate", handleHMR);
|
|
15712
|
-
return () => hot.
|
|
15713
|
-
});
|
|
15261
|
+
return () => hot.off?.("vite:afterUpdate", handleHMR);
|
|
15714
15262
|
}
|
|
15715
15263
|
if (typeof module !== "undefined" && module.hot) {
|
|
15716
15264
|
const hot = module.hot;
|
|
@@ -15733,7 +15281,20 @@ function CanvasImpl({
|
|
|
15733
15281
|
...style
|
|
15734
15282
|
},
|
|
15735
15283
|
...props,
|
|
15736
|
-
children:
|
|
15284
|
+
children: fallbackVisible ? (
|
|
15285
|
+
// Renderer setup failed: render the fallback as visible DOM. Inside <canvas> (below)
|
|
15286
|
+
// it exists in the tree but browsers never paint it, which is the whole bug (#3757).
|
|
15287
|
+
fallback
|
|
15288
|
+
) : /* @__PURE__ */ jsx("div", { ref: containerRef, className: "r3f-canvas-container", style: { width: "100%", height: "100%" }, children: /* @__PURE__ */ jsx(
|
|
15289
|
+
"canvas",
|
|
15290
|
+
{
|
|
15291
|
+
ref: canvasRef,
|
|
15292
|
+
id,
|
|
15293
|
+
className: "r3f-canvas",
|
|
15294
|
+
style: { display: "block", width: "100%", height: "100%" },
|
|
15295
|
+
children: fallback
|
|
15296
|
+
}
|
|
15297
|
+
) })
|
|
15737
15298
|
}
|
|
15738
15299
|
);
|
|
15739
15300
|
}
|
|
@@ -15806,23 +15367,50 @@ let ScopedStore = _ScopedStore;
|
|
|
15806
15367
|
function createScopedStore(data) {
|
|
15807
15368
|
return new ScopedStore(data);
|
|
15808
15369
|
}
|
|
15809
|
-
function createLazyCreatorState(state) {
|
|
15370
|
+
function createLazyCreatorState(state, store) {
|
|
15810
15371
|
let _uniforms = null;
|
|
15811
15372
|
let _nodes = null;
|
|
15373
|
+
let _buffers = null;
|
|
15374
|
+
let _gpuStorage = null;
|
|
15375
|
+
const view = (kind) => store ? withStagedOverlay(store, kind, state[kind]) : state[kind];
|
|
15812
15376
|
return Object.create(state, {
|
|
15813
15377
|
uniforms: {
|
|
15814
15378
|
get() {
|
|
15815
|
-
return _uniforms ?? (_uniforms = createScopedStore(
|
|
15379
|
+
return _uniforms ?? (_uniforms = createScopedStore(view("uniforms")));
|
|
15816
15380
|
}
|
|
15817
15381
|
},
|
|
15818
15382
|
nodes: {
|
|
15819
15383
|
get() {
|
|
15820
|
-
return _nodes ?? (_nodes = createScopedStore(
|
|
15384
|
+
return _nodes ?? (_nodes = createScopedStore(view("nodes")));
|
|
15385
|
+
}
|
|
15386
|
+
},
|
|
15387
|
+
buffers: {
|
|
15388
|
+
get() {
|
|
15389
|
+
return _buffers ?? (_buffers = createScopedStore(view("buffers")));
|
|
15390
|
+
}
|
|
15391
|
+
},
|
|
15392
|
+
gpuStorage: {
|
|
15393
|
+
get() {
|
|
15394
|
+
return _gpuStorage ?? (_gpuStorage = createScopedStore(view("gpuStorage")));
|
|
15821
15395
|
}
|
|
15822
15396
|
}
|
|
15823
15397
|
});
|
|
15824
15398
|
}
|
|
15825
15399
|
|
|
15400
|
+
function resolvePrimary(local) {
|
|
15401
|
+
return local.getState().primaryStore ?? local;
|
|
15402
|
+
}
|
|
15403
|
+
function usePrimaryStore() {
|
|
15404
|
+
const local = useStore();
|
|
15405
|
+
const primary = local.getState().primaryStore;
|
|
15406
|
+
return useMemo(() => primary ?? local, [primary, local]);
|
|
15407
|
+
}
|
|
15408
|
+
function usePrimaryThree(selector = (state) => state, equalityFn) {
|
|
15409
|
+
const local = useStore();
|
|
15410
|
+
const primary = resolvePrimary(local);
|
|
15411
|
+
return primary(selector, equalityFn);
|
|
15412
|
+
}
|
|
15413
|
+
|
|
15826
15414
|
function addTexture(set, key, value) {
|
|
15827
15415
|
set((state) => {
|
|
15828
15416
|
const newMap = new Map(state.textures);
|
|
@@ -15901,6 +15489,10 @@ function vectorize(inObject) {
|
|
|
15901
15489
|
if (obj.isVector2 || obj.isVector3 || obj.isVector4) return inObject;
|
|
15902
15490
|
if (obj.isMatrix3 || obj.isMatrix4) return inObject;
|
|
15903
15491
|
if (obj.isColor || obj.isEuler || obj.isQuaternion || obj.isSpherical) return inObject;
|
|
15492
|
+
if ("r" in obj && "g" in obj && "b" in obj && typeof obj.r === "number" && typeof obj.g === "number" && typeof obj.b === "number") {
|
|
15493
|
+
const scale = obj.r > 1 || obj.g > 1 || obj.b > 1 ? 1 / 255 : 1;
|
|
15494
|
+
return new Color(obj.r * scale, obj.g * scale, obj.b * scale);
|
|
15495
|
+
}
|
|
15904
15496
|
if ("x" in obj && "y" in obj && typeof obj.x === "number" && typeof obj.y === "number") {
|
|
15905
15497
|
if ("w" in obj && typeof obj.w === "number" && "z" in obj && typeof obj.z === "number") {
|
|
15906
15498
|
return new Vector4(obj.x, obj.y, obj.z, obj.w);
|
|
@@ -15922,65 +15514,56 @@ function useCompareMemoize(value, deep) {
|
|
|
15922
15514
|
return ref.current;
|
|
15923
15515
|
}
|
|
15924
15516
|
|
|
15517
|
+
function useScopedResource(options) {
|
|
15518
|
+
const { store, kind, scope, isLeaf, create, prepare, reconcile, input } = options;
|
|
15519
|
+
const scopeKey = scope ?? ROOT_SCOPE;
|
|
15520
|
+
const version = store((s) => s._hmrVersion);
|
|
15521
|
+
const entries = useMemo(() => {
|
|
15522
|
+
const staged = peekStaged(store, kind, scopeKey);
|
|
15523
|
+
const reusable = isScopeValid(store, kind, scopeKey);
|
|
15524
|
+
const map = store.getState()[kind];
|
|
15525
|
+
const committed = reusable ? readCommittedScope(map, scopeKey, isLeaf) : void 0;
|
|
15526
|
+
const result = {};
|
|
15527
|
+
let fresh = null;
|
|
15528
|
+
for (const [name, candidate] of Object.entries(create())) {
|
|
15529
|
+
const existing = staged?.[name] ?? committed?.[name];
|
|
15530
|
+
if (existing !== void 0) {
|
|
15531
|
+
result[name] = existing;
|
|
15532
|
+
reconcile?.(existing, candidate);
|
|
15533
|
+
} else {
|
|
15534
|
+
const value = prepare ? prepare(name, candidate) : candidate;
|
|
15535
|
+
result[name] = value;
|
|
15536
|
+
(fresh ?? (fresh = {}))[name] = value;
|
|
15537
|
+
}
|
|
15538
|
+
}
|
|
15539
|
+
if (fresh) stageEntries(store, kind, scopeKey, fresh);
|
|
15540
|
+
return result;
|
|
15541
|
+
}, [store, kind, scopeKey, version, input]);
|
|
15542
|
+
useIsomorphicLayoutEffect(() => {
|
|
15543
|
+
flushStagedScope(store, kind, scopeKey, isLeaf);
|
|
15544
|
+
});
|
|
15545
|
+
return entries;
|
|
15546
|
+
}
|
|
15547
|
+
|
|
15925
15548
|
const isUniformNode$1 = (value) => value !== null && typeof value === "object" && "value" in value && "uuid" in value;
|
|
15926
15549
|
function useUniforms(creatorOrScope, scope) {
|
|
15927
|
-
const store =
|
|
15550
|
+
const store = usePrimaryStore();
|
|
15928
15551
|
const removeUniforms2 = useCallback(
|
|
15929
|
-
(names, targetScope) =>
|
|
15930
|
-
const nameArray = Array.isArray(names) ? names : [names];
|
|
15931
|
-
store.setState((state) => {
|
|
15932
|
-
if (targetScope) {
|
|
15933
|
-
const currentScope = { ...state.uniforms[targetScope] };
|
|
15934
|
-
for (const name of nameArray) delete currentScope[name];
|
|
15935
|
-
return { uniforms: { ...state.uniforms, [targetScope]: currentScope } };
|
|
15936
|
-
}
|
|
15937
|
-
const uniforms2 = { ...state.uniforms };
|
|
15938
|
-
for (const name of nameArray) if (isUniformNode$1(uniforms2[name])) delete uniforms2[name];
|
|
15939
|
-
return { uniforms: uniforms2 };
|
|
15940
|
-
});
|
|
15941
|
-
},
|
|
15552
|
+
(names, targetScope) => removeResourceEntries(store, "uniforms", Array.isArray(names) ? names : [names], targetScope, isUniformNode$1),
|
|
15942
15553
|
[store]
|
|
15943
15554
|
);
|
|
15944
15555
|
const clearUniforms = useCallback(
|
|
15945
|
-
(targetScope) =>
|
|
15946
|
-
store.setState((state) => {
|
|
15947
|
-
if (targetScope && targetScope !== "root") {
|
|
15948
|
-
const { [targetScope]: _, ...rest } = state.uniforms;
|
|
15949
|
-
return { uniforms: rest };
|
|
15950
|
-
}
|
|
15951
|
-
if (targetScope === "root") {
|
|
15952
|
-
const uniforms2 = {};
|
|
15953
|
-
for (const [key, value] of Object.entries(state.uniforms)) {
|
|
15954
|
-
if (!isUniformNode$1(value)) uniforms2[key] = value;
|
|
15955
|
-
}
|
|
15956
|
-
return { uniforms: uniforms2 };
|
|
15957
|
-
}
|
|
15958
|
-
return { uniforms: {} };
|
|
15959
|
-
});
|
|
15960
|
-
},
|
|
15556
|
+
(targetScope) => clearResourceEntries(store, "uniforms", targetScope, isUniformNode$1),
|
|
15961
15557
|
[store]
|
|
15962
15558
|
);
|
|
15963
15559
|
const rebuildUniforms = useCallback(
|
|
15964
|
-
(targetScope) =>
|
|
15965
|
-
store.setState((state) => {
|
|
15966
|
-
let newUniforms = {};
|
|
15967
|
-
if (targetScope && targetScope !== "root") {
|
|
15968
|
-
const { [targetScope]: _, ...rest } = state.uniforms;
|
|
15969
|
-
newUniforms = rest;
|
|
15970
|
-
} else if (targetScope === "root") {
|
|
15971
|
-
for (const [key, value] of Object.entries(state.uniforms)) {
|
|
15972
|
-
if (!isUniformNode$1(value)) newUniforms[key] = value;
|
|
15973
|
-
}
|
|
15974
|
-
}
|
|
15975
|
-
return { uniforms: newUniforms, _hmrVersion: state._hmrVersion + 1 };
|
|
15976
|
-
});
|
|
15977
|
-
},
|
|
15560
|
+
(targetScope) => rebuildResource(store, "uniforms", targetScope),
|
|
15978
15561
|
[store]
|
|
15979
15562
|
);
|
|
15980
15563
|
const inputForMemoization = useMemo(() => {
|
|
15981
15564
|
let raw = creatorOrScope;
|
|
15982
15565
|
if (is.fun(creatorOrScope)) {
|
|
15983
|
-
const wrappedState = createLazyCreatorState(store.getState());
|
|
15566
|
+
const wrappedState = createLazyCreatorState(store.getState(), store);
|
|
15984
15567
|
raw = creatorOrScope(wrappedState);
|
|
15985
15568
|
}
|
|
15986
15569
|
if (raw && typeof raw === "object" && !Array.isArray(raw)) {
|
|
@@ -15994,80 +15577,44 @@ function useUniforms(creatorOrScope, scope) {
|
|
|
15994
15577
|
}, [creatorOrScope, store]);
|
|
15995
15578
|
const memoizedInput = useCompareMemoize(inputForMemoization);
|
|
15996
15579
|
const isReader = memoizedInput === void 0 || typeof memoizedInput === "string";
|
|
15997
|
-
const storeUniforms =
|
|
15998
|
-
const
|
|
15999
|
-
|
|
16000
|
-
|
|
16001
|
-
|
|
16002
|
-
|
|
16003
|
-
|
|
16004
|
-
|
|
16005
|
-
|
|
16006
|
-
|
|
16007
|
-
|
|
16008
|
-
|
|
16009
|
-
|
|
16010
|
-
|
|
16011
|
-
|
|
16012
|
-
|
|
16013
|
-
|
|
16014
|
-
|
|
16015
|
-
|
|
16016
|
-
|
|
16017
|
-
|
|
16018
|
-
|
|
16019
|
-
if (scope) {
|
|
16020
|
-
if (!state.uniforms[scope]) state.uniforms[scope] = {};
|
|
16021
|
-
targetRecord = state.uniforms[scope];
|
|
16022
|
-
}
|
|
16023
|
-
for (const [name, node] of Object.entries(created)) {
|
|
16024
|
-
if (targetRecord[name]) {
|
|
16025
|
-
result[name] = targetRecord[name];
|
|
16026
|
-
const existingVal = result[name].value;
|
|
16027
|
-
const newVal = vectorize(node);
|
|
16028
|
-
let equals = newVal === existingVal;
|
|
16029
|
-
if (!equals && hasEqualsMethod(existingVal) && hasEqualsMethod(newVal)) {
|
|
16030
|
-
if (isThreeVector(existingVal) && isThreeVector(newVal)) {
|
|
16031
|
-
equals = vectorEquals(existingVal, newVal);
|
|
16032
|
-
} else if (isSameThreeType(existingVal, newVal)) {
|
|
16033
|
-
equals = existingVal.equals(newVal);
|
|
16034
|
-
}
|
|
15580
|
+
const storeUniforms = usePrimaryThree((s) => s.uniforms);
|
|
15581
|
+
const created = useScopedResource({
|
|
15582
|
+
store,
|
|
15583
|
+
kind: "uniforms",
|
|
15584
|
+
scope: isReader ? void 0 : scope,
|
|
15585
|
+
isLeaf: isUniformNode$1,
|
|
15586
|
+
input: memoizedInput,
|
|
15587
|
+
create: () => {
|
|
15588
|
+
if (isReader) return {};
|
|
15589
|
+
if (typeof memoizedInput !== "object" || memoizedInput === null) throw new Error("Invalid uniform input");
|
|
15590
|
+
return memoizedInput;
|
|
15591
|
+
},
|
|
15592
|
+
prepare: (name, candidate) => createUniform(name, candidate, scope),
|
|
15593
|
+
reconcile: (existing, candidate) => {
|
|
15594
|
+
const existingVal = existing.value;
|
|
15595
|
+
const newVal = vectorize(candidate);
|
|
15596
|
+
let equals = newVal === existingVal;
|
|
15597
|
+
if (!equals && hasEqualsMethod(existingVal) && hasEqualsMethod(newVal)) {
|
|
15598
|
+
if (isThreeVector(existingVal) && isThreeVector(newVal)) {
|
|
15599
|
+
equals = vectorEquals(existingVal, newVal);
|
|
15600
|
+
} else if (isSameThreeType(existingVal, newVal)) {
|
|
15601
|
+
equals = existingVal.equals(newVal);
|
|
16035
15602
|
}
|
|
16036
|
-
if (!equals) result[name].value = newVal;
|
|
16037
|
-
} else {
|
|
16038
|
-
result[name] = createUniform(name, node, scope);
|
|
16039
|
-
hasNewUniforms = true;
|
|
16040
15603
|
}
|
|
15604
|
+
if (!equals) existing.value = newVal;
|
|
16041
15605
|
}
|
|
16042
|
-
|
|
16043
|
-
|
|
16044
|
-
|
|
16045
|
-
|
|
16046
|
-
|
|
16047
|
-
|
|
16048
|
-
|
|
16049
|
-
|
|
16050
|
-
} else {
|
|
16051
|
-
set((s) => ({ uniforms: { ...s.uniforms, ...result } }));
|
|
16052
|
-
}
|
|
16053
|
-
}
|
|
16054
|
-
return result;
|
|
16055
|
-
}, [store, memoizedInput, scope, readerDep, creatorDep]);
|
|
15606
|
+
});
|
|
15607
|
+
let uniforms = created;
|
|
15608
|
+
if (memoizedInput === void 0) {
|
|
15609
|
+
uniforms = storeUniforms;
|
|
15610
|
+
} else if (typeof memoizedInput === "string") {
|
|
15611
|
+
const scopeData = storeUniforms[memoizedInput];
|
|
15612
|
+
uniforms = scopeData && !isUniformNode$1(scopeData) ? scopeData : {};
|
|
15613
|
+
}
|
|
16056
15614
|
return { ...uniforms, removeUniforms: removeUniforms2, clearUniforms, rebuildUniforms };
|
|
16057
15615
|
}
|
|
16058
15616
|
function rebuildAllUniforms(store, scope) {
|
|
16059
|
-
store
|
|
16060
|
-
let newUniforms = {};
|
|
16061
|
-
if (scope && scope !== "root") {
|
|
16062
|
-
const { [scope]: _, ...rest } = state.uniforms;
|
|
16063
|
-
newUniforms = rest;
|
|
16064
|
-
} else if (scope === "root") {
|
|
16065
|
-
for (const [key, value] of Object.entries(state.uniforms)) {
|
|
16066
|
-
if (!isUniformNode$1(value)) newUniforms[key] = value;
|
|
16067
|
-
}
|
|
16068
|
-
}
|
|
16069
|
-
return { uniforms: newUniforms, _hmrVersion: state._hmrVersion + 1 };
|
|
16070
|
-
});
|
|
15617
|
+
rebuildResource(store, "uniforms", scope);
|
|
16071
15618
|
}
|
|
16072
15619
|
function removeUniforms(set, names, scope) {
|
|
16073
15620
|
set((state) => {
|
|
@@ -16131,191 +15678,91 @@ function isSameThreeType(a, b) {
|
|
|
16131
15678
|
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;
|
|
16132
15679
|
}
|
|
16133
15680
|
|
|
15681
|
+
const uniformOf = uniform;
|
|
16134
15682
|
const isUniformNode = (value) => value !== null && typeof value === "object" && "value" in value && "uuid" in value;
|
|
16135
15683
|
const isTSLNode$1 = (value) => value !== null && typeof value === "object" && "uuid" in value && "nodeType" in value;
|
|
16136
15684
|
function useUniform(name, value) {
|
|
16137
15685
|
const store = useStore();
|
|
16138
|
-
const
|
|
16139
|
-
|
|
16140
|
-
|
|
16141
|
-
|
|
16142
|
-
|
|
16143
|
-
|
|
15686
|
+
const registered = useScopedResource({
|
|
15687
|
+
store,
|
|
15688
|
+
kind: "uniforms",
|
|
15689
|
+
scope: void 0,
|
|
15690
|
+
isLeaf: isUniformNode,
|
|
15691
|
+
input: name,
|
|
15692
|
+
create: () => value === void 0 ? {} : { [name]: value },
|
|
15693
|
+
prepare: createNamedUniform,
|
|
15694
|
+
reconcile: (existing2) => {
|
|
16144
15695
|
if (value !== void 0 && !isTSLNode$1(value) && !isUniformNode(value)) {
|
|
16145
|
-
|
|
15696
|
+
existing2.value = typeof value === "string" ? new Color(value) : value;
|
|
16146
15697
|
}
|
|
16147
|
-
return existing;
|
|
16148
|
-
}
|
|
16149
|
-
if (value === void 0) {
|
|
16150
|
-
throw new Error(
|
|
16151
|
-
`[useUniform] Uniform "${name}" not found. Create it first with: useUniform('${name}', initialValue)`
|
|
16152
|
-
);
|
|
16153
|
-
}
|
|
16154
|
-
if (isUniformNode(value)) {
|
|
16155
|
-
const node2 = value;
|
|
16156
|
-
if (typeof node2.setName === "function") {
|
|
16157
|
-
node2.setName(name);
|
|
16158
|
-
}
|
|
16159
|
-
set((s) => ({
|
|
16160
|
-
uniforms: { ...s.uniforms, [name]: node2 }
|
|
16161
|
-
}));
|
|
16162
|
-
return node2;
|
|
16163
|
-
}
|
|
16164
|
-
let node;
|
|
16165
|
-
if (isTSLNode$1(value)) {
|
|
16166
|
-
node = uniform(value);
|
|
16167
|
-
} else if (typeof value === "string") {
|
|
16168
|
-
node = uniform(new Color(value));
|
|
16169
|
-
} else {
|
|
16170
|
-
node = uniform(value);
|
|
16171
15698
|
}
|
|
16172
|
-
|
|
16173
|
-
|
|
16174
|
-
|
|
16175
|
-
|
|
16176
|
-
|
|
16177
|
-
|
|
16178
|
-
|
|
16179
|
-
|
|
16180
|
-
|
|
16181
|
-
|
|
16182
|
-
|
|
15699
|
+
});
|
|
15700
|
+
if (registered[name]) return registered[name];
|
|
15701
|
+
const existing = peekStaged(store, "uniforms", ROOT_SCOPE)?.[name] ?? store.getState().uniforms[name];
|
|
15702
|
+
if (existing && isUniformNode(existing)) return existing;
|
|
15703
|
+
throw new Error(
|
|
15704
|
+
`[useUniform] Uniform "${name}" not found. Create it first with: useUniform('${name}', initialValue)`
|
|
15705
|
+
);
|
|
15706
|
+
}
|
|
15707
|
+
function createNamedUniform(name, value) {
|
|
15708
|
+
let node;
|
|
15709
|
+
if (isUniformNode(value)) {
|
|
15710
|
+
node = value;
|
|
15711
|
+
} else if (isTSLNode$1(value)) {
|
|
15712
|
+
node = uniformOf(value);
|
|
15713
|
+
} else if (typeof value === "string") {
|
|
15714
|
+
node = uniform(new Color(value));
|
|
15715
|
+
} else {
|
|
15716
|
+
node = uniformOf(value);
|
|
15717
|
+
}
|
|
15718
|
+
if (typeof node.setName === "function") {
|
|
15719
|
+
node.setName(name);
|
|
15720
|
+
}
|
|
15721
|
+
return node;
|
|
16183
15722
|
}
|
|
16184
15723
|
|
|
16185
15724
|
const isTSLNode = (value) => value !== null && typeof value === "object" && ("uuid" in value || "nodeType" in value);
|
|
16186
15725
|
function useNodes(creatorOrScope, scope) {
|
|
16187
|
-
const store =
|
|
15726
|
+
const store = usePrimaryStore();
|
|
16188
15727
|
const removeNodes2 = useCallback(
|
|
16189
|
-
(names, targetScope) =>
|
|
16190
|
-
const nameArray = Array.isArray(names) ? names : [names];
|
|
16191
|
-
store.setState((state) => {
|
|
16192
|
-
if (targetScope) {
|
|
16193
|
-
const currentScope = { ...state.nodes[targetScope] };
|
|
16194
|
-
for (const name of nameArray) delete currentScope[name];
|
|
16195
|
-
return { nodes: { ...state.nodes, [targetScope]: currentScope } };
|
|
16196
|
-
}
|
|
16197
|
-
const nodes2 = { ...state.nodes };
|
|
16198
|
-
for (const name of nameArray) if (isTSLNode(nodes2[name])) delete nodes2[name];
|
|
16199
|
-
return { nodes: nodes2 };
|
|
16200
|
-
});
|
|
16201
|
-
},
|
|
15728
|
+
(names, targetScope) => removeResourceEntries(store, "nodes", Array.isArray(names) ? names : [names], targetScope, isTSLNode),
|
|
16202
15729
|
[store]
|
|
16203
15730
|
);
|
|
16204
15731
|
const clearNodes = useCallback(
|
|
16205
|
-
(targetScope) =>
|
|
16206
|
-
store.setState((state) => {
|
|
16207
|
-
if (targetScope && targetScope !== "root") {
|
|
16208
|
-
const { [targetScope]: _, ...rest } = state.nodes;
|
|
16209
|
-
return { nodes: rest };
|
|
16210
|
-
}
|
|
16211
|
-
if (targetScope === "root") {
|
|
16212
|
-
const nodes2 = {};
|
|
16213
|
-
for (const [key, value] of Object.entries(state.nodes)) {
|
|
16214
|
-
if (!isTSLNode(value)) nodes2[key] = value;
|
|
16215
|
-
}
|
|
16216
|
-
return { nodes: nodes2 };
|
|
16217
|
-
}
|
|
16218
|
-
return { nodes: {} };
|
|
16219
|
-
});
|
|
16220
|
-
},
|
|
15732
|
+
(targetScope) => clearResourceEntries(store, "nodes", targetScope, isTSLNode),
|
|
16221
15733
|
[store]
|
|
16222
15734
|
);
|
|
16223
15735
|
const rebuildNodes = useCallback(
|
|
16224
|
-
(targetScope) =>
|
|
16225
|
-
store.setState((state) => {
|
|
16226
|
-
let newNodes = state.nodes;
|
|
16227
|
-
if (targetScope && targetScope !== "root") {
|
|
16228
|
-
const { [targetScope]: _, ...rest } = state.nodes;
|
|
16229
|
-
newNodes = rest;
|
|
16230
|
-
} else if (targetScope === "root") {
|
|
16231
|
-
newNodes = {};
|
|
16232
|
-
for (const [key, value] of Object.entries(state.nodes)) {
|
|
16233
|
-
if (!isTSLNode(value)) newNodes[key] = value;
|
|
16234
|
-
}
|
|
16235
|
-
} else {
|
|
16236
|
-
newNodes = {};
|
|
16237
|
-
}
|
|
16238
|
-
return { nodes: newNodes, _hmrVersion: state._hmrVersion + 1 };
|
|
16239
|
-
});
|
|
16240
|
-
},
|
|
15736
|
+
(targetScope) => rebuildResource(store, "nodes", targetScope),
|
|
16241
15737
|
[store]
|
|
16242
15738
|
);
|
|
16243
15739
|
const isReader = creatorOrScope === void 0 || typeof creatorOrScope === "string";
|
|
16244
|
-
const storeNodes =
|
|
16245
|
-
const
|
|
16246
|
-
|
|
16247
|
-
|
|
16248
|
-
|
|
16249
|
-
|
|
16250
|
-
|
|
16251
|
-
return
|
|
16252
|
-
|
|
16253
|
-
|
|
16254
|
-
|
|
16255
|
-
|
|
16256
|
-
return
|
|
16257
|
-
}
|
|
16258
|
-
const state = store.getState();
|
|
16259
|
-
const set = store.setState;
|
|
16260
|
-
const creator = creatorOrScope;
|
|
16261
|
-
const wrappedState = createLazyCreatorState(state);
|
|
16262
|
-
const created = creator(wrappedState);
|
|
16263
|
-
const result = {};
|
|
16264
|
-
let hasNewNodes = false;
|
|
16265
|
-
if (scope) {
|
|
16266
|
-
const currentScope = state.nodes[scope] ?? {};
|
|
16267
|
-
for (const [name, node] of Object.entries(created)) {
|
|
16268
|
-
if (currentScope[name]) {
|
|
16269
|
-
result[name] = currentScope[name];
|
|
16270
|
-
} else {
|
|
16271
|
-
node.setName?.(`${scope}.${name}`);
|
|
16272
|
-
result[name] = node;
|
|
16273
|
-
hasNewNodes = true;
|
|
16274
|
-
}
|
|
16275
|
-
}
|
|
16276
|
-
if (hasNewNodes) {
|
|
16277
|
-
set((s) => ({
|
|
16278
|
-
nodes: {
|
|
16279
|
-
...s.nodes,
|
|
16280
|
-
[scope]: { ...s.nodes[scope], ...result }
|
|
16281
|
-
}
|
|
16282
|
-
}));
|
|
16283
|
-
}
|
|
16284
|
-
return result;
|
|
16285
|
-
}
|
|
16286
|
-
for (const [name, node] of Object.entries(created)) {
|
|
16287
|
-
const existing = state.nodes[name];
|
|
16288
|
-
if (existing && isTSLNode(existing)) {
|
|
16289
|
-
result[name] = existing;
|
|
16290
|
-
} else {
|
|
16291
|
-
node.setName?.(name);
|
|
16292
|
-
result[name] = node;
|
|
16293
|
-
hasNewNodes = true;
|
|
16294
|
-
}
|
|
16295
|
-
}
|
|
16296
|
-
if (hasNewNodes) {
|
|
16297
|
-
set((s) => ({ nodes: { ...s.nodes, ...result } }));
|
|
15740
|
+
const storeNodes = usePrimaryThree((s) => s.nodes);
|
|
15741
|
+
const created = useScopedResource({
|
|
15742
|
+
store,
|
|
15743
|
+
kind: "nodes",
|
|
15744
|
+
scope: isReader ? void 0 : scope,
|
|
15745
|
+
isLeaf: isTSLNode,
|
|
15746
|
+
create: () => {
|
|
15747
|
+
if (isReader) return {};
|
|
15748
|
+
return creatorOrScope(createLazyCreatorState(store.getState(), store));
|
|
15749
|
+
},
|
|
15750
|
+
prepare: (name, node) => {
|
|
15751
|
+
node.setName?.(scope ? `${scope}.${name}` : name);
|
|
15752
|
+
return node;
|
|
16298
15753
|
}
|
|
16299
|
-
|
|
16300
|
-
|
|
15754
|
+
});
|
|
15755
|
+
let nodes = created;
|
|
15756
|
+
if (creatorOrScope === void 0) {
|
|
15757
|
+
nodes = storeNodes;
|
|
15758
|
+
} else if (typeof creatorOrScope === "string") {
|
|
15759
|
+
const scopeData = storeNodes[creatorOrScope];
|
|
15760
|
+
nodes = scopeData && !isTSLNode(scopeData) ? scopeData : {};
|
|
15761
|
+
}
|
|
16301
15762
|
return { ...nodes, removeNodes: removeNodes2, clearNodes, rebuildNodes };
|
|
16302
15763
|
}
|
|
16303
15764
|
function rebuildAllNodes(store, scope) {
|
|
16304
|
-
store
|
|
16305
|
-
let newNodes = state.nodes;
|
|
16306
|
-
if (scope && scope !== "root") {
|
|
16307
|
-
const { [scope]: _, ...rest } = state.nodes;
|
|
16308
|
-
newNodes = rest;
|
|
16309
|
-
} else if (scope === "root") {
|
|
16310
|
-
newNodes = {};
|
|
16311
|
-
for (const [key, value] of Object.entries(state.nodes)) {
|
|
16312
|
-
if (!isTSLNode(value)) newNodes[key] = value;
|
|
16313
|
-
}
|
|
16314
|
-
} else {
|
|
16315
|
-
newNodes = {};
|
|
16316
|
-
}
|
|
16317
|
-
return { nodes: newNodes, _hmrVersion: state._hmrVersion + 1 };
|
|
16318
|
-
});
|
|
15765
|
+
rebuildResource(store, "nodes", scope);
|
|
16319
15766
|
}
|
|
16320
15767
|
function removeNodes(set, names, scope) {
|
|
16321
15768
|
set((state) => {
|
|
@@ -16345,18 +15792,172 @@ function clearRootNodes(set) {
|
|
|
16345
15792
|
});
|
|
16346
15793
|
}
|
|
16347
15794
|
function useLocalNodes(creator) {
|
|
16348
|
-
const store =
|
|
16349
|
-
const uniforms =
|
|
16350
|
-
const nodes =
|
|
16351
|
-
const textures =
|
|
16352
|
-
const hmrVersion =
|
|
15795
|
+
const store = usePrimaryStore();
|
|
15796
|
+
const uniforms = usePrimaryThree((s) => s.uniforms);
|
|
15797
|
+
const nodes = usePrimaryThree((s) => s.nodes);
|
|
15798
|
+
const textures = usePrimaryThree((s) => s.textures);
|
|
15799
|
+
const hmrVersion = usePrimaryThree((s) => s._hmrVersion);
|
|
16353
15800
|
return useMemo(() => {
|
|
16354
|
-
const wrappedState = createLazyCreatorState(store.getState());
|
|
15801
|
+
const wrappedState = createLazyCreatorState(store.getState(), store);
|
|
16355
15802
|
return creator(wrappedState);
|
|
16356
15803
|
}, [store, creator, uniforms, nodes, textures, hmrVersion]);
|
|
16357
15804
|
}
|
|
16358
15805
|
|
|
16359
|
-
|
|
15806
|
+
const isBufferLike = (value) => {
|
|
15807
|
+
if (value === null || typeof value !== "object") return false;
|
|
15808
|
+
if (ArrayBuffer.isView(value)) return true;
|
|
15809
|
+
if ("isBufferAttribute" in value) return true;
|
|
15810
|
+
if ("uuid" in value || "nodeType" in value) return true;
|
|
15811
|
+
return false;
|
|
15812
|
+
};
|
|
15813
|
+
const disposeBuffer = (buffer) => {
|
|
15814
|
+
if (buffer === null || typeof buffer !== "object") return;
|
|
15815
|
+
if ("dispose" in buffer && typeof buffer.dispose === "function") {
|
|
15816
|
+
buffer.dispose();
|
|
15817
|
+
}
|
|
15818
|
+
};
|
|
15819
|
+
function useBuffers(creatorOrScope, scope) {
|
|
15820
|
+
const store = usePrimaryStore();
|
|
15821
|
+
const removeBuffers = useCallback(
|
|
15822
|
+
(names, targetScope) => removeResourceEntries(store, "buffers", Array.isArray(names) ? names : [names], targetScope, isBufferLike),
|
|
15823
|
+
[store]
|
|
15824
|
+
);
|
|
15825
|
+
const clearBuffers = useCallback(
|
|
15826
|
+
(targetScope) => clearResourceEntries(store, "buffers", targetScope, isBufferLike),
|
|
15827
|
+
[store]
|
|
15828
|
+
);
|
|
15829
|
+
const rebuildBuffers = useCallback(
|
|
15830
|
+
(targetScope) => rebuildResource(store, "buffers", targetScope),
|
|
15831
|
+
[store]
|
|
15832
|
+
);
|
|
15833
|
+
const disposeBuffers = useCallback(
|
|
15834
|
+
(names, targetScope) => {
|
|
15835
|
+
const nameArray = Array.isArray(names) ? names : [names];
|
|
15836
|
+
const state = store.getState();
|
|
15837
|
+
for (const name of nameArray) {
|
|
15838
|
+
const buffer = targetScope ? state.buffers[targetScope]?.[name] : state.buffers[name];
|
|
15839
|
+
if (buffer && isBufferLike(buffer)) {
|
|
15840
|
+
disposeBuffer(buffer);
|
|
15841
|
+
}
|
|
15842
|
+
}
|
|
15843
|
+
removeBuffers(names, targetScope);
|
|
15844
|
+
},
|
|
15845
|
+
[store, removeBuffers]
|
|
15846
|
+
);
|
|
15847
|
+
const isReader = creatorOrScope === void 0 || typeof creatorOrScope === "string";
|
|
15848
|
+
const storeBuffers = usePrimaryThree((s) => s.buffers);
|
|
15849
|
+
const created = useScopedResource({
|
|
15850
|
+
store,
|
|
15851
|
+
kind: "buffers",
|
|
15852
|
+
scope: isReader ? void 0 : scope,
|
|
15853
|
+
isLeaf: isBufferLike,
|
|
15854
|
+
create: () => {
|
|
15855
|
+
if (isReader) return {};
|
|
15856
|
+
return creatorOrScope(createLazyCreatorState(store.getState(), store));
|
|
15857
|
+
},
|
|
15858
|
+
prepare: (name, buffer) => {
|
|
15859
|
+
if ("setName" in buffer && typeof buffer.setName === "function") {
|
|
15860
|
+
buffer.setName(scope ? `${scope}.${name}` : name);
|
|
15861
|
+
}
|
|
15862
|
+
return buffer;
|
|
15863
|
+
}
|
|
15864
|
+
});
|
|
15865
|
+
let buffers = created;
|
|
15866
|
+
if (creatorOrScope === void 0) {
|
|
15867
|
+
buffers = storeBuffers;
|
|
15868
|
+
} else if (typeof creatorOrScope === "string") {
|
|
15869
|
+
const scopeData = storeBuffers[creatorOrScope];
|
|
15870
|
+
buffers = scopeData && !isBufferLike(scopeData) ? scopeData : {};
|
|
15871
|
+
}
|
|
15872
|
+
return { ...buffers, removeBuffers, clearBuffers, rebuildBuffers, disposeBuffers };
|
|
15873
|
+
}
|
|
15874
|
+
function rebuildAllBuffers(store, scope) {
|
|
15875
|
+
rebuildResource(store, "buffers", scope);
|
|
15876
|
+
}
|
|
15877
|
+
|
|
15878
|
+
const isStorageLike = (value) => {
|
|
15879
|
+
if (value === null || typeof value !== "object") return false;
|
|
15880
|
+
if ("isTexture" in value) return true;
|
|
15881
|
+
if ("isData3DTexture" in value) return true;
|
|
15882
|
+
if ("uuid" in value || "nodeType" in value) return true;
|
|
15883
|
+
return false;
|
|
15884
|
+
};
|
|
15885
|
+
const disposeStorage = (storage) => {
|
|
15886
|
+
if (storage === null || typeof storage !== "object") return;
|
|
15887
|
+
if ("dispose" in storage && typeof storage.dispose === "function") {
|
|
15888
|
+
storage.dispose();
|
|
15889
|
+
}
|
|
15890
|
+
};
|
|
15891
|
+
function useGPUStorage(creatorOrScope, scope) {
|
|
15892
|
+
const store = usePrimaryStore();
|
|
15893
|
+
const removeStorage = useCallback(
|
|
15894
|
+
(names, targetScope) => removeResourceEntries(store, "gpuStorage", Array.isArray(names) ? names : [names], targetScope, isStorageLike),
|
|
15895
|
+
[store]
|
|
15896
|
+
);
|
|
15897
|
+
const clearStorage = useCallback(
|
|
15898
|
+
(targetScope) => clearResourceEntries(store, "gpuStorage", targetScope, isStorageLike),
|
|
15899
|
+
[store]
|
|
15900
|
+
);
|
|
15901
|
+
const rebuildStorage = useCallback(
|
|
15902
|
+
(targetScope) => rebuildResource(store, "gpuStorage", targetScope),
|
|
15903
|
+
[store]
|
|
15904
|
+
);
|
|
15905
|
+
const disposeStorageFn = useCallback(
|
|
15906
|
+
(names, targetScope) => {
|
|
15907
|
+
const nameArray = Array.isArray(names) ? names : [names];
|
|
15908
|
+
const state = store.getState();
|
|
15909
|
+
for (const name of nameArray) {
|
|
15910
|
+
const storage = targetScope ? state.gpuStorage[targetScope]?.[name] : state.gpuStorage[name];
|
|
15911
|
+
if (storage && isStorageLike(storage)) {
|
|
15912
|
+
disposeStorage(storage);
|
|
15913
|
+
}
|
|
15914
|
+
}
|
|
15915
|
+
removeStorage(names, targetScope);
|
|
15916
|
+
},
|
|
15917
|
+
[store, removeStorage]
|
|
15918
|
+
);
|
|
15919
|
+
const isReader = creatorOrScope === void 0 || typeof creatorOrScope === "string";
|
|
15920
|
+
const storeStorage = usePrimaryThree((s) => s.gpuStorage);
|
|
15921
|
+
const created = useScopedResource({
|
|
15922
|
+
store,
|
|
15923
|
+
kind: "gpuStorage",
|
|
15924
|
+
scope: isReader ? void 0 : scope,
|
|
15925
|
+
isLeaf: isStorageLike,
|
|
15926
|
+
create: () => {
|
|
15927
|
+
if (isReader) return {};
|
|
15928
|
+
return creatorOrScope(createLazyCreatorState(store.getState(), store));
|
|
15929
|
+
},
|
|
15930
|
+
prepare: (name, storage) => {
|
|
15931
|
+
const label = scope ? `${scope}.${name}` : name;
|
|
15932
|
+
if ("setName" in storage && typeof storage.setName === "function") {
|
|
15933
|
+
storage.setName(label);
|
|
15934
|
+
}
|
|
15935
|
+
if ("name" in storage && typeof storage.name === "string") {
|
|
15936
|
+
storage.name = label;
|
|
15937
|
+
}
|
|
15938
|
+
return storage;
|
|
15939
|
+
}
|
|
15940
|
+
});
|
|
15941
|
+
let gpuStorage = created;
|
|
15942
|
+
if (creatorOrScope === void 0) {
|
|
15943
|
+
gpuStorage = storeStorage;
|
|
15944
|
+
} else if (typeof creatorOrScope === "string") {
|
|
15945
|
+
const scopeData = storeStorage[creatorOrScope];
|
|
15946
|
+
gpuStorage = scopeData && !isStorageLike(scopeData) ? scopeData : {};
|
|
15947
|
+
}
|
|
15948
|
+
return {
|
|
15949
|
+
...gpuStorage,
|
|
15950
|
+
removeStorage,
|
|
15951
|
+
clearStorage,
|
|
15952
|
+
rebuildStorage,
|
|
15953
|
+
disposeStorage: disposeStorageFn
|
|
15954
|
+
};
|
|
15955
|
+
}
|
|
15956
|
+
function rebuildAllStorage(store, scope) {
|
|
15957
|
+
rebuildResource(store, "gpuStorage", scope);
|
|
15958
|
+
}
|
|
15959
|
+
|
|
15960
|
+
function useRenderPipeline(mainCB, setupCB) {
|
|
16360
15961
|
const store = useStore();
|
|
16361
15962
|
const { scene, camera, renderer, isLegacy } = useThree();
|
|
16362
15963
|
const callbacksRanRef = useRef(false);
|
|
@@ -16375,7 +15976,7 @@ function usePostProcessing(mainCB, setupCB) {
|
|
|
16375
15976
|
}, [store]);
|
|
16376
15977
|
const reset = useCallback(() => {
|
|
16377
15978
|
store.setState({
|
|
16378
|
-
|
|
15979
|
+
renderPipeline: null,
|
|
16379
15980
|
passes: {}
|
|
16380
15981
|
});
|
|
16381
15982
|
callbacksRanRef.current = false;
|
|
@@ -16388,18 +15989,18 @@ function usePostProcessing(mainCB, setupCB) {
|
|
|
16388
15989
|
}, []);
|
|
16389
15990
|
useLayoutEffect(() => {
|
|
16390
15991
|
if (isLegacy) {
|
|
16391
|
-
throw new Error("
|
|
15992
|
+
throw new Error("useRenderPipeline is only available with WebGPU renderer. Set renderer prop on Canvas.");
|
|
16392
15993
|
}
|
|
16393
15994
|
if (!renderer || !scene || !camera) return;
|
|
16394
15995
|
const state = store.getState();
|
|
16395
15996
|
const set = store.setState;
|
|
16396
15997
|
try {
|
|
16397
|
-
let
|
|
15998
|
+
let pipeline = state.renderPipeline;
|
|
16398
15999
|
let currentPasses = { ...state.passes };
|
|
16399
|
-
let
|
|
16400
|
-
if (!
|
|
16401
|
-
|
|
16402
|
-
|
|
16000
|
+
let justCreatedPipeline = false;
|
|
16001
|
+
if (!pipeline) {
|
|
16002
|
+
pipeline = new RenderPipeline(renderer);
|
|
16003
|
+
justCreatedPipeline = true;
|
|
16403
16004
|
}
|
|
16404
16005
|
const cacheValid = scenePassCacheRef.current && scenePassCacheRef.current.sceneUuid === scene.uuid && scenePassCacheRef.current.cameraUuid === camera.uuid;
|
|
16405
16006
|
let scenePass;
|
|
@@ -16410,9 +16011,9 @@ function usePostProcessing(mainCB, setupCB) {
|
|
|
16410
16011
|
scenePassCacheRef.current = { sceneUuid: scene.uuid, cameraUuid: camera.uuid, scenePass };
|
|
16411
16012
|
}
|
|
16412
16013
|
currentPasses.scenePass = scenePass;
|
|
16413
|
-
if (!
|
|
16414
|
-
set({
|
|
16415
|
-
const shouldRunCallbacks =
|
|
16014
|
+
if (!pipeline.outputNode || justCreatedPipeline) pipeline.outputNode = scenePass;
|
|
16015
|
+
set({ renderPipeline: pipeline, passes: currentPasses });
|
|
16016
|
+
const shouldRunCallbacks = justCreatedPipeline || !callbacksRanRef.current || !cacheValid;
|
|
16416
16017
|
if (shouldRunCallbacks) {
|
|
16417
16018
|
if (setupCBRef.current) {
|
|
16418
16019
|
const freshState = store.getState();
|
|
@@ -16433,22 +16034,22 @@ function usePostProcessing(mainCB, setupCB) {
|
|
|
16433
16034
|
callbacksRanRef.current = true;
|
|
16434
16035
|
}
|
|
16435
16036
|
} catch (error) {
|
|
16436
|
-
console.
|
|
16037
|
+
console.warn("[useRenderPipeline] Setup failed; the render pipeline was not configured:", error);
|
|
16437
16038
|
}
|
|
16438
16039
|
}, [store, renderer, scene, camera, isLegacy, rebuildVersion]);
|
|
16439
16040
|
const passes = useThree((s) => s.passes);
|
|
16440
|
-
const
|
|
16041
|
+
const renderPipeline = useThree((s) => s.renderPipeline);
|
|
16441
16042
|
return {
|
|
16442
16043
|
passes,
|
|
16443
|
-
|
|
16044
|
+
renderPipeline,
|
|
16444
16045
|
clearPasses,
|
|
16445
16046
|
reset,
|
|
16446
16047
|
rebuild,
|
|
16447
|
-
// isReady indicates if
|
|
16448
|
-
isReady:
|
|
16048
|
+
// isReady indicates if RenderPipeline is configured and ready for rendering
|
|
16049
|
+
isReady: renderPipeline !== null
|
|
16449
16050
|
};
|
|
16450
16051
|
}
|
|
16451
16052
|
|
|
16452
16053
|
extend(THREE);
|
|
16453
16054
|
|
|
16454
|
-
export { Block, Canvas, Environment, EnvironmentCube, EnvironmentMap, EnvironmentPortal, ErrorBoundary, FROM_REF, IsObject, ONCE, Portal, R3F_BUILD_LEGACY, R3F_BUILD_WEBGPU, REACT_INTERNAL_PROPS, RESERVED_PROPS,
|
|
16055
|
+
export { Block, Canvas, Environment, EnvironmentCube, EnvironmentMap, EnvironmentPortal, ErrorBoundary, FROM_REF, IsObject, ONCE, Portal, R3F_BUILD_LEGACY, R3F_BUILD_WEBGPU, REACT_INTERNAL_PROPS, RESERVED_PROPS, Texture, _roots, addAfterEffect, addEffect, addTail, advance, applyProps, attach, buildGraph, calculateDpr, clearNodeScope, clearRootNodes, clearRootUniforms, clearScope, context, createEvents, createPointerEvents, createPortal, createRoot, createScopedStore, createStore, createTextureOperations, detach, diffProps, dispose, createPointerEvents as events, extend, findInitialRoot, flushSync, fromRef, getInstanceProps, getPrimary, getPrimaryIds, getRootState, getUuidPrefix, hasConstructor, hasPrimary, invalidate, invalidateInstance, is, isColorRepresentation, isCopyable, isFromRef, isObject3D, isOnce, isOrthographicCamera, isRef, isRenderer, isTexture, isVectorLike, once, prepare, presetsObj, rebuildAllBuffers, rebuildAllNodes, rebuildAllStorage, rebuildAllUniforms, reconciler, registerPrimary, removeInteractivity, removeNodes, removeUniforms, resolve, swapInteractivity, unmountComponentAtNode, unregisterPrimary, updateCamera, updateFrustum, useBridge, useBuffers, useEnvironment, useFrame, useGPUStorage, useGraph, useInstanceHandle, useIsomorphicLayoutEffect, useLoader, useLocalNodes, useMutableCallback, useNodes, useRenderPipeline, useRenderTarget, useStore, useTexture, useTextures, useThree, useUniform, useUniforms, waitForPrimary };
|