@react-three/fiber 10.0.0-canary.da7a768 → 10.0.0-canary.dc0ec15

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
@@ -11,6 +11,7 @@ const scheduler = require('@pmndrs/scheduler');
11
11
  const Tb = require('scheduler');
12
12
  const GroundedSkybox_js = require('three/examples/jsm/objects/GroundedSkybox.js');
13
13
  const HDRLoader_js = require('three/examples/jsm/loaders/HDRLoader.js');
14
+ const HDRCubeTextureLoader_js = require('three/examples/jsm/loaders/HDRCubeTextureLoader.js');
14
15
  const EXRLoader_js = require('three/examples/jsm/loaders/EXRLoader.js');
15
16
  const UltraHDRLoader_js = require('three/examples/jsm/loaders/UltraHDRLoader.js');
16
17
  const gainmapJs = require('@monogrid/gainmap-js');
@@ -313,7 +314,6 @@ function prepare(target, root, type, props) {
313
314
  }
314
315
  function invalidateInstance(instance) {
315
316
  if (!instance.parent) return;
316
- instance.props.onUpdate?.(instance.object);
317
317
  const state = instance.root?.getState?.();
318
318
  if (state && state.internal.frames === 0) state.invalidate();
319
319
  }
@@ -529,8 +529,10 @@ function hasVisibilityHandlers(handlers) {
529
529
  }
530
530
 
531
531
  const FROM_REF = Symbol.for("@react-three/fiber.fromRef");
