@react-three/fiber 10.0.0-canary.4bc4a97 → 10.0.0-canary.4bf885a

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 CHANGED
@@ -2,7 +2,6 @@
2
2
 
3
3
  const webgpu = require('three/webgpu');
4
4
  const three = require('three');
5
- const Inspector_js = require('three/addons/inspector/Inspector.js');
6
5
  const jsxRuntime = require('react/jsx-runtime');
7
6
  const React = require('react');
8
7
  const useMeasure = require('react-use-measure');
@@ -12,6 +11,7 @@ const scheduler = require('@pmndrs/scheduler');
12
11
  const Tb = require('scheduler');
13
12
  const GroundedSkybox_js = require('three/examples/jsm/objects/GroundedSkybox.js');
14
13
  const HDRLoader_js = require('three/examples/jsm/loaders/HDRLoader.js');
14
+ const HDRCubeTextureLoader_js = require('three/examples/jsm/loaders/HDRCubeTextureLoader.js');
15
15
  const EXRLoader_js = require('three/examples/jsm/loaders/EXRLoader.js');
16
16
  const UltraHDRLoader_js = require('three/examples/jsm/loaders/UltraHDRLoader.js');
17
17
  const gainmapJs = require('@monogrid/gainmap-js');
@@ -52,18 +52,22 @@ const Tb__default = /*#__PURE__*/_interopDefaultCompat(Tb);
52
52
 
53
53
  const R3F_BUILD_LEGACY = true;
54
54
  const R3F_BUILD_WEBGPU = true;
55
+ async function loadInspector() {
56
+ const { Inspector } = await import('three/addons/inspector/Inspector.js');
57
+ return Inspector;
58
+ }
55
59
 
56
60
  const THREE = /*#__PURE__*/_mergeNamespaces({
57
61
  __proto__: null,
58
62
  CubeRenderTarget: webgpu.CubeRenderTarget,
59
63
  CubeRenderTargetCompat: webgpu.CubeRenderTarget,
60
- Inspector: Inspector_js.Inspector,
61
64
  R3F_BUILD_LEGACY: R3F_BUILD_LEGACY,
62
65
  R3F_BUILD_WEBGPU: R3F_BUILD_WEBGPU,
63
66
  RenderTargetCompat: webgpu.RenderTarget,
64
67
  WebGLCubeRenderTarget: three.WebGLCubeRenderTarget,
65
68
  WebGLRenderTarget: three.WebGLRenderTarget,
66
- WebGLRenderer: three.WebGLRenderer
69
+ WebGLRenderer: three.WebGLRenderer,
70
+ loadInspector: loadInspector
67
71
  }, [webgpu__namespace]);
68
72
 
69
73
  const primaryRegistry = /* @__PURE__ */ new Map();
@@ -310,7 +314,6 @@ function prepare(target, root, type, props) {
310
314
  }
311
315
  function invalidateInstance(instance) {
312
316
  if (!instance.parent) return;
313
- instance.props.onUpdate?.(instance.object);
314
317
  const state = instance.root?.getState?.();
315
318
  if (state && state.internal.frames === 0) state.invalidate();
316
319
  }
@@ -526,8 +529,10 @@ function hasVisibilityHandlers(handlers) {
526
529
  }
527
530
 
528
531
  const FROM_REF = Symbol.for("@react-three/fiber.fromRef");
