@react-three/fiber 10.0.0-canary.dbbe704 → 10.0.0-canary.f046a54
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 +648 -1361
- package/dist/index.d.cts +249 -788
- package/dist/index.d.mts +249 -788
- package/dist/index.d.ts +249 -788
- package/dist/index.mjs +622 -1334
- package/dist/legacy.cjs +603 -1322
- package/dist/legacy.d.cts +249 -788
- package/dist/legacy.d.mts +249 -788
- package/dist/legacy.d.ts +249 -788
- package/dist/legacy.mjs +577 -1295
- package/dist/webgpu/index.cjs +1048 -1379
- package/dist/webgpu/index.d.cts +334 -801
- package/dist/webgpu/index.d.mts +334 -801
- package/dist/webgpu/index.d.ts +334 -801
- package/dist/webgpu/index.mjs +1017 -1351
- package/package.json +2 -1
- package/readme.md +2 -2
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, WebGLCubeRenderTarget, HalfFloatType, Color, Frustum, Matrix4, Group, BoxGeometry, MeshBasicNodeMaterial, Mesh, Node, NodeUpdateType, Layers, SRGBColorSpace, RGBAFormat, UnsignedByteType, Vector3, Vector2, TextureLoader, Texture as Texture$1, CanvasTarget, Raycaster, OrthographicCamera, PerspectiveCamera,
|
|
2
|
+
import { RenderTarget, CubeReflectionMapping, EquirectangularReflectionMapping, CubeTextureLoader, Scene, WebGLCubeRenderTarget, 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, PostProcessing } 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';
|
|
@@ -14,6 +14,8 @@ import { UltraHDRLoader } from 'three/examples/jsm/loaders/UltraHDRLoader.js';
|
|
|
14
14
|
import { GainMapLoader } from '@monogrid/gainmap-js';
|
|
15
15
|
import Tb, { unstable_scheduleCallback, unstable_IdlePriority } 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';
|
|
@@ -149,7 +151,7 @@ function useEnvironment({
|
|
|
149
151
|
useLoader$1.clear(loader, multiFile ? [files] : files);
|
|
150
152
|
}
|
|
151
153
|
renderer.domElement.addEventListener("webglcontextlost", clearGainmapTexture, { once: true });
|
|
152
|
-
}, [files, renderer.domElement]);
|
|
154
|
+
}, [extension, files, loader, multiFile, renderer.domElement]);
|
|
153
155
|
const loaderResult = useLoader$1(
|
|
154
156
|
loader,
|
|
155
157
|
multiFile ? [files] : files,
|
|
@@ -349,7 +351,22 @@ function EnvironmentPortal({
|
|
|
349
351
|
environmentIntensity,
|
|
350
352
|
environmentRotation
|
|
351
353
|
});
|
|
352
|
-
}, [
|
|
354
|
+
}, [
|
|
355
|
+
children,
|
|
356
|
+
virtualScene,
|
|
357
|
+
fbo.texture,
|
|
358
|
+
scene,
|
|
359
|
+
defaultScene,
|
|
360
|
+
background,
|
|
361
|
+
frames,
|
|
362
|
+
gl,
|
|
363
|
+
blur,
|
|
364
|
+
backgroundBlurriness,
|
|
365
|
+
backgroundIntensity,
|
|
366
|
+
backgroundRotation,
|
|
367
|
+
environmentIntensity,
|
|
368
|
+
environmentRotation
|
|
369
|
+
]);
|
|
353
370
|
let count = 1;
|
|
354
371
|
useFrame$1(() => {
|
|
355
372
|
if (frames === Infinity || count < frames) {
|
|
@@ -420,9 +437,9 @@ function Environment(props) {
|
|
|
420
437
|
return props.ground ? /* @__PURE__ */ jsx(EnvironmentGround, { ...props }) : props.map ? /* @__PURE__ */ jsx(EnvironmentMap, { ...props }) : props.children ? /* @__PURE__ */ jsx(EnvironmentPortal, { ...props }) : /* @__PURE__ */ jsx(EnvironmentCube, { ...props });
|
|
421
438
|
}
|
|
422
439
|
|
|
423
|
-
var __defProp$
|
|
424
|
-
var __defNormalProp$
|
|
425
|
-
var __publicField$
|
|
440
|
+
var __defProp$1 = Object.defineProperty;
|
|
441
|
+
var __defNormalProp$1 = (obj, key, value) => key in obj ? __defProp$1(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
442
|
+
var __publicField$1 = (obj, key, value) => __defNormalProp$1(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
426
443
|
const act = React["act"];
|
|
427
444
|
const useIsomorphicLayoutEffect = /* @__PURE__ */ (() => typeof window !== "undefined" && (window.document?.createElement || window.navigator?.product === "ReactNative"))() ? React.useLayoutEffect : React.useEffect;
|
|
428
445
|
function useMutableCallback(fn) {
|
|
@@ -454,7 +471,7 @@ const ErrorBoundary = /* @__PURE__ */ (() => {
|
|
|
454
471
|
return _a = class extends React.Component {
|
|
455
472
|
constructor() {
|
|
456
473
|
super(...arguments);
|
|
457
|
-
__publicField$
|
|
474
|
+
__publicField$1(this, "state", { error: false });
|
|
458
475
|
}
|
|
459
476
|
componentDidCatch(err) {
|
|
460
477
|
this.props.set(err);
|
|
@@ -462,7 +479,7 @@ const ErrorBoundary = /* @__PURE__ */ (() => {
|
|
|
462
479
|
render() {
|
|
463
480
|
return this.state.error ? null : this.props.children;
|
|
464
481
|
}
|
|
465
|
-
}, __publicField$
|
|
482
|
+
}, __publicField$1(_a, "getDerivedStateFromError", () => ({ error: true })), _a;
|
|
466
483
|
})();
|
|
467
484
|
|
|
468
485
|
const is = {
|
|
@@ -983,8 +1000,24 @@ function applyProps(object, props) {
|
|
|
983
1000
|
} else if (isVectorLike(target) && is.num(value)) {
|
|
984
1001
|
if ("setScalar" in target && typeof target.setScalar === "function") target.setScalar(value);
|
|
985
1002
|
else target.set(value);
|
|
1003
|
+
} else if (root instanceof ShaderMaterial && key === "uniforms" && is.obj(value)) {
|
|
1004
|
+
if (!is.obj(root.uniforms)) root.uniforms = {};
|
|
1005
|
+
const uniforms = root.uniforms;
|
|
1006
|
+
const nextUniforms = value;
|
|
1007
|
+
for (const name in nextUniforms) {
|
|
1008
|
+
const uniform = nextUniforms[name];
|
|
1009
|
+
const targetUniform = uniforms[name];
|
|
1010
|
+
if (targetUniform) Object.assign(targetUniform, uniform);
|
|
1011
|
+
else {
|
|
1012
|
+
const nextUniform = uniform instanceof Uniform ? uniform.clone() : new Uniform(uniform.value);
|
|
1013
|
+
uniforms[name] = nextUniform;
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
986
1016
|
} else {
|
|
987
1017
|
root[key] = value;
|
|
1018
|
+
if (key.endsWith("Node") && root.isMaterial) {
|
|
1019
|
+
root.needsUpdate = true;
|
|
1020
|
+
}
|
|
988
1021
|
if (rootState && rootState.renderer?.outputColorSpace === SRGBColorSpace && colorMaps.includes(key) && isTexture(value) && root[key]?.isTexture && // sRGB textures must be RGBA8 since r137 https://github.com/mrdoob/three.js/pull/23129
|
|
989
1022
|
root[key].format === RGBAFormat && root[key].type === UnsignedByteType) {
|
|
990
1023
|
root[key].colorSpace = rootState.textureColorSpace;
|
|
@@ -1018,38 +1051,60 @@ function applyProps(object, props) {
|
|
|
1018
1051
|
return object;
|
|
1019
1052
|
}
|
|
1020
1053
|
|
|
1054
|
+
const DEFAULT_POINTER_ID = 0;
|
|
1055
|
+
const XR_POINTER_ID_START = 1e3;
|
|
1056
|
+
function getPointerState(internal, pointerId) {
|
|
1057
|
+
let state = internal.pointerMap.get(pointerId);
|
|
1058
|
+
if (!state) {
|
|
1059
|
+
state = {
|
|
1060
|
+
hovered: /* @__PURE__ */ new Map(),
|
|
1061
|
+
captured: /* @__PURE__ */ new Map(),
|
|
1062
|
+
initialClick: [0, 0],
|
|
1063
|
+
initialHits: []
|
|
1064
|
+
};
|
|
1065
|
+
internal.pointerMap.set(pointerId, state);
|
|
1066
|
+
}
|
|
1067
|
+
return state;
|
|
1068
|
+
}
|
|
1069
|
+
function getPointerId(event) {
|
|
1070
|
+
return "pointerId" in event ? event.pointerId : DEFAULT_POINTER_ID;
|
|
1071
|
+
}
|
|
1021
1072
|
function makeId(event) {
|
|
1022
1073
|
return (event.eventObject || event.object).uuid + "/" + event.index + event.instanceId;
|
|
1023
1074
|
}
|
|
1024
|
-
function releaseInternalPointerCapture(
|
|
1025
|
-
const
|
|
1075
|
+
function releaseInternalPointerCapture(internal, obj, pointerId) {
|
|
1076
|
+
const pointerState = internal.pointerMap.get(pointerId);
|
|
1077
|
+
if (!pointerState) return;
|
|
1078
|
+
const captureData = pointerState.captured.get(obj);
|
|
1026
1079
|
if (captureData) {
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
capturedMap.delete(pointerId);
|
|
1030
|
-
captureData.target.releasePointerCapture(pointerId);
|
|
1031
|
-
}
|
|
1080
|
+
pointerState.captured.delete(obj);
|
|
1081
|
+
captureData.target.releasePointerCapture(pointerId);
|
|
1032
1082
|
}
|
|
1033
1083
|
}
|
|
1034
1084
|
function removeInteractivity(store, object) {
|
|
1035
1085
|
const { internal } = store.getState();
|
|
1036
1086
|
internal.interaction = internal.interaction.filter((o) => o !== object);
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1087
|
+
for (const [pointerId, pointerState] of internal.pointerMap) {
|
|
1088
|
+
pointerState.initialHits = pointerState.initialHits.filter((o) => o !== object);
|
|
1089
|
+
pointerState.hovered.forEach((value, key) => {
|
|
1090
|
+
if (value.eventObject === object || value.object === object) {
|
|
1091
|
+
pointerState.hovered.delete(key);
|
|
1092
|
+
}
|
|
1093
|
+
});
|
|
1094
|
+
if (pointerState.captured.has(object)) {
|
|
1095
|
+
releaseInternalPointerCapture(internal, object, pointerId);
|
|
1041
1096
|
}
|
|
1042
|
-
}
|
|
1043
|
-
internal.capturedMap.forEach((captures, pointerId) => {
|
|
1044
|
-
releaseInternalPointerCapture(internal.capturedMap, object, captures, pointerId);
|
|
1045
|
-
});
|
|
1097
|
+
}
|
|
1046
1098
|
unregisterVisibility(store, object);
|
|
1047
1099
|
}
|
|
1048
1100
|
function createEvents(store) {
|
|
1049
|
-
function calculateDistance(event) {
|
|
1101
|
+
function calculateDistance(event, pointerId) {
|
|
1050
1102
|
const { internal } = store.getState();
|
|
1051
|
-
const
|
|
1052
|
-
|
|
1103
|
+
const pointerState = internal.pointerMap.get(pointerId);
|
|
1104
|
+
if (!pointerState) return 0;
|
|
1105
|
+
const [initialX, initialY] = pointerState.initialClick;
|
|
1106
|
+
const dx = event.offsetX - initialX;
|
|
1107
|
+
const dy = event.offsetY - initialY;
|
|
1053
1108
|
return Math.round(Math.sqrt(dx * dx + dy * dy));
|
|
1054
1109
|
}
|
|
1055
1110
|
function filterPointerEvents(objects) {
|
|
@@ -1085,6 +1140,15 @@ function createEvents(store) {
|
|
|
1085
1140
|
return state2.raycaster.camera ? state2.raycaster.intersectObject(obj, true) : [];
|
|
1086
1141
|
}
|
|
1087
1142
|
let hits = eventsObjects.flatMap(handleRaycast).sort((a, b) => {
|
|
1143
|
+
const aInteractivePriority = a.object.userData?.interactivePriority;
|
|
1144
|
+
const bInteractivePriority = b.object.userData?.interactivePriority;
|
|
1145
|
+
if (aInteractivePriority !== void 0 || bInteractivePriority !== void 0) {
|
|
1146
|
+
if (aInteractivePriority !== void 0 && bInteractivePriority === void 0) return -1;
|
|
1147
|
+
if (bInteractivePriority !== void 0 && aInteractivePriority === void 0) return 1;
|
|
1148
|
+
if (aInteractivePriority !== bInteractivePriority) {
|
|
1149
|
+
return (bInteractivePriority ?? 0) - (aInteractivePriority ?? 0);
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1088
1152
|
const aState = getRootState(a.object);
|
|
1089
1153
|
const bState = getRootState(b.object);
|
|
1090
1154
|
const aPriority = aState?.events?.priority ?? 1;
|
|
@@ -1106,9 +1170,13 @@ function createEvents(store) {
|
|
|
1106
1170
|
eventObject = eventObject.parent;
|
|
1107
1171
|
}
|
|
1108
1172
|
}
|
|
1109
|
-
if ("pointerId" in event
|
|
1110
|
-
|
|
1111
|
-
|
|
1173
|
+
if ("pointerId" in event) {
|
|
1174
|
+
const pointerId = event.pointerId;
|
|
1175
|
+
const pointerState = state.internal.pointerMap.get(pointerId);
|
|
1176
|
+
if (pointerState?.captured.size) {
|
|
1177
|
+
for (const captureData of pointerState.captured.values()) {
|
|
1178
|
+
if (!duplicates.has(makeId(captureData.intersection))) intersections.push(captureData.intersection);
|
|
1179
|
+
}
|
|
1112
1180
|
}
|
|
1113
1181
|
}
|
|
1114
1182
|
return intersections;
|
|
@@ -1121,27 +1189,25 @@ function createEvents(store) {
|
|
|
1121
1189
|
if (state) {
|
|
1122
1190
|
const { raycaster, pointer, camera, internal } = state;
|
|
1123
1191
|
const unprojectedPoint = new Vector3(pointer.x, pointer.y, 0).unproject(camera);
|
|
1124
|
-
const hasPointerCapture = (id) =>
|
|
1192
|
+
const hasPointerCapture = (id) => {
|
|
1193
|
+
const pointerState = internal.pointerMap.get(id);
|
|
1194
|
+
return pointerState?.captured.has(hit.eventObject) ?? false;
|
|
1195
|
+
};
|
|
1125
1196
|
const setPointerCapture = (id) => {
|
|
1126
1197
|
const captureData = { intersection: hit, target: event.target };
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
} else {
|
|
1130
|
-
internal.capturedMap.set(id, /* @__PURE__ */ new Map([[hit.eventObject, captureData]]));
|
|
1131
|
-
}
|
|
1198
|
+
const pointerState = getPointerState(internal, id);
|
|
1199
|
+
pointerState.captured.set(hit.eventObject, captureData);
|
|
1132
1200
|
event.target.setPointerCapture(id);
|
|
1133
1201
|
};
|
|
1134
1202
|
const releasePointerCapture = (id) => {
|
|
1135
|
-
|
|
1136
|
-
if (captures) {
|
|
1137
|
-
releaseInternalPointerCapture(internal.capturedMap, hit.eventObject, captures, id);
|
|
1138
|
-
}
|
|
1203
|
+
releaseInternalPointerCapture(internal, hit.eventObject, id);
|
|
1139
1204
|
};
|
|
1140
1205
|
const extractEventProps = {};
|
|
1141
1206
|
for (const prop in event) {
|
|
1142
1207
|
const property = event[prop];
|
|
1143
1208
|
if (typeof property !== "function") extractEventProps[prop] = property;
|
|
1144
1209
|
}
|
|
1210
|
+
const eventPointerId = "pointerId" in event ? event.pointerId : void 0;
|
|
1145
1211
|
const raycastEvent = {
|
|
1146
1212
|
...hit,
|
|
1147
1213
|
...extractEventProps,
|
|
@@ -1152,18 +1218,19 @@ function createEvents(store) {
|
|
|
1152
1218
|
unprojectedPoint,
|
|
1153
1219
|
ray: raycaster.ray,
|
|
1154
1220
|
camera,
|
|
1221
|
+
pointerId: eventPointerId,
|
|
1155
1222
|
// Hijack stopPropagation, which just sets a flag
|
|
1156
1223
|
stopPropagation() {
|
|
1157
|
-
const
|
|
1224
|
+
const pointerState = eventPointerId !== void 0 ? internal.pointerMap.get(eventPointerId) : void 0;
|
|
1158
1225
|
if (
|
|
1159
1226
|
// ...if this pointer hasn't been captured
|
|
1160
|
-
!
|
|
1161
|
-
|
|
1227
|
+
!pointerState?.captured.size || // ... or if the hit object is capturing the pointer
|
|
1228
|
+
pointerState.captured.has(hit.eventObject)
|
|
1162
1229
|
) {
|
|
1163
1230
|
raycastEvent.stopped = localState.stopped = true;
|
|
1164
|
-
if (
|
|
1231
|
+
if (pointerState?.hovered.size && Array.from(pointerState.hovered.values()).find((i) => i.eventObject === hit.eventObject)) {
|
|
1165
1232
|
const higher = intersections.slice(0, intersections.indexOf(hit));
|
|
1166
|
-
cancelPointer([...higher, hit]);
|
|
1233
|
+
cancelPointer([...higher, hit], eventPointerId);
|
|
1167
1234
|
}
|
|
1168
1235
|
}
|
|
1169
1236
|
},
|
|
@@ -1179,15 +1246,18 @@ function createEvents(store) {
|
|
|
1179
1246
|
}
|
|
1180
1247
|
return intersections;
|
|
1181
1248
|
}
|
|
1182
|
-
function cancelPointer(intersections) {
|
|
1249
|
+
function cancelPointer(intersections, pointerId) {
|
|
1183
1250
|
const { internal } = store.getState();
|
|
1184
|
-
|
|
1251
|
+
const pid = pointerId ?? DEFAULT_POINTER_ID;
|
|
1252
|
+
const pointerState = internal.pointerMap.get(pid);
|
|
1253
|
+
if (!pointerState) return;
|
|
1254
|
+
for (const [hoveredId, hoveredObj] of pointerState.hovered) {
|
|
1185
1255
|
if (!intersections.length || !intersections.find(
|
|
1186
1256
|
(hit) => hit.object === hoveredObj.object && hit.index === hoveredObj.index && hit.instanceId === hoveredObj.instanceId
|
|
1187
1257
|
)) {
|
|
1188
1258
|
const eventObject = hoveredObj.eventObject;
|
|
1189
1259
|
const instance = eventObject.__r3f;
|
|
1190
|
-
|
|
1260
|
+
pointerState.hovered.delete(hoveredId);
|
|
1191
1261
|
if (instance?.eventCount) {
|
|
1192
1262
|
const handlers = instance.handlers;
|
|
1193
1263
|
const data = { ...hoveredObj, intersections };
|
|
@@ -1216,41 +1286,118 @@ function createEvents(store) {
|
|
|
1216
1286
|
instance?.handlers.onDropMissed?.(event);
|
|
1217
1287
|
}
|
|
1218
1288
|
}
|
|
1289
|
+
function cleanupPointer(pointerId) {
|
|
1290
|
+
const { internal } = store.getState();
|
|
1291
|
+
const pointerState = internal.pointerMap.get(pointerId);
|
|
1292
|
+
if (pointerState) {
|
|
1293
|
+
for (const [, hoveredObj] of pointerState.hovered) {
|
|
1294
|
+
const eventObject = hoveredObj.eventObject;
|
|
1295
|
+
const instance = eventObject.__r3f;
|
|
1296
|
+
if (instance?.eventCount) {
|
|
1297
|
+
const handlers = instance.handlers;
|
|
1298
|
+
const data = { ...hoveredObj, intersections: [] };
|
|
1299
|
+
handlers.onPointerOut?.(data);
|
|
1300
|
+
handlers.onPointerLeave?.(data);
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
internal.pointerMap.delete(pointerId);
|
|
1304
|
+
}
|
|
1305
|
+
internal.pointerDirty.delete(pointerId);
|
|
1306
|
+
}
|
|
1307
|
+
function processDeferredPointer(event, pointerId) {
|
|
1308
|
+
const state = store.getState();
|
|
1309
|
+
const { internal } = state;
|
|
1310
|
+
if (!state.events.enabled) return;
|
|
1311
|
+
const filter = filterPointerEvents;
|
|
1312
|
+
const hits = intersect(event, filter);
|
|
1313
|
+
cancelPointer(hits, pointerId);
|
|
1314
|
+
function onIntersect(data) {
|
|
1315
|
+
const eventObject = data.eventObject;
|
|
1316
|
+
const instance = eventObject.__r3f;
|
|
1317
|
+
if (!instance?.eventCount) return;
|
|
1318
|
+
const handlers = instance.handlers;
|
|
1319
|
+
if (handlers.onPointerOver || handlers.onPointerEnter || handlers.onPointerOut || handlers.onPointerLeave) {
|
|
1320
|
+
const id = makeId(data);
|
|
1321
|
+
const pointerState = getPointerState(internal, pointerId);
|
|
1322
|
+
const hoveredItem = pointerState.hovered.get(id);
|
|
1323
|
+
if (!hoveredItem) {
|
|
1324
|
+
pointerState.hovered.set(id, data);
|
|
1325
|
+
handlers.onPointerOver?.(data);
|
|
1326
|
+
handlers.onPointerEnter?.(data);
|
|
1327
|
+
} else if (hoveredItem.stopped) {
|
|
1328
|
+
data.stopPropagation();
|
|
1329
|
+
}
|
|
1330
|
+
}
|
|
1331
|
+
handlers.onPointerMove?.(data);
|
|
1332
|
+
}
|
|
1333
|
+
handleIntersects(hits, event, 0, onIntersect);
|
|
1334
|
+
}
|
|
1219
1335
|
function handlePointer(name) {
|
|
1220
1336
|
switch (name) {
|
|
1221
1337
|
case "onPointerLeave":
|
|
1222
|
-
case "onPointerCancel":
|
|
1223
1338
|
case "onDragLeave":
|
|
1224
1339
|
return () => cancelPointer([]);
|
|
1340
|
+
// Global cancel of these events
|
|
1341
|
+
case "onPointerCancel":
|
|
1342
|
+
return (event) => {
|
|
1343
|
+
const pointerId = getPointerId(event);
|
|
1344
|
+
cleanupPointer(pointerId);
|
|
1345
|
+
};
|
|
1225
1346
|
case "onLostPointerCapture":
|
|
1226
1347
|
return (event) => {
|
|
1227
1348
|
const { internal } = store.getState();
|
|
1228
|
-
|
|
1349
|
+
const pointerId = getPointerId(event);
|
|
1350
|
+
const pointerState = internal.pointerMap.get(pointerId);
|
|
1351
|
+
if (pointerState?.captured.size) {
|
|
1229
1352
|
requestAnimationFrame(() => {
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1353
|
+
const pointerState2 = internal.pointerMap.get(pointerId);
|
|
1354
|
+
if (pointerState2?.captured.size) {
|
|
1355
|
+
pointerState2.captured.clear();
|
|
1233
1356
|
}
|
|
1357
|
+
cancelPointer([], pointerId);
|
|
1234
1358
|
});
|
|
1235
1359
|
}
|
|
1236
1360
|
};
|
|
1237
1361
|
}
|
|
1238
1362
|
return function handleEvent(event) {
|
|
1239
1363
|
const state = store.getState();
|
|
1240
|
-
const { onPointerMissed, onDragOverMissed, onDropMissed, internal } = state;
|
|
1364
|
+
const { onPointerMissed, onDragOverMissed, onDropMissed, internal, events } = state;
|
|
1365
|
+
const pointerId = getPointerId(event);
|
|
1241
1366
|
internal.lastEvent.current = event;
|
|
1242
|
-
if (!
|
|
1367
|
+
if (!events.enabled) return;
|
|
1243
1368
|
const isPointerMove = name === "onPointerMove";
|
|
1244
1369
|
const isDragOver = name === "onDragOver";
|
|
1245
1370
|
const isDrop = name === "onDrop";
|
|
1246
1371
|
const isClickEvent = name === "onClick" || name === "onContextMenu" || name === "onDoubleClick";
|
|
1372
|
+
const isPointerDown = name === "onPointerDown";
|
|
1373
|
+
const isPointerUp = name === "onPointerUp";
|
|
1374
|
+
const isWheel = name === "onWheel";
|
|
1375
|
+
const canDeferRaycasts = events.frameTimedRaycasts && state.frameloop === "always";
|
|
1376
|
+
if (isPointerMove && canDeferRaycasts) {
|
|
1377
|
+
events.compute?.(event, state);
|
|
1378
|
+
internal.pointerDirty.set(pointerId, event);
|
|
1379
|
+
return;
|
|
1380
|
+
}
|
|
1381
|
+
if (isWheel && canDeferRaycasts && !events.alwaysFireOnScroll) {
|
|
1382
|
+
events.compute?.(event, state);
|
|
1383
|
+
internal.pointerDirty.set(pointerId, event);
|
|
1384
|
+
return;
|
|
1385
|
+
}
|
|
1386
|
+
if ((isClickEvent || isPointerDown || isPointerUp) && internal.pointerDirty.has(pointerId)) {
|
|
1387
|
+
const deferredEvent = internal.pointerDirty.get(pointerId);
|
|
1388
|
+
internal.pointerDirty.delete(pointerId);
|
|
1389
|
+
processDeferredPointer(deferredEvent, pointerId);
|
|
1390
|
+
}
|
|
1247
1391
|
const filter = isPointerMove || isDragOver || isDrop ? filterPointerEvents : void 0;
|
|
1248
1392
|
const hits = intersect(event, filter);
|
|
1249
|
-
const delta = isClickEvent ? calculateDistance(event) : 0;
|
|
1250
|
-
if (
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1393
|
+
const delta = isClickEvent ? calculateDistance(event, pointerId) : 0;
|
|
1394
|
+
if (isPointerDown) {
|
|
1395
|
+
const pointerState2 = getPointerState(internal, pointerId);
|
|
1396
|
+
pointerState2.initialClick = [event.offsetX, event.offsetY];
|
|
1397
|
+
pointerState2.initialHits = hits.map((hit) => hit.eventObject);
|
|
1398
|
+
}
|
|
1399
|
+
const pointerState = internal.pointerMap.get(pointerId);
|
|
1400
|
+
const initialHits = pointerState?.initialHits ?? [];
|
|
1254
1401
|
if (isClickEvent && !hits.length) {
|
|
1255
1402
|
if (delta <= 2) {
|
|
1256
1403
|
pointerMissed(event, internal.interaction);
|
|
@@ -1265,7 +1412,9 @@ function createEvents(store) {
|
|
|
1265
1412
|
dropMissed(event, internal.interaction);
|
|
1266
1413
|
if (onDropMissed) onDropMissed(event);
|
|
1267
1414
|
}
|
|
1268
|
-
if (isPointerMove || isDragOver)
|
|
1415
|
+
if (isPointerMove || isDragOver) {
|
|
1416
|
+
cancelPointer(hits, pointerId);
|
|
1417
|
+
}
|
|
1269
1418
|
function onIntersect(data) {
|
|
1270
1419
|
const eventObject = data.eventObject;
|
|
1271
1420
|
const instance = eventObject.__r3f;
|
|
@@ -1274,9 +1423,10 @@ function createEvents(store) {
|
|
|
1274
1423
|
if (isPointerMove) {
|
|
1275
1424
|
if (handlers.onPointerOver || handlers.onPointerEnter || handlers.onPointerOut || handlers.onPointerLeave) {
|
|
1276
1425
|
const id = makeId(data);
|
|
1277
|
-
const
|
|
1426
|
+
const pointerState2 = getPointerState(internal, pointerId);
|
|
1427
|
+
const hoveredItem = pointerState2.hovered.get(id);
|
|
1278
1428
|
if (!hoveredItem) {
|
|
1279
|
-
|
|
1429
|
+
pointerState2.hovered.set(id, data);
|
|
1280
1430
|
handlers.onPointerOver?.(data);
|
|
1281
1431
|
handlers.onPointerEnter?.(data);
|
|
1282
1432
|
} else if (hoveredItem.stopped) {
|
|
@@ -1286,9 +1436,10 @@ function createEvents(store) {
|
|
|
1286
1436
|
handlers.onPointerMove?.(data);
|
|
1287
1437
|
} else if (isDragOver) {
|
|
1288
1438
|
const id = makeId(data);
|
|
1289
|
-
const
|
|
1439
|
+
const pointerState2 = getPointerState(internal, pointerId);
|
|
1440
|
+
const hoveredItem = pointerState2.hovered.get(id);
|
|
1290
1441
|
if (!hoveredItem) {
|
|
1291
|
-
|
|
1442
|
+
pointerState2.hovered.set(id, data);
|
|
1292
1443
|
handlers.onDragOverEnter?.(data);
|
|
1293
1444
|
} else if (hoveredItem.stopped) {
|
|
1294
1445
|
data.stopPropagation();
|
|
@@ -1299,18 +1450,18 @@ function createEvents(store) {
|
|
|
1299
1450
|
} else {
|
|
1300
1451
|
const handler = handlers[name];
|
|
1301
1452
|
if (handler) {
|
|
1302
|
-
if (!isClickEvent ||
|
|
1453
|
+
if (!isClickEvent || initialHits.includes(eventObject)) {
|
|
1303
1454
|
pointerMissed(
|
|
1304
1455
|
event,
|
|
1305
|
-
internal.interaction.filter((object) => !
|
|
1456
|
+
internal.interaction.filter((object) => !initialHits.includes(object))
|
|
1306
1457
|
);
|
|
1307
1458
|
handler(data);
|
|
1308
1459
|
}
|
|
1309
1460
|
} else {
|
|
1310
|
-
if (isClickEvent &&
|
|
1461
|
+
if (isClickEvent && initialHits.includes(eventObject)) {
|
|
1311
1462
|
pointerMissed(
|
|
1312
1463
|
event,
|
|
1313
|
-
internal.interaction.filter((object) => !
|
|
1464
|
+
internal.interaction.filter((object) => !initialHits.includes(object))
|
|
1314
1465
|
);
|
|
1315
1466
|
}
|
|
1316
1467
|
}
|
|
@@ -1319,7 +1470,15 @@ function createEvents(store) {
|
|
|
1319
1470
|
handleIntersects(hits, event, delta, onIntersect);
|
|
1320
1471
|
};
|
|
1321
1472
|
}
|
|
1322
|
-
|
|
1473
|
+
function flushDeferredPointers() {
|
|
1474
|
+
const { internal, events } = store.getState();
|
|
1475
|
+
if (!events.frameTimedRaycasts) return;
|
|
1476
|
+
for (const [pointerId, event] of internal.pointerDirty) {
|
|
1477
|
+
processDeferredPointer(event, pointerId);
|
|
1478
|
+
}
|
|
1479
|
+
internal.pointerDirty.clear();
|
|
1480
|
+
}
|
|
1481
|
+
return { handlePointer, flushDeferredPointers, processDeferredPointer };
|
|
1323
1482
|
}
|
|
1324
1483
|
const DOM_EVENTS = {
|
|
1325
1484
|
onClick: ["click", false],
|
|
@@ -1338,10 +1497,15 @@ const DOM_EVENTS = {
|
|
|
1338
1497
|
onLostPointerCapture: ["lostpointercapture", true]
|
|
1339
1498
|
};
|
|
1340
1499
|
function createPointerEvents(store) {
|
|
1341
|
-
const { handlePointer } = createEvents(store);
|
|
1500
|
+
const { handlePointer, flushDeferredPointers, processDeferredPointer } = createEvents(store);
|
|
1501
|
+
let nextXRPointerId = XR_POINTER_ID_START;
|
|
1502
|
+
const xrPointers = /* @__PURE__ */ new Map();
|
|
1342
1503
|
return {
|
|
1343
1504
|
priority: 1,
|
|
1344
1505
|
enabled: true,
|
|
1506
|
+
frameTimedRaycasts: true,
|
|
1507
|
+
alwaysFireOnScroll: true,
|
|
1508
|
+
updateOnFrame: false,
|
|
1345
1509
|
compute(event, state) {
|
|
1346
1510
|
state.pointer.set(event.offsetX / state.size.width * 2 - 1, -(event.offsetY / state.size.height) * 2 + 1);
|
|
1347
1511
|
state.raycaster.setFromCamera(state.pointer, state.camera);
|
|
@@ -1351,11 +1515,33 @@ function createPointerEvents(store) {
|
|
|
1351
1515
|
(acc, key) => ({ ...acc, [key]: handlePointer(key) }),
|
|
1352
1516
|
{}
|
|
1353
1517
|
),
|
|
1354
|
-
update: () => {
|
|
1518
|
+
update: (pointerId) => {
|
|
1519
|
+
const { events, internal } = store.getState();
|
|
1520
|
+
if (!events.handlers) return;
|
|
1521
|
+
if (pointerId !== void 0) {
|
|
1522
|
+
const event = internal.pointerDirty.get(pointerId);
|
|
1523
|
+
if (event) {
|
|
1524
|
+
internal.pointerDirty.delete(pointerId);
|
|
1525
|
+
processDeferredPointer(event, pointerId);
|
|
1526
|
+
} else if (internal.lastEvent?.current) {
|
|
1527
|
+
processDeferredPointer(internal.lastEvent.current, pointerId);
|
|
1528
|
+
}
|
|
1529
|
+
} else {
|
|
1530
|
+
flushDeferredPointers();
|
|
1531
|
+
if (internal.lastEvent?.current) {
|
|
1532
|
+
events.handlers.onPointerMove(internal.lastEvent.current);
|
|
1533
|
+
}
|
|
1534
|
+
}
|
|
1535
|
+
},
|
|
1536
|
+
flush: () => {
|
|
1355
1537
|
const { events, internal } = store.getState();
|
|
1356
|
-
|
|
1538
|
+
flushDeferredPointers();
|
|
1539
|
+
if (events.updateOnFrame && internal.lastEvent?.current && events.handlers) {
|
|
1540
|
+
events.handlers.onPointerMove(internal.lastEvent.current);
|
|
1541
|
+
}
|
|
1357
1542
|
},
|
|
1358
1543
|
connect: (target) => {
|
|
1544
|
+
if (!target) return;
|
|
1359
1545
|
const { set, events } = store.getState();
|
|
1360
1546
|
events.disconnect?.();
|
|
1361
1547
|
set((state) => ({ events: { ...state.events, connected: target } }));
|
|
@@ -1379,6 +1565,32 @@ function createPointerEvents(store) {
|
|
|
1379
1565
|
}
|
|
1380
1566
|
set((state) => ({ events: { ...state.events, connected: void 0 } }));
|
|
1381
1567
|
}
|
|
1568
|
+
},
|
|
1569
|
+
registerPointer: (config) => {
|
|
1570
|
+
const pointerId = nextXRPointerId++;
|
|
1571
|
+
xrPointers.set(pointerId, config);
|
|
1572
|
+
const { internal } = store.getState();
|
|
1573
|
+
getPointerState(internal, pointerId);
|
|
1574
|
+
return pointerId;
|
|
1575
|
+
},
|
|
1576
|
+
unregisterPointer: (pointerId) => {
|
|
1577
|
+
xrPointers.delete(pointerId);
|
|
1578
|
+
const { internal } = store.getState();
|
|
1579
|
+
const pointerState = internal.pointerMap.get(pointerId);
|
|
1580
|
+
if (pointerState) {
|
|
1581
|
+
for (const [, hoveredObj] of pointerState.hovered) {
|
|
1582
|
+
const eventObject = hoveredObj.eventObject;
|
|
1583
|
+
const instance = eventObject.__r3f;
|
|
1584
|
+
if (instance?.eventCount) {
|
|
1585
|
+
const handlers = instance.handlers;
|
|
1586
|
+
const data = { ...hoveredObj, intersections: [] };
|
|
1587
|
+
handlers.onPointerOut?.(data);
|
|
1588
|
+
handlers.onPointerLeave?.(data);
|
|
1589
|
+
}
|
|
1590
|
+
}
|
|
1591
|
+
internal.pointerMap.delete(pointerId);
|
|
1592
|
+
}
|
|
1593
|
+
internal.pointerDirty.delete(pointerId);
|
|
1382
1594
|
}
|
|
1383
1595
|
};
|
|
1384
1596
|
}
|
|
@@ -1440,1036 +1652,12 @@ function notifyAlpha({ message, link }) {
|
|
|
1440
1652
|
}
|
|
1441
1653
|
}
|
|
1442
1654
|
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
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;
|
|
1709
|
-
}
|
|
1710
|
-
|
|
1711
|
-
var __defProp$1 = Object.defineProperty;
|
|
1712
|
-
var __defNormalProp$1 = (obj, key, value) => key in obj ? __defProp$1(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
1713
|
-
var __publicField$1 = (obj, key, value) => __defNormalProp$1(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
1714
|
-
const hmrData = (() => {
|
|
1715
|
-
if (typeof process !== "undefined" && process.env.NODE_ENV === "test") return void 0;
|
|
1716
|
-
if (typeof import_meta_hot !== "undefined") return import_meta_hot;
|
|
1717
|
-
try {
|
|
1718
|
-
return (0, eval)("import.meta.hot");
|
|
1719
|
-
} catch {
|
|
1720
|
-
return void 0;
|
|
1721
|
-
}
|
|
1722
|
-
})();
|
|
1723
|
-
const _Scheduler = class _Scheduler {
|
|
1724
|
-
//* Constructor ================================
|
|
1725
|
-
constructor() {
|
|
1726
|
-
//* Critical State ================================
|
|
1727
|
-
__publicField$1(this, "roots", /* @__PURE__ */ new Map());
|
|
1728
|
-
__publicField$1(this, "phaseGraph");
|
|
1729
|
-
__publicField$1(this, "loopState", {
|
|
1730
|
-
running: false,
|
|
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?.();
|
|
1655
|
+
const INTERNAL_LIB_MARKERS = ["zustand", "setState", "Object.assign"];
|
|
1656
|
+
const INTERNAL_FIBER_MARKERS = ["packages/fiber/src", "packages/fiber/dist", "@react-three/fiber", "@react-three_fiber"];
|
|
1657
|
+
function isInternalRendererAccess(stack, callerDepth = 2) {
|
|
1658
|
+
if (!stack) return false;
|
|
1659
|
+
const caller = stack.split("\n")[callerDepth] ?? "";
|
|
1660
|
+
return INTERNAL_LIB_MARKERS.some((m) => caller.includes(m)) || INTERNAL_FIBER_MARKERS.some((m) => caller.includes(m));
|
|
2473
1661
|
}
|
|
2474
1662
|
|
|
2475
1663
|
const R3F_CONTEXT = Symbol.for("@react-three/fiber.context");
|
|
@@ -2509,7 +1697,14 @@ const createStore = (invalidate, advance) => {
|
|
|
2509
1697
|
frustum: new Frustum(),
|
|
2510
1698
|
autoUpdateFrustum: true,
|
|
2511
1699
|
raycaster: null,
|
|
2512
|
-
events: {
|
|
1700
|
+
events: {
|
|
1701
|
+
priority: 1,
|
|
1702
|
+
enabled: true,
|
|
1703
|
+
connected: false,
|
|
1704
|
+
frameTimedRaycasts: true,
|
|
1705
|
+
alwaysFireOnScroll: true,
|
|
1706
|
+
updateOnFrame: false
|
|
1707
|
+
},
|
|
2513
1708
|
scene: null,
|
|
2514
1709
|
rootScene: null,
|
|
2515
1710
|
xr: null,
|
|
@@ -2600,11 +1795,14 @@ const createStore = (invalidate, advance) => {
|
|
|
2600
1795
|
},
|
|
2601
1796
|
setError: (error) => set(() => ({ error })),
|
|
2602
1797
|
error: null,
|
|
2603
|
-
//* TSL State (managed via hooks: useUniforms, useNodes, useTextures,
|
|
1798
|
+
//* TSL State (managed via hooks: useUniforms, useNodes, useBuffers, useGPUStorage, useTextures, useRenderPipeline) ==============================
|
|
2604
1799
|
uniforms: {},
|
|
2605
1800
|
nodes: {},
|
|
1801
|
+
buffers: {},
|
|
1802
|
+
gpuStorage: {},
|
|
2606
1803
|
textures: /* @__PURE__ */ new Map(),
|
|
2607
|
-
|
|
1804
|
+
_textureRefs: /* @__PURE__ */ new Map(),
|
|
1805
|
+
renderPipeline: null,
|
|
2608
1806
|
passes: {},
|
|
2609
1807
|
_hmrVersion: 0,
|
|
2610
1808
|
_sizeImperative: false,
|
|
@@ -2613,12 +1811,16 @@ const createStore = (invalidate, advance) => {
|
|
|
2613
1811
|
internal: {
|
|
2614
1812
|
// Events
|
|
2615
1813
|
interaction: [],
|
|
2616
|
-
hovered: /* @__PURE__ */ new Map(),
|
|
2617
1814
|
subscribers: [],
|
|
1815
|
+
// Per-pointer state (new unified structure)
|
|
1816
|
+
pointerMap: /* @__PURE__ */ new Map(),
|
|
1817
|
+
pointerDirty: /* @__PURE__ */ new Map(),
|
|
1818
|
+
lastEvent: React.createRef(),
|
|
1819
|
+
// Deprecated but kept for backwards compatibility
|
|
1820
|
+
hovered: /* @__PURE__ */ new Map(),
|
|
2618
1821
|
initialClick: [0, 0],
|
|
2619
1822
|
initialHits: [],
|
|
2620
1823
|
capturedMap: /* @__PURE__ */ new Map(),
|
|
2621
|
-
lastEvent: React.createRef(),
|
|
2622
1824
|
// Visibility tracking (onFramed, onOccluded, onVisible)
|
|
2623
1825
|
visibilityRegistry: /* @__PURE__ */ new Map(),
|
|
2624
1826
|
// Occlusion system (WebGPU only)
|
|
@@ -2657,8 +1859,7 @@ const createStore = (invalidate, advance) => {
|
|
|
2657
1859
|
const currentState = rootStore.getState();
|
|
2658
1860
|
if (!currentState.isLegacy && currentState.internal.actualRenderer) {
|
|
2659
1861
|
const stack = new Error().stack || "";
|
|
2660
|
-
|
|
2661
|
-
if (!isInternalAccess) {
|
|
1862
|
+
if (!isInternalRendererAccess(stack)) {
|
|
2662
1863
|
const cleanedStack = stack.split("\n").slice(2).join("\n") || "Stack trace unavailable";
|
|
2663
1864
|
notifyDepreciated({
|
|
2664
1865
|
heading: "Accessing state.gl in WebGPU mode",
|
|
@@ -2706,14 +1907,16 @@ const createStore = (invalidate, advance) => {
|
|
|
2706
1907
|
oldSize = size;
|
|
2707
1908
|
oldDpr = viewport.dpr;
|
|
2708
1909
|
updateCamera(camera, size);
|
|
2709
|
-
if (canvasTarget) {
|
|
1910
|
+
if (internal.isSecondary && canvasTarget) {
|
|
2710
1911
|
if (viewport.dpr > 0) canvasTarget.setPixelRatio(viewport.dpr);
|
|
2711
|
-
|
|
2712
|
-
canvasTarget.setSize(size.width, size.height, updateStyle);
|
|
1912
|
+
canvasTarget.setSize(size.width, size.height, false);
|
|
2713
1913
|
} else {
|
|
2714
1914
|
if (viewport.dpr > 0) actualRenderer.setPixelRatio(viewport.dpr);
|
|
2715
|
-
|
|
2716
|
-
|
|
1915
|
+
actualRenderer.setSize(size.width, size.height, false);
|
|
1916
|
+
if (canvasTarget) {
|
|
1917
|
+
if (viewport.dpr > 0) canvasTarget.setPixelRatio(viewport.dpr);
|
|
1918
|
+
canvasTarget.setSize(size.width, size.height, false);
|
|
1919
|
+
}
|
|
2717
1920
|
}
|
|
2718
1921
|
}
|
|
2719
1922
|
if (camera !== oldCamera) {
|
|
@@ -2769,19 +1972,25 @@ function loadingFn(extensions, onProgress) {
|
|
|
2769
1972
|
);
|
|
2770
1973
|
};
|
|
2771
1974
|
}
|
|
2772
|
-
function useLoader(loader, input, extensions, onProgress) {
|
|
2773
|
-
const
|
|
1975
|
+
function useLoader(loader, input, extensions, onProgress, cacheKey) {
|
|
1976
|
+
const inputs = Array.isArray(input) ? input : [input];
|
|
1977
|
+
const cacheKeys = Array.isArray(cacheKey ?? input) ? cacheKey ?? input : [cacheKey ?? input];
|
|
2774
1978
|
const fn = loadingFn(extensions, onProgress);
|
|
2775
|
-
const results =
|
|
1979
|
+
const results = cacheKeys.map(
|
|
1980
|
+
(key, index) => suspend(() => fn(loader, inputs[index]), [loader, key], { equal: is.equ })
|
|
1981
|
+
);
|
|
2776
1982
|
return Array.isArray(input) ? results : results[0];
|
|
2777
1983
|
}
|
|
2778
|
-
useLoader.preload = function(loader, input, extensions, onProgress) {
|
|
2779
|
-
const
|
|
2780
|
-
|
|
1984
|
+
useLoader.preload = function(loader, input, extensions, onProgress, cacheKey) {
|
|
1985
|
+
const inputs = Array.isArray(input) ? input : [input];
|
|
1986
|
+
const cacheKeys = Array.isArray(cacheKey ?? input) ? cacheKey ?? input : [cacheKey ?? input];
|
|
1987
|
+
cacheKeys.forEach(
|
|
1988
|
+
(key, index) => preload(() => loadingFn(extensions, onProgress)(loader, inputs[index]), [loader, key])
|
|
1989
|
+
);
|
|
2781
1990
|
};
|
|
2782
|
-
useLoader.clear = function(loader, input) {
|
|
2783
|
-
const
|
|
2784
|
-
|
|
1991
|
+
useLoader.clear = function(loader, input, cacheKey) {
|
|
1992
|
+
const cacheKeys = Array.isArray(cacheKey ?? input) ? cacheKey ?? input : [cacheKey ?? input];
|
|
1993
|
+
cacheKeys.forEach((key) => clear([loader, key]));
|
|
2785
1994
|
};
|
|
2786
1995
|
useLoader.loader = getLoader;
|
|
2787
1996
|
|
|
@@ -2857,20 +2066,14 @@ function useFrame(callback, priorityOrOptions) {
|
|
|
2857
2066
|
}
|
|
2858
2067
|
};
|
|
2859
2068
|
} else {
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
unregisterJob = registerOutside();
|
|
2869
|
-
});
|
|
2870
|
-
return () => {
|
|
2871
|
-
unsubReady();
|
|
2872
|
-
unregisterJob?.();
|
|
2873
|
-
};
|
|
2069
|
+
return scheduler.register(
|
|
2070
|
+
(state, delta) => {
|
|
2071
|
+
const frameState = state;
|
|
2072
|
+
if (!frameState.renderer) return;
|
|
2073
|
+
callbackRef.current?.(frameState, delta);
|
|
2074
|
+
},
|
|
2075
|
+
{ id, ...options }
|
|
2076
|
+
);
|
|
2874
2077
|
}
|
|
2875
2078
|
}, [store, scheduler, id, optionsKey, isLegacyPriority, isInsideCanvas]);
|
|
2876
2079
|
const isPaused = React.useSyncExternalStore(
|
|
@@ -2961,18 +2164,18 @@ function buildFromCache(input, textureCache) {
|
|
|
2961
2164
|
function useTexture(input, optionsOrOnLoad) {
|
|
2962
2165
|
const renderer = useThree((state) => state.internal.actualRenderer);
|
|
2963
2166
|
const store = useStore();
|
|
2964
|
-
const textureCache = useThree((state) => state.textures);
|
|
2965
2167
|
const options = typeof optionsOrOnLoad === "function" ? { onLoad: optionsOrOnLoad } : optionsOrOnLoad ?? {};
|
|
2966
|
-
const { onLoad, cache =
|
|
2168
|
+
const { onLoad, cache = true } = options;
|
|
2967
2169
|
const onLoadRef = useRef(onLoad);
|
|
2968
2170
|
onLoadRef.current = onLoad;
|
|
2969
2171
|
const onLoadCalledForRef = useRef(null);
|
|
2970
2172
|
const urls = useMemo(() => getUrls(input), [input]);
|
|
2971
2173
|
const cachedResult = useMemo(() => {
|
|
2972
2174
|
if (!cache) return null;
|
|
2973
|
-
|
|
2974
|
-
|
|
2975
|
-
|
|
2175
|
+
const textures = store.getState().textures;
|
|
2176
|
+
if (!allUrlsCached(urls, textures)) return null;
|
|
2177
|
+
return buildFromCache(input, textures);
|
|
2178
|
+
}, [cache, urls, input, store]);
|
|
2976
2179
|
const loadedTextures = useLoader(
|
|
2977
2180
|
TextureLoader,
|
|
2978
2181
|
IsObject(input) ? Object.values(input) : input
|
|
@@ -3016,8 +2219,6 @@ function useTexture(input, optionsOrOnLoad) {
|
|
|
3016
2219
|
}, [input, loadedTextures, cachedResult]);
|
|
3017
2220
|
useEffect(() => {
|
|
3018
2221
|
if (!cache) return;
|
|
3019
|
-
if (cachedResult) return;
|
|
3020
|
-
const set = store.setState;
|
|
3021
2222
|
const urlTextureMap = [];
|
|
3022
2223
|
if (typeof input === "string") {
|
|
3023
2224
|
urlTextureMap.push([input, mappedTextures]);
|
|
@@ -3031,18 +2232,32 @@ function useTexture(input, optionsOrOnLoad) {
|
|
|
3031
2232
|
urlTextureMap.push([url, textureRecord[key]]);
|
|
3032
2233
|
}
|
|
3033
2234
|
}
|
|
3034
|
-
|
|
3035
|
-
const
|
|
3036
|
-
let
|
|
2235
|
+
store.setState((state) => {
|
|
2236
|
+
const refs = new Map(state._textureRefs);
|
|
2237
|
+
let textures = state.textures;
|
|
2238
|
+
let added = false;
|
|
3037
2239
|
for (const [url, texture] of urlTextureMap) {
|
|
3038
|
-
if (!
|
|
3039
|
-
|
|
3040
|
-
|
|
2240
|
+
if (!textures.has(url)) {
|
|
2241
|
+
if (!added) {
|
|
2242
|
+
textures = new Map(textures);
|
|
2243
|
+
added = true;
|
|
2244
|
+
}
|
|
2245
|
+
textures.set(url, texture);
|
|
3041
2246
|
}
|
|
2247
|
+
refs.set(url, (refs.get(url) ?? 0) + 1);
|
|
3042
2248
|
}
|
|
3043
|
-
return
|
|
2249
|
+
return added ? { textures, _textureRefs: refs } : { _textureRefs: refs };
|
|
3044
2250
|
});
|
|
3045
|
-
|
|
2251
|
+
return () => store.setState((state) => {
|
|
2252
|
+
const refs = new Map(state._textureRefs);
|
|
2253
|
+
for (const [url] of urlTextureMap) {
|
|
2254
|
+
const next = (refs.get(url) ?? 0) - 1;
|
|
2255
|
+
if (next <= 0) refs.delete(url);
|
|
2256
|
+
else refs.set(url, next);
|
|
2257
|
+
}
|
|
2258
|
+
return { _textureRefs: refs };
|
|
2259
|
+
});
|
|
2260
|
+
}, [cache, input, mappedTextures, store]);
|
|
3046
2261
|
return mappedTextures;
|
|
3047
2262
|
}
|
|
3048
2263
|
useTexture.preload = (url) => useLoader.preload(TextureLoader, url);
|
|
@@ -3058,96 +2273,63 @@ const Texture = ({
|
|
|
3058
2273
|
return /* @__PURE__ */ jsx(Fragment, { children: children?.(ret) });
|
|
3059
2274
|
};
|
|
3060
2275
|
|
|
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() {
|
|
2276
|
+
function useTextures(selector) {
|
|
3069
2277
|
const store = useStore();
|
|
3070
|
-
|
|
3071
|
-
const set = store.setState;
|
|
2278
|
+
const registry = useMemo(() => {
|
|
3072
2279
|
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
|
-
};
|
|
2280
|
+
const setState = store.setState;
|
|
2281
|
+
const getOne = (key) => getState().textures.get(key);
|
|
3131
2282
|
return {
|
|
3132
|
-
|
|
3133
|
-
get textures() {
|
|
2283
|
+
get all() {
|
|
3134
2284
|
return getState().textures;
|
|
3135
2285
|
},
|
|
3136
|
-
|
|
3137
|
-
|
|
2286
|
+
get(input) {
|
|
2287
|
+
if (typeof input === "string") return getOne(input);
|
|
2288
|
+
if (Array.isArray(input)) return input.map(getOne);
|
|
2289
|
+
const out = {};
|
|
2290
|
+
for (const name in input) out[name] = getOne(input[name]);
|
|
2291
|
+
return out;
|
|
2292
|
+
},
|
|
3138
2293
|
has: (key) => getState().textures.has(key),
|
|
3139
|
-
|
|
3140
|
-
|
|
3141
|
-
|
|
3142
|
-
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
|
|
3146
|
-
|
|
3147
|
-
|
|
3148
|
-
|
|
2294
|
+
add(keyOrRecord, texture) {
|
|
2295
|
+
setState((state) => {
|
|
2296
|
+
const textures = new Map(state.textures);
|
|
2297
|
+
if (typeof keyOrRecord === "string") {
|
|
2298
|
+
textures.set(keyOrRecord, texture);
|
|
2299
|
+
} else {
|
|
2300
|
+
for (const key in keyOrRecord) textures.set(key, keyOrRecord[key]);
|
|
2301
|
+
}
|
|
2302
|
+
return { textures };
|
|
2303
|
+
});
|
|
2304
|
+
},
|
|
2305
|
+
dispose(key, options) {
|
|
2306
|
+
const state = getState();
|
|
2307
|
+
const refs = state._textureRefs.get(key) ?? 0;
|
|
2308
|
+
if (refs > 0 && !options?.force) {
|
|
2309
|
+
console.warn(
|
|
2310
|
+
`[useTextures] "${key}" still has ${refs} active reference(s); skipping dispose. Pass { force: true } to override.`
|
|
2311
|
+
);
|
|
2312
|
+
return false;
|
|
2313
|
+
}
|
|
2314
|
+
state.textures.get(key)?.dispose();
|
|
2315
|
+
setState((s) => {
|
|
2316
|
+
const textures = new Map(s.textures);
|
|
2317
|
+
textures.delete(key);
|
|
2318
|
+
const nextRefs = new Map(s._textureRefs);
|
|
2319
|
+
nextRefs.delete(key);
|
|
2320
|
+
return { textures, _textureRefs: nextRefs };
|
|
2321
|
+
});
|
|
2322
|
+
return true;
|
|
2323
|
+
},
|
|
2324
|
+
disposeAll() {
|
|
2325
|
+
for (const texture of getState().textures.values()) texture.dispose();
|
|
2326
|
+
setState({ textures: /* @__PURE__ */ new Map(), _textureRefs: /* @__PURE__ */ new Map() });
|
|
2327
|
+
}
|
|
3149
2328
|
};
|
|
3150
2329
|
}, [store]);
|
|
2330
|
+
const subscribe = selector ? () => selector(registry) : (state) => state.textures;
|
|
2331
|
+
const selected = useThree(subscribe);
|
|
2332
|
+
return selector ? selected : registry;
|
|
3151
2333
|
}
|
|
3152
2334
|
|
|
3153
2335
|
function useRenderTarget(widthOrOptions, heightOrOptions, options) {
|
|
@@ -14667,6 +13849,7 @@ function swapInstances() {
|
|
|
14667
13849
|
}
|
|
14668
13850
|
}
|
|
14669
13851
|
if (instance.isHidden) unhideInstance(instance);
|
|
13852
|
+
if (isObject3D(instance.object)) removeInteractivity(findInitialRoot(instance), instance.object);
|
|
14670
13853
|
if (instance.object.__r3f) delete instance.object.__r3f;
|
|
14671
13854
|
if (instance.type !== "primitive") disposeOnIdle(instance.object);
|
|
14672
13855
|
}
|
|
@@ -14679,6 +13862,8 @@ function swapInstances() {
|
|
|
14679
13862
|
instance.object.__r3f = instance;
|
|
14680
13863
|
setFiberRef(fiber, instance.object);
|
|
14681
13864
|
delete instance.appliedOnce;
|
|
13865
|
+
instance.eventCount = 0;
|
|
13866
|
+
instance.handlers = {};
|
|
14682
13867
|
applyProps(instance.object, instance.props);
|
|
14683
13868
|
if (instance.props.attach) {
|
|
14684
13869
|
attach(parent, instance);
|
|
@@ -14983,6 +14168,7 @@ function createRoot(canvas) {
|
|
|
14983
14168
|
const lastConfiguredProps = {};
|
|
14984
14169
|
let configured = false;
|
|
14985
14170
|
let pending = null;
|
|
14171
|
+
let rendererSetup = null;
|
|
14986
14172
|
return {
|
|
14987
14173
|
async configure(props = {}) {
|
|
14988
14174
|
let resolve;
|
|
@@ -14998,7 +14184,6 @@ function createRoot(canvas) {
|
|
|
14998
14184
|
events,
|
|
14999
14185
|
onCreated: onCreatedCallback,
|
|
15000
14186
|
shadows = false,
|
|
15001
|
-
textureColorSpace = SRGBColorSpace,
|
|
15002
14187
|
orthographic = false,
|
|
15003
14188
|
frameloop = "always",
|
|
15004
14189
|
dpr = [1, 2],
|
|
@@ -15013,7 +14198,8 @@ function createRoot(canvas) {
|
|
|
15013
14198
|
_sizeProps,
|
|
15014
14199
|
forceEven
|
|
15015
14200
|
} = props;
|
|
15016
|
-
const
|
|
14201
|
+
const textureColorSpace = is.obj(glConfig) && !is.fun(glConfig) && !isRenderer(glConfig) && glConfig.textureColorSpace || is.obj(rendererConfig) && !is.fun(rendererConfig) && !isRenderer(rendererConfig) && rendererConfig.textureColorSpace || SRGBColorSpace;
|
|
14202
|
+
let state = store.getState();
|
|
15017
14203
|
const defaultGPUProps = {
|
|
15018
14204
|
canvas,
|
|
15019
14205
|
antialias: true
|
|
@@ -15027,46 +14213,64 @@ function createRoot(canvas) {
|
|
|
15027
14213
|
throw new Error("Cannot use both gl and renderer props at the same time");
|
|
15028
14214
|
}
|
|
15029
14215
|
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
|
-
|
|
14216
|
+
if (!state.internal.actualRenderer) {
|
|
14217
|
+
if (!rendererSetup) {
|
|
14218
|
+
rendererSetup = (async () => {
|
|
14219
|
+
if (primaryCanvas) {
|
|
14220
|
+
const primary = await waitForPrimary(primaryCanvas);
|
|
14221
|
+
renderer = primary.renderer;
|
|
14222
|
+
state.internal.actualRenderer = renderer;
|
|
14223
|
+
const canvasTarget = new CanvasTarget(canvas);
|
|
14224
|
+
primary.store.setState((prev) => ({
|
|
14225
|
+
internal: { ...prev.internal, isMultiCanvas: true }
|
|
14226
|
+
}));
|
|
14227
|
+
state.set((prev) => ({
|
|
14228
|
+
webGPUSupported: primary.store.getState().webGPUSupported,
|
|
14229
|
+
renderer,
|
|
14230
|
+
primaryStore: primary.store,
|
|
14231
|
+
internal: {
|
|
14232
|
+
...prev.internal,
|
|
14233
|
+
canvasTarget,
|
|
14234
|
+
isMultiCanvas: true,
|
|
14235
|
+
isSecondary: true,
|
|
14236
|
+
targetId: primaryCanvas
|
|
14237
|
+
}
|
|
14238
|
+
}));
|
|
14239
|
+
} else {
|
|
14240
|
+
renderer = await resolveRenderer(rendererConfig, defaultGPUProps, WebGPURenderer);
|
|
14241
|
+
if (!renderer.hasInitialized?.()) {
|
|
14242
|
+
const size2 = computeInitialSize(canvas, propsSize);
|
|
14243
|
+
if (size2.width > 0 && size2.height > 0) {
|
|
14244
|
+
const pixelRatio = calculateDpr(dpr);
|
|
14245
|
+
canvas.width = size2.width * pixelRatio;
|
|
14246
|
+
canvas.height = size2.height * pixelRatio;
|
|
14247
|
+
}
|
|
14248
|
+
await renderer.init();
|
|
14249
|
+
}
|
|
14250
|
+
const backend = renderer.backend;
|
|
14251
|
+
const isWebGPUBackend = backend && "isWebGPUBackend" in backend;
|
|
14252
|
+
state.internal.actualRenderer = renderer;
|
|
14253
|
+
state.set({ webGPUSupported: isWebGPUBackend, renderer, primaryStore: store });
|
|
14254
|
+
if (canvasId && !state.internal.isSecondary) {
|
|
14255
|
+
const canvasTarget = new CanvasTarget(canvas);
|
|
14256
|
+
const unregisterPrimary = registerPrimary(canvasId, renderer, store);
|
|
14257
|
+
state.set((prev) => ({
|
|
14258
|
+
internal: {
|
|
14259
|
+
...prev.internal,
|
|
14260
|
+
canvasTarget,
|
|
14261
|
+
unregisterPrimary
|
|
14262
|
+
}
|
|
14263
|
+
}));
|
|
14264
|
+
}
|
|
15067
14265
|
}
|
|
15068
|
-
}))
|
|
14266
|
+
})().catch((err) => {
|
|
14267
|
+
rendererSetup = null;
|
|
14268
|
+
throw err;
|
|
14269
|
+
});
|
|
15069
14270
|
}
|
|
14271
|
+
await rendererSetup;
|
|
14272
|
+
state = store.getState();
|
|
14273
|
+
renderer = state.internal.actualRenderer;
|
|
15070
14274
|
}
|
|
15071
14275
|
let raycaster = state.raycaster;
|
|
15072
14276
|
if (!raycaster) state.set({ raycaster: raycaster = new Raycaster() });
|
|
@@ -15159,7 +14363,7 @@ function createRoot(canvas) {
|
|
|
15159
14363
|
lastConfiguredProps.performance = performance;
|
|
15160
14364
|
}
|
|
15161
14365
|
if (!state.xr) {
|
|
15162
|
-
const handleXRFrame = (timestamp,
|
|
14366
|
+
const handleXRFrame = (timestamp, _frame) => {
|
|
15163
14367
|
const state2 = store.getState();
|
|
15164
14368
|
if (state2.frameloop === "never") return;
|
|
15165
14369
|
advance(timestamp);
|
|
@@ -15195,15 +14399,22 @@ function createRoot(canvas) {
|
|
|
15195
14399
|
const oldType = renderer.shadowMap.type;
|
|
15196
14400
|
renderer.shadowMap.enabled = !!shadows;
|
|
15197
14401
|
if (is.boo(shadows)) {
|
|
15198
|
-
renderer.shadowMap.type =
|
|
14402
|
+
renderer.shadowMap.type = PCFShadowMap;
|
|
15199
14403
|
} else if (is.str(shadows)) {
|
|
14404
|
+
if (shadows === "soft") {
|
|
14405
|
+
notifyDepreciated({
|
|
14406
|
+
heading: 'shadows="soft" is deprecated',
|
|
14407
|
+
body: "Three has depreciated soft and improved basic PCFShadows, we converted for you.",
|
|
14408
|
+
link: "https://github.com/mrdoob/three.js/wiki/Migration-Guide?utm_source=chatgpt.com#181--182"
|
|
14409
|
+
});
|
|
14410
|
+
}
|
|
15200
14411
|
const types = {
|
|
15201
14412
|
basic: BasicShadowMap,
|
|
15202
14413
|
percentage: PCFShadowMap,
|
|
15203
|
-
soft:
|
|
14414
|
+
soft: PCFShadowMap,
|
|
15204
14415
|
variance: VSMShadowMap
|
|
15205
14416
|
};
|
|
15206
|
-
renderer.shadowMap.type = types[shadows] ??
|
|
14417
|
+
renderer.shadowMap.type = types[shadows] ?? PCFShadowMap;
|
|
15207
14418
|
} else if (is.obj(shadows)) {
|
|
15208
14419
|
Object.assign(renderer.shadowMap, shadows);
|
|
15209
14420
|
}
|
|
@@ -15219,13 +14430,24 @@ function createRoot(canvas) {
|
|
|
15219
14430
|
if (state.textureColorSpace !== textureColorSpace) state.set(() => ({ textureColorSpace }));
|
|
15220
14431
|
lastConfiguredProps.textureColorSpace = textureColorSpace;
|
|
15221
14432
|
}
|
|
14433
|
+
const r3fProps = ["textureColorSpace"];
|
|
14434
|
+
const constructorOnlyProps = ["samples", "antialias", "alpha", "canvas", "powerPreference"];
|
|
14435
|
+
const nonApplyProps = [...r3fProps, ...constructorOnlyProps];
|
|
15222
14436
|
if (glConfig && !is.fun(glConfig) && !isRenderer(glConfig) && !is.equ(glConfig, renderer, shallowLoose)) {
|
|
15223
|
-
|
|
14437
|
+
const glProps = {};
|
|
14438
|
+
for (const key in glConfig) {
|
|
14439
|
+
if (!nonApplyProps.includes(key)) glProps[key] = glConfig[key];
|
|
14440
|
+
}
|
|
14441
|
+
applyProps(renderer, glProps);
|
|
15224
14442
|
}
|
|
15225
14443
|
if (rendererConfig && !is.fun(rendererConfig) && !isRenderer(rendererConfig) && state.renderer) {
|
|
15226
14444
|
const currentRenderer = state.renderer;
|
|
15227
14445
|
if (!is.equ(rendererConfig, currentRenderer, shallowLoose)) {
|
|
15228
|
-
|
|
14446
|
+
const rendererProps = {};
|
|
14447
|
+
for (const key in rendererConfig) {
|
|
14448
|
+
if (!nonApplyProps.includes(key)) rendererProps[key] = rendererConfig[key];
|
|
14449
|
+
}
|
|
14450
|
+
applyProps(currentRenderer, rendererProps);
|
|
15229
14451
|
}
|
|
15230
14452
|
}
|
|
15231
14453
|
const scheduler = getScheduler();
|
|
@@ -15251,6 +14473,18 @@ function createRoot(canvas) {
|
|
|
15251
14473
|
system: true
|
|
15252
14474
|
}
|
|
15253
14475
|
);
|
|
14476
|
+
const unregisterEventsFlush = scheduler.register(
|
|
14477
|
+
() => {
|
|
14478
|
+
const state2 = store.getState();
|
|
14479
|
+
state2.events.flush?.();
|
|
14480
|
+
},
|
|
14481
|
+
{
|
|
14482
|
+
id: `${newRootId}_events`,
|
|
14483
|
+
rootId: newRootId,
|
|
14484
|
+
phase: "input",
|
|
14485
|
+
system: true
|
|
14486
|
+
}
|
|
14487
|
+
);
|
|
15254
14488
|
const unregisterFrustum = scheduler.register(
|
|
15255
14489
|
() => {
|
|
15256
14490
|
const state2 = store.getState();
|
|
@@ -15261,7 +14495,7 @@ function createRoot(canvas) {
|
|
|
15261
14495
|
{
|
|
15262
14496
|
id: `${newRootId}_frustum`,
|
|
15263
14497
|
rootId: newRootId,
|
|
15264
|
-
|
|
14498
|
+
before: "render",
|
|
15265
14499
|
system: true
|
|
15266
14500
|
}
|
|
15267
14501
|
);
|
|
@@ -15273,7 +14507,7 @@ function createRoot(canvas) {
|
|
|
15273
14507
|
{
|
|
15274
14508
|
id: `${newRootId}_visibility`,
|
|
15275
14509
|
rootId: newRootId,
|
|
15276
|
-
|
|
14510
|
+
before: "render",
|
|
15277
14511
|
system: true,
|
|
15278
14512
|
after: `${newRootId}_frustum`
|
|
15279
14513
|
}
|
|
@@ -15285,7 +14519,7 @@ function createRoot(canvas) {
|
|
|
15285
14519
|
const userHandlesRender = scheduler.hasUserJobsInPhase("render", newRootId);
|
|
15286
14520
|
if (userHandlesRender || state2.internal.priority) return;
|
|
15287
14521
|
try {
|
|
15288
|
-
if (state2.
|
|
14522
|
+
if (state2.renderPipeline?.render) state2.renderPipeline.render();
|
|
15289
14523
|
else if (renderer2?.render) renderer2.render(state2.scene, state2.camera);
|
|
15290
14524
|
} catch (error) {
|
|
15291
14525
|
state2.setError(error instanceof Error ? error : new Error(String(error)));
|
|
@@ -15310,6 +14544,7 @@ function createRoot(canvas) {
|
|
|
15310
14544
|
unregisterRoot: () => {
|
|
15311
14545
|
unregisterRoot();
|
|
15312
14546
|
unregisterCanvasTarget();
|
|
14547
|
+
unregisterEventsFlush();
|
|
15313
14548
|
unregisterFrustum();
|
|
15314
14549
|
unregisterVisibility();
|
|
15315
14550
|
unregisterRender();
|
|
@@ -15448,6 +14683,7 @@ function PortalInner({ state = {}, children, container }) {
|
|
|
15448
14683
|
viewport = rootState.viewport.getCurrentViewport(camera, new Vector3(), resolvedSize);
|
|
15449
14684
|
if (camera !== rootState.camera) updateCamera(camera, resolvedSize);
|
|
15450
14685
|
}
|
|
14686
|
+
const set = injectState.set;
|
|
15451
14687
|
return {
|
|
15452
14688
|
// The intersect consists of the previous root state
|
|
15453
14689
|
...rootState,
|
|
@@ -15466,7 +14702,7 @@ function PortalInner({ state = {}, children, container }) {
|
|
|
15466
14702
|
size: resolvedSize,
|
|
15467
14703
|
viewport: { ...rootState.viewport, ...viewport },
|
|
15468
14704
|
// Layers are allowed to override events
|
|
15469
|
-
setEvents: (events2) =>
|
|
14705
|
+
setEvents: (events2) => set((state2) => ({ ...state2, events: { ...state2.events, ...events2 } })),
|
|
15470
14706
|
// Container for child attachment - the portalScene (injected or container itself)
|
|
15471
14707
|
internal: { ...rootState.internal, ...injectState.internal, container: portalScene }
|
|
15472
14708
|
};
|
|
@@ -15475,9 +14711,13 @@ function PortalInner({ state = {}, children, container }) {
|
|
|
15475
14711
|
const store = createWithEqualityFn((set, get) => ({ ...rest, set, get }));
|
|
15476
14712
|
const onMutate = (prev) => store.setState((state2) => inject.current(prev, state2));
|
|
15477
14713
|
onMutate(previousRoot.getState());
|
|
15478
|
-
previousRoot.subscribe(onMutate);
|
|
15479
14714
|
return store;
|
|
15480
14715
|
}, [previousRoot, container]);
|
|
14716
|
+
useIsomorphicLayoutEffect(() => {
|
|
14717
|
+
const onMutate = (prev) => usePortalStore.setState((state2) => inject.current(prev, state2));
|
|
14718
|
+
const unsubscribe = previousRoot.subscribe(onMutate);
|
|
14719
|
+
return unsubscribe;
|
|
14720
|
+
}, [previousRoot, usePortalStore]);
|
|
15481
14721
|
return (
|
|
15482
14722
|
// @ts-ignore, reconciler types are not maintained
|
|
15483
14723
|
/* @__PURE__ */ jsx(Fragment, { children: reconciler.createPortal(
|
|
@@ -15491,6 +14731,59 @@ function flushSync(fn) {
|
|
|
15491
14731
|
return reconciler.flushSyncFromReconciler(fn);
|
|
15492
14732
|
}
|
|
15493
14733
|
|
|
14734
|
+
function parseBackground(background) {
|
|
14735
|
+
if (!background) return null;
|
|
14736
|
+
if (typeof background === "object" && !background.isColor) {
|
|
14737
|
+
const { backgroundMap, envMap, files, preset, ...rest } = background;
|
|
14738
|
+
return {
|
|
14739
|
+
...rest,
|
|
14740
|
+
preset,
|
|
14741
|
+
files: envMap || files,
|
|
14742
|
+
backgroundFiles: backgroundMap,
|
|
14743
|
+
background: true
|
|
14744
|
+
};
|
|
14745
|
+
}
|
|
14746
|
+
if (typeof background === "number") {
|
|
14747
|
+
return { color: background, background: true };
|
|
14748
|
+
}
|
|
14749
|
+
if (typeof background === "string") {
|
|
14750
|
+
if (background in presetsObj) {
|
|
14751
|
+
return { preset: background, background: true };
|
|
14752
|
+
}
|
|
14753
|
+
if (/^(https?:\/\/|\/|\.\/|\.\.\/)|\.(hdr|exr|jpg|jpeg|png|webp|gif)$/i.test(background)) {
|
|
14754
|
+
return { files: background, background: true };
|
|
14755
|
+
}
|
|
14756
|
+
return { color: background, background: true };
|
|
14757
|
+
}
|
|
14758
|
+
if (background.isColor) {
|
|
14759
|
+
return { color: background, background: true };
|
|
14760
|
+
}
|
|
14761
|
+
return null;
|
|
14762
|
+
}
|
|
14763
|
+
|
|
14764
|
+
function parseRendererConfig(rendererProp) {
|
|
14765
|
+
const isRendererConfig = typeof rendererProp === "object" && rendererProp !== null && !("render" in rendererProp) && ("primaryCanvas" in rendererProp || "scheduler" in rendererProp);
|
|
14766
|
+
if (isRendererConfig) {
|
|
14767
|
+
const { primaryCanvas: pc, scheduler: sc, ...rest } = rendererProp;
|
|
14768
|
+
return {
|
|
14769
|
+
primaryCanvas: pc,
|
|
14770
|
+
scheduler: sc,
|
|
14771
|
+
renderer: Object.keys(rest).length > 0 ? rest : rendererProp
|
|
14772
|
+
};
|
|
14773
|
+
}
|
|
14774
|
+
return { primaryCanvas: void 0, scheduler: void 0, renderer: rendererProp };
|
|
14775
|
+
}
|
|
14776
|
+
|
|
14777
|
+
function clearHmrCaches(store) {
|
|
14778
|
+
store.setState((state) => ({
|
|
14779
|
+
nodes: {},
|
|
14780
|
+
uniforms: {},
|
|
14781
|
+
buffers: {},
|
|
14782
|
+
gpuStorage: {},
|
|
14783
|
+
_hmrVersion: state._hmrVersion + 1
|
|
14784
|
+
}));
|
|
14785
|
+
}
|
|
14786
|
+
|
|
15494
14787
|
function CanvasImpl({
|
|
15495
14788
|
ref,
|
|
15496
14789
|
children,
|
|
@@ -15511,6 +14804,8 @@ function CanvasImpl({
|
|
|
15511
14804
|
raycaster,
|
|
15512
14805
|
camera,
|
|
15513
14806
|
scene,
|
|
14807
|
+
autoUpdateFrustum,
|
|
14808
|
+
occlusion,
|
|
15514
14809
|
onPointerMissed,
|
|
15515
14810
|
onDragOverMissed,
|
|
15516
14811
|
onDropMissed,
|
|
@@ -15522,39 +14817,10 @@ function CanvasImpl({
|
|
|
15522
14817
|
forceEven,
|
|
15523
14818
|
...props
|
|
15524
14819
|
}) {
|
|
15525
|
-
const { primaryCanvas, scheduler,
|
|
15526
|
-
const renderer = Object.keys(rendererConfig).length > 0 ? rendererConfig : rendererProp;
|
|
14820
|
+
const { primaryCanvas, scheduler, renderer } = parseRendererConfig(rendererProp);
|
|
15527
14821
|
React.useMemo(() => extend(THREE), []);
|
|
15528
14822
|
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]);
|
|
14823
|
+
const backgroundProps = React.useMemo(() => parseBackground(background), [background]);
|
|
15558
14824
|
const hasInitialSizeRef = React.useRef(false);
|
|
15559
14825
|
const measureConfig = React.useMemo(() => {
|
|
15560
14826
|
if (!hasInitialSizeRef.current) {
|
|
@@ -15596,6 +14862,7 @@ function CanvasImpl({
|
|
|
15596
14862
|
const handleDropMissed = useMutableCallback(onDropMissed);
|
|
15597
14863
|
const [block, setBlock] = React.useState(false);
|
|
15598
14864
|
const [error, setError] = React.useState(false);
|
|
14865
|
+
const [fallbackVisible, setFallbackVisible] = React.useState(false);
|
|
15599
14866
|
if (block) throw block;
|
|
15600
14867
|
if (error) throw error;
|
|
15601
14868
|
const root = React.useRef(null);
|
|
@@ -15603,6 +14870,7 @@ function CanvasImpl({
|
|
|
15603
14870
|
const unsubscribeErrorRef = React.useRef(null);
|
|
15604
14871
|
useIsomorphicLayoutEffect(() => {
|
|
15605
14872
|
effectActiveRef.current = true;
|
|
14873
|
+
if (fallbackVisible) return;
|
|
15606
14874
|
const canvas = canvasRef.current;
|
|
15607
14875
|
if (effectiveSize.width > 0 && effectiveSize.height > 0 && canvas) {
|
|
15608
14876
|
if (!root.current) {
|
|
@@ -15623,7 +14891,7 @@ function CanvasImpl({
|
|
|
15623
14891
|
}
|
|
15624
14892
|
async function run() {
|
|
15625
14893
|
if (!effectActiveRef.current || !root.current) return;
|
|
15626
|
-
await root.current.configure({
|
|
14894
|
+
const configured = await root.current.configure({
|
|
15627
14895
|
id,
|
|
15628
14896
|
primaryCanvas,
|
|
15629
14897
|
scheduler,
|
|
@@ -15638,6 +14906,8 @@ function CanvasImpl({
|
|
|
15638
14906
|
performance,
|
|
15639
14907
|
raycaster,
|
|
15640
14908
|
camera,
|
|
14909
|
+
autoUpdateFrustum,
|
|
14910
|
+
occlusion,
|
|
15641
14911
|
size: effectiveSize,
|
|
15642
14912
|
// Store size props for reset functionality
|
|
15643
14913
|
_sizeProps: width !== void 0 || height !== void 0 ? { width, height } : null,
|
|
@@ -15662,8 +14932,20 @@ function CanvasImpl({
|
|
|
15662
14932
|
}
|
|
15663
14933
|
onCreated?.(state);
|
|
15664
14934
|
}
|
|
15665
|
-
})
|
|
15666
|
-
|
|
14935
|
+
}).then(
|
|
14936
|
+
() => true,
|
|
14937
|
+
// Renderer setup failed (e.g. no WebGL/WebGPU support). The `fallback` prop lives
|
|
14938
|
+
// inside <canvas>, which browsers don't display, so surface it as visible DOM
|
|
14939
|
+
// instead; with no fallback, rethrow to an external error boundary. (#3757)
|
|
14940
|
+
(setupError) => {
|
|
14941
|
+
if (effectActiveRef.current) {
|
|
14942
|
+
if (fallback != null) setFallbackVisible(true);
|
|
14943
|
+
else setError(setupError);
|
|
14944
|
+
}
|
|
14945
|
+
return false;
|
|
14946
|
+
}
|
|
14947
|
+
);
|
|
14948
|
+
if (!configured || !effectActiveRef.current || !root.current) return;
|
|
15667
14949
|
root.current.render(
|
|
15668
14950
|
/* @__PURE__ */ jsx(Bridge, { children: /* @__PURE__ */ jsx(ErrorBoundary, { set: setError, children: /* @__PURE__ */ jsxs(React.Suspense, { fallback: /* @__PURE__ */ jsx(Block, { set: setBlock }), children: [
|
|
15669
14951
|
backgroundProps && /* @__PURE__ */ jsx(Environment, { ...backgroundProps }),
|
|
@@ -15697,20 +14979,13 @@ function CanvasImpl({
|
|
|
15697
14979
|
const handleHMR = () => {
|
|
15698
14980
|
queueMicrotask(() => {
|
|
15699
14981
|
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
|
-
}
|
|
14982
|
+
if (rootEntry?.store) clearHmrCaches(rootEntry.store);
|
|
15707
14983
|
});
|
|
15708
14984
|
};
|
|
15709
14985
|
if (typeof import.meta !== "undefined" && import.meta.hot) {
|
|
15710
14986
|
const hot = import.meta.hot;
|
|
15711
14987
|
hot.on("vite:afterUpdate", handleHMR);
|
|
15712
|
-
return () => hot.
|
|
15713
|
-
});
|
|
14988
|
+
return () => hot.off?.("vite:afterUpdate", handleHMR);
|
|
15714
14989
|
}
|
|
15715
14990
|
if (typeof module !== "undefined" && module.hot) {
|
|
15716
14991
|
const hot = module.hot;
|
|
@@ -15733,7 +15008,20 @@ function CanvasImpl({
|
|
|
15733
15008
|
...style
|
|
15734
15009
|
},
|
|
15735
15010
|
...props,
|
|
15736
|
-
children:
|
|
15011
|
+
children: fallbackVisible ? (
|
|
15012
|
+
// Renderer setup failed: render the fallback as visible DOM. Inside <canvas> (below)
|
|
15013
|
+
// it exists in the tree but browsers never paint it, which is the whole bug (#3757).
|
|
15014
|
+
fallback
|
|
15015
|
+
) : /* @__PURE__ */ jsx("div", { ref: containerRef, className: "r3f-canvas-container", style: { width: "100%", height: "100%" }, children: /* @__PURE__ */ jsx(
|
|
15016
|
+
"canvas",
|
|
15017
|
+
{
|
|
15018
|
+
ref: canvasRef,
|
|
15019
|
+
id,
|
|
15020
|
+
className: "r3f-canvas",
|
|
15021
|
+
style: { display: "block", width: "100%", height: "100%" },
|
|
15022
|
+
children: fallback
|
|
15023
|
+
}
|
|
15024
|
+
) })
|
|
15737
15025
|
}
|
|
15738
15026
|
);
|
|
15739
15027
|
}
|
|
@@ -15809,6 +15097,8 @@ function createScopedStore(data) {
|
|
|
15809
15097
|
function createLazyCreatorState(state) {
|
|
15810
15098
|
let _uniforms = null;
|
|
15811
15099
|
let _nodes = null;
|
|
15100
|
+
let _buffers = null;
|
|
15101
|
+
let _gpuStorage = null;
|
|
15812
15102
|
return Object.create(state, {
|
|
15813
15103
|
uniforms: {
|
|
15814
15104
|
get() {
|
|
@@ -15819,10 +15109,34 @@ function createLazyCreatorState(state) {
|
|
|
15819
15109
|
get() {
|
|
15820
15110
|
return _nodes ?? (_nodes = createScopedStore(state.nodes));
|
|
15821
15111
|
}
|
|
15112
|
+
},
|
|
15113
|
+
buffers: {
|
|
15114
|
+
get() {
|
|
15115
|
+
return _buffers ?? (_buffers = createScopedStore(state.buffers));
|
|
15116
|
+
}
|
|
15117
|
+
},
|
|
15118
|
+
gpuStorage: {
|
|
15119
|
+
get() {
|
|
15120
|
+
return _gpuStorage ?? (_gpuStorage = createScopedStore(state.gpuStorage));
|
|
15121
|
+
}
|
|
15822
15122
|
}
|
|
15823
15123
|
});
|
|
15824
15124
|
}
|
|
15825
15125
|
|
|
15126
|
+
function resolvePrimary(local) {
|
|
15127
|
+
return local.getState().primaryStore ?? local;
|
|
15128
|
+
}
|
|
15129
|
+
function usePrimaryStore() {
|
|
15130
|
+
const local = useStore();
|
|
15131
|
+
const primary = local.getState().primaryStore;
|
|
15132
|
+
return useMemo(() => primary ?? local, [primary, local]);
|
|
15133
|
+
}
|
|
15134
|
+
function usePrimaryThree(selector = (state) => state, equalityFn) {
|
|
15135
|
+
const local = useStore();
|
|
15136
|
+
const primary = resolvePrimary(local);
|
|
15137
|
+
return primary(selector, equalityFn);
|
|
15138
|
+
}
|
|
15139
|
+
|
|
15826
15140
|
function addTexture(set, key, value) {
|
|
15827
15141
|
set((state) => {
|
|
15828
15142
|
const newMap = new Map(state.textures);
|
|
@@ -15901,6 +15215,10 @@ function vectorize(inObject) {
|
|
|
15901
15215
|
if (obj.isVector2 || obj.isVector3 || obj.isVector4) return inObject;
|
|
15902
15216
|
if (obj.isMatrix3 || obj.isMatrix4) return inObject;
|
|
15903
15217
|
if (obj.isColor || obj.isEuler || obj.isQuaternion || obj.isSpherical) return inObject;
|
|
15218
|
+
if ("r" in obj && "g" in obj && "b" in obj && typeof obj.r === "number" && typeof obj.g === "number" && typeof obj.b === "number") {
|
|
15219
|
+
const scale = obj.r > 1 || obj.g > 1 || obj.b > 1 ? 1 / 255 : 1;
|
|
15220
|
+
return new Color(obj.r * scale, obj.g * scale, obj.b * scale);
|
|
15221
|
+
}
|
|
15904
15222
|
if ("x" in obj && "y" in obj && typeof obj.x === "number" && typeof obj.y === "number") {
|
|
15905
15223
|
if ("w" in obj && typeof obj.w === "number" && "z" in obj && typeof obj.z === "number") {
|
|
15906
15224
|
return new Vector4(obj.x, obj.y, obj.z, obj.w);
|
|
@@ -15924,7 +15242,7 @@ function useCompareMemoize(value, deep) {
|
|
|
15924
15242
|
|
|
15925
15243
|
const isUniformNode$1 = (value) => value !== null && typeof value === "object" && "value" in value && "uuid" in value;
|
|
15926
15244
|
function useUniforms(creatorOrScope, scope) {
|
|
15927
|
-
const store =
|
|
15245
|
+
const store = usePrimaryStore();
|
|
15928
15246
|
const removeUniforms2 = useCallback(
|
|
15929
15247
|
(names, targetScope) => {
|
|
15930
15248
|
const nameArray = Array.isArray(names) ? names : [names];
|
|
@@ -15994,8 +15312,8 @@ function useUniforms(creatorOrScope, scope) {
|
|
|
15994
15312
|
}, [creatorOrScope, store]);
|
|
15995
15313
|
const memoizedInput = useCompareMemoize(inputForMemoization);
|
|
15996
15314
|
const isReader = memoizedInput === void 0 || typeof memoizedInput === "string";
|
|
15997
|
-
const storeUniforms =
|
|
15998
|
-
const hmrVersion =
|
|
15315
|
+
const storeUniforms = usePrimaryThree((s) => s.uniforms);
|
|
15316
|
+
const hmrVersion = usePrimaryThree((s) => s._hmrVersion);
|
|
15999
15317
|
const readerDep = isReader ? storeUniforms : null;
|
|
16000
15318
|
const creatorDep = isReader ? null : hmrVersion;
|
|
16001
15319
|
const uniforms = useMemo(() => {
|
|
@@ -16056,6 +15374,7 @@ function useUniforms(creatorOrScope, scope) {
|
|
|
16056
15374
|
return { ...uniforms, removeUniforms: removeUniforms2, clearUniforms, rebuildUniforms };
|
|
16057
15375
|
}
|
|
16058
15376
|
function rebuildAllUniforms(store, scope) {
|
|
15377
|
+
store = store.getState().primaryStore ?? store;
|
|
16059
15378
|
store.setState((state) => {
|
|
16060
15379
|
let newUniforms = {};
|
|
16061
15380
|
if (scope && scope !== "root") {
|
|
@@ -16184,7 +15503,7 @@ function useUniform(name, value) {
|
|
|
16184
15503
|
|
|
16185
15504
|
const isTSLNode = (value) => value !== null && typeof value === "object" && ("uuid" in value || "nodeType" in value);
|
|
16186
15505
|
function useNodes(creatorOrScope, scope) {
|
|
16187
|
-
const store =
|
|
15506
|
+
const store = usePrimaryStore();
|
|
16188
15507
|
const removeNodes2 = useCallback(
|
|
16189
15508
|
(names, targetScope) => {
|
|
16190
15509
|
const nameArray = Array.isArray(names) ? names : [names];
|
|
@@ -16241,8 +15560,8 @@ function useNodes(creatorOrScope, scope) {
|
|
|
16241
15560
|
[store]
|
|
16242
15561
|
);
|
|
16243
15562
|
const isReader = creatorOrScope === void 0 || typeof creatorOrScope === "string";
|
|
16244
|
-
const storeNodes =
|
|
16245
|
-
const hmrVersion =
|
|
15563
|
+
const storeNodes = usePrimaryThree((s) => s.nodes);
|
|
15564
|
+
const hmrVersion = usePrimaryThree((s) => s._hmrVersion);
|
|
16246
15565
|
const scopeDep = typeof creatorOrScope === "string" ? creatorOrScope : scope;
|
|
16247
15566
|
const readerDep = isReader ? storeNodes : null;
|
|
16248
15567
|
const creatorDep = isReader ? null : hmrVersion;
|
|
@@ -16301,6 +15620,7 @@ function useNodes(creatorOrScope, scope) {
|
|
|
16301
15620
|
return { ...nodes, removeNodes: removeNodes2, clearNodes, rebuildNodes };
|
|
16302
15621
|
}
|
|
16303
15622
|
function rebuildAllNodes(store, scope) {
|
|
15623
|
+
store = store.getState().primaryStore ?? store;
|
|
16304
15624
|
store.setState((state) => {
|
|
16305
15625
|
let newNodes = state.nodes;
|
|
16306
15626
|
if (scope && scope !== "root") {
|
|
@@ -16345,18 +15665,364 @@ function clearRootNodes(set) {
|
|
|
16345
15665
|
});
|
|
16346
15666
|
}
|
|
16347
15667
|
function useLocalNodes(creator) {
|
|
16348
|
-
const store =
|
|
16349
|
-
const uniforms =
|
|
16350
|
-
const nodes =
|
|
16351
|
-
const textures =
|
|
16352
|
-
const hmrVersion =
|
|
15668
|
+
const store = usePrimaryStore();
|
|
15669
|
+
const uniforms = usePrimaryThree((s) => s.uniforms);
|
|
15670
|
+
const nodes = usePrimaryThree((s) => s.nodes);
|
|
15671
|
+
const textures = usePrimaryThree((s) => s.textures);
|
|
15672
|
+
const hmrVersion = usePrimaryThree((s) => s._hmrVersion);
|
|
16353
15673
|
return useMemo(() => {
|
|
16354
15674
|
const wrappedState = createLazyCreatorState(store.getState());
|
|
16355
15675
|
return creator(wrappedState);
|
|
16356
15676
|
}, [store, creator, uniforms, nodes, textures, hmrVersion]);
|
|
16357
15677
|
}
|
|
16358
15678
|
|
|
16359
|
-
|
|
15679
|
+
const isBufferLike = (value) => {
|
|
15680
|
+
if (value === null || typeof value !== "object") return false;
|
|
15681
|
+
if (ArrayBuffer.isView(value)) return true;
|
|
15682
|
+
if ("isBufferAttribute" in value) return true;
|
|
15683
|
+
if ("uuid" in value || "nodeType" in value) return true;
|
|
15684
|
+
return false;
|
|
15685
|
+
};
|
|
15686
|
+
const disposeBuffer = (buffer) => {
|
|
15687
|
+
if (buffer === null || typeof buffer !== "object") return;
|
|
15688
|
+
if ("dispose" in buffer && typeof buffer.dispose === "function") {
|
|
15689
|
+
buffer.dispose();
|
|
15690
|
+
}
|
|
15691
|
+
};
|
|
15692
|
+
function useBuffers(creatorOrScope, scope) {
|
|
15693
|
+
const store = usePrimaryStore();
|
|
15694
|
+
const removeBuffers = useCallback(
|
|
15695
|
+
(names, targetScope) => {
|
|
15696
|
+
const nameArray = Array.isArray(names) ? names : [names];
|
|
15697
|
+
store.setState((state) => {
|
|
15698
|
+
if (targetScope) {
|
|
15699
|
+
const currentScope = { ...state.buffers[targetScope] };
|
|
15700
|
+
for (const name of nameArray) delete currentScope[name];
|
|
15701
|
+
return { buffers: { ...state.buffers, [targetScope]: currentScope } };
|
|
15702
|
+
}
|
|
15703
|
+
const buffers2 = { ...state.buffers };
|
|
15704
|
+
for (const name of nameArray) if (isBufferLike(buffers2[name])) delete buffers2[name];
|
|
15705
|
+
return { buffers: buffers2 };
|
|
15706
|
+
});
|
|
15707
|
+
},
|
|
15708
|
+
[store]
|
|
15709
|
+
);
|
|
15710
|
+
const clearBuffers = useCallback(
|
|
15711
|
+
(targetScope) => {
|
|
15712
|
+
store.setState((state) => {
|
|
15713
|
+
if (targetScope && targetScope !== "root") {
|
|
15714
|
+
const { [targetScope]: _, ...rest } = state.buffers;
|
|
15715
|
+
return { buffers: rest };
|
|
15716
|
+
}
|
|
15717
|
+
if (targetScope === "root") {
|
|
15718
|
+
const buffers2 = {};
|
|
15719
|
+
for (const [key, value] of Object.entries(state.buffers)) {
|
|
15720
|
+
if (!isBufferLike(value)) buffers2[key] = value;
|
|
15721
|
+
}
|
|
15722
|
+
return { buffers: buffers2 };
|
|
15723
|
+
}
|
|
15724
|
+
return { buffers: {} };
|
|
15725
|
+
});
|
|
15726
|
+
},
|
|
15727
|
+
[store]
|
|
15728
|
+
);
|
|
15729
|
+
const rebuildBuffers = useCallback(
|
|
15730
|
+
(targetScope) => {
|
|
15731
|
+
store.setState((state) => {
|
|
15732
|
+
let newBuffers = state.buffers;
|
|
15733
|
+
if (targetScope && targetScope !== "root") {
|
|
15734
|
+
const { [targetScope]: _, ...rest } = state.buffers;
|
|
15735
|
+
newBuffers = rest;
|
|
15736
|
+
} else if (targetScope === "root") {
|
|
15737
|
+
newBuffers = {};
|
|
15738
|
+
for (const [key, value] of Object.entries(state.buffers)) {
|
|
15739
|
+
if (!isBufferLike(value)) newBuffers[key] = value;
|
|
15740
|
+
}
|
|
15741
|
+
} else {
|
|
15742
|
+
newBuffers = {};
|
|
15743
|
+
}
|
|
15744
|
+
return { buffers: newBuffers, _hmrVersion: state._hmrVersion + 1 };
|
|
15745
|
+
});
|
|
15746
|
+
},
|
|
15747
|
+
[store]
|
|
15748
|
+
);
|
|
15749
|
+
const disposeBuffers = useCallback(
|
|
15750
|
+
(names, targetScope) => {
|
|
15751
|
+
const nameArray = Array.isArray(names) ? names : [names];
|
|
15752
|
+
const state = store.getState();
|
|
15753
|
+
for (const name of nameArray) {
|
|
15754
|
+
const buffer = targetScope ? state.buffers[targetScope]?.[name] : state.buffers[name];
|
|
15755
|
+
if (buffer && isBufferLike(buffer)) {
|
|
15756
|
+
disposeBuffer(buffer);
|
|
15757
|
+
}
|
|
15758
|
+
}
|
|
15759
|
+
removeBuffers(names, targetScope);
|
|
15760
|
+
},
|
|
15761
|
+
[store, removeBuffers]
|
|
15762
|
+
);
|
|
15763
|
+
const isReader = creatorOrScope === void 0 || typeof creatorOrScope === "string";
|
|
15764
|
+
const storeBuffers = usePrimaryThree((s) => s.buffers);
|
|
15765
|
+
const hmrVersion = usePrimaryThree((s) => s._hmrVersion);
|
|
15766
|
+
const scopeDep = typeof creatorOrScope === "string" ? creatorOrScope : scope;
|
|
15767
|
+
const readerDep = isReader ? storeBuffers : null;
|
|
15768
|
+
const creatorDep = isReader ? null : hmrVersion;
|
|
15769
|
+
const buffers = useMemo(() => {
|
|
15770
|
+
if (creatorOrScope === void 0) {
|
|
15771
|
+
return storeBuffers;
|
|
15772
|
+
}
|
|
15773
|
+
if (typeof creatorOrScope === "string") {
|
|
15774
|
+
const scopeData = storeBuffers[creatorOrScope];
|
|
15775
|
+
if (scopeData && !isBufferLike(scopeData)) return scopeData;
|
|
15776
|
+
return {};
|
|
15777
|
+
}
|
|
15778
|
+
const state = store.getState();
|
|
15779
|
+
const set = store.setState;
|
|
15780
|
+
const creator = creatorOrScope;
|
|
15781
|
+
const wrappedState = createLazyCreatorState(state);
|
|
15782
|
+
const created = creator(wrappedState);
|
|
15783
|
+
const result = {};
|
|
15784
|
+
let hasNewBuffers = false;
|
|
15785
|
+
if (scope) {
|
|
15786
|
+
const currentScope = state.buffers[scope] ?? {};
|
|
15787
|
+
for (const [name, buffer] of Object.entries(created)) {
|
|
15788
|
+
if (currentScope[name]) {
|
|
15789
|
+
result[name] = currentScope[name];
|
|
15790
|
+
} else {
|
|
15791
|
+
if ("setName" in buffer && typeof buffer.setName === "function") {
|
|
15792
|
+
buffer.setName(`${scope}.${name}`);
|
|
15793
|
+
}
|
|
15794
|
+
result[name] = buffer;
|
|
15795
|
+
hasNewBuffers = true;
|
|
15796
|
+
}
|
|
15797
|
+
}
|
|
15798
|
+
if (hasNewBuffers) {
|
|
15799
|
+
set((s) => ({
|
|
15800
|
+
buffers: {
|
|
15801
|
+
...s.buffers,
|
|
15802
|
+
[scope]: { ...s.buffers[scope], ...result }
|
|
15803
|
+
}
|
|
15804
|
+
}));
|
|
15805
|
+
}
|
|
15806
|
+
return result;
|
|
15807
|
+
}
|
|
15808
|
+
for (const [name, buffer] of Object.entries(created)) {
|
|
15809
|
+
const existing = state.buffers[name];
|
|
15810
|
+
if (existing && isBufferLike(existing)) {
|
|
15811
|
+
result[name] = existing;
|
|
15812
|
+
} else {
|
|
15813
|
+
if ("setName" in buffer && typeof buffer.setName === "function") {
|
|
15814
|
+
buffer.setName(name);
|
|
15815
|
+
}
|
|
15816
|
+
result[name] = buffer;
|
|
15817
|
+
hasNewBuffers = true;
|
|
15818
|
+
}
|
|
15819
|
+
}
|
|
15820
|
+
if (hasNewBuffers) {
|
|
15821
|
+
set((s) => ({ buffers: { ...s.buffers, ...result } }));
|
|
15822
|
+
}
|
|
15823
|
+
return result;
|
|
15824
|
+
}, [store, scopeDep, readerDep, creatorDep]);
|
|
15825
|
+
return { ...buffers, removeBuffers, clearBuffers, rebuildBuffers, disposeBuffers };
|
|
15826
|
+
}
|
|
15827
|
+
function rebuildAllBuffers(store, scope) {
|
|
15828
|
+
store = store.getState().primaryStore ?? store;
|
|
15829
|
+
store.setState((state) => {
|
|
15830
|
+
let newBuffers = state.buffers;
|
|
15831
|
+
if (scope && scope !== "root") {
|
|
15832
|
+
const { [scope]: _, ...rest } = state.buffers;
|
|
15833
|
+
newBuffers = rest;
|
|
15834
|
+
} else if (scope === "root") {
|
|
15835
|
+
newBuffers = {};
|
|
15836
|
+
for (const [key, value] of Object.entries(state.buffers)) {
|
|
15837
|
+
if (!isBufferLike(value)) newBuffers[key] = value;
|
|
15838
|
+
}
|
|
15839
|
+
} else {
|
|
15840
|
+
newBuffers = {};
|
|
15841
|
+
}
|
|
15842
|
+
return { buffers: newBuffers, _hmrVersion: state._hmrVersion + 1 };
|
|
15843
|
+
});
|
|
15844
|
+
}
|
|
15845
|
+
|
|
15846
|
+
const isStorageLike = (value) => {
|
|
15847
|
+
if (value === null || typeof value !== "object") return false;
|
|
15848
|
+
if ("isTexture" in value) return true;
|
|
15849
|
+
if ("isData3DTexture" in value) return true;
|
|
15850
|
+
if ("uuid" in value || "nodeType" in value) return true;
|
|
15851
|
+
return false;
|
|
15852
|
+
};
|
|
15853
|
+
const disposeStorage = (storage) => {
|
|
15854
|
+
if (storage === null || typeof storage !== "object") return;
|
|
15855
|
+
if ("dispose" in storage && typeof storage.dispose === "function") {
|
|
15856
|
+
storage.dispose();
|
|
15857
|
+
}
|
|
15858
|
+
};
|
|
15859
|
+
function useGPUStorage(creatorOrScope, scope) {
|
|
15860
|
+
const store = usePrimaryStore();
|
|
15861
|
+
const removeStorage = useCallback(
|
|
15862
|
+
(names, targetScope) => {
|
|
15863
|
+
const nameArray = Array.isArray(names) ? names : [names];
|
|
15864
|
+
store.setState((state) => {
|
|
15865
|
+
if (targetScope) {
|
|
15866
|
+
const currentScope = { ...state.gpuStorage[targetScope] };
|
|
15867
|
+
for (const name of nameArray) delete currentScope[name];
|
|
15868
|
+
return { gpuStorage: { ...state.gpuStorage, [targetScope]: currentScope } };
|
|
15869
|
+
}
|
|
15870
|
+
const gpuStorage2 = { ...state.gpuStorage };
|
|
15871
|
+
for (const name of nameArray) if (isStorageLike(gpuStorage2[name])) delete gpuStorage2[name];
|
|
15872
|
+
return { gpuStorage: gpuStorage2 };
|
|
15873
|
+
});
|
|
15874
|
+
},
|
|
15875
|
+
[store]
|
|
15876
|
+
);
|
|
15877
|
+
const clearStorage = useCallback(
|
|
15878
|
+
(targetScope) => {
|
|
15879
|
+
store.setState((state) => {
|
|
15880
|
+
if (targetScope && targetScope !== "root") {
|
|
15881
|
+
const { [targetScope]: _, ...rest } = state.gpuStorage;
|
|
15882
|
+
return { gpuStorage: rest };
|
|
15883
|
+
}
|
|
15884
|
+
if (targetScope === "root") {
|
|
15885
|
+
const gpuStorage2 = {};
|
|
15886
|
+
for (const [key, value] of Object.entries(state.gpuStorage)) {
|
|
15887
|
+
if (!isStorageLike(value)) gpuStorage2[key] = value;
|
|
15888
|
+
}
|
|
15889
|
+
return { gpuStorage: gpuStorage2 };
|
|
15890
|
+
}
|
|
15891
|
+
return { gpuStorage: {} };
|
|
15892
|
+
});
|
|
15893
|
+
},
|
|
15894
|
+
[store]
|
|
15895
|
+
);
|
|
15896
|
+
const rebuildStorage = useCallback(
|
|
15897
|
+
(targetScope) => {
|
|
15898
|
+
store.setState((state) => {
|
|
15899
|
+
let newStorage = state.gpuStorage;
|
|
15900
|
+
if (targetScope && targetScope !== "root") {
|
|
15901
|
+
const { [targetScope]: _, ...rest } = state.gpuStorage;
|
|
15902
|
+
newStorage = rest;
|
|
15903
|
+
} else if (targetScope === "root") {
|
|
15904
|
+
newStorage = {};
|
|
15905
|
+
for (const [key, value] of Object.entries(state.gpuStorage)) {
|
|
15906
|
+
if (!isStorageLike(value)) newStorage[key] = value;
|
|
15907
|
+
}
|
|
15908
|
+
} else {
|
|
15909
|
+
newStorage = {};
|
|
15910
|
+
}
|
|
15911
|
+
return { gpuStorage: newStorage, _hmrVersion: state._hmrVersion + 1 };
|
|
15912
|
+
});
|
|
15913
|
+
},
|
|
15914
|
+
[store]
|
|
15915
|
+
);
|
|
15916
|
+
const disposeStorageFn = useCallback(
|
|
15917
|
+
(names, targetScope) => {
|
|
15918
|
+
const nameArray = Array.isArray(names) ? names : [names];
|
|
15919
|
+
const state = store.getState();
|
|
15920
|
+
for (const name of nameArray) {
|
|
15921
|
+
const storage = targetScope ? state.gpuStorage[targetScope]?.[name] : state.gpuStorage[name];
|
|
15922
|
+
if (storage && isStorageLike(storage)) {
|
|
15923
|
+
disposeStorage(storage);
|
|
15924
|
+
}
|
|
15925
|
+
}
|
|
15926
|
+
removeStorage(names, targetScope);
|
|
15927
|
+
},
|
|
15928
|
+
[store, removeStorage]
|
|
15929
|
+
);
|
|
15930
|
+
const isReader = creatorOrScope === void 0 || typeof creatorOrScope === "string";
|
|
15931
|
+
const storeStorage = usePrimaryThree((s) => s.gpuStorage);
|
|
15932
|
+
const hmrVersion = usePrimaryThree((s) => s._hmrVersion);
|
|
15933
|
+
const scopeDep = typeof creatorOrScope === "string" ? creatorOrScope : scope;
|
|
15934
|
+
const readerDep = isReader ? storeStorage : null;
|
|
15935
|
+
const creatorDep = isReader ? null : hmrVersion;
|
|
15936
|
+
const gpuStorage = useMemo(() => {
|
|
15937
|
+
if (creatorOrScope === void 0) {
|
|
15938
|
+
return storeStorage;
|
|
15939
|
+
}
|
|
15940
|
+
if (typeof creatorOrScope === "string") {
|
|
15941
|
+
const scopeData = storeStorage[creatorOrScope];
|
|
15942
|
+
if (scopeData && !isStorageLike(scopeData)) return scopeData;
|
|
15943
|
+
return {};
|
|
15944
|
+
}
|
|
15945
|
+
const state = store.getState();
|
|
15946
|
+
const set = store.setState;
|
|
15947
|
+
const creator = creatorOrScope;
|
|
15948
|
+
const wrappedState = createLazyCreatorState(state);
|
|
15949
|
+
const created = creator(wrappedState);
|
|
15950
|
+
const result = {};
|
|
15951
|
+
let hasNewStorage = false;
|
|
15952
|
+
if (scope) {
|
|
15953
|
+
const currentScope = state.gpuStorage[scope] ?? {};
|
|
15954
|
+
for (const [name, storage] of Object.entries(created)) {
|
|
15955
|
+
if (currentScope[name]) {
|
|
15956
|
+
result[name] = currentScope[name];
|
|
15957
|
+
} else {
|
|
15958
|
+
if ("setName" in storage && typeof storage.setName === "function") {
|
|
15959
|
+
storage.setName(`${scope}.${name}`);
|
|
15960
|
+
}
|
|
15961
|
+
if ("name" in storage && typeof storage.name === "string") {
|
|
15962
|
+
storage.name = `${scope}.${name}`;
|
|
15963
|
+
}
|
|
15964
|
+
result[name] = storage;
|
|
15965
|
+
hasNewStorage = true;
|
|
15966
|
+
}
|
|
15967
|
+
}
|
|
15968
|
+
if (hasNewStorage) {
|
|
15969
|
+
set((s) => ({
|
|
15970
|
+
gpuStorage: {
|
|
15971
|
+
...s.gpuStorage,
|
|
15972
|
+
[scope]: { ...s.gpuStorage[scope], ...result }
|
|
15973
|
+
}
|
|
15974
|
+
}));
|
|
15975
|
+
}
|
|
15976
|
+
return result;
|
|
15977
|
+
}
|
|
15978
|
+
for (const [name, storage] of Object.entries(created)) {
|
|
15979
|
+
const existing = state.gpuStorage[name];
|
|
15980
|
+
if (existing && isStorageLike(existing)) {
|
|
15981
|
+
result[name] = existing;
|
|
15982
|
+
} else {
|
|
15983
|
+
if ("setName" in storage && typeof storage.setName === "function") {
|
|
15984
|
+
storage.setName(name);
|
|
15985
|
+
}
|
|
15986
|
+
if ("name" in storage && typeof storage.name === "string") {
|
|
15987
|
+
storage.name = name;
|
|
15988
|
+
}
|
|
15989
|
+
result[name] = storage;
|
|
15990
|
+
hasNewStorage = true;
|
|
15991
|
+
}
|
|
15992
|
+
}
|
|
15993
|
+
if (hasNewStorage) {
|
|
15994
|
+
set((s) => ({ gpuStorage: { ...s.gpuStorage, ...result } }));
|
|
15995
|
+
}
|
|
15996
|
+
return result;
|
|
15997
|
+
}, [store, scopeDep, readerDep, creatorDep]);
|
|
15998
|
+
return {
|
|
15999
|
+
...gpuStorage,
|
|
16000
|
+
removeStorage,
|
|
16001
|
+
clearStorage,
|
|
16002
|
+
rebuildStorage,
|
|
16003
|
+
disposeStorage: disposeStorageFn
|
|
16004
|
+
};
|
|
16005
|
+
}
|
|
16006
|
+
function rebuildAllStorage(store, scope) {
|
|
16007
|
+
store = store.getState().primaryStore ?? store;
|
|
16008
|
+
store.setState((state) => {
|
|
16009
|
+
let newStorage = state.gpuStorage;
|
|
16010
|
+
if (scope && scope !== "root") {
|
|
16011
|
+
const { [scope]: _, ...rest } = state.gpuStorage;
|
|
16012
|
+
newStorage = rest;
|
|
16013
|
+
} else if (scope === "root") {
|
|
16014
|
+
newStorage = {};
|
|
16015
|
+
for (const [key, value] of Object.entries(state.gpuStorage)) {
|
|
16016
|
+
if (!isStorageLike(value)) newStorage[key] = value;
|
|
16017
|
+
}
|
|
16018
|
+
} else {
|
|
16019
|
+
newStorage = {};
|
|
16020
|
+
}
|
|
16021
|
+
return { gpuStorage: newStorage, _hmrVersion: state._hmrVersion + 1 };
|
|
16022
|
+
});
|
|
16023
|
+
}
|
|
16024
|
+
|
|
16025
|
+
function useRenderPipeline(mainCB, setupCB) {
|
|
16360
16026
|
const store = useStore();
|
|
16361
16027
|
const { scene, camera, renderer, isLegacy } = useThree();
|
|
16362
16028
|
const callbacksRanRef = useRef(false);
|
|
@@ -16375,7 +16041,7 @@ function usePostProcessing(mainCB, setupCB) {
|
|
|
16375
16041
|
}, [store]);
|
|
16376
16042
|
const reset = useCallback(() => {
|
|
16377
16043
|
store.setState({
|
|
16378
|
-
|
|
16044
|
+
renderPipeline: null,
|
|
16379
16045
|
passes: {}
|
|
16380
16046
|
});
|
|
16381
16047
|
callbacksRanRef.current = false;
|
|
@@ -16388,13 +16054,13 @@ function usePostProcessing(mainCB, setupCB) {
|
|
|
16388
16054
|
}, []);
|
|
16389
16055
|
useLayoutEffect(() => {
|
|
16390
16056
|
if (isLegacy) {
|
|
16391
|
-
throw new Error("
|
|
16057
|
+
throw new Error("useRenderPipeline is only available with WebGPU renderer. Set renderer prop on Canvas.");
|
|
16392
16058
|
}
|
|
16393
16059
|
if (!renderer || !scene || !camera) return;
|
|
16394
16060
|
const state = store.getState();
|
|
16395
16061
|
const set = store.setState;
|
|
16396
16062
|
try {
|
|
16397
|
-
let pp = state.
|
|
16063
|
+
let pp = state.renderPipeline;
|
|
16398
16064
|
let currentPasses = { ...state.passes };
|
|
16399
16065
|
let justCreatedPP = false;
|
|
16400
16066
|
if (!pp) {
|
|
@@ -16411,7 +16077,7 @@ function usePostProcessing(mainCB, setupCB) {
|
|
|
16411
16077
|
}
|
|
16412
16078
|
currentPasses.scenePass = scenePass;
|
|
16413
16079
|
if (!pp.outputNode || justCreatedPP) pp.outputNode = scenePass;
|
|
16414
|
-
set({
|
|
16080
|
+
set({ renderPipeline: pp, passes: currentPasses });
|
|
16415
16081
|
const shouldRunCallbacks = justCreatedPP || !callbacksRanRef.current || !cacheValid;
|
|
16416
16082
|
if (shouldRunCallbacks) {
|
|
16417
16083
|
if (setupCBRef.current) {
|
|
@@ -16433,22 +16099,22 @@ function usePostProcessing(mainCB, setupCB) {
|
|
|
16433
16099
|
callbacksRanRef.current = true;
|
|
16434
16100
|
}
|
|
16435
16101
|
} catch (error) {
|
|
16436
|
-
console.error("[
|
|
16102
|
+
console.error("[useRenderPipeline] Setup error:", error);
|
|
16437
16103
|
}
|
|
16438
16104
|
}, [store, renderer, scene, camera, isLegacy, rebuildVersion]);
|
|
16439
16105
|
const passes = useThree((s) => s.passes);
|
|
16440
|
-
const
|
|
16106
|
+
const renderPipeline = useThree((s) => s.renderPipeline);
|
|
16441
16107
|
return {
|
|
16442
16108
|
passes,
|
|
16443
|
-
|
|
16109
|
+
renderPipeline,
|
|
16444
16110
|
clearPasses,
|
|
16445
16111
|
reset,
|
|
16446
16112
|
rebuild,
|
|
16447
|
-
// isReady indicates if
|
|
16448
|
-
isReady:
|
|
16113
|
+
// isReady indicates if RenderPipeline is configured and ready for rendering
|
|
16114
|
+
isReady: renderPipeline !== null
|
|
16449
16115
|
};
|
|
16450
16116
|
}
|
|
16451
16117
|
|
|
16452
16118
|
extend(THREE);
|
|
16453
16119
|
|
|
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,
|
|
16120
|
+
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, act, 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 };
|