532
- function fromRef(ref) {
533
- return { [FROM_REF]: ref };
532
+ function fromRef(ref, transform) {
533
+ const marker = { [FROM_REF]: ref };
534
+ if (transform) marker.transform = transform;
535
+ return marker;
534
536
  }
535
537
  function isFromRef(value) {
536
538
  return value !== null && typeof value === "object" && FROM_REF in value;
@@ -544,6 +546,63 @@ function isOnce(value) {
544
546
  return value !== null && typeof value === "object" && ONCE in value;
545
547
  }
546
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
+
547
606
  const RESERVED_PROPS = [
548
607
  "children",
549
608
  "key",
@@ -553,7 +612,6 @@ const RESERVED_PROPS = [
553
612
  "dispose",
554
613
  "attach",
555
614
  "object",
556
- "onUpdate",
557
615
  // Behavior flags
558
616
  "dispose"
559
617
  ];
@@ -631,11 +689,9 @@ function diffProps(instance, newProps) {
631
689
  for (const prop in instance.props) {
632
690
  if (RESERVED_PROPS.includes(prop) || newProps.hasOwnProperty(prop)) continue;
633
691
  const { root, key } = resolve(instance.object, prop);
634
- if (root.constructor && root.constructor.length === 0) {
692
+ if (typeof root?.constructor === "function") {
635
693
  const ctor = getMemoizedPrototype(root);
636
694
  if (!is.und(ctor)) changedProps[prop] = ctor[key];
637
- } else {
638
- changedProps[prop] = 0;
639
695
  }
640
696
  }
641
697
  return changedProps;
@@ -647,6 +703,13 @@ function applyProps(object, props) {
647
703
  for (const prop in props) {
648
704
  const value = props[prop];
649
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
+ }
650
713
  if (instance && EVENT_REGEX.test(prop)) {
651
714
  if (typeof value === "function") instance.handlers[prop] = value;
652
715
  else delete instance.handlers[prop];
@@ -752,682 +815,378 @@ function applyProps(object, props) {
752
815
  return object;
753
816
  }
754
817
 
755
- const shownNotices = /* @__PURE__ */ new Set();
756
- function notifyDepreciated({ heading, body, link }) {
757
- if (typeof process !== "undefined" && (process.env.NODE_ENV === "test" || process.env.JEST_WORKER_ID !== void 0) && process.env.R3F_SHOW_DEPRECATION_WARNINGS !== "true") {
758
- return;
759
- }
760
- if (shownNotices.has(heading)) return;
761
- shownNotices.add(heading);
762
- const caller = getCallerFrame();
763
- if (caller) {
764
- console.log();
765
- }
766
- const boxStyle = "background: #ff9800; color: #1a1a1a; padding: 8px 12px; border-radius: 4px; font-weight: 500;";
767
- if (caller) {
768
- console.log(`%c\u26A0\uFE0F ${heading}`, boxStyle);
769
- }
770
- if (body || link) {
771
- let message = "";
772
- if (body) message += body;
773
- if (link) message += (body ? "\n\n" : "") + `More info: ${link}`;
774
- console.warn(`%c${message}`, "font-weight: bold;");
775
- }
776
- }
777
- function getCallerFrame(depth = 3) {
778
- const stack = new Error().stack;
779
- if (!stack) return null;
780
- const lines = stack.split("\n");
781
- const frame = lines[depth];
782
- if (!frame) return null;
783
- let match = frame.match(/^\s*at (?:(.+?) )?\(?(.+?):(\d+):(\d+)\)?$/);
784
- if (!match) {
785
- match = frame.match(/^(?:(.+?)@)?(.+?):(\d+):(\d+)$/);
786
- }
787
- if (!match) return null;
788
- const [, fn, url, line] = match;
789
- return {
790
- functionName: fn ?? "<anonymous>",
791
- location: formatLocation(url, Number(line))
792
- };
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;
793
825
  }
794
- function formatLocation(url, line) {
795
- const clean = url.split("?")[0];
796
- const file = clean.split("/").pop() ?? clean;
797
- return `${file}:${line}`;
826
+ function useThree(selector = (state) => state, equalityFn) {
827
+ return useStore()(selector, equalityFn);
798
828
  }
799
- function notifyAlpha({ message, link }) {
800
- if (typeof process !== "undefined" && (process.env.NODE_ENV === "test" || process.env.JEST_WORKER_ID !== void 0) && process.env.R3F_SHOW_ALPHA_WARNINGS !== "true") {
801
- return;
802
- }
803
- if (shownNotices.has(message)) return;
804
- shownNotices.add(message);
805
- const boxStyle = "background: #6366f1; color: #ffffff; padding: 6px 10px; border-radius: 4px; font-weight: 500;";
806
- console.log(`%c\u{1F52C} ${message}`, boxStyle);
807
- {
808
- 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;
809
840
  }
841
+ return Proto;
810
842
  }
811
-
812
- const INTERNAL_LIB_MARKERS = ["zustand", "setState", "Object.assign"];
813
- function isInternalRendererAccess(stack, callerDepth = 2) {
814
- if (!stack) return false;
815
- const caller = stack.split("\n")[callerDepth] ?? "";
816
- 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];
817
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;
818
891
 
819
- const R3F_CONTEXT = Symbol.for("@react-three/fiber.context");
820
- const context = globalThis[R3F_CONTEXT] ?? (globalThis[R3F_CONTEXT] = React__namespace.createContext(null));
821
- const createStore = (invalidate, advance) => {
822
- const rootStore = traditional.createWithEqualityFn((set, get) => {
823
- const position = new webgpu.Vector3();
824
- const defaultTarget = new webgpu.Vector3();
825
- const tempTarget = new webgpu.Vector3();
826
- function getCurrentViewport(camera = get().camera, target = defaultTarget, size = get().size) {
827
- const { width, height, top, left } = size;
828
- const aspect = width / height;
829
- if (target.isVector3) tempTarget.copy(target);
830
- else tempTarget.set(...target);
831
- const distance = camera.getWorldPosition(position).distanceTo(tempTarget);
832
- if (isOrthographicCamera(camera)) {
833
- return { width: width / camera.zoom, height: height / camera.zoom, top, left, factor: 1, distance, aspect };
834
- } else {
835
- const fov = camera.fov * Math.PI / 180;
836
- const h = 2 * Math.tan(fov / 2) * distance;
837
- const w = h * (width / height);
838
- 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
+ });
839
931
  }
840
- }
841
- let performanceTimeout = void 0;
842
- const setPerformanceCurrent = (current) => set((state2) => ({ performance: { ...state2.performance, current } }));
843
- const pointer = new webgpu.Vector2();
844
- const rootState = {
845
- set,
846
- get,
847
- // Mock objects that have to be configured
848
- // primaryStore is set after store creation (self-reference for primary, primary's store for secondary)
849
- primaryStore: null,
850
- gl: null,
851
- renderer: null,
852
- camera: null,
853
- frustum: new webgpu.Frustum(),
854
- autoUpdateFrustum: true,
855
- raycaster: null,
856
- events: {
857
- priority: 1,
858
- enabled: true,
859
- connected: false,
860
- frameTimedRaycasts: true,
861
- alwaysFireOnScroll: true,
862
- updateOnFrame: false
863
- },
864
- scene: null,
865
- rootScene: null,
866
- xr: null,
867
- inspector: null,
868
- invalidate: (frames = 1, stackFrames = false) => invalidate(get(), frames, stackFrames),
869
- advance: (timestamp, runGlobalEffects) => advance(timestamp, runGlobalEffects, get()),
870
- textureColorSpace: webgpu.SRGBColorSpace,
871
- isLegacy: false,
872
- webGPUSupported: false,
873
- isNative: false,
874
- controls: null,
875
- pointer,
876
- mouse: pointer,
877
- frameloop: "always",
878
- onPointerMissed: void 0,
879
- onDragOverMissed: void 0,
880
- onDropMissed: void 0,
881
- performance: {
882
- current: 1,
883
- min: 0.5,
884
- max: 1,
885
- debounce: 200,
886
- regress: () => {
887
- const state2 = get();
888
- if (performanceTimeout) clearTimeout(performanceTimeout);
889
- if (state2.performance.current !== state2.performance.min) setPerformanceCurrent(state2.performance.min);
890
- performanceTimeout = setTimeout(
891
- () => setPerformanceCurrent(get().performance.max),
892
- state2.performance.debounce
893
- );
894
- }
895
- },
896
- size: { width: 0, height: 0, top: 0, left: 0 },
897
- viewport: {
898
- initialDpr: 0,
899
- dpr: 0,
900
- width: 0,
901
- height: 0,
902
- top: 0,
903
- left: 0,
904
- aspect: 0,
905
- distance: 0,
906
- factor: 0,
907
- getCurrentViewport
908
- },
909
- setEvents: (events) => set((state2) => ({ ...state2, events: { ...state2.events, ...events } })),
910
- setSize: (width, height, top, left) => {
911
- const state2 = get();
912
- if (width === void 0) {
913
- set({ _sizeImperative: false });
914
- if (state2._sizeProps) {
915
- const { width: propW, height: propH } = state2._sizeProps;
916
- if (propW !== void 0 || propH !== void 0) {
917
- const currentSize = state2.size;
918
- const newSize = {
919
- width: propW ?? currentSize.width,
920
- height: propH ?? currentSize.height,
921
- top: currentSize.top,
922
- left: currentSize.left
923
- };
924
- set((s) => ({
925
- size: newSize,
926
- viewport: { ...s.viewport, ...getCurrentViewport(state2.camera, defaultTarget, newSize) }
927
- }));
928
- 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;
929
959
  }
930
960
  }
931
- return;
932
961
  }
933
- const w = width;
934
- const h = height ?? width;
935
- const t = top ?? state2.size.top;
936
- const l = left ?? state2.size.left;
937
- const size = { width: w, height: h, top: t, left: l };
938
- set((s) => ({
939
- size,
940
- viewport: { ...s.viewport, ...getCurrentViewport(state2.camera, defaultTarget, size) },
941
- _sizeImperative: true
942
- }));
943
- 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);
944
979
  },
945
- setDpr: (dpr) => set((state2) => {
946
- const resolved = calculateDpr(dpr);
947
- return { viewport: { ...state2.viewport, dpr: resolved, initialDpr: state2.viewport.initialDpr || resolved } };
948
- }),
949
- setFrameloop: (frameloop = "always") => {
950
- set(() => ({ frameloop }));
951
- 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);
952
1003
  },
953
- setError: (error) => set(() => ({ error })),
954
- error: null,
955
- //* TSL State (managed via hooks: useUniforms, useNodes, useBuffers, useGPUStorage, useTextures, useRenderPipeline) ==============================
956
- uniforms: {},
957
- nodes: {},
958
- buffers: {},
959
- gpuStorage: {},
960
- textures: /* @__PURE__ */ new Map(),
961
- _textureRefs: /* @__PURE__ */ new Map(),
962
- renderPipeline: null,
963
- passes: {},
964
- _hmrVersion: 0,
965
- _sizeImperative: false,
966
- _sizeProps: null,
967
- previousRoot: void 0,
968
- internal: {
969
- // Events
970
- interaction: [],
971
- subscribers: [],
972
- // Per-pointer state (new unified structure)
973
- pointerMap: /* @__PURE__ */ new Map(),
974
- pointerDirty: /* @__PURE__ */ new Map(),
975
- lastEvent: React__namespace.createRef(),
976
- // Deprecated but kept for backwards compatibility
977
- hovered: /* @__PURE__ */ new Map(),
978
- initialClick: [0, 0],
979
- initialHits: [],
980
- capturedMap: /* @__PURE__ */ new Map(),
981
- // Visibility tracking (onFramed, onOccluded, onVisible)
982
- visibilityRegistry: /* @__PURE__ */ new Map(),
983
- // Occlusion system (WebGPU only)
984
- occlusionEnabled: false,
985
- occlusionObserver: null,
986
- occlusionCache: /* @__PURE__ */ new Map(),
987
- helperGroup: null,
988
- // Updates
989
- active: false,
990
- frames: 0,
991
- priority: 0,
992
- subscribe: (ref, priority, store) => {
993
- const internal = get().internal;
994
- internal.priority = internal.priority + (priority > 0 ? 1 : 0);
995
- internal.subscribers.push({ ref, priority, store });
996
- internal.subscribers = internal.subscribers.sort((a, b) => a.priority - b.priority);
997
- return () => {
998
- const internal2 = get().internal;
999
- if (internal2?.subscribers) {
1000
- internal2.priority = internal2.priority - (priority > 0 ? 1 : 0);
1001
- internal2.subscribers = internal2.subscribers.filter((s) => s.ref !== ref);
1002
- }
1003
- };
1004
- },
1005
- // Renderer Storage (single source of truth)
1006
- actualRenderer: null,
1007
- // Scheduler for useFrameNext (initialized in renderer.tsx)
1008
- scheduler: null
1009
- }
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
1010
1046
  };
1011
- return rootState;
1012
- });
1013
- const state = rootStore.getState();
1014
- Object.defineProperty(state, "gl", {
1015
- get() {
1016
- const currentState = rootStore.getState();
1017
- if (!currentState.isLegacy && currentState.internal.actualRenderer) {
1018
- const stack = new Error().stack || "";
1019
- if (!isInternalRendererAccess(stack)) {
1020
- const cleanedStack = stack.split("\n").slice(2).join("\n") || "Stack trace unavailable";
1021
- notifyDepreciated({
1022
- heading: "Accessing state.gl in WebGPU mode",
1023
- 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
1024
- });
1025
- }
1026
- }
1027
- return currentState.internal.actualRenderer;
1028
- },
1029
- set(value) {
1030
- rootStore.getState().internal.actualRenderer = value;
1031
- },
1032
- enumerable: true,
1033
- configurable: true
1034
- });
1035
- Object.defineProperty(state, "renderer", {
1036
- get() {
1037
- return rootStore.getState().internal.actualRenderer;
1038
- },
1039
- set(value) {
1040
- rootStore.getState().internal.actualRenderer = value;
1041
- },
1042
- enumerable: true,
1043
- configurable: true
1044
- });
1045
- let oldScene = state.scene;
1046
- rootStore.subscribe(() => {
1047
- const currentState = rootStore.getState();
1048
- const { scene, rootScene, set } = currentState;
1049
- if (scene !== oldScene) {
1050
- oldScene = scene;
1051
- if (scene?.isScene && scene !== rootScene) {
1052
- set({ rootScene: scene });
1053
- }
1054
- }
1055
- });
1056
- let oldSize = state.size;
1057
- let oldDpr = state.viewport.dpr;
1058
- let oldCamera = state.camera;
1059
- rootStore.subscribe(() => {
1060
- const { camera, size, viewport, set, internal } = rootStore.getState();
1061
- const actualRenderer = internal.actualRenderer;
1062
- const canvasTarget = internal.canvasTarget;
1063
- if (size.width !== oldSize.width || size.height !== oldSize.height || viewport.dpr !== oldDpr) {
1064
- oldSize = size;
1065
- oldDpr = viewport.dpr;
1066
- updateCamera(camera, size);
1067
- if (internal.isSecondary && canvasTarget) {
1068
- if (viewport.dpr > 0) canvasTarget.setPixelRatio(viewport.dpr);
1069
- canvasTarget.setSize(size.width, size.height, false);
1070
- } else {
1071
- if (viewport.dpr > 0) actualRenderer.setPixelRatio(viewport.dpr);
1072
- actualRenderer.setSize(size.width, size.height, false);
1073
- if (canvasTarget) {
1074
- if (viewport.dpr > 0) canvasTarget.setPixelRatio(viewport.dpr);
1075
- canvasTarget.setSize(size.width, size.height, false);
1076
- }
1077
- }
1078
- }
1079
- if (camera !== oldCamera) {
1080
- oldCamera = camera;
1081
- const { rootScene } = rootStore.getState();
1082
- if (camera && rootScene && !camera.parent) {
1083
- rootScene.add(camera);
1084
- }
1085
- set((state2) => ({ viewport: { ...state2.viewport, ...state2.viewport.getCurrentViewport(camera) } }));
1086
- const currentState = rootStore.getState();
1087
- if (currentState.autoUpdateFrustum && camera) {
1088
- updateFrustum(camera, currentState.frustum);
1089
- }
1090
- }
1091
- });
1092
- rootStore.subscribe((state2) => invalidate(state2));
1093
- return rootStore;
1094
- };
1047
+ }, [id, isPaused]);
1048
+ return controls;
1049
+ }
1095
1050
 
1096
- const memoizedLoaders = /* @__PURE__ */ new WeakMap();
1097
- const isConstructor$1 = (value) => typeof value === "function" && value?.prototype?.constructor === value;
1098
- function getLoader$1(Proto) {
1099
- if (isConstructor$1(Proto)) {
1100
- let loader = memoizedLoaders.get(Proto);
1101
- if (!loader) {
1102
- loader = new Proto();
1103
- 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]);
1104
1075
  }
1105
- return loader;
1076
+ return result;
1106
1077
  }
