@react-three/tsl 10.0.0-canary.bf4b0be → 10.0.0-canary.d0fe0de
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 +331 -31
- package/dist/index.d.cts +120 -50
- package/dist/index.d.mts +120 -50
- package/dist/index.d.ts +120 -50
- package/dist/index.mjs +333 -33
- package/package.json +3 -3
- package/readme.md +1 -1
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
374
|
-
|
|
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 =
|
|
566
|
+
return _uniforms ?? (_uniforms = wrap("uniforms", isUniformNode));
|
|
378
567
|
}
|
|
379
568
|
},
|
|
380
569
|
nodes: {
|
|
381
570
|
get() {
|
|
382
|
-
return _nodes ?? (_nodes =
|
|
571
|
+
return _nodes ?? (_nodes = wrap("nodes", isTSLNode));
|
|
383
572
|
}
|
|
384
573
|
},
|
|
385
574
|
buffers: {
|
|
386
575
|
get() {
|
|
387
|
-
return _buffers ?? (_buffers =
|
|
576
|
+
return _buffers ?? (_buffers = wrap("buffers", isBufferLike));
|
|
388
577
|
}
|
|
389
578
|
},
|
|
390
579
|
gpuStorage: {
|
|
391
580
|
get() {
|
|
392
|
-
return _gpuStorage ?? (_gpuStorage =
|
|
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)) {
|
|
@@ -550,18 +748,20 @@ function isSameThreeType(a, b) {
|
|
|
550
748
|
const uniformOf = uniform;
|
|
551
749
|
function useUniform(name, value) {
|
|
552
750
|
const store = usePrimaryStore();
|
|
751
|
+
const memoizedInput = useCompareMemoize(value === void 0 ? name : { [name]: normalizeValue(value) });
|
|
553
752
|
const registered = useScopedResource({
|
|
554
753
|
store,
|
|
555
754
|
kind: "uniforms",
|
|
556
755
|
scope: void 0,
|
|
557
756
|
isLeaf: isUniformNode,
|
|
558
|
-
input:
|
|
757
|
+
input: memoizedInput,
|
|
559
758
|
create: () => value === void 0 ? {} : { [name]: value },
|
|
560
759
|
prepare: createNamedUniform,
|
|
561
760
|
reconcile: (existing2) => {
|
|
562
|
-
if (value
|
|
563
|
-
|
|
564
|
-
|
|
761
|
+
if (value === void 0 || isUniformNode(value)) return;
|
|
762
|
+
const next = normalizeValue(value);
|
|
763
|
+
if (isTSLNode(next)) return;
|
|
764
|
+
existing2.value = next;
|
|
565
765
|
}
|
|
566
766
|
});
|
|
567
767
|
if (registered[name]) return registered[name];
|
|
@@ -571,6 +771,9 @@ function useUniform(name, value) {
|
|
|
571
771
|
`[useUniform] Uniform "${name}" not found. Create it first with: useUniform('${name}', initialValue)`
|
|
572
772
|
);
|
|
573
773
|
}
|
|
774
|
+
function normalizeValue(value) {
|
|
775
|
+
return typeof value === "string" ? new Color(value) : vectorize(value);
|
|
776
|
+
}
|
|
574
777
|
function createNamedUniform(name, value) {
|
|
575
778
|
let node;
|
|
576
779
|
if (isUniformNode(value)) {
|
|
@@ -611,7 +814,16 @@ function useNodes(creatorOrScope, scope) {
|
|
|
611
814
|
isLeaf: isTSLNode,
|
|
612
815
|
create: () => {
|
|
613
816
|
if (isReader) return {};
|
|
614
|
-
|
|
817
|
+
const nodes2 = creatorOrScope(
|
|
818
|
+
createLazyCreatorState(store.getState(), store, { reads: warnMissingReads("useNodes") })
|
|
819
|
+
);
|
|
820
|
+
if (nodes2 == null) {
|
|
821
|
+
warnOnce(
|
|
822
|
+
"[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."
|
|
823
|
+
);
|
|
824
|
+
return {};
|
|
825
|
+
}
|
|
826
|
+
return nodes2;
|
|
615
827
|
},
|
|
616
828
|
prepare: (name, node) => {
|
|
617
829
|
const setName = Reflect.get(node, "setName");
|
|
@@ -631,16 +843,100 @@ function useNodes(creatorOrScope, scope) {
|
|
|
631
843
|
function rebuildAllNodes(store, scope) {
|
|
632
844
|
rebuildResource(store, "nodes", scope);
|
|
633
845
|
}
|
|
634
|
-
function
|
|
846
|
+
function areDepsEqual(next, prev) {
|
|
847
|
+
if (next.length !== prev.length) return false;
|
|
848
|
+
for (let i = 0; i < next.length; i++) if (!Object.is(next[i], prev[i])) return false;
|
|
849
|
+
return true;
|
|
850
|
+
}
|
|
851
|
+
function useDependencyToken(deps) {
|
|
852
|
+
const previous = useRef(null);
|
|
853
|
+
const record = previous.current;
|
|
854
|
+
if (typeof process !== "undefined" && process.env.NODE_ENV !== "production" && record) {
|
|
855
|
+
const hadDeps = record.deps !== void 0;
|
|
856
|
+
const hasDeps = deps !== void 0;
|
|
857
|
+
if (hadDeps !== hasDeps) {
|
|
858
|
+
console.warn(
|
|
859
|
+
`[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.`
|
|
860
|
+
);
|
|
861
|
+
} else if (hasDeps && record.deps.length !== deps.length) {
|
|
862
|
+
console.warn(
|
|
863
|
+
`[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.`
|
|
864
|
+
);
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
if (deps === void 0) {
|
|
868
|
+
previous.current = { deps: void 0, token: {} };
|
|
869
|
+
} else if (!record || record.deps === void 0 || !areDepsEqual(deps, record.deps)) {
|
|
870
|
+
previous.current = { deps, token: {} };
|
|
871
|
+
} else if (record.deps !== deps) {
|
|
872
|
+
previous.current = { deps, token: record.token };
|
|
873
|
+
}
|
|
874
|
+
return previous.current.token;
|
|
875
|
+
}
|
|
876
|
+
function useLocalNodes(creator, deps) {
|
|
877
|
+
const local = useStore();
|
|
635
878
|
const store = usePrimaryStore();
|
|
636
|
-
const
|
|
637
|
-
const nodes = usePrimaryThree((s) => s.nodes);
|
|
638
|
-
const textures = usePrimaryThree((s) => s.textures);
|
|
879
|
+
const view = useMemo(() => createResourceView(store, local), [store, local]);
|
|
639
880
|
const hmrVersion = usePrimaryThree((s) => s._hmrVersion);
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
881
|
+
const depsToken = useDependencyToken(deps);
|
|
882
|
+
const committedReads = useRef(null);
|
|
883
|
+
const readsVersion = useRef(0);
|
|
884
|
+
const getReadsVersion = () => {
|
|
885
|
+
const tracker2 = committedReads.current;
|
|
886
|
+
if (tracker2 && !tracker2.stale && isTrackerStale(tracker2, view)) readsVersion.current++;
|
|
887
|
+
return readsVersion.current;
|
|
888
|
+
};
|
|
889
|
+
const subscribe = useCallback(
|
|
890
|
+
(onChange) => {
|
|
891
|
+
const unsubscribe = store.subscribe(onChange);
|
|
892
|
+
if (local === store) return unsubscribe;
|
|
893
|
+
const unsubscribeLocal = local.subscribe(onChange);
|
|
894
|
+
return () => {
|
|
895
|
+
unsubscribe();
|
|
896
|
+
unsubscribeLocal();
|
|
897
|
+
};
|
|
898
|
+
},
|
|
899
|
+
[store, local]
|
|
900
|
+
);
|
|
901
|
+
const resourceVersion = useSyncExternalStore(subscribe, getReadsVersion, getReadsVersion);
|
|
902
|
+
const evaluation = useMemo(() => {
|
|
903
|
+
const tracker2 = createReadTracker("useLocalNodes");
|
|
904
|
+
const reads = { observe: tracker2.observe, nested: true };
|
|
905
|
+
const value2 = creator(createLazyCreatorState(local.getState(), store, { reads, view }));
|
|
906
|
+
tracker2.closed = true;
|
|
907
|
+
return { value: value2, tracker: tracker2 };
|
|
908
|
+
}, [view, hmrVersion, depsToken, resourceVersion]);
|
|
909
|
+
const { value, tracker } = evaluation;
|
|
910
|
+
const installing = typeof value === "function";
|
|
911
|
+
useModeDiagnostics(value, installing);
|
|
912
|
+
useIsomorphicLayoutEffect(() => {
|
|
913
|
+
committedReads.current = tracker;
|
|
914
|
+
if (!installing) return;
|
|
915
|
+
tracker.closed = false;
|
|
916
|
+
let cleanup;
|
|
917
|
+
try {
|
|
918
|
+
cleanup = value();
|
|
919
|
+
} finally {
|
|
920
|
+
tracker.closed = true;
|
|
921
|
+
}
|
|
922
|
+
return typeof cleanup === "function" ? cleanup : void 0;
|
|
923
|
+
}, [evaluation]);
|
|
924
|
+
return installing ? void 0 : value;
|
|
925
|
+
}
|
|
926
|
+
function useModeDiagnostics(value, installing) {
|
|
927
|
+
const previous = useRef(null);
|
|
928
|
+
if (typeof process !== "undefined" && process.env.NODE_ENV === "production") return;
|
|
929
|
+
if (value === void 0 || value === null) {
|
|
930
|
+
warnOnce(
|
|
931
|
+
"[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."
|
|
932
|
+
);
|
|
933
|
+
}
|
|
934
|
+
if (previous.current !== null && previous.current !== installing) {
|
|
935
|
+
warnOnce(
|
|
936
|
+
"[useLocalNodes] The creator switched between returning a record and returning an install function. Keep one form for a mounted component."
|
|
937
|
+
);
|
|
938
|
+
}
|
|
939
|
+
previous.current = installing;
|
|
644
940
|
}
|
|
645
941
|
|
|
646
942
|
const disposeBuffer = (buffer) => {
|
|
@@ -686,7 +982,9 @@ function useBuffers(creatorOrScope, scope) {
|
|
|
686
982
|
isLeaf: isBufferLike,
|
|
687
983
|
create: () => {
|
|
688
984
|
if (isReader) return {};
|
|
689
|
-
return creatorOrScope(
|
|
985
|
+
return creatorOrScope(
|
|
986
|
+
createLazyCreatorState(store.getState(), store, { reads: warnMissingReads("useBuffers") })
|
|
987
|
+
);
|
|
690
988
|
},
|
|
691
989
|
prepare: (name, buffer) => {
|
|
692
990
|
const setName = Reflect.get(buffer, "setName");
|
|
@@ -750,7 +1048,9 @@ function useGPUStorage(creatorOrScope, scope) {
|
|
|
750
1048
|
isLeaf: isStorageLike,
|
|
751
1049
|
create: () => {
|
|
752
1050
|
if (isReader) return {};
|
|
753
|
-
return creatorOrScope(
|
|
1051
|
+
return creatorOrScope(
|
|
1052
|
+
createLazyCreatorState(store.getState(), store, { reads: warnMissingReads("useGPUStorage") })
|
|
1053
|
+
);
|
|
754
1054
|
},
|
|
755
1055
|
prepare: (name, storage) => {
|
|
756
1056
|
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.
|
|
3
|
+
"version": "10.0.0-canary.d0fe0de",
|
|
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.
|
|
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.
|
|
59
|
+
"@react-three/fiber": "10.0.0-canary.d0fe0de"
|
|
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
|
|
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
|
|