529
- function fromRef(ref) {
530
- return { [FROM_REF]: ref };
532
+ function fromRef(ref, transform) {
533
+ const marker = { [FROM_REF]: ref };
534
+ if (transform) marker.transform = transform;
535
+ return marker;
531
536
  }
532
537
  function isFromRef(value) {
533
538
  return value !== null && typeof value === "object" && FROM_REF in value;
@@ -541,6 +546,63 @@ function isOnce(value) {
541
546
  return value !== null && typeof value === "object" && ONCE in value;
542
547
  }
543
548
 
549
+ const shownNotices = /* @__PURE__ */ new Set();
550
+ function notifyDepreciated({ heading, body, link }) {
551
+ if (typeof process !== "undefined" && (process.env.NODE_ENV === "test" || process.env.JEST_WORKER_ID !== void 0) && process.env.R3F_SHOW_DEPRECATION_WARNINGS !== "true") {
552
+ return;
553
+ }
554
+ if (shownNotices.has(heading)) return;
555
+ shownNotices.add(heading);
556
+ const caller = getCallerFrame();
557
+ if (caller) {
558
+ console.log();
559
+ }
560
+ const boxStyle = "background: #ff9800; color: #1a1a1a; padding: 8px 12px; border-radius: 4px; font-weight: 500;";
561
+ if (caller) {
562
+ console.log(`%c\u26A0\uFE0F ${heading}`, boxStyle);
563
+ }
564
+ if (body || link) {
565
+ let message = "";
566
+ if (body) message += body;
567
+ if (link) message += (body ? "\n\n" : "") + `More info: ${link}`;
568
+ console.warn(`%c${message}`, "font-weight: bold;");
569
+ }
570
+ }
571
+ function getCallerFrame(depth = 3) {
572
+ const stack = new Error().stack;
573
+ if (!stack) return null;
574
+ const lines = stack.split("\n");
575
+ const frame = lines[depth];
576
+ if (!frame) return null;
577
+ let match = frame.match(/^\s*at (?:(.+?) )?\(?(.+?):(\d+):(\d+)\)?$/);
578
+ if (!match) {
579
+ match = frame.match(/^(?:(.+?)@)?(.+?):(\d+):(\d+)$/);
580
+ }
581
+ if (!match) return null;
582
+ const [, fn, url, line] = match;
583
+ return {
584
+ functionName: fn ?? "<anonymous>",
585
+ location: formatLocation(url, Number(line))
586
+ };
587
+ }
588
+ function formatLocation(url, line) {
589
+ const clean = url.split("?")[0];
590
+ const file = clean.split("/").pop() ?? clean;
591
+ return `${file}:${line}`;
592
+ }
593
+ function notifyAlpha({ message, link }) {
594
+ if (typeof process !== "undefined" && (process.env.NODE_ENV === "test" || process.env.JEST_WORKER_ID !== void 0) && process.env.R3F_SHOW_ALPHA_WARNINGS !== "true") {
595
+ return;
596
+ }
597
+ if (shownNotices.has(message)) return;
598
+ shownNotices.add(message);
599
+ const boxStyle = "background: #6366f1; color: #ffffff; padding: 6px 10px; border-radius: 4px; font-weight: 500;";
600
+ console.log(`%c\u{1F52C} ${message}`, boxStyle);
601
+ {
602
+ console.log(`%cMore info: ${link}`, "color: #6366f1; font-weight: normal;");
603
+ }
604
+ }
605
+
544
606
  const RESERVED_PROPS = [
545
607
  "children",
546
608
  "key",
@@ -550,7 +612,6 @@ const RESERVED_PROPS = [
550
612
  "dispose",
551
613
  "attach",
552
614
  "object",
553
- "onUpdate",
554
615
  // Behavior flags
555
616
  "dispose"
556
617
  ];
@@ -628,11 +689,9 @@ function diffProps(instance, newProps) {
628
689
  for (const prop in instance.props) {
629
690
  if (RESERVED_PROPS.includes(prop) || newProps.hasOwnProperty(prop)) continue;
630
691
  const { root, key } = resolve(instance.object, prop);
631
- if (root.constructor && root.constructor.length === 0) {
692
+ if (typeof root?.constructor === "function") {
632
693
  const ctor = getMemoizedPrototype(root);
633
694
  if (!is.und(ctor)) changedProps[prop] = ctor[key];
634
- } else {
635
- changedProps[prop] = 0;
636
695
  }
637
696
  }
638
697
  return changedProps;
@@ -644,6 +703,13 @@ function applyProps(object, props) {
644
703
  for (const prop in props) {
645
704
  const value = props[prop];
646
705
  if (RESERVED_PROPS.includes(prop)) continue;
706
+ if (prop === "onUpdate") {
707
+ notifyDepreciated({
708
+ heading: "onUpdate is no longer an R3F prop",
709
+ body: "v10 removed the onUpdate hook: R3F no longer calls it after prop updates. The value is assigned to the object by name. On a Texture that sets three's Texture.onUpdate, which the renderer calls after a GPU upload, not after prop changes. On a node it replaces the Node.onUpdate() method. On other objects it is never called.\n\nUse an effect keyed on the props that change, read the object in useFrame, or use a ref callback.",
710
+ link: "https://docs.pmnd.rs/react-three-fiber/migration/v10#onupdate-removed"
711
+ });
712
+ }
647
713
  if (instance && EVENT_REGEX.test(prop)) {
648
714
  if (typeof value === "function") instance.handlers[prop] = value;
649
715
  else delete instance.handlers[prop];
@@ -749,682 +815,378 @@ function applyProps(object, props) {
749
815
  return object;
750
816
  }
751
817
 
752
- const shownNotices = /* @__PURE__ */ new Set();
753
- function notifyDepreciated({ heading, body, link }) {
754
- if (typeof process !== "undefined" && (process.env.NODE_ENV === "test" || process.env.JEST_WORKER_ID !== void 0) && process.env.R3F_SHOW_DEPRECATION_WARNINGS !== "true") {
755
- return;
756
- }
757
- if (shownNotices.has(heading)) return;
758
- shownNotices.add(heading);
759
- const caller = getCallerFrame();
760
- if (caller) {
761
- console.log();
762
- }
763
- const boxStyle = "background: #ff9800; color: #1a1a1a; padding: 8px 12px; border-radius: 4px; font-weight: 500;";
764
- if (caller) {
765
- console.log(`%c\u26A0\uFE0F ${heading}`, boxStyle);
766
- }
767
- if (body || link) {
768
- let message = "";
769
- if (body) message += body;
770
- if (link) message += (body ? "\n\n" : "") + `More info: ${link}`;
771
- console.warn(`%c${message}`, "font-weight: bold;");
772
- }
773
- }
774
- function getCallerFrame(depth = 3) {
775
- const stack = new Error().stack;
776
- if (!stack) return null;
777
- const lines = stack.split("\n");
778
- const frame = lines[depth];
779
- if (!frame) return null;
780
- let match = frame.match(/^\s*at (?:(.+?) )?\(?(.+?):(\d+):(\d+)\)?$/);
781
- if (!match) {
782
- match = frame.match(/^(?:(.+?)@)?(.+?):(\d+):(\d+)$/);
783
- }
784
- if (!match) return null;
785
- const [, fn, url, line] = match;
786
- return {
787
- functionName: fn ?? "<anonymous>",
788
- location: formatLocation(url, Number(line))
789
- };
818
+ const R3F_CONTEXT = Symbol.for("@react-three/fiber.context");
819
+ const context = globalThis[R3F_CONTEXT] ?? (globalThis[R3F_CONTEXT] = React__namespace.createContext(null));
820
+
821
+ function useStore() {
822
+ const store = React.useContext(context);
823
+ if (!store) throw new Error("R3F: Hooks can only be used within the Canvas component!");
824
+ return store;
790
825
  }
791
- function formatLocation(url, line) {
792
- const clean = url.split("?")[0];
793
- const file = clean.split("/").pop() ?? clean;
794
- return `${file}:${line}`;
826
+ function useThree(selector = (state) => state, equalityFn) {
827
+ return useStore()(selector, equalityFn);
795
828
  }
796
- function notifyAlpha({ message, link }) {
797
- if (typeof process !== "undefined" && (process.env.NODE_ENV === "test" || process.env.JEST_WORKER_ID !== void 0) && process.env.R3F_SHOW_ALPHA_WARNINGS !== "true") {
798
- return;
799
- }
800
- if (shownNotices.has(message)) return;
801
- shownNotices.add(message);
802
- const boxStyle = "background: #6366f1; color: #ffffff; padding: 6px 10px; border-radius: 4px; font-weight: 500;";
803
- console.log(`%c\u{1F52C} ${message}`, boxStyle);
804
- {
805
- console.log(`%cMore info: ${link}`, "color: #6366f1; font-weight: normal;");
829
+
830
+ const memoizedLoaders = /* @__PURE__ */ new WeakMap();
831
+ const isConstructor$1 = (value) => typeof value === "function" && value?.prototype?.constructor === value;
832
+ function getLoader$1(Proto) {
833
+ if (isConstructor$1(Proto)) {
834
+ let loader = memoizedLoaders.get(Proto);
835
+ if (!loader) {
836
+ loader = new Proto();
837
+ memoizedLoaders.set(Proto, loader);
838
+ }
839
+ return loader;
806
840
  }
841
+ return Proto;
807
842
  }
808
-
809
- const INTERNAL_LIB_MARKERS = ["zustand", "setState", "Object.assign"];
810
- function isInternalRendererAccess(stack, callerDepth = 2) {
811
- if (!stack) return false;
812
- const caller = stack.split("\n")[callerDepth] ?? "";
813
- return INTERNAL_LIB_MARKERS.some((m) => caller.includes(m));
843
+ function loadingFn(extensions, onProgress) {
844
+ return function(Proto, input) {
845
+ const loader = getLoader$1(Proto);
846
+ if (extensions) extensions(loader);
847
+ if ("loadAsync" in loader && typeof loader.loadAsync === "function") {
848
+ return loader.loadAsync(input, onProgress).then((data) => {
849
+ if (isObject3D(data?.scene)) Object.assign(data, buildGraph(data.scene));
850
+ return data;
851
+ });
852
+ }
853
+ return new Promise(
854
+ (res, reject) => loader.load(
855
+ input,
856
+ (data) => {
857
+ if (isObject3D(data?.scene)) Object.assign(data, buildGraph(data.scene));
858
+ res(data);
859
+ },
860
+ onProgress,
861
+ (error) => reject(new Error(`Could not load ${input}: ${error?.message}`))
862
+ )
863
+ );
864
+ };
865
+ }
866
+ function useLoader(loader, input, extensions, onProgress, cacheKey) {
867
+ const inputs = Array.isArray(input) ? input : [input];
868
+ const cacheKeys = Array.isArray(cacheKey ?? input) ? cacheKey ?? input : [cacheKey ?? input];
869
+ const fn = loadingFn(extensions, onProgress);
870
+ const results = cacheKeys.map(
871
+ (key, index) => suspendReact.suspend(() => fn(loader, inputs[index]), [loader, key], { equal: is.equ })
872
+ );
873
+ const stableRef = React.useRef(null);
874
+ const previous = stableRef.current;
875
+ const stable = previous !== null && previous.length === results.length && previous.every((value, i) => value === results[i]) ? previous : results;
876
+ stableRef.current = stable;
877
+ return Array.isArray(input) ? stable : stable[0];
814
878
  }
879
+ useLoader.preload = function(loader, input, extensions, onProgress, cacheKey) {
880
+ const inputs = Array.isArray(input) ? input : [input];
881
+ const cacheKeys = Array.isArray(cacheKey ?? input) ? cacheKey ?? input : [cacheKey ?? input];
882
+ cacheKeys.forEach(
883
+ (key, index) => suspendReact.preload(() => loadingFn(extensions, onProgress)(loader, inputs[index]), [loader, key])
884
+ );
885
+ };
886
+ useLoader.clear = function(loader, input, cacheKey) {
887
+ const cacheKeys = Array.isArray(cacheKey ?? input) ? cacheKey ?? input : [cacheKey ?? input];
888
+ cacheKeys.forEach((key) => suspendReact.clear([loader, key]));
889
+ };
890
+ useLoader.loader = getLoader$1;
815
891
 
816
- const R3F_CONTEXT = Symbol.for("@react-three/fiber.context");
817
- const context = globalThis[R3F_CONTEXT] ?? (globalThis[R3F_CONTEXT] = React__namespace.createContext(null));
818
- const createStore = (invalidate, advance) => {
819
- const rootStore = traditional.createWithEqualityFn((set, get) => {
820
- const position = new webgpu.Vector3();
821
- const defaultTarget = new webgpu.Vector3();
822
- const tempTarget = new webgpu.Vector3();
823
- function getCurrentViewport(camera = get().camera, target = defaultTarget, size = get().size) {
824
- const { width, height, top, left } = size;
825
- const aspect = width / height;
826
- if (target.isVector3) tempTarget.copy(target);
827
- else tempTarget.set(...target);
828
- const distance = camera.getWorldPosition(position).distanceTo(tempTarget);
829
- if (isOrthographicCamera(camera)) {
830
- return { width: width / camera.zoom, height: height / camera.zoom, top, left, factor: 1, distance, aspect };
831
- } else {
832
- const fov = camera.fov * Math.PI / 180;
833
- const h = 2 * Math.tan(fov / 2) * distance;
834
- const w = h * (width / height);
835
- return { width: w, height: h, top, left, factor: width / w, distance, aspect };
892
+ function useFrame(callback, priorityOrOptions) {
893
+ const store = React__namespace.useContext(context);
894
+ const isInsideCanvas = store !== null;
895
+ const scheduler$1 = scheduler.getScheduler();
896
+ const optionsKey = typeof priorityOrOptions === "number" ? `p:${priorityOrOptions}` : priorityOrOptions ? JSON.stringify({
897
+ id: priorityOrOptions.id,
898
+ phase: priorityOrOptions.phase,
899
+ priority: priorityOrOptions.priority,
900
+ fps: priorityOrOptions.fps,
901
+ drop: priorityOrOptions.drop,
902
+ enabled: priorityOrOptions.enabled,
903
+ before: priorityOrOptions.before,
904
+ after: priorityOrOptions.after
905
+ }) : "";
906
+ const options = React__namespace.useMemo(() => {
907
+ return typeof priorityOrOptions === "number" ? { priority: priorityOrOptions } : priorityOrOptions ?? {};
908
+ }, [optionsKey]);
909
+ const reactId = React__namespace.useId();
910
+ const id = options.id ?? reactId;
911
+ const callbackRef = useMutableCallback(callback);
912
+ const isLegacyPriority = typeof priorityOrOptions === "number" && priorityOrOptions > 0;
913
+ useIsomorphicLayoutEffect(() => {
914
+ if (!callback) return;
915
+ if (isInsideCanvas) {
916
+ const state = store.getState();
917
+ const rootId = state.internal.rootId;
918
+ if (isLegacyPriority) {
919
+ state.internal.priority++;
920
+ let parentRoot = state.previousRoot;
921
+ while (parentRoot) {
922
+ const parentState = parentRoot.getState();
923
+ if (parentState?.internal) parentState.internal.priority++;
924
+ parentRoot = parentState?.previousRoot;
925
+ }
926
+ notifyDepreciated({
927
+ heading: "useFrame with numeric priority is deprecated",
928
+ body: 'Using useFrame(callback, number) to control render order is deprecated.\n\nFor custom rendering, use: useFrame(callback, { phase: "render" })\nFor execution order within update phase, use: useFrame(callback, { priority: number })',
929
+ link: "https://docs.pmnd.rs/react-three-fiber/api/hooks#useframe"
930
+ });
836
931
  }
837
- }
838
- let performanceTimeout = void 0;
839
- const setPerformanceCurrent = (current) => set((state2) => ({ performance: { ...state2.performance, current } }));
840
- const pointer = new webgpu.Vector2();
841
- const rootState = {
842
- set,
843
- get,
844
- // Mock objects that have to be configured
845
- // primaryStore is set after store creation (self-reference for primary, primary's store for secondary)
846
- primaryStore: null,
847
- gl: null,
848
- renderer: null,
849
- camera: null,
850
- frustum: new webgpu.Frustum(),
851
- autoUpdateFrustum: true,
852
- raycaster: null,
853
- events: {
854
- priority: 1,
855
- enabled: true,
856
- connected: false,
857
- frameTimedRaycasts: true,
858
- alwaysFireOnScroll: true,
859
- updateOnFrame: false
860
- },
861
- scene: null,
862
- rootScene: null,
863
- xr: null,
864
- inspector: null,
865
- invalidate: (frames = 1, stackFrames = false) => invalidate(get(), frames, stackFrames),
866
- advance: (timestamp, runGlobalEffects) => advance(timestamp, runGlobalEffects, get()),
867
- textureColorSpace: webgpu.SRGBColorSpace,
868
- isLegacy: false,
869
- webGPUSupported: false,
870
- isNative: false,
871
- controls: null,
872
- pointer,
873
- mouse: pointer,
874
- frameloop: "always",
875
- onPointerMissed: void 0,
876
- onDragOverMissed: void 0,
877
- onDropMissed: void 0,
878
- performance: {
879
- current: 1,
880
- min: 0.5,
881
- max: 1,
882
- debounce: 200,
883
- regress: () => {
884
- const state2 = get();
885
- if (performanceTimeout) clearTimeout(performanceTimeout);
886
- if (state2.performance.current !== state2.performance.min) setPerformanceCurrent(state2.performance.min);
887
- performanceTimeout = setTimeout(
888
- () => setPerformanceCurrent(get().performance.max),
889
- state2.performance.debounce
890
- );
891
- }
892
- },
893
- size: { width: 0, height: 0, top: 0, left: 0 },
894
- viewport: {
895
- initialDpr: 0,
896
- dpr: 0,
897
- width: 0,
898
- height: 0,
899
- top: 0,
900
- left: 0,
901
- aspect: 0,
902
- distance: 0,
903
- factor: 0,
904
- getCurrentViewport
905
- },
906
- setEvents: (events) => set((state2) => ({ ...state2, events: { ...state2.events, ...events } })),
907
- setSize: (width, height, top, left) => {
908
- const state2 = get();
909
- if (width === void 0) {
910
- set({ _sizeImperative: false });
911
- if (state2._sizeProps) {
912
- const { width: propW, height: propH } = state2._sizeProps;
913
- if (propW !== void 0 || propH !== void 0) {
914
- const currentSize = state2.size;
915
- const newSize = {
916
- width: propW ?? currentSize.width,
917
- height: propH ?? currentSize.height,
918
- top: currentSize.top,
919
- left: currentSize.left
920
- };
921
- set((s) => ({
922
- size: newSize,
923
- viewport: { ...s.viewport, ...getCurrentViewport(state2.camera, defaultTarget, newSize) }
924
- }));
925
- scheduler.getScheduler().invalidate();
932
+ const wrappedCallback = (frameState, delta) => {
933
+ const localState = store.getState();
934
+ const mergedState = {
935
+ ...localState,
936
+ time: frameState.time,
937
+ delta: frameState.delta,
938
+ elapsed: frameState.elapsed,
939
+ frame: frameState.frame
940
+ };
941
+ callbackRef.current?.(mergedState, delta);
942
+ };
943
+ const unregister = scheduler$1.register(wrappedCallback, {
944
+ id,
945
+ rootId,
946
+ ...options
947
+ });
948
+ return () => {
949
+ unregister();
950
+ if (isLegacyPriority) {
951
+ const currentState = store.getState();
952
+ if (currentState.internal) {
953
+ currentState.internal.priority--;
954
+ let parentRoot = currentState.previousRoot;
955
+ while (parentRoot) {
956
+ const parentState = parentRoot.getState();
957
+ if (parentState?.internal) parentState.internal.priority--;
958
+ parentRoot = parentState?.previousRoot;
926
959
  }
927
960
  }
928
- return;
929
961
  }
930
- const w = width;
931
- const h = height ?? width;
932
- const t = top ?? state2.size.top;
933
- const l = left ?? state2.size.left;
934
- const size = { width: w, height: h, top: t, left: l };
935
- set((s) => ({
936
- size,
937
- viewport: { ...s.viewport, ...getCurrentViewport(state2.camera, defaultTarget, size) },
938
- _sizeImperative: true
939
- }));
940
- scheduler.getScheduler().invalidate();
962
+ };
963
+ } else {
964
+ return scheduler$1.register(
965
+ (state, delta) => {
966
+ const frameState = state;
967
+ if (!frameState.renderer) return;
968
+ callbackRef.current?.(frameState, delta);
969
+ },
970
+ { id, ...options }
971
+ );
972
+ }
973
+ }, [store, scheduler$1, id, optionsKey, isLegacyPriority, isInsideCanvas]);
974
+ const isPaused = React__namespace.useSyncExternalStore(
975
+ // Subscribe function
976
+ React__namespace.useCallback(
977
+ (onStoreChange) => {
978
+ return scheduler.getScheduler().subscribeJobState(id, onStoreChange);
941
979
  },
942
- setDpr: (dpr) => set((state2) => {
943
- const resolved = calculateDpr(dpr);
944
- return { viewport: { ...state2.viewport, dpr: resolved, initialDpr: state2.viewport.initialDpr || resolved } };
945
- }),
946
- setFrameloop: (frameloop = "always") => {
947
- set(() => ({ frameloop }));
948
- scheduler.getScheduler().frameloop = frameloop;
980
+ [id]
981
+ ),
982
+ // getSnapshot function
983
+ React__namespace.useCallback(() => scheduler.getScheduler().isJobPaused(id), [id]),
984
+ // getServerSnapshot function (SSR)
985
+ React__namespace.useCallback(() => false, [])
986
+ );
987
+ const controls = React__namespace.useMemo(() => {
988
+ const scheduler2 = scheduler.getScheduler();
989
+ return {
990
+ /** The job's unique ID */
991
+ id,
992
+ /**
993
+ * Access to the global scheduler for frame loop control.
994
+ * Use for controlling the entire frame loop, adding phases, etc.
995
+ */
996
+ scheduler: scheduler2,
997
+ /**
998
+ * The root that currently owns this job. Resolve on access because ambient
999
+ * jobs can be adopted by a Canvas after the controls object is created.
1000
+ */
1001
+ get rootId() {
1002
+ return scheduler.getScheduler().getJobRootId(id);
949
1003
  },
950
- setError: (error) => set(() => ({ error })),
951
- error: null,
952
- //* TSL State (managed via hooks: useUniforms, useNodes, useBuffers, useGPUStorage, useTextures, useRenderPipeline) ==============================
953
- uniforms: {},
954
- nodes: {},
955
- buffers: {},
956
- gpuStorage: {},
957
- textures: /* @__PURE__ */ new Map(),
958
- _textureRefs: /* @__PURE__ */ new Map(),
959
- renderPipeline: null,
960
- passes: {},
961
- _hmrVersion: 0,
962
- _sizeImperative: false,
963
- _sizeProps: null,
964
- previousRoot: void 0,
965
- internal: {
966
- // Events
967
- interaction: [],
968
- subscribers: [],
969
- // Per-pointer state (new unified structure)
970
- pointerMap: /* @__PURE__ */ new Map(),
971
- pointerDirty: /* @__PURE__ */ new Map(),
972
- lastEvent: React__namespace.createRef(),
973
- // Deprecated but kept for backwards compatibility
974
- hovered: /* @__PURE__ */ new Map(),
975
- initialClick: [0, 0],
976
- initialHits: [],
977
- capturedMap: /* @__PURE__ */ new Map(),
978
- // Visibility tracking (onFramed, onOccluded, onVisible)
979
- visibilityRegistry: /* @__PURE__ */ new Map(),
980
- // Occlusion system (WebGPU only)
981
- occlusionEnabled: false,
982
- occlusionObserver: null,
983
- occlusionCache: /* @__PURE__ */ new Map(),
984
- helperGroup: null,
985
- // Updates
986
- active: false,
987
- frames: 0,
988
- priority: 0,
989
- subscribe: (ref, priority, store) => {
990
- const internal = get().internal;
991
- internal.priority = internal.priority + (priority > 0 ? 1 : 0);
992
- internal.subscribers.push({ ref, priority, store });
993
- internal.subscribers = internal.subscribers.sort((a, b) => a.priority - b.priority);
994
- return () => {
995
- const internal2 = get().internal;
996
- if (internal2?.subscribers) {
997
- internal2.priority = internal2.priority - (priority > 0 ? 1 : 0);
998
- internal2.subscribers = internal2.subscribers.filter((s) => s.ref !== ref);
999
- }
1000
- };
1001
- },
1002
- // Renderer Storage (single source of truth)
1003
- actualRenderer: null,
1004
- // Scheduler for useFrameNext (initialized in renderer.tsx)
1005
- scheduler: null
1006
- }
1004
+ /**
1005
+ * Manually step this job only.
1006
+ * Bypasses FPS limiting - always runs.
1007
+ * @param timestamp Optional timestamp (defaults to performance.now())
1008
+ */
1009
+ step: (timestamp) => {
1010
+ scheduler.getScheduler().stepJob(id, timestamp);
1011
+ },
1012
+ /**
1013
+ * Manually step ALL jobs in the scheduler.
1014
+ * Useful for frameloop='never' mode.
1015
+ * @param timestamp Optional timestamp (defaults to performance.now())
1016
+ */
1017
+ stepAll: (timestamp) => {
1018
+ scheduler.getScheduler().step(timestamp);
1019
+ },
1020
+ /**
1021
+ * Request frames for this job's owning root without waking sibling roots.
1022
+ */
1023
+ invalidate: (frames, stackFrames) => {
1024
+ const scheduler3 = scheduler.getScheduler();
1025
+ const rootId = scheduler3.getJobRootId(id);
1026
+ if (rootId) scheduler3.invalidateRoot(rootId, frames, stackFrames);
1027
+ },
1028
+ /**
1029
+ * Pause this job (set enabled=false).
1030
+ * Job remains registered but won't run.
1031
+ */
1032
+ pause: () => {
1033
+ scheduler.getScheduler().pauseJob(id);
1034
+ },
1035
+ /**
1036
+ * Resume this job (set enabled=true).
1037
+ */
1038
+ resume: () => {
1039
+ scheduler.getScheduler().resumeJob(id);
1040
+ },
1041
+ /**
1042
+ * Reactive paused state - automatically updates when pause/resume is called.
1043
+ * No need for forceUpdate() in your components.
1044
+ */
1045
+ isPaused
1007
1046
  };
1008
- return rootState;
1009
- });
1010
- const state = rootStore.getState();
1011
- Object.defineProperty(state, "gl", {
1012
- get() {
1013
- const currentState = rootStore.getState();
1014
- if (!currentState.isLegacy && currentState.internal.actualRenderer) {
1015
- const stack = new Error().stack || "";
1016
- if (!isInternalRendererAccess(stack)) {
1017
- const cleanedStack = stack.split("\n").slice(2).join("\n") || "Stack trace unavailable";
1018
- notifyDepreciated({
1019
- heading: "Accessing state.gl in WebGPU mode",
1020
- body: "Please use state.renderer instead. state.gl is deprecated and will be removed in future versions.\n\nFor backwards compatibility, state.gl currently maps to state.renderer, but this may cause issues with libraries expecting WebGLRenderer.\n\nAccessed from:\n" + cleanedStack
1021
- });
1022
- }
1023
- }
1024
- return currentState.internal.actualRenderer;
1025
- },
1026
- set(value) {
1027
- rootStore.getState().internal.actualRenderer = value;
1028
- },
1029
- enumerable: true,
1030
- configurable: true
1031
- });
1032
- Object.defineProperty(state, "renderer", {
1033
- get() {
1034
- return rootStore.getState().internal.actualRenderer;
1035
- },
1036
- set(value) {
1037
- rootStore.getState().internal.actualRenderer = value;
1038
- },
1039
- enumerable: true,
1040
- configurable: true
1041
- });
1042
- let oldScene = state.scene;
1043
- rootStore.subscribe(() => {
1044
- const currentState = rootStore.getState();
1045
- const { scene, rootScene, set } = currentState;
1046
- if (scene !== oldScene) {
1047
- oldScene = scene;
1048
- if (scene?.isScene && scene !== rootScene) {
1049
- set({ rootScene: scene });
1050
- }
1051
- }
1052
- });
1053
- let oldSize = state.size;
1054
- let oldDpr = state.viewport.dpr;
1055
- let oldCamera = state.camera;
1056
- rootStore.subscribe(() => {
1057
- const { camera, size, viewport, set, internal } = rootStore.getState();
1058
- const actualRenderer = internal.actualRenderer;
1059
- const canvasTarget = internal.canvasTarget;
1060
- if (size.width !== oldSize.width || size.height !== oldSize.height || viewport.dpr !== oldDpr) {
1061
- oldSize = size;
1062
- oldDpr = viewport.dpr;
1063
- updateCamera(camera, size);
1064
- if (internal.isSecondary && canvasTarget) {
1065
- if (viewport.dpr > 0) canvasTarget.setPixelRatio(viewport.dpr);
1066
- canvasTarget.setSize(size.width, size.height, false);
1067
- } else {
1068
- if (viewport.dpr > 0) actualRenderer.setPixelRatio(viewport.dpr);
1069
- actualRenderer.setSize(size.width, size.height, false);
1070
- if (canvasTarget) {
1071
- if (viewport.dpr > 0) canvasTarget.setPixelRatio(viewport.dpr);
1072
- canvasTarget.setSize(size.width, size.height, false);
1073
- }
1074
- }
1075
- }
1076
- if (camera !== oldCamera) {
1077
- oldCamera = camera;
1078
- const { rootScene } = rootStore.getState();
1079
- if (camera && rootScene && !camera.parent) {
1080
- rootScene.add(camera);
1081
- }
1082
- set((state2) => ({ viewport: { ...state2.viewport, ...state2.viewport.getCurrentViewport(camera) } }));
1083
- const currentState = rootStore.getState();
1084
- if (currentState.autoUpdateFrustum && camera) {
1085
- updateFrustum(camera, currentState.frustum);
1086
- }
1087
- }
1088
- });
1089
- rootStore.subscribe((state2) => invalidate(state2));
1090
- return rootStore;
1091
- };
1047
+ }, [id, isPaused]);
1048
+ return controls;
1049
+ }
1092
1050
 
1093
- const memoizedLoaders = /* @__PURE__ */ new WeakMap();
1094
- const isConstructor$1 = (value) => typeof value === "function" && value?.prototype?.constructor === value;
1095
- function getLoader$1(Proto) {
1096
- if (isConstructor$1(Proto)) {
1097
- let loader = memoizedLoaders.get(Proto);
1098
- if (!loader) {
1099
- loader = new Proto();
1100
- memoizedLoaders.set(Proto, loader);
1051
+ const IsObject = (url) => url === Object(url) && !Array.isArray(url) && typeof url !== "function";
1052
+ function getUrls(input) {
1053
+ if (typeof input === "string") return [input];
1054
+ if (Array.isArray(input)) return input;
1055
+ return Object.values(input);
1056
+ }
1057
+ function inputSignature(input) {
1058
+ if (typeof input === "string") return input;
1059
+ if (Array.isArray(input)) return input.join("\0");
1060
+ return Object.keys(input).sort().map((key) => `${key}\0${input[key]}`).join("");
1061
+ }
1062
+ function allUrlsCached(urls, textureCache) {
1063
+ return urls.every((url) => textureCache.has(url));
1064
+ }
1065
+ function buildFromCache(input, textureCache) {
1066
+ if (typeof input === "string") {
1067
+ return textureCache.get(input);
1068
+ } else if (Array.isArray(input)) {
1069
+ return input.map((url) => textureCache.get(url));
1070
+ } else {
1071
+ const result = {};
1072
+ const objInput = input;
1073
+ for (const key in objInput) {
1074
+ result[key] = textureCache.get(objInput[key]);
1101
1075
  }
1102
- return loader;
1076
+ return result;
1103
1077
  }
1104
- return Proto;
1105
1078
  }
1106
- function loadingFn(extensions, onProgress) {
1107
- return function(Proto, input) {
1108
- const loader = getLoader$1(Proto);
1109
- if (extensions) extensions(loader);
1110
- if ("loadAsync" in loader && typeof loader.loadAsync === "function") {
1111
- return loader.loadAsync(input, onProgress).then((data) => {
1112
- if (isObject3D(data?.scene)) Object.assign(data, buildGraph(data.scene));
1113
- return data;
1079
+ function useTexture(input, optionsOrOnLoad) {
1080
+ const renderer = useThree((state) => state.internal.actualRenderer);
1081
+ const store = useStore();
1082
+ const options = typeof optionsOrOnLoad === "function" ? { onLoad: optionsOrOnLoad } : optionsOrOnLoad ?? {};
1083
+ const { onLoad, cache = true } = options;
1084
+ const onLoadRef = React.useRef(onLoad);
1085
+ onLoadRef.current = onLoad;
1086
+ const onLoadCalledForRef = React.useRef(null);
1087
+ const inputKey = React.useMemo(() => inputSignature(input), [input]);
1088
+ const stableInput = React.useMemo(() => input, [inputKey]);
1089
+ const urls = React.useMemo(() => getUrls(stableInput), [stableInput]);
1090
+ const cachedResult = React.useMemo(() => {
1091
+ if (!cache) return null;
1092
+ const textures = store.getState().textures;
1093
+ if (!allUrlsCached(urls, textures)) return null;
1094
+ return buildFromCache(stableInput, textures);
1095
+ }, [cache, urls, stableInput, store]);
1096
+ const loadedTextures = useLoader(
1097
+ webgpu.TextureLoader,
1098
+ IsObject(stableInput) ? Object.values(stableInput) : stableInput
1099
+ );
1100
+ const mappedTextures = React.useMemo(() => {
1101
+ if (cachedResult) return cachedResult;
1102
+ if (IsObject(stableInput)) {
1103
+ const keyed = {};
1104
+ const textureArray = loadedTextures;
1105
+ let i = 0;
1106
+ for (const key in stableInput) keyed[key] = textureArray[i++];
1107
+ return keyed;
1108
+ } else {
1109
+ return loadedTextures;
1110
+ }
1111
+ }, [stableInput, loadedTextures, cachedResult]);
1112
+ React.useLayoutEffect(() => {
1113
+ if (cachedResult) return;
1114
+ if (onLoadCalledForRef.current === inputKey) return;
1115
+ onLoadCalledForRef.current = inputKey;
1116
+ onLoadRef.current?.(mappedTextures);
1117
+ }, [cachedResult, mappedTextures, inputKey]);
1118
+ React.useEffect(() => {
1119
+ if (cachedResult) return;
1120
+ if ("initTexture" in renderer) {
1121
+ let textureArray = [];
1122
+ if (Array.isArray(loadedTextures)) {
1123
+ textureArray = loadedTextures;
1124
+ } else if (loadedTextures instanceof webgpu.Texture) {
1125
+ textureArray = [loadedTextures];
1126
+ } else if (IsObject(loadedTextures)) {
1127
+ textureArray = Object.values(loadedTextures);
1128
+ }
1129
+ textureArray.forEach((texture) => {
1130
+ if (texture instanceof webgpu.Texture) {
1131
+ renderer.initTexture(texture);
1132
+ }
1114
1133
  });
1115
1134
  }
1116
- return new Promise(
1117
- (res, reject) => loader.load(
1118
- input,
1119
- (data) => {
1120
- if (isObject3D(data?.scene)) Object.assign(data, buildGraph(data.scene));
1121
- res(data);
1122
- },
1123
- onProgress,
1124
- (error) => reject(new Error(`Could not load ${input}: ${error?.message}`))
1125
- )
1126
- );
1127
- };
1128
- }
1129
- function useLoader(loader, input, extensions, onProgress, cacheKey) {
1130
- const inputs = Array.isArray(input) ? input : [input];
1131
- const cacheKeys = Array.isArray(cacheKey ?? input) ? cacheKey ?? input : [cacheKey ?? input];
1132
- const fn = loadingFn(extensions, onProgress);
1133
- const results = cacheKeys.map(
1134
- (key, index) => suspendReact.suspend(() => fn(loader, inputs[index]), [loader, key], { equal: is.equ })
1135
- );
1136
- return Array.isArray(input) ? results : results[0];
1135
+ }, [renderer, loadedTextures, cachedResult]);
1136
+ React.useEffect(() => {
1137
+ if (!cache) return;
1138
+ const urlTextureMap = [];
1139
+ if (typeof stableInput === "string") {
1140
+ urlTextureMap.push([stableInput, mappedTextures]);
1141
+ } else if (Array.isArray(stableInput)) {
1142
+ const textureArray = mappedTextures;
1143
+ stableInput.forEach((url, i) => urlTextureMap.push([url, textureArray[i]]));
1144
+ } else if (IsObject(stableInput)) {
1145
+ const textureRecord = mappedTextures;
1146
+ for (const key in stableInput) {
1147
+ const url = stableInput[key];
1148
+ urlTextureMap.push([url, textureRecord[key]]);
1149
+ }
1150
+ }
1151
+ store.setState((state) => {
1152
+ const refs = new Map(state._textureRefs);
1153
+ let textures = state.textures;
1154
+ let added = false;
1155
+ for (const [url, texture] of urlTextureMap) {
1156
+ if (!textures.has(url)) {
1157
+ if (!added) {
1158
+ textures = new Map(textures);
1159
+ added = true;
1160
+ }
1161
+ textures.set(url, texture);
1162
+ }
1163
+ refs.set(url, (refs.get(url) ?? 0) + 1);
1164
+ }
1165
+ return added ? { textures, _textureRefs: refs } : { _textureRefs: refs };
1166
+ });
1167
+ return () => store.setState((state) => {
1168
+ const refs = new Map(state._textureRefs);
1169
+ for (const [url] of urlTextureMap) {
1170
+ const next = (refs.get(url) ?? 0) - 1;
1171
+ if (next <= 0) refs.delete(url);
1172
+ else refs.set(url, next);
1173
+ }
1174
+ return { _textureRefs: refs };
1175
+ });
1176
+ }, [cache, stableInput, mappedTextures, store]);
1177
+ return mappedTextures;
1137
1178
  }
1138
- useLoader.preload = function(loader, input, extensions, onProgress, cacheKey) {
1139
- const inputs = Array.isArray(input) ? input : [input];
1140
- const cacheKeys = Array.isArray(cacheKey ?? input) ? cacheKey ?? input : [cacheKey ?? input];
1141
- cacheKeys.forEach(
1142
- (key, index) => suspendReact.preload(() => loadingFn(extensions, onProgress)(loader, inputs[index]), [loader, key])
1143
- );
1144
- };
1145
- useLoader.clear = function(loader, input, cacheKey) {
1146
- const cacheKeys = Array.isArray(cacheKey ?? input) ? cacheKey ?? input : [cacheKey ?? input];
1147
- cacheKeys.forEach((key) => suspendReact.clear([loader, key]));
1148
- };
1149
- useLoader.loader = getLoader$1;
1150
-
1151
- function useFrame(callback, priorityOrOptions) {
1152
- const store = React__namespace.useContext(context);
1153
- const isInsideCanvas = store !== null;
1154
- const scheduler$1 = scheduler.getScheduler();
1155
- const optionsKey = typeof priorityOrOptions === "number" ? `p:${priorityOrOptions}` : priorityOrOptions ? JSON.stringify({
1156
- id: priorityOrOptions.id,
1157
- phase: priorityOrOptions.phase,
1158
- priority: priorityOrOptions.priority,
1159
- fps: priorityOrOptions.fps,
1160
- drop: priorityOrOptions.drop,
1161
- enabled: priorityOrOptions.enabled,
1162
- before: priorityOrOptions.before,
1163
- after: priorityOrOptions.after
1164
- }) : "";
1165
- const options = React__namespace.useMemo(() => {
1166
- return typeof priorityOrOptions === "number" ? { priority: priorityOrOptions } : priorityOrOptions ?? {};
1167
- }, [optionsKey]);
1168
- const reactId = React__namespace.useId();
1169
- const id = options.id ?? reactId;
1170
- const callbackRef = useMutableCallback(callback);
1171
- const isLegacyPriority = typeof priorityOrOptions === "number" && priorityOrOptions > 0;
1172
- useIsomorphicLayoutEffect(() => {
1173
- if (!callback) return;
1174
- if (isInsideCanvas) {
1175
- const state = store.getState();
1176
- const rootId = state.internal.rootId;
1177
- if (isLegacyPriority) {
1178
- state.internal.priority++;
1179
- let parentRoot = state.previousRoot;
1180
- while (parentRoot) {
1181
- const parentState = parentRoot.getState();
1182
- if (parentState?.internal) parentState.internal.priority++;
1183
- parentRoot = parentState?.previousRoot;
1184
- }
1185
- notifyDepreciated({
1186
- heading: "useFrame with numeric priority is deprecated",
1187
- body: 'Using useFrame(callback, number) to control render order is deprecated.\n\nFor custom rendering, use: useFrame(callback, { phase: "render" })\nFor execution order within update phase, use: useFrame(callback, { priority: number })',
1188
- link: "https://docs.pmnd.rs/react-three-fiber/api/hooks#useframe"
1189
- });
1190
- }
1191
- const wrappedCallback = (frameState, delta) => {
1192
- const localState = store.getState();
1193
- const mergedState = {
1194
- ...localState,
1195
- time: frameState.time,
1196
- delta: frameState.delta,
1197
- elapsed: frameState.elapsed,
1198
- frame: frameState.frame
1199
- };
1200
- callbackRef.current?.(mergedState, delta);
1201
- };
1202
- const unregister = scheduler$1.register(wrappedCallback, {
1203
- id,
1204
- rootId,
1205
- ...options
1206
- });
1207
- return () => {
1208
- unregister();
1209
- if (isLegacyPriority) {
1210
- const currentState = store.getState();
1211
- if (currentState.internal) {
1212
- currentState.internal.priority--;
1213
- let parentRoot = currentState.previousRoot;
1214
- while (parentRoot) {
1215
- const parentState = parentRoot.getState();
1216
- if (parentState?.internal) parentState.internal.priority--;
1217
- parentRoot = parentState?.previousRoot;
1218
- }
1219
- }
1220
- }
1221
- };
1222
- } else {
1223
- return scheduler$1.register(
1224
- (state, delta) => {
1225
- const frameState = state;
1226
- if (!frameState.renderer) return;
1227
- callbackRef.current?.(frameState, delta);
1228
- },
1229
- { id, ...options }
1230
- );
1231
- }
1232
- }, [store, scheduler$1, id, optionsKey, isLegacyPriority, isInsideCanvas]);
1233
- const isPaused = React__namespace.useSyncExternalStore(
1234
- // Subscribe function
1235
- React__namespace.useCallback(
1236
- (onStoreChange) => {
1237
- return scheduler.getScheduler().subscribeJobState(id, onStoreChange);
1238
- },
1239
- [id]
1240
- ),
1241
- // getSnapshot function
1242
- React__namespace.useCallback(() => scheduler.getScheduler().isJobPaused(id), [id]),
1243
- // getServerSnapshot function (SSR)
1244
- React__namespace.useCallback(() => false, [])
1245
- );
1246
- const controls = React__namespace.useMemo(() => {
1247
- const scheduler2 = scheduler.getScheduler();
1248
- return {
1249
- /** The job's unique ID */
1250
- id,
1251
- /**
1252
- * Access to the global scheduler for frame loop control.
1253
- * Use for controlling the entire frame loop, adding phases, etc.
1254
- */
1255
- scheduler: scheduler2,
1256
- /**
1257
- * Manually step this job only.
1258
- * Bypasses FPS limiting - always runs.
1259
- * @param timestamp Optional timestamp (defaults to performance.now())
1260
- */
1261
- step: (timestamp) => {
1262
- scheduler.getScheduler().stepJob(id, timestamp);
1263
- },
1264
- /**
1265
- * Manually step ALL jobs in the scheduler.
1266
- * Useful for frameloop='never' mode.
1267
- * @param timestamp Optional timestamp (defaults to performance.now())
1268
- */
1269
- stepAll: (timestamp) => {
1270
- scheduler.getScheduler().step(timestamp);
1271
- },
1272
- /**
1273
- * Pause this job (set enabled=false).
1274
- * Job remains registered but won't run.
1275
- */
1276
- pause: () => {
1277
- scheduler.getScheduler().pauseJob(id);
1278
- },
1279
- /**
1280
- * Resume this job (set enabled=true).
1281
- */
1282
- resume: () => {
1283
- scheduler.getScheduler().resumeJob(id);
1284
- },
1285
- /**
1286
- * Reactive paused state - automatically updates when pause/resume is called.
1287
- * No need for forceUpdate() in your components.
1288
- */
1289
- isPaused
1290
- };
1291
- }, [id, isPaused]);
1292
- return controls;
1293
- }
1294
-
1295
- const IsObject = (url) => url === Object(url) && !Array.isArray(url) && typeof url !== "function";
1296
- function getUrls(input) {
1297
- if (typeof input === "string") return [input];
1298
- if (Array.isArray(input)) return input;
1299
- return Object.values(input);
1300
- }
1301
- function allUrlsCached(urls, textureCache) {
1302
- return urls.every((url) => textureCache.has(url));
1303
- }
1304
- function buildFromCache(input, textureCache) {
1305
- if (typeof input === "string") {
1306
- return textureCache.get(input);
1307
- } else if (Array.isArray(input)) {
1308
- return input.map((url) => textureCache.get(url));
1309
- } else {
1310
- const result = {};
1311
- const objInput = input;
1312
- for (const key in objInput) {
1313
- result[key] = textureCache.get(objInput[key]);
1314
- }
1315
- return result;
1316
- }
1317
- }
1318
- function useTexture(input, optionsOrOnLoad) {
1319
- const renderer = useThree((state) => state.internal.actualRenderer);
1320
- const store = useStore();
1321
- const options = typeof optionsOrOnLoad === "function" ? { onLoad: optionsOrOnLoad } : optionsOrOnLoad ?? {};
1322
- const { onLoad, cache = true } = options;
1323
- const onLoadRef = React.useRef(onLoad);
1324
- onLoadRef.current = onLoad;
1325
- const onLoadCalledForRef = React.useRef(null);
1326
- const urls = React.useMemo(() => getUrls(input), [input]);
1327
- const cachedResult = React.useMemo(() => {
1328
- if (!cache) return null;
1329
- const textures = store.getState().textures;
1330
- if (!allUrlsCached(urls, textures)) return null;
1331
- return buildFromCache(input, textures);
1332
- }, [cache, urls, input, store]);
1333
- const loadedTextures = useLoader(
1334
- webgpu.TextureLoader,
1335
- IsObject(input) ? Object.values(input) : input
1336
- );
1337
- const inputKey = urls.join("\0");
1338
- React.useLayoutEffect(() => {
1339
- if (cachedResult) return;
1340
- if (onLoadCalledForRef.current === inputKey) return;
1341
- onLoadCalledForRef.current = inputKey;
1342
- onLoadRef.current?.(loadedTextures);
1343
- }, [cachedResult, loadedTextures, inputKey]);
1344
- React.useEffect(() => {
1345
- if (cachedResult) return;
1346
- if ("initTexture" in renderer) {
1347
- let textureArray = [];
1348
- if (Array.isArray(loadedTextures)) {
1349
- textureArray = loadedTextures;
1350
- } else if (loadedTextures instanceof webgpu.Texture) {
1351
- textureArray = [loadedTextures];
1352
- } else if (IsObject(loadedTextures)) {
1353
- textureArray = Object.values(loadedTextures);
1354
- }
1355
- textureArray.forEach((texture) => {
1356
- if (texture instanceof webgpu.Texture) {
1357
- renderer.initTexture(texture);
1358
- }
1359
- });
1360
- }
1361
- }, [renderer, loadedTextures, cachedResult]);
1362
- const mappedTextures = React.useMemo(() => {
1363
- if (cachedResult) return cachedResult;
1364
- if (IsObject(input)) {
1365
- const keyed = {};
1366
- const textureArray = loadedTextures;
1367
- let i = 0;
1368
- for (const key in input) keyed[key] = textureArray[i++];
1369
- return keyed;
1370
- } else {
1371
- return loadedTextures;
1372
- }
1373
- }, [input, loadedTextures, cachedResult]);
1374
- React.useEffect(() => {
1375
- if (!cache) return;
1376
- const urlTextureMap = [];
1377
- if (typeof input === "string") {
1378
- urlTextureMap.push([input, mappedTextures]);
1379
- } else if (Array.isArray(input)) {
1380
- const textureArray = mappedTextures;
1381
- input.forEach((url, i) => urlTextureMap.push([url, textureArray[i]]));
1382
- } else if (IsObject(input)) {
1383
- const textureRecord = mappedTextures;
1384
- for (const key in input) {
1385
- const url = input[key];
1386
- urlTextureMap.push([url, textureRecord[key]]);
1387
- }
1388
- }
1389
- store.setState((state) => {
1390
- const refs = new Map(state._textureRefs);
1391
- let textures = state.textures;
1392
- let added = false;
1393
- for (const [url, texture] of urlTextureMap) {
1394
- if (!textures.has(url)) {
1395
- if (!added) {
1396
- textures = new Map(textures);
1397
- added = true;
1398
- }
1399
- textures.set(url, texture);
1400
- }
1401
- refs.set(url, (refs.get(url) ?? 0) + 1);
1402
- }
1403
- return added ? { textures, _textureRefs: refs } : { _textureRefs: refs };
1404
- });
1405
- return () => store.setState((state) => {
1406
- const refs = new Map(state._textureRefs);
1407
- for (const [url] of urlTextureMap) {
1408
- const next = (refs.get(url) ?? 0) - 1;
1409
- if (next <= 0) refs.delete(url);
1410
- else refs.set(url, next);
1411
- }
1412
- return { _textureRefs: refs };
1413
- });
1414
- }, [cache, input, mappedTextures, store]);
1415
- return mappedTextures;
1416
- }
1417
- useTexture.preload = (url) => useLoader.preload(webgpu.TextureLoader, url);
1418
- useTexture.clear = (input) => useLoader.clear(webgpu.TextureLoader, input);
1419
- const Texture = ({
1420
- children,
1421
- input,
1422
- onLoad,
1423
- cache
1424
- }) => {
1425
- const options = typeof onLoad === "function" ? { onLoad, cache } : { ...onLoad, cache };
1426
- const ret = useTexture(input, options);
1427
- return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: children?.(ret) });
1179
+ useTexture.preload = (url) => useLoader.preload(webgpu.TextureLoader, url);
1180
+ useTexture.clear = (input) => useLoader.clear(webgpu.TextureLoader, input);
1181
+ const Texture = ({
1182
+ children,
1183
+ input,
1184
+ onLoad,
1185
+ cache
1186
+ }) => {
1187
+ const options = typeof onLoad === "function" ? { onLoad, cache } : { ...onLoad, cache };
1188
+ const ret = useTexture(input, options);
1189
+ return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: children?.(ret) });
1428
1190
  };
1429
1191
 
1430
1192
  function useTextures(selector) {
@@ -1574,7 +1336,7 @@ function useEnvironment({
1574
1336
  texture = texture.renderTarget?.texture;
1575
1337
  }
1576
1338
  texture.mapping = isCubemap ? webgpu.CubeReflectionMapping : webgpu.EquirectangularReflectionMapping;
1577
- texture.colorSpace = colorSpace ?? (isCubemap ? "srgb" : "srgb-linear");
1339
+ texture.colorSpace = colorSpace ?? (isCubemap && extension !== "hdr-cube" ? "srgb" : "srgb-linear");
1578
1340
  return texture;
1579
1341
  }
1580
1342
  const preloadDefaultOptions = {
@@ -1627,22 +1389,15 @@ function getExtension(files) {
1627
1389
  const isCubemap = isArray(files) && files.length === 6;
1628
1390
  const isGainmap = isArray(files) && files.length === 3 && files.some((file) => file.endsWith("json"));
1629
1391
  const firstEntry = isArray(files) ? files[0] : files;
1630
- const extension = isCubemap ? "cube" : isGainmap ? "webp" : firstEntry.startsWith("data:application/exr") ? "exr" : firstEntry.startsWith("data:application/hdr") ? "hdr" : firstEntry.startsWith("data:image/jpeg") ? "jpg" : firstEntry.split(".").pop()?.split("?")?.shift()?.toLowerCase();
1392
+ const firstExtension = firstEntry.split(".").pop()?.split("?")?.shift()?.toLowerCase();
1393
+ const extension = isCubemap ? firstExtension === "hdr" ? "hdr-cube" : "cube" : isGainmap ? "webp" : firstEntry.startsWith("data:application/exr") ? "exr" : firstEntry.startsWith("data:application/hdr") ? "hdr" : firstEntry.startsWith("data:image/jpeg") ? "jpg" : firstExtension;
1631
1394
  return { extension, isCubemap, isGainmap };
1632
1395
  }
1633
1396
  function getLoader(extension) {
1634
- const loader = extension === "cube" ? webgpu.CubeTextureLoader : extension === "hdr" ? HDRLoader_js.HDRLoader : extension === "exr" ? EXRLoader_js.EXRLoader : extension === "jpg" || extension === "jpeg" ? UltraHDRLoader_js.UltraHDRLoader : extension === "webp" ? gainmapJs.GainMapLoader : null;
1397
+ const loader = extension === "cube" ? webgpu.CubeTextureLoader : extension === "hdr-cube" ? HDRCubeTextureLoader_js.HDRCubeTextureLoader : extension === "hdr" ? HDRLoader_js.HDRLoader : extension === "exr" ? EXRLoader_js.EXRLoader : extension === "jpg" || extension === "jpeg" ? UltraHDRLoader_js.UltraHDRLoader : extension === "webp" ? gainmapJs.GainMapLoader : null;
1635
1398
  return loader;
1636
1399
  }
1637
1400
 
1638
- function useStore() {
1639
- const store = React.useContext(context);
1640
- if (!store) throw new Error("R3F: Hooks can only be used within the Canvas component!");
1641
- return store;
1642
- }
1643
- function useThree(selector = (state) => state, equalityFn) {
1644
- return useStore()(selector, equalityFn);
1645
- }
1646
1401
  function useInstanceHandle(ref) {
1647
1402
  const instance = React.useRef(null);
1648
1403
  React.useImperativeHandle(instance, () => ref.current.__r3f, [ref]);
@@ -1682,13 +1437,19 @@ function addTail(callback) {
1682
1437
  return scheduler.getScheduler().onIdle(callback);
1683
1438
  }
1684
1439
  function invalidate(state, frames = 1, stackFrames = false) {
1685
- scheduler.getScheduler().invalidate(frames, stackFrames);
1440
+ const rootId = state?.internal?.rootId;
1441
+ const scheduler$1 = scheduler.getScheduler();
1442
+ if (rootId) scheduler$1.invalidateRoot(rootId, frames, stackFrames);
1443
+ else scheduler$1.invalidate(frames, stackFrames);
1686
1444
  }
1687
- function advance(timestamp) {
1688
- scheduler.getScheduler().step(timestamp);
1445
+ function advance(timestamp, runGlobalEffects, state) {
1446
+ const rootId = state?.internal?.rootId;
1447
+ const scheduler$1 = scheduler.getScheduler();
1448
+ if (rootId) scheduler$1.stepRoot(rootId, timestamp);
1449
+ else scheduler$1.step(timestamp);
1689
1450
  }
1690
1451
 
1691
- const version = "10.0.0-alpha.3";
1452
+ const version = "10.0.0-alpha.5";
1692
1453
  const packageData = {
1693
1454
  version: version};
1694
1455
 
@@ -13547,7 +13308,10 @@ function extend(objects) {
13547
13308
  catalogue[Component] = objects;
13548
13309
  return Component;
13549
13310
  } else {
13550
- Object.assign(catalogue, objects);
13311
+ for (const name in objects) {
13312
+ const object = objects[name];
13313
+ if (isConstructor(object)) catalogue[name] = object;
13314
+ }
13551
13315
  }
13552
13316
  }
13553
13317
  function validateInstance(type, props) {
@@ -13895,7 +13659,7 @@ const reconciler = /* @__PURE__ */ createReconciler({
13895
13659
  getRootHostContext: () => NO_CONTEXT,
13896
13660
  getChildHostContext: () => NO_CONTEXT,
13897
13661
  commitUpdate(instance, type, oldProps, newProps, fiber) {
13898
- validateInstance(type, newProps);
13662
+ validateInstance(instance.type, newProps);
13899
13663
  let reconstruct = false;
13900
13664
  if (instance.type === "primitive" && oldProps.object !== newProps.object) reconstruct = true;
13901
13665
  else if (newProps.args?.length !== oldProps.args?.length) reconstruct = true;
@@ -13910,162 +13674,491 @@ const reconciler = /* @__PURE__ */ createReconciler({
13910
13674
  else delete instance.props.attach;
13911
13675
  if (Object.keys(changedProps).length) applyProps(instance.object, changedProps);
13912
13676
  }
13913
- },
13914
- finalizeInitialChildren: (instance) => {
13915
- for (const prop in instance.props) {
13916
- if (isFromRef(instance.props[prop])) return true;
13677
+ },
13678
+ finalizeInitialChildren: (instance) => {
13679
+ for (const prop in instance.props) {
13680
+ if (isFromRef(instance.props[prop])) return true;
13681
+ }
13682
+ return false;
13683
+ },
13684
+ commitMount(instance) {
13685
+ const resolved = {};
13686
+ for (const prop in instance.props) {
13687
+ const value = instance.props[prop];
13688
+ if (isFromRef(value)) {
13689
+ const { [FROM_REF]: ref, transform } = value;
13690
+ if (ref.current != null) resolved[prop] = transform ? transform(ref.current) : ref.current;
13691
+ }
13692
+ }
13693
+ if (Object.keys(resolved).length) applyProps(instance.object, resolved);
13694
+ },
13695
+ getPublicInstance: (instance) => instance?.object,
13696
+ prepareForCommit: () => null,
13697
+ preparePortalMount: (container) => {
13698
+ const target = container.getState().internal.container ?? container.getState().scene;
13699
+ return prepare(target, container, "", {});
13700
+ },
13701
+ // Reconstructed instances are swapped once all mutations are committed,
13702
+ // before layout effects run so refs point to the new objects
13703
+ resetAfterCommit: flushReconstructedInstances,
13704
+ shouldSetTextContent: () => false,
13705
+ clearContainer: () => false,
13706
+ hideInstance,
13707
+ unhideInstance,
13708
+ createTextInstance: handleTextInstance,
13709
+ hideTextInstance: handleTextInstance,
13710
+ unhideTextInstance: handleTextInstance,
13711
+ // Mirrors react-dom, which flushes discrete work in microtasks
13712
+ supportsMicrotasks: true,
13713
+ scheduleMicrotask,
13714
+ scheduleTimeout: typeof setTimeout === "function" ? setTimeout : void 0,
13715
+ cancelTimeout: typeof clearTimeout === "function" ? clearTimeout : void 0,
13716
+ noTimeout: -1,
13717
+ getInstanceFromNode: () => null,
13718
+ beforeActiveInstanceBlur() {
13719
+ },
13720
+ afterActiveInstanceBlur() {
13721
+ },
13722
+ detachDeletedInstance() {
13723
+ },
13724
+ prepareScopeUpdate() {
13725
+ },
13726
+ getInstanceFromScope: () => null,
13727
+ shouldAttemptEagerTransition: () => false,
13728
+ trackSchedulerEvent: () => {
13729
+ },
13730
+ resolveEventType: () => null,
13731
+ resolveEventTimeStamp: () => -1.1,
13732
+ requestPostPaintCallback() {
13733
+ },
13734
+ maySuspendCommit: () => false,
13735
+ preloadInstance: () => true,
13736
+ // true indicates already loaded
13737
+ suspendInstance() {
13738
+ },
13739
+ waitForCommitToBeReady: () => null,
13740
+ NotPendingTransition: null,
13741
+ // The reconciler types use the internal ReactContext with all the hidden properties
13742
+ // so we have to cast from the public React.Context type
13743
+ HostTransitionContext: /* @__PURE__ */ React__namespace.createContext(
13744
+ null
13745
+ ),
13746
+ setCurrentUpdatePriority(newPriority) {
13747
+ currentUpdatePriority = newPriority;
13748
+ },
13749
+ getCurrentUpdatePriority() {
13750
+ return currentUpdatePriority;
13751
+ },
13752
+ resolveUpdatePriority() {
13753
+ if (currentUpdatePriority !== NoEventPriority) return currentUpdatePriority;
13754
+ const eventType = typeof window !== "undefined" ? window.event?.type : void 0;
13755
+ if (eventType === void 0) return r;
13756
+ return getEventPriority(eventType);
13757
+ },
13758
+ resetFormInstance() {
13759
+ },
13760
+ // @ts-ignore DefinitelyTyped is not up to date
13761
+ rendererPackageName: "@react-three/fiber",
13762
+ rendererVersion: packageData.version,
13763
+ // 19.2 Reconciler
13764
+ // https://github.com/facebook/react/pull/31975
13765
+ // https://github.com/facebook/react/pull/31999
13766
+ applyViewTransitionName(_instance, _name, _className) {
13767
+ },
13768
+ restoreViewTransitionName(_instance, _props) {
13769
+ },
13770
+ cancelViewTransitionName(_instance, _name, _props) {
13771
+ },
13772
+ cancelRootViewTransitionName(_rootContainer) {
13773
+ },
13774
+ restoreRootViewTransitionName(_rootContainer) {
13775
+ },
13776
+ InstanceMeasurement: null,
13777
+ measureInstance: (_instance) => null,
13778
+ wasInstanceInViewport: (_measurement) => true,
13779
+ hasInstanceChanged: (_oldMeasurement, _newMeasurement) => false,
13780
+ hasInstanceAffectedParent: (_oldMeasurement, _newMeasurement) => false,
13781
+ // https://github.com/facebook/react/pull/32002
13782
+ // https://github.com/facebook/react/pull/34486
13783
+ suspendOnActiveViewTransition(_state, _container) {
13784
+ },
13785
+ // https://github.com/facebook/react/pull/32451
13786
+ // https://github.com/facebook/react/pull/32760
13787
+ startGestureTransition: () => null,
13788
+ startViewTransition: () => null,
13789
+ stopViewTransition(_transition) {
13790
+ },
13791
+ // https://github.com/facebook/react/pull/32038
13792
+ createViewTransitionInstance: (_name) => null,
13793
+ // https://github.com/facebook/react/pull/32379
13794
+ // https://github.com/facebook/react/pull/32786
13795
+ getCurrentGestureOffset(_provider) {
13796
+ throw new Error("startGestureTransition is not yet supported in react-three-fiber.");
13797
+ },
13798
+ // https://github.com/facebook/react/pull/32500
13799
+ cloneMutableInstance(instance, _keepChildren) {
13800
+ return instance;
13801
+ },
13802
+ cloneMutableTextInstance(textInstance) {
13803
+ return textInstance;
13804
+ },
13805
+ // React 19.2 added these for root-level view transitions. They only run for hosts that opt into
13806
+ // view transitions by returning a container from cloneRootViewTransitionContainer; R3F renders
13807
+ // into a WebGL/WebGPU canvas, where cloning the root has no meaning, so we never opt in and
13808
+ // React never calls them. They stay as no-ops rather than throws: the host config is shared by
13809
+ // every root, and a throw here would turn a React-internal probe into a hard crash for users.
13810
+ // Guarded by a test asserting a full render + unmount never reaches them.
13811
+ cloneRootViewTransitionContainer(_rootContainer) {
13812
+ },
13813
+ removeRootViewTransitionClone(_rootContainer, _clone) {
13814
+ },
13815
+ // https://github.com/facebook/react/pull/32465
13816
+ createFragmentInstance: (_fiber) => null,
13817
+ updateFragmentInstanceFiber(_fiber, _instance) {
13818
+ },
13819
+ commitNewChildToFragmentInstance(_child, _fragmentInstance) {
13820
+ },
13821
+ deleteChildFromFragmentInstance(_child, _fragmentInstance) {
13822
+ },
13823
+ // https://github.com/facebook/react/pull/32653
13824
+ measureClonedInstance: (_instance) => null,
13825
+ // https://github.com/facebook/react/pull/32819
13826
+ maySuspendCommitOnUpdate: (_type, _oldProps, _newProps) => false,
13827
+ maySuspendCommitInSyncRender: (_type, _props) => false,
13828
+ // https://github.com/facebook/react/pull/34486
13829
+ startSuspendingCommit: () => null,
13830
+ // https://github.com/facebook/react/pull/34522
13831
+ getSuspendedCommitReason: (_state, _rootContainer) => null
13832
+ });
13833
+
13834
+ const INTERNAL_LIB_MARKERS = ["zustand", "setState", "Object.assign"];
13835
+ function isInternalRendererAccess(stack, callerDepth = 2) {
13836
+ if (!stack) return false;
13837
+ const caller = stack.split("\n")[callerDepth] ?? "";
13838
+ return INTERNAL_LIB_MARKERS.some((m) => caller.includes(m));
13839
+ }
13840
+
13841
+ const createStore = (invalidate, advance) => {
13842
+ const rootStore = traditional.createWithEqualityFn((set, get) => {
13843
+ const position = new webgpu.Vector3();
13844
+ const defaultTarget = new webgpu.Vector3();
13845
+ const tempTarget = new webgpu.Vector3();
13846
+ function getCurrentViewport(camera = get().camera, target = defaultTarget, size = get().size) {
13847
+ const { width, height, top, left } = size;
13848
+ const aspect = width / height;
13849
+ if (target.isVector3) tempTarget.copy(target);
13850
+ else tempTarget.set(...target);
13851
+ const distance = camera.getWorldPosition(position).distanceTo(tempTarget);
13852
+ if (isOrthographicCamera(camera)) {
13853
+ return { width: width / camera.zoom, height: height / camera.zoom, top, left, factor: 1, distance, aspect };
13854
+ } else {
13855
+ const fov = camera.fov * Math.PI / 180;
13856
+ const h = 2 * Math.tan(fov / 2) * distance;
13857
+ const w = h * (width / height);
13858
+ return { width: w, height: h, top, left, factor: width / w, distance, aspect };
13859
+ }
13860
+ }
13861
+ let performanceTimeout = void 0;
13862
+ const setPerformanceCurrent = (current) => set((state2) => ({ performance: { ...state2.performance, current } }));
13863
+ const pointer = new webgpu.Vector2();
13864
+ const rootState = {
13865
+ set,
13866
+ get,
13867
+ // Mock objects that have to be configured
13868
+ // primaryStore is set after store creation (self-reference for primary, primary's store for secondary)
13869
+ primaryStore: null,
13870
+ gl: null,
13871
+ renderer: null,
13872
+ camera: null,
13873
+ frustum: new webgpu.Frustum(),
13874
+ autoUpdateFrustum: true,
13875
+ raycaster: null,
13876
+ events: {
13877
+ priority: 1,
13878
+ enabled: true,
13879
+ connected: false,
13880
+ frameTimedRaycasts: true,
13881
+ alwaysFireOnScroll: true,
13882
+ updateOnFrame: false
13883
+ },
13884
+ scene: null,
13885
+ rootScene: null,
13886
+ xr: null,
13887
+ inspector: null,
13888
+ invalidate: (frames = 1, stackFrames = false) => invalidate(get(), frames, stackFrames),
13889
+ advance: (timestamp, runGlobalEffects) => advance(timestamp, runGlobalEffects, get()),
13890
+ textureColorSpace: webgpu.SRGBColorSpace,
13891
+ isLegacy: false,
13892
+ webGPUSupported: false,
13893
+ isNative: false,
13894
+ controls: null,
13895
+ pointer,
13896
+ mouse: pointer,
13897
+ frameloop: "always",
13898
+ onPointerMissed: void 0,
13899
+ onDragOverMissed: void 0,
13900
+ onDropMissed: void 0,
13901
+ performance: {
13902
+ current: 1,
13903
+ min: 0.5,
13904
+ max: 1,
13905
+ debounce: 200,
13906
+ regress: () => {
13907
+ const state2 = get();
13908
+ if (performanceTimeout) clearTimeout(performanceTimeout);
13909
+ if (state2.performance.current !== state2.performance.min) setPerformanceCurrent(state2.performance.min);
13910
+ performanceTimeout = setTimeout(
13911
+ () => setPerformanceCurrent(get().performance.max),
13912
+ state2.performance.debounce
13913
+ );
13914
+ }
13915
+ },
13916
+ size: { width: 0, height: 0, top: 0, left: 0 },
13917
+ viewport: {
13918
+ initialDpr: 0,
13919
+ dpr: 0,
13920
+ width: 0,
13921
+ height: 0,
13922
+ top: 0,
13923
+ left: 0,
13924
+ aspect: 0,
13925
+ distance: 0,
13926
+ factor: 0,
13927
+ getCurrentViewport
13928
+ },
13929
+ setEvents: (events) => set((state2) => ({ ...state2, events: { ...state2.events, ...events } })),
13930
+ setSize: (width, height, top, left) => {
13931
+ const state2 = get();
13932
+ if (width === void 0) {
13933
+ set({ _sizeImperative: false });
13934
+ if (state2._sizeProps) {
13935
+ const { width: propW, height: propH } = state2._sizeProps;
13936
+ if (propW !== void 0 || propH !== void 0) {
13937
+ const currentSize = state2.size;
13938
+ const newSize = {
13939
+ width: propW ?? currentSize.width,
13940
+ height: propH ?? currentSize.height,
13941
+ top: currentSize.top,
13942
+ left: currentSize.left
13943
+ };
13944
+ set((s) => ({
13945
+ size: newSize,
13946
+ viewport: { ...s.viewport, ...getCurrentViewport(state2.camera, defaultTarget, newSize) }
13947
+ }));
13948
+ get().invalidate();
13949
+ }
13950
+ }
13951
+ return;
13952
+ }
13953
+ const w = width;
13954
+ const h = height ?? width;
13955
+ const t = top ?? state2.size.top;
13956
+ const l = left ?? state2.size.left;
13957
+ const size = { width: w, height: h, top: t, left: l };
13958
+ set((s) => ({
13959
+ size,
13960
+ viewport: { ...s.viewport, ...getCurrentViewport(state2.camera, defaultTarget, size) },
13961
+ _sizeImperative: true
13962
+ }));
13963
+ get().invalidate();
13964
+ },
13965
+ setDpr: (dpr) => set((state2) => {
13966
+ const resolved = calculateDpr(dpr);
13967
+ return { viewport: { ...state2.viewport, dpr: resolved, initialDpr: state2.viewport.initialDpr || resolved } };
13968
+ }),
13969
+ setFrameloop: (frameloop = "always") => {
13970
+ const rootId = get().internal.rootId;
13971
+ if (rootId) scheduler.getScheduler().setRootFrameloop(rootId, frameloop);
13972
+ set(() => ({ frameloop }));
13973
+ },
13974
+ setError: (error) => set(() => ({ error })),
13975
+ error: null,
13976
+ //* TSL State (managed via hooks: useUniforms, useNodes, useBuffers, useGPUStorage, useTextures, useRenderPipeline) ==============================
13977
+ uniforms: {},
13978
+ nodes: {},
13979
+ buffers: {},
13980
+ gpuStorage: {},
13981
+ textures: /* @__PURE__ */ new Map(),
13982
+ _textureRefs: /* @__PURE__ */ new Map(),
13983
+ renderPipeline: null,
13984
+ passes: {},
13985
+ _hmrVersion: 0,
13986
+ _sizeImperative: false,
13987
+ _sizeProps: null,
13988
+ previousRoot: void 0,
13989
+ internal: {
13990
+ // Events
13991
+ interaction: [],
13992
+ subscribers: [],
13993
+ // Per-pointer state (new unified structure)
13994
+ pointerMap: /* @__PURE__ */ new Map(),
13995
+ pointerDirty: /* @__PURE__ */ new Map(),
13996
+ lastEvent: React__namespace.createRef(),
13997
+ // Deprecated but kept for backwards compatibility
13998
+ hovered: /* @__PURE__ */ new Map(),
13999
+ initialClick: [0, 0],
14000
+ initialHits: [],
14001
+ capturedMap: /* @__PURE__ */ new Map(),
14002
+ // Visibility tracking (onFramed, onOccluded, onVisible)
14003
+ visibilityRegistry: /* @__PURE__ */ new Map(),
14004
+ // Occlusion system (WebGPU only)
14005
+ occlusionEnabled: false,
14006
+ occlusionObserver: null,
14007
+ occlusionCache: /* @__PURE__ */ new Map(),
14008
+ helperGroup: null,
14009
+ // Updates
14010
+ active: false,
14011
+ frames: 0,
14012
+ priority: 0,
14013
+ subscribe: (ref, priority, store) => {
14014
+ const internal = get().internal;
14015
+ internal.priority = internal.priority + (priority > 0 ? 1 : 0);
14016
+ internal.subscribers.push({ ref, priority, store });
14017
+ internal.subscribers = internal.subscribers.sort((a, b) => a.priority - b.priority);
14018
+ return () => {
14019
+ const internal2 = get().internal;
14020
+ if (internal2?.subscribers) {
14021
+ internal2.priority = internal2.priority - (priority > 0 ? 1 : 0);
14022
+ internal2.subscribers = internal2.subscribers.filter((s) => s.ref !== ref);
14023
+ }
14024
+ };
14025
+ },
14026
+ // Renderer Storage (single source of truth)
14027
+ actualRenderer: null,
14028
+ // Scheduler for useFrameNext (initialized in renderer.tsx)
14029
+ scheduler: null,
14030
+ // Replaces the default render call when set (see setRenderOverride)
14031
+ renderOverride: null
14032
+ }
14033
+ };
14034
+ return rootState;
14035
+ });
14036
+ const state = rootStore.getState();
14037
+ Object.defineProperty(state, "gl", {
14038
+ get() {
14039
+ const currentState = rootStore.getState();
14040
+ if (!currentState.isLegacy && currentState.internal.actualRenderer) {
14041
+ const stack = new Error().stack || "";
14042
+ if (!isInternalRendererAccess(stack)) {
14043
+ const cleanedStack = stack.split("\n").slice(2).join("\n") || "Stack trace unavailable";
14044
+ notifyDepreciated({
14045
+ heading: "Accessing state.gl in WebGPU mode",
14046
+ body: "Please use state.renderer instead. state.gl is deprecated and will be removed in future versions.\n\nFor backwards compatibility, state.gl currently maps to state.renderer, but this may cause issues with libraries expecting WebGLRenderer.\n\nAccessed from:\n" + cleanedStack
14047
+ });
14048
+ }
14049
+ }
14050
+ return currentState.internal.actualRenderer;
14051
+ },
14052
+ set(value) {
14053
+ rootStore.getState().internal.actualRenderer = value;
14054
+ },
14055
+ enumerable: true,
14056
+ configurable: true
14057
+ });
14058
+ Object.defineProperty(state, "renderer", {
14059
+ get() {
14060
+ return rootStore.getState().internal.actualRenderer;
14061
+ },
14062
+ set(value) {
14063
+ rootStore.getState().internal.actualRenderer = value;
14064
+ },
14065
+ enumerable: true,
14066
+ configurable: true
14067
+ });
14068
+ let oldScene = state.scene;
14069
+ rootStore.subscribe(() => {
14070
+ const currentState = rootStore.getState();
14071
+ const { scene, rootScene, set } = currentState;
14072
+ if (scene !== oldScene) {
14073
+ oldScene = scene;
14074
+ if (scene?.isScene && scene !== rootScene) {
14075
+ set({ rootScene: scene });
14076
+ }
14077
+ }
14078
+ });
14079
+ let oldSize = state.size;
14080
+ let oldDpr = state.viewport.dpr;
14081
+ let oldCamera = state.camera;
14082
+ rootStore.subscribe(() => {
14083
+ const { camera, size, viewport, set, internal } = rootStore.getState();
14084
+ const actualRenderer = internal.actualRenderer;
14085
+ const canvasTarget = internal.canvasTarget;
14086
+ if (size.width !== oldSize.width || size.height !== oldSize.height || viewport.dpr !== oldDpr) {
14087
+ oldSize = size;
14088
+ oldDpr = viewport.dpr;
14089
+ updateCamera(camera, size);
14090
+ const resizeTarget = canvasTarget ?? actualRenderer;
14091
+ if (viewport.dpr > 0) resizeTarget.setPixelRatio(viewport.dpr);
14092
+ resizeTarget.setSize(size.width, size.height, false);
14093
+ const activeTarget = actualRenderer.getCanvasTarget?.();
14094
+ if (!canvasTarget || activeTarget !== canvasTarget) internal.canvasTargetSizeDirty = true;
13917
14095
  }
13918
- return false;
13919
- },
13920
- commitMount(instance) {
13921
- const resolved = {};
13922
- for (const prop in instance.props) {
13923
- const value = instance.props[prop];
13924
- if (isFromRef(value)) {
13925
- const ref = value[FROM_REF];
13926
- if (ref.current != null) resolved[prop] = ref.current;
14096
+ if (camera !== oldCamera) {
14097
+ oldCamera = camera;
14098
+ const { rootScene } = rootStore.getState();
14099
+ if (camera && rootScene && !camera.parent) {
14100
+ rootScene.add(camera);
14101
+ }
14102
+ set((state2) => ({ viewport: { ...state2.viewport, ...state2.viewport.getCurrentViewport(camera) } }));
14103
+ const currentState = rootStore.getState();
14104
+ if (currentState.autoUpdateFrustum && camera) {
14105
+ updateFrustum(camera, currentState.frustum);
13927
14106
  }
13928
14107
  }
13929
- if (Object.keys(resolved).length) applyProps(instance.object, resolved);
13930
- },
13931
- getPublicInstance: (instance) => instance?.object,
13932
- prepareForCommit: () => null,
13933
- preparePortalMount: (container) => {
13934
- const target = container.getState().internal.container ?? container.getState().scene;
13935
- return prepare(target, container, "", {});
13936
- },
13937
- // Reconstructed instances are swapped once all mutations are committed,
13938
- // before layout effects run so refs point to the new objects
13939
- resetAfterCommit: flushReconstructedInstances,
13940
- shouldSetTextContent: () => false,
13941
- clearContainer: () => false,
13942
- hideInstance,
13943
- unhideInstance,
13944
- createTextInstance: handleTextInstance,
13945
- hideTextInstance: handleTextInstance,
13946
- unhideTextInstance: handleTextInstance,
13947
- // Mirrors react-dom, which flushes discrete work in microtasks
13948
- supportsMicrotasks: true,
13949
- scheduleMicrotask,
13950
- scheduleTimeout: typeof setTimeout === "function" ? setTimeout : void 0,
13951
- cancelTimeout: typeof clearTimeout === "function" ? clearTimeout : void 0,
13952
- noTimeout: -1,
13953
- getInstanceFromNode: () => null,
13954
- beforeActiveInstanceBlur() {
13955
- },
13956
- afterActiveInstanceBlur() {
13957
- },
13958
- detachDeletedInstance() {
13959
- },
13960
- prepareScopeUpdate() {
13961
- },
13962
- getInstanceFromScope: () => null,
13963
- shouldAttemptEagerTransition: () => false,
13964
- trackSchedulerEvent: () => {
13965
- },
13966
- resolveEventType: () => null,
13967
- resolveEventTimeStamp: () => -1.1,
13968
- requestPostPaintCallback() {
13969
- },
13970
- maySuspendCommit: () => false,
13971
- preloadInstance: () => true,
13972
- // true indicates already loaded
13973
- suspendInstance() {
13974
- },
13975
- waitForCommitToBeReady: () => null,
13976
- NotPendingTransition: null,
13977
- // The reconciler types use the internal ReactContext with all the hidden properties
13978
- // so we have to cast from the public React.Context type
13979
- HostTransitionContext: /* @__PURE__ */ React__namespace.createContext(
13980
- null
13981
- ),
13982
- setCurrentUpdatePriority(newPriority) {
13983
- currentUpdatePriority = newPriority;
13984
- },
13985
- getCurrentUpdatePriority() {
13986
- return currentUpdatePriority;
13987
- },
13988
- resolveUpdatePriority() {
13989
- if (currentUpdatePriority !== NoEventPriority) return currentUpdatePriority;
13990
- const eventType = typeof window !== "undefined" ? window.event?.type : void 0;
13991
- if (eventType === void 0) return r;
13992
- return getEventPriority(eventType);
13993
- },
13994
- resetFormInstance() {
13995
- },
13996
- // @ts-ignore DefinitelyTyped is not up to date
13997
- rendererPackageName: "@react-three/fiber",
13998
- rendererVersion: packageData.version,
13999
- // 19.2 Reconciler
14000
- // https://github.com/facebook/react/pull/31975
14001
- // https://github.com/facebook/react/pull/31999
14002
- applyViewTransitionName(_instance, _name, _className) {
14003
- },
14004
- restoreViewTransitionName(_instance, _props) {
14005
- },
14006
- cancelViewTransitionName(_instance, _name, _props) {
14007
- },
14008
- cancelRootViewTransitionName(_rootContainer) {
14009
- },
14010
- restoreRootViewTransitionName(_rootContainer) {
14011
- },
14012
- InstanceMeasurement: null,
14013
- measureInstance: (_instance) => null,
14014
- wasInstanceInViewport: (_measurement) => true,
14015
- hasInstanceChanged: (_oldMeasurement, _newMeasurement) => false,
14016
- hasInstanceAffectedParent: (_oldMeasurement, _newMeasurement) => false,
14017
- // https://github.com/facebook/react/pull/32002
14018
- // https://github.com/facebook/react/pull/34486
14019
- suspendOnActiveViewTransition(_state, _container) {
14020
- },
14021
- // https://github.com/facebook/react/pull/32451
14022
- // https://github.com/facebook/react/pull/32760
14023
- startGestureTransition: () => null,
14024
- startViewTransition: () => null,
14025
- stopViewTransition(_transition) {
14026
- },
14027
- // https://github.com/facebook/react/pull/32038
14028
- createViewTransitionInstance: (_name) => null,
14029
- // https://github.com/facebook/react/pull/32379
14030
- // https://github.com/facebook/react/pull/32786
14031
- getCurrentGestureOffset(_provider) {
14032
- throw new Error("startGestureTransition is not yet supported in react-three-fiber.");
14033
- },
14034
- // https://github.com/facebook/react/pull/32500
14035
- cloneMutableInstance(instance, _keepChildren) {
14036
- return instance;
14037
- },
14038
- cloneMutableTextInstance(textInstance) {
14039
- return textInstance;
14040
- },
14041
- // React 19.2 added these for root-level view transitions. They only run for hosts that opt into
14042
- // view transitions by returning a container from cloneRootViewTransitionContainer; R3F renders
14043
- // into a WebGL/WebGPU canvas, where cloning the root has no meaning, so we never opt in and
14044
- // React never calls them. They stay as no-ops rather than throws: the host config is shared by
14045
- // every root, and a throw here would turn a React-internal probe into a hard crash for users.
14046
- // Guarded by a test asserting a full render + unmount never reaches them.
14047
- cloneRootViewTransitionContainer(_rootContainer) {
14048
- },
14049
- removeRootViewTransitionClone(_rootContainer, _clone) {
14050
- },
14051
- // https://github.com/facebook/react/pull/32465
14052
- createFragmentInstance: (_fiber) => null,
14053
- updateFragmentInstanceFiber(_fiber, _instance) {
14054
- },
14055
- commitNewChildToFragmentInstance(_child, _fragmentInstance) {
14056
- },
14057
- deleteChildFromFragmentInstance(_child, _fragmentInstance) {
14058
- },
14059
- // https://github.com/facebook/react/pull/32653
14060
- measureClonedInstance: (_instance) => null,
14061
- // https://github.com/facebook/react/pull/32819
14062
- maySuspendCommitOnUpdate: (_type, _oldProps, _newProps) => false,
14063
- maySuspendCommitInSyncRender: (_type, _props) => false,
14064
- // https://github.com/facebook/react/pull/34486
14065
- startSuspendingCommit: () => null,
14066
- // https://github.com/facebook/react/pull/34522
14067
- getSuspendedCommitReason: (_state, _rootContainer) => null
14068
- });
14108
+ });
14109
+ rootStore.subscribe((state2) => invalidate(state2));
14110
+ return rootStore;
14111
+ };
14112
+
14113
+ const R3F_EXTENSIONS = Symbol.for("@react-three/fiber.extensions");
14114
+ function getRegistry() {
14115
+ return globalThis[R3F_EXTENSIONS] ?? (globalThis[R3F_EXTENSIONS] = {
14116
+ extensions: /* @__PURE__ */ new Map(),
14117
+ roots: /* @__PURE__ */ new Set(),
14118
+ applied: /* @__PURE__ */ new WeakMap()
14119
+ });
14120
+ }
14121
+ function setupRoot(registry, store, extension) {
14122
+ let applied = registry.applied.get(store);
14123
+ if (!applied) registry.applied.set(store, applied = /* @__PURE__ */ new Map());
14124
+ if (applied.has(extension.name)) return;
14125
+ applied.set(extension.name, extension);
14126
+ const patch = extension.setup?.(store);
14127
+ if (patch) store.setState(patch);
14128
+ }
14129
+ function registerRootExtension(extension) {
14130
+ const registry = getRegistry();
14131
+ registry.extensions.set(extension.name, extension);
14132
+ for (const store of registry.roots) setupRoot(registry, store, extension);
14133
+ return () => {
14134
+ if (registry.extensions.get(extension.name) === extension) registry.extensions.delete(extension.name);
14135
+ };
14136
+ }
14137
+ function setRenderOverride(store, render) {
14138
+ store.getState().internal.renderOverride = render;
14139
+ }
14140
+ function attachRootExtensions(store) {
14141
+ const registry = getRegistry();
14142
+ registry.roots.add(store);
14143
+ for (const extension of registry.extensions.values()) setupRoot(registry, store, extension);
14144
+ }
14145
+ function current(registry, setUpBy) {
14146
+ return registry.extensions.get(setUpBy.name) ?? setUpBy;
14147
+ }
14148
+ function detachRootExtensions(store) {
14149
+ const registry = getRegistry();
14150
+ if (!registry.roots.delete(store)) return;
14151
+ const applied = registry.applied.get(store);
14152
+ registry.applied.delete(store);
14153
+ if (!applied) return;
14154
+ for (const setUpBy of applied.values()) current(registry, setUpBy).dispose?.(store);
14155
+ }
14156
+ function notifyRootExtensionsHmr(store) {
14157
+ const registry = getRegistry();
14158
+ const applied = registry.applied.get(store);
14159
+ if (!applied) return;
14160
+ for (const setUpBy of applied.values()) current(registry, setUpBy).hmr?.(store);
14161
+ }
14069
14162
 
14070
14163
  const isRenderer = (def) => !!def?.render;
14071
14164
  const _roots = /* @__PURE__ */ new Map();
@@ -14222,9 +14315,8 @@ function createRoot(canvas) {
14222
14315
  if (!renderer.hasInitialized?.()) {
14223
14316
  const size2 = computeInitialSize(canvas, propsSize);
14224
14317
  if (size2.width > 0 && size2.height > 0) {
14225
- const pixelRatio = calculateDpr(dpr);
14226
- canvas.width = size2.width * pixelRatio;
14227
- canvas.height = size2.height * pixelRatio;
14318
+ renderer.setPixelRatio(calculateDpr(dpr));
14319
+ renderer.setSize(size2.width, size2.height, false);
14228
14320
  }
14229
14321
  await renderer.init();
14230
14322
  }
@@ -14233,7 +14325,7 @@ function createRoot(canvas) {
14233
14325
  state.internal.actualRenderer = renderer;
14234
14326
  state.set({ webGPUSupported: isWebGPUBackend, renderer, primaryStore: store });
14235
14327
  if (canvasId && !state.internal.isSecondary) {
14236
- const canvasTarget = new webgpu.CanvasTarget(canvas);
14328
+ const canvasTarget = renderer.getCanvasTarget?.();
14237
14329
  const unregisterPrimary = registerPrimary(canvasId, renderer, store);
14238
14330
  state.set((prev) => ({
14239
14331
  internal: {
@@ -14347,7 +14439,7 @@ function createRoot(canvas) {
14347
14439
  const handleXRFrame = (timestamp, _frame) => {
14348
14440
  const state2 = store.getState();
14349
14441
  if (state2.frameloop === "never") return;
14350
- advance(timestamp);
14442
+ advance(timestamp, true, state2);
14351
14443
  };
14352
14444
  const actualRenderer = state.internal.actualRenderer;
14353
14445
  const handleSessionChange = () => {
@@ -14355,7 +14447,7 @@ function createRoot(canvas) {
14355
14447
  const renderer2 = state2.internal.actualRenderer;
14356
14448
  actualRenderer.xr.enabled = actualRenderer.xr.isPresenting;
14357
14449
  renderer2.xr.setAnimationLoop(renderer2.xr.isPresenting ? handleXRFrame : null);
14358
- if (!renderer2.xr.isPresenting) invalidate();
14450
+ if (!renderer2.xr.isPresenting) invalidate(state2);
14359
14451
  };
14360
14452
  const xr = {
14361
14453
  connect() {
@@ -14437,14 +14529,23 @@ function createRoot(canvas) {
14437
14529
  const newRootId = canvasId || scheduler$1.generateRootId();
14438
14530
  const unregisterRoot = scheduler$1.registerRoot(newRootId, {
14439
14531
  getState: () => store.getState(),
14440
- onError: (err) => store.getState().setError(err)
14532
+ onError: (err) => store.getState().setError(err),
14533
+ frameloop: store.getState().frameloop,
14534
+ before: schedulerConfig?.before,
14535
+ after: schedulerConfig?.after,
14536
+ order: schedulerConfig?.order
14441
14537
  });
14442
14538
  const unregisterCanvasTarget = scheduler$1.register(
14443
14539
  () => {
14444
14540
  const state2 = store.getState();
14445
- if (state2.internal.isMultiCanvas && state2.internal.canvasTarget) {
14541
+ const canvasTarget = state2.internal.canvasTarget;
14542
+ if (canvasTarget) {
14446
14543
  const renderer2 = state2.internal.actualRenderer;
14447
- renderer2.setCanvasTarget(state2.internal.canvasTarget);
14544
+ if (renderer2.getCanvasTarget() !== canvasTarget) renderer2.setCanvasTarget(canvasTarget);
14545
+ if (state2.internal.canvasTargetSizeDirty) {
14546
+ state2.internal.canvasTargetSizeDirty = false;
14547
+ renderer2.backend?.updateSize?.();
14548
+ }
14448
14549
  }
14449
14550
  },
14450
14551
  {
@@ -14500,7 +14601,8 @@ function createRoot(canvas) {
14500
14601
  const userHandlesRender = scheduler$1.hasUserJobsInPhase("render", newRootId);
14501
14602
  if (userHandlesRender || state2.internal.priority) return;
14502
14603
  try {
14503
- if (state2.renderPipeline?.render) state2.renderPipeline.render();
14604
+ const renderOverride = state2.internal.renderOverride;
14605
+ if (renderOverride) renderOverride();
14504
14606
  else if (renderer2?.render) renderer2.render(state2.scene, state2.camera);
14505
14607
  } catch (error) {
14506
14608
  state2.setError(error instanceof Error ? error : new Error(String(error)));
@@ -14513,8 +14615,7 @@ function createRoot(canvas) {
14513
14615
  phase: "render",
14514
14616
  system: true,
14515
14617
  // Internal flag: this is a system job, not user-controlled
14516
- // Apply scheduler config for render ordering and rate limiting
14517
- ...schedulerConfig?.after && { after: schedulerConfig.after },
14618
+ // FPS throttles only the default render job; Canvas ordering belongs to the root.
14518
14619
  ...schedulerConfig?.fps && { fps: schedulerConfig.fps }
14519
14620
  }
14520
14621
  );
@@ -14533,8 +14634,22 @@ function createRoot(canvas) {
14533
14634
  scheduler: scheduler$1
14534
14635
  }
14535
14636
  }));
14637
+ } else {
14638
+ const constraintsChanged = !is.equ(schedulerConfig?.before, lastConfiguredProps.schedulerBefore, shallowLoose) || !is.equ(schedulerConfig?.after, lastConfiguredProps.schedulerAfter, shallowLoose);
14639
+ if (constraintsChanged) {
14640
+ scheduler$1.setRootConstraints(rootId, {
14641
+ before: schedulerConfig?.before,
14642
+ after: schedulerConfig?.after
14643
+ });
14644
+ }
14645
+ if (schedulerConfig?.order !== lastConfiguredProps.schedulerOrder) {
14646
+ scheduler$1.setRootOrder(rootId, schedulerConfig?.order ?? 0);
14647
+ }
14536
14648
  }
14537
- scheduler$1.frameloop = frameloop;
14649
+ lastConfiguredProps.schedulerBefore = schedulerConfig?.before;
14650
+ lastConfiguredProps.schedulerAfter = schedulerConfig?.after;
14651
+ lastConfiguredProps.schedulerOrder = schedulerConfig?.order;
14652
+ attachRootExtensions(store);
14538
14653
  onCreated = onCreatedCallback;
14539
14654
  configured = true;
14540
14655
  resolve();
@@ -14576,18 +14691,24 @@ function unmountComponentAtNode(canvas, callback) {
14576
14691
  const fiber = root?.fiber;
14577
14692
  if (fiber) {
14578
14693
  const state = root?.store.getState();
14579
- if (state) state.internal.active = false;
14694
+ if (state) {
14695
+ state.internal.active = false;
14696
+ const unregisterRoot = state.internal.unregisterRoot;
14697
+ if (unregisterRoot) {
14698
+ unregisterRoot();
14699
+ state.internal.unregisterRoot = void 0;
14700
+ }
14701
+ }
14580
14702
  reconciler.updateContainer(null, fiber, null, () => {
14703
+ if (root?.store) detachRootExtensions(root.store);
14581
14704
  if (state) {
14582
14705
  setTimeout(() => {
14583
14706
  try {
14584
14707
  const renderer = state.internal.actualRenderer;
14585
- const unregisterRoot = state.internal.unregisterRoot;
14586
- if (unregisterRoot) unregisterRoot();
14587
14708
  const unregisterPrimary = state.internal.unregisterPrimary;
14588
14709
  if (unregisterPrimary) unregisterPrimary();
14589
14710
  const canvasTarget = state.internal.canvasTarget;
14590
- if (canvasTarget?.dispose) canvasTarget.dispose();
14711
+ if (state.internal.isSecondary && canvasTarget?.dispose) canvasTarget.dispose();
14591
14712
  state.events.disconnect?.();
14592
14713
  cleanupHelperGroup(root.store);
14593
14714
  if (state.isLegacy && renderer) {
@@ -14728,16 +14849,11 @@ function setEnvProps(background, scene, defaultScene, texture, sceneProps = {})
14728
14849
  const oldbg = target.background;
14729
14850
  const oldenv = target.environment;
14730
14851
  const oldSceneProps = {
14731
- // @ts-ignore
14732
14852
  backgroundBlurriness: target.backgroundBlurriness,
14733
- // @ts-ignore
14734
14853
  backgroundIntensity: target.backgroundIntensity,
14735
- // @ts-ignore
14736
- backgroundRotation: target.backgroundRotation?.clone?.() ?? [0, 0, 0],
14737
- // @ts-ignore
14854
+ backgroundRotation: target.backgroundRotation.clone(),
14738
14855
  environmentIntensity: target.environmentIntensity,
14739
- // @ts-ignore
14740
- environmentRotation: target.environmentRotation?.clone?.() ?? [0, 0, 0]
14856
+ environmentRotation: target.environmentRotation.clone()
14741
14857
  };
14742
14858
  if (background !== "only") target.environment = texture;
14743
14859
  if (background) target.background = texture;
@@ -14962,38 +15078,6 @@ function parseRendererConfig(rendererProp) {
14962
15078
  return { primaryCanvas: void 0, scheduler: void 0, renderer: rendererProp };
14963
15079
  }
14964
15080
 
14965
- const RESOURCE_KINDS = ["nodes", "uniforms", "buffers", "gpuStorage"];
14966
- const registries = /* @__PURE__ */ new WeakMap();
14967
- function getKindRegistry(store, kind) {
14968
- let registry = registries.get(store);
14969
- if (!registry) {
14970
- registry = {
14971
- nodes: { valid: /* @__PURE__ */ new Set(), staged: /* @__PURE__ */ new Map() },
14972
- uniforms: { valid: /* @__PURE__ */ new Set(), staged: /* @__PURE__ */ new Map() },
14973
- buffers: { valid: /* @__PURE__ */ new Set(), staged: /* @__PURE__ */ new Map() },
14974
- gpuStorage: { valid: /* @__PURE__ */ new Set(), staged: /* @__PURE__ */ new Map() }
14975
- };
14976
- registries.set(store, registry);
14977
- }
14978
- return registry[kind];
14979
- }
14980
- function invalidateResource(store, kind, scopeKey) {
14981
- const registry = getKindRegistry(store, kind);
14982
- {
14983
- registry.valid.clear();
14984
- registry.staged.clear();
14985
- }
14986
- }
14987
- function invalidateAllResources(store) {
14988
- for (const kind of RESOURCE_KINDS) invalidateResource(store, kind);
14989
- }
14990
-
14991
- function clearHmrCaches(store) {
14992
- const primary = store.getState().primaryStore ?? store;
14993
- invalidateAllResources(primary);
14994
- primary.setState((state) => ({ _hmrVersion: state._hmrVersion + 1 }));
14995
- }
14996
-
14997
15081
  function CanvasImpl({
14998
15082
  ref,
14999
15083
  children,
@@ -15031,9 +15115,9 @@ function CanvasImpl({
15031
15115
  React__namespace.useMemo(() => extend(THREE), []);
15032
15116
  const Bridge = useBridge();
15033
15117
  const backgroundProps = React__namespace.useMemo(() => parseBackground(background), [background]);
15034
- const hasInitialSizeRef = React__namespace.useRef(false);
15118
+ const [hasInitialSize, setHasInitialSize] = React__namespace.useState(false);
15035
15119
  const measureConfig = React__namespace.useMemo(() => {
15036
- if (!hasInitialSizeRef.current) {
15120
+ if (!hasInitialSize) {
15037
15121
  return {
15038
15122
  ...resize,
15039
15123
  scroll: resize?.scroll ?? true,
@@ -15045,7 +15129,7 @@ function CanvasImpl({
15045
15129
  debounce: { scroll: 50, resize: 0 },
15046
15130
  ...resize
15047
15131
  };
15048
- }, [resize, hasInitialSizeRef.current]);
15132
+ }, [hasInitialSize, resize]);
15049
15133
  const [containerRef, containerRect] = useMeasure__default(measureConfig);
15050
15134
  const effectiveSize = React__namespace.useMemo(() => {
15051
15135
  let w = width ?? containerRect.width;
@@ -15061,9 +15145,7 @@ function CanvasImpl({
15061
15145
  left: containerRect.left
15062
15146
  };
15063
15147
  }, [width, height, containerRect, forceEven]);
15064
- if (!hasInitialSizeRef.current && effectiveSize.width > 0 && effectiveSize.height > 0) {
15065
- hasInitialSizeRef.current = true;
15066
- }
15148
+ if (!hasInitialSize && effectiveSize.width > 0 && effectiveSize.height > 0) setHasInitialSize(true);
15067
15149
  const canvasRef = React__namespace.useRef(null);
15068
15150
  const divRef = React__namespace.useRef(null);
15069
15151
  React__namespace.useImperativeHandle(ref, () => canvasRef.current);
@@ -15177,6 +15259,7 @@ function CanvasImpl({
15177
15259
  const canvas = canvasRef.current;
15178
15260
  if (canvas) {
15179
15261
  return () => {
15262
+ if (canvas.isConnected) return;
15180
15263
  unmountComponentAtNode(canvas);
15181
15264
  root.current = null;
15182
15265
  };
@@ -15189,7 +15272,7 @@ function CanvasImpl({
15189
15272
  const handleHMR = () => {
15190
15273
  queueMicrotask(() => {
15191
15274
  const rootEntry = _roots.get(canvas);
15192
- if (rootEntry?.store) clearHmrCaches(rootEntry.store);
15275
+ if (rootEntry?.store) notifyRootExtensionsHmr(rootEntry.store);
15193
15276
  });
15194
15277
  };
15195
15278
  if (typeof ({ url: (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)) }) !== "undefined" && undefined) {
@@ -15307,8 +15390,10 @@ exports.prepare = prepare;
15307
15390
  exports.presetsObj = presetsObj;
15308
15391
  exports.reconciler = reconciler;
15309
15392
  exports.registerPrimary = registerPrimary;
15393
+ exports.registerRootExtension = registerRootExtension;
15310
15394
  exports.removeInteractivity = removeInteractivity;
15311
15395
  exports.resolve = resolve;
15396
+ exports.setRenderOverride = setRenderOverride;
15312
15397
  exports.swapInteractivity = swapInteractivity;
15313
15398
  exports.unmountComponentAtNode = unmountComponentAtNode;
15314
15399
  exports.unregisterPrimary = unregisterPrimary;