1107
- return Proto;
1108
1078
  }
1109
- function loadingFn(extensions, onProgress) {
1110
- return function(Proto, input) {
1111
- const loader = getLoader$1(Proto);
1112
- if (extensions) extensions(loader);
1113
- if ("loadAsync" in loader && typeof loader.loadAsync === "function") {
1114
- return loader.loadAsync(input, onProgress).then((data) => {
1115
- if (isObject3D(data?.scene)) Object.assign(data, buildGraph(data.scene));
1116
- 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
+ }
1117
1133
  });
1118
1134
  }
1119
- return new Promise(
1120
- (res, reject) => loader.load(
1121
- input,
1122
- (data) => {
1123
- if (isObject3D(data?.scene)) Object.assign(data, buildGraph(data.scene));
1124
- res(data);
1125
- },
1126
- onProgress,
1127
- (error) => reject(new Error(`Could not load ${input}: ${error?.message}`))
1128
- )
1129
- );
1130
- };
1131
- }
1132
- function useLoader(loader, input, extensions, onProgress, cacheKey) {
1133
- const inputs = Array.isArray(input) ? input : [input];
1134
- const cacheKeys = Array.isArray(cacheKey ?? input) ? cacheKey ?? input : [cacheKey ?? input];
1135
- const fn = loadingFn(extensions, onProgress);
1136
- const results = cacheKeys.map(
1137
- (key, index) => suspendReact.suspend(() => fn(loader, inputs[index]), [loader, key], { equal: is.equ })
1138
- );
1139
- 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;
1140
1178
  }
