@react-three/tsl 10.0.0-canary.f81281f → 10.0.0-canary.fbc659b

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.mjs CHANGED
@@ -1,9 +1,9 @@
1
- import { registerRootExtension, useStore, useThree, setRenderOverride } from '@react-three/fiber/extension';
1
+ import { registerRootExtension, getTextureView, useStore, useThree, setRenderOverride } from '@react-three/fiber/extension';
2
2
  import { uniform, pass } from 'three/tsl';
3
3
  import * as THREE from 'three/webgpu';
4
4
  import { Color } from 'three/webgpu';
5
5
  import * as React from 'react';
6
- import { useMemo, useRef, useCallback, useState, useEffect, useLayoutEffect } from 'react';
6
+ import { useMemo, useRef, useCallback, useSyncExternalStore, useState, useEffect, useLayoutEffect } from 'react';
7
7
  import { dequal } from 'dequal/lite';
8
8
  import 'zustand/shallow';
9
9
 
@@ -162,6 +162,10 @@ function extractTSLValue(value) {
162
162
  if (node.isConstNode) {
163
163
  return node.value;
164
164
  }
165
+ if (node.isVarNode && node.node) {
166
+ const inner = extractTSLValue(node.node);
167
+ return inner === node.node ? value : inner;
168
+ }
165
169
  if ("value" in node) {
166
170
  let extractedValue = node.value;
167
171
  if (typeof node.traverse === "function") {
@@ -297,33 +301,129 @@ function ensureTSLExtension() {
297
301
  });
298
302
  }
299
303
 
304
+ const SCOPE = Symbol("readTracking.scope");
305
+ const LEAF_GUARDS = {
306
+ uniforms: isUniformNode,
307
+ nodes: isTSLNode,
308
+ buffers: isBufferLike,
309
+ gpuStorage: isStorageLike
310
+ };
311
+ function isScope(kind, value) {
312
+ return !!value && typeof value === "object" && !LEAF_GUARDS[kind](value);
313
+ }
314
+ function classify(kind, value) {
315
+ return kind !== "textures" && isScope(kind, value) ? SCOPE : value;
316
+ }
317
+ function resolveContainer(kind, root, scopePath) {
318
+ let container = root;
319
+ for (const key of scopePath) {
320
+ const next = container?.[key];
321
+ container = isScope(kind, next) ? next : void 0;
322
+ }
323
+ return container;
324
+ }
325
+ function observeNow(read, view) {
326
+ if (read.kind === "textures") {
327
+ const map = view("textures");
328
+ if (read.op === "keys") return [...map.keys()];
329
+ return read.op === "has" ? map.has(read.path[0]) : map.get(read.path[0]);
330
+ }
331
+ const root = view(read.kind);
332
+ if (read.op === "keys") return Object.keys(resolveContainer(read.kind, root, read.path) ?? {});
333
+ const container = resolveContainer(read.kind, root, read.path.slice(0, -1));
334
+ const key = read.path[read.path.length - 1];
335
+ if (read.op === "has") return !!container && key in container;
336
+ return classify(read.kind, container?.[key]);
337
+ }
338
+ function sameKeys(a, b) {
339
+ return a.length === b.length && a.every((key, i) => key === b[i]);
340
+ }
341
+ function hasChanged(read, view) {
342
+ const now = observeNow(read, view);
343
+ return read.op === "keys" ? !sameKeys(read.seen, now) : !Object.is(read.seen, now);
344
+ }
345
+ const warned = /* @__PURE__ */ new Set();
346
+ function warnOnce(message) {
347
+ if (typeof process !== "undefined" && process.env.NODE_ENV === "production") return;
348
+ if (warned.has(message)) return;
349
+ warned.add(message);
350
+ console.warn(message);
351
+ }
352
+ function describeRead(kind, path) {
353
+ if (kind === "textures") return `textures.get('${path[0]}')`;
354
+ const scopes = path.slice(0, -1).map((key) => `.scope('${key}')`);
355
+ return `${kind}${scopes.join("")}.${path[path.length - 1] ?? ""}`;
356
+ }
357
+ function createReadTracker(hookName) {
358
+ const tracker = {
359
+ reads: [],
360
+ closed: false,
361
+ stale: false,
362
+ observe: (read) => {
363
+ if (tracker.closed) {
364
+ warnOnce(
365
+ `[${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.`
366
+ );
367
+ return;
368
+ }
369
+ tracker.reads.push(read);
370
+ }
371
+ };
372
+ return tracker;
373
+ }
374
+ function isTrackerStale(tracker, view) {
375
+ if (!tracker.stale) tracker.stale = tracker.reads.some((read) => hasChanged(read, view));
376
+ return tracker.stale;
377
+ }
378
+ function warnMissingReads(hookName) {
379
+ return {
380
+ nested: false,
381
+ observe: (read) => {
382
+ if (read.op !== "get" || read.seen !== void 0) return;
383
+ warnOnce(
384
+ `[${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().`
385
+ );
386
+ }
387
+ };
388
+ }
389
+
300
390
  var __defProp = Object.defineProperty;
301
391
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
302
392
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
303
- var _a, _b;
393
+ var _a, _b, _c, _d;
304
394
  const INTERNAL_DATA = Symbol("ScopedStore.data");
305
395
  const INTERNAL_IS_LEAF = Symbol("ScopedStore.isLeaf");
306
- _b = INTERNAL_DATA, _a = INTERNAL_IS_LEAF;
396
+ const INTERNAL_READS = Symbol("ScopedStore.reads");
397
+ const INTERNAL_CHILDREN = Symbol("ScopedStore.children");
398
+ _d = INTERNAL_DATA, _c = INTERNAL_IS_LEAF, _b = INTERNAL_READS, _a = INTERNAL_CHILDREN;
307
399
  const _ScopedStore = class _ScopedStore {
308
- constructor(data, isLeaf) {
400
+ constructor(data, isLeaf, reads) {
401
+ /** @internal */
402
+ __publicField(this, _d);
403
+ /** @internal */
404
+ __publicField(this, _c);
405
+ /** @internal */
309
406
  __publicField(this, _b);
407
+ /** @internal */
310
408
  __publicField(this, _a);
311
409
  this[INTERNAL_DATA] = data;
312
410
  this[INTERNAL_IS_LEAF] = isLeaf;
411
+ this[INTERNAL_READS] = reads;
313
412
  return new Proxy(this, {
314
413
  get(target, prop, receiver) {
315
414
  if (typeof prop === "string") {
316
415
  if (prop === "scope" || prop === "has" || prop === "keys") {
317
416
  return Reflect.get(target, prop, receiver);
318
417
  }
319
- return target[INTERNAL_DATA][prop];
418
+ return readEntry(target, prop);
320
419
  }
321
420
  return Reflect.get(target, prop, receiver);
322
421
  },
323
422
  has(target, prop) {
324
- return typeof prop === "string" ? prop in target[INTERNAL_DATA] : Reflect.has(target, prop);
423
+ return typeof prop === "string" ? target.has(prop) : Reflect.has(target, prop);
325
424
  },
326
425
  ownKeys(target) {
426
+ observeKeys(target);
327
427
  return Reflect.ownKeys(target[INTERNAL_DATA]);
328
428
  },
329
429
  getOwnPropertyDescriptor(target, prop) {
@@ -345,54 +445,152 @@ const _ScopedStore = class _ScopedStore {
345
445
  scope(key) {
346
446
  const value = this[INTERNAL_DATA][key];
347
447
  const isLeaf = this[INTERNAL_IS_LEAF];
448
+ const reads = this[INTERNAL_READS];
348
449
  const scope = value && typeof value === "object" && !isLeaf(value) ? value : {};
349
- return new _ScopedStore(scope, isLeaf);
450
+ const child = reads ? { ...reads, path: [...reads.path, key] } : void 0;
451
+ return new _ScopedStore(scope, isLeaf, child);
350
452
  }
351
453
  /**
352
454
  * Check if a key exists in the store.
353
455
  */
354
456
  has(key) {
355
- return key in this[INTERNAL_DATA];
457
+ const found = key in this[INTERNAL_DATA];
458
+ const reads = this[INTERNAL_READS];
459
+ reads?.observe({ kind: reads.kind, op: "has", path: [...reads.path, key], seen: found });
460
+ return found;
356
461
  }
357
462
  /**
358
463
  * Get all keys in the store.
359
464
  */
360
465
  keys() {
466
+ observeKeys(this);
361
467
  return Object.keys(this[INTERNAL_DATA]);
362
468
  }
363
469
  };
364
470
  let ScopedStore = _ScopedStore;
471
+ function readEntry(target, key) {
472
+ const value = target[INTERNAL_DATA][key];
473
+ const reads = target[INTERNAL_READS];
474
+ if (!reads) return value;
475
+ const isLeaf = target[INTERNAL_IS_LEAF];
476
+ const isScope = !!value && typeof value === "object" && !isLeaf(value);
477
+ const path = [...reads.path, key];
478
+ reads.observe({ kind: reads.kind, op: "get", path, seen: isScope ? SCOPE : value });
479
+ if (!isScope || !reads.nested) return value;
480
+ const children = target[INTERNAL_CHILDREN] ?? (target[INTERNAL_CHILDREN] = /* @__PURE__ */ new Map());
481
+ let child = children.get(key);
482
+ if (!child) {
483
+ child = new ScopedStore(value, isLeaf, { ...reads, path });
484
+ children.set(key, child);
485
+ }
486
+ return child;
487
+ }
488
+ function observeKeys(target) {
489
+ const reads = target[INTERNAL_READS];
490
+ reads?.observe({ kind: reads.kind, op: "keys", path: reads.path, seen: Object.keys(target[INTERNAL_DATA]) });
491
+ }
365
492
  function createScopedStore(data, isLeaf) {
366
493
  return new ScopedStore(data, isLeaf);
367
494
  }
368
- function createLazyCreatorState(state, store) {
495
+ function observeTextures(map, observe) {
496
+ const observeKeys2 = () => observe({ kind: "textures", op: "keys", path: [], seen: [...map.keys()] });
497
+ const observeAll = () => {
498
+ observeKeys2();
499
+ for (const [url, texture] of map) observe({ kind: "textures", op: "get", path: [url], seen: texture });
500
+ };
501
+ return new Proxy(map, {
502
+ get(target, prop) {
503
+ switch (prop) {
504
+ case "get":
505
+ return (url) => {
506
+ const texture = target.get(url);
507
+ observe({ kind: "textures", op: "get", path: [url], seen: texture });
508
+ return texture;
509
+ };
510
+ case "has":
511
+ return (url) => {
512
+ const found = target.has(url);
513
+ observe({ kind: "textures", op: "has", path: [url], seen: found });
514
+ return found;
515
+ };
516
+ case "size":
517
+ observeKeys2();
518
+ return target.size;
519
+ case "keys":
520
+ return () => {
521
+ observeKeys2();
522
+ return target.keys();
523
+ };
524
+ case "values":
525
+ return () => {
526
+ observeAll();
527
+ return target.values();
528
+ };
529
+ case "entries":
530
+ case Symbol.iterator:
531
+ return () => {
532
+ observeAll();
533
+ return target.entries();
534
+ };
535
+ case "forEach":
536
+ return (callback, thisArg) => {
537
+ observeAll();
538
+ target.forEach(callback, thisArg);
539
+ };
540
+ }
541
+ const value = Reflect.get(target, prop, target);
542
+ return typeof value === "function" ? value.bind(target) : value;
543
+ }
544
+ });
545
+ }
546
+ function createResourceView(primary, local = primary) {
547
+ return ((kind) => kind === "textures" ? getTextureView(local) : withStagedOverlay(primary, kind, primary.getState()[kind]));
548
+ }
549
+ function createLazyCreatorState(state, store, options = {}) {
550
+ const { reads } = options;
551
+ const view = options.view ?? (store ? createResourceView(store) : void 0);
369
552
  let _uniforms = null;
370
553
  let _nodes = null;
371
554
  let _buffers = null;
372
555
  let _gpuStorage = null;
373
- const view = (kind) => store ? withStagedOverlay(store, kind, state[kind]) : state[kind];
374
- return Object.create(state, {
556
+ let _textures = null;
557
+ const read = (kind) => view ? view(kind) : state[kind];
558
+ const wrap = (kind, isLeaf) => new ScopedStore(
559
+ read(kind),
560
+ isLeaf,
561
+ reads && { ...reads, kind, path: [] }
562
+ );
563
+ const properties = {
375
564
  uniforms: {
376
565
  get() {
377
- return _uniforms ?? (_uniforms = createScopedStore(view("uniforms"), isUniformNode));
566
+ return _uniforms ?? (_uniforms = wrap("uniforms", isUniformNode));
378
567
  }
379
568
  },
380
569
  nodes: {
381
570
  get() {
382
- return _nodes ?? (_nodes = createScopedStore(view("nodes"), isTSLNode));
571
+ return _nodes ?? (_nodes = wrap("nodes", isTSLNode));
383
572
  }
384
573
  },
385
574
  buffers: {
386
575
  get() {
387
- return _buffers ?? (_buffers = createScopedStore(view("buffers"), isBufferLike));
576
+ return _buffers ?? (_buffers = wrap("buffers", isBufferLike));
388
577
  }
389
578
  },
390
579
  gpuStorage: {
391
580
  get() {
392
- return _gpuStorage ?? (_gpuStorage = createScopedStore(view("gpuStorage"), isStorageLike));
581
+ return _gpuStorage ?? (_gpuStorage = wrap("gpuStorage", isStorageLike));
393
582
  }
394
583
  }
395
- });
584
+ };
585
+ if (options.view) {
586
+ properties.textures = {
587
+ get() {
588
+ const textures = options.view("textures");
589
+ return _textures ?? (_textures = reads ? observeTextures(textures, reads.observe) : textures);
590
+ }
591
+ };
592
+ }
593
+ return Object.create(state, properties);
396
594
  }
397
595
 
398
596
  function resolvePrimary(local) {
@@ -468,7 +666,7 @@ function useUniforms(creatorOrScope, scope) {
468
666
  const processedInput = useMemo(() => {
469
667
  let raw = creatorOrScope;
470
668
  if (typeof creatorOrScope === "function") {
471
- const wrappedState = createLazyCreatorState(store.getState(), store);
669
+ const wrappedState = createLazyCreatorState(store.getState(), store, { reads: warnMissingReads("useUniforms") });
472
670
  raw = creatorOrScope(wrappedState);
473
671
  }
474
672
  if (raw && typeof raw === "object" && !Array.isArray(raw)) {
@@ -491,8 +689,9 @@ function useUniforms(creatorOrScope, scope) {
491
689
  input: memoizedInput,
492
690
  create: () => {
493
691
  if (isReader) return {};
494
- if (typeof processedInput.candidates !== "object" || processedInput.candidates === null)
692
+ if (typeof processedInput.candidates !== "object" || processedInput.candidates === null) {
495
693
  throw new Error("Invalid uniform input");
694
+ }
496
695
  return processedInput.candidates;
497
696
  },
498
697
  prepare: (name, candidate) => createUniform(name, candidate, scope),
@@ -550,18 +749,20 @@ function isSameThreeType(a, b) {
550
749
  const uniformOf = uniform;
551
750
  function useUniform(name, value) {
552
751
  const store = usePrimaryStore();
752
+ const memoizedInput = useCompareMemoize(value === void 0 ? name : { [name]: normalizeValue(value) });
553
753
  const registered = useScopedResource({
554
754
  store,
555
755
  kind: "uniforms",
556
756
  scope: void 0,
557
757
  isLeaf: isUniformNode,
558
- input: name,
758
+ input: memoizedInput,
559
759
  create: () => value === void 0 ? {} : { [name]: value },
560
760
  prepare: createNamedUniform,
561
761
  reconcile: (existing2) => {
562
- if (value !== void 0 && !isTSLNode(value) && !isUniformNode(value)) {
563
- existing2.value = typeof value === "string" ? new Color(value) : value;
564
- }
762
+ if (value === void 0 || isUniformNode(value)) return;
763
+ const next = normalizeValue(value);
764
+ if (isTSLNode(next)) return;
765
+ existing2.value = next;
565
766
  }
566
767
  });
567
768
  if (registered[name]) return registered[name];
@@ -571,6 +772,9 @@ function useUniform(name, value) {
571
772
  `[useUniform] Uniform "${name}" not found. Create it first with: useUniform('${name}', initialValue)`
572
773
  );
573
774
  }
775
+ function normalizeValue(value) {
776
+ return typeof value === "string" ? new Color(value) : vectorize(value);
777
+ }
574
778
  function createNamedUniform(name, value) {
575
779
  let node;
576
780
  if (isUniformNode(value)) {
@@ -611,7 +815,16 @@ function useNodes(creatorOrScope, scope) {
611
815
  isLeaf: isTSLNode,
612
816
  create: () => {
613
817
  if (isReader) return {};
614
- return creatorOrScope(createLazyCreatorState(store.getState(), store));
818
+ const nodes2 = creatorOrScope(
819
+ createLazyCreatorState(store.getState(), store, { reads: warnMissingReads("useNodes") })
820
+ );
821
+ if (nodes2 == null) {
822
+ warnOnce(
823
+ "[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."
824
+ );
825
+ return {};
826
+ }
827
+ return nodes2;
615
828
  },
616
829
  prepare: (name, node) => {
617
830
  const setName = Reflect.get(node, "setName");
@@ -631,16 +844,100 @@ function useNodes(creatorOrScope, scope) {
631
844
  function rebuildAllNodes(store, scope) {
632
845
  rebuildResource(store, "nodes", scope);
633
846
  }
634
- function useLocalNodes(creator) {
847
+ function areDepsEqual(next, prev) {
848
+ if (next.length !== prev.length) return false;
849
+ for (let i = 0; i < next.length; i++) if (!Object.is(next[i], prev[i])) return false;
850
+ return true;
851
+ }
852
+ function useDependencyToken(deps) {
853
+ const previous = useRef(null);
854
+ const record = previous.current;
855
+ if (typeof process !== "undefined" && process.env.NODE_ENV !== "production" && record) {
856
+ const hadDeps = record.deps !== void 0;
857
+ const hasDeps = deps !== void 0;
858
+ if (hadDeps !== hasDeps) {
859
+ console.warn(
860
+ `[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.`
861
+ );
862
+ } else if (hasDeps && record.deps.length !== deps.length) {
863
+ console.warn(
864
+ `[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.`
865
+ );
866
+ }
867
+ }
868
+ if (deps === void 0) {
869
+ previous.current = { deps: void 0, token: {} };
870
+ } else if (!record || record.deps === void 0 || !areDepsEqual(deps, record.deps)) {
871
+ previous.current = { deps, token: {} };
872
+ } else if (record.deps !== deps) {
873
+ previous.current = { deps, token: record.token };
874
+ }
875
+ return previous.current.token;
876
+ }
877
+ function useLocalNodes(creator, deps) {
878
+ const local = useStore();
635
879
  const store = usePrimaryStore();
636
- const uniforms = usePrimaryThree((s) => s.uniforms);
637
- const nodes = usePrimaryThree((s) => s.nodes);
638
- const textures = usePrimaryThree((s) => s.textures);
880
+ const view = useMemo(() => createResourceView(store, local), [store, local]);
639
881
  const hmrVersion = usePrimaryThree((s) => s._hmrVersion);
640
- return useMemo(() => {
641
- const wrappedState = createLazyCreatorState(store.getState(), store);
642
- return creator(wrappedState);
643
- }, [store, creator, uniforms, nodes, textures, hmrVersion]);
882
+ const depsToken = useDependencyToken(deps);
883
+ const committedReads = useRef(null);
884
+ const readsVersion = useRef(0);
885
+ const getReadsVersion = () => {
886
+ const tracker2 = committedReads.current;
887
+ if (tracker2 && !tracker2.stale && isTrackerStale(tracker2, view)) readsVersion.current++;
888
+ return readsVersion.current;
889
+ };
890
+ const subscribe = useCallback(
891
+ (onChange) => {
892
+ const unsubscribe = store.subscribe(onChange);
893
+ if (local === store) return unsubscribe;
894
+ const unsubscribeLocal = local.subscribe(onChange);
895
+ return () => {
896
+ unsubscribe();
897
+ unsubscribeLocal();
898
+ };
899
+ },
900
+ [store, local]
901
+ );
902
+ const resourceVersion = useSyncExternalStore(subscribe, getReadsVersion, getReadsVersion);
903
+ const evaluation = useMemo(() => {
904
+ const tracker2 = createReadTracker("useLocalNodes");
905
+ const reads = { observe: tracker2.observe, nested: true };
906
+ const value2 = creator(createLazyCreatorState(local.getState(), store, { reads, view }));
907
+ tracker2.closed = true;
908
+ return { value: value2, tracker: tracker2 };
909
+ }, [view, hmrVersion, depsToken, resourceVersion]);
910
+ const { value, tracker } = evaluation;
911
+ const installing = typeof value === "function";
912
+ useModeDiagnostics(value, installing);
913
+ useIsomorphicLayoutEffect(() => {
914
+ committedReads.current = tracker;
915
+ if (!installing) return;
916
+ tracker.closed = false;
917
+ let cleanup;
918
+ try {
919
+ cleanup = value();
920
+ } finally {
921
+ tracker.closed = true;
922
+ }
923
+ return typeof cleanup === "function" ? cleanup : void 0;
924
+ }, [evaluation]);
925
+ return installing ? void 0 : value;
926
+ }
927
+ function useModeDiagnostics(value, installing) {
928
+ const previous = useRef(null);
929
+ if (typeof process !== "undefined" && process.env.NODE_ENV === "production") return;
930
+ if (value === void 0 || value === null) {
931
+ warnOnce(
932
+ "[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."
933
+ );
934
+ }
935
+ if (previous.current !== null && previous.current !== installing) {
936
+ warnOnce(
937
+ "[useLocalNodes] The creator switched between returning a record and returning an install function. Keep one form for a mounted component."
938
+ );
939
+ }
940
+ previous.current = installing;
644
941
  }
645
942
 
646
943
  const disposeBuffer = (buffer) => {
@@ -686,7 +983,9 @@ function useBuffers(creatorOrScope, scope) {
686
983
  isLeaf: isBufferLike,
687
984
  create: () => {
688
985
  if (isReader) return {};
689
- return creatorOrScope(createLazyCreatorState(store.getState(), store));
986
+ return creatorOrScope(
987
+ createLazyCreatorState(store.getState(), store, { reads: warnMissingReads("useBuffers") })
988
+ );
690
989
  },
691
990
  prepare: (name, buffer) => {
692
991
  const setName = Reflect.get(buffer, "setName");
@@ -750,7 +1049,9 @@ function useGPUStorage(creatorOrScope, scope) {
750
1049
  isLeaf: isStorageLike,
751
1050
  create: () => {
752
1051
  if (isReader) return {};
753
- return creatorOrScope(createLazyCreatorState(store.getState(), store));
1052
+ return creatorOrScope(
1053
+ createLazyCreatorState(store.getState(), store, { reads: warnMissingReads("useGPUStorage") })
1054
+ );
754
1055
  },
755
1056
  prepare: (name, storage) => {
756
1057
  const label = scopedNodeName(scope, name);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@react-three/tsl",
3
- "version": "10.0.0-canary.f81281f",
3
+ "version": "10.0.0-canary.fbc659b",
4
4
  "description": "TSL resource hooks for react-three-fiber: uniforms, nodes, buffers, GPU storage and render pipelines on WebGPU",
5
5
  "keywords": [
6
6
  "react",
@@ -49,14 +49,14 @@
49
49
  },
50
50
  "peerDependencies": {
51
51
  "@react-three/fiber": "^10.0.0-alpha.6",
52
- "react": ">=19.0 <19.3",
52
+ "react": ">=19.0 <19.4",
53
53
  "three": ">=0.185.0"
54
54
  },
55
55
  "devDependencies": {
56
56
  "@types/three": "^0.185.0",
57
57
  "@webgpu/types": "^0.1.64",
58
58
  "three": ">=0.185.0",
59
- "@react-three/fiber": "10.0.0-canary.f81281f"
59
+ "@react-three/fiber": "10.0.0-canary.fbc659b"
60
60
  },
61
61
  "scripts": {
62
62
  "build": "unbuild",
package/readme.md CHANGED
@@ -46,7 +46,7 @@ export const App = () => (
46
46
  | `useBuffers`, `useGPUStorage` | Buffers and storage textures for compute |
47
47
  | `useRenderPipeline` | Post-processing with three's `RenderPipeline` (scene pass, MRT, output node) |
48
48
 
49
- Resources belong to the renderer: a primary canvas, its secondary canvases (`renderer={{ primaryCanvas }}`) and every portal inside them share one set. They live on the primary canvas's `RootState` (`state.uniforms`, `state.nodes`, `state.buffers`, `state.gpuStorage`); secondaries hold the same objects and portals inherit them from their parent (a portal's scene and camera stay its own), so `state.uniforms` reads the same everywhere.
49
+ Resources belong to the renderer: a `<Canvas primary>`, the canvases sharing its renderer (every other WebGPU canvas, or `share="id"`) and every portal inside them share one set. They live on the primary canvas's `RootState` (`state.uniforms`, `state.nodes`, `state.buffers`, `state.gpuStorage`); secondaries hold the same objects and portals inherit them from their parent (a portal's scene and camera stay its own), so `state.uniforms` reads the same everywhere.
50
50
 
51
51
  Works under any Canvas that runs the WebGPU renderer: `@react-three/fiber` with the `renderer` prop, or `@react-three/fiber/webgpu`. It reaches fiber only through the three-free `@react-three/fiber/extension` entry, so it adds no renderer and no second copy of fiber to your bundle.
52
52