Skip to content

Commit 47dba57

Browse files
committed
fix(soba): unwrap afterNextRender in injectEnvironment and
`injectNormalTexture` BREAKING CHANGE: this is considered a breaking change because of timing for unwrapping `afterNextRender` The consumers can migrate these CIFs with the following decision making guidances: - If the result of the **CIFs** are used as bindings on the template, there's no need to change anything. This also applies to `computed` using these result and the computed(s) are used as bindings on the template. - If the result of the **CIFs** are used in a side-effects, then the consumers need to make sure the parameters to these **CIFs** have a chance to resolve (i.e: `input` and `afterNextRender`; this was the reason `afterNextRender` was used internally). In addition, the consumers also need to make sure the **CIFs** are invoked in an Injection Context.
1 parent 0599c1a commit 47dba57

File tree

4 files changed

+130
-109
lines changed

4 files changed

+130
-109
lines changed

libs/soba/staging/src/lib/environment.ts

Lines changed: 91 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
afterNextRender,
1212
computed,
1313
contentChild,
14+
effect,
1415
inject,
1516
input,
1617
output,
@@ -165,6 +166,7 @@ export function injectEnvironment(
165166
: firstEntry.startsWith('data:image/jpeg')
166167
? 'jpg'
167168
: firstEntry.split('.').pop()?.split('?')?.shift()?.toLowerCase();
169+
168170
return { multiFile, extension, isCubeMap };
169171
});
170172

@@ -190,74 +192,79 @@ export function injectEnvironment(
190192
return loader as typeof Loader;
191193
});
192194

193-
const assertedInjector = inject(Injector);
194-
const autoEffect = injectAutoEffect();
195195
const store = injectStore();
196196
const gl = store.select('gl');
197197