1141
- useLoader.preload = function(loader, input, extensions, onProgress, cacheKey) {
1142
- const inputs = Array.isArray(input) ? input : [input];
1143
- const cacheKeys = Array.isArray(cacheKey ?? input) ? cacheKey ?? input : [cacheKey ?? input];
1144
- cacheKeys.forEach(
1145
- (key, index) => suspendReact.preload(() => loadingFn(extensions, onProgress)(loader, inputs[index]), [loader, key])
1146
- );
1147
- };
1148
- useLoader.clear = function(loader, input, cacheKey) {
1149
- const cacheKeys = Array.isArray(cacheKey ?? input) ? cacheKey ?? input : [cacheKey ?? input];
1150
- cacheKeys.forEach((key) => suspendReact.clear([loader, key]));
1151
- };
1152
- useLoader.loader = getLoader$1;
1153
-
1154
- function useFrame(callback, priorityOrOptions) {
1155
- const store = React__namespace.useContext(context);
1156
- const isInsideCanvas = store !== null;
1157
- const scheduler$1 = scheduler.getScheduler();
1158
- const optionsKey = typeof priorityOrOptions === "number" ? `p:${priorityOrOptions}` : priorityOrOptions ? JSON.stringify({
1159
- id: priorityOrOptions.id,
1160
- phase: priorityOrOptions.phase,
1161
- priority: priorityOrOptions.priority,
1162
- fps: priorityOrOptions.fps,
1163
- drop: priorityOrOptions.drop,
1164
- enabled: priorityOrOptions.enabled,
1165
- before: priorityOrOptions.before,
1166
- after: priorityOrOptions.after
1167
- }) : "";
1168
- const options = React__namespace.useMemo(() => {
1169
- return typeof priorityOrOptions === "number" ? { priority: priorityOrOptions } : priorityOrOptions ?? {};
1170
- }, [optionsKey]);
1171
- const reactId = React__namespace.useId();
1172
- const id = options.id ?? reactId;
1173
- const callbackRef = useMutableCallback(callback);
1174
- const isLegacyPriority = typeof priorityOrOptions === "number" && priorityOrOptions > 0;
1175
- useIsomorphicLayoutEffect(() => {
1176
- if (!callback) return;
1177
- if (isInsideCanvas) {
1178
- const state = store.getState();
1179
- const rootId = state.internal.rootId;
1180
- if (isLegacyPriority) {
1181
- state.internal.priority++;
1182
- let parentRoot = state.previousRoot;
1183
- while (parentRoot) {
1184
- const parentState = parentRoot.getState();
1185
- if (parentState?.internal) parentState.internal.priority++;
1186
- parentRoot = parentState?.previousRoot;
1187
- }
1188
- notifyDepreciated({
1189
- heading: "useFrame with numeric priority is deprecated",
1190
- 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 })',
1191
- link: "https://docs.pmnd.rs/react-three-fiber/api/hooks#useframe"
1192
- });
1193
- }
1194
- const wrappedCallback = (frameState, delta) => {
1195
- const localState = store.getState();
1196
- const mergedState = {
1197
- ...localState,
1198
- time: frameState.time,
1199
- delta: frameState.delta,
1200
- elapsed: frameState.elapsed,
1201
- frame: frameState.frame
1202
- };
1203
- callbackRef.current?.(mergedState, delta);
1204
- };
1205
- const unregister = scheduler$1.register(wrappedCallback, {
1206
- id,
1207
- rootId,
1208
- ...options
1209
- });
1210
- return () => {
1211
- unregister();
1212
- if (isLegacyPriority) {
1213
- const currentState = store.getState();
1214
- if (currentState.internal) {
1215
- currentState.internal.priority--;
1216
- let parentRoot = currentState.previousRoot;
1217
- while (parentRoot) {
1218
- const parentState = parentRoot.getState();
1219
- if (parentState?.internal) parentState.internal.priority--;
1220
- parentRoot = parentState?.previousRoot;
1221
- }
1222
- }
1223
- }
1224
- };
1225
- } else {
1226
- return scheduler$1.register(
1227
- (state, delta) => {
1228
- const frameState = state;
1229
- if (!frameState.renderer) return;
1230
- callbackRef.current?.(frameState, delta);
1231
- },
1232
- { id, ...options }
1233
- );
1234
- }
1235
- }, [store, scheduler$1, id, optionsKey, isLegacyPriority, isInsideCanvas]);
1236
- const isPaused = React__namespace.useSyncExternalStore(
1237
- // Subscribe function
1238
- React__namespace.useCallback(
1239
- (onStoreChange) => {
1240
- return scheduler.getScheduler().subscribeJobState(id, onStoreChange);
1241
- },
1242
- [id]
1243
- ),
1244
- // getSnapshot function
1245
- React__namespace.useCallback(() => scheduler.getScheduler().isJobPaused(id), [id]),
1246
- // getServerSnapshot function (SSR)
1247
- React__namespace.useCallback(() => false, [])
1248
- );
1249
- const controls = React__namespace.useMemo(() => {
1250
- const scheduler2 = scheduler.getScheduler();
1251
- return {
1252
- /** The job's unique ID */
1253
- id,
1254
- /**
1255
- * Access to the global scheduler for frame loop control.
1256
- * Use for controlling the entire frame loop, adding phases, etc.
1257
- */
1258
- scheduler: scheduler2,
1259
- /**
1260
- * Manually step this job only.
1261
- * Bypasses FPS limiting - always runs.
1262
- * @param timestamp Optional timestamp (defaults to performance.now())
1263
- */
1264
- step: (timestamp) => {
1265
- scheduler.getScheduler().stepJob(id, timestamp);
1266
- },
1267
- /**
1268
- * Manually step ALL jobs in the scheduler.
1269
- * Useful for frameloop='never' mode.
1270
- * @param timestamp Optional timestamp (defaults to performance.now())
1271
- */
1272
- stepAll: (timestamp) => {
1273
- scheduler.getScheduler().step(timestamp);
1274
- },
1275
- /**
1276
- * Pause this job (set enabled=false).
1277
- * Job remains registered but won't run.
1278
- */
1279
- pause: () => {
1280
- scheduler.getScheduler().pauseJob(id);
1281
- },
1282
- /**
1283
- * Resume this job (set enabled=true).
1284
- */
1285
- resume: () => {
1286
- scheduler.getScheduler().resumeJob(id);
1287
- },
1288
- /**
1289
- * Reactive paused state - automatically updates when pause/resume is called.
1290
- * No need for forceUpdate() in your components.
1291
- */
1292
- isPaused
1293
- };
1294
- }, [id, isPaused]);
1295
- return controls;
1296
- }
1297
-
1298
- const IsObject = (url) => url === Object(url) && !Array.isArray(url) && typeof url !== "function";
1299
- function getUrls(input) {
1300
- if (typeof input === "string") return [input];
1301
- if (Array.isArray(input)) return input;
1302
- return Object.values(input);
1303
- }
1304
- function allUrlsCached(urls, textureCache) {
1305
- return urls.every((url) => textureCache.has(url));
1306
- }
1307
- function buildFromCache(input, textureCache) {
1308
- if (typeof input === "string") {
1309
- return textureCache.get(input);
1310
- } else if (Array.isArray(input)) {
1311
- return input.map((url) => textureCache.get(url));
1312
- } else {
1313
- const result = {};
1314
- const objInput = input;
1315
- for (const key in objInput) {
1316
- result[key] = textureCache.get(objInput[key]);
1317
- }
1318
- return result;
1319
- }
1320
- }
1321
- function useTexture(input, optionsOrOnLoad) {
1322
- const renderer = useThree((state) => state.internal.actualRenderer);
1323
- const store = useStore();
1324
- const options = typeof optionsOrOnLoad === "function" ? { onLoad: optionsOrOnLoad } : optionsOrOnLoad ?? {};
1325
- const { onLoad, cache = true } = options;
1326
- const onLoadRef = React.useRef(onLoad);
1327
- onLoadRef.current = onLoad;
1328
- const onLoadCalledForRef = React.useRef(null);
1329
- const urls = React.useMemo(() => getUrls(input), [input]);
1330
- const cachedResult = React.useMemo(() => {
1331
- if (!cache) return null;
1332
- const textures = store.getState().textures;
1333
- if (!allUrlsCached(urls, textures)) return null;
1334
- return buildFromCache(input, textures);
1335
- }, [cache, urls, input, store]);
1336
- const loadedTextures = useLoader(
1337
- webgpu.TextureLoader,
1338
- IsObject(input) ? Object.values(input) : input
1339
- );
1340
- const inputKey = urls.join("\0");
1341
- React.useLayoutEffect(() => {
1342
- if (cachedResult) return;
1343
- if (onLoadCalledForRef.current === inputKey) return;
1344
- onLoadCalledForRef.current = inputKey;
1345
- onLoadRef.current?.(loadedTextures);
1346
- }, [cachedResult, loadedTextures, inputKey]);
1347
- React.useEffect(() => {
1348
- if (cachedResult) return;
1349
- if ("initTexture" in renderer) {
1350
- let textureArray = [];
1351
- if (Array.isArray(loadedTextures)) {
1352
- textureArray = loadedTextures;
1353
- } else if (loadedTextures instanceof webgpu.Texture) {
1354
- textureArray = [loadedTextures];
1355
- } else if (IsObject(loadedTextures)) {
1356
- textureArray = Object.values(loadedTextures);
1357
- }
1358
- textureArray.forEach((texture) => {
1359
- if (texture instanceof webgpu.Texture) {
1360
- renderer.initTexture(texture);
1361
- }
1362
- });
1363
- }
1364
- }, [renderer, loadedTextures, cachedResult]);
1365
- const mappedTextures = React.useMemo(() => {
1366
- if (cachedResult) return cachedResult;
1367
- if (IsObject(input)) {
1368
- const keyed = {};
1369
- const textureArray = loadedTextures;
1370
- let i = 0;
1371
- for (const key in input) keyed[key] = textureArray[i++];
1372
- return keyed;
1373
- } else {
1374
- return loadedTextures;
1375
- }
1376
- }, [input, loadedTextures, cachedResult]);
1377
- React.useEffect(() => {
1378
- if (!cache) return;
1379
- const urlTextureMap = [];
1380
- if (typeof input === "string") {
1381
- urlTextureMap.push([input, mappedTextures]);
1382
- } else if (Array.isArray(input)) {
1383
- const textureArray = mappedTextures;
1384
- input.forEach((url, i) => urlTextureMap.push([url, textureArray[i]]));
1385
- } else if (IsObject(input)) {
1386
- const textureRecord = mappedTextures;
1387
- for (const key in input) {
1388
- const url = input[key];
1389
- urlTextureMap.push([url, textureRecord[key]]);
1390
- }
1391
- }
1392
- store.setState((state) => {
1393
- const refs = new Map(state._textureRefs);
1394
- let textures = state.textures;
1395
- let added = false;
1396
- for (const [url, texture] of urlTextureMap) {
1397
- if (!textures.has(url)) {
1398
- if (!added) {
1399
- textures = new Map(textures);
1400
- added = true;
1401
- }
1402
- textures.set(url, texture);
1403
- }
1404
- refs.set(url, (refs.get(url) ?? 0) + 1);
1405
- }
1406
- return added ? { textures, _textureRefs: refs } : { _textureRefs: refs };
1407
- });
1408
- return () => store.setState((state) => {
1409
- const refs = new Map(state._textureRefs);
1410
- for (const [url] of urlTextureMap) {
1411
- const next = (refs.get(url) ?? 0) - 1;
1412
- if (next <= 0) refs.delete(url);
1413
- else refs.set(url, next);
1414
- }
1415
- return { _textureRefs: refs };
1416
- });
1417
- }, [cache, input, mappedTextures, store]);
1418
- return mappedTextures;
1419
- }
1420
- useTexture.preload = (url) => useLoader.preload(webgpu.TextureLoader, url);
1421
- useTexture.clear = (input) => useLoader.clear(webgpu.TextureLoader, input);
1422
- const Texture = ({
1423
- children,
1424
- input,
1425
- onLoad,
1426
- cache
1427
- }) => {
1428
- const options = typeof onLoad === "function" ? { onLoad, cache } : { ...onLoad, cache };
1429
- const ret = useTexture(input, options);
1430
- 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) });
1431
1190
  };
