@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.cjs +333 -32
- 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 +335 -34
- 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)) {
|
|
@@ -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:
|
|
758
|
+
input: memoizedInput,
|
|
559
759
|
create: () => value === void 0 ? {} : { [name]: value },
|
|
560
760
|
prepare: createNamedUniform,
|
|
561
761
|
reconcile: (existing2) => {
|
|
562
|
-
if (value
|
|
563
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
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(
|
|
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(
|
|
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.
|
|
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.
|
|
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.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
|
|
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
|
|