@react-three/tsl 10.0.0-canary.777b94d → 10.0.0-canary.908b65b

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
@@ -73,6 +73,13 @@ function withStagedOverlay(store, kind, committed) {
73
73
  }
74
74
  return view;
75
75
  }
76
+ function readWithStaged(store, kind, committed, scope, isLeaf) {
77
+ if (scope === void 0) return withStagedOverlay(store, kind, committed);
78
+ const scopeData = committed[scope];
79
+ const current = scopeData && typeof scopeData === "object" && !isLeaf(scopeData) ? scopeData : {};
80
+ const staged = scope === ROOT_SCOPE ? void 0 : peekStaged(store, kind, scope);
81
+ return staged ? { ...current, ...staged } : current;
82
+ }
76
83
  function flushStagedScope(store, kind, scopeKey, isLeaf) {
77
84
  const registry = getKindRegistry(store, kind);
78
85
  const staged = registry.staged.get(scopeKey);
@@ -177,6 +184,10 @@ function extractTSLValue(value) {
177
184
  if (node.isConstNode) {
178
185
  return node.value;
179
186
  }
187
+ if (node.isVarNode && node.node) {
188
+ const inner = extractTSLValue(node.node);
189
+ return inner === node.node ? value : inner;
190
+ }
180
191
  if ("value" in node) {
181
192
  let extractedValue = node.value;
182
193
  if (typeof node.traverse === "function") {
@@ -312,33 +323,129 @@ function ensureTSLExtension() {
312
323
  });
313
324
  }
314
325
 
326
+ const SCOPE = Symbol("readTracking.scope");
327
+ const LEAF_GUARDS = {
328
+ uniforms: isUniformNode,
329
+ nodes: isTSLNode,
330
+ buffers: isBufferLike,
331
+ gpuStorage: isStorageLike
332
+ };
333
+ function isScope(kind, value) {
334
+ return !!value && typeof value === "object" && !LEAF_GUARDS[kind](value);
335
+ }
336
+ function classify(kind, value) {
337
+ return kind !== "textures" && isScope(kind, value) ? SCOPE : value;
338
+ }
339
+ function resolveContainer(kind, root, scopePath) {
340
+ let container = root;
341
+ for (const key of scopePath) {
342
+ const next = container?.[key];
343
+ container = isScope(kind, next) ? next : void 0;
344
+ }
345
+ return container;
346
+ }
347
+ function observeNow(read, view) {
348
+ if (read.kind === "textures") {
349
+ const map = view("textures");
350
+ if (read.op === "keys") return [...map.keys()];
351
+ return read.op === "has" ? map.has(read.path[0]) : map.get(read.path[0]);
352
+ }
353
+ const root = view(read.kind);
354
+ if (read.op === "keys") return Object.keys(resolveContainer(read.kind, root, read.path) ?? {});
355
+ const container = resolveContainer(read.kind, root, read.path.slice(0, -1));
356
+ const key = read.path[read.path.length - 1];
357
+ if (read.op === "has") return !!container && key in container;
358
+ return classify(read.kind, container?.[key]);
359
+ }
360
+ function sameKeys(a, b) {
361
+ return a.length === b.length && a.every((key, i) => key === b[i]);
362
+ }
363
+ function hasChanged(read, view) {
364
+ const now = observeNow(read, view);
365
+ return read.op === "keys" ? !sameKeys(read.seen, now) : !Object.is(read.seen, now);
366
+ }
367
+ const warned = /* @__PURE__ */ new Set();
368
+ function warnOnce(message) {
369
+ if (typeof process !== "undefined" && process.env.NODE_ENV === "production") return;
370
+ if (warned.has(message)) return;
371
+ warned.add(message);
372
+ console.warn(message);
373
+ }
374
+ function describeRead(kind, path) {
375
+ if (kind === "textures") return `textures.get('${path[0]}')`;
376
+ const scopes = path.slice(0, -1).map((key) => `.scope('${key}')`);
377
+ return `${kind}${scopes.join("")}.${path[path.length - 1] ?? ""}`;
378
+ }
379
+ function createReadTracker(hookName) {
380
+ const tracker = {
381
+ reads: [],
382
+ closed: false,
383
+ stale: false,
384
+ observe: (read) => {
385
+ if (tracker.closed) {
386
+ warnOnce(
387
+ `[${hookName}] ${describeRead(read.kind, read.path)} was read after the creator returned, probably inside Fn(). That read is not tracked, so replacing the resource will not rebuild this graph. Read the resource in the creator and close over it in Fn.`
388
+ );
389
+ return;
390
+ }
391
+ tracker.reads.push(read);
392
+ }
393
+ };
394
+ return tracker;
395
+ }
396
+ function isTrackerStale(tracker, view) {
397
+ if (!tracker.stale) tracker.stale = tracker.reads.some((read) => hasChanged(read, view));
398
+ return tracker.stale;
399
+ }
400
+ function warnMissingReads(hookName) {
401
+ return {
402
+ nested: false,
403
+ observe: (read) => {
404
+ if (read.op !== "get" || read.seen !== void 0) return;
405
+ warnOnce(
406
+ `[${hookName}] The creator read ${describeRead(read.kind, read.path)}, which does not exist yet. ${hookName} creators run once per generation, so this one will not re-run when it appears. Register it earlier (above this hook, or higher in the tree), or call rebuild* once it exists. To probe on purpose, use .has().`
407
+ );
408
+ }
409
+ };
410
+ }
411
+
315
412
  var __defProp = Object.defineProperty;
316
413
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
317
414
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
318
- var _a, _b;
415
+ var _a, _b, _c, _d;
319
416
  const INTERNAL_DATA = Symbol("ScopedStore.data");
320
417
  const INTERNAL_IS_LEAF = Symbol("ScopedStore.isLeaf");
321
- _b = INTERNAL_DATA, _a = INTERNAL_IS_LEAF;
418
+ const INTERNAL_READS = Symbol("ScopedStore.reads");
419
+ const INTERNAL_CHILDREN = Symbol("ScopedStore.children");
420
+ _d = INTERNAL_DATA, _c = INTERNAL_IS_LEAF, _b = INTERNAL_READS, _a = INTERNAL_CHILDREN;
322
421
  const _ScopedStore = class _ScopedStore {
323
- constructor(data, isLeaf) {
422
+ constructor(data, isLeaf, reads) {
423
+ /** @internal */
424
+ __publicField(this, _d);
425
+ /** @internal */
426
+ __publicField(this, _c);
427
+ /** @internal */
324
428
  __publicField(this, _b);
429
+ /** @internal */
325
430
  __publicField(this, _a);
326
431
  this[INTERNAL_DATA] = data;
327
432
  this[INTERNAL_IS_LEAF] = isLeaf;
433
+ this[INTERNAL_READS] = reads;
328
434
  return new Proxy(this, {
329
435
  get(target, prop, receiver) {
330
436
  if (typeof prop === "string") {
331
437
  if (prop === "scope" || prop === "has" || prop === "keys") {
332
438
  return Reflect.get(target, prop, receiver);
333
439
  }
334
- return target[INTERNAL_DATA][prop];
440
+ return readEntry(target, prop);
335
441
  }
336
442
  return Reflect.get(target, prop, receiver);
337
443
  },
338
444
  has(target, prop) {
339
- return typeof prop === "string" ? prop in target[INTERNAL_DATA] : Reflect.has(target, prop);
445
+ return typeof prop === "string" ? target.has(prop) : Reflect.has(target, prop);
340
446
  },
341
447
  ownKeys(target) {
448
+ observeKeys(target);
342
449
  return Reflect.ownKeys(target[INTERNAL_DATA]);
343
450
  },
344
451
  getOwnPropertyDescriptor(target, prop) {
@@ -360,54 +467,152 @@ const _ScopedStore = class _ScopedStore {
360
467
  scope(key) {
361
468
  const value = this[INTERNAL_DATA][key];
362
469
  const isLeaf = this[INTERNAL_IS_LEAF];
470
+ const reads = this[INTERNAL_READS];
363
471
  const scope = value && typeof value === "object" && !isLeaf(value) ? value : {};
364
- return new _ScopedStore(scope, isLeaf);
472
+ const child = reads ? { ...reads, path: [...reads.path, key] } : void 0;
473
+ return new _ScopedStore(scope, isLeaf, child);
365
474
  }
366
475
  /**
367
476
  * Check if a key exists in the store.
368
477
  */
369
478
  has(key) {
370
- return key in this[INTERNAL_DATA];
479
+ const found = key in this[INTERNAL_DATA];
480
+ const reads = this[INTERNAL_READS];
481
+ reads?.observe({ kind: reads.kind, op: "has", path: [...reads.path, key], seen: found });
482
+ return found;
371
483
  }
372
484
  /**
373
485
  * Get all keys in the store.
374
486
  */
375
487
  keys() {
488
+ observeKeys(this);
376
489
  return Object.keys(this[INTERNAL_DATA]);
377
490
  }
378
491
  };
379
492
  let ScopedStore = _ScopedStore;
493
+ function readEntry(target, key) {
494
+ const value = target[INTERNAL_DATA][key];
495
+ const reads = target[INTERNAL_READS];
496
+ if (!reads) return value;
497
+ const isLeaf = target[INTERNAL_IS_LEAF];
498
+ const isScope = !!value && typeof value === "object" && !isLeaf(value);
499
+ const path = [...reads.path, key];
500
+ reads.observe({ kind: reads.kind, op: "get", path, seen: isScope ? SCOPE : value });
501
+ if (!isScope || !reads.nested) return value;
502
+ const children = target[INTERNAL_CHILDREN] ?? (target[INTERNAL_CHILDREN] = /* @__PURE__ */ new Map());
503
+ let child = children.get(key);
504
+ if (!child) {
505
+ child = new ScopedStore(value, isLeaf, { ...reads, path });
506
+ children.set(key, child);
507
+ }
508
+ return child;
509
+ }
510
+ function observeKeys(target) {
511
+ const reads = target[INTERNAL_READS];
512
+ reads?.observe({ kind: reads.kind, op: "keys", path: reads.path, seen: Object.keys(target[INTERNAL_DATA]) });
513
+ }
380
514
  function createScopedStore(data, isLeaf) {
381
515
  return new ScopedStore(data, isLeaf);
382
516
  }
383
- function createLazyCreatorState(state, store) {
517
+ function observeTextures(map, observe) {
518
+ const observeKeys2 = () => observe({ kind: "textures", op: "keys", path: [], seen: [...map.keys()] });
519
+ const observeAll = () => {
520
+ observeKeys2();
521
+ for (const [url, texture] of map) observe({ kind: "textures", op: "get", path: [url], seen: texture });
522
+ };
523
+ return new Proxy(map, {
524
+ get(target, prop) {
525
+ switch (prop) {
526
+ case "get":
527
+ return (url) => {
528
+ const texture = target.get(url);
529
+ observe({ kind: "textures", op: "get", path: [url], seen: texture });
530
+ return texture;
531
+ };
532
+ case "has":
533
+ return (url) => {
534
+ const found = target.has(url);
535
+ observe({ kind: "textures", op: "has", path: [url], seen: found });
536
+ return found;
537
+ };
538
+ case "size":
539
+ observeKeys2();
540
+ return target.size;
541
+ case "keys":
542
+ return () => {
543
+ observeKeys2();
544
+ return target.keys();
545
+ };
546
+ case "values":
547
+ return () => {
548
+ observeAll();
549
+ return target.values();
550
+ };
551
+ case "entries":
552
+ case Symbol.iterator:
553
+ return () => {
554
+ observeAll();
555
+ return target.entries();
556
+ };
557
+ case "forEach":
558
+ return (callback, thisArg) => {
559
+ observeAll();
560
+ target.forEach(callback, thisArg);
561
+ };
562
+ }
563
+ const value = Reflect.get(target, prop, target);
564
+ return typeof value === "function" ? value.bind(target) : value;
565
+ }
566
+ });
567
+ }
568
+ function createResourceView(primary, local = primary) {
569
+ return ((kind) => kind === "textures" ? extension.getTextureView(local) : withStagedOverlay(primary, kind, primary.getState()[kind]));
570
+ }
571
+ function createLazyCreatorState(state, store, options = {}) {
572
+ const { reads } = options;
573
+ const view = options.view ?? (store ? createResourceView(store) : void 0);
384
574
  let _uniforms = null;
385
575
  let _nodes = null;
386
576
  let _buffers = null;
387
577
  let _gpuStorage = null;
388
- const view = (kind) => store ? withStagedOverlay(store, kind, state[kind]) : state[kind];
389
- return Object.create(state, {
578
+ let _textures = null;
579
+ const read = (kind) => view ? view(kind) : state[kind];
580
+ const wrap = (kind, isLeaf) => new ScopedStore(
581
+ read(kind),
582
+ isLeaf,
583
+ reads && { ...reads, kind, path: [] }
584
+ );
585
+ const properties = {
390
586
  uniforms: {
391
587
  get() {
392
- return _uniforms ?? (_uniforms = createScopedStore(view("uniforms"), isUniformNode));
588
+ return _uniforms ?? (_uniforms = wrap("uniforms", isUniformNode));
393
589
  }
394
590
  },
395
591
  nodes: {
396
592
  get() {
397
- return _nodes ?? (_nodes = createScopedStore(view("nodes"), isTSLNode));
593
+ return _nodes ?? (_nodes = wrap("nodes", isTSLNode));
398
594
  }
399
595
  },
400
596
  buffers: {
401
597
  get() {
402
- return _buffers ?? (_buffers = createScopedStore(view("buffers"), isBufferLike));
598
+ return _buffers ?? (_buffers = wrap("buffers", isBufferLike));
403
599
  }
404
600
  },
405
601
  gpuStorage: {
406
602
  get() {
407
- return _gpuStorage ?? (_gpuStorage = createScopedStore(view("gpuStorage"), isStorageLike));
603
+ return _gpuStorage ?? (_gpuStorage = wrap("gpuStorage", isStorageLike));
408
604
  }
409
605
  }
410
- });
606
+ };
607
+ if (options.view) {
608
+ properties.textures = {
609
+ get() {
610
+ const textures = options.view("textures");
611
+ return _textures ?? (_textures = reads ? observeTextures(textures, reads.observe) : textures);
612
+ }
613
+ };
614
+ }
615
+ return Object.create(state, properties);
411
616
  }
412
617
 
413
618
  function resolvePrimary(local) {
@@ -483,7 +688,7 @@ function useUniforms(creatorOrScope, scope) {
483
688
  const processedInput = React.useMemo(() => {
484
689
  let raw = creatorOrScope;
485
690
  if (typeof creatorOrScope === "function") {
486
- const wrappedState = createLazyCreatorState(store.getState(), store);
691
+ const wrappedState = createLazyCreatorState(store.getState(), store, { reads: warnMissingReads("useUniforms") });
487
692
  raw = creatorOrScope(wrappedState);
488
693
  }
489
694
  if (raw && typeof raw === "object" && !Array.isArray(raw)) {
@@ -506,8 +711,9 @@ function useUniforms(creatorOrScope, scope) {
506
711
  input: memoizedInput,
507
712
  create: () => {
508
713
  if (isReader) return {};
509
- if (typeof processedInput.candidates !== "object" || processedInput.candidates === null)
714
+ if (typeof processedInput.candidates !== "object" || processedInput.candidates === null) {
510
715
  throw new Error("Invalid uniform input");
716
+ }
511
717
  return processedInput.candidates;
512
718
  },
513
719
  prepare: (name, candidate) => createUniform(name, candidate, scope),
@@ -525,13 +731,7 @@ function useUniforms(creatorOrScope, scope) {
525
731
  if (!equals) existing.value = newVal;
526
732
  }
527
733
  });
528
- let uniforms = created;
529
- if (memoizedInput === void 0) {
530
- uniforms = storeUniforms;
531
- } else if (typeof memoizedInput === "string") {
532
- const scopeData = storeUniforms[memoizedInput];
533
- uniforms = scopeData && !isUniformNode(scopeData) ? scopeData : {};
534
- }
734
+ const uniforms = isReader ? readWithStaged(store, "uniforms", storeUniforms, memoizedInput, isUniformNode) : created;
535
735
  return { ...uniforms, removeUniforms, clearUniforms, rebuildUniforms };
536
736
  }
537
737
  function rebuildAllUniforms(store, scope) {
@@ -565,18 +765,20 @@ function isSameThreeType(a, b) {
565
765
  const uniformOf = tsl.uniform;
566
766
  function useUniform(name, value) {
567
767
  const store = usePrimaryStore();
768
+ const memoizedInput = useCompareMemoize(value === void 0 ? name : { [name]: normalizeValue(value) });
568
769
  const registered = useScopedResource({
569
770
  store,
570
771
  kind: "uniforms",
571
772
  scope: void 0,
572
773
  isLeaf: isUniformNode,
573
- input: name,
774
+ input: memoizedInput,
574
775
  create: () => value === void 0 ? {} : { [name]: value },
575
776
  prepare: createNamedUniform,
576
777
  reconcile: (existing2) => {
577
- if (value !== void 0 && !isTSLNode(value) && !isUniformNode(value)) {
578
- existing2.value = typeof value === "string" ? new THREE.Color(value) : value;
579
- }
778
+ if (value === void 0 || isUniformNode(value)) return;
779
+ const next = normalizeValue(value);
780
+ if (isTSLNode(next)) return;
781
+ existing2.value = next;
580
782
  }
581
783
  });
582
784
  if (registered[name]) return registered[name];
@@ -586,6 +788,9 @@ function useUniform(name, value) {
586
788
  `[useUniform] Uniform "${name}" not found. Create it first with: useUniform('${name}', initialValue)`
587
789
  );
588
790
  }
791
+ function normalizeValue(value) {
792
+ return typeof value === "string" ? new THREE.Color(value) : vectorize(value);
793
+ }
589
794
  function createNamedUniform(name, value) {
590
795
  let node;
591
796
  if (isUniformNode(value)) {
@@ -626,7 +831,16 @@ function useNodes(creatorOrScope, scope) {
626
831
  isLeaf: isTSLNode,
627
832
  create: () => {
628
833
  if (isReader) return {};
629
- return creatorOrScope(createLazyCreatorState(store.getState(), store));
834
+ const nodes2 = creatorOrScope(
835
+ createLazyCreatorState(store.getState(), store, { reads: warnMissingReads("useNodes") })
836
+ );
837
+ if (nodes2 == null) {
838
+ warnOnce(
839
+ "[useNodes] The creator returned nothing, so nothing was registered. useNodes registers the nodes its creator returns. To put a node onto a Three object (scene.fogNode = ...), use useLocalNodes and return a function that assigns it: it runs after commit and may return a cleanup."
840
+ );
841
+ return {};
842
+ }
843
+ return nodes2;
630
844
  },
631
845
  prepare: (name, node) => {
632
846
  const setName = Reflect.get(node, "setName");
@@ -634,28 +848,106 @@ function useNodes(creatorOrScope, scope) {
634
848
  return node;
635
849
  }
636
850
  });
637
- let nodes = created;
638
- if (creatorOrScope === void 0) {
639
- nodes = storeNodes;
640
- } else if (typeof creatorOrScope === "string") {
641
- const scopeData = storeNodes[creatorOrScope];
642
- nodes = scopeData && !isTSLNode(scopeData) ? scopeData : {};
643
- }
851
+ const nodes = isReader ? readWithStaged(store, "nodes", storeNodes, creatorOrScope, isTSLNode) : created;
644
852
  return { ...nodes, removeNodes, clearNodes, rebuildNodes };
645
853
  }
646
854
  function rebuildAllNodes(store, scope) {
647
855
  rebuildResource(store, "nodes", scope);
648
856
  }
649
- function useLocalNodes(creator) {
857
+ function areDepsEqual(next, prev) {
858
+ if (next.length !== prev.length) return false;
859
+ for (let i = 0; i < next.length; i++) if (!Object.is(next[i], prev[i])) return false;
860
+ return true;
861
+ }
862
+ function useDependencyToken(deps) {
863
+ const previous = React.useRef(null);
864
+ const record = previous.current;
865
+ if (typeof process !== "undefined" && process.env.NODE_ENV !== "production" && record) {
866
+ const hadDeps = record.deps !== void 0;
867
+ const hasDeps = deps !== void 0;
868
+ if (hadDeps !== hasDeps) {
869
+ console.warn(
870
+ `[useLocalNodes] The dependency array was ${hasDeps ? "added" : "omitted"} between renders. Pass an array on every render or on none; the mode must not change for a mounted component.`
871
+ );
872
+ } else if (hasDeps && record.deps.length !== deps.length) {
873
+ console.warn(
874
+ `[useLocalNodes] The dependency array length changed between renders (${record.deps.length} \u2192 ${deps.length}). Declare a fixed-length list; conditional dependencies belong inside the array as values.`
875
+ );
876
+ }
877
+ }
878
+ if (deps === void 0) {
879
+ previous.current = { deps: void 0, token: {} };
880
+ } else if (!record || record.deps === void 0 || !areDepsEqual(deps, record.deps)) {
881
+ previous.current = { deps, token: {} };
882
+ } else if (record.deps !== deps) {
883
+ previous.current = { deps, token: record.token };
884
+ }
885
+ return previous.current.token;
886
+ }
887
+ function useLocalNodes(creator, deps) {
888
+ const local = extension.useStore();
650
889
  const store = usePrimaryStore();
651
- const uniforms = usePrimaryThree((s) => s.uniforms);
652
- const nodes = usePrimaryThree((s) => s.nodes);
653
- const textures = usePrimaryThree((s) => s.textures);
890
+ const view = React.useMemo(() => createResourceView(store, local), [store, local]);
654
891
  const hmrVersion = usePrimaryThree((s) => s._hmrVersion);
655
- return React.useMemo(() => {
656
- const wrappedState = createLazyCreatorState(store.getState(), store);
657
- return creator(wrappedState);
658
- }, [store, creator, uniforms, nodes, textures, hmrVersion]);
892
+ const depsToken = useDependencyToken(deps);
893
+ const committedReads = React.useRef(null);
894
+ const readsVersion = React.useRef(0);
895
+ const getReadsVersion = () => {
896
+ const tracker2 = committedReads.current;
897
+ if (tracker2 && !tracker2.stale && isTrackerStale(tracker2, view)) readsVersion.current++;
898
+ return readsVersion.current;
899
+ };
900
+ const subscribe = React.useCallback(
901
+ (onChange) => {
902
+ const unsubscribe = store.subscribe(onChange);
903
+ if (local === store) return unsubscribe;
904
+ const unsubscribeLocal = local.subscribe(onChange);
905
+ return () => {
906
+ unsubscribe();
907
+ unsubscribeLocal();
908
+ };
909
+ },
910
+ [store, local]
911
+ );
912
+ const resourceVersion = React.useSyncExternalStore(subscribe, getReadsVersion, getReadsVersion);
913
+ const evaluation = React.useMemo(() => {
914
+ const tracker2 = createReadTracker("useLocalNodes");
915
+ const reads = { observe: tracker2.observe, nested: true };
916
+ const value2 = creator(createLazyCreatorState(local.getState(), store, { reads, view }));
917
+ tracker2.closed = true;
918
+ return { value: value2, tracker: tracker2 };
919
+ }, [view, hmrVersion, depsToken, resourceVersion]);
920
+ const { value, tracker } = evaluation;
921
+ const installing = typeof value === "function";
922
+ useModeDiagnostics(value, installing);
923
+ useIsomorphicLayoutEffect(() => {
924
+ committedReads.current = tracker;
925
+ if (!installing) return;
926
+ tracker.closed = false;
927
+ let cleanup;
928
+ try {
929
+ cleanup = value();
930
+ } finally {
931
+ tracker.closed = true;
932
+ }
933
+ return typeof cleanup === "function" ? cleanup : void 0;
934
+ }, [evaluation]);
935
+ return installing ? void 0 : value;
936
+ }
937
+ function useModeDiagnostics(value, installing) {
938
+ const previous = React.useRef(null);
939
+ if (typeof process !== "undefined" && process.env.NODE_ENV === "production") return;
940
+ if (value === void 0 || value === null) {
941
+ warnOnce(
942
+ "[useLocalNodes] The creator returned nothing. To put a node onto a Three object (scene.fogNode = ...), build it in the creator and return a function that assigns it: that function runs after commit and may return a cleanup. Assigning inside the creator runs during render, which React may discard or repeat."
943
+ );
944
+ }
945
+ if (previous.current !== null && previous.current !== installing) {
946
+ warnOnce(
947
+ "[useLocalNodes] The creator switched between returning a record and returning an install function. Keep one form for a mounted component."
948
+ );
949
+ }
950
+ previous.current = installing;
659
951
  }
660
952
 
661
953
  const disposeBuffer = (buffer) => {
@@ -701,7 +993,9 @@ function useBuffers(creatorOrScope, scope) {
701
993
  isLeaf: isBufferLike,
702
994
  create: () => {
703
995
  if (isReader) return {};
704
- return creatorOrScope(createLazyCreatorState(store.getState(), store));
996
+ return creatorOrScope(
997
+ createLazyCreatorState(store.getState(), store, { reads: warnMissingReads("useBuffers") })
998
+ );
705
999
  },
706
1000
  prepare: (name, buffer) => {
707
1001
  const setName = Reflect.get(buffer, "setName");
@@ -709,13 +1003,7 @@ function useBuffers(creatorOrScope, scope) {
709
1003
  return buffer;
710
1004
  }
711
1005
  });
712
- let buffers = created;
713
- if (creatorOrScope === void 0) {
714
- buffers = storeBuffers;
715
- } else if (typeof creatorOrScope === "string") {
716
- const scopeData = storeBuffers[creatorOrScope];
717
- buffers = scopeData && !isBufferLike(scopeData) ? scopeData : {};
718
- }
1006
+ const buffers = isReader ? readWithStaged(store, "buffers", storeBuffers, creatorOrScope, isBufferLike) : created;
719
1007
  return { ...buffers, removeBuffers, clearBuffers, rebuildBuffers, disposeBuffers };
720
1008
  }
721
1009
  function rebuildAllBuffers(store, scope) {
@@ -765,7 +1053,9 @@ function useGPUStorage(creatorOrScope, scope) {
765
1053
  isLeaf: isStorageLike,
766
1054
  create: () => {
767
1055
  if (isReader) return {};
768
- return creatorOrScope(createLazyCreatorState(store.getState(), store));
1056
+ return creatorOrScope(
1057
+ createLazyCreatorState(store.getState(), store, { reads: warnMissingReads("useGPUStorage") })
1058
+ );
769
1059
  },
770
1060
  prepare: (name, storage) => {
771
1061
  const label = scopedNodeName(scope, name);
@@ -777,13 +1067,7 @@ function useGPUStorage(creatorOrScope, scope) {
777
1067
  return storage;
778
1068
  }
779
1069
  });
780
- let gpuStorage = created;
781
- if (creatorOrScope === void 0) {
782
- gpuStorage = storeStorage;
783
- } else if (typeof creatorOrScope === "string") {
784
- const scopeData = storeStorage[creatorOrScope];
785
- gpuStorage = scopeData && !isStorageLike(scopeData) ? scopeData : {};
786
- }
1070
+ const gpuStorage = isReader ? readWithStaged(store, "gpuStorage", storeStorage, creatorOrScope, isStorageLike) : created;
787
1071
  return {
788
1072
  ...gpuStorage,
789
1073
  removeStorage,