1432
1191
 
1433
1192
  function useTextures(selector) {
@@ -1577,7 +1336,7 @@ function useEnvironment({
1577
1336
  texture = texture.renderTarget?.texture;
1578
1337
  }
1579
1338
  texture.mapping = isCubemap ? webgpu.CubeReflectionMapping : webgpu.EquirectangularReflectionMapping;
1580
- texture.colorSpace = colorSpace ?? (isCubemap ? "srgb" : "srgb-linear");
1339
+ texture.colorSpace = colorSpace ?? (isCubemap && extension !== "hdr-cube" ? "srgb" : "srgb-linear");
1581
1340
  return texture;
1582
1341
  }
1583
1342
  const preloadDefaultOptions = {
@@ -1630,22 +1389,15 @@ function getExtension(files) {
1630
1389
  const isCubemap = isArray(files) && files.length === 6;
1631
1390
  const isGainmap = isArray(files) && files.length === 3 && files.some((file) => file.endsWith("json"));
1632
1391
  const firstEntry = isArray(files) ? files[0] : files;
1633
- 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;
1634
1394
  return { extension, isCubemap, isGainmap };
1635
1395
  }
1636
1396
  function getLoader(extension) {
1637
- 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;
1638
1398
  return loader;
1639
1399
  }
1640
1400
 
1641
- function useStore() {
1642
- const store = React.useContext(context);
1643
- if (!store) throw new Error("R3F: Hooks can only be used within the Canvas component!");
1644
- return store;
1645
- }
1646
- function useThree(selector = (state) => state, equalityFn) {
1647
- return useStore()(selector, equalityFn);
1648
- }
1649
1401
  function useInstanceHandle(ref) {
1650
1402
  const instance = React.useRef(null);
1651
1403
  React.useImperativeHandle(instance, () => ref.current.__r3f, [ref]);
@@ -1685,13 +1437,19 @@ function addTail(callback) {
1685
1437
  return scheduler.getScheduler().onIdle(callback);
1686
1438
  }
1687
1439
  function invalidate(state, frames = 1, stackFrames = false) {
1688
- 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);
1689
1444
  }
1690
- function advance(timestamp) {
1691
- 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);
1692
1450
  }
1693
1451
 
1694
- const version = "10.0.0-alpha.3";
1452
+ const version = "10.0.0-alpha.5";
1695
1453
  const packageData = {
1696
1454
  version: version};
1697
1455
 
@@ -13550,7 +13308,10 @@ function extend(objects) {
13550
13308
  catalogue[Component] = objects;
13551
13309
  return Component;
13552
13310
  } else {
13553
- Object.assign(catalogue, objects);
13311
+ for (const name in objects) {
13312
+ const object = objects[name];
13313
+ if (isConstructor(object)) catalogue[name] = object;
13314
+ }
13554
13315
  }
13555
13316
  }
