@react-three/tsl 10.0.0-canary.777b94d → 10.0.0-canary.88b1ca7
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 +552 -126
- package/dist/index.d.cts +424 -113
- package/dist/index.d.mts +424 -113
- package/dist/index.d.ts +424 -113
- package/dist/index.mjs +554 -128
- package/package.json +3 -3
- package/readme.md +4 -4
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") {
|
|
@@ -267,6 +278,103 @@ function configureTSL(next) {
|
|
|
267
278
|
for (const store of primaries) applyConfig(store, config);
|
|
268
279
|
}
|
|
269
280
|
|
|
281
|
+
const owners = /* @__PURE__ */ new WeakMap();
|
|
282
|
+
const warned$1 = /* @__PURE__ */ new WeakSet();
|
|
283
|
+
const sweeps = /* @__PURE__ */ new WeakMap();
|
|
284
|
+
function createOwner(store, rebuild) {
|
|
285
|
+
return { store, pipeline: null, scenePass: null, passes: {}, mounted: false, releaseTimer: null, rebuild };
|
|
286
|
+
}
|
|
287
|
+
function disposeOwnerResources(owner) {
|
|
288
|
+
owner.scenePass?.node.dispose();
|
|
289
|
+
owner.pipeline?.dispose();
|
|
290
|
+
owner.scenePass = null;
|
|
291
|
+
owner.pipeline = null;
|
|
292
|
+
owner.passes = {};
|
|
293
|
+
}
|
|
294
|
+
function activeOwner(store) {
|
|
295
|
+
return owners.get(store)?.at(-1);
|
|
296
|
+
}
|
|
297
|
+
function renderThroughPipeline(store) {
|
|
298
|
+
return () => {
|
|
299
|
+
const pipeline = store.getState().renderPipeline;
|
|
300
|
+
if (pipeline?.render) pipeline.render();
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
function claimOwnership(owner) {
|
|
304
|
+
if (owner.releaseTimer !== null) clearTimeout(owner.releaseTimer);
|
|
305
|
+
owner.releaseTimer = null;
|
|
306
|
+
owner.mounted = true;
|
|
307
|
+
let list = owners.get(owner.store);
|
|
308
|
+
if (!list) owners.set(owner.store, list = []);
|
|
309
|
+
if (list.includes(owner)) return;
|
|
310
|
+
if (list.some((other) => other.mounted) && !warned$1.has(owner.store)) {
|
|
311
|
+
warned$1.add(owner.store);
|
|
312
|
+
if (typeof process === "undefined" || process.env.NODE_ENV !== "production") {
|
|
313
|
+
console.warn(
|
|
314
|
+
"[useRenderPipeline] More than one mounted component configures this canvas's render pipeline. The most recently mounted one renders; the others take over again when it unmounts."
|
|
315
|
+
);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
list.push(owner);
|
|
319
|
+
scheduleSweep(owner.store);
|
|
320
|
+
}
|
|
321
|
+
function scheduleSweep(store) {
|
|
322
|
+
if (sweeps.has(store)) return;
|
|
323
|
+
sweeps.set(
|
|
324
|
+
store,
|
|
325
|
+
setTimeout(() => {
|
|
326
|
+
sweeps.delete(store);
|
|
327
|
+
const active = activeOwner(store);
|
|
328
|
+
for (const owner of [...owners.get(store) ?? []]) {
|
|
329
|
+
if (owner !== active && !owner.mounted && owner.releaseTimer === null) releaseOwner(owner);
|
|
330
|
+
}
|
|
331
|
+
})
|
|
332
|
+
);
|
|
333
|
+
}
|
|
334
|
+
function publishOwner(owner) {
|
|
335
|
+
if (activeOwner(owner.store) !== owner || !owner.pipeline) return;
|
|
336
|
+
owner.store.setState({ renderPipeline: owner.pipeline, passes: owner.passes });
|
|
337
|
+
extension.setRenderOverride(owner.store, renderThroughPipeline(owner.store));
|
|
338
|
+
}
|
|
339
|
+
function scheduleRelease(owner) {
|
|
340
|
+
owner.mounted = false;
|
|
341
|
+
if (owner.releaseTimer !== null) return;
|
|
342
|
+
owner.releaseTimer = setTimeout(() => releaseOwner(owner));
|
|
343
|
+
}
|
|
344
|
+
function releaseOwner(owner) {
|
|
345
|
+
if (owner.releaseTimer !== null) clearTimeout(owner.releaseTimer);
|
|
346
|
+
owner.releaseTimer = null;
|
|
347
|
+
const { store } = owner;
|
|
348
|
+
const list = owners.get(store);
|
|
349
|
+
const index = list?.indexOf(owner) ?? -1;
|
|
350
|
+
if (list && index !== -1) {
|
|
351
|
+
const wasActive = index === list.length - 1;
|
|
352
|
+
list.splice(index, 1);
|
|
353
|
+
if (wasActive) {
|
|
354
|
+
const next = activeOwner(store);
|
|
355
|
+
if (next) {
|
|
356
|
+
publishOwner(next);
|
|
357
|
+
} else {
|
|
358
|
+
store.setState({ renderPipeline: null, passes: {} });
|
|
359
|
+
extension.setRenderOverride(store, null);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
disposeOwnerResources(owner);
|
|
364
|
+
}
|
|
365
|
+
function releaseAllOwners(store) {
|
|
366
|
+
clearTimeout(sweeps.get(store));
|
|
367
|
+
sweeps.delete(store);
|
|
368
|
+
const list = owners.get(store);
|
|
369
|
+
if (!list) return;
|
|
370
|
+
owners.delete(store);
|
|
371
|
+
for (const owner of list) {
|
|
372
|
+
if (owner.releaseTimer !== null) clearTimeout(owner.releaseTimer);
|
|
373
|
+
owner.releaseTimer = null;
|
|
374
|
+
disposeOwnerResources(owner);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
270
378
|
const SHARED_KEYS = ["uniforms", "nodes", "buffers", "gpuStorage", "_hmrVersion"];
|
|
271
379
|
const following = /* @__PURE__ */ new WeakMap();
|
|
272
380
|
function createResourceState() {
|
|
@@ -295,6 +403,7 @@ function followPrimary(store, primary) {
|
|
|
295
403
|
following.set(store, primary.subscribe(sync));
|
|
296
404
|
}
|
|
297
405
|
function dispose(store) {
|
|
406
|
+
releaseAllOwners(store);
|
|
298
407
|
untrackPrimary(store);
|
|
299
408
|
following.get(store)?.();
|
|
300
409
|
following.delete(store);
|
|
@@ -312,33 +421,129 @@ function ensureTSLExtension() {
|
|
|
312
421
|
});
|
|
313
422
|
}
|
|
314
423
|
|
|
424
|
+
const SCOPE = Symbol("readTracking.scope");
|
|
425
|
+
const LEAF_GUARDS = {
|
|
426
|
+
uniforms: isUniformNode,
|
|
427
|
+
nodes: isTSLNode,
|
|
428
|
+
buffers: isBufferLike,
|
|
429
|
+
gpuStorage: isStorageLike
|
|
430
|
+
};
|
|
431
|
+
function isScope(kind, value) {
|
|
432
|
+
return !!value && typeof value === "object" && !LEAF_GUARDS[kind](value);
|
|
433
|
+
}
|
|
434
|
+
function classify(kind, value) {
|
|
435
|
+
return kind !== "textures" && isScope(kind, value) ? SCOPE : value;
|
|
436
|
+
}
|
|
437
|
+
function resolveContainer(kind, root, scopePath) {
|
|
438
|
+
let container = root;
|
|
439
|
+
for (const key of scopePath) {
|
|
440
|
+
const next = container?.[key];
|
|
441
|
+
container = isScope(kind, next) ? next : void 0;
|
|
442
|
+
}
|
|
443
|
+
return container;
|
|
444
|
+
}
|
|
445
|
+
function observeNow(read, view) {
|
|
446
|
+
if (read.kind === "textures") {
|
|
447
|
+
const map = view("textures");
|
|
448
|
+
if (read.op === "keys") return [...map.keys()];
|
|
449
|
+
return read.op === "has" ? map.has(read.path[0]) : map.get(read.path[0]);
|
|
450
|
+
}
|
|
451
|
+
const root = view(read.kind);
|
|
452
|
+
if (read.op === "keys") return Object.keys(resolveContainer(read.kind, root, read.path) ?? {});
|
|
453
|
+
const container = resolveContainer(read.kind, root, read.path.slice(0, -1));
|
|
454
|
+
const key = read.path[read.path.length - 1];
|
|
455
|
+
if (read.op === "has") return !!container && key in container;
|
|
456
|
+
return classify(read.kind, container?.[key]);
|
|
457
|
+
}
|
|
458
|
+
function sameKeys(a, b) {
|
|
459
|
+
return a.length === b.length && a.every((key, i) => key === b[i]);
|
|
460
|
+
}
|
|
461
|
+
function hasChanged(read, view) {
|
|
462
|
+
const now = observeNow(read, view);
|
|
463
|
+
return read.op === "keys" ? !sameKeys(read.seen, now) : !Object.is(read.seen, now);
|
|
464
|
+
}
|
|
465
|
+
const warned = /* @__PURE__ */ new Set();
|
|
466
|
+
function warnOnce(message) {
|
|
467
|
+
if (typeof process !== "undefined" && process.env.NODE_ENV === "production") return;
|
|
468
|
+
if (warned.has(message)) return;
|
|
469
|
+
warned.add(message);
|
|
470
|
+
console.warn(message);
|
|
471
|
+
}
|
|
472
|
+
function describeRead(kind, path) {
|
|
473
|
+
if (kind === "textures") return `textures.get('${path[0]}')`;
|
|
474
|
+
const scopes = path.slice(0, -1).map((key) => `.scope('${key}')`);
|
|
475
|
+
return `${kind}${scopes.join("")}.${path[path.length - 1] ?? ""}`;
|
|
476
|
+
}
|
|
477
|
+
function createReadTracker(hookName) {
|
|
478
|
+
const tracker = {
|
|
479
|
+
reads: [],
|
|
480
|
+
closed: false,
|
|
481
|
+
stale: false,
|
|
482
|
+
observe: (read) => {
|
|
483
|
+
if (tracker.closed) {
|
|
484
|
+
warnOnce(
|
|
485
|
+
`[${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.`
|
|
486
|
+
);
|
|
487
|
+
return;
|
|
488
|
+
}
|
|
489
|
+
tracker.reads.push(read);
|
|
490
|
+
}
|
|
491
|
+
};
|
|
492
|
+
return tracker;
|
|
493
|
+
}
|
|
494
|
+
function isTrackerStale(tracker, view) {
|
|
495
|
+
if (!tracker.stale) tracker.stale = tracker.reads.some((read) => hasChanged(read, view));
|
|
496
|
+
return tracker.stale;
|
|
497
|
+
}
|
|
498
|
+
function warnMissingReads(hookName) {
|
|
499
|
+
return {
|
|
500
|
+
nested: false,
|
|
501
|
+
observe: (read) => {
|
|
502
|
+
if (read.op !== "get" || read.seen !== void 0) return;
|
|
503
|
+
warnOnce(
|
|
504
|
+
`[${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().`
|
|
505
|
+
);
|
|
506
|
+
}
|
|
507
|
+
};
|
|
508
|
+
}
|
|
509
|
+
|
|
315
510
|
var __defProp = Object.defineProperty;
|
|
316
511
|
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
317
512
|
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
318
|
-
var _a, _b;
|
|
513
|
+
var _a, _b, _c, _d;
|
|
319
514
|
const INTERNAL_DATA = Symbol("ScopedStore.data");
|
|
320
515
|
const INTERNAL_IS_LEAF = Symbol("ScopedStore.isLeaf");
|
|
321
|
-
|
|
516
|
+
const INTERNAL_READS = Symbol("ScopedStore.reads");
|
|
517
|
+
const INTERNAL_CHILDREN = Symbol("ScopedStore.children");
|
|
518
|
+
_d = INTERNAL_DATA, _c = INTERNAL_IS_LEAF, _b = INTERNAL_READS, _a = INTERNAL_CHILDREN;
|
|
322
519
|
const _ScopedStore = class _ScopedStore {
|
|
323
|
-
constructor(data, isLeaf) {
|
|
520
|
+
constructor(data, isLeaf, reads) {
|
|
521
|
+
/** @internal */
|
|
522
|
+
__publicField(this, _d);
|
|
523
|
+
/** @internal */
|
|
524
|
+
__publicField(this, _c);
|
|
525
|
+
/** @internal */
|
|
324
526
|
__publicField(this, _b);
|
|
527
|
+
/** @internal */
|
|
325
528
|
__publicField(this, _a);
|
|
326
529
|
this[INTERNAL_DATA] = data;
|
|
327
530
|
this[INTERNAL_IS_LEAF] = isLeaf;
|
|
531
|
+
this[INTERNAL_READS] = reads;
|
|
328
532
|
return new Proxy(this, {
|
|
329
533
|
get(target, prop, receiver) {
|
|
330
534
|
if (typeof prop === "string") {
|
|
331
535
|
if (prop === "scope" || prop === "has" || prop === "keys") {
|
|
332
536
|
return Reflect.get(target, prop, receiver);
|
|
333
537
|
}
|
|
334
|
-
return target
|
|
538
|
+
return readEntry(target, prop);
|
|
335
539
|
}
|
|
336
540
|
return Reflect.get(target, prop, receiver);
|
|
337
541
|
},
|
|
338
542
|
has(target, prop) {
|
|
339
|
-
return typeof prop === "string" ? prop
|
|
543
|
+
return typeof prop === "string" ? target.has(prop) : Reflect.has(target, prop);
|
|
340
544
|
},
|
|
341
545
|
ownKeys(target) {
|
|
546
|
+
observeKeys(target);
|
|
342
547
|
return Reflect.ownKeys(target[INTERNAL_DATA]);
|
|
343
548
|
},
|
|
344
549
|
getOwnPropertyDescriptor(target, prop) {
|
|
@@ -360,54 +565,152 @@ const _ScopedStore = class _ScopedStore {
|
|
|
360
565
|
scope(key) {
|
|
361
566
|
const value = this[INTERNAL_DATA][key];
|
|
362
567
|
const isLeaf = this[INTERNAL_IS_LEAF];
|
|
568
|
+
const reads = this[INTERNAL_READS];
|
|
363
569
|
const scope = value && typeof value === "object" && !isLeaf(value) ? value : {};
|
|
364
|
-
|
|
570
|
+
const child = reads ? { ...reads, path: [...reads.path, key] } : void 0;
|
|
571
|
+
return new _ScopedStore(scope, isLeaf, child);
|
|
365
572
|
}
|
|
366
573
|
/**
|
|
367
574
|
* Check if a key exists in the store.
|
|
368
575
|
*/
|
|
369
576
|
has(key) {
|
|
370
|
-
|
|
577
|
+
const found = key in this[INTERNAL_DATA];
|
|
578
|
+
const reads = this[INTERNAL_READS];
|
|
579
|
+
reads?.observe({ kind: reads.kind, op: "has", path: [...reads.path, key], seen: found });
|
|
580
|
+
return found;
|
|
371
581
|
}
|
|
372
582
|
/**
|
|
373
583
|
* Get all keys in the store.
|
|
374
584
|
*/
|
|
375
585
|
keys() {
|
|
586
|
+
observeKeys(this);
|
|
376
587
|
return Object.keys(this[INTERNAL_DATA]);
|
|
377
588
|
}
|
|
378
589
|
};
|
|
379
590
|
let ScopedStore = _ScopedStore;
|
|
591
|
+
function readEntry(target, key) {
|
|
592
|
+
const value = target[INTERNAL_DATA][key];
|
|
593
|
+
const reads = target[INTERNAL_READS];
|
|
594
|
+
if (!reads) return value;
|
|
595
|
+
const isLeaf = target[INTERNAL_IS_LEAF];
|
|
596
|
+
const isScope = !!value && typeof value === "object" && !isLeaf(value);
|
|
597
|
+
const path = [...reads.path, key];
|
|
598
|
+
reads.observe({ kind: reads.kind, op: "get", path, seen: isScope ? SCOPE : value });
|
|
599
|
+
if (!isScope || !reads.nested) return value;
|
|
600
|
+
const children = target[INTERNAL_CHILDREN] ?? (target[INTERNAL_CHILDREN] = /* @__PURE__ */ new Map());
|
|
601
|
+
let child = children.get(key);
|
|
602
|
+
if (!child) {
|
|
603
|
+
child = new ScopedStore(value, isLeaf, { ...reads, path });
|
|
604
|
+
children.set(key, child);
|
|
605
|
+
}
|
|
606
|
+
return child;
|
|
607
|
+
}
|
|
608
|
+
function observeKeys(target) {
|
|
609
|
+
const reads = target[INTERNAL_READS];
|
|
610
|
+
reads?.observe({ kind: reads.kind, op: "keys", path: reads.path, seen: Object.keys(target[INTERNAL_DATA]) });
|
|
611
|
+
}
|
|
380
612
|
function createScopedStore(data, isLeaf) {
|
|
381
613
|
return new ScopedStore(data, isLeaf);
|
|
382
614
|
}
|
|
383
|
-
function
|
|
615
|
+
function observeTextures(map, observe) {
|
|
616
|
+
const observeKeys2 = () => observe({ kind: "textures", op: "keys", path: [], seen: [...map.keys()] });
|
|
617
|
+
const observeAll = () => {
|
|
618
|
+
observeKeys2();
|
|
619
|
+
for (const [url, texture] of map) observe({ kind: "textures", op: "get", path: [url], seen: texture });
|
|
620
|
+
};
|
|
621
|
+
return new Proxy(map, {
|
|
622
|
+
get(target, prop) {
|
|
623
|
+
switch (prop) {
|
|
624
|
+
case "get":
|
|
625
|
+
return (url) => {
|
|
626
|
+
const texture = target.get(url);
|
|
627
|
+
observe({ kind: "textures", op: "get", path: [url], seen: texture });
|
|
628
|
+
return texture;
|
|
629
|
+
};
|
|
630
|
+
case "has":
|
|
631
|
+
return (url) => {
|
|
632
|
+
const found = target.has(url);
|
|
633
|
+
observe({ kind: "textures", op: "has", path: [url], seen: found });
|
|
634
|
+
return found;
|
|
635
|
+
};
|
|
636
|
+
case "size":
|
|
637
|
+
observeKeys2();
|
|
638
|
+
return target.size;
|
|
639
|
+
case "keys":
|
|
640
|
+
return () => {
|
|
641
|
+
observeKeys2();
|
|
642
|
+
return target.keys();
|
|
643
|
+
};
|
|
644
|
+
case "values":
|
|
645
|
+
return () => {
|
|
646
|
+
observeAll();
|
|
647
|
+
return target.values();
|
|
648
|
+
};
|
|
649
|
+
case "entries":
|
|
650
|
+
case Symbol.iterator:
|
|
651
|
+
return () => {
|
|
652
|
+
observeAll();
|
|
653
|
+
return target.entries();
|
|
654
|
+
};
|
|
655
|
+
case "forEach":
|
|
656
|
+
return (callback, thisArg) => {
|
|
657
|
+
observeAll();
|
|
658
|
+
target.forEach(callback, thisArg);
|
|
659
|
+
};
|
|
660
|
+
}
|
|
661
|
+
const value = Reflect.get(target, prop, target);
|
|
662
|
+
return typeof value === "function" ? value.bind(target) : value;
|
|
663
|
+
}
|
|
664
|
+
});
|
|
665
|
+
}
|
|
666
|
+
function createResourceView(primary, local = primary) {
|
|
667
|
+
return ((kind) => kind === "textures" ? extension.getTextureView(local) : withStagedOverlay(primary, kind, primary.getState()[kind]));
|
|
668
|
+
}
|
|
669
|
+
function createLazyCreatorState(state, store, options = {}) {
|
|
670
|
+
const { reads } = options;
|
|
671
|
+
const view = options.view ?? (store ? createResourceView(store) : void 0);
|
|
384
672
|
let _uniforms = null;
|
|
385
673
|
let _nodes = null;
|
|
386
674
|
let _buffers = null;
|
|
387
675
|
let _gpuStorage = null;
|
|
388
|
-
|
|
389
|
-
|
|
676
|
+
let _textures = null;
|
|
677
|
+
const read = (kind) => view ? view(kind) : state[kind];
|
|
678
|
+
const wrap = (kind, isLeaf) => new ScopedStore(
|
|
679
|
+
read(kind),
|
|
680
|
+
isLeaf,
|
|
681
|
+
reads && { ...reads, kind, path: [] }
|
|
682
|
+
);
|
|
683
|
+
const properties = {
|
|
390
684
|
uniforms: {
|
|
391
685
|
get() {
|
|
392
|
-
return _uniforms ?? (_uniforms =
|
|
686
|
+
return _uniforms ?? (_uniforms = wrap("uniforms", isUniformNode));
|
|
393
687
|
}
|
|
394
688
|
},
|
|
395
689
|
nodes: {
|
|
396
690
|
get() {
|
|
397
|
-
return _nodes ?? (_nodes =
|
|
691
|
+
return _nodes ?? (_nodes = wrap("nodes", isTSLNode));
|
|
398
692
|
}
|
|
399
693
|
},
|
|
400
694
|
buffers: {
|
|
401
695
|
get() {
|
|
402
|
-
return _buffers ?? (_buffers =
|
|
696
|
+
return _buffers ?? (_buffers = wrap("buffers", isBufferLike));
|
|
403
697
|
}
|
|
404
698
|
},
|
|
405
699
|
gpuStorage: {
|
|
406
700
|
get() {
|
|
407
|
-
return _gpuStorage ?? (_gpuStorage =
|
|
701
|
+
return _gpuStorage ?? (_gpuStorage = wrap("gpuStorage", isStorageLike));
|
|
408
702
|
}
|
|
409
703
|
}
|
|
410
|
-
}
|
|
704
|
+
};
|
|
705
|
+
if (options.view) {
|
|
706
|
+
properties.textures = {
|
|
707
|
+
get() {
|
|
708
|
+
const textures = options.view("textures");
|
|
709
|
+
return _textures ?? (_textures = reads ? observeTextures(textures, reads.observe) : textures);
|
|
710
|
+
}
|
|
711
|
+
};
|
|
712
|
+
}
|
|
713
|
+
return Object.create(state, properties);
|
|
411
714
|
}
|
|
412
715
|
|
|
413
716
|
function resolvePrimary(local) {
|
|
@@ -483,7 +786,7 @@ function useUniforms(creatorOrScope, scope) {
|
|
|
483
786
|
const processedInput = React.useMemo(() => {
|
|
484
787
|
let raw = creatorOrScope;
|
|
485
788
|
if (typeof creatorOrScope === "function") {
|
|
486
|
-
const wrappedState = createLazyCreatorState(store.getState(), store);
|
|
789
|
+
const wrappedState = createLazyCreatorState(store.getState(), store, { reads: warnMissingReads("useUniforms") });
|
|
487
790
|
raw = creatorOrScope(wrappedState);
|
|
488
791
|
}
|
|
489
792
|
if (raw && typeof raw === "object" && !Array.isArray(raw)) {
|
|
@@ -506,8 +809,9 @@ function useUniforms(creatorOrScope, scope) {
|
|
|
506
809
|
input: memoizedInput,
|
|
507
810
|
create: () => {
|
|
508
811
|
if (isReader) return {};
|
|
509
|
-
if (typeof processedInput.candidates !== "object" || processedInput.candidates === null)
|
|
812
|
+
if (typeof processedInput.candidates !== "object" || processedInput.candidates === null) {
|
|
510
813
|
throw new Error("Invalid uniform input");
|
|
814
|
+
}
|
|
511
815
|
return processedInput.candidates;
|
|
512
816
|
},
|
|
513
817
|
prepare: (name, candidate) => createUniform(name, candidate, scope),
|
|
@@ -525,13 +829,7 @@ function useUniforms(creatorOrScope, scope) {
|
|
|
525
829
|
if (!equals) existing.value = newVal;
|
|
526
830
|
}
|
|
527
831
|
});
|
|
528
|
-
|
|
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
|
-
}
|
|
832
|
+
const uniforms = isReader ? readWithStaged(store, "uniforms", storeUniforms, memoizedInput, isUniformNode) : created;
|
|
535
833
|
return { ...uniforms, removeUniforms, clearUniforms, rebuildUniforms };
|
|
536
834
|
}
|
|
537
835
|
function rebuildAllUniforms(store, scope) {
|
|
@@ -565,18 +863,20 @@ function isSameThreeType(a, b) {
|
|
|
565
863
|
const uniformOf = tsl.uniform;
|
|
566
864
|
function useUniform(name, value) {
|
|
567
865
|
const store = usePrimaryStore();
|
|
866
|
+
const memoizedInput = useCompareMemoize(value === void 0 ? name : { [name]: normalizeValue(value) });
|
|
568
867
|
const registered = useScopedResource({
|
|
569
868
|
store,
|
|
570
869
|
kind: "uniforms",
|
|
571
870
|
scope: void 0,
|
|
572
871
|
isLeaf: isUniformNode,
|
|
573
|
-
input:
|
|
872
|
+
input: memoizedInput,
|
|
574
873
|
create: () => value === void 0 ? {} : { [name]: value },
|
|
575
874
|
prepare: createNamedUniform,
|
|
576
875
|
reconcile: (existing2) => {
|
|
577
|
-
if (value
|
|
578
|
-
|
|
579
|
-
|
|
876
|
+
if (value === void 0 || isUniformNode(value)) return;
|
|
877
|
+
const next = normalizeValue(value);
|
|
878
|
+
if (isTSLNode(next)) return;
|
|
879
|
+
existing2.value = next;
|
|
580
880
|
}
|
|
581
881
|
});
|
|
582
882
|
if (registered[name]) return registered[name];
|
|
@@ -586,6 +886,9 @@ function useUniform(name, value) {
|
|
|
586
886
|
`[useUniform] Uniform "${name}" not found. Create it first with: useUniform('${name}', initialValue)`
|
|
587
887
|
);
|
|
588
888
|
}
|
|
889
|
+
function normalizeValue(value) {
|
|
890
|
+
return typeof value === "string" ? new THREE.Color(value) : vectorize(value);
|
|
891
|
+
}
|
|
589
892
|
function createNamedUniform(name, value) {
|
|
590
893
|
let node;
|
|
591
894
|
if (isUniformNode(value)) {
|
|
@@ -626,7 +929,16 @@ function useNodes(creatorOrScope, scope) {
|
|
|
626
929
|
isLeaf: isTSLNode,
|
|
627
930
|
create: () => {
|
|
628
931
|
if (isReader) return {};
|
|
629
|
-
|
|
932
|
+
const nodes2 = creatorOrScope(
|
|
933
|
+
createLazyCreatorState(store.getState(), store, { reads: warnMissingReads("useNodes") })
|
|
934
|
+
);
|
|
935
|
+
if (nodes2 == null) {
|
|
936
|
+
warnOnce(
|
|
937
|
+
"[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."
|
|
938
|
+
);
|
|
939
|
+
return {};
|
|
940
|
+
}
|
|
941
|
+
return nodes2;
|
|
630
942
|
},
|
|
631
943
|
prepare: (name, node) => {
|
|
632
944
|
const setName = Reflect.get(node, "setName");
|
|
@@ -634,28 +946,106 @@ function useNodes(creatorOrScope, scope) {
|
|
|
634
946
|
return node;
|
|
635
947
|
}
|
|
636
948
|
});
|
|
637
|
-
|
|
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
|
-
}
|
|
949
|
+
const nodes = isReader ? readWithStaged(store, "nodes", storeNodes, creatorOrScope, isTSLNode) : created;
|
|
644
950
|
return { ...nodes, removeNodes, clearNodes, rebuildNodes };
|
|
645
951
|
}
|
|
646
952
|
function rebuildAllNodes(store, scope) {
|
|
647
953
|
rebuildResource(store, "nodes", scope);
|
|
648
954
|
}
|
|
649
|
-
function
|
|
955
|
+
function areDepsEqual(next, prev) {
|
|
956
|
+
if (next.length !== prev.length) return false;
|
|
957
|
+
for (let i = 0; i < next.length; i++) if (!Object.is(next[i], prev[i])) return false;
|
|
958
|
+
return true;
|
|
959
|
+
}
|
|
960
|
+
function useDependencyToken(deps) {
|
|
961
|
+
const previous = React.useRef(null);
|
|
962
|
+
const record = previous.current;
|
|
963
|
+
if (typeof process !== "undefined" && process.env.NODE_ENV !== "production" && record) {
|
|
964
|
+
const hadDeps = record.deps !== void 0;
|
|
965
|
+
const hasDeps = deps !== void 0;
|
|
966
|
+
if (hadDeps !== hasDeps) {
|
|
967
|
+
console.warn(
|
|
968
|
+
`[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.`
|
|
969
|
+
);
|
|
970
|
+
} else if (hasDeps && record.deps.length !== deps.length) {
|
|
971
|
+
console.warn(
|
|
972
|
+
`[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.`
|
|
973
|
+
);
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
if (deps === void 0) {
|
|
977
|
+
previous.current = { deps: void 0, token: {} };
|
|
978
|
+
} else if (!record || record.deps === void 0 || !areDepsEqual(deps, record.deps)) {
|
|
979
|
+
previous.current = { deps, token: {} };
|
|
980
|
+
} else if (record.deps !== deps) {
|
|
981
|
+
previous.current = { deps, token: record.token };
|
|
982
|
+
}
|
|
983
|
+
return previous.current.token;
|
|
984
|
+
}
|
|
985
|
+
function useLocalNodes(creator, deps) {
|
|
986
|
+
const local = extension.useStore();
|
|
650
987
|
const store = usePrimaryStore();
|
|
651
|
-
const
|
|
652
|
-
const nodes = usePrimaryThree((s) => s.nodes);
|
|
653
|
-
const textures = usePrimaryThree((s) => s.textures);
|
|
988
|
+
const view = React.useMemo(() => createResourceView(store, local), [store, local]);
|
|
654
989
|
const hmrVersion = usePrimaryThree((s) => s._hmrVersion);
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
990
|
+
const depsToken = useDependencyToken(deps);
|
|
991
|
+
const committedReads = React.useRef(null);
|
|
992
|
+
const readsVersion = React.useRef(0);
|
|
993
|
+
const getReadsVersion = () => {
|
|
994
|
+
const tracker2 = committedReads.current;
|
|
995
|
+
if (tracker2 && !tracker2.stale && isTrackerStale(tracker2, view)) readsVersion.current++;
|
|
996
|
+
return readsVersion.current;
|
|
997
|
+
};
|
|
998
|
+
const subscribe = React.useCallback(
|
|
999
|
+
(onChange) => {
|
|
1000
|
+
const unsubscribe = store.subscribe(onChange);
|
|
1001
|
+
if (local === store) return unsubscribe;
|
|
1002
|
+
const unsubscribeLocal = local.subscribe(onChange);
|
|
1003
|
+
return () => {
|
|
1004
|
+
unsubscribe();
|
|
1005
|
+
unsubscribeLocal();
|
|
1006
|
+
};
|
|
1007
|
+
},
|
|
1008
|
+
[store, local]
|
|
1009
|
+
);
|
|
1010
|
+
const resourceVersion = React.useSyncExternalStore(subscribe, getReadsVersion, getReadsVersion);
|
|
1011
|
+
const evaluation = React.useMemo(() => {
|
|
1012
|
+
const tracker2 = createReadTracker("useLocalNodes");
|
|
1013
|
+
const reads = { observe: tracker2.observe, nested: true };
|
|
1014
|
+
const value2 = creator(createLazyCreatorState(local.getState(), store, { reads, view }));
|
|
1015
|
+
tracker2.closed = true;
|
|
1016
|
+
return { value: value2, tracker: tracker2 };
|
|
1017
|
+
}, [view, hmrVersion, depsToken, resourceVersion]);
|
|
1018
|
+
const { value, tracker } = evaluation;
|
|
1019
|
+
const installing = typeof value === "function";
|
|
1020
|
+
useModeDiagnostics(value, installing);
|
|
1021
|
+
useIsomorphicLayoutEffect(() => {
|
|
1022
|
+
committedReads.current = tracker;
|
|
1023
|
+
if (!installing) return;
|
|
1024
|
+
tracker.closed = false;
|
|
1025
|
+
let cleanup;
|
|
1026
|
+
try {
|
|
1027
|
+
cleanup = value();
|
|
1028
|
+
} finally {
|
|
1029
|
+
tracker.closed = true;
|
|
1030
|
+
}
|
|
1031
|
+
return typeof cleanup === "function" ? cleanup : void 0;
|
|
1032
|
+
}, [evaluation]);
|
|
1033
|
+
return installing ? void 0 : value;
|
|
1034
|
+
}
|
|
1035
|
+
function useModeDiagnostics(value, installing) {
|
|
1036
|
+
const previous = React.useRef(null);
|
|
1037
|
+
if (typeof process !== "undefined" && process.env.NODE_ENV === "production") return;
|
|
1038
|
+
if (value === void 0 || value === null) {
|
|
1039
|
+
warnOnce(
|
|
1040
|
+
"[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."
|
|
1041
|
+
);
|
|
1042
|
+
}
|
|
1043
|
+
if (previous.current !== null && previous.current !== installing) {
|
|
1044
|
+
warnOnce(
|
|
1045
|
+
"[useLocalNodes] The creator switched between returning a record and returning an install function. Keep one form for a mounted component."
|
|
1046
|
+
);
|
|
1047
|
+
}
|
|
1048
|
+
previous.current = installing;
|
|
659
1049
|
}
|
|
660
1050
|
|
|
661
1051
|
const disposeBuffer = (buffer) => {
|
|
@@ -701,7 +1091,9 @@ function useBuffers(creatorOrScope, scope) {
|
|
|
701
1091
|
isLeaf: isBufferLike,
|
|
702
1092
|
create: () => {
|
|
703
1093
|
if (isReader) return {};
|
|
704
|
-
return creatorOrScope(
|
|
1094
|
+
return creatorOrScope(
|
|
1095
|
+
createLazyCreatorState(store.getState(), store, { reads: warnMissingReads("useBuffers") })
|
|
1096
|
+
);
|
|
705
1097
|
},
|
|
706
1098
|
prepare: (name, buffer) => {
|
|
707
1099
|
const setName = Reflect.get(buffer, "setName");
|
|
@@ -709,13 +1101,7 @@ function useBuffers(creatorOrScope, scope) {
|
|
|
709
1101
|
return buffer;
|
|
710
1102
|
}
|
|
711
1103
|
});
|
|
712
|
-
|
|
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
|
-
}
|
|
1104
|
+
const buffers = isReader ? readWithStaged(store, "buffers", storeBuffers, creatorOrScope, isBufferLike) : created;
|
|
719
1105
|
return { ...buffers, removeBuffers, clearBuffers, rebuildBuffers, disposeBuffers };
|
|
720
1106
|
}
|
|
721
1107
|
function rebuildAllBuffers(store, scope) {
|
|
@@ -765,7 +1151,9 @@ function useGPUStorage(creatorOrScope, scope) {
|
|
|
765
1151
|
isLeaf: isStorageLike,
|
|
766
1152
|
create: () => {
|
|
767
1153
|
if (isReader) return {};
|
|
768
|
-
return creatorOrScope(
|
|
1154
|
+
return creatorOrScope(
|
|
1155
|
+
createLazyCreatorState(store.getState(), store, { reads: warnMissingReads("useGPUStorage") })
|
|
1156
|
+
);
|
|
769
1157
|
},
|
|
770
1158
|
prepare: (name, storage) => {
|
|
771
1159
|
const label = scopedNodeName(scope, name);
|
|
@@ -777,13 +1165,7 @@ function useGPUStorage(creatorOrScope, scope) {
|
|
|
777
1165
|
return storage;
|
|
778
1166
|
}
|
|
779
1167
|
});
|
|
780
|
-
|
|
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
|
-
}
|
|
1168
|
+
const gpuStorage = isReader ? readWithStaged(store, "gpuStorage", storeStorage, creatorOrScope, isStorageLike) : created;
|
|
787
1169
|
return {
|
|
788
1170
|
...gpuStorage,
|
|
789
1171
|
removeStorage,
|
|
@@ -797,120 +1179,164 @@ function rebuildAllStorage(store, scope) {
|
|
|
797
1179
|
}
|
|
798
1180
|
|
|
799
1181
|
const NO_PASSES = Object.freeze({});
|
|
800
|
-
function
|
|
801
|
-
return () => {
|
|
802
|
-
const pipeline = store.getState().renderPipeline;
|
|
803
|
-
if (pipeline?.render) pipeline.render();
|
|
804
|
-
};
|
|
805
|
-
}
|
|
806
|
-
function useRenderPipeline(mainCB, setupCB) {
|
|
1182
|
+
function useRenderPipeline(mainCB, setupCB, options) {
|
|
807
1183
|
const store = extension.useStore();
|
|
1184
|
+
const primary = usePrimaryStore();
|
|
1185
|
+
const view = React.useMemo(() => createResourceView(primary, store), [primary, store]);
|
|
808
1186
|
const { scene, camera, renderer, isLegacy } = extension.useThree();
|
|
1187
|
+
const hmrVersion = usePrimaryThree((s) => s._hmrVersion);
|
|
1188
|
+
const isOwner = mainCB !== void 0 || setupCB !== void 0;
|
|
809
1189
|
const callbacksRanRef = React.useRef(false);
|
|
810
|
-
const scenePassCacheRef = React.useRef(null);
|
|
811
1190
|
const forceScenePassRebuildRef = React.useRef(false);
|
|
812
1191
|
const mainCBRef = React.useRef(mainCB);
|
|
813
1192
|
const setupCBRef = React.useRef(setupCB);
|
|
1193
|
+
const persistRef = React.useRef(options?.persist ?? false);
|
|
814
1194
|
mainCBRef.current = mainCB;
|
|
815
1195
|
setupCBRef.current = setupCB;
|
|
1196
|
+
persistRef.current = options?.persist ?? false;
|
|
816
1197
|
const [rebuildVersion, setRebuildVersion] = React.useState(0);
|
|
817
|
-
React.
|
|
1198
|
+
const rerun = React.useCallback((freshScenePass) => {
|
|
818
1199
|
callbacksRanRef.current = false;
|
|
819
|
-
forceScenePassRebuildRef.current = true;
|
|
1200
|
+
if (freshScenePass) forceScenePassRebuildRef.current = true;
|
|
1201
|
+
setRebuildVersion((v) => v + 1);
|
|
820
1202
|
}, []);
|
|
1203
|
+
const ownerRef = React.useRef(null);
|
|
1204
|
+
const target = React.useCallback(() => (isOwner ? ownerRef.current : activeOwner(store)) ?? null, [isOwner, store]);
|
|
821
1205
|
const clearPasses = React.useCallback(() => {
|
|
822
|
-
|
|
823
|
-
|
|
1206
|
+
const owner = target();
|
|
1207
|
+
if (!owner) return;
|
|
1208
|
+
owner.passes = {};
|
|
1209
|
+
if (activeOwner(store) === owner) store.setState({ passes: {} });
|
|
1210
|
+
}, [store, target]);
|
|
824
1211
|
const reset = React.useCallback(() => {
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
scenePassCacheRef.current?.scenePass.dispose();
|
|
831
|
-
callbacksRanRef.current = false;
|
|
832
|
-
scenePassCacheRef.current = null;
|
|
833
|
-
forceScenePassRebuildRef.current = false;
|
|
834
|
-
}, [store]);
|
|
1212
|
+
const owner = target();
|
|
1213
|
+
if (!owner) return;
|
|
1214
|
+
releaseOwner(owner);
|
|
1215
|
+
if (owner === ownerRef.current) committedReads.current = null;
|
|
1216
|
+
}, [target]);
|
|
835
1217
|
const rebuild = React.useCallback(() => {
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
1218
|
+
if (isOwner) rerun(true);
|
|
1219
|
+
else activeOwner(store)?.rebuild();
|
|
1220
|
+
}, [isOwner, rerun, store]);
|
|
1221
|
+
const hmrVersionRef = React.useRef(hmrVersion);
|
|
1222
|
+
React.useEffect(() => {
|
|
1223
|
+
if (hmrVersionRef.current === hmrVersion) return;
|
|
1224
|
+
hmrVersionRef.current = hmrVersion;
|
|
1225
|
+
if (isOwner) rerun(false);
|
|
1226
|
+
}, [hmrVersion, isOwner, rerun]);
|
|
1227
|
+
const committedReads = React.useRef(null);
|
|
1228
|
+
const readsVersion = React.useRef(0);
|
|
1229
|
+
const getReadsVersion = () => {
|
|
1230
|
+
const tracker = committedReads.current;
|
|
1231
|
+
if (tracker && !tracker.stale && isTrackerStale(tracker, view)) readsVersion.current++;
|
|
1232
|
+
return readsVersion.current;
|
|
1233
|
+
};
|
|
1234
|
+
const subscribe = React.useCallback(
|
|
1235
|
+
(onChange) => {
|
|
1236
|
+
const unsubscribe = primary.subscribe(onChange);
|
|
1237
|
+
if (store === primary) return unsubscribe;
|
|
1238
|
+
const unsubscribeLocal = store.subscribe(onChange);
|
|
1239
|
+
return () => {
|
|
1240
|
+
unsubscribe();
|
|
1241
|
+
unsubscribeLocal();
|
|
1242
|
+
};
|
|
1243
|
+
},
|
|
1244
|
+
[primary, store]
|
|
1245
|
+
);
|
|
1246
|
+
const resourceVersion = React.useSyncExternalStore(subscribe, getReadsVersion, getReadsVersion);
|
|
1247
|
+
const lastResourceVersion = React.useRef(resourceVersion);
|
|
840
1248
|
React.useLayoutEffect(() => {
|
|
841
1249
|
if (isLegacy) {
|
|
842
|
-
throw new Error(
|
|
1250
|
+
throw new Error(
|
|
1251
|
+
"useRenderPipeline needs WebGPU: import Canvas from @react-three/fiber, not @react-three/fiber/legacy."
|
|
1252
|
+
);
|
|
843
1253
|
}
|
|
1254
|
+
if (!isOwner) return;
|
|
844
1255
|
if (!renderer || !scene || !camera) return;
|
|
845
1256
|
if (!("isWebGPURenderer" in renderer)) {
|
|
846
1257
|
throw new Error("useRenderPipeline: renderer is not a WebGPURenderer but isLegacy is false.");
|
|
847
1258
|
}
|
|
848
|
-
|
|
849
|
-
|
|
1259
|
+
let owner = ownerRef.current;
|
|
1260
|
+
if (owner && owner.store !== store) {
|
|
1261
|
+
releaseOwner(owner);
|
|
1262
|
+
owner = null;
|
|
1263
|
+
}
|
|
1264
|
+
if (!owner) owner = ownerRef.current = createOwner(store, () => rerun(true));
|
|
1265
|
+
claimOwnership(owner);
|
|
1266
|
+
const currentOwner = owner;
|
|
850
1267
|
try {
|
|
851
|
-
let pipeline = state.renderPipeline ?? null;
|
|
852
|
-
let currentPasses = { ...state.passes };
|
|
853
1268
|
let justCreatedPipeline = false;
|
|
854
|
-
if (!pipeline) {
|
|
855
|
-
|
|
1269
|
+
if (!owner.pipeline || owner.pipeline.renderer !== renderer) {
|
|
1270
|
+
disposeOwnerResources(owner);
|
|
1271
|
+
owner.pipeline = new THREE__namespace.RenderPipeline(renderer);
|
|
856
1272
|
justCreatedPipeline = true;
|
|
857
1273
|
}
|
|
858
|
-
const
|
|
1274
|
+
const pipeline = owner.pipeline;
|
|
1275
|
+
const cached = owner.scenePass;
|
|
1276
|
+
const cacheValid = !forceScenePassRebuildRef.current && cached !== null && cached.sceneUuid === scene.uuid && cached.cameraUuid === camera.uuid;
|
|
859
1277
|
let outgoingScenePass = null;
|
|
860
1278
|
let scenePass;
|
|
861
|
-
if (cacheValid) {
|
|
862
|
-
scenePass =
|
|
1279
|
+
if (cacheValid && cached) {
|
|
1280
|
+
scenePass = cached.node;
|
|
863
1281
|
} else {
|
|
864
|
-
outgoingScenePass =
|
|
1282
|
+
outgoingScenePass = cached?.node ?? null;
|
|
865
1283
|
scenePass = tsl.pass(scene, camera);
|
|
866
|
-
|
|
1284
|
+
owner.scenePass = { sceneUuid: scene.uuid, cameraUuid: camera.uuid, node: scenePass };
|
|
867
1285
|
forceScenePassRebuildRef.current = false;
|
|
868
1286
|
}
|
|
869
|
-
currentPasses.scenePass = scenePass;
|
|
870
1287
|
let outputNodeChanged = false;
|
|
871
1288
|
if (!pipeline.outputNode || justCreatedPipeline || pipeline.outputNode === outgoingScenePass) {
|
|
872
1289
|
pipeline.outputNode = scenePass;
|
|
873
1290
|
outputNodeChanged = true;
|
|
874
1291
|
}
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
const shouldRunCallbacks = justCreatedPipeline || !callbacksRanRef.current || !cacheValid;
|
|
1292
|
+
const readsChanged = lastResourceVersion.current !== resourceVersion;
|
|
1293
|
+
lastResourceVersion.current = resourceVersion;
|
|
1294
|
+
const shouldRunCallbacks = justCreatedPipeline || !callbacksRanRef.current || !cacheValid || readsChanged;
|
|
878
1295
|
if (shouldRunCallbacks) {
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
1296
|
+
let currentPasses = { scenePass };
|
|
1297
|
+
owner.passes = currentPasses;
|
|
1298
|
+
const tracker = createReadTracker("useRenderPipeline");
|
|
1299
|
+
const callbackState = () => Object.assign(
|
|
1300
|
+
createLazyCreatorState(store.getState(), primary, {
|
|
1301
|
+
reads: { observe: tracker.observe, nested: true },
|
|
1302
|
+
view
|
|
1303
|
+
}),
|
|
1304
|
+
{ renderPipeline: pipeline, passes: { ...currentPasses, scenePass }, renderer, isLegacy: false }
|
|
1305
|
+
);
|
|
1306
|
+
try {
|
|
1307
|
+
if (setupCBRef.current) {
|
|
1308
|
+
const setupResult = setupCBRef.current(callbackState());
|
|
1309
|
+
if (setupResult && typeof setupResult === "object") {
|
|
1310
|
+
currentPasses = { ...currentPasses, ...setupResult };
|
|
1311
|
+
owner.passes = currentPasses;
|
|
1312
|
+
}
|
|
890
1313
|
}
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
isLegacy: false
|
|
898
|
-
});
|
|
899
|
-
const mainResult = mainCBRef.current(freshState);
|
|
900
|
-
if (mainResult && typeof mainResult === "object") {
|
|
901
|
-
currentPasses = { ...currentPasses, ...mainResult };
|
|
902
|
-
set({ passes: currentPasses });
|
|
1314
|
+
if (mainCBRef.current) {
|
|
1315
|
+
const mainResult = mainCBRef.current(callbackState());
|
|
1316
|
+
if (mainResult && typeof mainResult === "object") {
|
|
1317
|
+
currentPasses = { ...currentPasses, ...mainResult };
|
|
1318
|
+
owner.passes = currentPasses;
|
|
1319
|
+
}
|
|
903
1320
|
}
|
|
1321
|
+
} finally {
|
|
1322
|
+
tracker.closed = true;
|
|
1323
|
+
committedReads.current = tracker;
|
|
904
1324
|
}
|
|
905
1325
|
outputNodeChanged = true;
|
|
906
1326
|
callbacksRanRef.current = true;
|
|
907
1327
|
}
|
|
908
1328
|
if (outputNodeChanged) pipeline.needsUpdate = true;
|
|
1329
|
+
publishOwner(owner);
|
|
909
1330
|
if (outgoingScenePass && outgoingScenePass !== scenePass) outgoingScenePass.dispose();
|
|
910
1331
|
} catch (error) {
|
|
1332
|
+
publishOwner(owner);
|
|
911
1333
|
console.warn("[useRenderPipeline] Setup failed; the render pipeline was not configured:", error);
|
|
912
1334
|
}
|
|
913
|
-
|
|
1335
|
+
return () => {
|
|
1336
|
+
if (persistRef.current) currentOwner.mounted = false;
|
|
1337
|
+
else scheduleRelease(currentOwner);
|
|
1338
|
+
};
|
|
1339
|
+
}, [store, primary, view, renderer, scene, camera, isLegacy, isOwner, rebuildVersion, resourceVersion, rerun]);
|
|
914
1340
|
const passes = extension.useThree((s) => s.passes) ?? NO_PASSES;
|
|
915
1341
|
const renderPipeline = extension.useThree((s) => s.renderPipeline) ?? null;
|
|
916
1342
|
const actions = { passes, clearPasses, reset, rebuild };
|