@react-three/tsl 10.0.0-canary.04087d8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs ADDED
@@ -0,0 +1,1208 @@
1
+ import { registerRootExtension, getTextureView, useStore, useThree, setRenderOverride } from '@react-three/fiber/extension';
2
+ import { uniform, pass } from 'three/tsl';
3
+ import * as THREE from 'three/webgpu';
4
+ import { Color } from 'three/webgpu';
5
+ import * as React from 'react';
6
+ import { useMemo, useRef, useCallback, useSyncExternalStore, useState, useEffect, useLayoutEffect } from 'react';
7
+ import { dequal } from 'dequal/lite';
8
+ import 'zustand/shallow';
9
+
10
+ const RESOURCE_KINDS = ["nodes", "uniforms", "buffers", "gpuStorage"];
11
+ const ROOT_SCOPE = "";
12
+ const registries = /* @__PURE__ */ new WeakMap();
13
+ function getKindRegistry(store, kind) {
14
+ let registry = registries.get(store);
15
+ if (!registry) {
16
+ registry = {
17
+ nodes: { valid: /* @__PURE__ */ new Set(), staged: /* @__PURE__ */ new Map() },
18
+ uniforms: { valid: /* @__PURE__ */ new Set(), staged: /* @__PURE__ */ new Map() },
19
+ buffers: { valid: /* @__PURE__ */ new Set(), staged: /* @__PURE__ */ new Map() },
20
+ gpuStorage: { valid: /* @__PURE__ */ new Set(), staged: /* @__PURE__ */ new Map() }
21
+ };
22
+ registries.set(store, registry);
23
+ }
24
+ return registry[kind];
25
+ }
26
+ function resolvePrimary$1(store) {
27
+ return store.getState().primaryStore ?? store;
28
+ }
29
+ function isScopeValid(store, kind, scopeKey) {
30
+ return getKindRegistry(store, kind).valid.has(scopeKey);
31
+ }
32
+ function peekStaged(store, kind, scopeKey) {
33
+ return getKindRegistry(store, kind).staged.get(scopeKey);
34
+ }
35
+ function stageEntries(store, kind, scopeKey, entries) {
36
+ const { staged } = getKindRegistry(store, kind);
37
+ const current = staged.get(scopeKey);
38
+ if (current) Object.assign(current, entries);
39
+ else staged.set(scopeKey, { ...entries });
40
+ }
41
+ function readCommittedScope(map, scopeKey, isLeaf) {
42
+ if (scopeKey === ROOT_SCOPE) {
43
+ const leaves = {};
44
+ for (const [key, value] of Object.entries(map)) if (isLeaf(value)) leaves[key] = value;
45
+ return leaves;
46
+ }
47
+ const scopeData = map[scopeKey];
48
+ if (scopeData && typeof scopeData === "object" && !isLeaf(scopeData)) return scopeData;
49
+ return {};
50
+ }
51
+ function withStagedOverlay(store, kind, committed) {
52
+ const { staged } = getKindRegistry(store, kind);
53
+ if (staged.size === 0) return committed;
54
+ const view = { ...committed };
55
+ for (const [scopeKey, entries] of staged) {
56
+ if (scopeKey === ROOT_SCOPE) Object.assign(view, entries);
57
+ else view[scopeKey] = { ...view[scopeKey], ...entries };
58
+ }
59
+ return view;
60
+ }
61
+ function flushStagedScope(store, kind, scopeKey, isLeaf) {
62
+ const registry = getKindRegistry(store, kind);
63
+ const staged = registry.staged.get(scopeKey);
64
+ if (!staged) return;
65
+ registry.staged.delete(scopeKey);
66
+ const replace = !registry.valid.has(scopeKey);
67
+ store.setState((state) => {
68
+ const map = state[kind];
69
+ if (scopeKey === ROOT_SCOPE) {
70
+ if (!replace) return { [kind]: { ...map, ...staged } };
71
+ const next = {};
72
+ for (const [key, value] of Object.entries(map)) if (!isLeaf(value)) next[key] = value;
73
+ return { [kind]: { ...next, ...staged } };
74
+ }
75
+ const current = replace ? {} : map[scopeKey] ?? {};
76
+ return { [kind]: { ...map, [scopeKey]: { ...current, ...staged } } };
77
+ });
78
+ registry.valid.add(scopeKey);
79
+ }
80
+ function invalidateResource(store, kind, scopeKey) {
81
+ const registry = getKindRegistry(store, kind);
82
+ if (scopeKey === void 0) {
83
+ registry.valid.clear();
84
+ registry.staged.clear();
85
+ } else {
86
+ registry.valid.delete(scopeKey);
87
+ registry.staged.delete(scopeKey);
88
+ }
89
+ }
90
+ function rebuildResource(store, kind, userScope) {
91
+ const primary = resolvePrimary$1(store);
92
+ invalidateResource(primary, kind, userScope === "root" ? ROOT_SCOPE : userScope);
93
+ primary.setState((state) => ({ _hmrVersion: state._hmrVersion + 1 }));
94
+ }
95
+ function invalidateAllResources(store) {
96
+ for (const kind of RESOURCE_KINDS) invalidateResource(store, kind);
97
+ }
98
+ function removeResourceEntries(store, kind, names, scope, isLeaf) {
99
+ const staged = peekStaged(store, kind, scope ?? ROOT_SCOPE);
100
+ if (staged) for (const name of names) delete staged[name];
101
+ store.setState((state) => {
102
+ const map = state[kind];
103
+ if (scope) {
104
+ const currentScope = { ...map[scope] };
105
+ for (const name of names) delete currentScope[name];
106
+ return { [kind]: { ...map, [scope]: currentScope } };
107
+ }
108
+ const next = { ...map };
109
+ for (const name of names) if (isLeaf(next[name])) delete next[name];
110
+ return { [kind]: next };
111
+ });
112
+ }
113
+ function clearResourceEntries(store, kind, scope, isLeaf) {
114
+ const registry = getKindRegistry(store, kind);
115
+ if (scope && scope !== "root") registry.staged.delete(scope);
116
+ else if (scope === "root") registry.staged.delete(ROOT_SCOPE);
117
+ else registry.staged.clear();
118
+ store.setState((state) => {
119
+ const map = state[kind];
120
+ if (scope && scope !== "root") {
121
+ const { [scope]: _, ...rest } = map;
122
+ return { [kind]: rest };
123
+ }
124
+ if (scope === "root") {
125
+ const next = {};
126
+ for (const [key, value] of Object.entries(map)) if (!isLeaf(value)) next[key] = value;
127
+ return { [kind]: next };
128
+ }
129
+ return { [kind]: {} };
130
+ });
131
+ }
132
+
133
+ function clearHmrCaches(store) {
134
+ const primary = store.getState().primaryStore ?? store;
135
+ invalidateAllResources(primary);
136
+ primary.setState((state) => ({ _hmrVersion: state._hmrVersion + 1 }));
137
+ }
138
+
139
+ const isObjectLike = (value) => value !== null && (typeof value === "object" || typeof value === "function");
140
+ const hasValidLegacyNodeMarkers = (value) => {
141
+ const hasUuid = "uuid" in value;
142
+ const hasNodeType = "nodeType" in value;
143
+ const uuid = Reflect.get(value, "uuid");
144
+ const nodeType = Reflect.get(value, "nodeType");
145
+ return (hasUuid || hasNodeType) && (!hasUuid || uuid === void 0 || typeof uuid === "string") && (!hasNodeType || nodeType === void 0 || nodeType === null || typeof nodeType === "string");
146
+ };
147
+ const isUniformNode = (value) => isObjectLike(value) && "isUniformNode" in value && value.isUniformNode === true;
148
+ const isTSLNode = (value) => isObjectLike(value) && (Reflect.get(value, "isNode") === true || Reflect.get(value, "shaderNode") !== void 0 && typeof Reflect.get(value, "id") === "number" || hasValidLegacyNodeMarkers(value));
149
+ const isBufferLike = (value) => ArrayBuffer.isView(value) && !(value instanceof DataView) || isObjectLike(value) && ("isBufferAttribute" in value || "isInterleavedBufferAttribute" in value || isTSLNode(value));
150
+ const isStorageLike = (value) => isObjectLike(value) && ("isTexture" in value || "isData3DTexture" in value || isTSLNode(value));
151
+
152
+ function scopedNodeName(scope, name) {
153
+ const raw = scope ? `${scope}_${name}` : name;
154
+ const safe = raw.replace(/[^A-Za-z0-9_]/g, "_");
155
+ return /^[A-Za-z_]/.test(safe) ? safe : `_${safe}`;
156
+ }
157
+ function extractTSLValue(value) {
158
+ if (value === null || value === void 0) return value;
159
+ if (typeof value !== "object") return value;
160
+ const node = value;
161
+ if (!node.isNode) return value;
162
+ if (node.isConstNode) {
163
+ return node.value;
164
+ }
165
+ if (node.isVarNode && node.node) {
166
+ const inner = extractTSLValue(node.node);
167
+ return inner === node.node ? value : inner;
168
+ }
169
+ if ("value" in node) {
170
+ let extractedValue = node.value;
171
+ if (typeof node.traverse === "function") {
172
+ node.traverse((n) => {
173
+ if (n.isConstNode) {
174
+ extractedValue = n.value;
175
+ }
176
+ });
177
+ }
178
+ return extractedValue;
179
+ }
180
+ return value;
181
+ }
182
+ function vectorize(inObject) {
183
+ if (inObject === null || inObject === void 0) return inObject;
184
+ if (typeof inObject === "string") {
185
+ const isHexColor = /^(#|0x)[0-9a-f]{3,8}$/i.test(inObject);
186
+ const isNamedColor = /^[a-z]+$/i.test(inObject);
187
+ if (isHexColor || isNamedColor) {
188
+ return new THREE.Color(inObject);
189
+ }
190
+ return inObject;
191
+ }
192
+ if (typeof inObject !== "object") return inObject;
193
+ const obj = inObject;
194
+ if (obj.isNode) {
195
+ return extractTSLValue(inObject);
196
+ }
197
+ if (obj.isVector2 || obj.isVector3 || obj.isVector4) return inObject;
198
+ if (obj.isMatrix2 || obj.isMatrix3 || obj.isMatrix4) return inObject;
199
+ if (obj.isColor || obj.isEuler || obj.isQuaternion || obj.isSpherical) return inObject;
200
+ if ("r" in obj && "g" in obj && "b" in obj && typeof obj.r === "number" && typeof obj.g === "number" && typeof obj.b === "number") {
201
+ const scale = obj.r > 1 || obj.g > 1 || obj.b > 1 ? 1 / 255 : 1;
202
+ return new THREE.Color(obj.r * scale, obj.g * scale, obj.b * scale);
203
+ }
204
+ if ("x" in obj && "y" in obj && typeof obj.x === "number" && typeof obj.y === "number") {
205
+ if ("w" in obj && typeof obj.w === "number" && "z" in obj && typeof obj.z === "number") {
206
+ return new THREE.Vector4(obj.x, obj.y, obj.z, obj.w);
207
+ }
208
+ if ("z" in obj && typeof obj.z === "number") {
209
+ return new THREE.Vector3(obj.x, obj.y, obj.z);
210
+ }
211
+ return new THREE.Vector2(obj.x, obj.y);
212
+ }
213
+ return inObject;
214
+ }
215
+
216
+ function createUniform(inName, node, scope) {
217
+ if (node.type === "UniformNode") return node;
218
+ const inValue = vectorize(node);
219
+ const newUniform = uniform(inValue);
220
+ if (typeof newUniform.setName === "function") {
221
+ newUniform.setName(scopedNodeName(scope, inName));
222
+ }
223
+ return newUniform;
224
+ }
225
+
226
+ const primaries = /* @__PURE__ */ new Set();
227
+ let config = null;
228
+ function trackPrimary(store) {
229
+ primaries.add(store);
230
+ if (config) applyConfig(store, config);
231
+ }
232
+ function untrackPrimary(store) {
233
+ primaries.delete(store);
234
+ }
235
+ function applyConfig(store, config2) {
236
+ const add = (scope, inputs) => {
237
+ if (!inputs) return;
238
+ const scopeKey = scope ?? ROOT_SCOPE;
239
+ const existing = readCommittedScope(store.getState().uniforms, scopeKey, isUniformNode);
240
+ const fresh = {};
241
+ for (const [name, value] of Object.entries(inputs)) {
242
+ if (!(name in existing)) fresh[name] = createUniform(name, value, scope);
243
+ }
244
+ if (!Object.keys(fresh).length) return;
245
+ stageEntries(store, "uniforms", scopeKey, fresh);
246
+ flushStagedScope(store, "uniforms", scopeKey, isUniformNode);
247
+ };
248
+ add(void 0, config2.uniforms);
249
+ for (const [scope, inputs] of Object.entries(config2.scopes ?? {})) add(scope, inputs);
250
+ }
251
+ function configureTSL(next) {
252
+ config = {
253
+ uniforms: { ...config?.uniforms, ...next.uniforms },
254
+ scopes: { ...config?.scopes, ...next.scopes }
255
+ };
256
+ for (const store of primaries) applyConfig(store, config);
257
+ }
258
+
259
+ const SHARED_KEYS = ["uniforms", "nodes", "buffers", "gpuStorage", "_hmrVersion"];
260
+ const following = /* @__PURE__ */ new WeakMap();
261
+ function createResourceState() {
262
+ return { uniforms: {}, nodes: {}, buffers: {}, gpuStorage: {}, _hmrVersion: 0 };
263
+ }
264
+ function setup(store) {
265
+ const primary = store.getState().primaryStore;
266
+ if (!primary || primary === store) {
267
+ store.setState(createResourceState());
268
+ trackPrimary(store);
269
+ return;
270
+ }
271
+ followPrimary(store, primary);
272
+ }
273
+ function followPrimary(store, primary) {
274
+ const sync = () => {
275
+ const source = primary.getState();
276
+ const target = store.getState();
277
+ let patch = null;
278
+ for (const key of SHARED_KEYS) {
279
+ if (target[key] !== source[key]) (patch ?? (patch = {}))[key] = source[key];
280
+ }
281
+ if (patch) store.setState(patch);
282
+ };
283
+ sync();
284
+ following.set(store, primary.subscribe(sync));
285
+ }
286
+ function dispose(store) {
287
+ untrackPrimary(store);
288
+ following.get(store)?.();
289
+ following.delete(store);
290
+ }
291
+ let registered = false;
292
+ function ensureTSLExtension() {
293
+ if (registered) return;
294
+ registered = true;
295
+ registerRootExtension({
296
+ name: "@react-three/tsl",
297
+ setup,
298
+ dispose,
299
+ // Resolves primaryStore itself, so every canvas sharing a renderer refreshes the same resources.
300
+ hmr: clearHmrCaches
301
+ });
302
+ }
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
+
390
+ var __defProp = Object.defineProperty;
391
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
392
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
393
+ var _a, _b, _c, _d;
394
+ const INTERNAL_DATA = Symbol("ScopedStore.data");
395
+ const INTERNAL_IS_LEAF = Symbol("ScopedStore.isLeaf");
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;
399
+ const _ScopedStore = class _ScopedStore {
400
+ constructor(data, isLeaf, reads) {
401
+ /** @internal */
402
+ __publicField(this, _d);
403
+ /** @internal */
404
+ __publicField(this, _c);
405
+ /** @internal */
406
+ __publicField(this, _b);
407
+ /** @internal */
408
+ __publicField(this, _a);
409
+ this[INTERNAL_DATA] = data;
410
+ this[INTERNAL_IS_LEAF] = isLeaf;
411
+ this[INTERNAL_READS] = reads;
412
+ return new Proxy(this, {
413
+ get(target, prop, receiver) {
414
+ if (typeof prop === "string") {
415
+ if (prop === "scope" || prop === "has" || prop === "keys") {
416
+ return Reflect.get(target, prop, receiver);
417
+ }
418
+ return readEntry(target, prop);
419
+ }
420
+ return Reflect.get(target, prop, receiver);
421
+ },
422
+ has(target, prop) {
423
+ return typeof prop === "string" ? target.has(prop) : Reflect.has(target, prop);
424
+ },
425
+ ownKeys(target) {
426
+ observeKeys(target);
427
+ return Reflect.ownKeys(target[INTERNAL_DATA]);
428
+ },
429
+ getOwnPropertyDescriptor(target, prop) {
430
+ if (typeof prop === "string" && prop in target[INTERNAL_DATA]) {
431
+ return {
432
+ configurable: true,
433
+ enumerable: true,
434
+ value: target[INTERNAL_DATA][prop]
435
+ };
436
+ }
437
+ return void 0;
438
+ }
439
+ });
440
+ }
441
+ /**
442
+ * Access a nested scope by key.
443
+ * If the key doesn't exist or isn't a scope object, returns an empty ScopedStore.
444
+ */
445
+ scope(key) {
446
+ const value = this[INTERNAL_DATA][key];
447
+ const isLeaf = this[INTERNAL_IS_LEAF];
448
+ const reads = this[INTERNAL_READS];
449
+ const scope = value && typeof value === "object" && !isLeaf(value) ? value : {};
450
+ const child = reads ? { ...reads, path: [...reads.path, key] } : void 0;
451
+ return new _ScopedStore(scope, isLeaf, child);
452
+ }
453
+ /**
454
+ * Check if a key exists in the store.
455
+ */
456
+ has(key) {
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;
461
+ }
462
+ /**
463
+ * Get all keys in the store.
464
+ */
465
+ keys() {
466
+ observeKeys(this);
467
+ return Object.keys(this[INTERNAL_DATA]);
468
+ }
469
+ };
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
+ }
492
+ function createScopedStore(data, isLeaf) {
493
+ return new ScopedStore(data, isLeaf);
494
+ }
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);
552
+ let _uniforms = null;
553
+ let _nodes = null;
554
+ let _buffers = null;
555
+ let _gpuStorage = null;
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 = {
564
+ uniforms: {
565
+ get() {
566
+ return _uniforms ?? (_uniforms = wrap("uniforms", isUniformNode));
567
+ }
568
+ },
569
+ nodes: {
570
+ get() {
571
+ return _nodes ?? (_nodes = wrap("nodes", isTSLNode));
572
+ }
573
+ },
574
+ buffers: {
575
+ get() {
576
+ return _buffers ?? (_buffers = wrap("buffers", isBufferLike));
577
+ }
578
+ },
579
+ gpuStorage: {
580
+ get() {
581
+ return _gpuStorage ?? (_gpuStorage = wrap("gpuStorage", isStorageLike));
582
+ }
583
+ }
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);
594
+ }
595
+
596
+ function resolvePrimary(local) {
597
+ return local.getState().primaryStore ?? local;
598
+ }
599
+ function usePrimaryStore() {
600
+ const local = useStore();
601
+ const primary = local.getState().primaryStore;
602
+ return useMemo(() => primary ?? local, [primary, local]);
603
+ }
604
+ function usePrimaryThree(selector = (state) => state, equalityFn) {
605
+ const local = useStore();
606
+ const primary = resolvePrimary(local);
607
+ return primary(selector, equalityFn);
608
+ }
609
+
610
+ function useCompareMemoize(value, deep) {
611
+ const ref = useRef(value);
612
+ const compare = dequal ;
613
+ if (!compare(value, ref.current)) {
614
+ ref.current = value;
615
+ }
616
+ return ref.current;
617
+ }
618
+
619
+ const useIsomorphicLayoutEffect = typeof window !== "undefined" && (window.document?.createElement || window.navigator?.product === "ReactNative") ? React.useLayoutEffect : React.useEffect;
620
+
621
+ function useScopedResource(options) {
622
+ const { store, kind, scope, isLeaf, create, prepare, reconcile, input } = options;
623
+ const scopeKey = scope ?? ROOT_SCOPE;
624
+ const version = store((s) => s._hmrVersion);
625
+ const entries = useMemo(() => {
626
+ const staged = peekStaged(store, kind, scopeKey);
627
+ const reusable = isScopeValid(store, kind, scopeKey);
628
+ const map = store.getState()[kind];
629
+ const committed = reusable ? readCommittedScope(map, scopeKey, isLeaf) : void 0;
630
+ const result = {};
631
+ let fresh = null;
632
+ for (const [name, candidate] of Object.entries(create())) {
633
+ const existing = staged?.[name] ?? committed?.[name];
634
+ if (existing !== void 0) {
635
+ result[name] = existing;
636
+ reconcile?.(existing, candidate);
637
+ } else {
638
+ const value = prepare ? prepare(name, candidate) : candidate;
639
+ result[name] = value;
640
+ (fresh ?? (fresh = {}))[name] = value;
641
+ }
642
+ }
643
+ if (fresh) stageEntries(store, kind, scopeKey, fresh);
644
+ return result;
645
+ }, [store, kind, scopeKey, version, input]);
646
+ useIsomorphicLayoutEffect(() => {
647
+ flushStagedScope(store, kind, scopeKey, isLeaf);
648
+ });
649
+ return entries;
650
+ }
651
+
652
+ function useUniforms(creatorOrScope, scope) {
653
+ const store = usePrimaryStore();
654
+ const removeUniforms = useCallback(
655
+ (names, targetScope) => removeResourceEntries(store, "uniforms", Array.isArray(names) ? names : [names], targetScope, isUniformNode),
656
+ [store]
657
+ );
658
+ const clearUniforms = useCallback(
659
+ (targetScope) => clearResourceEntries(store, "uniforms", targetScope, isUniformNode),
660
+ [store]
661
+ );
662
+ const rebuildUniforms = useCallback(
663
+ (targetScope) => rebuildResource(store, "uniforms", targetScope),
664
+ [store]
665
+ );
666
+ const processedInput = useMemo(() => {
667
+ let raw = creatorOrScope;
668
+ if (typeof creatorOrScope === "function") {
669
+ const wrappedState = createLazyCreatorState(store.getState(), store, { reads: warnMissingReads("useUniforms") });
670
+ raw = creatorOrScope(wrappedState);
671
+ }
672
+ if (raw && typeof raw === "object" && !Array.isArray(raw)) {
673
+ const normalized = {};
674
+ for (const [key, value] of Object.entries(raw)) {
675
+ normalized[key] = vectorize(value);
676
+ }
677
+ return { candidates: raw, normalized };
678
+ }
679
+ return { candidates: raw, normalized: raw };
680
+ }, [creatorOrScope, store]);
681
+ const memoizedInput = useCompareMemoize(processedInput.normalized);
682
+ const isReader = memoizedInput === void 0 || typeof memoizedInput === "string";
683
+ const storeUniforms = usePrimaryThree((s) => s.uniforms);
684
+ const created = useScopedResource({
685
+ store,
686
+ kind: "uniforms",
687
+ scope: isReader ? void 0 : scope,
688
+ isLeaf: isUniformNode,
689
+ input: memoizedInput,
690
+ create: () => {
691
+ if (isReader) return {};
692
+ if (typeof processedInput.candidates !== "object" || processedInput.candidates === null)
693
+ throw new Error("Invalid uniform input");
694
+ return processedInput.candidates;
695
+ },
696
+ prepare: (name, candidate) => createUniform(name, candidate, scope),
697
+ reconcile: (existing, candidate) => {
698
+ const existingVal = existing.value;
699
+ const newVal = vectorize(candidate);
700
+ let equals = newVal === existingVal;
701
+ if (!equals && hasEqualsMethod(existingVal) && hasEqualsMethod(newVal)) {
702
+ if (isThreeVector(existingVal) && isThreeVector(newVal)) {
703
+ equals = vectorEquals(existingVal, newVal);
704
+ } else if (isSameThreeType(existingVal, newVal)) {
705
+ equals = existingVal.equals(newVal);
706
+ }
707
+ }
708
+ if (!equals) existing.value = newVal;
709
+ }
710
+ });
711
+ let uniforms = created;
712
+ if (memoizedInput === void 0) {
713
+ uniforms = storeUniforms;
714
+ } else if (typeof memoizedInput === "string") {
715
+ const scopeData = storeUniforms[memoizedInput];
716
+ uniforms = scopeData && !isUniformNode(scopeData) ? scopeData : {};
717
+ }
718
+ return { ...uniforms, removeUniforms, clearUniforms, rebuildUniforms };
719
+ }
720
+ function rebuildAllUniforms(store, scope) {
721
+ rebuildResource(store, "uniforms", scope);
722
+ }
723
+ function isThreeVector(inVector) {
724
+ if (!inVector) return false;
725
+ return inVector.isVector2 || inVector.isVector3 || inVector.isVector4;
726
+ }
727
+ function vectorEquals(a, b) {
728
+ if (a.isVector2 && b.isVector2) {
729
+ return a.equals(b);
730
+ }
731
+ if (a.isVector3 && b.isVector3) {
732
+ return a.equals(b);
733
+ }
734
+ if (a.isVector4 && b.isVector4) {
735
+ return a.equals(b);
736
+ }
737
+ return false;
738
+ }
739
+ function hasEqualsMethod(value) {
740
+ return value !== null && typeof value === "object" && "equals" in value && typeof value.equals === "function";
741
+ }
742
+ function isSameThreeType(a, b) {
743
+ const obj_a = a;
744
+ const obj_b = b;
745
+ return obj_a.isColor && obj_b.isColor || obj_a.isMatrix2 && obj_b.isMatrix2 || obj_a.isMatrix3 && obj_b.isMatrix3 || obj_a.isMatrix4 && obj_b.isMatrix4 || obj_a.isEuler && obj_b.isEuler || obj_a.isQuaternion && obj_b.isQuaternion;
746
+ }
747
+
748
+ const uniformOf = uniform;
749
+ function useUniform(name, value) {
750
+ const store = usePrimaryStore();
751
+ const memoizedInput = useCompareMemoize(value === void 0 ? name : { [name]: normalizeValue(value) });
752
+ const registered = useScopedResource({
753
+ store,
754
+ kind: "uniforms",
755
+ scope: void 0,
756
+ isLeaf: isUniformNode,
757
+ input: memoizedInput,
758
+ create: () => value === void 0 ? {} : { [name]: value },
759
+ prepare: createNamedUniform,
760
+ reconcile: (existing2) => {
761
+ if (value === void 0 || isUniformNode(value)) return;
762
+ const next = normalizeValue(value);
763
+ if (isTSLNode(next)) return;
764
+ existing2.value = next;
765
+ }
766
+ });
767
+ if (registered[name]) return registered[name];
768
+ const existing = peekStaged(store, "uniforms", ROOT_SCOPE)?.[name] ?? store.getState().uniforms[name];
769
+ if (existing && isUniformNode(existing)) return existing;
770
+ throw new Error(
771
+ `[useUniform] Uniform "${name}" not found. Create it first with: useUniform('${name}', initialValue)`
772
+ );
773
+ }
774
+ function normalizeValue(value) {
775
+ return typeof value === "string" ? new Color(value) : vectorize(value);
776
+ }
777
+ function createNamedUniform(name, value) {
778
+ let node;
779
+ if (isUniformNode(value)) {
780
+ node = value;
781
+ } else if (isTSLNode(value)) {
782
+ node = uniformOf(value);
783
+ } else if (typeof value === "string") {
784
+ node = uniform(new Color(value));
785
+ } else {
786
+ node = uniformOf(value);
787
+ }
788
+ if (typeof node.setName === "function") {
789
+ node.setName(name);
790
+ }
791
+ return node;
792
+ }
793
+
794
+ function useNodes(creatorOrScope, scope) {
795
+ const store = usePrimaryStore();
796
+ const removeNodes = useCallback(
797
+ (names, targetScope) => removeResourceEntries(store, "nodes", Array.isArray(names) ? names : [names], targetScope, isTSLNode),
798
+ [store]
799
+ );
800
+ const clearNodes = useCallback(
801
+ (targetScope) => clearResourceEntries(store, "nodes", targetScope, isTSLNode),
802
+ [store]
803
+ );
804
+ const rebuildNodes = useCallback(
805
+ (targetScope) => rebuildResource(store, "nodes", targetScope),
806
+ [store]
807
+ );
808
+ const isReader = creatorOrScope === void 0 || typeof creatorOrScope === "string";
809
+ const storeNodes = usePrimaryThree((s) => s.nodes);
810
+ const created = useScopedResource({
811
+ store,
812
+ kind: "nodes",
813
+ scope: isReader ? void 0 : scope,
814
+ isLeaf: isTSLNode,
815
+ create: () => {
816
+ if (isReader) return {};
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;
827
+ },
828
+ prepare: (name, node) => {
829
+ const setName = Reflect.get(node, "setName");
830
+ if (typeof setName === "function") setName.call(node, scopedNodeName(scope, name));
831
+ return node;
832
+ }
833
+ });
834
+ let nodes = created;
835
+ if (creatorOrScope === void 0) {
836
+ nodes = storeNodes;
837
+ } else if (typeof creatorOrScope === "string") {
838
+ const scopeData = storeNodes[creatorOrScope];
839
+ nodes = scopeData && !isTSLNode(scopeData) ? scopeData : {};
840
+ }
841
+ return { ...nodes, removeNodes, clearNodes, rebuildNodes };
842
+ }
843
+ function rebuildAllNodes(store, scope) {
844
+ rebuildResource(store, "nodes", scope);
845
+ }
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();
878
+ const store = usePrimaryStore();
879
+ const view = useMemo(() => createResourceView(store, local), [store, local]);
880
+ const hmrVersion = usePrimaryThree((s) => s._hmrVersion);
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;
940
+ }
941
+
942
+ const disposeBuffer = (buffer) => {
943
+ if (buffer === null || typeof buffer !== "object") return;
944
+ if ("dispose" in buffer && typeof buffer.dispose === "function") {
945
+ buffer.dispose();
946
+ }
947
+ };
948
+ function useBuffers(creatorOrScope, scope) {
949
+ const store = usePrimaryStore();
950
+ const removeBuffers = useCallback(
951
+ (names, targetScope) => removeResourceEntries(store, "buffers", Array.isArray(names) ? names : [names], targetScope, isBufferLike),
952
+ [store]
953
+ );
954
+ const clearBuffers = useCallback(
955
+ (targetScope) => clearResourceEntries(store, "buffers", targetScope, isBufferLike),
956
+ [store]
957
+ );
958
+ const rebuildBuffers = useCallback(
959
+ (targetScope) => rebuildResource(store, "buffers", targetScope),
960
+ [store]
961
+ );
962
+ const disposeBuffers = useCallback(
963
+ (names, targetScope) => {
964
+ const nameArray = Array.isArray(names) ? names : [names];
965
+ const state = store.getState();
966
+ for (const name of nameArray) {
967
+ const buffer = targetScope ? state.buffers[targetScope]?.[name] : state.buffers[name];
968
+ if (buffer && isBufferLike(buffer)) {
969
+ disposeBuffer(buffer);
970
+ }
971
+ }
972
+ removeBuffers(names, targetScope);
973
+ },
974
+ [store, removeBuffers]
975
+ );
976
+ const isReader = creatorOrScope === void 0 || typeof creatorOrScope === "string";
977
+ const storeBuffers = usePrimaryThree((s) => s.buffers);
978
+ const created = useScopedResource({
979
+ store,
980
+ kind: "buffers",
981
+ scope: isReader ? void 0 : scope,
982
+ isLeaf: isBufferLike,
983
+ create: () => {
984
+ if (isReader) return {};
985
+ return creatorOrScope(
986
+ createLazyCreatorState(store.getState(), store, { reads: warnMissingReads("useBuffers") })
987
+ );
988
+ },
989
+ prepare: (name, buffer) => {
990
+ const setName = Reflect.get(buffer, "setName");
991
+ if (typeof setName === "function") setName.call(buffer, scopedNodeName(scope, name));
992
+ return buffer;
993
+ }
994
+ });
995
+ let buffers = created;
996
+ if (creatorOrScope === void 0) {
997
+ buffers = storeBuffers;
998
+ } else if (typeof creatorOrScope === "string") {
999
+ const scopeData = storeBuffers[creatorOrScope];
1000
+ buffers = scopeData && !isBufferLike(scopeData) ? scopeData : {};
1001
+ }
1002
+ return { ...buffers, removeBuffers, clearBuffers, rebuildBuffers, disposeBuffers };
1003
+ }
1004
+ function rebuildAllBuffers(store, scope) {
1005
+ rebuildResource(store, "buffers", scope);
1006
+ }
1007
+
1008
+ const disposeStorage = (storage) => {
1009
+ if (storage === null || typeof storage !== "object") return;
1010
+ if ("dispose" in storage && typeof storage.dispose === "function") {
1011
+ storage.dispose();
1012
+ }
1013
+ };
1014
+ function useGPUStorage(creatorOrScope, scope) {
1015
+ const store = usePrimaryStore();
1016
+ const removeStorage = useCallback(
1017
+ (names, targetScope) => removeResourceEntries(store, "gpuStorage", Array.isArray(names) ? names : [names], targetScope, isStorageLike),
1018
+ [store]
1019
+ );
1020
+ const clearStorage = useCallback(
1021
+ (targetScope) => clearResourceEntries(store, "gpuStorage", targetScope, isStorageLike),
1022
+ [store]
1023
+ );
1024
+ const rebuildStorage = useCallback(
1025
+ (targetScope) => rebuildResource(store, "gpuStorage", targetScope),
1026
+ [store]
1027
+ );
1028
+ const disposeStorageFn = useCallback(
1029
+ (names, targetScope) => {
1030
+ const nameArray = Array.isArray(names) ? names : [names];
1031
+ const state = store.getState();
1032
+ for (const name of nameArray) {
1033
+ const storage = targetScope ? state.gpuStorage[targetScope]?.[name] : state.gpuStorage[name];
1034
+ if (storage && isStorageLike(storage)) {
1035
+ disposeStorage(storage);
1036
+ }
1037
+ }
1038
+ removeStorage(names, targetScope);
1039
+ },
1040
+ [store, removeStorage]
1041
+ );
1042
+ const isReader = creatorOrScope === void 0 || typeof creatorOrScope === "string";
1043
+ const storeStorage = usePrimaryThree((s) => s.gpuStorage);
1044
+ const created = useScopedResource({
1045
+ store,
1046
+ kind: "gpuStorage",
1047
+ scope: isReader ? void 0 : scope,
1048
+ isLeaf: isStorageLike,
1049
+ create: () => {
1050
+ if (isReader) return {};
1051
+ return creatorOrScope(
1052
+ createLazyCreatorState(store.getState(), store, { reads: warnMissingReads("useGPUStorage") })
1053
+ );
1054
+ },
1055
+ prepare: (name, storage) => {
1056
+ const label = scopedNodeName(scope, name);
1057
+ const setName = Reflect.get(storage, "setName");
1058
+ if (typeof setName === "function") setName.call(storage, label);
1059
+ if ("name" in storage && typeof storage.name === "string") {
1060
+ storage.name = label;
1061
+ }
1062
+ return storage;
1063
+ }
1064
+ });
1065
+ let gpuStorage = created;
1066
+ if (creatorOrScope === void 0) {
1067
+ gpuStorage = storeStorage;
1068
+ } else if (typeof creatorOrScope === "string") {
1069
+ const scopeData = storeStorage[creatorOrScope];
1070
+ gpuStorage = scopeData && !isStorageLike(scopeData) ? scopeData : {};
1071
+ }
1072
+ return {
1073
+ ...gpuStorage,
1074
+ removeStorage,
1075
+ clearStorage,
1076
+ rebuildStorage,
1077
+ disposeStorage: disposeStorageFn
1078
+ };
1079
+ }
1080
+ function rebuildAllStorage(store, scope) {
1081
+ rebuildResource(store, "gpuStorage", scope);
1082
+ }
1083
+
1084
+ const NO_PASSES = Object.freeze({});
1085
+ function renderThroughPipeline(store) {
1086
+ return () => {
1087
+ const pipeline = store.getState().renderPipeline;
1088
+ if (pipeline?.render) pipeline.render();
1089
+ };
1090
+ }
1091
+ function useRenderPipeline(mainCB, setupCB) {
1092
+ const store = useStore();
1093
+ const { scene, camera, renderer, isLegacy } = useThree();
1094
+ const callbacksRanRef = useRef(false);
1095
+ const scenePassCacheRef = useRef(null);
1096
+ const forceScenePassRebuildRef = useRef(false);
1097
+ const mainCBRef = useRef(mainCB);
1098
+ const setupCBRef = useRef(setupCB);
1099
+ mainCBRef.current = mainCB;
1100
+ setupCBRef.current = setupCB;
1101
+ const [rebuildVersion, setRebuildVersion] = useState(0);
1102
+ useEffect(() => {
1103
+ callbacksRanRef.current = false;
1104
+ forceScenePassRebuildRef.current = true;
1105
+ }, []);
1106
+ const clearPasses = useCallback(() => {
1107
+ store.setState({ passes: {} });
1108
+ }, [store]);
1109
+ const reset = useCallback(() => {
1110
+ store.setState({
1111
+ renderPipeline: null,
1112
+ passes: {}
1113
+ });
1114
+ setRenderOverride(store, null);
1115
+ scenePassCacheRef.current?.scenePass.dispose();
1116
+ callbacksRanRef.current = false;
1117
+ scenePassCacheRef.current = null;
1118
+ forceScenePassRebuildRef.current = false;
1119
+ }, [store]);
1120
+ const rebuild = useCallback(() => {
1121
+ callbacksRanRef.current = false;
1122
+ forceScenePassRebuildRef.current = true;
1123
+ setRebuildVersion((v) => v + 1);
1124
+ }, []);
1125
+ useLayoutEffect(() => {
1126
+ if (isLegacy) {
1127
+ throw new Error("useRenderPipeline is only available with WebGPU renderer. Set renderer prop on Canvas.");
1128
+ }
1129
+ if (!renderer || !scene || !camera) return;
1130
+ if (!("isWebGPURenderer" in renderer)) {
1131
+ throw new Error("useRenderPipeline: renderer is not a WebGPURenderer but isLegacy is false.");
1132
+ }
1133
+ const state = store.getState();
1134
+ const set = store.setState;
1135
+ try {
1136
+ let pipeline = state.renderPipeline ?? null;
1137
+ let currentPasses = { ...state.passes };
1138
+ let justCreatedPipeline = false;
1139
+ if (!pipeline) {
1140
+ pipeline = new THREE.RenderPipeline(renderer);
1141
+ justCreatedPipeline = true;
1142
+ }
1143
+ const cacheValid = !forceScenePassRebuildRef.current && scenePassCacheRef.current && scenePassCacheRef.current.sceneUuid === scene.uuid && scenePassCacheRef.current.cameraUuid === camera.uuid;
1144
+ let outgoingScenePass = null;
1145
+ let scenePass;
1146
+ if (cacheValid) {
1147
+ scenePass = scenePassCacheRef.current.scenePass;
1148
+ } else {
1149
+ outgoingScenePass = scenePassCacheRef.current?.scenePass ?? null;
1150
+ scenePass = pass(scene, camera);
1151
+ scenePassCacheRef.current = { sceneUuid: scene.uuid, cameraUuid: camera.uuid, scenePass };
1152
+ forceScenePassRebuildRef.current = false;
1153
+ }
1154
+ currentPasses.scenePass = scenePass;
1155
+ let outputNodeChanged = false;
1156
+ if (!pipeline.outputNode || justCreatedPipeline || pipeline.outputNode === outgoingScenePass) {
1157
+ pipeline.outputNode = scenePass;
1158
+ outputNodeChanged = true;
1159
+ }
1160
+ set({ renderPipeline: pipeline, passes: currentPasses });
1161
+ setRenderOverride(store, renderThroughPipeline(store));
1162
+ const shouldRunCallbacks = justCreatedPipeline || !callbacksRanRef.current || !cacheValid;
1163
+ if (shouldRunCallbacks) {
1164
+ if (setupCBRef.current) {
1165
+ const freshState = Object.assign({}, store.getState(), {
1166
+ renderPipeline: pipeline,
1167
+ passes: { ...currentPasses, scenePass },
1168
+ renderer,
1169
+ isLegacy: false
1170
+ });
1171
+ const setupResult = setupCBRef.current(freshState);
1172
+ if (setupResult && typeof setupResult === "object") {
1173
+ currentPasses = { ...currentPasses, ...setupResult };
1174
+ set({ passes: currentPasses });
1175
+ }
1176
+ }
1177
+ if (mainCBRef.current) {
1178
+ const freshState = Object.assign({}, store.getState(), {
1179
+ renderPipeline: pipeline,
1180
+ passes: { ...currentPasses, scenePass },
1181
+ renderer,
1182
+ isLegacy: false
1183
+ });
1184
+ const mainResult = mainCBRef.current(freshState);
1185
+ if (mainResult && typeof mainResult === "object") {
1186
+ currentPasses = { ...currentPasses, ...mainResult };
1187
+ set({ passes: currentPasses });
1188
+ }
1189
+ }
1190
+ outputNodeChanged = true;
1191
+ callbacksRanRef.current = true;
1192
+ }
1193
+ if (outputNodeChanged) pipeline.needsUpdate = true;
1194
+ if (outgoingScenePass && outgoingScenePass !== scenePass) outgoingScenePass.dispose();
1195
+ } catch (error) {
1196
+ console.warn("[useRenderPipeline] Setup failed; the render pipeline was not configured:", error);
1197
+ }
1198
+ }, [store, renderer, scene, camera, isLegacy, rebuildVersion]);
1199
+ const passes = useThree((s) => s.passes) ?? NO_PASSES;
1200
+ const renderPipeline = useThree((s) => s.renderPipeline) ?? null;
1201
+ const actions = { passes, clearPasses, reset, rebuild };
1202
+ if (renderPipeline) return { ...actions, isReady: true, renderPipeline };
1203
+ return { ...actions, isReady: false, renderPipeline: null };
1204
+ }
1205
+
1206
+ ensureTSLExtension();
1207
+
1208
+ export { configureTSL, createScopedStore, rebuildAllBuffers, rebuildAllNodes, rebuildAllStorage, rebuildAllUniforms, useBuffers, useGPUStorage, useLocalNodes, useNodes, useRenderPipeline, useUniform, useUniforms };