13556
13317
  function validateInstance(type, props) {
@@ -13898,7 +13659,7 @@ const reconciler = /* @__PURE__ */ createReconciler({
13898
13659
  getRootHostContext: () => NO_CONTEXT,
13899
13660
  getChildHostContext: () => NO_CONTEXT,
13900
13661
  commitUpdate(instance, type, oldProps, newProps, fiber) {
13901
- validateInstance(type, newProps);
13662
+ validateInstance(instance.type, newProps);
13902
13663
  let reconstruct = false;
13903
13664
  if (instance.type === "primitive" && oldProps.object !== newProps.object) reconstruct = true;
13904
13665
  else if (newProps.args?.length !== oldProps.args?.length) reconstruct = true;
@@ -13913,162 +13674,491 @@ const reconciler = /* @__PURE__ */ createReconciler({
13913
13674
  else delete instance.props.attach;
13914
13675
  if (Object.keys(changedProps).length) applyProps(instance.object, changedProps);
13915
13676
  }
13916
- },
13917
- finalizeInitialChildren: (instance) => {
13918
- for (const prop in instance.props) {
13919
- 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;
13920
14095
  }
13921
- return false;
13922
- },
13923
- commitMount(instance) {
13924
- const resolved = {};
13925
- for (const prop in instance.props) {
13926
- const value = instance.props[prop];
13927
- if (isFromRef(value)) {
13928
- const ref = value[FROM_REF];
13929
- 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);
13930
14106
  }
13931
14107
  }
13932
- if (Object.keys(resolved).length) applyProps(instance.object, resolved);
13933
- },
13934
- getPublicInstance: (instance) => instance?.object,
13935
- prepareForCommit: () => null,
13936
- preparePortalMount: (container) => {
13937
- const target = container.getState().internal.container ?? container.getState().scene;
13938
- return prepare(target, container, "", {});
13939
- },
13940
- // Reconstructed instances are swapped once all mutations are committed,
13941
- // before layout effects run so refs point to the new objects
13942
- resetAfterCommit: flushReconstructedInstances,
13943
- shouldSetTextContent: () => false,
13944
- clearContainer: () => false,
13945
- hideInstance,
13946
- unhideInstance,
13947
- createTextInstance: handleTextInstance,
13948
- hideTextInstance: handleTextInstance,
13949
- unhideTextInstance: handleTextInstance,
13950
- // Mirrors react-dom, which flushes discrete work in microtasks
13951
- supportsMicrotasks: true,
13952
- scheduleMicrotask,
13953
- scheduleTimeout: typeof setTimeout === "function" ? setTimeout : void 0,
13954
- cancelTimeout: typeof clearTimeout === "function" ? clearTimeout : void 0,
13955
- noTimeout: -1,
13956
- getInstanceFromNode: () => null,
13957
- beforeActiveInstanceBlur() {
13958
- },
13959
- afterActiveInstanceBlur() {
13960
- },
13961
- detachDeletedInstance() {
13962
- },
13963
- prepareScopeUpdate() {
13964
- },
13965
- getInstanceFromScope: () => null,
13966
- shouldAttemptEagerTransition: () => false,
13967
- trackSchedulerEvent: () => {
13968
- },
13969
- resolveEventType: () => null,
13970
- resolveEventTimeStamp: () => -1.1,
13971
- requestPostPaintCallback() {
13972
- },
13973
- maySuspendCommit: () => false,
13974
- preloadInstance: () => true,
13975
- // true indicates already loaded
13976
- suspendInstance() {
13977
- },
13978
- waitForCommitToBeReady: () => null,
13979
- NotPendingTransition: null,
13980
- // The reconciler types use the internal ReactContext with all the hidden properties
13981
- // so we have to cast from the public React.Context type
13982
- HostTransitionContext: /* @__PURE__ */ React__namespace.createContext(
13983
- null
13984
- ),
13985
- setCurrentUpdatePriority(newPriority) {
13986
- currentUpdatePriority = newPriority;
13987
- },
13988
- getCurrentUpdatePriority() {
13989
- return currentUpdatePriority;
13990
- },
13991
- resolveUpdatePriority() {
13992
- if (currentUpdatePriority !== NoEventPriority) return currentUpdatePriority;
13993
- const eventType = typeof window !== "undefined" ? window.event?.type : void 0;
13994
- if (eventType === void 0) return r;
13995
- return getEventPriority(eventType);
13996
- },
13997
- resetFormInstance() {
13998
- },
13999
- // @ts-ignore DefinitelyTyped is not up to date
14000
- rendererPackageName: "@react-three/fiber",
14001
- rendererVersion: packageData.version,
14002
- // 19.2 Reconciler
14003
- // https://github.com/facebook/react/pull/31975
14004
- // https://github.com/facebook/react/pull/31999
14005
- applyViewTransitionName(_instance, _name, _className) {
14006
- },
14007
- restoreViewTransitionName(_instance, _props) {
14008
- },
14009
- cancelViewTransitionName(_instance, _name, _props) {
14010
- },
14011
- cancelRootViewTransitionName(_rootContainer) {
14012
- },
14013
- restoreRootViewTransitionName(_rootContainer) {
14014
- },
14015
- InstanceMeasurement: null,
14016
- measureInstance: (_instance) => null,
14017
- wasInstanceInViewport: (_measurement) => true,
14018
- hasInstanceChanged: (_oldMeasurement, _newMeasurement) => false,
14019
- hasInstanceAffectedParent: (_oldMeasurement, _newMeasurement) => false,
14020
- // https://github.com/facebook/react/pull/32002
14021
- // https://github.com/facebook/react/pull/34486
14022
- suspendOnActiveViewTransition(_state, _container) {
14023
- },
14024
- // https://github.com/facebook/react/pull/32451
14025
- // https://github.com/facebook/react/pull/32760
14026
- startGestureTransition: () => null,
14027
- startViewTransition: () => null,
14028
- stopViewTransition(_transition) {
14029
- },
14030
- // https://github.com/facebook/react/pull/32038
14031
- createViewTransitionInstance: (_name) => null,
14032
- // https://github.com/facebook/react/pull/32379
14033
- // https://github.com/facebook/react/pull/32786
14034
- getCurrentGestureOffset(_provider) {
14035
- throw new Error("startGestureTransition is not yet supported in react-three-fiber.");
14036
- },
14037
- // https://github.com/facebook/react/pull/32500
14038
- cloneMutableInstance(instance, _keepChildren) {
14039
- return instance;
14040
- },
14041
- cloneMutableTextInstance(textInstance) {
14042
- return textInstance;
14043
- },
14044
- // React 19.2 added these for root-level view transitions. They only run for hosts that opt into
14045
- // view transitions by returning a container from cloneRootViewTransitionContainer; R3F renders
14046
- // into a WebGL/WebGPU canvas, where cloning the root has no meaning, so we never opt in and
14047
- // React never calls them. They stay as no-ops rather than throws: the host config is shared by
14048
- // every root, and a throw here would turn a React-internal probe into a hard crash for users.
14049
- // Guarded by a test asserting a full render + unmount never reaches them.
14050
- cloneRootViewTransitionContainer(_rootContainer) {
14051
- },
14052
- removeRootViewTransitionClone(_rootContainer, _clone) {
14053
- },
14054
- // https://github.com/facebook/react/pull/32465
14055
- createFragmentInstance: (_fiber) => null,
14056
- updateFragmentInstanceFiber(_fiber, _instance) {
14057
- },
14058
- commitNewChildToFragmentInstance(_child, _fragmentInstance) {
14059
- },
14060
- deleteChildFromFragmentInstance(_child, _fragmentInstance) {
14061
- },
14062
- // https://github.com/facebook/react/pull/32653
14063
- measureClonedInstance: (_instance) => null,
14064
- // https://github.com/facebook/react/pull/32819
14065
- maySuspendCommitOnUpdate: (_type, _oldProps, _newProps) => false,
14066
- maySuspendCommitInSyncRender: (_type, _props) => false,
14067
- // https://github.com/facebook/react/pull/34486
14068
- startSuspendingCommit: () => null,
14069
- // https://github.com/facebook/react/pull/34522
14070
- getSuspendedCommitReason: (_state, _rootContainer) => null
14071
- });
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
+ }
14072
14162
 
14073
14163
  const isRenderer = (def) => !!def?.render;
14074
14164
  const _roots = /* @__PURE__ */ new Map();
