@react-three/fiber 10.0.0-canary.604355a → 10.0.0-canary.6d6e5ab
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 +844 -1423
- package/dist/index.d.cts +291 -811
- package/dist/index.d.mts +291 -811
- package/dist/index.d.ts +291 -811
- package/dist/index.mjs +820 -1397
- package/dist/legacy.cjs +799 -1384
- package/dist/legacy.d.cts +292 -811
- package/dist/legacy.d.mts +292 -811
- package/dist/legacy.d.ts +292 -811
- package/dist/legacy.mjs +774 -1357
- package/dist/webgpu/index.cjs +1300 -1738
- package/dist/webgpu/index.d.cts +407 -825
- package/dist/webgpu/index.d.mts +407 -825
- package/dist/webgpu/index.d.ts +407 -825
- package/dist/webgpu/index.mjs +1270 -1710
- 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,60 @@ 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
|
-
capturedMap.delete(pointerId);
|
|
1030
|
-
captureData.target.releasePointerCapture(pointerId);
|
|
1031
|
-
}
|
|
1095
|
+
pointerState.captured.delete(obj);
|
|
1096
|
+
captureData.target.releasePointerCapture(pointerId);
|
|
1032
1097
|
}
|
|
1033
1098
|
}
|
|
1034
1099
|
function removeInteractivity(store, object) {
|
|
1035
1100
|
const { internal } = store.getState();
|
|
1036
1101
|
internal.interaction = internal.interaction.filter((o) => o !== object);
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1102
|
+
for (const [pointerId, pointerState] of internal.pointerMap) {
|
|
1103
|
+
pointerState.initialHits = pointerState.initialHits.filter((o) => o !== object);
|
|
1104
|
+
pointerState.hovered.forEach((value, key) => {
|
|
1105
|
+
if (value.eventObject === object || value.object === object) {
|
|
1106
|
+
pointerState.hovered.delete(key);
|
|
1107
|
+
}
|
|
1108
|
+
});
|
|
1109
|
+
if (pointerState.captured.has(object)) {
|
|
1110
|
+
releaseInternalPointerCapture(internal, object, pointerId);
|
|
1041
1111
|
}
|
|
1042
|
-
}
|
|
1043
|
-
internal.capturedMap.forEach((captures, pointerId) => {
|
|
1044
|
-
releaseInternalPointerCapture(internal.capturedMap, object, captures, pointerId);
|
|
1045
|
-
});
|
|
1112
|
+
}
|
|
1046
1113
|
unregisterVisibility(store, object);
|
|
1047
1114
|
}
|
|
1048
1115
|
function createEvents(store) {
|
|
1049
|
-
function calculateDistance(event) {
|
|
1116
|
+
function calculateDistance(event, pointerId) {
|
|
1050
1117
|
const { internal } = store.getState();
|
|
1051
|
-
const
|
|
1052
|
-
|
|
1118
|
+
const pointerState = internal.pointerMap.get(pointerId);
|
|
1119
|
+
if (!pointerState) return 0;
|
|
1120
|
+
const [initialX, initialY] = pointerState.initialClick;
|
|
1121
|
+
const dx = event.offsetX - initialX;
|
|
1122
|
+
const dy = event.offsetY - initialY;
|
|
1053
1123
|
return Math.round(Math.sqrt(dx * dx + dy * dy));
|
|
1054
1124
|
}
|
|
1055
1125
|
function filterPointerEvents(objects) {
|
|
@@ -1085,6 +1155,15 @@ function createEvents(store) {
|
|
|
1085
1155
|
return state2.raycaster.camera ? state2.raycaster.intersectObject(obj, true) : [];
|
|
1086
1156
|
}
|
|
1087
1157
|
let hits = eventsObjects.flatMap(handleRaycast).sort((a, b) => {
|
|
1158
|
+
const aInteractivePriority = a.object.userData?.interactivePriority;
|
|
1159
|
+
const bInteractivePriority = b.object.userData?.interactivePriority;
|
|
1160
|
+
if (aInteractivePriority !== void 0 || bInteractivePriority !== void 0) {
|
|
1161
|
+
if (aInteractivePriority !== void 0 && bInteractivePriority === void 0) return -1;
|
|
1162
|
+
if (bInteractivePriority !== void 0 && aInteractivePriority === void 0) return 1;
|
|
1163
|
+
if (aInteractivePriority !== bInteractivePriority) {
|
|
1164
|
+
return (bInteractivePriority ?? 0) - (aInteractivePriority ?? 0);
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1088
1167
|
const aState = getRootState(a.object);
|
|
1089
1168
|
const bState = getRootState(b.object);
|
|
1090
1169
|
const aPriority = aState?.events?.priority ?? 1;
|
|
@@ -1106,9 +1185,13 @@ function createEvents(store) {
|
|
|
1106
1185
|
eventObject = eventObject.parent;
|
|
1107
1186
|
}
|
|
1108
1187
|
}
|
|
1109
|
-
if ("pointerId" in event
|
|
1110
|
-
|
|
1111
|
-
|
|
1188
|
+
if ("pointerId" in event) {
|
|
1189
|
+
const pointerId = event.pointerId;
|
|
1190
|
+
const pointerState = state.internal.pointerMap.get(pointerId);
|
|
1191
|
+
if (pointerState?.captured.size) {
|
|
1192
|
+
for (const captureData of pointerState.captured.values()) {
|
|
1193
|
+
if (!duplicates.has(makeId(captureData.intersection))) intersections.push(captureData.intersection);
|
|
1194
|
+
}
|
|
1112
1195
|
}
|
|
1113
1196
|
}
|
|
1114
1197
|
return intersections;
|
|
@@ -1121,27 +1204,25 @@ function createEvents(store) {
|
|
|
1121
1204
|
if (state) {
|
|
1122
1205
|
const { raycaster, pointer, camera, internal } = state;
|
|
1123
1206
|
const unprojectedPoint = new Vector3(pointer.x, pointer.y, 0).unproject(camera);
|
|
1124
|
-
const hasPointerCapture = (id) =>
|
|
1207
|
+
const hasPointerCapture = (id) => {
|
|
1208
|
+
const pointerState = internal.pointerMap.get(id);
|
|
1209
|
+
return pointerState?.captured.has(hit.eventObject) ?? false;
|
|
1210
|
+
};
|
|
1125
1211
|
const setPointerCapture = (id) => {
|
|
1126
1212
|
const captureData = { intersection: hit, target: event.target };
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
} else {
|
|
1130
|
-
internal.capturedMap.set(id, /* @__PURE__ */ new Map([[hit.eventObject, captureData]]));
|
|
1131
|
-
}
|
|
1213
|
+
const pointerState = getPointerState(internal, id);
|
|
1214
|
+
pointerState.captured.set(hit.eventObject, captureData);
|
|
1132
1215
|
event.target.setPointerCapture(id);
|
|
1133
1216
|
};
|
|
1134
1217
|
const releasePointerCapture = (id) => {
|
|
1135
|
-
|
|
1136
|
-
if (captures) {
|
|
1137
|
-
releaseInternalPointerCapture(internal.capturedMap, hit.eventObject, captures, id);
|
|
1138
|
-
}
|
|
1218
|
+
releaseInternalPointerCapture(internal, hit.eventObject, id);
|
|
1139
1219
|
};
|
|
1140
1220
|
const extractEventProps = {};
|
|
1141
1221
|
for (const prop in event) {
|
|
1142
1222
|
const property = event[prop];
|
|
1143
1223
|
if (typeof property !== "function") extractEventProps[prop] = property;
|
|
1144
1224
|
}
|
|
1225
|
+
const eventPointerId = "pointerId" in event ? event.pointerId : void 0;
|
|
1145
1226
|
const raycastEvent = {
|
|
1146
1227
|
...hit,
|
|
1147
1228
|
...extractEventProps,
|
|
@@ -1152,18 +1233,19 @@ function createEvents(store) {
|
|
|
1152
1233
|
unprojectedPoint,
|
|
1153
1234
|
ray: raycaster.ray,
|
|
1154
1235
|
camera,
|
|
1236
|
+
pointerId: eventPointerId,
|
|
1155
1237
|
// Hijack stopPropagation, which just sets a flag
|
|
1156
1238
|
stopPropagation() {
|
|
1157
|
-
const
|
|
1239
|
+
const pointerState = eventPointerId !== void 0 ? internal.pointerMap.get(eventPointerId) : void 0;
|
|
1158
1240
|
if (
|
|
1159
1241
|
// ...if this pointer hasn't been captured
|
|
1160
|
-
!
|
|
1161
|
-
|
|
1242
|
+
!pointerState?.captured.size || // ... or if the hit object is capturing the pointer
|
|
1243
|
+
pointerState.captured.has(hit.eventObject)
|
|
1162
1244
|
) {
|
|
1163
1245
|
raycastEvent.stopped = localState.stopped = true;
|
|
1164
|
-
if (
|
|
1246
|
+
if (pointerState?.hovered.size && Array.from(pointerState.hovered.values()).find((i) => i.eventObject === hit.eventObject)) {
|
|
1165
1247
|
const higher = intersections.slice(0, intersections.indexOf(hit));
|
|
1166
|
-
cancelPointer([...higher, hit]);
|
|
1248
|
+
cancelPointer([...higher, hit], eventPointerId);
|
|
1167
1249
|
}
|
|
1168
1250
|
}
|
|
1169
1251
|
},
|
|
@@ -1179,15 +1261,18 @@ function createEvents(store) {
|
|
|
1179
1261
|
}
|
|
1180
1262
|
return intersections;
|
|
1181
1263
|
}
|
|
1182
|
-
function cancelPointer(intersections) {
|
|
1264
|
+
function cancelPointer(intersections, pointerId) {
|
|
1183
1265
|
const { internal } = store.getState();
|
|
1184
|
-
|
|
1266
|
+
const pid = pointerId ?? DEFAULT_POINTER_ID;
|
|
1267
|
+
const pointerState = internal.pointerMap.get(pid);
|
|
1268
|
+
if (!pointerState) return;
|
|
1269
|
+
for (const [hoveredId, hoveredObj] of pointerState.hovered) {
|
|
1185
1270
|
if (!intersections.length || !intersections.find(
|
|
1186
1271
|
(hit) => hit.object === hoveredObj.object && hit.index === hoveredObj.index && hit.instanceId === hoveredObj.instanceId
|
|
1187
1272
|
)) {
|
|
1188
1273
|
const eventObject = hoveredObj.eventObject;
|
|
1189
1274
|
const instance = eventObject.__r3f;
|
|
1190
|
-
|
|
1275
|
+
pointerState.hovered.delete(hoveredId);
|
|
1191
1276
|
if (instance?.eventCount) {
|
|
1192
1277
|
const handlers = instance.handlers;
|
|
1193
1278
|
const data = { ...hoveredObj, intersections };
|
|
@@ -1216,41 +1301,118 @@ function createEvents(store) {
|
|
|
1216
1301
|
instance?.handlers.onDropMissed?.(event);
|
|
1217
1302
|
}
|
|
1218
1303
|
}
|
|
1304
|
+
function cleanupPointer(pointerId) {
|
|
1305
|
+
const { internal } = store.getState();
|
|
1306
|
+
const pointerState = internal.pointerMap.get(pointerId);
|
|
1307
|
+
if (pointerState) {
|
|
1308
|
+
for (const [, hoveredObj] of pointerState.hovered) {
|
|
1309
|
+
const eventObject = hoveredObj.eventObject;
|
|
1310
|
+
const instance = eventObject.__r3f;
|
|
1311
|
+
if (instance?.eventCount) {
|
|
1312
|
+
const handlers = instance.handlers;
|
|
1313
|
+
const data = { ...hoveredObj, intersections: [] };
|
|
1314
|
+
handlers.onPointerOut?.(data);
|
|
1315
|
+
handlers.onPointerLeave?.(data);
|
|
1316
|
+
}
|
|
1317
|
+
}
|
|
1318
|
+
internal.pointerMap.delete(pointerId);
|
|
1319
|
+
}
|
|
1320
|
+
internal.pointerDirty.delete(pointerId);
|
|
1321
|
+
}
|
|
1322
|
+
function processDeferredPointer(event, pointerId) {
|
|
1323
|
+
const state = store.getState();
|
|
1324
|
+
const { internal } = state;
|
|
1325
|
+
if (!state.events.enabled) return;
|
|
1326
|
+
const filter = filterPointerEvents;
|
|
1327
|
+
const hits = intersect(event, filter);
|
|
1328
|
+
cancelPointer(hits, pointerId);
|
|
1329
|
+
function onIntersect(data) {
|
|
1330
|
+
const eventObject = data.eventObject;
|
|
1331
|
+
const instance = eventObject.__r3f;
|
|
1332
|
+
if (!instance?.eventCount) return;
|
|
1333
|
+
const handlers = instance.handlers;
|
|
1334
|
+
if (handlers.onPointerOver || handlers.onPointerEnter || handlers.onPointerOut || handlers.onPointerLeave) {
|
|
1335
|
+
const id = makeId(data);
|
|
1336
|
+
const pointerState = getPointerState(internal, pointerId);
|
|
1337
|
+
const hoveredItem = pointerState.hovered.get(id);
|
|
1338
|
+
if (!hoveredItem) {
|
|
1339
|
+
pointerState.hovered.set(id, data);
|
|
1340
|
+
handlers.onPointerOver?.(data);
|
|
1341
|
+
handlers.onPointerEnter?.(data);
|
|
1342
|
+
} else if (hoveredItem.stopped) {
|
|
1343
|
+
data.stopPropagation();
|
|
1344
|
+
}
|
|
1345
|
+
}
|
|
1346
|
+
handlers.onPointerMove?.(data);
|
|
1347
|
+
}
|
|
1348
|
+
handleIntersects(hits, event, 0, onIntersect);
|
|
1349
|
+
}
|
|
1219
1350
|
function handlePointer(name) {
|
|
1220
1351
|
switch (name) {
|
|
1221
1352
|
case "onPointerLeave":
|
|
1222
|
-
case "onPointerCancel":
|
|
1223
1353
|
case "onDragLeave":
|
|
1224
1354
|
return () => cancelPointer([]);
|
|
1355
|
+
// Global cancel of these events
|
|
1356
|
+
case "onPointerCancel":
|
|
1357
|
+
return (event) => {
|
|
1358
|
+
const pointerId = getPointerId(event);
|
|
1359
|
+
cleanupPointer(pointerId);
|
|
1360
|
+
};
|
|
1225
1361
|
case "onLostPointerCapture":
|
|
1226
1362
|
return (event) => {
|
|
1227
1363
|
const { internal } = store.getState();
|
|
1228
|
-
|
|
1364
|
+
const pointerId = getPointerId(event);
|
|
1365
|
+
const pointerState = internal.pointerMap.get(pointerId);
|
|
1366
|
+
if (pointerState?.captured.size) {
|
|
1229
1367
|
requestAnimationFrame(() => {
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1368
|
+
const pointerState2 = internal.pointerMap.get(pointerId);
|
|
1369
|
+
if (pointerState2?.captured.size) {
|
|
1370
|
+
pointerState2.captured.clear();
|
|
1233
1371
|
}
|
|
1372
|
+
cancelPointer([], pointerId);
|
|
1234
1373
|
});
|
|
1235
1374
|
}
|
|
1236
1375
|
};
|
|
1237
1376
|
}
|
|
1238
1377
|
return function handleEvent(event) {
|
|
1239
1378
|
const state = store.getState();
|
|
1240
|
-
const { onPointerMissed, onDragOverMissed, onDropMissed, internal } = state;
|
|
1379
|
+
const { onPointerMissed, onDragOverMissed, onDropMissed, internal, events } = state;
|
|
1380
|
+
const pointerId = getPointerId(event);
|
|
1241
1381
|
internal.lastEvent.current = event;
|
|
1242
|
-
if (!
|
|
1382
|
+
if (!events.enabled) return;
|
|
1243
1383
|
const isPointerMove = name === "onPointerMove";
|
|
1244
1384
|
const isDragOver = name === "onDragOver";
|
|
1245
1385
|
const isDrop = name === "onDrop";
|
|
1246
1386
|
const isClickEvent = name === "onClick" || name === "onContextMenu" || name === "onDoubleClick";
|
|
1387
|
+
const isPointerDown = name === "onPointerDown";
|
|
1388
|
+
const isPointerUp = name === "onPointerUp";
|
|
1389
|
+
const isWheel = name === "onWheel";
|
|
1390
|
+
const canDeferRaycasts = events.frameTimedRaycasts && state.frameloop === "always";
|
|
1391
|
+
if (isPointerMove && canDeferRaycasts) {
|
|
1392
|
+
events.compute?.(event, state);
|
|
1393
|
+
internal.pointerDirty.set(pointerId, event);
|
|
1394
|
+
return;
|
|
1395
|
+
}
|
|
1396
|
+
if (isWheel && canDeferRaycasts && !events.alwaysFireOnScroll) {
|
|
1397
|
+
events.compute?.(event, state);
|
|
1398
|
+
internal.pointerDirty.set(pointerId, event);
|
|
1399
|
+
return;
|
|
1400
|
+
}
|
|
1401
|
+
if ((isClickEvent || isPointerDown || isPointerUp) && internal.pointerDirty.has(pointerId)) {
|
|
1402
|
+
const deferredEvent = internal.pointerDirty.get(pointerId);
|
|
1403
|
+
internal.pointerDirty.delete(pointerId);
|
|
1404
|
+
processDeferredPointer(deferredEvent, pointerId);
|
|
1405
|
+
}
|
|
1247
1406
|
const filter = isPointerMove || isDragOver || isDrop ? filterPointerEvents : void 0;
|
|
1248
1407
|
const hits = intersect(event, filter);
|
|
1249
|
-
const delta = isClickEvent ? calculateDistance(event) : 0;
|
|
1250
|
-
if (
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1408
|
+
const delta = isClickEvent ? calculateDistance(event, pointerId) : 0;
|
|
1409
|
+
if (isPointerDown) {
|
|
1410
|
+
const pointerState2 = getPointerState(internal, pointerId);
|
|
1411
|
+
pointerState2.initialClick = [event.offsetX, event.offsetY];
|
|
1412
|
+
pointerState2.initialHits = hits.map((hit) => hit.eventObject);
|
|
1413
|
+
}
|
|
1414
|
+
const pointerState = internal.pointerMap.get(pointerId);
|
|
1415
|
+
const initialHits = pointerState?.initialHits ?? [];
|
|
1254
1416
|
if (isClickEvent && !hits.length) {
|
|
1255
1417
|
if (delta <= 2) {
|
|
1256
1418
|
pointerMissed(event, internal.interaction);
|
|
@@ -1265,7 +1427,9 @@ function createEvents(store) {
|
|
|
1265
1427
|
dropMissed(event, internal.interaction);
|
|
1266
1428
|
if (onDropMissed) onDropMissed(event);
|
|
1267
1429
|
}
|
|
1268
|
-
if (isPointerMove || isDragOver)
|
|
1430
|
+
if (isPointerMove || isDragOver) {
|
|
1431
|
+
cancelPointer(hits, pointerId);
|
|
1432
|
+
}
|
|
1269
1433
|
function onIntersect(data) {
|
|
1270
1434
|
const eventObject = data.eventObject;
|
|
1271
1435
|
const instance = eventObject.__r3f;
|
|
@@ -1274,9 +1438,10 @@ function createEvents(store) {
|
|
|
1274
1438
|
if (isPointerMove) {
|
|
1275
1439
|
if (handlers.onPointerOver || handlers.onPointerEnter || handlers.onPointerOut || handlers.onPointerLeave) {
|
|
1276
1440
|
const id = makeId(data);
|
|
1277
|
-
const
|
|
1441
|
+
const pointerState2 = getPointerState(internal, pointerId);
|
|
1442
|
+
const hoveredItem = pointerState2.hovered.get(id);
|
|
1278
1443
|
if (!hoveredItem) {
|
|
1279
|
-
|
|
1444
|
+
pointerState2.hovered.set(id, data);
|
|
1280
1445
|
handlers.onPointerOver?.(data);
|
|
1281
1446
|
handlers.onPointerEnter?.(data);
|
|
1282
1447
|
} else if (hoveredItem.stopped) {
|
|
@@ -1286,9 +1451,10 @@ function createEvents(store) {
|
|
|
1286
1451
|
handlers.onPointerMove?.(data);
|
|
1287
1452
|
} else if (isDragOver) {
|
|
1288
1453
|
const id = makeId(data);
|
|
1289
|
-
const
|
|
1454
|
+
const pointerState2 = getPointerState(internal, pointerId);
|
|
1455
|
+
const hoveredItem = pointerState2.hovered.get(id);
|
|
1290
1456
|
if (!hoveredItem) {
|
|
1291
|
-
|
|
1457
|
+
pointerState2.hovered.set(id, data);
|
|
1292
1458
|
handlers.onDragOverEnter?.(data);
|
|
1293
1459
|
} else if (hoveredItem.stopped) {
|
|
1294
1460
|
data.stopPropagation();
|
|
@@ -1299,18 +1465,18 @@ function createEvents(store) {
|
|
|
1299
1465
|
} else {
|
|
1300
1466
|
const handler = handlers[name];
|
|
1301
1467
|
if (handler) {
|
|
1302
|
-
if (!isClickEvent ||
|
|
1468
|
+
if (!isClickEvent || initialHits.includes(eventObject)) {
|
|
1303
1469
|
pointerMissed(
|
|
1304
1470
|
event,
|
|
1305
|
-
internal.interaction.filter((object) => !
|
|
1471
|
+
internal.interaction.filter((object) => !initialHits.includes(object))
|
|
1306
1472
|
);
|
|
1307
1473
|
handler(data);
|
|
1308
1474
|
}
|
|
1309
1475
|
} else {
|
|
1310
|
-
if (isClickEvent &&
|
|
1476
|
+
if (isClickEvent && initialHits.includes(eventObject)) {
|
|
1311
1477
|
pointerMissed(
|
|
1312
1478
|
event,
|
|
1313
|
-
internal.interaction.filter((object) => !
|
|
1479
|
+
internal.interaction.filter((object) => !initialHits.includes(object))
|
|
1314
1480
|
);
|
|
1315
1481
|
}
|
|
1316
1482
|
}
|
|
@@ -1319,7 +1485,15 @@ function createEvents(store) {
|
|
|
1319
1485
|
handleIntersects(hits, event, delta, onIntersect);
|
|
1320
1486
|
};
|
|
1321
1487
|
}
|
|
1322
|
-
|
|
1488
|
+
function flushDeferredPointers() {
|
|
1489
|
+
const { internal, events } = store.getState();
|
|
1490
|
+
if (!events.frameTimedRaycasts) return;
|
|
1491
|
+
for (const [pointerId, event] of internal.pointerDirty) {
|
|
1492
|
+
processDeferredPointer(event, pointerId);
|
|
1493
|
+
}
|
|
1494
|
+
internal.pointerDirty.clear();
|
|
1495
|
+
}
|
|
1496
|
+
return { handlePointer, flushDeferredPointers, processDeferredPointer };
|
|
1323
1497
|
}
|
|
1324
1498
|
const DOM_EVENTS = {
|
|
1325
1499
|
onClick: ["click", false],
|
|
@@ -1338,10 +1512,15 @@ const DOM_EVENTS = {
|
|
|
1338
1512
|
onLostPointerCapture: ["lostpointercapture", true]
|
|
1339
1513
|
};
|
|
1340
1514
|
function createPointerEvents(store) {
|
|
1341
|
-
const { handlePointer } = createEvents(store);
|
|
1515
|
+
const { handlePointer, flushDeferredPointers, processDeferredPointer } = createEvents(store);
|
|
1516
|
+
let nextXRPointerId = XR_POINTER_ID_START;
|
|
1517
|
+
const xrPointers = /* @__PURE__ */ new Map();
|
|
1342
1518
|
return {
|
|
1343
1519
|
priority: 1,
|
|
1344
1520
|
enabled: true,
|
|
1521
|
+
frameTimedRaycasts: true,
|
|
1522
|
+
alwaysFireOnScroll: true,
|
|
1523
|
+
updateOnFrame: false,
|
|
1345
1524
|
compute(event, state) {
|
|
1346
1525
|
state.pointer.set(event.offsetX / state.size.width * 2 - 1, -(event.offsetY / state.size.height) * 2 + 1);
|
|
1347
1526
|
state.raycaster.setFromCamera(state.pointer, state.camera);
|
|
@@ -1351,11 +1530,33 @@ function createPointerEvents(store) {
|
|
|
1351
1530
|
(acc, key) => ({ ...acc, [key]: handlePointer(key) }),
|
|
1352
1531
|
{}
|
|
1353
1532
|
),
|
|
1354
|
-
update: () => {
|
|
1533
|
+
update: (pointerId) => {
|
|
1534
|
+
const { events, internal } = store.getState();
|
|
1535
|
+
if (!events.handlers) return;
|
|
1536
|
+
if (pointerId !== void 0) {
|
|
1537
|
+
const event = internal.pointerDirty.get(pointerId);
|
|
1538
|
+
if (event) {
|
|
1539
|
+
internal.pointerDirty.delete(pointerId);
|
|
1540
|
+
processDeferredPointer(event, pointerId);
|
|
1541
|
+
} else if (internal.lastEvent?.current) {
|
|
1542
|
+
processDeferredPointer(internal.lastEvent.current, pointerId);
|
|
1543
|
+
}
|
|
1544
|
+
} else {
|
|
1545
|
+
flushDeferredPointers();
|
|
1546
|
+
if (internal.lastEvent?.current) {
|
|
1547
|
+
events.handlers.onPointerMove(internal.lastEvent.current);
|
|
1548
|
+
}
|
|
1549
|
+
}
|
|
1550
|
+
},
|
|
1551
|
+
flush: () => {
|
|
1355
1552
|
const { events, internal } = store.getState();
|
|
1356
|
-
|
|
1553
|
+
flushDeferredPointers();
|
|
1554
|
+
if (events.updateOnFrame && internal.lastEvent?.current && events.handlers) {
|
|
1555
|
+
events.handlers.onPointerMove(internal.lastEvent.current);
|
|
1556
|
+
}
|
|
1357
1557
|
},
|
|
1358
1558
|
connect: (target) => {
|
|
1559
|
+
if (!target) return;
|
|
1359
1560
|
const { set, events } = store.getState();
|
|
1360
1561
|
events.disconnect?.();
|
|
1361
1562
|
set((state) => ({ events: { ...state.events, connected: target } }));
|
|
@@ -1379,6 +1580,33 @@ function createPointerEvents(store) {
|
|
|
1379
1580
|
}
|
|
1380
1581
|
set((state) => ({ events: { ...state.events, connected: void 0 } }));
|
|
1381
1582
|
}
|
|
1583
|
+
},
|
|
1584
|
+
// PREVIEW (A8): the XR pointer registration API may change before stable.
|
|
1585
|
+
registerPointer: (config) => {
|
|
1586
|
+
const pointerId = nextXRPointerId++;
|
|
1587
|
+
xrPointers.set(pointerId, config);
|
|
1588
|
+
const { internal } = store.getState();
|
|
1589
|
+
getPointerState(internal, pointerId);
|
|
1590
|
+
return pointerId;
|
|
1591
|
+
},
|
|
1592
|
+
unregisterPointer: (pointerId) => {
|
|
1593
|
+
xrPointers.delete(pointerId);
|
|
1594
|
+
const { internal } = store.getState();
|
|
1595
|
+
const pointerState = internal.pointerMap.get(pointerId);
|
|
1596
|
+
if (pointerState) {
|
|
1597
|
+
for (const [, hoveredObj] of pointerState.hovered) {
|
|
1598
|
+
const eventObject = hoveredObj.eventObject;
|
|
1599
|
+
const instance = eventObject.__r3f;
|
|
1600
|
+
if (instance?.eventCount) {
|
|
1601
|
+
const handlers = instance.handlers;
|
|
1602
|
+
const data = { ...hoveredObj, intersections: [] };
|
|
1603
|
+
handlers.onPointerOut?.(data);
|
|
1604
|
+
handlers.onPointerLeave?.(data);
|
|
1605
|
+
}
|
|
1606
|
+
}
|
|
1607
|
+
internal.pointerMap.delete(pointerId);
|
|
1608
|
+
}
|
|
1609
|
+
internal.pointerDirty.delete(pointerId);
|
|
1382
1610
|
}
|
|
1383
1611
|
};
|
|
1384
1612
|
}
|
|
@@ -1440,1058 +1668,33 @@ function notifyAlpha({ message, link }) {
|
|
|
1440
1668
|
}
|
|
1441
1669
|
}
|
|
1442
1670
|
|
|
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;
|
|
1671
|
+
const INTERNAL_LIB_MARKERS = ["zustand", "setState", "Object.assign"];
|
|
1672
|
+
function isInternalRendererAccess(stack, callerDepth = 2) {
|
|
1673
|
+
if (!stack) return false;
|
|
1674
|
+
const caller = stack.split("\n")[callerDepth] ?? "";
|
|
1675
|
+
return INTERNAL_LIB_MARKERS.some((m) => caller.includes(m));
|
|
1709
1676
|
}
|
|
1710
1677
|
|
|
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 };
|
|
1678
|
+
const R3F_CONTEXT = Symbol.for("@react-three/fiber.context");
|
|
1679
|
+
const context = globalThis[R3F_CONTEXT] ?? (globalThis[R3F_CONTEXT] = React.createContext(null));
|
|
1680
|
+
const createStore = (invalidate, advance) => {
|
|
1681
|
+
const rootStore = createWithEqualityFn((set, get) => {
|
|
1682
|
+
const position = new Vector3();
|
|
1683
|
+
const defaultTarget = new Vector3();
|
|
1684
|
+
const tempTarget = new Vector3();
|
|
1685
|
+
function getCurrentViewport(camera = get().camera, target = defaultTarget, size = get().size) {
|
|
1686
|
+
const { width, height, top, left } = size;
|
|
1687
|
+
const aspect = width / height;
|
|
1688
|
+
if (target.isVector3) tempTarget.copy(target);
|
|
1689
|
+
else tempTarget.set(...target);
|
|
1690
|
+
const distance = camera.getWorldPosition(position).distanceTo(tempTarget);
|
|
1691
|
+
if (isOrthographicCamera(camera)) {
|
|
1692
|
+
return { width: width / camera.zoom, height: height / camera.zoom, top, left, factor: 1, distance, aspect };
|
|
1693
|
+
} else {
|
|
1694
|
+
const fov = camera.fov * Math.PI / 180;
|
|
1695
|
+
const h = 2 * Math.tan(fov / 2) * distance;
|
|
1696
|
+
const w = h * (width / height);
|
|
1697
|
+
return { width: w, height: h, top, left, factor: width / w, distance, aspect };
|
|
2495
1698
|
}
|
|
2496
1699
|
}
|
|
2497
1700
|
let performanceTimeout = void 0;
|
|
@@ -2509,7 +1712,14 @@ const createStore = (invalidate, advance) => {
|
|
|
2509
1712
|
frustum: new Frustum(),
|
|
2510
1713
|
autoUpdateFrustum: true,
|
|
2511
1714
|
raycaster: null,
|
|
2512
|
-
events: {
|
|
1715
|
+
events: {
|
|
1716
|
+
priority: 1,
|
|
1717
|
+
enabled: true,
|
|
1718
|
+
connected: false,
|
|
1719
|
+
frameTimedRaycasts: true,
|
|
1720
|
+
alwaysFireOnScroll: true,
|
|
1721
|
+
updateOnFrame: false
|
|
1722
|
+
},
|
|
2513
1723
|
scene: null,
|
|
2514
1724
|
rootScene: null,
|
|
2515
1725
|
xr: null,
|
|
@@ -2597,14 +1807,18 @@ const createStore = (invalidate, advance) => {
|
|
|
2597
1807
|
}),
|
|
2598
1808
|
setFrameloop: (frameloop = "always") => {
|
|
2599
1809
|
set(() => ({ frameloop }));
|
|
1810
|
+
getScheduler().frameloop = frameloop;
|
|
2600
1811
|
},
|
|
2601
1812
|
setError: (error) => set(() => ({ error })),
|
|
2602
1813
|
error: null,
|
|
2603
|
-
//* TSL State (managed via hooks: useUniforms, useNodes, useTextures,
|
|
1814
|
+
//* TSL State (managed via hooks: useUniforms, useNodes, useBuffers, useGPUStorage, useTextures, useRenderPipeline) ==============================
|
|
2604
1815
|
uniforms: {},
|
|
2605
1816
|
nodes: {},
|
|
1817
|
+
buffers: {},
|
|
1818
|
+
gpuStorage: {},
|
|
2606
1819
|
textures: /* @__PURE__ */ new Map(),
|
|
2607
|
-
|
|
1820
|
+
_textureRefs: /* @__PURE__ */ new Map(),
|
|
1821
|
+
renderPipeline: null,
|
|
2608
1822
|
passes: {},
|
|
2609
1823
|
_hmrVersion: 0,
|
|
2610
1824
|
_sizeImperative: false,
|
|
@@ -2613,12 +1827,16 @@ const createStore = (invalidate, advance) => {
|
|
|
2613
1827
|
internal: {
|
|
2614
1828
|
// Events
|
|
2615
1829
|
interaction: [],
|
|
2616
|
-
hovered: /* @__PURE__ */ new Map(),
|
|
2617
1830
|
subscribers: [],
|
|
1831
|
+
// Per-pointer state (new unified structure)
|
|
1832
|
+
pointerMap: /* @__PURE__ */ new Map(),
|
|
1833
|
+
pointerDirty: /* @__PURE__ */ new Map(),
|
|
1834
|
+
lastEvent: React.createRef(),
|
|
1835
|
+
// Deprecated but kept for backwards compatibility
|
|
1836
|
+
hovered: /* @__PURE__ */ new Map(),
|
|
2618
1837
|
initialClick: [0, 0],
|
|
2619
1838
|
initialHits: [],
|
|
2620
1839
|
capturedMap: /* @__PURE__ */ new Map(),
|
|
2621
|
-
lastEvent: React.createRef(),
|
|
2622
1840
|
// Visibility tracking (onFramed, onOccluded, onVisible)
|
|
2623
1841
|
visibilityRegistry: /* @__PURE__ */ new Map(),
|
|
2624
1842
|
// Occlusion system (WebGPU only)
|
|
@@ -2657,8 +1875,7 @@ const createStore = (invalidate, advance) => {
|
|
|
2657
1875
|
const currentState = rootStore.getState();
|
|
2658
1876
|
if (!currentState.isLegacy && currentState.internal.actualRenderer) {
|
|
2659
1877
|
const stack = new Error().stack || "";
|
|
2660
|
-
|
|
2661
|
-
if (!isInternalAccess) {
|
|
1878
|
+
if (!isInternalRendererAccess(stack)) {
|
|
2662
1879
|
const cleanedStack = stack.split("\n").slice(2).join("\n") || "Stack trace unavailable";
|
|
2663
1880
|
notifyDepreciated({
|
|
2664
1881
|
heading: "Accessing state.gl in WebGPU mode",
|
|
@@ -2706,14 +1923,16 @@ const createStore = (invalidate, advance) => {
|
|
|
2706
1923
|
oldSize = size;
|
|
2707
1924
|
oldDpr = viewport.dpr;
|
|
2708
1925
|
updateCamera(camera, size);
|
|
2709
|
-
if (canvasTarget) {
|
|
1926
|
+
if (internal.isSecondary && canvasTarget) {
|
|
2710
1927
|
if (viewport.dpr > 0) canvasTarget.setPixelRatio(viewport.dpr);
|
|
2711
|
-
|
|
2712
|
-
canvasTarget.setSize(size.width, size.height, updateStyle);
|
|
1928
|
+
canvasTarget.setSize(size.width, size.height, false);
|
|
2713
1929
|
} else {
|
|
2714
1930
|
if (viewport.dpr > 0) actualRenderer.setPixelRatio(viewport.dpr);
|
|
2715
|
-
|
|
2716
|
-
|
|
1931
|
+
actualRenderer.setSize(size.width, size.height, false);
|
|
1932
|
+
if (canvasTarget) {
|
|
1933
|
+
if (viewport.dpr > 0) canvasTarget.setPixelRatio(viewport.dpr);
|
|
1934
|
+
canvasTarget.setSize(size.width, size.height, false);
|
|
1935
|
+
}
|
|
2717
1936
|
}
|
|
2718
1937
|
}
|
|
2719
1938
|
if (camera !== oldCamera) {
|
|
@@ -2769,19 +1988,25 @@ function loadingFn(extensions, onProgress) {
|
|
|
2769
1988
|
);
|
|
2770
1989
|
};
|
|
2771
1990
|
}
|
|
2772
|
-
function useLoader(loader, input, extensions, onProgress) {
|
|
2773
|
-
const
|
|
1991
|
+
function useLoader(loader, input, extensions, onProgress, cacheKey) {
|
|
1992
|
+
const inputs = Array.isArray(input) ? input : [input];
|
|
1993
|
+
const cacheKeys = Array.isArray(cacheKey ?? input) ? cacheKey ?? input : [cacheKey ?? input];
|
|
2774
1994
|
const fn = loadingFn(extensions, onProgress);
|
|
2775
|
-
const results =
|
|
1995
|
+
const results = cacheKeys.map(
|
|
1996
|
+
(key, index) => suspend(() => fn(loader, inputs[index]), [loader, key], { equal: is.equ })
|
|
1997
|
+
);
|
|
2776
1998
|
return Array.isArray(input) ? results : results[0];
|
|
2777
1999
|
}
|
|
2778
|
-
useLoader.preload = function(loader, input, extensions, onProgress) {
|
|
2779
|
-
const
|
|
2780
|
-
|
|
2000
|
+
useLoader.preload = function(loader, input, extensions, onProgress, cacheKey) {
|
|
2001
|
+
const inputs = Array.isArray(input) ? input : [input];
|
|
2002
|
+
const cacheKeys = Array.isArray(cacheKey ?? input) ? cacheKey ?? input : [cacheKey ?? input];
|
|
2003
|
+
cacheKeys.forEach(
|
|
2004
|
+
(key, index) => preload(() => loadingFn(extensions, onProgress)(loader, inputs[index]), [loader, key])
|
|
2005
|
+
);
|
|
2781
2006
|
};
|
|
2782
|
-
useLoader.clear = function(loader, input) {
|
|
2783
|
-
const
|
|
2784
|
-
|
|
2007
|
+
useLoader.clear = function(loader, input, cacheKey) {
|
|
2008
|
+
const cacheKeys = Array.isArray(cacheKey ?? input) ? cacheKey ?? input : [cacheKey ?? input];
|
|
2009
|
+
cacheKeys.forEach((key) => clear([loader, key]));
|
|
2785
2010
|
};
|
|
2786
2011
|
useLoader.loader = getLoader;
|
|
2787
2012
|
|
|
@@ -2857,20 +2082,14 @@ function useFrame(callback, priorityOrOptions) {
|
|
|
2857
2082
|
}
|
|
2858
2083
|
};
|
|
2859
2084
|
} else {
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
unregisterJob = registerOutside();
|
|
2869
|
-
});
|
|
2870
|
-
return () => {
|
|
2871
|
-
unsubReady();
|
|
2872
|
-
unregisterJob?.();
|
|
2873
|
-
};
|
|
2085
|
+
return scheduler.register(
|
|
2086
|
+
(state, delta) => {
|
|
2087
|
+
const frameState = state;
|
|
2088
|
+
if (!frameState.renderer) return;
|
|
2089
|
+
callbackRef.current?.(frameState, delta);
|
|
2090
|
+
},
|
|
2091
|
+
{ id, ...options }
|
|
2092
|
+
);
|
|
2874
2093
|
}
|
|
2875
2094
|
}, [store, scheduler, id, optionsKey, isLegacyPriority, isInsideCanvas]);
|
|
2876
2095
|
const isPaused = React.useSyncExternalStore(
|
|
@@ -2961,18 +2180,18 @@ function buildFromCache(input, textureCache) {
|
|
|
2961
2180
|
function useTexture(input, optionsOrOnLoad) {
|
|
2962
2181
|
const renderer = useThree((state) => state.internal.actualRenderer);
|
|
2963
2182
|
const store = useStore();
|
|
2964
|
-
const textureCache = useThree((state) => state.textures);
|
|
2965
2183
|
const options = typeof optionsOrOnLoad === "function" ? { onLoad: optionsOrOnLoad } : optionsOrOnLoad ?? {};
|
|
2966
|
-
const { onLoad, cache =
|
|
2184
|
+
const { onLoad, cache = true } = options;
|
|
2967
2185
|
const onLoadRef = useRef(onLoad);
|
|
2968
2186
|
onLoadRef.current = onLoad;
|
|
2969
2187
|
const onLoadCalledForRef = useRef(null);
|
|
2970
2188
|
const urls = useMemo(() => getUrls(input), [input]);
|
|
2971
2189
|
const cachedResult = useMemo(() => {
|
|
2972
2190
|
if (!cache) return null;
|
|
2973
|
-
|
|
2974
|
-
|
|
2975
|
-
|
|
2191
|
+
const textures = store.getState().textures;
|
|
2192
|
+
if (!allUrlsCached(urls, textures)) return null;
|
|
2193
|
+
return buildFromCache(input, textures);
|
|
2194
|
+
}, [cache, urls, input, store]);
|
|
2976
2195
|
const loadedTextures = useLoader(
|
|
2977
2196
|
TextureLoader,
|
|
2978
2197
|
IsObject(input) ? Object.values(input) : input
|
|
@@ -3016,8 +2235,6 @@ function useTexture(input, optionsOrOnLoad) {
|
|
|
3016
2235
|
}, [input, loadedTextures, cachedResult]);
|
|
3017
2236
|
useEffect(() => {
|
|
3018
2237
|
if (!cache) return;
|
|
3019
|
-
if (cachedResult) return;
|
|
3020
|
-
const set = store.setState;
|
|
3021
2238
|
const urlTextureMap = [];
|
|
3022
2239
|
if (typeof input === "string") {
|
|
3023
2240
|
urlTextureMap.push([input, mappedTextures]);
|
|
@@ -3031,18 +2248,32 @@ function useTexture(input, optionsOrOnLoad) {
|
|
|
3031
2248
|
urlTextureMap.push([url, textureRecord[key]]);
|
|
3032
2249
|
}
|
|
3033
2250
|
}
|
|
3034
|
-
|
|
3035
|
-
const
|
|
3036
|
-
let
|
|
2251
|
+
store.setState((state) => {
|
|
2252
|
+
const refs = new Map(state._textureRefs);
|
|
2253
|
+
let textures = state.textures;
|
|
2254
|
+
let added = false;
|
|
3037
2255
|
for (const [url, texture] of urlTextureMap) {
|
|
3038
|
-
if (!
|
|
3039
|
-
|
|
3040
|
-
|
|
2256
|
+
if (!textures.has(url)) {
|
|
2257
|
+
if (!added) {
|
|
2258
|
+
textures = new Map(textures);
|
|
2259
|
+
added = true;
|
|
2260
|
+
}
|
|
2261
|
+
textures.set(url, texture);
|
|
3041
2262
|
}
|
|
2263
|
+
refs.set(url, (refs.get(url) ?? 0) + 1);
|
|
3042
2264
|
}
|
|
3043
|
-
return
|
|
2265
|
+
return added ? { textures, _textureRefs: refs } : { _textureRefs: refs };
|
|
3044
2266
|
});
|
|
3045
|
-
|
|
2267
|
+
return () => store.setState((state) => {
|
|
2268
|
+
const refs = new Map(state._textureRefs);
|
|
2269
|
+
for (const [url] of urlTextureMap) {
|
|
2270
|
+
const next = (refs.get(url) ?? 0) - 1;
|
|
2271
|
+
if (next <= 0) refs.delete(url);
|
|
2272
|
+
else refs.set(url, next);
|
|
2273
|
+
}
|
|
2274
|
+
return { _textureRefs: refs };
|
|
2275
|
+
});
|
|
2276
|
+
}, [cache, input, mappedTextures, store]);
|
|
3046
2277
|
return mappedTextures;
|
|
3047
2278
|
}
|
|
3048
2279
|
useTexture.preload = (url) => useLoader.preload(TextureLoader, url);
|
|
@@ -3058,96 +2289,63 @@ const Texture = ({
|
|
|
3058
2289
|
return /* @__PURE__ */ jsx(Fragment, { children: children?.(ret) });
|
|
3059
2290
|
};
|
|
3060
2291
|
|
|
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() {
|
|
2292
|
+
function useTextures(selector) {
|
|
3069
2293
|
const store = useStore();
|
|
3070
|
-
|
|
3071
|
-
const set = store.setState;
|
|
2294
|
+
const registry = useMemo(() => {
|
|
3072
2295
|
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
|
-
};
|
|
2296
|
+
const setState = store.setState;
|
|
2297
|
+
const getOne = (key) => getState().textures.get(key);
|
|
3131
2298
|
return {
|
|
3132
|
-
|
|
3133
|
-
get textures() {
|
|
2299
|
+
get all() {
|
|
3134
2300
|
return getState().textures;
|
|
3135
2301
|
},
|
|
3136
|
-
|
|
3137
|
-
|
|
2302
|
+
get(input) {
|
|
2303
|
+
if (typeof input === "string") return getOne(input);
|
|
2304
|
+
if (Array.isArray(input)) return input.map(getOne);
|
|
2305
|
+
const out = {};
|
|
2306
|
+
for (const name in input) out[name] = getOne(input[name]);
|
|
2307
|
+
return out;
|
|
2308
|
+
},
|
|
3138
2309
|
has: (key) => getState().textures.has(key),
|
|
3139
|
-
|
|
3140
|
-
|
|
3141
|
-
|
|
3142
|
-
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
|
|
3146
|
-
|
|
3147
|
-
|
|
3148
|
-
|
|
2310
|
+
add(keyOrRecord, texture) {
|
|
2311
|
+
setState((state) => {
|
|
2312
|
+
const textures = new Map(state.textures);
|
|
2313
|
+
if (typeof keyOrRecord === "string") {
|
|
2314
|
+
textures.set(keyOrRecord, texture);
|
|
2315
|
+
} else {
|
|
2316
|
+
for (const key in keyOrRecord) textures.set(key, keyOrRecord[key]);
|
|
2317
|
+
}
|
|
2318
|
+
return { textures };
|
|
2319
|
+
});
|
|
2320
|
+
},
|
|
2321
|
+
dispose(key, options) {
|
|
2322
|
+
const state = getState();
|
|
2323
|
+
const refs = state._textureRefs.get(key) ?? 0;
|
|
2324
|
+
if (refs > 0 && !options?.force) {
|
|
2325
|
+
console.warn(
|
|
2326
|
+
`[useTextures] "${key}" still has ${refs} active reference(s); skipping dispose. Pass { force: true } to override.`
|
|
2327
|
+
);
|
|
2328
|
+
return false;
|
|
2329
|
+
}
|
|
2330
|
+
state.textures.get(key)?.dispose();
|
|
2331
|
+
setState((s) => {
|
|
2332
|
+
const textures = new Map(s.textures);
|
|
2333
|
+
textures.delete(key);
|
|
2334
|
+
const nextRefs = new Map(s._textureRefs);
|
|
2335
|
+
nextRefs.delete(key);
|
|
2336
|
+
return { textures, _textureRefs: nextRefs };
|
|
2337
|
+
});
|
|
2338
|
+
return true;
|
|
2339
|
+
},
|
|
2340
|
+
disposeAll() {
|
|
2341
|
+
for (const texture of getState().textures.values()) texture.dispose();
|
|
2342
|
+
setState({ textures: /* @__PURE__ */ new Map(), _textureRefs: /* @__PURE__ */ new Map() });
|
|
2343
|
+
}
|
|
3149
2344
|
};
|
|
3150
2345
|
}, [store]);
|
|
2346
|
+
const subscribe = selector ? () => selector(registry) : (state) => state.textures;
|
|
2347
|
+
const selected = useThree(subscribe);
|
|
2348
|
+
return selector ? selected : registry;
|
|
3151
2349
|
}
|
|
3152
2350
|
|
|
3153
2351
|
function useRenderTarget(widthOrOptions, heightOrOptions, options) {
|
|
@@ -3228,7 +2426,7 @@ function advance(timestamp) {
|
|
|
3228
2426
|
getScheduler().step(timestamp);
|
|
3229
2427
|
}
|
|
3230
2428
|
|
|
3231
|
-
const version = "10.0.0-alpha.
|
|
2429
|
+
const version = "10.0.0-alpha.3";
|
|
3232
2430
|
const packageData = {
|
|
3233
2431
|
version: version};
|
|
3234
2432
|
|
|
@@ -14484,7 +13682,7 @@ function n0() {
|
|
|
14484
13682
|
var t0 = n0();
|
|
14485
13683
|
const r0 = Xb(t0);
|
|
14486
13684
|
|
|
14487
|
-
const t = 1, o = 8, r = 32, e = 2;
|
|
13685
|
+
const t = 1, o = 8, r = 32, e = 2, i$1 = 268435456;
|
|
14488
13686
|
|
|
14489
13687
|
function createReconciler(config) {
|
|
14490
13688
|
const reconciler2 = r0(config);
|
|
@@ -14649,7 +13847,15 @@ function setFiberRef(fiber, publicInstance) {
|
|
|
14649
13847
|
}
|
|
14650
13848
|
}
|
|
14651
13849
|
const reconstructed = [];
|
|
14652
|
-
function
|
|
13850
|
+
function flushReconstructedInstances() {
|
|
13851
|
+
if (reconstructed.length === 0) return;
|
|
13852
|
+
try {
|
|
13853
|
+
swapReconstructedInstances();
|
|
13854
|
+
} finally {
|
|
13855
|
+
reconstructed.length = 0;
|
|
13856
|
+
}
|
|
13857
|
+
}
|
|
13858
|
+
function swapReconstructedInstances() {
|
|
14653
13859
|
for (const [instance] of reconstructed) {
|
|
14654
13860
|
const parent = instance.parent;
|
|
14655
13861
|
if (parent) {
|
|
@@ -14667,6 +13873,7 @@ function swapInstances() {
|
|
|
14667
13873
|
}
|
|
14668
13874
|
}
|
|
14669
13875
|
if (instance.isHidden) unhideInstance(instance);
|
|
13876
|
+
if (isObject3D(instance.object)) removeInteractivity(findInitialRoot(instance), instance.object);
|
|
14670
13877
|
if (instance.object.__r3f) delete instance.object.__r3f;
|
|
14671
13878
|
if (instance.type !== "primitive") disposeOnIdle(instance.object);
|
|
14672
13879
|
}
|
|
@@ -14679,6 +13886,8 @@ function swapInstances() {
|
|
|
14679
13886
|
instance.object.__r3f = instance;
|
|
14680
13887
|
setFiberRef(fiber, instance.object);
|
|
14681
13888
|
delete instance.appliedOnce;
|
|
13889
|
+
instance.eventCount = 0;
|
|
13890
|
+
instance.handlers = {};
|
|
14682
13891
|
applyProps(instance.object, instance.props);
|
|
14683
13892
|
if (instance.props.attach) {
|
|
14684
13893
|
attach(parent, instance);
|
|
@@ -14695,14 +13904,114 @@ function swapInstances() {
|
|
|
14695
13904
|
invalidateInstance(instance);
|
|
14696
13905
|
}
|
|
14697
13906
|
}
|
|
14698
|
-
reconstructed.length = 0;
|
|
14699
13907
|
}
|
|
14700
13908
|
const handleTextInstance = () => {
|
|
14701
13909
|
};
|
|
14702
13910
|
const NO_CONTEXT = {};
|
|
14703
13911
|
let currentUpdatePriority = NoEventPriority;
|
|
14704
|
-
|
|
14705
|
-
|
|
13912
|
+
function getEventPriority(type) {
|
|
13913
|
+
switch (type) {
|
|
13914
|
+
case "beforetoggle":
|
|
13915
|
+
case "cancel":
|
|
13916
|
+
case "click":
|
|
13917
|
+
case "close":
|
|
13918
|
+
case "contextmenu":
|
|
13919
|
+
case "copy":
|
|
13920
|
+
case "cut":
|
|
13921
|
+
case "auxclick":
|
|
13922
|
+
case "dblclick":
|
|
13923
|
+
case "dragend":
|
|
13924
|
+
case "dragstart":
|
|
13925
|
+
case "drop":
|
|
13926
|
+
case "focusin":
|
|
13927
|
+
case "focusout":
|
|
13928
|
+
case "input":
|
|
13929
|
+
case "invalid":
|
|
13930
|
+
case "keydown":
|
|
13931
|
+
case "keypress":
|
|
13932
|
+
case "keyup":
|
|
13933
|
+
case "mousedown":
|
|
13934
|
+
case "mouseup":
|
|
13935
|
+
case "paste":
|
|
13936
|
+
case "pause":
|
|
13937
|
+
case "play":
|
|
13938
|
+
case "pointercancel":
|
|
13939
|
+
case "pointerdown":
|
|
13940
|
+
case "pointerup":
|
|
13941
|
+
case "ratechange":
|
|
13942
|
+
case "reset":
|
|
13943
|
+
case "resize":
|
|
13944
|
+
case "seeked":
|
|
13945
|
+
case "submit":
|
|
13946
|
+
case "touchcancel":
|
|
13947
|
+
case "touchend":
|
|
13948
|
+
case "touchstart":
|
|
13949
|
+
case "volumechange":
|
|
13950
|
+
case "change":
|
|
13951
|
+
case "selectionchange":
|
|
13952
|
+
case "compositionstart":
|
|
13953
|
+
case "compositionend":
|
|
13954
|
+
case "compositionupdate":
|
|
13955
|
+
case "beforeinput":
|
|
13956
|
+
case "blur":
|
|
13957
|
+
case "fullscreenchange":
|
|
13958
|
+
case "focus":
|
|
13959
|
+
case "hashchange":
|
|
13960
|
+
case "popstate":
|
|
13961
|
+
case "select":
|
|
13962
|
+
case "selectstart":
|
|
13963
|
+
return e;
|
|
13964
|
+
case "drag":
|
|
13965
|
+
case "dragenter":
|
|
13966
|
+
case "dragexit":
|
|
13967
|
+
case "dragleave":
|
|
13968
|
+
case "dragover":
|
|
13969
|
+
case "mousemove":
|
|
13970
|
+
case "mouseout":
|
|
13971
|
+
case "mouseover":
|
|
13972
|
+
case "pointermove":
|
|
13973
|
+
case "pointerout":
|
|
13974
|
+
case "pointerover":
|
|
13975
|
+
case "scroll":
|
|
13976
|
+
case "touchmove":
|
|
13977
|
+
case "wheel":
|
|
13978
|
+
case "mouseenter":
|
|
13979
|
+
case "mouseleave":
|
|
13980
|
+
case "pointerenter":
|
|
13981
|
+
case "pointerleave":
|
|
13982
|
+
return o;
|
|
13983
|
+
case "message": {
|
|
13984
|
+
switch (unstable_getCurrentPriorityLevel()) {
|
|
13985
|
+
case unstable_ImmediatePriority:
|
|
13986
|
+
return e;
|
|
13987
|
+
case unstable_UserBlockingPriority:
|
|
13988
|
+
return o;
|
|
13989
|
+
case unstable_NormalPriority:
|
|
13990
|
+
case unstable_LowPriority:
|
|
13991
|
+
return r;
|
|
13992
|
+
case unstable_IdlePriority:
|
|
13993
|
+
return i$1;
|
|
13994
|
+
default:
|
|
13995
|
+
return r;
|
|
13996
|
+
}
|
|
13997
|
+
}
|
|
13998
|
+
default:
|
|
13999
|
+
return r;
|
|
14000
|
+
}
|
|
14001
|
+
}
|
|
14002
|
+
function scheduleMicrotask(callback) {
|
|
14003
|
+
if (typeof queueMicrotask === "function") {
|
|
14004
|
+
queueMicrotask(callback);
|
|
14005
|
+
} else if (typeof Promise !== "undefined") {
|
|
14006
|
+
Promise.resolve().then(callback).catch((error) => {
|
|
14007
|
+
setTimeout(() => {
|
|
14008
|
+
throw error;
|
|
14009
|
+
});
|
|
14010
|
+
});
|
|
14011
|
+
} else {
|
|
14012
|
+
setTimeout(callback);
|
|
14013
|
+
}
|
|
14014
|
+
}
|
|
14706
14015
|
const reconciler = /* @__PURE__ */ createReconciler({
|
|
14707
14016
|
isPrimaryRenderer: false,
|
|
14708
14017
|
warnsIfNotActing: false,
|
|
@@ -14741,16 +14050,15 @@ const reconciler = /* @__PURE__ */ createReconciler({
|
|
|
14741
14050
|
else if (newProps.args?.length !== oldProps.args?.length) reconstruct = true;
|
|
14742
14051
|
else if (newProps.args?.some((value, index) => value !== oldProps.args?.[index])) reconstruct = true;
|
|
14743
14052
|
if (reconstruct) {
|
|
14744
|
-
reconstructed.push([instance,
|
|
14053
|
+
reconstructed.push([instance, getInstanceProps(newProps), fiber]);
|
|
14745
14054
|
} else {
|
|
14746
14055
|
const changedProps = diffProps(instance, newProps);
|
|
14747
|
-
|
|
14748
|
-
|
|
14749
|
-
|
|
14750
|
-
|
|
14056
|
+
const attach2 = instance.props.attach;
|
|
14057
|
+
instance.props = getInstanceProps(newProps);
|
|
14058
|
+
if (attach2 !== void 0) instance.props.attach = attach2;
|
|
14059
|
+
else delete instance.props.attach;
|
|
14060
|
+
if (Object.keys(changedProps).length) applyProps(instance.object, changedProps);
|
|
14751
14061
|
}
|
|
14752
|
-
const isTailSibling = fiber.sibling === null || (fiber.flags & Update) === NoFlags;
|
|
14753
|
-
if (isTailSibling) swapInstances();
|
|
14754
14062
|
},
|
|
14755
14063
|
finalizeInitialChildren: (instance) => {
|
|
14756
14064
|
for (const prop in instance.props) {
|
|
@@ -14775,8 +14083,9 @@ const reconciler = /* @__PURE__ */ createReconciler({
|
|
|
14775
14083
|
const target = container.getState().internal.container ?? container.getState().scene;
|
|
14776
14084
|
return prepare(target, container, "", {});
|
|
14777
14085
|
},
|
|
14778
|
-
|
|
14779
|
-
|
|
14086
|
+
// Reconstructed instances are swapped once all mutations are committed,
|
|
14087
|
+
// before layout effects run so refs point to the new objects
|
|
14088
|
+
resetAfterCommit: flushReconstructedInstances,
|
|
14780
14089
|
shouldSetTextContent: () => false,
|
|
14781
14090
|
clearContainer: () => false,
|
|
14782
14091
|
hideInstance,
|
|
@@ -14784,6 +14093,9 @@ const reconciler = /* @__PURE__ */ createReconciler({
|
|
|
14784
14093
|
createTextInstance: handleTextInstance,
|
|
14785
14094
|
hideTextInstance: handleTextInstance,
|
|
14786
14095
|
unhideTextInstance: handleTextInstance,
|
|
14096
|
+
// Mirrors react-dom, which flushes discrete work in microtasks
|
|
14097
|
+
supportsMicrotasks: true,
|
|
14098
|
+
scheduleMicrotask,
|
|
14787
14099
|
scheduleTimeout: typeof setTimeout === "function" ? setTimeout : void 0,
|
|
14788
14100
|
cancelTimeout: typeof clearTimeout === "function" ? clearTimeout : void 0,
|
|
14789
14101
|
noTimeout: -1,
|
|
@@ -14824,28 +14136,9 @@ const reconciler = /* @__PURE__ */ createReconciler({
|
|
|
14824
14136
|
},
|
|
14825
14137
|
resolveUpdatePriority() {
|
|
14826
14138
|
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
|
-
}
|
|
14139
|
+
const eventType = typeof window !== "undefined" ? window.event?.type : void 0;
|
|
14140
|
+
if (eventType === void 0) return r;
|
|
14141
|
+
return getEventPriority(eventType);
|
|
14849
14142
|
},
|
|
14850
14143
|
resetFormInstance() {
|
|
14851
14144
|
},
|
|
@@ -14894,11 +14187,15 @@ const reconciler = /* @__PURE__ */ createReconciler({
|
|
|
14894
14187
|
cloneMutableTextInstance(textInstance) {
|
|
14895
14188
|
return textInstance;
|
|
14896
14189
|
},
|
|
14190
|
+
// React 19.2 added these for root-level view transitions. They only run for hosts that opt into
|
|
14191
|
+
// view transitions by returning a container from cloneRootViewTransitionContainer; R3F renders
|
|
14192
|
+
// into a WebGL/WebGPU canvas, where cloning the root has no meaning, so we never opt in and
|
|
14193
|
+
// React never calls them. They stay as no-ops rather than throws: the host config is shared by
|
|
14194
|
+
// every root, and a throw here would turn a React-internal probe into a hard crash for users.
|
|
14195
|
+
// Guarded by a test asserting a full render + unmount never reaches them.
|
|
14897
14196
|
cloneRootViewTransitionContainer(_rootContainer) {
|
|
14898
|
-
throw new Error("Not implemented.");
|
|
14899
14197
|
},
|
|
14900
14198
|
removeRootViewTransitionClone(_rootContainer, _clone) {
|
|
14901
|
-
throw new Error("Not implemented.");
|
|
14902
14199
|
},
|
|
14903
14200
|
// https://github.com/facebook/react/pull/32465
|
|
14904
14201
|
createFragmentInstance: (_fiber) => null,
|
|
@@ -14983,6 +14280,7 @@ function createRoot(canvas) {
|
|
|
14983
14280
|
const lastConfiguredProps = {};
|
|
14984
14281
|
let configured = false;
|
|
14985
14282
|
let pending = null;
|
|
14283
|
+
let rendererSetup = null;
|
|
14986
14284
|
return {
|
|
14987
14285
|
async configure(props = {}) {
|
|
14988
14286
|
let resolve;
|
|
@@ -14998,7 +14296,6 @@ function createRoot(canvas) {
|
|
|
14998
14296
|
events,
|
|
14999
14297
|
onCreated: onCreatedCallback,
|
|
15000
14298
|
shadows = false,
|
|
15001
|
-
textureColorSpace = SRGBColorSpace,
|
|
15002
14299
|
orthographic = false,
|
|
15003
14300
|
frameloop = "always",
|
|
15004
14301
|
dpr = [1, 2],
|
|
@@ -15013,7 +14310,8 @@ function createRoot(canvas) {
|
|
|
15013
14310
|
_sizeProps,
|
|
15014
14311
|
forceEven
|
|
15015
14312
|
} = props;
|
|
15016
|
-
const
|
|
14313
|
+
const textureColorSpace = is.obj(glConfig) && !is.fun(glConfig) && !isRenderer(glConfig) && glConfig.textureColorSpace || is.obj(rendererConfig) && !is.fun(rendererConfig) && !isRenderer(rendererConfig) && rendererConfig.textureColorSpace || SRGBColorSpace;
|
|
14314
|
+
let state = store.getState();
|
|
15017
14315
|
const defaultGPUProps = {
|
|
15018
14316
|
canvas,
|
|
15019
14317
|
antialias: true
|
|
@@ -15027,46 +14325,64 @@ function createRoot(canvas) {
|
|
|
15027
14325
|
throw new Error("Cannot use both gl and renderer props at the same time");
|
|
15028
14326
|
}
|
|
15029
14327
|
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
|
-
|
|
14328
|
+
if (!state.internal.actualRenderer) {
|
|
14329
|
+
if (!rendererSetup) {
|
|
14330
|
+
rendererSetup = (async () => {
|
|
14331
|
+
if (primaryCanvas) {
|
|
14332
|
+
const primary = await waitForPrimary(primaryCanvas);
|
|
14333
|
+
renderer = primary.renderer;
|
|
14334
|
+
state.internal.actualRenderer = renderer;
|
|
14335
|
+
const canvasTarget = new CanvasTarget(canvas);
|
|
14336
|
+
primary.store.setState((prev) => ({
|
|
14337
|
+
internal: { ...prev.internal, isMultiCanvas: true }
|
|
14338
|
+
}));
|
|
14339
|
+
state.set((prev) => ({
|
|
14340
|
+
webGPUSupported: primary.store.getState().webGPUSupported,
|
|
14341
|
+
renderer,
|
|
14342
|
+
primaryStore: primary.store,
|
|
14343
|
+
internal: {
|
|
14344
|
+
...prev.internal,
|
|
14345
|
+
canvasTarget,
|
|
14346
|
+
isMultiCanvas: true,
|
|
14347
|
+
isSecondary: true,
|
|
14348
|
+
targetId: primaryCanvas
|
|
14349
|
+
}
|
|
14350
|
+
}));
|
|
14351
|
+
} else {
|
|
14352
|
+
renderer = await resolveRenderer(rendererConfig, defaultGPUProps, WebGPURenderer);
|
|
14353
|
+
if (!renderer.hasInitialized?.()) {
|
|
14354
|
+
const size2 = computeInitialSize(canvas, propsSize);
|
|
14355
|
+
if (size2.width > 0 && size2.height > 0) {
|
|
14356
|
+
const pixelRatio = calculateDpr(dpr);
|
|
14357
|
+
canvas.width = size2.width * pixelRatio;
|
|
14358
|
+
canvas.height = size2.height * pixelRatio;
|
|
14359
|
+
}
|
|
14360
|
+
await renderer.init();
|
|
14361
|
+
}
|
|
14362
|
+
const backend = renderer.backend;
|
|
14363
|
+
const isWebGPUBackend = backend && "isWebGPUBackend" in backend;
|
|
14364
|
+
state.internal.actualRenderer = renderer;
|
|
14365
|
+
state.set({ webGPUSupported: isWebGPUBackend, renderer, primaryStore: store });
|
|
14366
|
+
if (canvasId && !state.internal.isSecondary) {
|
|
14367
|
+
const canvasTarget = new CanvasTarget(canvas);
|
|
14368
|
+
const unregisterPrimary = registerPrimary(canvasId, renderer, store);
|
|
14369
|
+
state.set((prev) => ({
|
|
14370
|
+
internal: {
|
|
14371
|
+
...prev.internal,
|
|
14372
|
+
canvasTarget,
|
|
14373
|
+
unregisterPrimary
|
|
14374
|
+
}
|
|
14375
|
+
}));
|
|
14376
|
+
}
|
|
15067
14377
|
}
|
|
15068
|
-
}))
|
|
14378
|
+
})().catch((err) => {
|
|
14379
|
+
rendererSetup = null;
|
|
14380
|
+
throw err;
|
|
14381
|
+
});
|
|
15069
14382
|
}
|
|
14383
|
+
await rendererSetup;
|
|
14384
|
+
state = store.getState();
|
|
14385
|
+
renderer = state.internal.actualRenderer;
|
|
15070
14386
|
}
|
|
15071
14387
|
let raycaster = state.raycaster;
|
|
15072
14388
|
if (!raycaster) state.set({ raycaster: raycaster = new Raycaster() });
|
|
@@ -15159,7 +14475,7 @@ function createRoot(canvas) {
|
|
|
15159
14475
|
lastConfiguredProps.performance = performance;
|
|
15160
14476
|
}
|
|
15161
14477
|
if (!state.xr) {
|
|
15162
|
-
const handleXRFrame = (timestamp,
|
|
14478
|
+
const handleXRFrame = (timestamp, _frame) => {
|
|
15163
14479
|
const state2 = store.getState();
|
|
15164
14480
|
if (state2.frameloop === "never") return;
|
|
15165
14481
|
advance(timestamp);
|
|
@@ -15195,15 +14511,22 @@ function createRoot(canvas) {
|
|
|
15195
14511
|
const oldType = renderer.shadowMap.type;
|
|
15196
14512
|
renderer.shadowMap.enabled = !!shadows;
|
|
15197
14513
|
if (is.boo(shadows)) {
|
|
15198
|
-
renderer.shadowMap.type =
|
|
14514
|
+
renderer.shadowMap.type = PCFShadowMap;
|
|
15199
14515
|
} else if (is.str(shadows)) {
|
|
14516
|
+
if (shadows === "soft") {
|
|
14517
|
+
notifyDepreciated({
|
|
14518
|
+
heading: 'shadows="soft" is deprecated',
|
|
14519
|
+
body: "Three has depreciated soft and improved basic PCFShadows, we converted for you.",
|
|
14520
|
+
link: "https://github.com/mrdoob/three.js/wiki/Migration-Guide?utm_source=chatgpt.com#181--182"
|
|
14521
|
+
});
|
|
14522
|
+
}
|
|
15200
14523
|
const types = {
|
|
15201
14524
|
basic: BasicShadowMap,
|
|
15202
14525
|
percentage: PCFShadowMap,
|
|
15203
|
-
soft:
|
|
14526
|
+
soft: PCFShadowMap,
|
|
15204
14527
|
variance: VSMShadowMap
|
|
15205
14528
|
};
|
|
15206
|
-
renderer.shadowMap.type = types[shadows] ??
|
|
14529
|
+
renderer.shadowMap.type = types[shadows] ?? PCFShadowMap;
|
|
15207
14530
|
} else if (is.obj(shadows)) {
|
|
15208
14531
|
Object.assign(renderer.shadowMap, shadows);
|
|
15209
14532
|
}
|
|
@@ -15219,13 +14542,24 @@ function createRoot(canvas) {
|
|
|
15219
14542
|
if (state.textureColorSpace !== textureColorSpace) state.set(() => ({ textureColorSpace }));
|
|
15220
14543
|
lastConfiguredProps.textureColorSpace = textureColorSpace;
|
|
15221
14544
|
}
|
|
14545
|
+
const r3fProps = ["textureColorSpace"];
|
|
14546
|
+
const constructorOnlyProps = ["samples", "antialias", "alpha", "canvas", "powerPreference"];
|
|
14547
|
+
const nonApplyProps = [...r3fProps, ...constructorOnlyProps];
|
|
15222
14548
|
if (glConfig && !is.fun(glConfig) && !isRenderer(glConfig) && !is.equ(glConfig, renderer, shallowLoose)) {
|
|
15223
|
-
|
|
14549
|
+
const glProps = {};
|
|
14550
|
+
for (const key in glConfig) {
|
|
14551
|
+
if (!nonApplyProps.includes(key)) glProps[key] = glConfig[key];
|
|
14552
|
+
}
|
|
14553
|
+
applyProps(renderer, glProps);
|
|
15224
14554
|
}
|
|
15225
14555
|
if (rendererConfig && !is.fun(rendererConfig) && !isRenderer(rendererConfig) && state.renderer) {
|
|
15226
14556
|
const currentRenderer = state.renderer;
|
|
15227
14557
|
if (!is.equ(rendererConfig, currentRenderer, shallowLoose)) {
|
|
15228
|
-
|
|
14558
|
+
const rendererProps = {};
|
|
14559
|
+
for (const key in rendererConfig) {
|
|
14560
|
+
if (!nonApplyProps.includes(key)) rendererProps[key] = rendererConfig[key];
|
|
14561
|
+
}
|
|
14562
|
+
applyProps(currentRenderer, rendererProps);
|
|
15229
14563
|
}
|
|
15230
14564
|
}
|
|
15231
14565
|
const scheduler = getScheduler();
|
|
@@ -15251,6 +14585,18 @@ function createRoot(canvas) {
|
|
|
15251
14585
|
system: true
|
|
15252
14586
|
}
|
|
15253
14587
|
);
|
|
14588
|
+
const unregisterEventsFlush = scheduler.register(
|
|
14589
|
+
() => {
|
|
14590
|
+
const state2 = store.getState();
|
|
14591
|
+
state2.events.flush?.();
|
|
14592
|
+
},
|
|
14593
|
+
{
|
|
14594
|
+
id: `${newRootId}_events`,
|
|
14595
|
+
rootId: newRootId,
|
|
14596
|
+
phase: "input",
|
|
14597
|
+
system: true
|
|
14598
|
+
}
|
|
14599
|
+
);
|
|
15254
14600
|
const unregisterFrustum = scheduler.register(
|
|
15255
14601
|
() => {
|
|
15256
14602
|
const state2 = store.getState();
|
|
@@ -15261,7 +14607,7 @@ function createRoot(canvas) {
|
|
|
15261
14607
|
{
|
|
15262
14608
|
id: `${newRootId}_frustum`,
|
|
15263
14609
|
rootId: newRootId,
|
|
15264
|
-
|
|
14610
|
+
before: "render",
|
|
15265
14611
|
system: true
|
|
15266
14612
|
}
|
|
15267
14613
|
);
|
|
@@ -15273,7 +14619,7 @@ function createRoot(canvas) {
|
|
|
15273
14619
|
{
|
|
15274
14620
|
id: `${newRootId}_visibility`,
|
|
15275
14621
|
rootId: newRootId,
|
|
15276
|
-
|
|
14622
|
+
before: "render",
|
|
15277
14623
|
system: true,
|
|
15278
14624
|
after: `${newRootId}_frustum`
|
|
15279
14625
|
}
|
|
@@ -15285,7 +14631,7 @@ function createRoot(canvas) {
|
|
|
15285
14631
|
const userHandlesRender = scheduler.hasUserJobsInPhase("render", newRootId);
|
|
15286
14632
|
if (userHandlesRender || state2.internal.priority) return;
|
|
15287
14633
|
try {
|
|
15288
|
-
if (state2.
|
|
14634
|
+
if (state2.renderPipeline?.render) state2.renderPipeline.render();
|
|
15289
14635
|
else if (renderer2?.render) renderer2.render(state2.scene, state2.camera);
|
|
15290
14636
|
} catch (error) {
|
|
15291
14637
|
state2.setError(error instanceof Error ? error : new Error(String(error)));
|
|
@@ -15310,6 +14656,7 @@ function createRoot(canvas) {
|
|
|
15310
14656
|
unregisterRoot: () => {
|
|
15311
14657
|
unregisterRoot();
|
|
15312
14658
|
unregisterCanvasTarget();
|
|
14659
|
+
unregisterEventsFlush();
|
|
15313
14660
|
unregisterFrustum();
|
|
15314
14661
|
unregisterVisibility();
|
|
15315
14662
|
unregisterRender();
|
|
@@ -15385,7 +14732,8 @@ function unmountComponentAtNode(canvas, callback) {
|
|
|
15385
14732
|
dispose(state.scene);
|
|
15386
14733
|
_roots.delete(canvas);
|
|
15387
14734
|
if (callback) callback(canvas);
|
|
15388
|
-
} catch {
|
|
14735
|
+
} catch (error) {
|
|
14736
|
+
console.warn("[R3F] Error while unmounting root; teardown may be incomplete:", error);
|
|
15389
14737
|
}
|
|
15390
14738
|
}, 500);
|
|
15391
14739
|
}
|
|
@@ -15448,6 +14796,7 @@ function PortalInner({ state = {}, children, container }) {
|
|
|
15448
14796
|
viewport = rootState.viewport.getCurrentViewport(camera, new Vector3(), resolvedSize);
|
|
15449
14797
|
if (camera !== rootState.camera) updateCamera(camera, resolvedSize);
|
|
15450
14798
|
}
|
|
14799
|
+
const set = injectState.set;
|
|
15451
14800
|
return {
|
|
15452
14801
|
// The intersect consists of the previous root state
|
|
15453
14802
|
...rootState,
|
|
@@ -15466,7 +14815,7 @@ function PortalInner({ state = {}, children, container }) {
|
|
|
15466
14815
|
size: resolvedSize,
|
|
15467
14816
|
viewport: { ...rootState.viewport, ...viewport },
|
|
15468
14817
|
// Layers are allowed to override events
|
|
15469
|
-
setEvents: (events2) =>
|
|
14818
|
+
setEvents: (events2) => set((state2) => ({ ...state2, events: { ...state2.events, ...events2 } })),
|
|
15470
14819
|
// Container for child attachment - the portalScene (injected or container itself)
|
|
15471
14820
|
internal: { ...rootState.internal, ...injectState.internal, container: portalScene }
|
|
15472
14821
|
};
|
|
@@ -15475,9 +14824,13 @@ function PortalInner({ state = {}, children, container }) {
|
|
|
15475
14824
|
const store = createWithEqualityFn((set, get) => ({ ...rest, set, get }));
|
|
15476
14825
|
const onMutate = (prev) => store.setState((state2) => inject.current(prev, state2));
|
|
15477
14826
|
onMutate(previousRoot.getState());
|
|
15478
|
-
previousRoot.subscribe(onMutate);
|
|
15479
14827
|
return store;
|
|
15480
14828
|
}, [previousRoot, container]);
|
|
14829
|
+
useIsomorphicLayoutEffect(() => {
|
|
14830
|
+
const onMutate = (prev) => usePortalStore.setState((state2) => inject.current(prev, state2));
|
|
14831
|
+
const unsubscribe = previousRoot.subscribe(onMutate);
|
|
14832
|
+
return unsubscribe;
|
|
14833
|
+
}, [previousRoot, usePortalStore]);
|
|
15481
14834
|
return (
|
|
15482
14835
|
// @ts-ignore, reconciler types are not maintained
|
|
15483
14836
|
/* @__PURE__ */ jsx(Fragment, { children: reconciler.createPortal(
|
|
@@ -15491,6 +14844,178 @@ function flushSync(fn) {
|
|
|
15491
14844
|
return reconciler.flushSyncFromReconciler(fn);
|
|
15492
14845
|
}
|
|
15493
14846
|
|
|
14847
|
+
function parseBackground(background) {
|
|
14848
|
+
if (!background) return null;
|
|
14849
|
+
if (typeof background === "object" && !background.isColor) {
|
|
14850
|
+
const { backgroundMap, envMap, files, preset, ...rest } = background;
|
|
14851
|
+
return {
|
|
14852
|
+
...rest,
|
|
14853
|
+
preset,
|
|
14854
|
+
files: envMap || files,
|
|
14855
|
+
backgroundFiles: backgroundMap,
|
|
14856
|
+
background: true
|
|
14857
|
+
};
|
|
14858
|
+
}
|
|
14859
|
+
if (typeof background === "number") {
|
|
14860
|
+
return { color: background, background: true };
|
|
14861
|
+
}
|
|
14862
|
+
if (typeof background === "string") {
|
|
14863
|
+
if (background in presetsObj) {
|
|
14864
|
+
return { preset: background, background: true };
|
|
14865
|
+
}
|
|
14866
|
+
if (/^(https?:\/\/|\/|\.\/|\.\.\/)|\.(hdr|exr|jpg|jpeg|png|webp|gif)$/i.test(background)) {
|
|
14867
|
+
return { files: background, background: true };
|
|
14868
|
+
}
|
|
14869
|
+
return { color: background, background: true };
|
|
14870
|
+
}
|
|
14871
|
+
if (background.isColor) {
|
|
14872
|
+
return { color: background, background: true };
|
|
14873
|
+
}
|
|
14874
|
+
return null;
|
|
14875
|
+
}
|
|
14876
|
+
|
|
14877
|
+
function parseRendererConfig(rendererProp) {
|
|
14878
|
+
const isRendererConfig = typeof rendererProp === "object" && rendererProp !== null && !("render" in rendererProp) && ("primaryCanvas" in rendererProp || "scheduler" in rendererProp);
|
|
14879
|
+
if (isRendererConfig) {
|
|
14880
|
+
const { primaryCanvas: pc, scheduler: sc, ...rest } = rendererProp;
|
|
14881
|
+
return {
|
|
14882
|
+
primaryCanvas: pc,
|
|
14883
|
+
scheduler: sc,
|
|
14884
|
+
renderer: Object.keys(rest).length > 0 ? rest : rendererProp
|
|
14885
|
+
};
|
|
14886
|
+
}
|
|
14887
|
+
return { primaryCanvas: void 0, scheduler: void 0, renderer: rendererProp };
|
|
14888
|
+
}
|
|
14889
|
+
|
|
14890
|
+
const RESOURCE_KINDS = ["nodes", "uniforms", "buffers", "gpuStorage"];
|
|
14891
|
+
const ROOT_SCOPE = "";
|
|
14892
|
+
const registries = /* @__PURE__ */ new WeakMap();
|
|
14893
|
+
function getKindRegistry(store, kind) {
|
|
14894
|
+
let registry = registries.get(store);
|
|
14895
|
+
if (!registry) {
|
|
14896
|
+
registry = {
|
|
14897
|
+
nodes: { valid: /* @__PURE__ */ new Set(), staged: /* @__PURE__ */ new Map() },
|
|
14898
|
+
uniforms: { valid: /* @__PURE__ */ new Set(), staged: /* @__PURE__ */ new Map() },
|
|
14899
|
+
buffers: { valid: /* @__PURE__ */ new Set(), staged: /* @__PURE__ */ new Map() },
|
|
14900
|
+
gpuStorage: { valid: /* @__PURE__ */ new Set(), staged: /* @__PURE__ */ new Map() }
|
|
14901
|
+
};
|
|
14902
|
+
registries.set(store, registry);
|
|
14903
|
+
}
|
|
14904
|
+
return registry[kind];
|
|
14905
|
+
}
|
|
14906
|
+
function resolvePrimary$1(store) {
|
|
14907
|
+
return store.getState().primaryStore ?? store;
|
|
14908
|
+
}
|
|
14909
|
+
function isScopeValid(store, kind, scopeKey) {
|
|
14910
|
+
return getKindRegistry(store, kind).valid.has(scopeKey);
|
|
14911
|
+
}
|
|
14912
|
+
function peekStaged(store, kind, scopeKey) {
|
|
14913
|
+
return getKindRegistry(store, kind).staged.get(scopeKey);
|
|
14914
|
+
}
|
|
14915
|
+
function stageEntries(store, kind, scopeKey, entries) {
|
|
14916
|
+
const { staged } = getKindRegistry(store, kind);
|
|
14917
|
+
const current = staged.get(scopeKey);
|
|
14918
|
+
if (current) Object.assign(current, entries);
|
|
14919
|
+
else staged.set(scopeKey, { ...entries });
|
|
14920
|
+
}
|
|
14921
|
+
function readCommittedScope(map, scopeKey, isLeaf) {
|
|
14922
|
+
if (scopeKey === ROOT_SCOPE) {
|
|
14923
|
+
const leaves = {};
|
|
14924
|
+
for (const [key, value] of Object.entries(map)) if (isLeaf(value)) leaves[key] = value;
|
|
14925
|
+
return leaves;
|
|
14926
|
+
}
|
|
14927
|
+
const scopeData = map[scopeKey];
|
|
14928
|
+
if (scopeData && typeof scopeData === "object" && !isLeaf(scopeData)) return scopeData;
|
|
14929
|
+
return {};
|
|
14930
|
+
}
|
|
14931
|
+
function withStagedOverlay(store, kind, committed) {
|
|
14932
|
+
const { staged } = getKindRegistry(store, kind);
|
|
14933
|
+
if (staged.size === 0) return committed;
|
|
14934
|
+
const view = { ...committed };
|
|
14935
|
+
for (const [scopeKey, entries] of staged) {
|
|
14936
|
+
if (scopeKey === ROOT_SCOPE) Object.assign(view, entries);
|
|
14937
|
+
else view[scopeKey] = { ...view[scopeKey], ...entries };
|
|
14938
|
+
}
|
|
14939
|
+
return view;
|
|
14940
|
+
}
|
|
14941
|
+
function flushStagedScope(store, kind, scopeKey, isLeaf) {
|
|
14942
|
+
const registry = getKindRegistry(store, kind);
|
|
14943
|
+
const staged = registry.staged.get(scopeKey);
|
|
14944
|
+
if (!staged) return;
|
|
14945
|
+
registry.staged.delete(scopeKey);
|
|
14946
|
+
const replace = !registry.valid.has(scopeKey);
|
|
14947
|
+
store.setState((state) => {
|
|
14948
|
+
const map = state[kind];
|
|
14949
|
+
if (scopeKey === ROOT_SCOPE) {
|
|
14950
|
+
if (!replace) return { [kind]: { ...map, ...staged } };
|
|
14951
|
+
const next = {};
|
|
14952
|
+
for (const [key, value] of Object.entries(map)) if (!isLeaf(value)) next[key] = value;
|
|
14953
|
+
return { [kind]: { ...next, ...staged } };
|
|
14954
|
+
}
|
|
14955
|
+
const current = replace ? {} : map[scopeKey] ?? {};
|
|
14956
|
+
return { [kind]: { ...map, [scopeKey]: { ...current, ...staged } } };
|
|
14957
|
+
});
|
|
14958
|
+
registry.valid.add(scopeKey);
|
|
14959
|
+
}
|
|
14960
|
+
function invalidateResource(store, kind, scopeKey) {
|
|
14961
|
+
const registry = getKindRegistry(store, kind);
|
|
14962
|
+
if (scopeKey === void 0) {
|
|
14963
|
+
registry.valid.clear();
|
|
14964
|
+
registry.staged.clear();
|
|
14965
|
+
} else {
|
|
14966
|
+
registry.valid.delete(scopeKey);
|
|
14967
|
+
registry.staged.delete(scopeKey);
|
|
14968
|
+
}
|
|
14969
|
+
}
|
|
14970
|
+
function rebuildResource(store, kind, userScope) {
|
|
14971
|
+
const primary = resolvePrimary$1(store);
|
|
14972
|
+
invalidateResource(primary, kind, userScope === "root" ? ROOT_SCOPE : userScope);
|
|
14973
|
+
primary.setState((state) => ({ _hmrVersion: state._hmrVersion + 1 }));
|
|
14974
|
+
}
|
|
14975
|
+
function invalidateAllResources(store) {
|
|
14976
|
+
for (const kind of RESOURCE_KINDS) invalidateResource(store, kind);
|
|
14977
|
+
}
|
|
14978
|
+
function removeResourceEntries(store, kind, names, scope, isLeaf) {
|
|
14979
|
+
const staged = peekStaged(store, kind, scope ?? ROOT_SCOPE);
|
|
14980
|
+
if (staged) for (const name of names) delete staged[name];
|
|
14981
|
+
store.setState((state) => {
|
|
14982
|
+
const map = state[kind];
|
|
14983
|
+
if (scope) {
|
|
14984
|
+
const currentScope = { ...map[scope] };
|
|
14985
|
+
for (const name of names) delete currentScope[name];
|
|
14986
|
+
return { [kind]: { ...map, [scope]: currentScope } };
|
|
14987
|
+
}
|
|
14988
|
+
const next = { ...map };
|
|
14989
|
+
for (const name of names) if (isLeaf(next[name])) delete next[name];
|
|
14990
|
+
return { [kind]: next };
|
|
14991
|
+
});
|
|
14992
|
+
}
|
|
14993
|
+
function clearResourceEntries(store, kind, scope, isLeaf) {
|
|
14994
|
+
const registry = getKindRegistry(store, kind);
|
|
14995
|
+
if (scope && scope !== "root") registry.staged.delete(scope);
|
|
14996
|
+
else if (scope === "root") registry.staged.delete(ROOT_SCOPE);
|
|
14997
|
+
else registry.staged.clear();
|
|
14998
|
+
store.setState((state) => {
|
|
14999
|
+
const map = state[kind];
|
|
15000
|
+
if (scope && scope !== "root") {
|
|
15001
|
+
const { [scope]: _, ...rest } = map;
|
|
15002
|
+
return { [kind]: rest };
|
|
15003
|
+
}
|
|
15004
|
+
if (scope === "root") {
|
|
15005
|
+
const next = {};
|
|
15006
|
+
for (const [key, value] of Object.entries(map)) if (!isLeaf(value)) next[key] = value;
|
|
15007
|
+
return { [kind]: next };
|
|
15008
|
+
}
|
|
15009
|
+
return { [kind]: {} };
|
|
15010
|
+
});
|
|
15011
|
+
}
|
|
15012
|
+
|
|
15013
|
+
function clearHmrCaches(store) {
|
|
15014
|
+
const primary = store.getState().primaryStore ?? store;
|
|
15015
|
+
invalidateAllResources(primary);
|
|
15016
|
+
primary.setState((state) => ({ _hmrVersion: state._hmrVersion + 1 }));
|
|
15017
|
+
}
|
|
15018
|
+
|
|
15494
15019
|
function CanvasImpl({
|
|
15495
15020
|
ref,
|
|
15496
15021
|
children,
|
|
@@ -15511,6 +15036,8 @@ function CanvasImpl({
|
|
|
15511
15036
|
raycaster,
|
|
15512
15037
|
camera,
|
|
15513
15038
|
scene,
|
|
15039
|
+
autoUpdateFrustum,
|
|
15040
|
+
occlusion,
|
|
15514
15041
|
onPointerMissed,
|
|
15515
15042
|
onDragOverMissed,
|
|
15516
15043
|
onDropMissed,
|
|
@@ -15522,39 +15049,10 @@ function CanvasImpl({
|
|
|
15522
15049
|
forceEven,
|
|
15523
15050
|
...props
|
|
15524
15051
|
}) {
|
|
15525
|
-
const { primaryCanvas, scheduler,
|
|
15526
|
-
const renderer = Object.keys(rendererConfig).length > 0 ? rendererConfig : rendererProp;
|
|
15052
|
+
const { primaryCanvas, scheduler, renderer } = parseRendererConfig(rendererProp);
|
|
15527
15053
|
React.useMemo(() => extend(THREE), []);
|
|
15528
15054
|
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]);
|
|
15055
|
+
const backgroundProps = React.useMemo(() => parseBackground(background), [background]);
|
|
15558
15056
|
const hasInitialSizeRef = React.useRef(false);
|
|
15559
15057
|
const measureConfig = React.useMemo(() => {
|
|
15560
15058
|
if (!hasInitialSizeRef.current) {
|
|
@@ -15596,6 +15094,7 @@ function CanvasImpl({
|
|
|
15596
15094
|
const handleDropMissed = useMutableCallback(onDropMissed);
|
|
15597
15095
|
const [block, setBlock] = React.useState(false);
|
|
15598
15096
|
const [error, setError] = React.useState(false);
|
|
15097
|
+
const [fallbackVisible, setFallbackVisible] = React.useState(false);
|
|
15599
15098
|
if (block) throw block;
|
|
15600
15099
|
if (error) throw error;
|
|
15601
15100
|
const root = React.useRef(null);
|
|
@@ -15603,6 +15102,7 @@ function CanvasImpl({
|
|
|
15603
15102
|
const unsubscribeErrorRef = React.useRef(null);
|
|
15604
15103
|
useIsomorphicLayoutEffect(() => {
|
|
15605
15104
|
effectActiveRef.current = true;
|
|
15105
|
+
if (fallbackVisible) return;
|
|
15606
15106
|
const canvas = canvasRef.current;
|
|
15607
15107
|
if (effectiveSize.width > 0 && effectiveSize.height > 0 && canvas) {
|
|
15608
15108
|
if (!root.current) {
|
|
@@ -15623,7 +15123,7 @@ function CanvasImpl({
|
|
|
15623
15123
|
}
|
|
15624
15124
|
async function run() {
|
|
15625
15125
|
if (!effectActiveRef.current || !root.current) return;
|
|
15626
|
-
await root.current.configure({
|
|
15126
|
+
const configured = await root.current.configure({
|
|
15627
15127
|
id,
|
|
15628
15128
|
primaryCanvas,
|
|
15629
15129
|
scheduler,
|
|
@@ -15638,6 +15138,8 @@ function CanvasImpl({
|
|
|
15638
15138
|
performance,
|
|
15639
15139
|
raycaster,
|
|
15640
15140
|
camera,
|
|
15141
|
+
autoUpdateFrustum,
|
|
15142
|
+
occlusion,
|
|
15641
15143
|
size: effectiveSize,
|
|
15642
15144
|
// Store size props for reset functionality
|
|
15643
15145
|
_sizeProps: width !== void 0 || height !== void 0 ? { width, height } : null,
|
|
@@ -15662,8 +15164,20 @@ function CanvasImpl({
|
|
|
15662
15164
|
}
|
|
15663
15165
|
onCreated?.(state);
|
|
15664
15166
|
}
|
|
15665
|
-
})
|
|
15666
|
-
|
|
15167
|
+
}).then(
|
|
15168
|
+
() => true,
|
|
15169
|
+
// Renderer setup failed (e.g. no WebGL/WebGPU support). The `fallback` prop lives
|
|
15170
|
+
// inside <canvas>, which browsers don't display, so surface it as visible DOM
|
|
15171
|
+
// instead; with no fallback, rethrow to an external error boundary. (#3757)
|
|
15172
|
+
(setupError) => {
|
|
15173
|
+
if (effectActiveRef.current) {
|
|
15174
|
+
if (fallback != null) setFallbackVisible(true);
|
|
15175
|
+
else setError(setupError);
|
|
15176
|
+
}
|
|
15177
|
+
return false;
|
|
15178
|
+
}
|
|
15179
|
+
);
|
|
15180
|
+
if (!configured || !effectActiveRef.current || !root.current) return;
|
|
15667
15181
|
root.current.render(
|
|
15668
15182
|
/* @__PURE__ */ jsx(Bridge, { children: /* @__PURE__ */ jsx(ErrorBoundary, { set: setError, children: /* @__PURE__ */ jsxs(React.Suspense, { fallback: /* @__PURE__ */ jsx(Block, { set: setBlock }), children: [
|
|
15669
15183
|
backgroundProps && /* @__PURE__ */ jsx(Environment, { ...backgroundProps }),
|
|
@@ -15697,20 +15211,13 @@ function CanvasImpl({
|
|
|
15697
15211
|
const handleHMR = () => {
|
|
15698
15212
|
queueMicrotask(() => {
|
|
15699
15213
|
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
|
-
}
|
|
15214
|
+
if (rootEntry?.store) clearHmrCaches(rootEntry.store);
|
|
15707
15215
|
});
|
|
15708
15216
|
};
|
|
15709
15217
|
if (typeof import.meta !== "undefined" && import.meta.hot) {
|
|
15710
15218
|
const hot = import.meta.hot;
|
|
15711
15219
|
hot.on("vite:afterUpdate", handleHMR);
|
|
15712
|
-
return () => hot.
|
|
15713
|
-
});
|
|
15220
|
+
return () => hot.off?.("vite:afterUpdate", handleHMR);
|
|
15714
15221
|
}
|
|
15715
15222
|
if (typeof module !== "undefined" && module.hot) {
|
|
15716
15223
|
const hot = module.hot;
|
|
@@ -15733,7 +15240,20 @@ function CanvasImpl({
|
|
|
15733
15240
|
...style
|
|
15734
15241
|
},
|
|
15735
15242
|
...props,
|
|
15736
|
-
children:
|
|
15243
|
+
children: fallbackVisible ? (
|
|
15244
|
+
// Renderer setup failed: render the fallback as visible DOM. Inside <canvas> (below)
|
|
15245
|
+
// it exists in the tree but browsers never paint it, which is the whole bug (#3757).
|
|
15246
|
+
fallback
|
|
15247
|
+
) : /* @__PURE__ */ jsx("div", { ref: containerRef, className: "r3f-canvas-container", style: { width: "100%", height: "100%" }, children: /* @__PURE__ */ jsx(
|
|
15248
|
+
"canvas",
|
|
15249
|
+
{
|
|
15250
|
+
ref: canvasRef,
|
|
15251
|
+
id,
|
|
15252
|
+
className: "r3f-canvas",
|
|
15253
|
+
style: { display: "block", width: "100%", height: "100%" },
|
|
15254
|
+
children: fallback
|
|
15255
|
+
}
|
|
15256
|
+
) })
|
|
15737
15257
|
}
|
|
15738
15258
|
);
|
|
15739
15259
|
}
|
|
@@ -15806,23 +15326,50 @@ let ScopedStore = _ScopedStore;
|
|
|
15806
15326
|
function createScopedStore(data) {
|
|
15807
15327
|
return new ScopedStore(data);
|
|
15808
15328
|
}
|
|
15809
|
-
function createLazyCreatorState(state) {
|
|
15329
|
+
function createLazyCreatorState(state, store) {
|
|
15810
15330
|
let _uniforms = null;
|
|
15811
15331
|
let _nodes = null;
|
|
15332
|
+
let _buffers = null;
|
|
15333
|
+
let _gpuStorage = null;
|
|
15334
|
+
const view = (kind) => store ? withStagedOverlay(store, kind, state[kind]) : state[kind];
|
|
15812
15335
|
return Object.create(state, {
|
|
15813
15336
|
uniforms: {
|
|
15814
15337
|
get() {
|
|
15815
|
-
return _uniforms ?? (_uniforms = createScopedStore(
|
|
15338
|
+
return _uniforms ?? (_uniforms = createScopedStore(view("uniforms")));
|
|
15816
15339
|
}
|
|
15817
15340
|
},
|
|
15818
15341
|
nodes: {
|
|
15819
15342
|
get() {
|
|
15820
|
-
return _nodes ?? (_nodes = createScopedStore(
|
|
15343
|
+
return _nodes ?? (_nodes = createScopedStore(view("nodes")));
|
|
15344
|
+
}
|
|
15345
|
+
},
|
|
15346
|
+
buffers: {
|
|
15347
|
+
get() {
|
|
15348
|
+
return _buffers ?? (_buffers = createScopedStore(view("buffers")));
|
|
15349
|
+
}
|
|
15350
|
+
},
|
|
15351
|
+
gpuStorage: {
|
|
15352
|
+
get() {
|
|
15353
|
+
return _gpuStorage ?? (_gpuStorage = createScopedStore(view("gpuStorage")));
|
|
15821
15354
|
}
|
|
15822
15355
|
}
|
|
15823
15356
|
});
|
|
15824
15357
|
}
|
|
15825
15358
|
|
|
15359
|
+
function resolvePrimary(local) {
|
|
15360
|
+
return local.getState().primaryStore ?? local;
|
|
15361
|
+
}
|
|
15362
|
+
function usePrimaryStore() {
|
|
15363
|
+
const local = useStore();
|
|
15364
|
+
const primary = local.getState().primaryStore;
|
|
15365
|
+
return useMemo(() => primary ?? local, [primary, local]);
|
|
15366
|
+
}
|
|
15367
|
+
function usePrimaryThree(selector = (state) => state, equalityFn) {
|
|
15368
|
+
const local = useStore();
|
|
15369
|
+
const primary = resolvePrimary(local);
|
|
15370
|
+
return primary(selector, equalityFn);
|
|
15371
|
+
}
|
|
15372
|
+
|
|
15826
15373
|
function addTexture(set, key, value) {
|
|
15827
15374
|
set((state) => {
|
|
15828
15375
|
const newMap = new Map(state.textures);
|
|
@@ -15901,6 +15448,10 @@ function vectorize(inObject) {
|
|
|
15901
15448
|
if (obj.isVector2 || obj.isVector3 || obj.isVector4) return inObject;
|
|
15902
15449
|
if (obj.isMatrix3 || obj.isMatrix4) return inObject;
|
|
15903
15450
|
if (obj.isColor || obj.isEuler || obj.isQuaternion || obj.isSpherical) return inObject;
|
|
15451
|
+
if ("r" in obj && "g" in obj && "b" in obj && typeof obj.r === "number" && typeof obj.g === "number" && typeof obj.b === "number") {
|
|
15452
|
+
const scale = obj.r > 1 || obj.g > 1 || obj.b > 1 ? 1 / 255 : 1;
|
|
15453
|
+
return new Color(obj.r * scale, obj.g * scale, obj.b * scale);
|
|
15454
|
+
}
|
|
15904
15455
|
if ("x" in obj && "y" in obj && typeof obj.x === "number" && typeof obj.y === "number") {
|
|
15905
15456
|
if ("w" in obj && typeof obj.w === "number" && "z" in obj && typeof obj.z === "number") {
|
|
15906
15457
|
return new Vector4(obj.x, obj.y, obj.z, obj.w);
|
|
@@ -15922,65 +15473,56 @@ function useCompareMemoize(value, deep) {
|
|
|
15922
15473
|
return ref.current;
|
|
15923
15474
|
}
|
|
15924
15475
|
|
|
15476
|
+
function useScopedResource(options) {
|
|
15477
|
+
const { store, kind, scope, isLeaf, create, prepare, reconcile, input } = options;
|
|
15478
|
+
const scopeKey = scope ?? ROOT_SCOPE;
|
|
15479
|
+
const version = store((s) => s._hmrVersion);
|
|
15480
|
+
const entries = useMemo(() => {
|
|
15481
|
+
const staged = peekStaged(store, kind, scopeKey);
|
|
15482
|
+
const reusable = isScopeValid(store, kind, scopeKey);
|
|
15483
|
+
const map = store.getState()[kind];
|
|
15484
|
+
const committed = reusable ? readCommittedScope(map, scopeKey, isLeaf) : void 0;
|
|
15485
|
+
const result = {};
|
|
15486
|
+
let fresh = null;
|
|
15487
|
+
for (const [name, candidate] of Object.entries(create())) {
|
|
15488
|
+
const existing = staged?.[name] ?? committed?.[name];
|
|
15489
|
+
if (existing !== void 0) {
|
|
15490
|
+
result[name] = existing;
|
|
15491
|
+
reconcile?.(existing, candidate);
|
|
15492
|
+
} else {
|
|
15493
|
+
const value = prepare ? prepare(name, candidate) : candidate;
|
|
15494
|
+
result[name] = value;
|
|
15495
|
+
(fresh ?? (fresh = {}))[name] = value;
|
|
15496
|
+
}
|
|
15497
|
+
}
|
|
15498
|
+
if (fresh) stageEntries(store, kind, scopeKey, fresh);
|
|
15499
|
+
return result;
|
|
15500
|
+
}, [store, kind, scopeKey, version, input]);
|
|
15501
|
+
useIsomorphicLayoutEffect(() => {
|
|
15502
|
+
flushStagedScope(store, kind, scopeKey, isLeaf);
|
|
15503
|
+
});
|
|
15504
|
+
return entries;
|
|
15505
|
+
}
|
|
15506
|
+
|
|
15925
15507
|
const isUniformNode$1 = (value) => value !== null && typeof value === "object" && "value" in value && "uuid" in value;
|
|
15926
15508
|
function useUniforms(creatorOrScope, scope) {
|
|
15927
|
-
const store =
|
|
15509
|
+
const store = usePrimaryStore();
|
|
15928
15510
|
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
|
-
},
|
|
15511
|
+
(names, targetScope) => removeResourceEntries(store, "uniforms", Array.isArray(names) ? names : [names], targetScope, isUniformNode$1),
|
|
15942
15512
|
[store]
|
|
15943
15513
|
);
|
|
15944
15514
|
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
|
-
},
|
|
15515
|
+
(targetScope) => clearResourceEntries(store, "uniforms", targetScope, isUniformNode$1),
|
|
15961
15516
|
[store]
|
|
15962
15517
|
);
|
|
15963
15518
|
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
|
-
},
|
|
15519
|
+
(targetScope) => rebuildResource(store, "uniforms", targetScope),
|
|
15978
15520
|
[store]
|
|
15979
15521
|
);
|
|
15980
15522
|
const inputForMemoization = useMemo(() => {
|
|
15981
15523
|
let raw = creatorOrScope;
|
|
15982
15524
|
if (is.fun(creatorOrScope)) {
|
|
15983
|
-
const wrappedState = createLazyCreatorState(store.getState());
|
|
15525
|
+
const wrappedState = createLazyCreatorState(store.getState(), store);
|
|
15984
15526
|
raw = creatorOrScope(wrappedState);
|
|
15985
15527
|
}
|
|
15986
15528
|
if (raw && typeof raw === "object" && !Array.isArray(raw)) {
|
|
@@ -15994,80 +15536,44 @@ function useUniforms(creatorOrScope, scope) {
|
|
|
15994
15536
|
}, [creatorOrScope, store]);
|
|
15995
15537
|
const memoizedInput = useCompareMemoize(inputForMemoization);
|
|
15996
15538
|
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
|
-
}
|
|
15539
|
+
const storeUniforms = usePrimaryThree((s) => s.uniforms);
|
|
15540
|
+
const created = useScopedResource({
|
|
15541
|
+
store,
|
|
15542
|
+
kind: "uniforms",
|
|
15543
|
+
scope: isReader ? void 0 : scope,
|
|
15544
|
+
isLeaf: isUniformNode$1,
|
|
15545
|
+
input: memoizedInput,
|
|
15546
|
+
create: () => {
|
|
15547
|
+
if (isReader) return {};
|
|
15548
|
+
if (typeof memoizedInput !== "object" || memoizedInput === null) throw new Error("Invalid uniform input");
|
|
15549
|
+
return memoizedInput;
|
|
15550
|
+
},
|
|
15551
|
+
prepare: (name, candidate) => createUniform(name, candidate, scope),
|
|
15552
|
+
reconcile: (existing, candidate) => {
|
|
15553
|
+
const existingVal = existing.value;
|
|
15554
|
+
const newVal = vectorize(candidate);
|
|
15555
|
+
let equals = newVal === existingVal;
|
|
15556
|
+
if (!equals && hasEqualsMethod(existingVal) && hasEqualsMethod(newVal)) {
|
|
15557
|
+
if (isThreeVector(existingVal) && isThreeVector(newVal)) {
|
|
15558
|
+
equals = vectorEquals(existingVal, newVal);
|
|
15559
|
+
} else if (isSameThreeType(existingVal, newVal)) {
|
|
15560
|
+
equals = existingVal.equals(newVal);
|
|
16035
15561
|
}
|
|
16036
|
-
if (!equals) result[name].value = newVal;
|
|
16037
|
-
} else {
|
|
16038
|
-
result[name] = createUniform(name, node, scope);
|
|
16039
|
-
hasNewUniforms = true;
|
|
16040
15562
|
}
|
|
15563
|
+
if (!equals) existing.value = newVal;
|
|
16041
15564
|
}
|
|
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]);
|
|
15565
|
+
});
|
|
15566
|
+
let uniforms = created;
|
|
15567
|
+
if (memoizedInput === void 0) {
|
|
15568
|
+
uniforms = storeUniforms;
|
|
15569
|
+
} else if (typeof memoizedInput === "string") {
|
|
15570
|
+
const scopeData = storeUniforms[memoizedInput];
|
|
15571
|
+
uniforms = scopeData && !isUniformNode$1(scopeData) ? scopeData : {};
|
|
15572
|
+
}
|
|
16056
15573
|
return { ...uniforms, removeUniforms: removeUniforms2, clearUniforms, rebuildUniforms };
|
|
16057
15574
|
}
|
|
16058
15575
|
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
|
-
});
|
|
15576
|
+
rebuildResource(store, "uniforms", scope);
|
|
16071
15577
|
}
|
|
16072
15578
|
function removeUniforms(set, names, scope) {
|
|
16073
15579
|
set((state) => {
|
|
@@ -16131,191 +15637,91 @@ function isSameThreeType(a, b) {
|
|
|
16131
15637
|
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
15638
|
}
|
|
16133
15639
|
|
|
15640
|
+
const uniformOf = uniform;
|
|
16134
15641
|
const isUniformNode = (value) => value !== null && typeof value === "object" && "value" in value && "uuid" in value;
|
|
16135
15642
|
const isTSLNode$1 = (value) => value !== null && typeof value === "object" && "uuid" in value && "nodeType" in value;
|
|
16136
15643
|
function useUniform(name, value) {
|
|
16137
15644
|
const store = useStore();
|
|
16138
|
-
const
|
|
16139
|
-
|
|
16140
|
-
|
|
16141
|
-
|
|
16142
|
-
|
|
16143
|
-
|
|
15645
|
+
const registered = useScopedResource({
|
|
15646
|
+
store,
|
|
15647
|
+
kind: "uniforms",
|
|
15648
|
+
scope: void 0,
|
|
15649
|
+
isLeaf: isUniformNode,
|
|
15650
|
+
input: name,
|
|
15651
|
+
create: () => value === void 0 ? {} : { [name]: value },
|
|
15652
|
+
prepare: createNamedUniform,
|
|
15653
|
+
reconcile: (existing2) => {
|
|
16144
15654
|
if (value !== void 0 && !isTSLNode$1(value) && !isUniformNode(value)) {
|
|
16145
|
-
|
|
15655
|
+
existing2.value = typeof value === "string" ? new Color(value) : value;
|
|
16146
15656
|
}
|
|
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
15657
|
}
|
|
16164
|
-
|
|
16165
|
-
|
|
16166
|
-
|
|
16167
|
-
|
|
16168
|
-
|
|
16169
|
-
}
|
|
16170
|
-
|
|
16171
|
-
|
|
16172
|
-
|
|
16173
|
-
|
|
16174
|
-
|
|
16175
|
-
|
|
16176
|
-
|
|
16177
|
-
|
|
16178
|
-
|
|
16179
|
-
|
|
16180
|
-
|
|
16181
|
-
|
|
16182
|
-
}
|
|
15658
|
+
});
|
|
15659
|
+
if (registered[name]) return registered[name];
|
|
15660
|
+
const existing = peekStaged(store, "uniforms", ROOT_SCOPE)?.[name] ?? store.getState().uniforms[name];
|
|
15661
|
+
if (existing && isUniformNode(existing)) return existing;
|
|
15662
|
+
throw new Error(
|
|
15663
|
+
`[useUniform] Uniform "${name}" not found. Create it first with: useUniform('${name}', initialValue)`
|
|
15664
|
+
);
|
|
15665
|
+
}
|
|
15666
|
+
function createNamedUniform(name, value) {
|
|
15667
|
+
let node;
|
|
15668
|
+
if (isUniformNode(value)) {
|
|
15669
|
+
node = value;
|
|
15670
|
+
} else if (isTSLNode$1(value)) {
|
|
15671
|
+
node = uniformOf(value);
|
|
15672
|
+
} else if (typeof value === "string") {
|
|
15673
|
+
node = uniform(new Color(value));
|
|
15674
|
+
} else {
|
|
15675
|
+
node = uniformOf(value);
|
|
15676
|
+
}
|
|
15677
|
+
if (typeof node.setName === "function") {
|
|
15678
|
+
node.setName(name);
|
|
15679
|
+
}
|
|
15680
|
+
return node;
|
|
16183
15681
|
}
|
|
16184
15682
|
|
|
16185
15683
|
const isTSLNode = (value) => value !== null && typeof value === "object" && ("uuid" in value || "nodeType" in value);
|
|
16186
15684
|
function useNodes(creatorOrScope, scope) {
|
|
16187
|
-
const store =
|
|
15685
|
+
const store = usePrimaryStore();
|
|
16188
15686
|
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
|
-
},
|
|
15687
|
+
(names, targetScope) => removeResourceEntries(store, "nodes", Array.isArray(names) ? names : [names], targetScope, isTSLNode),
|
|
16202
15688
|
[store]
|
|
16203
15689
|
);
|
|
16204
15690
|
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
|
-
},
|
|
15691
|
+
(targetScope) => clearResourceEntries(store, "nodes", targetScope, isTSLNode),
|
|
16221
15692
|
[store]
|
|
16222
15693
|
);
|
|
16223
15694
|
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
|
-
},
|
|
15695
|
+
(targetScope) => rebuildResource(store, "nodes", targetScope),
|
|
16241
15696
|
[store]
|
|
16242
15697
|
);
|
|
16243
15698
|
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 } }));
|
|
15699
|
+
const storeNodes = usePrimaryThree((s) => s.nodes);
|
|
15700
|
+
const created = useScopedResource({
|
|
15701
|
+
store,
|
|
15702
|
+
kind: "nodes",
|
|
15703
|
+
scope: isReader ? void 0 : scope,
|
|
15704
|
+
isLeaf: isTSLNode,
|
|
15705
|
+
create: () => {
|
|
15706
|
+
if (isReader) return {};
|
|
15707
|
+
return creatorOrScope(createLazyCreatorState(store.getState(), store));
|
|
15708
|
+
},
|
|
15709
|
+
prepare: (name, node) => {
|
|
15710
|
+
node.setName?.(scope ? `${scope}.${name}` : name);
|
|
15711
|
+
return node;
|
|
16298
15712
|
}
|
|
16299
|
-
|
|
16300
|
-
|
|
15713
|
+
});
|
|
15714
|
+
let nodes = created;
|
|
15715
|
+
if (creatorOrScope === void 0) {
|
|
15716
|
+
nodes = storeNodes;
|
|
15717
|
+
} else if (typeof creatorOrScope === "string") {
|
|
15718
|
+
const scopeData = storeNodes[creatorOrScope];
|
|
15719
|
+
nodes = scopeData && !isTSLNode(scopeData) ? scopeData : {};
|
|
15720
|
+
}
|
|
16301
15721
|
return { ...nodes, removeNodes: removeNodes2, clearNodes, rebuildNodes };
|
|
16302
15722
|
}
|
|
16303
15723
|
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
|
-
});
|
|
15724
|
+
rebuildResource(store, "nodes", scope);
|
|
16319
15725
|
}
|
|
16320
15726
|
function removeNodes(set, names, scope) {
|
|
16321
15727
|
set((state) => {
|
|
@@ -16345,18 +15751,172 @@ function clearRootNodes(set) {
|
|
|
16345
15751
|
});
|
|
16346
15752
|
}
|
|
16347
15753
|
function useLocalNodes(creator) {
|
|
16348
|
-
const store =
|
|
16349
|
-
const uniforms =
|
|
16350
|
-
const nodes =
|
|
16351
|
-
const textures =
|
|
16352
|
-
const hmrVersion =
|
|
15754
|
+
const store = usePrimaryStore();
|
|
15755
|
+
const uniforms = usePrimaryThree((s) => s.uniforms);
|
|
15756
|
+
const nodes = usePrimaryThree((s) => s.nodes);
|
|
15757
|
+
const textures = usePrimaryThree((s) => s.textures);
|
|
15758
|
+
const hmrVersion = usePrimaryThree((s) => s._hmrVersion);
|
|
16353
15759
|
return useMemo(() => {
|
|
16354
|
-
const wrappedState = createLazyCreatorState(store.getState());
|
|
15760
|
+
const wrappedState = createLazyCreatorState(store.getState(), store);
|
|
16355
15761
|
return creator(wrappedState);
|
|
16356
15762
|
}, [store, creator, uniforms, nodes, textures, hmrVersion]);
|
|
16357
15763
|
}
|
|
16358
15764
|
|
|
16359
|
-
|
|
15765
|
+
const isBufferLike = (value) => {
|
|
15766
|
+
if (value === null || typeof value !== "object") return false;
|
|
15767
|
+
if (ArrayBuffer.isView(value)) return true;
|
|
15768
|
+
if ("isBufferAttribute" in value) return true;
|
|
15769
|
+
if ("uuid" in value || "nodeType" in value) return true;
|
|
15770
|
+
return false;
|
|
15771
|
+
};
|
|
15772
|
+
const disposeBuffer = (buffer) => {
|
|
15773
|
+
if (buffer === null || typeof buffer !== "object") return;
|
|
15774
|
+
if ("dispose" in buffer && typeof buffer.dispose === "function") {
|
|
15775
|
+
buffer.dispose();
|
|
15776
|
+
}
|
|
15777
|
+
};
|
|
15778
|
+
function useBuffers(creatorOrScope, scope) {
|
|
15779
|
+
const store = usePrimaryStore();
|
|
15780
|
+
const removeBuffers = useCallback(
|
|
15781
|
+
(names, targetScope) => removeResourceEntries(store, "buffers", Array.isArray(names) ? names : [names], targetScope, isBufferLike),
|
|
15782
|
+
[store]
|
|
15783
|
+
);
|
|
15784
|
+
const clearBuffers = useCallback(
|
|
15785
|
+
(targetScope) => clearResourceEntries(store, "buffers", targetScope, isBufferLike),
|
|
15786
|
+
[store]
|
|
15787
|
+
);
|
|
15788
|
+
const rebuildBuffers = useCallback(
|
|
15789
|
+
(targetScope) => rebuildResource(store, "buffers", targetScope),
|
|
15790
|
+
[store]
|
|
15791
|
+
);
|
|
15792
|
+
const disposeBuffers = useCallback(
|
|
15793
|
+
(names, targetScope) => {
|
|
15794
|
+
const nameArray = Array.isArray(names) ? names : [names];
|
|
15795
|
+
const state = store.getState();
|
|
15796
|
+
for (const name of nameArray) {
|
|
15797
|
+
const buffer = targetScope ? state.buffers[targetScope]?.[name] : state.buffers[name];
|
|
15798
|
+
if (buffer && isBufferLike(buffer)) {
|
|
15799
|
+
disposeBuffer(buffer);
|
|
15800
|
+
}
|
|
15801
|
+
}
|
|
15802
|
+
removeBuffers(names, targetScope);
|
|
15803
|
+
},
|
|
15804
|
+
[store, removeBuffers]
|
|
15805
|
+
);
|
|
15806
|
+
const isReader = creatorOrScope === void 0 || typeof creatorOrScope === "string";
|
|
15807
|
+
const storeBuffers = usePrimaryThree((s) => s.buffers);
|
|
15808
|
+
const created = useScopedResource({
|
|
15809
|
+
store,
|
|
15810
|
+
kind: "buffers",
|
|
15811
|
+
scope: isReader ? void 0 : scope,
|
|
15812
|
+
isLeaf: isBufferLike,
|
|
15813
|
+
create: () => {
|
|
15814
|
+
if (isReader) return {};
|
|
15815
|
+
return creatorOrScope(createLazyCreatorState(store.getState(), store));
|
|
15816
|
+
},
|
|
15817
|
+
prepare: (name, buffer) => {
|
|
15818
|
+
if ("setName" in buffer && typeof buffer.setName === "function") {
|
|
15819
|
+
buffer.setName(scope ? `${scope}.${name}` : name);
|
|
15820
|
+
}
|
|
15821
|
+
return buffer;
|
|
15822
|
+
}
|
|
15823
|
+
});
|
|
15824
|
+
let buffers = created;
|
|
15825
|
+
if (creatorOrScope === void 0) {
|
|
15826
|
+
buffers = storeBuffers;
|
|
15827
|
+
} else if (typeof creatorOrScope === "string") {
|
|
15828
|
+
const scopeData = storeBuffers[creatorOrScope];
|
|
15829
|
+
buffers = scopeData && !isBufferLike(scopeData) ? scopeData : {};
|
|
15830
|
+
}
|
|
15831
|
+
return { ...buffers, removeBuffers, clearBuffers, rebuildBuffers, disposeBuffers };
|
|
15832
|
+
}
|
|
15833
|
+
function rebuildAllBuffers(store, scope) {
|
|
15834
|
+
rebuildResource(store, "buffers", scope);
|
|
15835
|
+
}
|
|
15836
|
+
|
|
15837
|
+
const isStorageLike = (value) => {
|
|
15838
|
+
if (value === null || typeof value !== "object") return false;
|
|
15839
|
+
if ("isTexture" in value) return true;
|
|
15840
|
+
if ("isData3DTexture" in value) return true;
|
|
15841
|
+
if ("uuid" in value || "nodeType" in value) return true;
|
|
15842
|
+
return false;
|
|
15843
|
+
};
|
|
15844
|
+
const disposeStorage = (storage) => {
|
|
15845
|
+
if (storage === null || typeof storage !== "object") return;
|
|
15846
|
+
if ("dispose" in storage && typeof storage.dispose === "function") {
|
|
15847
|
+
storage.dispose();
|
|
15848
|
+
}
|
|
15849
|
+
};
|
|
15850
|
+
function useGPUStorage(creatorOrScope, scope) {
|
|
15851
|
+
const store = usePrimaryStore();
|
|
15852
|
+
const removeStorage = useCallback(
|
|
15853
|
+
(names, targetScope) => removeResourceEntries(store, "gpuStorage", Array.isArray(names) ? names : [names], targetScope, isStorageLike),
|
|
15854
|
+
[store]
|
|
15855
|
+
);
|
|
15856
|
+
const clearStorage = useCallback(
|
|
15857
|
+
(targetScope) => clearResourceEntries(store, "gpuStorage", targetScope, isStorageLike),
|
|
15858
|
+
[store]
|
|
15859
|
+
);
|
|
15860
|
+
const rebuildStorage = useCallback(
|
|
15861
|
+
(targetScope) => rebuildResource(store, "gpuStorage", targetScope),
|
|
15862
|
+
[store]
|
|
15863
|
+
);
|
|
15864
|
+
const disposeStorageFn = useCallback(
|
|
15865
|
+
(names, targetScope) => {
|
|
15866
|
+
const nameArray = Array.isArray(names) ? names : [names];
|
|
15867
|
+
const state = store.getState();
|
|
15868
|
+
for (const name of nameArray) {
|
|
15869
|
+
const storage = targetScope ? state.gpuStorage[targetScope]?.[name] : state.gpuStorage[name];
|
|
15870
|
+
if (storage && isStorageLike(storage)) {
|
|
15871
|
+
disposeStorage(storage);
|
|
15872
|
+
}
|
|
15873
|
+
}
|
|
15874
|
+
removeStorage(names, targetScope);
|
|
15875
|
+
},
|
|
15876
|
+
[store, removeStorage]
|
|
15877
|
+
);
|
|
15878
|
+
const isReader = creatorOrScope === void 0 || typeof creatorOrScope === "string";
|
|
15879
|
+
const storeStorage = usePrimaryThree((s) => s.gpuStorage);
|
|
15880
|
+
const created = useScopedResource({
|
|
15881
|
+
store,
|
|
15882
|
+
kind: "gpuStorage",
|
|
15883
|
+
scope: isReader ? void 0 : scope,
|
|
15884
|
+
isLeaf: isStorageLike,
|
|
15885
|
+
create: () => {
|
|
15886
|
+
if (isReader) return {};
|
|
15887
|
+
return creatorOrScope(createLazyCreatorState(store.getState(), store));
|
|
15888
|
+
},
|
|
15889
|
+
prepare: (name, storage) => {
|
|
15890
|
+
const label = scope ? `${scope}.${name}` : name;
|
|
15891
|
+
if ("setName" in storage && typeof storage.setName === "function") {
|
|
15892
|
+
storage.setName(label);
|
|
15893
|
+
}
|
|
15894
|
+
if ("name" in storage && typeof storage.name === "string") {
|
|
15895
|
+
storage.name = label;
|
|
15896
|
+
}
|
|
15897
|
+
return storage;
|
|
15898
|
+
}
|
|
15899
|
+
});
|
|
15900
|
+
let gpuStorage = created;
|
|
15901
|
+
if (creatorOrScope === void 0) {
|
|
15902
|
+
gpuStorage = storeStorage;
|
|
15903
|
+
} else if (typeof creatorOrScope === "string") {
|
|
15904
|
+
const scopeData = storeStorage[creatorOrScope];
|
|
15905
|
+
gpuStorage = scopeData && !isStorageLike(scopeData) ? scopeData : {};
|
|
15906
|
+
}
|
|
15907
|
+
return {
|
|
15908
|
+
...gpuStorage,
|
|
15909
|
+
removeStorage,
|
|
15910
|
+
clearStorage,
|
|
15911
|
+
rebuildStorage,
|
|
15912
|
+
disposeStorage: disposeStorageFn
|
|
15913
|
+
};
|
|
15914
|
+
}
|
|
15915
|
+
function rebuildAllStorage(store, scope) {
|
|
15916
|
+
rebuildResource(store, "gpuStorage", scope);
|
|
15917
|
+
}
|
|
15918
|
+
|
|
15919
|
+
function useRenderPipeline(mainCB, setupCB) {
|
|
16360
15920
|
const store = useStore();
|
|
16361
15921
|
const { scene, camera, renderer, isLegacy } = useThree();
|
|
16362
15922
|
const callbacksRanRef = useRef(false);
|
|
@@ -16375,7 +15935,7 @@ function usePostProcessing(mainCB, setupCB) {
|
|
|
16375
15935
|
}, [store]);
|
|
16376
15936
|
const reset = useCallback(() => {
|
|
16377
15937
|
store.setState({
|
|
16378
|
-
|
|
15938
|
+
renderPipeline: null,
|
|
16379
15939
|
passes: {}
|
|
16380
15940
|
});
|
|
16381
15941
|
callbacksRanRef.current = false;
|
|
@@ -16388,18 +15948,18 @@ function usePostProcessing(mainCB, setupCB) {
|
|
|
16388
15948
|
}, []);
|
|
16389
15949
|
useLayoutEffect(() => {
|
|
16390
15950
|
if (isLegacy) {
|
|
16391
|
-
throw new Error("
|
|
15951
|
+
throw new Error("useRenderPipeline is only available with WebGPU renderer. Set renderer prop on Canvas.");
|
|
16392
15952
|
}
|
|
16393
15953
|
if (!renderer || !scene || !camera) return;
|
|
16394
15954
|
const state = store.getState();
|
|
16395
15955
|
const set = store.setState;
|
|
16396
15956
|
try {
|
|
16397
|
-
let
|
|
15957
|
+
let pipeline = state.renderPipeline;
|
|
16398
15958
|
let currentPasses = { ...state.passes };
|
|
16399
|
-
let
|
|
16400
|
-
if (!
|
|
16401
|
-
|
|
16402
|
-
|
|
15959
|
+
let justCreatedPipeline = false;
|
|
15960
|
+
if (!pipeline) {
|
|
15961
|
+
pipeline = new RenderPipeline(renderer);
|
|
15962
|
+
justCreatedPipeline = true;
|
|
16403
15963
|
}
|
|
16404
15964
|
const cacheValid = scenePassCacheRef.current && scenePassCacheRef.current.sceneUuid === scene.uuid && scenePassCacheRef.current.cameraUuid === camera.uuid;
|
|
16405
15965
|
let scenePass;
|
|
@@ -16410,9 +15970,9 @@ function usePostProcessing(mainCB, setupCB) {
|
|
|
16410
15970
|
scenePassCacheRef.current = { sceneUuid: scene.uuid, cameraUuid: camera.uuid, scenePass };
|
|
16411
15971
|
}
|
|
16412
15972
|
currentPasses.scenePass = scenePass;
|
|
16413
|
-
if (!
|
|
16414
|
-
set({
|
|
16415
|
-
const shouldRunCallbacks =
|
|
15973
|
+
if (!pipeline.outputNode || justCreatedPipeline) pipeline.outputNode = scenePass;
|
|
15974
|
+
set({ renderPipeline: pipeline, passes: currentPasses });
|
|
15975
|
+
const shouldRunCallbacks = justCreatedPipeline || !callbacksRanRef.current || !cacheValid;
|
|
16416
15976
|
if (shouldRunCallbacks) {
|
|
16417
15977
|
if (setupCBRef.current) {
|
|
16418
15978
|
const freshState = store.getState();
|
|
@@ -16433,22 +15993,22 @@ function usePostProcessing(mainCB, setupCB) {
|
|
|
16433
15993
|
callbacksRanRef.current = true;
|
|
16434
15994
|
}
|
|
16435
15995
|
} catch (error) {
|
|
16436
|
-
console.
|
|
15996
|
+
console.warn("[useRenderPipeline] Setup failed; the render pipeline was not configured:", error);
|
|
16437
15997
|
}
|
|
16438
15998
|
}, [store, renderer, scene, camera, isLegacy, rebuildVersion]);
|
|
16439
15999
|
const passes = useThree((s) => s.passes);
|
|
16440
|
-
const
|
|
16000
|
+
const renderPipeline = useThree((s) => s.renderPipeline);
|
|
16441
16001
|
return {
|
|
16442
16002
|
passes,
|
|
16443
|
-
|
|
16003
|
+
renderPipeline,
|
|
16444
16004
|
clearPasses,
|
|
16445
16005
|
reset,
|
|
16446
16006
|
rebuild,
|
|
16447
|
-
// isReady indicates if
|
|
16448
|
-
isReady:
|
|
16007
|
+
// isReady indicates if RenderPipeline is configured and ready for rendering
|
|
16008
|
+
isReady: renderPipeline !== null
|
|
16449
16009
|
};
|
|
16450
16010
|
}
|
|
16451
16011
|
|
|
16452
16012
|
extend(THREE);
|
|
16453
16013
|
|
|
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,
|
|
16014
|
+
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, 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 };
|