198198
const texture = signal<Texture | CubeTexture | null>(null);
199-
afterNextRender(() => {
200-
autoEffect(() => {
201-
const [{ extension, multiFile }, _files] = [untracked(resultOptions), files()];
202-
203-
if (extension !== 'webp' && extension !== 'jpg' && extension !== 'jpeg') return;
204-
205-
gl().domElement.addEventListener(
206-
'webglcontextlost',
207-
() => {
208-
// @ts-expect-error - files is correctly passed
209-
injectLoader.clear(multiFile ? [_files] : _files);
210-
},
211-
{ once: true },
212-
);
213-
});
214199

215-
const result = injectLoader(
216-
loader,
217-
// @ts-expect-error - ensure the files is an array
200+
effect(() => {
201+
const [{ extension, multiFile }, _files] = [untracked(resultOptions), files()];
202+
203+
if (extension !== 'webp' && extension !== 'jpg' && extension !== 'jpeg') return;
204+
205+
gl().domElement.addEventListener(
206+
'webglcontextlost',
218207
() => {
219-
const { files } = adjustedOptions();
220-
return Array.isArray(files) ? [files] : files;
221-
},
222-
{
223-
injector: assertedInjector,
224-
extensions: (loader) => {
225-
const { extensions, path } = adjustedOptions();
226-
const { extension } = resultOptions();
227-
if (extension === 'webp' || extension === 'jpg' || extension === 'jpeg') {
228-
// @ts-expect-error - Gainmap requires a renderer
229-
loader.setRenderer(gl());
230-
}
231-
232-
loader.setPath?.(path);
233-
if (extensions) extensions(loader);
234-
},
208+
// @ts-expect-error - files is correctly passed
209+
injectLoader.clear(multiFile ? [_files] : _files);
235210
},
211+
{ once: true },
236212
);
213+
});
214+
215+
const result = injectLoader(
216+
loader,
217+
// @ts-expect-error - ensure the files is an array
218+
() => {
219+
const { files } = adjustedOptions();
220+
return Array.isArray(files) ? [files] : files;
221+
},
222+
{
223+
extensions: (loader) => {
224+
const { extensions, path } = adjustedOptions();
225+
const { extension } = resultOptions();
226+
if (extension === 'webp' || extension === 'jpg' || extension === 'jpeg') {
227+
// @ts-expect-error - Gainmap requires a renderer
228+
loader.setRenderer(gl());
229+
}
230+
231+
loader.setPath?.(path);
232+
if (extensions) extensions(loader);
233+
},
234+
},
235+
);
237236

238-
autoEffect(() => {
239-
const loaderResult = result();
240-
if (!loaderResult) return;
237+
effect(() => {
238+
const loaderResult = result();
239+
if (!loaderResult) return;
241240

242-
untracked(() => {
243-
const { multiFile, extension, isCubeMap } = resultOptions();
244-
const { encoding } = adjustedOptions();
241+
untracked(() => {
242+
const { multiFile, extension, isCubeMap } = resultOptions();
243+
const { encoding } = adjustedOptions();
245244

246-
// @ts-expect-error - ensure textureResult is a Texture or CubeTexture
247-
let textureResult = (multiFile ? loaderResult[0] : loaderResult) as Texture | CubeTexture;
245+
// @ts-expect-error - ensure textureResult is a Texture or CubeTexture
246+
let textureResult = (multiFile ? loaderResult[0] : loaderResult) as Texture | CubeTexture;
248247

249-
if (extension === 'jpg' || extension === 'jpeg' || extension === 'webp') {
250-
textureResult = (textureResult as any).renderTarget?.texture;
251-
}
248+
// NOTE: racing condition, we can skip this
249+
// we just said above that if multiFile is false, it is a single Texture
250+
if (!multiFile && Array.isArray(textureResult) && textureResult[0] instanceof CubeTexture) {
251+
return;
252+
}
253+
254+
if (
255+
!(textureResult instanceof CubeTexture) &&
256+
(extension === 'jpg' || extension === 'jpeg' || extension === 'webp')
257+
) {
258+
textureResult = (textureResult as any).renderTarget?.texture;
259+
}
252260

253-
textureResult.mapping = isCubeMap ? CubeReflectionMapping : EquirectangularReflectionMapping;
261+
textureResult.mapping = isCubeMap ? CubeReflectionMapping : EquirectangularReflectionMapping;
254262

255-
if ('colorSpace' in textureResult)
256-
(textureResult as any).colorSpace = encoding ?? (isCubeMap ? 'srgb' : 'srgb-linear');
257-
else (textureResult as any).encoding = encoding ?? (isCubeMap ? sRGBEncoding : LinearEncoding);
263+
if ('colorSpace' in textureResult)
264+
(textureResult as any).colorSpace = encoding ?? (isCubeMap ? 'srgb' : 'srgb-linear');
265+
else (textureResult as any).encoding = encoding ?? (isCubeMap ? sRGBEncoding : LinearEncoding);
258266

259-
texture.set(textureResult);
260-
});
267+
texture.set(textureResult);
261268
});
262269
});
263270

@@ -295,11 +302,11 @@ export class NgtsEnvironmentMap {
295302
options = input(defaultBackground, { transform: mergeInputs(defaultBackground) });
296303
envSet = output<void>();
297304

298-
autoEffect = injectAutoEffect();
299-
store = injectStore();
300-
defaultScene = this.store.select('scene');
305+
private autoEffect = injectAutoEffect();
306+
private store = injectStore();
307+
private defaultScene = this.store.select('scene');
301308

302-
envConfig = computed(() => {
309+
private envConfig = computed(() => {
303310
const {
304311
background = false,
305312
scene,
@@ -323,7 +330,7 @@ export class NgtsEnvironmentMap {
323330
};
324331
});
325332

326-
map = pick(this.options, 'map');
333+
private map = pick(this.options, 'map');
327334

328335
constructor() {
329336
afterNextRender(() => {
@@ -344,11 +351,12 @@ export class NgtsEnvironmentCube {
344351
options = input(defaultBackground, { transform: mergeInputs(defaultBackground) });
345352
envSet = output<void>();
346353

347-
autoEffect = injectAutoEffect();
348-
store = injectStore();
349-
defaultScene = this.store.select('scene');
354+
private autoEffect = injectAutoEffect();
355+
private store = injectStore();
356+
private defaultScene = this.store.select('scene');
357+
private injector = inject(Injector);
350358

351-
envConfig = computed(() => {
359+
private envConfig = computed(() => {
352360
const {
353361
background = false,
354362
scene,
@@ -372,16 +380,12 @@ export class NgtsEnvironmentCube {
372380
};
373381
});
374382

375-
environmentOptions = computed(() => {
376-
const { encoding, preset, files, path, extensions } = this.options();
377-
return { encoding, preset, files, path, extensions };
378-
});
379-
texture = injectEnvironment(this.environmentOptions);
380-
381383
constructor() {
382384
afterNextRender(() => {
385+
const _texture = injectEnvironment(this.options, { injector: this.injector });
386+
383387
this.autoEffect(() => {
384-
const texture = this.texture();
388+
const texture = _texture();
385389
if (!texture) return;
386390
const { background = false, scene, ...config } = this.envConfig();
387391
const cleanup = setEnvProps(background, scene, this.defaultScene(), texture, config);
@@ -553,21 +557,35 @@ export class NgtsEnvironmentGround {
553557
options = input({} as NgtsEnvironmentOptions);
554558
envSet = output<void>();
555559

556-
private defaultTexture = injectEnvironment(this.options);
557-
private texture = computed(() => this.options().map || this.defaultTexture());
560+
args = signal<[Texture | null]>([null]);
558561

559-
args = computed(() => [this.texture()]);
560562
height = computed(() => (this.options().ground as any)?.height);
561563
radius = computed(() => (this.options().ground as any)?.radius);
562564
scale = computed(() => (this.options().ground as any)?.scale ?? 1000);
563565

564566
envMapOptions = computed(() => {
565567
const { map: _, ...options } = this.options();
566-
return Object.assign(options, { map: this.texture() }) as NgtsEnvironmentOptions;
568+
const [map] = this.args();
569+
return Object.assign(options, { map }) as NgtsEnvironmentOptions;
567570
});
568571

569572
constructor() {
570573
extend({ GroundProjectedEnv });
574+
575+
const injector = inject(Injector);
576+
const autoEffect = injectAutoEffect();
577+
578+
afterNextRender(() => {
579+
const defaultTexture = injectEnvironment(this.options, { injector });
580+
const texture = computed(() => this.options().map || defaultTexture());
581+
582+
autoEffect(
583+
() => {
584+
this.args.set([texture()]);
585+
},
586+
{ allowSignalWrites: true },
587+
);
588+
});
571589
}
572590
}
573591

libs/soba/staging/src/lib/normal-texture.ts

Lines changed: 10 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
ViewContainerRef,
99
afterNextRender,
1010
computed,
11+
effect,
1112
inject,
1213
input,
1314
output,
@@ -16,7 +17,6 @@ import {
1617
} from '@angular/core';
1718
import { injectTexture } from 'angular-three-soba/loaders';
1819
import { assertInjector } from 'ngxtension/assert-injector';
19-
import { injectAutoEffect } from 'ngxtension/auto-effect';
2020
import { RepeatWrapping, Texture, Vector2 } from 'three';
2121

2222
const NORMAL_ROOT = 'https://rawcdn.githack.com/pmndrs/drei-assets/7a3104997e1576f83472829815b00880d88b32fb';
@@ -37,8 +37,6 @@ export function injectNormalTexture(
3737
}: { settings?: () => NgtsNormalTextureSettings; onLoad?: (texture: Texture[]) => void; injector?: Injector },
3838
) {
3939
return assertInjector(injectNormalTexture, injector, () => {
40-
const autoEffect = injectAutoEffect();
41-
4240
const normalList = signal<Record<string, string>>({});
4341

4442
fetch(LIST_URL)
@@ -68,17 +66,15 @@ export function injectNormalTexture(
6866

6967
const normalTexture = injectTexture(url, { onLoad });
7068

71-
afterNextRender(() => {
72-
autoEffect(() => {
73-
const texture = normalTexture();
74-
if (!texture) return;
75-
76-
const { anisotropy = 1, repeat = [1, 1], offset = [0, 0] } = settings();
77-
texture.wrapS = texture.wrapT = RepeatWrapping;
78-
texture.repeat = new Vector2(repeat[0], repeat[1]);
79-
texture.offset = new Vector2(offset[0], offset[1]);
80-
texture.anisotropy = anisotropy;
81-
});
69+
effect(() => {
70+
const texture = normalTexture();
71+
if (!texture) return;
72+
73+
const { anisotropy = 1, repeat = [1, 1], offset = [0, 0] } = settings();
74+
texture.wrapS = texture.wrapT = RepeatWrapping;
75+
texture.repeat = new Vector2(repeat[0], repeat[1]);
76+
texture.offset = new Vector2(offset[0], offset[1]);
77+
texture.anisotropy = anisotropy;
8278
});
8379

8480
return { url, texture: normalTexture, numTot };

libs/soba/staging/src/lib/render-texture.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@ export class NgtsRenderTexture {
198198
eventPriority = pick(this.options, 'eventPriority');
199199
compute = computed(() => this.options().compute || this.uvCompute);
200200

201-
uvCompute: NgtComputeFunction = (event, root, previous) => {
201+
private uvCompute: NgtComputeFunction = (event, root, previous) => {
202202
const fbo = this.fbo();
203203
if (!fbo) return;
204204
const state = root.snapshot;

0 commit comments

Comments
 (0)