@react-three/fiber 10.0.0-canary.164c7be → 10.0.0-canary.1be335a

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.
@@ -63,13 +63,16 @@ const WebGLRenderer = class WebGLRenderer2 {
63
63
  }
64
64
  };
65
65
  const WebGLRenderTarget = null;
66
+ const WebGLCubeRenderTarget = null;
66
67
 
67
68
  const THREE = /*#__PURE__*/_mergeNamespaces({
68
69
  __proto__: null,
70
+ CubeRenderTargetCompat: webgpu.CubeRenderTarget,
69
71
  Inspector: Inspector_js.Inspector,
70
72
  R3F_BUILD_LEGACY: R3F_BUILD_LEGACY,
71
73
  R3F_BUILD_WEBGPU: R3F_BUILD_WEBGPU,
72
74
  RenderTargetCompat: webgpu.RenderTarget,
75
+ WebGLCubeRenderTarget: WebGLCubeRenderTarget,
73
76
  WebGLRenderTarget: WebGLRenderTarget,
74
77
  WebGLRenderer: WebGLRenderer
75
78
  }, [webgpu__namespace]);
@@ -343,15 +346,16 @@ function EnvironmentPortal({
343
346
  preset = void 0,
344
347
  extensions
345
348
  }) {
346
- const gl = fiber.useThree((state) => state.gl);
349
+ const renderer = fiber.useThree((state) => state.renderer);
350
+ const isLegacy = fiber.useThree((state) => state.isLegacy);
347
351
  const defaultScene = fiber.useThree((state) => state.scene);
348
352
  const camera = React__namespace.useRef(null);
349
353
  const [virtualScene] = React__namespace.useState(() => new webgpu.Scene());
350
354
  const fbo = React__namespace.useMemo(() => {
351
- const fbo2 = new webgpu.WebGLCubeRenderTarget(resolution);
355
+ const fbo2 = isLegacy ? new WebGLCubeRenderTarget(resolution) : new webgpu.CubeRenderTarget(resolution);
352
356
  fbo2.texture.type = webgpu.HalfFloatType;
353
357
  return fbo2;
354
- }, [resolution]);
358
+ }, [resolution, isLegacy]);
355
359
  React__namespace.useEffect(() => {
356
360
  return () => {
357
361
  fbo.dispose();
@@ -359,10 +363,10 @@ function EnvironmentPortal({
359
363
  }, [fbo]);
360
364
  React__namespace.useLayoutEffect(() => {
361
365
  if (frames === 1) {
362
- const autoClear = gl.autoClear;
363
- gl.autoClear = true;
364
- camera.current.update(gl, virtualScene);
365
- gl.autoClear = autoClear;
366
+ const autoClear = renderer.autoClear;
367
+ renderer.autoClear = true;
368
+ camera.current.update(renderer, virtualScene);
369
+ renderer.autoClear = autoClear;
366
370
  }
367
371
  return setEnvProps(background, scene, defaultScene, fbo.texture, {
368
372
  backgroundBlurriness: blur ?? backgroundBlurriness,
@@ -379,7 +383,7 @@ function EnvironmentPortal({
379
383
  defaultScene,
380
384
  background,
381
385
  frames,
382
- gl,
386
+ renderer,
383
387
  blur,
384
388
  backgroundBlurriness,
385
389
  backgroundIntensity,
@@ -390,10 +394,10 @@ function EnvironmentPortal({
390
394
  let count = 1;
391
395
  fiber.useFrame(() => {
392
396
  if (frames === Infinity || count < frames) {
393
- const autoClear = gl.autoClear;
394
- gl.autoClear = true;
395
- camera.current.update(gl, virtualScene);
396
- gl.autoClear = autoClear;
397
+ const autoClear = renderer.autoClear;
398
+ renderer.autoClear = true;
399
+ camera.current.update(renderer, virtualScene);
400
+ renderer.autoClear = autoClear;
397
401
  count++;
398
402
  }
399
403
  });
@@ -636,8 +640,7 @@ function prepare(target, root, type, props) {
636
640
  object,
637
641
  eventCount: 0,
638
642
  handlers: {},
639
- isHidden: false,
640
- deferredRefs: []
643
+ isHidden: false
641
644
  };
642
645
  if (object) object.__r3f = instance;
643
646
  }
@@ -794,6 +797,17 @@ function unregisterVisibility(store, object) {
794
797
  internal.visibilityRegistry.delete(object.uuid);
795
798
  internal.occlusionCache.delete(object);
796
799
  }
800
+ function updateVisibilityHandlers(store, object, handlers) {
801
+ const { internal } = store.getState();
802
+ const entry = internal.visibilityRegistry.get(object.uuid);
803
+ if (!entry) return false;
804
+ entry.handlers = handlers;
805
+ if ((handlers.onOccluded || handlers.onVisible) && !internal.occlusionEnabled) {
806
+ object.occlusionTest = true;
807
+ enableOcclusion(store);
808
+ }
809
+ return true;
810
+ }
797
811
  function checkVisibility(state) {
798
812
  const { internal, camera } = state;
799
813
  const registry = internal.visibilityRegistry;
@@ -954,9 +968,9 @@ function diffProps(instance, newProps) {
954
968
  const { root, key } = resolve(instance.object, prop);
955
969
  if (root.constructor && root.constructor.length === 0) {
956
970
  const ctor = getMemoizedPrototype(root);
957
- if (!is.und(ctor)) changedProps[key] = ctor[key];
971
+ if (!is.und(ctor)) changedProps[prop] = ctor[key];
958
972
  } else {
959
- changedProps[key] = 0;
973
+ changedProps[prop] = 0;
960
974
  }
961
975
  }
962
976
  return changedProps;
@@ -981,10 +995,7 @@ function applyProps(object, props) {
981
995
  continue;
982
996
  }
983
997
  if (value === void 0) continue;
984
- if (isFromRef(value)) {
985
- instance?.deferredRefs?.push({ prop, ref: value[FROM_REF] });
986
- continue;
987
- }
998
+ if (isFromRef(value)) continue;
988
999
  if (isOnce(value)) {
989
1000
  if (instance?.appliedOnce?.has(prop)) continue;
990
1001
  if (instance) {
@@ -1020,6 +1031,19 @@ function applyProps(object, props) {
1020
1031
  } else if (isVectorLike(target) && is.num(value)) {
1021
1032
  if ("setScalar" in target && typeof target.setScalar === "function") target.setScalar(value);
1022
1033
  else target.set(value);
1034
+ } else if (root instanceof webgpu.ShaderMaterial && key === "uniforms" && is.obj(value)) {
1035
+ if (!is.obj(root.uniforms)) root.uniforms = {};
1036
+ const uniforms = root.uniforms;
1037
+ const nextUniforms = value;
1038
+ for (const name in nextUniforms) {
1039
+ const uniform = nextUniforms[name];
1040
+ const targetUniform = uniforms[name];
1041
+ if (targetUniform) Object.assign(targetUniform, uniform);
1042
+ else {
1043
+ const nextUniform = uniform instanceof webgpu.Uniform ? uniform.clone() : new webgpu.Uniform(uniform.value);
1044
+ uniforms[name] = nextUniform;
1045
+ }
1046
+ }
1023
1047
  } else {
1024
1048
  root[key] = value;
1025
1049
  if (key.endsWith("Node") && root.isMaterial) {
@@ -1038,6 +1062,9 @@ function applyProps(object, props) {
1038
1062
  if (instance.eventCount && object2.raycast !== null) {
1039
1063
  rootState.internal.interaction.push(object2);
1040
1064
  }
1065
+ }
1066
+ if (instance?.parent && rootState?.internal && instance.object?.isObject3D) {
1067
+ const object2 = instance.object;
1041
1068
  const root = findInitialRoot(instance);
1042
1069
  const visibilityHandlers = {
1043
1070
  onFramed: instance.handlers.onFramed,
@@ -1045,7 +1072,9 @@ function applyProps(object, props) {
1045
1072
  onVisible: instance.handlers.onVisible
1046
1073
  };
1047
1074
  if (hasVisibilityHandlers(visibilityHandlers)) {
1048
- registerVisibility(root, object2, visibilityHandlers);
1075
+ if (!updateVisibilityHandlers(root, object2, visibilityHandlers)) {
1076
+ registerVisibility(root, object2, visibilityHandlers);
1077
+ }
1049
1078
  } else {
1050
1079
  unregisterVisibility(root, object2);
1051
1080
  }
@@ -1573,6 +1602,7 @@ function createPointerEvents(store) {
1573
1602
  set((state) => ({ events: { ...state.events, connected: void 0 } }));
1574
1603
  }
1575
1604
  },
1605
+ // PREVIEW (A8): the XR pointer registration API may change before stable.
1576
1606
  registerPointer: (config) => {
1577
1607
  const pointerId = nextXRPointerId++;
1578
1608
  xrPointers.set(pointerId, config);
@@ -1659,6 +1689,13 @@ function notifyAlpha({ message, link }) {
1659
1689
  }
1660
1690
  }
1661
1691
 
1692
+ const INTERNAL_LIB_MARKERS = ["zustand", "setState", "Object.assign"];
1693
+ function isInternalRendererAccess(stack, callerDepth = 2) {
1694
+ if (!stack) return false;
1695
+ const caller = stack.split("\n")[callerDepth] ?? "";
1696
+ return INTERNAL_LIB_MARKERS.some((m) => caller.includes(m));
1697
+ }
1698
+
1662
1699
  const R3F_CONTEXT = Symbol.for("@react-three/fiber.context");
1663
1700
  const context = globalThis[R3F_CONTEXT] ?? (globalThis[R3F_CONTEXT] = React__namespace.createContext(null));
1664
1701
  const createStore = (invalidate, advance) => {
@@ -1791,6 +1828,7 @@ const createStore = (invalidate, advance) => {
1791
1828
  }),
1792
1829
  setFrameloop: (frameloop = "always") => {
1793
1830
  set(() => ({ frameloop }));
1831
+ scheduler.getScheduler().frameloop = frameloop;
1794
1832
  },
1795
1833
  setError: (error) => set(() => ({ error })),
1796
1834
  error: null,
@@ -1858,8 +1896,7 @@ const createStore = (invalidate, advance) => {
1858
1896
  const currentState = rootStore.getState();
1859
1897
  if (!currentState.isLegacy && currentState.internal.actualRenderer) {
1860
1898
  const stack = new Error().stack || "";
1861
- const isInternalAccess = stack.includes("zustand") || stack.includes("setState") || stack.includes("Object.assign") || stack.includes("react-three-fiber/packages/fiber/src/core");
1862
- if (!isInternalAccess) {
1899
+ if (!isInternalRendererAccess(stack)) {
1863
1900
  const cleanedStack = stack.split("\n").slice(2).join("\n") || "Stack trace unavailable";
1864
1901
  notifyDepreciated({
1865
1902
  heading: "Accessing state.gl in WebGPU mode",
@@ -1972,19 +2009,25 @@ function loadingFn(extensions, onProgress) {
1972
2009
  );
1973
2010
  };
1974
2011
  }
1975
- function useLoader(loader, input, extensions, onProgress) {
1976
- const keys = Array.isArray(input) ? input : [input];
2012
+ function useLoader(loader, input, extensions, onProgress, cacheKey) {
2013
+ const inputs = Array.isArray(input) ? input : [input];
2014
+ const cacheKeys = Array.isArray(cacheKey ?? input) ? cacheKey ?? input : [cacheKey ?? input];
1977
2015
  const fn = loadingFn(extensions, onProgress);
1978
- const results = keys.map((key) => suspendReact.suspend(fn, [loader, key], { equal: is.equ }));
2016
+ const results = cacheKeys.map(
2017
+ (key, index) => suspendReact.suspend(() => fn(loader, inputs[index]), [loader, key], { equal: is.equ })
2018
+ );
1979
2019
  return Array.isArray(input) ? results : results[0];
1980
2020
  }
1981
- useLoader.preload = function(loader, input, extensions, onProgress) {
1982
- const keys = Array.isArray(input) ? input : [input];
1983
- keys.forEach((key) => suspendReact.preload(loadingFn(extensions, onProgress), [loader, key]));
2021
+ useLoader.preload = function(loader, input, extensions, onProgress, cacheKey) {
2022
+ const inputs = Array.isArray(input) ? input : [input];
2023
+ const cacheKeys = Array.isArray(cacheKey ?? input) ? cacheKey ?? input : [cacheKey ?? input];
2024
+ cacheKeys.forEach(
2025
+ (key, index) => suspendReact.preload(() => loadingFn(extensions, onProgress)(loader, inputs[index]), [loader, key])
2026
+ );
1984
2027
  };
1985
- useLoader.clear = function(loader, input) {
1986
- const keys = Array.isArray(input) ? input : [input];
1987
- keys.forEach((key) => suspendReact.clear([loader, key]));
2028
+ useLoader.clear = function(loader, input, cacheKey) {
2029
+ const cacheKeys = Array.isArray(cacheKey ?? input) ? cacheKey ?? input : [cacheKey ?? input];
2030
+ cacheKeys.forEach((key) => suspendReact.clear([loader, key]));
1988
2031
  };
1989
2032
  useLoader.loader = getLoader;
1990
2033
 
@@ -2404,7 +2447,7 @@ function advance(timestamp) {
2404
2447
  scheduler.getScheduler().step(timestamp);
2405
2448
  }
2406
2449
 
2407
- const version = "10.0.0-alpha.2";
2450
+ const version = "10.0.0-alpha.3";
2408
2451
  const packageData = {
2409
2452
  version: version};
2410
2453
 
@@ -13660,7 +13703,7 @@ function n0() {
13660
13703
  var t0 = n0();
13661
13704
  const r0 = Xb(t0);
13662
13705
 
13663
- const t = 1, o = 8, r = 32, e = 2;
13706
+ const t = 1, o = 8, r = 32, e = 2, i$1 = 268435456;
13664
13707
 
13665
13708
  function createReconciler(config) {
13666
13709
  const reconciler2 = r0(config);
@@ -13825,7 +13868,15 @@ function setFiberRef(fiber, publicInstance) {
13825
13868
  }
13826
13869
  }
13827
13870
  const reconstructed = [];
13828
- function swapInstances() {
13871
+ function flushReconstructedInstances() {
13872
+ if (reconstructed.length === 0) return;
13873
+ try {
13874
+ swapReconstructedInstances();
13875
+ } finally {
13876
+ reconstructed.length = 0;
13877
+ }
13878
+ }
13879
+ function swapReconstructedInstances() {
13829
13880
  for (const [instance] of reconstructed) {
13830
13881
  const parent = instance.parent;
13831
13882
  if (parent) {
@@ -13843,6 +13894,7 @@ function swapInstances() {
13843
13894
  }
13844
13895
  }
13845
13896
  if (instance.isHidden) unhideInstance(instance);
13897
+ if (isObject3D(instance.object)) removeInteractivity(findInitialRoot(instance), instance.object);
13846
13898
  if (instance.object.__r3f) delete instance.object.__r3f;
13847
13899
  if (instance.type !== "primitive") disposeOnIdle(instance.object);
13848
13900
  }
@@ -13855,6 +13907,8 @@ function swapInstances() {
13855
13907
  instance.object.__r3f = instance;
13856
13908
  setFiberRef(fiber, instance.object);
13857
13909
  delete instance.appliedOnce;
13910
+ instance.eventCount = 0;
13911
+ instance.handlers = {};
13858
13912
  applyProps(instance.object, instance.props);
13859
13913
  if (instance.props.attach) {
13860
13914
  attach(parent, instance);
@@ -13871,14 +13925,114 @@ function swapInstances() {
13871
13925
  invalidateInstance(instance);
13872
13926
  }
13873
13927
  }
13874
- reconstructed.length = 0;
13875
13928
  }
13876
13929
  const handleTextInstance = () => {
13877
13930
  };
13878
13931
  const NO_CONTEXT = {};
13879
13932
  let currentUpdatePriority = NoEventPriority;
13880
- const NoFlags = 0;
13881
- const Update = 4;
13933
+ function getEventPriority(type) {
13934
+ switch (type) {
13935
+ case "beforetoggle":
13936
+ case "cancel":
13937
+ case "click":
13938
+ case "close":
13939
+ case "contextmenu":
13940
+ case "copy":
13941
+ case "cut":
13942
+ case "auxclick":
13943
+ case "dblclick":
13944
+ case "dragend":
13945
+ case "dragstart":
13946
+ case "drop":
13947
+ case "focusin":
13948
+ case "focusout":
13949
+ case "input":
13950
+ case "invalid":
13951
+ case "keydown":
13952
+ case "keypress":
13953
+ case "keyup":
13954
+ case "mousedown":
13955
+ case "mouseup":
13956
+ case "paste":
13957
+ case "pause":
13958
+ case "play":
13959
+ case "pointercancel":
13960
+ case "pointerdown":
13961
+ case "pointerup":
13962
+ case "ratechange":
13963
+ case "reset":
13964
+ case "resize":
13965
+ case "seeked":
13966
+ case "submit":
13967
+ case "touchcancel":
13968
+ case "touchend":
13969
+ case "touchstart":
13970
+ case "volumechange":
13971
+ case "change":
13972
+ case "selectionchange":
13973
+ case "compositionstart":
13974
+ case "compositionend":
13975
+ case "compositionupdate":
13976
+ case "beforeinput":
13977
+ case "blur":
13978
+ case "fullscreenchange":
13979
+ case "focus":
13980
+ case "hashchange":
13981
+ case "popstate":
13982
+ case "select":
13983
+ case "selectstart":
13984
+ return e;
13985
+ case "drag":
13986
+ case "dragenter":
13987
+ case "dragexit":
13988
+ case "dragleave":
13989
+ case "dragover":
13990
+ case "mousemove":
13991
+ case "mouseout":
13992
+ case "mouseover":
13993
+ case "pointermove":
13994
+ case "pointerout":
13995
+ case "pointerover":
13996
+ case "scroll":
13997
+ case "touchmove":
13998
+ case "wheel":
13999
+ case "mouseenter":
14000
+ case "mouseleave":
14001
+ case "pointerenter":
14002
+ case "pointerleave":
14003
+ return o;
14004
+ case "message": {
14005
+ switch (Tb.unstable_getCurrentPriorityLevel()) {
14006
+ case Tb.unstable_ImmediatePriority:
14007
+ return e;
14008
+ case Tb.unstable_UserBlockingPriority:
14009
+ return o;
14010
+ case Tb.unstable_NormalPriority:
14011
+ case Tb.unstable_LowPriority:
14012
+ return r;
14013
+ case Tb.unstable_IdlePriority:
14014
+ return i$1;
14015
+ default:
14016
+ return r;
14017
+ }
14018
+ }
14019
+ default:
14020
+ return r;
14021
+ }
14022
+ }
14023
+ function scheduleMicrotask(callback) {
14024
+ if (typeof queueMicrotask === "function") {
14025
+ queueMicrotask(callback);
14026
+ } else if (typeof Promise !== "undefined") {
14027
+ Promise.resolve().then(callback).catch((error) => {
14028
+ setTimeout(() => {
14029
+ throw error;
14030
+ });
14031
+ });
14032
+ } else {
14033
+ setTimeout(callback);
14034
+ }
14035
+ }
13882
14036
  const reconciler = /* @__PURE__ */ createReconciler({
13883
14037
  isPrimaryRenderer: false,
13884
14038
  warnsIfNotActing: false,
@@ -13917,16 +14071,15 @@ const reconciler = /* @__PURE__ */ createReconciler({
13917
14071
  else if (newProps.args?.length !== oldProps.args?.length) reconstruct = true;
13918
14072
  else if (newProps.args?.some((value, index) => value !== oldProps.args?.[index])) reconstruct = true;
13919
14073
  if (reconstruct) {
13920
- reconstructed.push([instance, { ...newProps }, fiber]);
14074
+ reconstructed.push([instance, getInstanceProps(newProps), fiber]);
13921
14075
  } else {
13922
14076
  const changedProps = diffProps(instance, newProps);
13923
- if (Object.keys(changedProps).length) {
13924
- Object.assign(instance.props, changedProps);
13925
- applyProps(instance.object, changedProps);
13926
- }
14077
+ const attach2 = instance.props.attach;
14078
+ instance.props = getInstanceProps(newProps);
14079
+ if (attach2 !== void 0) instance.props.attach = attach2;
14080
+ else delete instance.props.attach;
14081
+ if (Object.keys(changedProps).length) applyProps(instance.object, changedProps);
13927
14082
  }
13928
- const isTailSibling = fiber.sibling === null || (fiber.flags & Update) === NoFlags;
13929
- if (isTailSibling) swapInstances();
13930
14083
  },
13931
14084
  finalizeInitialChildren: (instance) => {
13932
14085
  for (const prop in instance.props) {
@@ -13951,8 +14104,9 @@ const reconciler = /* @__PURE__ */ createReconciler({
13951
14104
  const target = container.getState().internal.container ?? container.getState().scene;
13952
14105
  return prepare(target, container, "", {});
13953
14106
  },
13954
- resetAfterCommit: () => {
13955
- },
14107
+ // Reconstructed instances are swapped once all mutations are committed,
14108
+ // before layout effects run so refs point to the new objects
14109
+ resetAfterCommit: flushReconstructedInstances,
13956
14110
  shouldSetTextContent: () => false,
13957
14111
  clearContainer: () => false,
13958
14112
  hideInstance,
@@ -13960,6 +14114,9 @@ const reconciler = /* @__PURE__ */ createReconciler({
13960
14114
  createTextInstance: handleTextInstance,
13961
14115
  hideTextInstance: handleTextInstance,
13962
14116
  unhideTextInstance: handleTextInstance,
14117
+ // Mirrors react-dom, which flushes discrete work in microtasks
14118
+ supportsMicrotasks: true,
14119
+ scheduleMicrotask,
13963
14120
  scheduleTimeout: typeof setTimeout === "function" ? setTimeout : void 0,
13964
14121
  cancelTimeout: typeof clearTimeout === "function" ? clearTimeout : void 0,
13965
14122
  noTimeout: -1,
@@ -14000,28 +14157,9 @@ const reconciler = /* @__PURE__ */ createReconciler({
14000
14157
  },
14001
14158
  resolveUpdatePriority() {
14002
14159
  if (currentUpdatePriority !== NoEventPriority) return currentUpdatePriority;
14003
- switch (typeof window !== "undefined" && window.event?.type) {
14004
- case "click":
14005
- case "contextmenu":
14006
- case "dblclick":
14007
- case "dragenter":
14008
- case "dragleave":
14009
- case "drop":
14010
- case "pointercancel":
14011
- case "pointerdown":
14012
- case "pointerup":
14013
- return e;
14014
- case "dragover":
14015
- case "pointermove":
14016
- case "pointerout":
14017
- case "pointerover":
14018
- case "pointerenter":
14019
- case "pointerleave":
14020
- case "wheel":
14021
- return o;
14022
- default:
14023
- return r;
14024
- }
14160
+ const eventType = typeof window !== "undefined" ? window.event?.type : void 0;
14161
+ if (eventType === void 0) return r;
14162
+ return getEventPriority(eventType);
14025
14163
  },
14026
14164
  resetFormInstance() {
14027
14165
  },
@@ -14070,11 +14208,15 @@ const reconciler = /* @__PURE__ */ createReconciler({
14070
14208
  cloneMutableTextInstance(textInstance) {
14071
14209
  return textInstance;
14072
14210
  },
14211
+ // React 19.2 added these for root-level view transitions. They only run for hosts that opt into
14212
+ // view transitions by returning a container from cloneRootViewTransitionContainer; R3F renders
14213
+ // into a WebGL/WebGPU canvas, where cloning the root has no meaning, so we never opt in and
14214
+ // React never calls them. They stay as no-ops rather than throws: the host config is shared by
14215
+ // every root, and a throw here would turn a React-internal probe into a hard crash for users.
14216
+ // Guarded by a test asserting a full render + unmount never reaches them.
14073
14217
  cloneRootViewTransitionContainer(_rootContainer) {
14074
- throw new Error("Not implemented.");
14075
14218
  },
14076
14219
  removeRootViewTransitionClone(_rootContainer, _clone) {
14077
- throw new Error("Not implemented.");
14078
14220
  },
14079
14221
  // https://github.com/facebook/react/pull/32465
14080
14222
  createFragmentInstance: (_fiber) => null,
@@ -14159,6 +14301,7 @@ function createRoot(canvas) {
14159
14301
  const lastConfiguredProps = {};
14160
14302
  let configured = false;
14161
14303
  let pending = null;
14304
+ let rendererSetup = null;
14162
14305
  return {
14163
14306
  async configure(props = {}) {
14164
14307
  let resolve;
@@ -14189,7 +14332,7 @@ function createRoot(canvas) {
14189
14332
  forceEven
14190
14333
  } = props;
14191
14334
  const textureColorSpace = is.obj(glConfig) && !is.fun(glConfig) && !isRenderer(glConfig) && glConfig.textureColorSpace || is.obj(rendererConfig) && !is.fun(rendererConfig) && !isRenderer(rendererConfig) && rendererConfig.textureColorSpace || webgpu.SRGBColorSpace;
14192
- const state = store.getState();
14335
+ let state = store.getState();
14193
14336
  const defaultGPUProps = {
14194
14337
  canvas,
14195
14338
  antialias: true
@@ -14203,52 +14346,64 @@ function createRoot(canvas) {
14203
14346
  throw new Error("Cannot use both gl and renderer props at the same time");
14204
14347
  }
14205
14348
  let renderer = state.internal.actualRenderer;
14206
- if (primaryCanvas && !state.internal.actualRenderer) {
14207
- const primary = await waitForPrimary(primaryCanvas);
14208
- renderer = primary.renderer;
14209
- state.internal.actualRenderer = renderer;
14210
- const canvasTarget = new webgpu.CanvasTarget(canvas);
14211
- primary.store.setState((prev) => ({
14212
- internal: { ...prev.internal, isMultiCanvas: true }
14213
- }));
14214
- state.set((prev) => ({
14215
- webGPUSupported: primary.store.getState().webGPUSupported,
14216
- renderer,
14217
- primaryStore: primary.store,
14218
- internal: {
14219
- ...prev.internal,
14220
- canvasTarget,
14221
- isMultiCanvas: true,
14222
- isSecondary: true,
14223
- targetId: primaryCanvas
14224
- }
14225
- }));
14226
- } else if (!state.internal.actualRenderer) {
14227
- renderer = await resolveRenderer(rendererConfig, defaultGPUProps, webgpu.WebGPURenderer);
14228
- if (!renderer.hasInitialized?.()) {
14229
- const size2 = computeInitialSize(canvas, propsSize);
14230
- if (size2.width > 0 && size2.height > 0) {
14231
- const pixelRatio = calculateDpr(dpr);
14232
- canvas.width = size2.width * pixelRatio;
14233
- canvas.height = size2.height * pixelRatio;
14234
- }
14235
- await renderer.init();
14236
- }
14237
- const backend = renderer.backend;
14238
- const isWebGPUBackend = backend && "isWebGPUBackend" in backend;
14239
- state.internal.actualRenderer = renderer;
14240
- state.set({ webGPUSupported: isWebGPUBackend, renderer, primaryStore: store });
14241
- if (canvasId && !state.internal.isSecondary) {
14242
- const canvasTarget = new webgpu.CanvasTarget(canvas);
14243
- const unregisterPrimary = registerPrimary(canvasId, renderer, store);
14244
- state.set((prev) => ({
14245
- internal: {
14246
- ...prev.internal,
14247
- canvasTarget,
14248
- unregisterPrimary
14349
+ if (!state.internal.actualRenderer) {
14350
+ if (!rendererSetup) {
14351
+ rendererSetup = (async () => {
14352
+ if (primaryCanvas) {
14353
+ const primary = await waitForPrimary(primaryCanvas);
14354
+ renderer = primary.renderer;
14355
+ state.internal.actualRenderer = renderer;
14356
+ const canvasTarget = new webgpu.CanvasTarget(canvas);
14357
+ primary.store.setState((prev) => ({
14358
+ internal: { ...prev.internal, isMultiCanvas: true }
14359
+ }));
14360
+ state.set((prev) => ({
14361
+ webGPUSupported: primary.store.getState().webGPUSupported,
14362
+ renderer,
14363
+ primaryStore: primary.store,
14364
+ internal: {
14365
+ ...prev.internal,
14366
+ canvasTarget,
14367
+ isMultiCanvas: true,
14368
+ isSecondary: true,
14369
+ targetId: primaryCanvas
14370
+ }
14371
+ }));
14372
+ } else {
14373
+ renderer = await resolveRenderer(rendererConfig, defaultGPUProps, webgpu.WebGPURenderer);
14374
+ if (!renderer.hasInitialized?.()) {
14375
+ const size2 = computeInitialSize(canvas, propsSize);
14376
+ if (size2.width > 0 && size2.height > 0) {
14377
+ const pixelRatio = calculateDpr(dpr);
14378
+ canvas.width = size2.width * pixelRatio;
14379
+ canvas.height = size2.height * pixelRatio;
14380
+ }
14381
+ await renderer.init();
14382
+ }
14383
+ const backend = renderer.backend;
14384
+ const isWebGPUBackend = backend && "isWebGPUBackend" in backend;
14385
+ state.internal.actualRenderer = renderer;
14386
+ state.set({ webGPUSupported: isWebGPUBackend, renderer, primaryStore: store });
14387
+ if (canvasId && !state.internal.isSecondary) {
14388
+ const canvasTarget = new webgpu.CanvasTarget(canvas);
14389
+ const unregisterPrimary = registerPrimary(canvasId, renderer, store);
14390
+ state.set((prev) => ({
14391
+ internal: {
14392
+ ...prev.internal,
14393
+ canvasTarget,
14394
+ unregisterPrimary
14395
+ }
14396
+ }));
14397
+ }
14249
14398
  }
14250
- }));
14399
+ })().catch((err) => {
14400
+ rendererSetup = null;
14401
+ throw err;
14402
+ });
14251
14403
  }
14404
+ await rendererSetup;
14405
+ state = store.getState();
14406
+ renderer = state.internal.actualRenderer;
14252
14407
  }
14253
14408
  let raycaster = state.raycaster;
14254
14409
  if (!raycaster) state.set({ raycaster: raycaster = new webgpu.Raycaster() });
@@ -14598,7 +14753,8 @@ function unmountComponentAtNode(canvas, callback) {
14598
14753
  dispose(state.scene);
14599
14754
  _roots.delete(canvas);
14600
14755
  if (callback) callback(canvas);
14601
- } catch {
14756
+ } catch (error) {
14757
+ console.warn("[R3F] Error while unmounting root; teardown may be incomplete:", error);
14602
14758
  }
14603
14759
  }, 500);
14604
14760
  }
@@ -14661,6 +14817,7 @@ function PortalInner({ state = {}, children, container }) {
14661
14817
  viewport = rootState.viewport.getCurrentViewport(camera, new webgpu.Vector3(), resolvedSize);
14662
14818
  if (camera !== rootState.camera) updateCamera(camera, resolvedSize);
14663
14819
  }
14820
+ const set = injectState.set;
14664
14821
  return {
14665
14822
  // The intersect consists of the previous root state
14666
14823
  ...rootState,
@@ -14679,7 +14836,7 @@ function PortalInner({ state = {}, children, container }) {
14679
14836
  size: resolvedSize,
14680
14837
  viewport: { ...rootState.viewport, ...viewport },
14681
14838
  // Layers are allowed to override events
14682
- setEvents: (events2) => injectState.set((state2) => ({ ...state2, events: { ...state2.events, ...events2 } })),
14839
+ setEvents: (events2) => set((state2) => ({ ...state2, events: { ...state2.events, ...events2 } })),
14683
14840
  // Container for child attachment - the portalScene (injected or container itself)
14684
14841
  internal: { ...rootState.internal, ...injectState.internal, container: portalScene }
14685
14842
  };
@@ -14738,14 +14895,146 @@ function parseBackground(background) {
14738
14895
  return null;
14739
14896
  }
14740
14897
 
14898
+ function parseRendererConfig(rendererProp) {
14899
+ const isRendererConfig = typeof rendererProp === "object" && rendererProp !== null && !("render" in rendererProp) && ("primaryCanvas" in rendererProp || "scheduler" in rendererProp);
14900
+ if (isRendererConfig) {
14901
+ const { primaryCanvas: pc, scheduler: sc, ...rest } = rendererProp;
14902
+ return {
14903
+ primaryCanvas: pc,
14904
+ scheduler: sc,
14905
+ renderer: Object.keys(rest).length > 0 ? rest : rendererProp
14906
+ };
14907
+ }
14908
+ return { primaryCanvas: void 0, scheduler: void 0, renderer: rendererProp };
14909
+ }
14910
+
14911
+ const RESOURCE_KINDS = ["nodes", "uniforms", "buffers", "gpuStorage"];
14912
+ const ROOT_SCOPE = "";
14913
+ const registries = /* @__PURE__ */ new WeakMap();
14914
+ function getKindRegistry(store, kind) {
14915
+ let registry = registries.get(store);
14916
+ if (!registry) {
14917
+ registry = {
14918
+ nodes: { valid: /* @__PURE__ */ new Set(), staged: /* @__PURE__ */ new Map() },
14919
+ uniforms: { valid: /* @__PURE__ */ new Set(), staged: /* @__PURE__ */ new Map() },
14920
+ buffers: { valid: /* @__PURE__ */ new Set(), staged: /* @__PURE__ */ new Map() },
14921
+ gpuStorage: { valid: /* @__PURE__ */ new Set(), staged: /* @__PURE__ */ new Map() }
14922
+ };
14923
+ registries.set(store, registry);
14924
+ }
14925
+ return registry[kind];
14926
+ }
14927
+ function resolvePrimary$1(store) {
14928
+ return store.getState().primaryStore ?? store;
14929
+ }
14930
+ function isScopeValid(store, kind, scopeKey) {
14931
+ return getKindRegistry(store, kind).valid.has(scopeKey);
14932
+ }
14933
+ function peekStaged(store, kind, scopeKey) {
14934
+ return getKindRegistry(store, kind).staged.get(scopeKey);
14935
+ }
14936
+ function stageEntries(store, kind, scopeKey, entries) {
14937
+ const { staged } = getKindRegistry(store, kind);
14938
+ const current = staged.get(scopeKey);
14939
+ if (current) Object.assign(current, entries);
14940
+ else staged.set(scopeKey, { ...entries });
14941
+ }
14942
+ function readCommittedScope(map, scopeKey, isLeaf) {
14943
+ if (scopeKey === ROOT_SCOPE) {
14944
+ const leaves = {};
14945
+ for (const [key, value] of Object.entries(map)) if (isLeaf(value)) leaves[key] = value;
14946
+ return leaves;
14947
+ }
14948
+ const scopeData = map[scopeKey];
14949
+ if (scopeData && typeof scopeData === "object" && !isLeaf(scopeData)) return scopeData;
14950
+ return {};
14951
+ }
14952
+ function withStagedOverlay(store, kind, committed) {
14953
+ const { staged } = getKindRegistry(store, kind);
14954
+ if (staged.size === 0) return committed;
14955
+ const view = { ...committed };
14956
+ for (const [scopeKey, entries] of staged) {
14957
+ if (scopeKey === ROOT_SCOPE) Object.assign(view, entries);
14958
+ else view[scopeKey] = { ...view[scopeKey], ...entries };
14959
+ }
14960
+ return view;
14961
+ }
14962
+ function flushStagedScope(store, kind, scopeKey, isLeaf) {
14963
+ const registry = getKindRegistry(store, kind);
14964
+ const staged = registry.staged.get(scopeKey);
14965
+ if (!staged) return;
14966
+ registry.staged.delete(scopeKey);
14967
+ const replace = !registry.valid.has(scopeKey);
14968
+ store.setState((state) => {
14969
+ const map = state[kind];
14970
+ if (scopeKey === ROOT_SCOPE) {
14971
+ if (!replace) return { [kind]: { ...map, ...staged } };
14972
+ const next = {};
14973
+ for (const [key, value] of Object.entries(map)) if (!isLeaf(value)) next[key] = value;
14974
+ return { [kind]: { ...next, ...staged } };
14975
+ }
14976
+ const current = replace ? {} : map[scopeKey] ?? {};
14977
+ return { [kind]: { ...map, [scopeKey]: { ...current, ...staged } } };
14978
+ });
14979
+ registry.valid.add(scopeKey);
14980
+ }
14981
+ function invalidateResource(store, kind, scopeKey) {
14982
+ const registry = getKindRegistry(store, kind);
14983
+ if (scopeKey === void 0) {
14984
+ registry.valid.clear();
14985
+ registry.staged.clear();
14986
+ } else {
14987
+ registry.valid.delete(scopeKey);
14988
+ registry.staged.delete(scopeKey);
14989
+ }
14990
+ }
14991
+ function rebuildResource(store, kind, userScope) {
14992
+ const primary = resolvePrimary$1(store);
14993
+ invalidateResource(primary, kind, userScope === "root" ? ROOT_SCOPE : userScope);
14994
+ primary.setState((state) => ({ _hmrVersion: state._hmrVersion + 1 }));
14995
+ }
14996
+ function invalidateAllResources(store) {
14997
+ for (const kind of RESOURCE_KINDS) invalidateResource(store, kind);
14998
+ }
14999
+ function removeResourceEntries(store, kind, names, scope, isLeaf) {
15000
+ const staged = peekStaged(store, kind, scope ?? ROOT_SCOPE);
15001
+ if (staged) for (const name of names) delete staged[name];
15002
+ store.setState((state) => {
15003
+ const map = state[kind];
15004
+ if (scope) {
15005
+ const currentScope = { ...map[scope] };
15006
+ for (const name of names) delete currentScope[name];
15007
+ return { [kind]: { ...map, [scope]: currentScope } };
15008
+ }
15009
+ const next = { ...map };
15010
+ for (const name of names) if (isLeaf(next[name])) delete next[name];
15011
+ return { [kind]: next };
15012
+ });
15013
+ }
15014
+ function clearResourceEntries(store, kind, scope, isLeaf) {
15015
+ const registry = getKindRegistry(store, kind);
15016
+ if (scope && scope !== "root") registry.staged.delete(scope);
15017
+ else if (scope === "root") registry.staged.delete(ROOT_SCOPE);
15018
+ else registry.staged.clear();
15019
+ store.setState((state) => {
15020
+ const map = state[kind];
15021
+ if (scope && scope !== "root") {
15022
+ const { [scope]: _, ...rest } = map;
15023
+ return { [kind]: rest };
15024
+ }
15025
+ if (scope === "root") {
15026
+ const next = {};
15027
+ for (const [key, value] of Object.entries(map)) if (!isLeaf(value)) next[key] = value;
15028
+ return { [kind]: next };
15029
+ }
15030
+ return { [kind]: {} };
15031
+ });
15032
+ }
15033
+
14741
15034
  function clearHmrCaches(store) {
14742
- store.setState((state) => ({
14743
- nodes: {},
14744
- uniforms: {},
14745
- buffers: {},
14746
- gpuStorage: {},
14747
- _hmrVersion: state._hmrVersion + 1
14748
- }));
15035
+ const primary = store.getState().primaryStore ?? store;
15036
+ invalidateAllResources(primary);
15037
+ primary.setState((state) => ({ _hmrVersion: state._hmrVersion + 1 }));
14749
15038
  }
14750
15039
 
14751
15040
  function CanvasImpl({
@@ -14781,18 +15070,7 @@ function CanvasImpl({
14781
15070
  forceEven,
14782
15071
  ...props
14783
15072
  }) {
14784
- const isRendererConfig = typeof rendererProp === "object" && rendererProp !== null && !("render" in rendererProp) && ("primaryCanvas" in rendererProp || "scheduler" in rendererProp);
14785
- let primaryCanvas;
14786
- let scheduler;
14787
- let renderer;
14788
- if (isRendererConfig) {
14789
- const { primaryCanvas: pc, scheduler: sc, ...rest } = rendererProp;
14790
- primaryCanvas = pc;
14791
- scheduler = sc;
14792
- renderer = Object.keys(rest).length > 0 ? rest : rendererProp;
14793
- } else {
14794
- renderer = rendererProp;
14795
- }
15073
+ const { primaryCanvas, scheduler, renderer } = parseRendererConfig(rendererProp);
14796
15074
  React__namespace.useMemo(() => extend(THREE), []);
14797
15075
  const Bridge = useBridge();
14798
15076
  const backgroundProps = React__namespace.useMemo(() => parseBackground(background), [background]);
@@ -14837,6 +15115,7 @@ function CanvasImpl({
14837
15115
  const handleDropMissed = useMutableCallback(onDropMissed);
14838
15116
  const [block, setBlock] = React__namespace.useState(false);
14839
15117
  const [error, setError] = React__namespace.useState(false);
15118
+ const [fallbackVisible, setFallbackVisible] = React__namespace.useState(false);
14840
15119
  if (block) throw block;
14841
15120
  if (error) throw error;
14842
15121
  const root = React__namespace.useRef(null);
@@ -14844,6 +15123,7 @@ function CanvasImpl({
14844
15123
  const unsubscribeErrorRef = React__namespace.useRef(null);
14845
15124
  useIsomorphicLayoutEffect(() => {
14846
15125
  effectActiveRef.current = true;
15126
+ if (fallbackVisible) return;
14847
15127
  const canvas = canvasRef.current;
14848
15128
  if (effectiveSize.width > 0 && effectiveSize.height > 0 && canvas) {
14849
15129
  if (!root.current) {
@@ -14864,7 +15144,7 @@ function CanvasImpl({
14864
15144
  }
14865
15145
  async function run() {
14866
15146
  if (!effectActiveRef.current || !root.current) return;
14867
- await root.current.configure({
15147
+ const configured = await root.current.configure({
14868
15148
  id,
14869
15149
  primaryCanvas,
14870
15150
  scheduler,
@@ -14905,8 +15185,20 @@ function CanvasImpl({
14905
15185
  }
14906
15186
  onCreated?.(state);
14907
15187
  }
14908
- });
14909
- if (!effectActiveRef.current || !root.current) return;
15188
+ }).then(
15189
+ () => true,
15190
+ // Renderer setup failed (e.g. no WebGL/WebGPU support). The `fallback` prop lives
15191
+ // inside <canvas>, which browsers don't display, so surface it as visible DOM
15192
+ // instead; with no fallback, rethrow to an external error boundary. (#3757)
15193
+ (setupError) => {
15194
+ if (effectActiveRef.current) {
15195
+ if (fallback != null) setFallbackVisible(true);
15196
+ else setError(setupError);
15197
+ }
15198
+ return false;
15199
+ }
15200
+ );
15201
+ if (!configured || !effectActiveRef.current || !root.current) return;
14910
15202
  root.current.render(
14911
15203
  /* @__PURE__ */ jsxRuntime.jsx(Bridge, { children: /* @__PURE__ */ jsxRuntime.jsx(ErrorBoundary, { set: setError, children: /* @__PURE__ */ jsxRuntime.jsxs(React__namespace.Suspense, { fallback: /* @__PURE__ */ jsxRuntime.jsx(Block, { set: setBlock }), children: [
14912
15204
  backgroundProps && /* @__PURE__ */ jsxRuntime.jsx(Environment, { ...backgroundProps }),
@@ -14969,7 +15261,11 @@ function CanvasImpl({
14969
15261
  ...style
14970
15262
  },
14971
15263
  ...props,
14972
- children: /* @__PURE__ */ jsxRuntime.jsx("div", { ref: containerRef, className: "r3f-canvas-container", style: { width: "100%", height: "100%" }, children: /* @__PURE__ */ jsxRuntime.jsx(
15264
+ children: fallbackVisible ? (
15265
+ // Renderer setup failed: render the fallback as visible DOM. Inside <canvas> (below)
15266
+ // it exists in the tree but browsers never paint it, which is the whole bug (#3757).
15267
+ fallback
15268
+ ) : /* @__PURE__ */ jsxRuntime.jsx("div", { ref: containerRef, className: "r3f-canvas-container", style: { width: "100%", height: "100%" }, children: /* @__PURE__ */ jsxRuntime.jsx(
14973
15269
  "canvas",
14974
15270
  {
14975
15271
  ref: canvasRef,
@@ -15051,30 +15347,31 @@ let ScopedStore = _ScopedStore;
15051
15347
  function createScopedStore(data) {
15052
15348
  return new ScopedStore(data);
15053
15349
  }
15054
- function createLazyCreatorState(state) {
15350
+ function createLazyCreatorState(state, store) {
15055
15351
  let _uniforms = null;
15056
15352
  let _nodes = null;
15057
15353
  let _buffers = null;
15058
15354
  let _gpuStorage = null;
15355
+ const view = (kind) => store ? withStagedOverlay(store, kind, state[kind]) : state[kind];
15059
15356
  return Object.create(state, {
15060
15357
  uniforms: {
15061
15358
  get() {
15062
- return _uniforms ?? (_uniforms = createScopedStore(state.uniforms));
15359
+ return _uniforms ?? (_uniforms = createScopedStore(view("uniforms")));
15063
15360
  }
15064
15361
  },
15065
15362
  nodes: {
15066
15363
  get() {
15067
- return _nodes ?? (_nodes = createScopedStore(state.nodes));
15364
+ return _nodes ?? (_nodes = createScopedStore(view("nodes")));
15068
15365
  }
15069
15366
  },
15070
15367
  buffers: {
15071
15368
  get() {
15072
- return _buffers ?? (_buffers = createScopedStore(state.buffers));
15369
+ return _buffers ?? (_buffers = createScopedStore(view("buffers")));
15073
15370
  }
15074
15371
  },
15075
15372
  gpuStorage: {
15076
15373
  get() {
15077
- return _gpuStorage ?? (_gpuStorage = createScopedStore(state.gpuStorage));
15374
+ return _gpuStorage ?? (_gpuStorage = createScopedStore(view("gpuStorage")));
15078
15375
  }
15079
15376
  }
15080
15377
  });
@@ -15197,65 +15494,56 @@ function useCompareMemoize(value, deep) {
15197
15494
  return ref.current;
15198
15495
  }
15199
15496
 
15497
+ function useScopedResource(options) {
15498
+ const { store, kind, scope, isLeaf, create, prepare, reconcile, input } = options;
15499
+ const scopeKey = scope ?? ROOT_SCOPE;
15500
+ const version = store((s) => s._hmrVersion);
15501
+ const entries = React.useMemo(() => {
15502
+ const staged = peekStaged(store, kind, scopeKey);
15503
+ const reusable = isScopeValid(store, kind, scopeKey);
15504
+ const map = store.getState()[kind];
15505
+ const committed = reusable ? readCommittedScope(map, scopeKey, isLeaf) : void 0;
15506
+ const result = {};
15507
+ let fresh = null;
15508
+ for (const [name, candidate] of Object.entries(create())) {
15509
+ const existing = staged?.[name] ?? committed?.[name];
15510
+ if (existing !== void 0) {
15511
+ result[name] = existing;
15512
+ reconcile?.(existing, candidate);
15513
+ } else {
15514
+ const value = prepare ? prepare(name, candidate) : candidate;
15515
+ result[name] = value;
15516
+ (fresh ?? (fresh = {}))[name] = value;
15517
+ }
15518
+ }
15519
+ if (fresh) stageEntries(store, kind, scopeKey, fresh);
15520
+ return result;
15521
+ }, [store, kind, scopeKey, version, input]);
15522
+ useIsomorphicLayoutEffect(() => {
15523
+ flushStagedScope(store, kind, scopeKey, isLeaf);
15524
+ });
15525
+ return entries;
15526
+ }
15527
+
15200
15528
  const isUniformNode$1 = (value) => value !== null && typeof value === "object" && "value" in value && "uuid" in value;
15201
15529
  function useUniforms(creatorOrScope, scope) {
15202
15530
  const store = usePrimaryStore();
15203
15531
  const removeUniforms2 = React.useCallback(
15204
- (names, targetScope) => {
15205
- const nameArray = Array.isArray(names) ? names : [names];
15206
- store.setState((state) => {
15207
- if (targetScope) {
15208
- const currentScope = { ...state.uniforms[targetScope] };
15209
- for (const name of nameArray) delete currentScope[name];
15210
- return { uniforms: { ...state.uniforms, [targetScope]: currentScope } };
15211
- }
15212
- const uniforms2 = { ...state.uniforms };
15213
- for (const name of nameArray) if (isUniformNode$1(uniforms2[name])) delete uniforms2[name];
15214
- return { uniforms: uniforms2 };
15215
- });
15216
- },
15532
+ (names, targetScope) => removeResourceEntries(store, "uniforms", Array.isArray(names) ? names : [names], targetScope, isUniformNode$1),
15217
15533
  [store]
15218
15534
  );
15219
15535
  const clearUniforms = React.useCallback(
15220
- (targetScope) => {
15221
- store.setState((state) => {
15222
- if (targetScope && targetScope !== "root") {
15223
- const { [targetScope]: _, ...rest } = state.uniforms;
15224
- return { uniforms: rest };
15225
- }
15226
- if (targetScope === "root") {
15227
- const uniforms2 = {};
15228
- for (const [key, value] of Object.entries(state.uniforms)) {
15229
- if (!isUniformNode$1(value)) uniforms2[key] = value;
15230
- }
15231
- return { uniforms: uniforms2 };
15232
- }
15233
- return { uniforms: {} };
15234
- });
15235
- },
15536
+ (targetScope) => clearResourceEntries(store, "uniforms", targetScope, isUniformNode$1),
15236
15537
  [store]
15237
15538
  );
15238
15539
  const rebuildUniforms = React.useCallback(
15239
- (targetScope) => {
15240
- store.setState((state) => {
15241
- let newUniforms = {};
15242
- if (targetScope && targetScope !== "root") {
15243
- const { [targetScope]: _, ...rest } = state.uniforms;
15244
- newUniforms = rest;
15245
- } else if (targetScope === "root") {
15246
- for (const [key, value] of Object.entries(state.uniforms)) {
15247
- if (!isUniformNode$1(value)) newUniforms[key] = value;
15248
- }
15249
- }
15250
- return { uniforms: newUniforms, _hmrVersion: state._hmrVersion + 1 };
15251
- });
15252
- },
15540
+ (targetScope) => rebuildResource(store, "uniforms", targetScope),
15253
15541
  [store]
15254
15542
  );
15255
15543
  const inputForMemoization = React.useMemo(() => {
15256
15544
  let raw = creatorOrScope;
15257
15545
  if (is.fun(creatorOrScope)) {
15258
- const wrappedState = createLazyCreatorState(store.getState());
15546
+ const wrappedState = createLazyCreatorState(store.getState(), store);
15259
15547
  raw = creatorOrScope(wrappedState);
15260
15548
  }
15261
15549
  if (raw && typeof raw === "object" && !Array.isArray(raw)) {
@@ -15270,80 +15558,43 @@ function useUniforms(creatorOrScope, scope) {
15270
15558
  const memoizedInput = useCompareMemoize(inputForMemoization);
15271
15559
  const isReader = memoizedInput === void 0 || typeof memoizedInput === "string";
15272
15560
  const storeUniforms = usePrimaryThree((s) => s.uniforms);
15273
- const hmrVersion = usePrimaryThree((s) => s._hmrVersion);
15274
- const readerDep = isReader ? storeUniforms : null;
15275
- const creatorDep = isReader ? null : hmrVersion;
15276
- const uniforms = React.useMemo(() => {
15277
- if (memoizedInput === void 0) {
15278
- return storeUniforms;
15279
- }
15280
- if (typeof memoizedInput === "string") {
15281
- const scopeData = storeUniforms[memoizedInput];
15282
- if (scopeData && !isUniformNode$1(scopeData)) return scopeData;
15283
- return {};
15284
- }
15285
- const state = store.getState();
15286
- const set = store.setState;
15287
- if (typeof memoizedInput !== "object" || memoizedInput === null) {
15288
- throw new Error("Invalid uniform input");
15289
- }
15290
- const created = memoizedInput;
15291
- const result = {};
15292
- let hasNewUniforms = false;
15293
- let targetRecord = state.uniforms;
15294
- if (scope) {
15295
- if (!state.uniforms[scope]) state.uniforms[scope] = {};
15296
- targetRecord = state.uniforms[scope];
15297
- }
15298
- for (const [name, node] of Object.entries(created)) {
15299
- if (targetRecord[name]) {
15300
- result[name] = targetRecord[name];
15301
- const existingVal = result[name].value;
15302
- const newVal = vectorize(node);
15303
- let equals = newVal === existingVal;
15304
- if (!equals && hasEqualsMethod(existingVal) && hasEqualsMethod(newVal)) {
15305
- if (isThreeVector(existingVal) && isThreeVector(newVal)) {
15306
- equals = vectorEquals(existingVal, newVal);
15307
- } else if (isSameThreeType(existingVal, newVal)) {
15308
- equals = existingVal.equals(newVal);
15309
- }
15310
- }
15311
- if (!equals) result[name].value = newVal;
15312
- } else {
15313
- result[name] = createUniform(name, node, scope);
15314
- hasNewUniforms = true;
15315
- }
15316
- }
15317
- if (hasNewUniforms) {
15318
- if (scope) {
15319
- set((s) => ({
15320
- uniforms: {
15321
- ...s.uniforms,
15322
- [scope]: { ...s.uniforms[scope], ...result }
15323
- }
15324
- }));
15325
- } else {
15326
- set((s) => ({ uniforms: { ...s.uniforms, ...result } }));
15561
+ const created = useScopedResource({
15562
+ store,
15563
+ kind: "uniforms",
15564
+ scope: isReader ? void 0 : scope,
15565
+ isLeaf: isUniformNode$1,
15566
+ input: memoizedInput,
15567
+ create: () => {
15568
+ if (isReader) return {};
15569
+ if (typeof memoizedInput !== "object" || memoizedInput === null) throw new Error("Invalid uniform input");
15570
+ return memoizedInput;
15571
+ },
15572
+ prepare: (name, candidate) => createUniform(name, candidate, scope),
15573
+ reconcile: (existing, candidate) => {
15574
+ const existingVal = existing.value;
15575
+ const newVal = vectorize(candidate);
15576
+ let equals = newVal === existingVal;
15577
+ if (!equals && hasEqualsMethod(existingVal) && hasEqualsMethod(newVal)) {
15578
+ if (isThreeVector(existingVal) && isThreeVector(newVal)) {
15579
+ equals = vectorEquals(existingVal, newVal);
15580
+ } else if (isSameThreeType(existingVal, newVal)) {
15581
+ equals = existingVal.equals(newVal);
15582
+ }
15327
15583
  }
15584
+ if (!equals) existing.value = newVal;
15328
15585
  }
15329
- return result;
15330
- }, [store, memoizedInput, scope, readerDep, creatorDep]);
15586
+ });
15587
+ let uniforms = created;
15588
+ if (memoizedInput === void 0) {
15589
+ uniforms = storeUniforms;
15590
+ } else if (typeof memoizedInput === "string") {
15591
+ const scopeData = storeUniforms[memoizedInput];
15592
+ uniforms = scopeData && !isUniformNode$1(scopeData) ? scopeData : {};
15593
+ }
15331
15594
  return { ...uniforms, removeUniforms: removeUniforms2, clearUniforms, rebuildUniforms };
15332
15595
  }
15333
15596
  function rebuildAllUniforms(store, scope) {
15334
- store = store.getState().primaryStore ?? store;
15335
- store.setState((state) => {
15336
- let newUniforms = {};
15337
- if (scope && scope !== "root") {
15338
- const { [scope]: _, ...rest } = state.uniforms;
15339
- newUniforms = rest;
15340
- } else if (scope === "root") {
15341
- for (const [key, value] of Object.entries(state.uniforms)) {
15342
- if (!isUniformNode$1(value)) newUniforms[key] = value;
15343
- }
15344
- }
15345
- return { uniforms: newUniforms, _hmrVersion: state._hmrVersion + 1 };
15346
- });
15597
+ rebuildResource(store, "uniforms", scope);
15347
15598
  }
15348
15599
  function removeUniforms(set, names, scope) {
15349
15600
  set((state) => {
@@ -15407,192 +15658,91 @@ function isSameThreeType(a, b) {
15407
15658
  return obj_a.isColor && obj_b.isColor || obj_a.isMatrix3 && obj_b.isMatrix3 || obj_a.isMatrix4 && obj_b.isMatrix4 || obj_a.isEuler && obj_b.isEuler || obj_a.isQuaternion && obj_b.isQuaternion;
15408
15659
  }
15409
15660
 
15661
+ const uniformOf = tsl.uniform;
15410
15662
  const isUniformNode = (value) => value !== null && typeof value === "object" && "value" in value && "uuid" in value;
15411
15663
  const isTSLNode$1 = (value) => value !== null && typeof value === "object" && "uuid" in value && "nodeType" in value;
15412
15664
  function useUniform(name, value) {
15413
15665
  const store = useStore();
15414
- const hmrVersion = useThree((s) => s._hmrVersion);
15415
- return React.useMemo(() => {
15416
- const state = store.getState();
15417
- const set = store.setState;
15418
- const existing = state.uniforms[name];
15419
- if (existing && isUniformNode(existing)) {
15666
+ const registered = useScopedResource({
15667
+ store,
15668
+ kind: "uniforms",
15669
+ scope: void 0,
15670
+ isLeaf: isUniformNode,
15671
+ input: name,
15672
+ create: () => value === void 0 ? {} : { [name]: value },
15673
+ prepare: createNamedUniform,
15674
+ reconcile: (existing2) => {
15420
15675
  if (value !== void 0 && !isTSLNode$1(value) && !isUniformNode(value)) {
15421
- existing.value = typeof value === "string" ? new webgpu.Color(value) : value;
15676
+ existing2.value = typeof value === "string" ? new webgpu.Color(value) : value;
15422
15677
  }
15423
- return existing;
15424
- }
15425
- if (value === void 0) {
15426
- throw new Error(
15427
- `[useUniform] Uniform "${name}" not found. Create it first with: useUniform('${name}', initialValue)`
15428
- );
15429
- }
15430
- if (isUniformNode(value)) {
15431
- const node2 = value;
15432
- if (typeof node2.setName === "function") {
15433
- node2.setName(name);
15434
- }
15435
- set((s) => ({
15436
- uniforms: { ...s.uniforms, [name]: node2 }
15437
- }));
15438
- return node2;
15439
- }
15440
- let node;
15441
- if (isTSLNode$1(value)) {
15442
- node = tsl.uniform(value);
15443
- } else if (typeof value === "string") {
15444
- node = tsl.uniform(new webgpu.Color(value));
15445
- } else {
15446
- node = tsl.uniform(value);
15447
- }
15448
- if (typeof node.setName === "function") {
15449
- node.setName(name);
15450
15678
  }
15451
- set((s) => ({
15452
- uniforms: {
15453
- ...s.uniforms,
15454
- [name]: node
15455
- }
15456
- }));
15457
- return node;
15458
- }, [store, name, hmrVersion]);
15679
+ });
15680
+ if (registered[name]) return registered[name];
15681
+ const existing = peekStaged(store, "uniforms", ROOT_SCOPE)?.[name] ?? store.getState().uniforms[name];
15682
+ if (existing && isUniformNode(existing)) return existing;
15683
+ throw new Error(
15684
+ `[useUniform] Uniform "${name}" not found. Create it first with: useUniform('${name}', initialValue)`
15685
+ );
15686
+ }
15687
+ function createNamedUniform(name, value) {
15688
+ let node;
15689
+ if (isUniformNode(value)) {
15690
+ node = value;
15691
+ } else if (isTSLNode$1(value)) {
15692
+ node = uniformOf(value);
15693
+ } else if (typeof value === "string") {
15694
+ node = tsl.uniform(new webgpu.Color(value));
15695
+ } else {
15696
+ node = uniformOf(value);
15697
+ }
15698
+ if (typeof node.setName === "function") {
15699
+ node.setName(name);
15700
+ }
15701
+ return node;
15459
15702
  }
15460
15703
 
15461
15704
  const isTSLNode = (value) => value !== null && typeof value === "object" && ("uuid" in value || "nodeType" in value);
15462
15705
  function useNodes(creatorOrScope, scope) {
15463
15706
  const store = usePrimaryStore();
15464
15707
  const removeNodes2 = React.useCallback(
15465
- (names, targetScope) => {
15466
- const nameArray = Array.isArray(names) ? names : [names];
15467
- store.setState((state) => {
15468
- if (targetScope) {
15469
- const currentScope = { ...state.nodes[targetScope] };
15470
- for (const name of nameArray) delete currentScope[name];
15471
- return { nodes: { ...state.nodes, [targetScope]: currentScope } };
15472
- }
15473
- const nodes2 = { ...state.nodes };
15474
- for (const name of nameArray) if (isTSLNode(nodes2[name])) delete nodes2[name];
15475
- return { nodes: nodes2 };
15476
- });
15477
- },
15708
+ (names, targetScope) => removeResourceEntries(store, "nodes", Array.isArray(names) ? names : [names], targetScope, isTSLNode),
15478
15709
  [store]
15479
15710
  );
15480
15711
  const clearNodes = React.useCallback(
15481
- (targetScope) => {
15482
- store.setState((state) => {
15483
- if (targetScope && targetScope !== "root") {
15484
- const { [targetScope]: _, ...rest } = state.nodes;
15485
- return { nodes: rest };
15486
- }
15487
- if (targetScope === "root") {
15488
- const nodes2 = {};
15489
- for (const [key, value] of Object.entries(state.nodes)) {
15490
- if (!isTSLNode(value)) nodes2[key] = value;
15491
- }
15492
- return { nodes: nodes2 };
15493
- }
15494
- return { nodes: {} };
15495
- });
15496
- },
15712
+ (targetScope) => clearResourceEntries(store, "nodes", targetScope, isTSLNode),
15497
15713
  [store]
15498
15714
  );
15499
15715
  const rebuildNodes = React.useCallback(
15500
- (targetScope) => {
15501
- store.setState((state) => {
15502
- let newNodes = state.nodes;
15503
- if (targetScope && targetScope !== "root") {
15504
- const { [targetScope]: _, ...rest } = state.nodes;
15505
- newNodes = rest;
15506
- } else if (targetScope === "root") {
15507
- newNodes = {};
15508
- for (const [key, value] of Object.entries(state.nodes)) {
15509
- if (!isTSLNode(value)) newNodes[key] = value;
15510
- }
15511
- } else {
15512
- newNodes = {};
15513
- }
15514
- return { nodes: newNodes, _hmrVersion: state._hmrVersion + 1 };
15515
- });
15516
- },
15716
+ (targetScope) => rebuildResource(store, "nodes", targetScope),
15517
15717
  [store]
15518
15718
  );
15519
15719
  const isReader = creatorOrScope === void 0 || typeof creatorOrScope === "string";
15520
15720
  const storeNodes = usePrimaryThree((s) => s.nodes);
15521
- const hmrVersion = usePrimaryThree((s) => s._hmrVersion);
15522
- const scopeDep = typeof creatorOrScope === "string" ? creatorOrScope : scope;
15523
- const readerDep = isReader ? storeNodes : null;
15524
- const creatorDep = isReader ? null : hmrVersion;
15525
- const nodes = React.useMemo(() => {
15526
- if (creatorOrScope === void 0) {
15527
- return storeNodes;
15528
- }
15529
- if (typeof creatorOrScope === "string") {
15530
- const scopeData = storeNodes[creatorOrScope];
15531
- if (scopeData && !isTSLNode(scopeData)) return scopeData;
15532
- return {};
15533
- }
15534
- const state = store.getState();
15535
- const set = store.setState;
15536
- const creator = creatorOrScope;
15537
- const wrappedState = createLazyCreatorState(state);
15538
- const created = creator(wrappedState);
15539
- const result = {};
15540
- let hasNewNodes = false;
15541
- if (scope) {
15542
- const currentScope = state.nodes[scope] ?? {};
15543
- for (const [name, node] of Object.entries(created)) {
15544
- if (currentScope[name]) {
15545
- result[name] = currentScope[name];
15546
- } else {
15547
- node.setName?.(`${scope}.${name}`);
15548
- result[name] = node;
15549
- hasNewNodes = true;
15550
- }
15551
- }
15552
- if (hasNewNodes) {
15553
- set((s) => ({
15554
- nodes: {
15555
- ...s.nodes,
15556
- [scope]: { ...s.nodes[scope], ...result }
15557
- }
15558
- }));
15559
- }
15560
- return result;
15561
- }
15562
- for (const [name, node] of Object.entries(created)) {
15563
- const existing = state.nodes[name];
15564
- if (existing && isTSLNode(existing)) {
15565
- result[name] = existing;
15566
- } else {
15567
- node.setName?.(name);
15568
- result[name] = node;
15569
- hasNewNodes = true;
15570
- }
15571
- }
15572
- if (hasNewNodes) {
15573
- set((s) => ({ nodes: { ...s.nodes, ...result } }));
15721
+ const created = useScopedResource({
15722
+ store,
15723
+ kind: "nodes",
15724
+ scope: isReader ? void 0 : scope,
15725
+ isLeaf: isTSLNode,
15726
+ create: () => {
15727
+ if (isReader) return {};
15728
+ return creatorOrScope(createLazyCreatorState(store.getState(), store));
15729
+ },
15730
+ prepare: (name, node) => {
15731
+ node.setName?.(scope ? `${scope}.${name}` : name);
15732
+ return node;
15574
15733
  }
15575
- return result;
15576
- }, [store, scopeDep, readerDep, creatorDep]);
15734
+ });
15735
+ let nodes = created;
15736
+ if (creatorOrScope === void 0) {
15737
+ nodes = storeNodes;
15738
+ } else if (typeof creatorOrScope === "string") {
15739
+ const scopeData = storeNodes[creatorOrScope];
15740
+ nodes = scopeData && !isTSLNode(scopeData) ? scopeData : {};
15741
+ }
15577
15742
  return { ...nodes, removeNodes: removeNodes2, clearNodes, rebuildNodes };
15578
15743
  }
15579
15744
  function rebuildAllNodes(store, scope) {
15580
- store = store.getState().primaryStore ?? store;
15581
- store.setState((state) => {
15582
- let newNodes = state.nodes;
15583
- if (scope && scope !== "root") {
15584
- const { [scope]: _, ...rest } = state.nodes;
15585
- newNodes = rest;
15586
- } else if (scope === "root") {
15587
- newNodes = {};
15588
- for (const [key, value] of Object.entries(state.nodes)) {
15589
- if (!isTSLNode(value)) newNodes[key] = value;
15590
- }
15591
- } else {
15592
- newNodes = {};
15593
- }
15594
- return { nodes: newNodes, _hmrVersion: state._hmrVersion + 1 };
15595
- });
15745
+ rebuildResource(store, "nodes", scope);
15596
15746
  }
15597
15747
  function removeNodes(set, names, scope) {
15598
15748
  set((state) => {
@@ -15628,7 +15778,7 @@ function useLocalNodes(creator) {
15628
15778
  const textures = usePrimaryThree((s) => s.textures);
15629
15779
  const hmrVersion = usePrimaryThree((s) => s._hmrVersion);
15630
15780
  return React.useMemo(() => {
15631
- const wrappedState = createLazyCreatorState(store.getState());
15781
+ const wrappedState = createLazyCreatorState(store.getState(), store);
15632
15782
  return creator(wrappedState);
15633
15783
  }, [store, creator, uniforms, nodes, textures, hmrVersion]);
15634
15784
  }
@@ -15649,58 +15799,15 @@ const disposeBuffer = (buffer) => {
15649
15799
  function useBuffers(creatorOrScope, scope) {
15650
15800
  const store = usePrimaryStore();
15651
15801
  const removeBuffers = React.useCallback(
15652
- (names, targetScope) => {
15653
- const nameArray = Array.isArray(names) ? names : [names];
15654
- store.setState((state) => {
15655
- if (targetScope) {
15656
- const currentScope = { ...state.buffers[targetScope] };
15657
- for (const name of nameArray) delete currentScope[name];
15658
- return { buffers: { ...state.buffers, [targetScope]: currentScope } };
15659
- }
15660
- const buffers2 = { ...state.buffers };
15661
- for (const name of nameArray) if (isBufferLike(buffers2[name])) delete buffers2[name];
15662
- return { buffers: buffers2 };
15663
- });
15664
- },
15802
+ (names, targetScope) => removeResourceEntries(store, "buffers", Array.isArray(names) ? names : [names], targetScope, isBufferLike),
15665
15803
  [store]
15666
15804
  );
15667
15805
  const clearBuffers = React.useCallback(
15668
- (targetScope) => {
15669
- store.setState((state) => {
15670
- if (targetScope && targetScope !== "root") {
15671
- const { [targetScope]: _, ...rest } = state.buffers;
15672
- return { buffers: rest };
15673
- }
15674
- if (targetScope === "root") {
15675
- const buffers2 = {};
15676
- for (const [key, value] of Object.entries(state.buffers)) {
15677
- if (!isBufferLike(value)) buffers2[key] = value;
15678
- }
15679
- return { buffers: buffers2 };
15680
- }
15681
- return { buffers: {} };
15682
- });
15683
- },
15806
+ (targetScope) => clearResourceEntries(store, "buffers", targetScope, isBufferLike),
15684
15807
  [store]
15685
15808
  );
15686
15809
  const rebuildBuffers = React.useCallback(
15687
- (targetScope) => {
15688
- store.setState((state) => {
15689
- let newBuffers = state.buffers;
15690
- if (targetScope && targetScope !== "root") {
15691
- const { [targetScope]: _, ...rest } = state.buffers;
15692
- newBuffers = rest;
15693
- } else if (targetScope === "root") {
15694
- newBuffers = {};
15695
- for (const [key, value] of Object.entries(state.buffers)) {
15696
- if (!isBufferLike(value)) newBuffers[key] = value;
15697
- }
15698
- } else {
15699
- newBuffers = {};
15700
- }
15701
- return { buffers: newBuffers, _hmrVersion: state._hmrVersion + 1 };
15702
- });
15703
- },
15810
+ (targetScope) => rebuildResource(store, "buffers", targetScope),
15704
15811
  [store]
15705
15812
  );
15706
15813
  const disposeBuffers = React.useCallback(
@@ -15719,85 +15826,33 @@ function useBuffers(creatorOrScope, scope) {
15719
15826
  );
15720
15827
  const isReader = creatorOrScope === void 0 || typeof creatorOrScope === "string";
15721
15828
  const storeBuffers = usePrimaryThree((s) => s.buffers);
15722
- const hmrVersion = usePrimaryThree((s) => s._hmrVersion);
15723
- const scopeDep = typeof creatorOrScope === "string" ? creatorOrScope : scope;
15724
- const readerDep = isReader ? storeBuffers : null;
15725
- const creatorDep = isReader ? null : hmrVersion;
15726
- const buffers = React.useMemo(() => {
15727
- if (creatorOrScope === void 0) {
15728
- return storeBuffers;
15729
- }
15730
- if (typeof creatorOrScope === "string") {
15731
- const scopeData = storeBuffers[creatorOrScope];
15732
- if (scopeData && !isBufferLike(scopeData)) return scopeData;
15733
- return {};
15734
- }
15735
- const state = store.getState();
15736
- const set = store.setState;
15737
- const creator = creatorOrScope;
15738
- const wrappedState = createLazyCreatorState(state);
15739
- const created = creator(wrappedState);
15740
- const result = {};
15741
- let hasNewBuffers = false;
15742
- if (scope) {
15743
- const currentScope = state.buffers[scope] ?? {};
15744
- for (const [name, buffer] of Object.entries(created)) {
15745
- if (currentScope[name]) {
15746
- result[name] = currentScope[name];
15747
- } else {
15748
- if ("setName" in buffer && typeof buffer.setName === "function") {
15749
- buffer.setName(`${scope}.${name}`);
15750
- }
15751
- result[name] = buffer;
15752
- hasNewBuffers = true;
15753
- }
15754
- }
15755
- if (hasNewBuffers) {
15756
- set((s) => ({
15757
- buffers: {
15758
- ...s.buffers,
15759
- [scope]: { ...s.buffers[scope], ...result }
15760
- }
15761
- }));
15762
- }
15763
- return result;
15764
- }
15765
- for (const [name, buffer] of Object.entries(created)) {
15766
- const existing = state.buffers[name];
15767
- if (existing && isBufferLike(existing)) {
15768
- result[name] = existing;
15769
- } else {
15770
- if ("setName" in buffer && typeof buffer.setName === "function") {
15771
- buffer.setName(name);
15772
- }
15773
- result[name] = buffer;
15774
- hasNewBuffers = true;
15829
+ const created = useScopedResource({
15830
+ store,
15831
+ kind: "buffers",
15832
+ scope: isReader ? void 0 : scope,
15833
+ isLeaf: isBufferLike,
15834
+ create: () => {
15835
+ if (isReader) return {};
15836
+ return creatorOrScope(createLazyCreatorState(store.getState(), store));
15837
+ },
15838
+ prepare: (name, buffer) => {
15839
+ if ("setName" in buffer && typeof buffer.setName === "function") {
15840
+ buffer.setName(scope ? `${scope}.${name}` : name);
15775
15841
  }
15842
+ return buffer;
15776
15843
  }
15777
- if (hasNewBuffers) {
15778
- set((s) => ({ buffers: { ...s.buffers, ...result } }));
15779
- }
15780
- return result;
15781
- }, [store, scopeDep, readerDep, creatorDep]);
15844
+ });
15845
+ let buffers = created;
15846
+ if (creatorOrScope === void 0) {
15847
+ buffers = storeBuffers;
15848
+ } else if (typeof creatorOrScope === "string") {
15849
+ const scopeData = storeBuffers[creatorOrScope];
15850
+ buffers = scopeData && !isBufferLike(scopeData) ? scopeData : {};
15851
+ }
15782
15852
  return { ...buffers, removeBuffers, clearBuffers, rebuildBuffers, disposeBuffers };
15783
15853
  }
15784
15854
  function rebuildAllBuffers(store, scope) {
15785
- store = store.getState().primaryStore ?? store;
15786
- store.setState((state) => {
15787
- let newBuffers = state.buffers;
15788
- if (scope && scope !== "root") {
15789
- const { [scope]: _, ...rest } = state.buffers;
15790
- newBuffers = rest;
15791
- } else if (scope === "root") {
15792
- newBuffers = {};
15793
- for (const [key, value] of Object.entries(state.buffers)) {
15794
- if (!isBufferLike(value)) newBuffers[key] = value;
15795
- }
15796
- } else {
15797
- newBuffers = {};
15798
- }
15799
- return { buffers: newBuffers, _hmrVersion: state._hmrVersion + 1 };
15800
- });
15855
+ rebuildResource(store, "buffers", scope);
15801
15856
  }
15802
15857
 
15803
15858
  const isStorageLike = (value) => {
@@ -15816,58 +15871,15 @@ const disposeStorage = (storage) => {
15816
15871
  function useGPUStorage(creatorOrScope, scope) {
15817
15872
  const store = usePrimaryStore();
15818
15873
  const removeStorage = React.useCallback(
15819
- (names, targetScope) => {
15820
- const nameArray = Array.isArray(names) ? names : [names];
15821
- store.setState((state) => {
15822
- if (targetScope) {
15823
- const currentScope = { ...state.gpuStorage[targetScope] };
15824
- for (const name of nameArray) delete currentScope[name];
15825
- return { gpuStorage: { ...state.gpuStorage, [targetScope]: currentScope } };
15826
- }
15827
- const gpuStorage2 = { ...state.gpuStorage };
15828
- for (const name of nameArray) if (isStorageLike(gpuStorage2[name])) delete gpuStorage2[name];
15829
- return { gpuStorage: gpuStorage2 };
15830
- });
15831
- },
15874
+ (names, targetScope) => removeResourceEntries(store, "gpuStorage", Array.isArray(names) ? names : [names], targetScope, isStorageLike),
15832
15875
  [store]
15833
15876
  );
15834
15877
  const clearStorage = React.useCallback(
15835
- (targetScope) => {
15836
- store.setState((state) => {
15837
- if (targetScope && targetScope !== "root") {
15838
- const { [targetScope]: _, ...rest } = state.gpuStorage;
15839
- return { gpuStorage: rest };
15840
- }
15841
- if (targetScope === "root") {
15842
- const gpuStorage2 = {};
15843
- for (const [key, value] of Object.entries(state.gpuStorage)) {
15844
- if (!isStorageLike(value)) gpuStorage2[key] = value;
15845
- }
15846
- return { gpuStorage: gpuStorage2 };
15847
- }
15848
- return { gpuStorage: {} };
15849
- });
15850
- },
15878
+ (targetScope) => clearResourceEntries(store, "gpuStorage", targetScope, isStorageLike),
15851
15879
  [store]
15852
15880
  );
15853
15881
  const rebuildStorage = React.useCallback(
15854
- (targetScope) => {
15855
- store.setState((state) => {
15856
- let newStorage = state.gpuStorage;
15857
- if (targetScope && targetScope !== "root") {
15858
- const { [targetScope]: _, ...rest } = state.gpuStorage;
15859
- newStorage = rest;
15860
- } else if (targetScope === "root") {
15861
- newStorage = {};
15862
- for (const [key, value] of Object.entries(state.gpuStorage)) {
15863
- if (!isStorageLike(value)) newStorage[key] = value;
15864
- }
15865
- } else {
15866
- newStorage = {};
15867
- }
15868
- return { gpuStorage: newStorage, _hmrVersion: state._hmrVersion + 1 };
15869
- });
15870
- },
15882
+ (targetScope) => rebuildResource(store, "gpuStorage", targetScope),
15871
15883
  [store]
15872
15884
  );
15873
15885
  const disposeStorageFn = React.useCallback(
@@ -15886,72 +15898,33 @@ function useGPUStorage(creatorOrScope, scope) {
15886
15898
  );
15887
15899
  const isReader = creatorOrScope === void 0 || typeof creatorOrScope === "string";
15888
15900
  const storeStorage = usePrimaryThree((s) => s.gpuStorage);
15889
- const hmrVersion = usePrimaryThree((s) => s._hmrVersion);
15890
- const scopeDep = typeof creatorOrScope === "string" ? creatorOrScope : scope;
15891
- const readerDep = isReader ? storeStorage : null;
15892
- const creatorDep = isReader ? null : hmrVersion;
15893
- const gpuStorage = React.useMemo(() => {
15894
- if (creatorOrScope === void 0) {
15895
- return storeStorage;
15896
- }
15897
- if (typeof creatorOrScope === "string") {
15898
- const scopeData = storeStorage[creatorOrScope];
15899
- if (scopeData && !isStorageLike(scopeData)) return scopeData;
15900
- return {};
15901
- }
15902
- const state = store.getState();
15903
- const set = store.setState;
15904
- const creator = creatorOrScope;
15905
- const wrappedState = createLazyCreatorState(state);
15906
- const created = creator(wrappedState);
15907
- const result = {};
15908
- let hasNewStorage = false;
15909
- if (scope) {
15910
- const currentScope = state.gpuStorage[scope] ?? {};
15911
- for (const [name, storage] of Object.entries(created)) {
15912
- if (currentScope[name]) {
15913
- result[name] = currentScope[name];
15914
- } else {
15915
- if ("setName" in storage && typeof storage.setName === "function") {
15916
- storage.setName(`${scope}.${name}`);
15917
- }
15918
- if ("name" in storage && typeof storage.name === "string") {
15919
- storage.name = `${scope}.${name}`;
15920
- }
15921
- result[name] = storage;
15922
- hasNewStorage = true;
15923
- }
15924
- }
15925
- if (hasNewStorage) {
15926
- set((s) => ({
15927
- gpuStorage: {
15928
- ...s.gpuStorage,
15929
- [scope]: { ...s.gpuStorage[scope], ...result }
15930
- }
15931
- }));
15901
+ const created = useScopedResource({
15902
+ store,
15903
+ kind: "gpuStorage",
15904
+ scope: isReader ? void 0 : scope,
15905
+ isLeaf: isStorageLike,
15906
+ create: () => {
15907
+ if (isReader) return {};
15908
+ return creatorOrScope(createLazyCreatorState(store.getState(), store));
15909
+ },
15910
+ prepare: (name, storage) => {
15911
+ const label = scope ? `${scope}.${name}` : name;
15912
+ if ("setName" in storage && typeof storage.setName === "function") {
15913
+ storage.setName(label);
15932
15914
  }
15933
- return result;
15934
- }
15935
- for (const [name, storage] of Object.entries(created)) {
15936
- const existing = state.gpuStorage[name];
15937
- if (existing && isStorageLike(existing)) {
15938
- result[name] = existing;
15939
- } else {
15940
- if ("setName" in storage && typeof storage.setName === "function") {
15941
- storage.setName(name);
15942
- }
15943
- if ("name" in storage && typeof storage.name === "string") {
15944
- storage.name = name;
15945
- }
15946
- result[name] = storage;
15947
- hasNewStorage = true;
15915
+ if ("name" in storage && typeof storage.name === "string") {
15916
+ storage.name = label;
15948
15917
  }
15918
+ return storage;
15949
15919
  }
15950
- if (hasNewStorage) {
15951
- set((s) => ({ gpuStorage: { ...s.gpuStorage, ...result } }));
15952
- }
15953
- return result;
15954
- }, [store, scopeDep, readerDep, creatorDep]);
15920
+ });
15921
+ let gpuStorage = created;
15922
+ if (creatorOrScope === void 0) {
15923
+ gpuStorage = storeStorage;
15924
+ } else if (typeof creatorOrScope === "string") {
15925
+ const scopeData = storeStorage[creatorOrScope];
15926
+ gpuStorage = scopeData && !isStorageLike(scopeData) ? scopeData : {};
15927
+ }
15955
15928
  return {
15956
15929
  ...gpuStorage,
15957
15930
  removeStorage,
@@ -15961,22 +15934,7 @@ function useGPUStorage(creatorOrScope, scope) {
15961
15934
  };
15962
15935
  }
15963
15936
  function rebuildAllStorage(store, scope) {
15964
- store = store.getState().primaryStore ?? store;
15965
- store.setState((state) => {
15966
- let newStorage = state.gpuStorage;
15967
- if (scope && scope !== "root") {
15968
- const { [scope]: _, ...rest } = state.gpuStorage;
15969
- newStorage = rest;
15970
- } else if (scope === "root") {
15971
- newStorage = {};
15972
- for (const [key, value] of Object.entries(state.gpuStorage)) {
15973
- if (!isStorageLike(value)) newStorage[key] = value;
15974
- }
15975
- } else {
15976
- newStorage = {};
15977
- }
15978
- return { gpuStorage: newStorage, _hmrVersion: state._hmrVersion + 1 };
15979
- });
15937
+ rebuildResource(store, "gpuStorage", scope);
15980
15938
  }
15981
15939
 
15982
15940
  function useRenderPipeline(mainCB, setupCB) {
@@ -16017,12 +15975,12 @@ function useRenderPipeline(mainCB, setupCB) {
16017
15975
  const state = store.getState();
16018
15976
  const set = store.setState;
16019
15977
  try {
16020
- let pp = state.renderPipeline;
15978
+ let pipeline = state.renderPipeline;
16021
15979
  let currentPasses = { ...state.passes };
16022
- let justCreatedPP = false;
16023
- if (!pp) {
16024
- pp = new webgpu.PostProcessing(renderer);
16025
- justCreatedPP = true;
15980
+ let justCreatedPipeline = false;
15981
+ if (!pipeline) {
15982
+ pipeline = new webgpu.RenderPipeline(renderer);
15983
+ justCreatedPipeline = true;
16026
15984
  }
16027
15985
  const cacheValid = scenePassCacheRef.current && scenePassCacheRef.current.sceneUuid === scene.uuid && scenePassCacheRef.current.cameraUuid === camera.uuid;
16028
15986
  let scenePass;
@@ -16033,9 +15991,9 @@ function useRenderPipeline(mainCB, setupCB) {
16033
15991
  scenePassCacheRef.current = { sceneUuid: scene.uuid, cameraUuid: camera.uuid, scenePass };
16034
15992
  }
16035
15993
  currentPasses.scenePass = scenePass;
16036
- if (!pp.outputNode || justCreatedPP) pp.outputNode = scenePass;
16037
- set({ renderPipeline: pp, passes: currentPasses });
16038
- const shouldRunCallbacks = justCreatedPP || !callbacksRanRef.current || !cacheValid;
15994
+ if (!pipeline.outputNode || justCreatedPipeline) pipeline.outputNode = scenePass;
15995
+ set({ renderPipeline: pipeline, passes: currentPasses });
15996
+ const shouldRunCallbacks = justCreatedPipeline || !callbacksRanRef.current || !cacheValid;
16039
15997
  if (shouldRunCallbacks) {
16040
15998
  if (setupCBRef.current) {
16041
15999
  const freshState = store.getState();
@@ -16056,7 +16014,7 @@ function useRenderPipeline(mainCB, setupCB) {
16056
16014
  callbacksRanRef.current = true;
16057
16015
  }
16058
16016
  } catch (error) {
16059
- console.error("[useRenderPipeline] Setup error:", error);
16017
+ console.warn("[useRenderPipeline] Setup failed; the render pipeline was not configured:", error);
16060
16018
  }
16061
16019
  }, [store, renderer, scene, camera, isLegacy, rebuildVersion]);
16062
16020
  const passes = useThree((s) => s.passes);