@@ -14225,9 +14315,8 @@ function createRoot(canvas) {
14225
14315
  if (!renderer.hasInitialized?.()) {
14226
14316
  const size2 = computeInitialSize(canvas, propsSize);
14227
14317
  if (size2.width > 0 && size2.height > 0) {
14228
- const pixelRatio = calculateDpr(dpr);
14229
- canvas.width = size2.width * pixelRatio;
14230
- canvas.height = size2.height * pixelRatio;
14318
+ renderer.setPixelRatio(calculateDpr(dpr));
14319
+ renderer.setSize(size2.width, size2.height, false);
14231
14320
  }
14232
14321
  await renderer.init();
14233
14322
  }
@@ -14236,7 +14325,7 @@ function createRoot(canvas) {
14236
14325
  state.internal.actualRenderer = renderer;
14237
14326
  state.set({ webGPUSupported: isWebGPUBackend, renderer, primaryStore: store });
14238
14327
  if (canvasId && !state.internal.isSecondary) {
14239
- const canvasTarget = new webgpu.CanvasTarget(canvas);
14328
+ const canvasTarget = renderer.getCanvasTarget?.();
14240
14329
  const unregisterPrimary = registerPrimary(canvasId, renderer, store);
14241
14330
  state.set((prev) => ({
14242
14331
  internal: {
@@ -14350,7 +14439,7 @@ function createRoot(canvas) {
14350
14439
  const handleXRFrame = (timestamp, _frame) => {
14351
14440
  const state2 = store.getState();
14352
14441
  if (state2.frameloop === "never") return;
14353
- advance(timestamp);
14442
+ advance(timestamp, true, state2);
14354
14443
  };
14355
14444
  const actualRenderer = state.internal.actualRenderer;
14356
14445
  const handleSessionChange = () => {
@@ -14358,7 +14447,7 @@ function createRoot(canvas) {
14358
14447
  const renderer2 = state2.internal.actualRenderer;
14359
14448
  actualRenderer.xr.enabled = actualRenderer.xr.isPresenting;
14360
14449
  renderer2.xr.setAnimationLoop(renderer2.xr.isPresenting ? handleXRFrame : null);
14361
- if (!renderer2.xr.isPresenting) invalidate();
14450
+ if (!renderer2.xr.isPresenting) invalidate(state2);
14362
14451
  };
14363
14452
  const xr = {
14364
14453
  connect() {
@@ -14440,14 +14529,23 @@ function createRoot(canvas) {
14440
14529
  const newRootId = canvasId || scheduler$1.generateRootId();
14441
14530
  const unregisterRoot = scheduler$1.registerRoot(newRootId, {
14442
14531
  getState: () => store.getState(),
14443
- 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
14444
14537
  });
14445
14538
  const unregisterCanvasTarget = scheduler$1.register(
14446
14539
  () => {
14447
14540
  const state2 = store.getState();
14448
- if (state2.internal.isMultiCanvas && state2.internal.canvasTarget) {
14541
+ const canvasTarget = state2.internal.canvasTarget;
14542
+ if (canvasTarget) {
14449
14543
  const renderer2 = state2.internal.actualRenderer;
14450
- 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
+ }
14451
14549
  }
14452
14550
  },
14453
14551
  {
@@ -14503,7 +14601,8 @@ function createRoot(canvas) {
14503
14601
  const userHandlesRender = scheduler$1.hasUserJobsInPhase("render", newRootId);
14504
14602
  if (userHandlesRender || state2.internal.priority) return;
14505
14603
  try {
14506
- if (state2.renderPipeline?.render) state2.renderPipeline.render();
14604
+ const renderOverride = state2.internal.renderOverride;
14605
+ if (renderOverride) renderOverride();
14507
14606
  else if (renderer2?.render) renderer2.render(state2.scene, state2.camera);
14508
14607
  } catch (error) {
14509
14608
  state2.setError(error instanceof Error ? error : new Error(String(error)));
@@ -14516,8 +14615,7 @@ function createRoot(canvas) {
14516
14615
  phase: "render",
14517
14616
  system: true,
14518
14617
  // Internal flag: this is a system job, not user-controlled
14519
- // Apply scheduler config for render ordering and rate limiting
14520
- ...schedulerConfig?.after && { after: schedulerConfig.after },
14618
+ // FPS throttles only the default render job; Canvas ordering belongs to the root.
14521
14619
  ...schedulerConfig?.fps && { fps: schedulerConfig.fps }
14522
14620
  }
14523
14621
  );
@@ -14536,8 +14634,22 @@ function createRoot(canvas) {
14536
14634
  scheduler: scheduler$1
14537
14635
  }
14538
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
+ }
14539
14648
  }
14540
- scheduler$1.frameloop = frameloop;
14649
+ lastConfiguredProps.schedulerBefore = schedulerConfig?.before;
14650
+ lastConfiguredProps.schedulerAfter = schedulerConfig?.after;
14651
+ lastConfiguredProps.schedulerOrder = schedulerConfig?.order;
14652
+ attachRootExtensions(store);
14541
14653
  onCreated = onCreatedCallback;
14542
14654
  configured = true;
14543
14655
  resolve();
@@ -14579,18 +14691,24 @@ function unmountComponentAtNode(canvas, callback) {
14579
14691
  const fiber = root?.fiber;
14580
14692
  if (fiber) {
14581
14693
  const state = root?.store.getState();
14582
- 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
+ }
14583
14702
  reconciler.updateContainer(null, fiber, null, () => {
14703
+ if (root?.store) detachRootExtensions(root.store);
14584
14704
  if (state) {
14585
14705
  setTimeout(() => {
14586
14706
  try {
14587
14707
  const renderer = state.internal.actualRenderer;
14588
- const unregisterRoot = state.internal.unregisterRoot;
14589
- if (unregisterRoot) unregisterRoot();
14590
14708
  const unregisterPrimary = state.internal.unregisterPrimary;
14591
14709
  if (unregisterPrimary) unregisterPrimary();
14592
14710
  const canvasTarget = state.internal.canvasTarget;
14593
- if (canvasTarget?.dispose) canvasTarget.dispose();
14711
+ if (state.internal.isSecondary && canvasTarget?.dispose) canvasTarget.dispose();
14594
14712
  state.events.disconnect?.();
14595
14713
  cleanupHelperGroup(root.store);
14596
14714
  if (state.isLegacy && renderer) {
@@ -14660,11 +14778,24 @@ function PortalInner({ state = {}, children, container }) {
14660
14778
  }
14661
14779
  };
14662
14780
  }, [portalScene, container, injectScene]);
14781
+ const lastRootState = React.useRef(null);
14663
14782
  const inject = useMutableCallback((rootState, injectState) => {
14783
+ const followed = {};
14784
+ const lastRoot = lastRootState.current;
14785
+ if (lastRoot) {
14786
+ for (const key in rootState) {
14787
+ const field = key;
14788
+ const changed = rootState[field] !== lastRoot[field];
14789
+ const inherited = !(key in rest) && injectState[field] === lastRoot[field];
14790
+ if (changed && inherited) followed[field] = rootState[field];
14791
+ }
14792
+ }
14793
+ lastRootState.current = rootState;
14664
14794
  const resolvedSize = { ...rootState.size, ...injectState.size, ...size };
14665
14795
  let viewport = void 0;
14666
- if (injectState.camera && (size || injectState.size)) {
14667
- const camera = injectState.camera;
14796
+ const portalCamera = followed.camera ?? injectState.camera;
14797
+ if (portalCamera && (size || injectState.size)) {
14798
+ const camera = portalCamera;
14668
14799
  viewport = rootState.viewport.getCurrentViewport(camera, new webgpu.Vector3(), resolvedSize);
14669
14800
  if (camera !== rootState.camera) updateCamera(camera, resolvedSize);
14670
14801
  }
@@ -14673,6 +14804,7 @@ function PortalInner({ state = {}, children, container }) {
14673
14804
  // The intersect consists of the previous root state
14674
14805
  ...rootState,
14675
14806
  ...injectState,
14807
+ ...followed,
14676
14808
  // Portals have their own scene - always a real THREE.Scene (injected if needed)
14677
14809
  scene: portalScene,
14678
14810
  // rootScene always points to the actual THREE.Scene, even inside portals
@@ -14731,16 +14863,11 @@ function setEnvProps(background, scene, defaultScene, texture, sceneProps = {})
14731
14863
  const oldbg = target.background;
14732
14864
  const oldenv = target.environment;
14733
14865
  const oldSceneProps = {
14734
- // @ts-ignore
14735
14866
  backgroundBlurriness: target.backgroundBlurriness,
14736
- // @ts-ignore
14737
14867
  backgroundIntensity: target.backgroundIntensity,
14738
- // @ts-ignore
14739
- backgroundRotation: target.backgroundRotation?.clone?.() ?? [0, 0, 0],
14740
- // @ts-ignore
14868
+ backgroundRotation: target.backgroundRotation.clone(),
14741
14869
  environmentIntensity: target.environmentIntensity,
14742
- // @ts-ignore
14743
- environmentRotation: target.environmentRotation?.clone?.() ?? [0, 0, 0]
14870
+ environmentRotation: target.environmentRotation.clone()
14744
14871
  };
14745
14872
  if (background !== "only") target.environment = texture;
14746
14873
  if (background) target.background = texture;
@@ -14965,38 +15092,6 @@ function parseRendererConfig(rendererProp) {
14965
15092
  return { primaryCanvas: void 0, scheduler: void 0, renderer: rendererProp };
14966
15093
  }
14967
15094
 
14968
- const RESOURCE_KINDS = ["nodes", "uniforms", "buffers", "gpuStorage"];
14969
- const registries = /* @__PURE__ */ new WeakMap();
14970
- function getKindRegistry(store, kind) {
14971
- let registry = registries.get(store);
14972
- if (!registry) {
14973
- registry = {
14974
- nodes: { valid: /* @__PURE__ */ new Set(), staged: /* @__PURE__ */ new Map() },
14975
- uniforms: { valid: /* @__PURE__ */ new Set(), staged: /* @__PURE__ */ new Map() },
14976
- buffers: { valid: /* @__PURE__ */ new Set(), staged: /* @__PURE__ */ new Map() },
14977
- gpuStorage: { valid: /* @__PURE__ */ new Set(), staged: /* @__PURE__ */ new Map() }
14978
- };
14979
- registries.set(store, registry);
14980
- }
14981
- return registry[kind];
14982
- }
14983
- function invalidateResource(store, kind, scopeKey) {
14984
- const registry = getKindRegistry(store, kind);
14985
- {
14986
- registry.valid.clear();
14987
- registry.staged.clear();
14988
- }
14989
- }
14990
- function invalidateAllResources(store) {
14991
- for (const kind of RESOURCE_KINDS) invalidateResource(store, kind);
14992
- }
14993
-
14994
- function clearHmrCaches(store) {
14995
- const primary = store.getState().primaryStore ?? store;
14996
- invalidateAllResources(primary);
14997
- primary.setState((state) => ({ _hmrVersion: state._hmrVersion + 1 }));
14998
- }
14999
-
15000
15095
  function CanvasImpl({
15001
15096
  ref,
15002
15097
  children,
@@ -15034,9 +15129,9 @@ function CanvasImpl({
15034
15129
  React__namespace.useMemo(() => extend(THREE), []);
15035
15130
  const Bridge = useBridge();
15036
15131
  const backgroundProps = React__namespace.useMemo(() => parseBackground(background), [background]);
15037
- const hasInitialSizeRef = React__namespace.useRef(false);
15132
+ const [hasInitialSize, setHasInitialSize] = React__namespace.useState(false);
15038
15133
  const measureConfig = React__namespace.useMemo(() => {
15039
- if (!hasInitialSizeRef.current) {
15134
+ if (!hasInitialSize) {
15040
15135
  return {
15041
15136
  ...resize,
15042
15137
  scroll: resize?.scroll ?? true,
@@ -15048,7 +15143,7 @@ function CanvasImpl({
15048
15143
  debounce: { scroll: 50, resize: 0 },
15049
15144
  ...resize
15050
15145
  };
15051
- }, [resize, hasInitialSizeRef.current]);
15146
+ }, [hasInitialSize, resize]);
15052
15147
  const [containerRef, containerRect] = useMeasure__default(measureConfig);
15053
15148
  const effectiveSize = React__namespace.useMemo(() => {
15054
15149
  let w = width ?? containerRect.width;
@@ -15064,9 +15159,7 @@ function CanvasImpl({
15064
15159
  left: containerRect.left
15065
15160
  };
15066
15161
  }, [width, height, containerRect, forceEven]);
15067
- if (!hasInitialSizeRef.current && effectiveSize.width > 0 && effectiveSize.height > 0) {
15068
- hasInitialSizeRef.current = true;
15069
- }
15162
+ if (!hasInitialSize && effectiveSize.width > 0 && effectiveSize.height > 0) setHasInitialSize(true);
15070
15163
  const canvasRef = React__namespace.useRef(null);
15071
15164
  const divRef = React__namespace.useRef(null);
15072
15165
  React__namespace.useImperativeHandle(ref, () => canvasRef.current);
@@ -15180,6 +15273,7 @@ function CanvasImpl({
15180
15273
  const canvas = canvasRef.current;
15181
15274
  if (canvas) {
15182
15275
  return () => {
15276
+ if (canvas.isConnected) return;
15183
15277
  unmountComponentAtNode(canvas);
15184
15278
  root.current = null;
15185
15279
  };
@@ -15192,7 +15286,7 @@ function CanvasImpl({
15192
15286
  const handleHMR = () => {
15193
15287
  queueMicrotask(() => {
15194
15288
  const rootEntry = _roots.get(canvas);
15195
- if (rootEntry?.store) clearHmrCaches(rootEntry.store);
15289
+ if (rootEntry?.store) notifyRootExtensionsHmr(rootEntry.store);
15196
15290
  });
15197
15291
  };
15198
15292
  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) {
@@ -15310,8 +15404,10 @@ exports.prepare = prepare;
15310
15404
  exports.presetsObj = presetsObj;
15311
15405
  exports.reconciler = reconciler;
15312
15406
  exports.registerPrimary = registerPrimary;
15407
+ exports.registerRootExtension = registerRootExtension;
15313
15408
  exports.removeInteractivity = removeInteractivity;
15314
15409
  exports.resolve = resolve;
15410
+ exports.setRenderOverride = setRenderOverride;
15315
15411
  exports.swapInteractivity = swapInteractivity;
15316
15412
  exports.unmountComponentAtNode = unmountComponentAtNode;
15317
15413
  exports.unregisterPrimary = unregisterPrimary;