Skip to content

Commit 0599c1a

Browse files
committed
fix(soba): unwrap afterNextRender in injectFBO, injectIntersect, and injectSurfaceSampler
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 537be01 commit 0599c1a

File tree

4 files changed

+127
-119
lines changed

4 files changed

+127
-119
lines changed

libs/soba/misc/src/lib/fbo.ts

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,13 @@ import {
77
ViewContainerRef,
88
afterNextRender,
99
computed,
10+
effect,
1011
inject,
1112
input,
1213
untracked,
1314
} from '@angular/core';
1415
import { injectStore } from 'angular-three';
1516
import { assertInjector } from 'ngxtension/assert-injector';
16-
import { injectAutoEffect } from 'ngxtension/auto-effect';
1717
import {
1818
ColorSpace,
1919
DepthTexture,
@@ -58,7 +58,6 @@ export interface NgtsFBOParams {
5858

5959
export function injectFBO(params: () => NgtsFBOParams, { injector }: { injector?: Injector } = {}) {
6060
return assertInjector(injectFBO, injector, () => {
61-
const autoEffect = injectAutoEffect();
6261
const store = injectStore();
6362
const size = store.select('size');
6463
const viewport = store.select('viewport');
@@ -101,12 +100,10 @@ export function injectFBO(params: () => NgtsFBOParams, { injector }: { injector?
101100
return target;
102101
});
103102

104-
afterNextRender(() => {
105-
autoEffect(() => {
106-
const [{ samples = 0 }, _width, _height, _target] = [settings(), width(), height(), target()];
107-
_target.setSize(_width, _height);
108-
if (samples) _target.samples = samples;
109-
});
103+
effect(() => {
104+
const [{ samples = 0 }, _width, _height, _target] = [settings(), width(), height(), target()];
105+
_target.setSize(_width, _height);
106+
if (samples) _target.samples = samples;
110107
});
111108

112109
inject(DestroyRef).onDestroy(() => target().dispose());
@@ -119,19 +116,24 @@ export function injectFBO(params: () => NgtsFBOParams, { injector }: { injector?
119116
export class NgtsFBO {
120117
fbo = input({} as { width: NgtsFBOParams['width']; height: NgtsFBOParams['height'] } & FBOSettings);
121118

122-
template = inject(TemplateRef);
123-
viewContainerRef = inject(ViewContainerRef);
124-
125-
fboTarget = injectFBO(() => {
126-
const { width, height, ...settings } = this.fbo();
127-
return { width, height, settings };
128-
});
119+
private template = inject(TemplateRef);
120+
private viewContainerRef = inject(ViewContainerRef);
129121

130122
constructor() {
131123
let ref: EmbeddedViewRef<{ $implicit: ReturnType<typeof injectFBO> }>;
124+
const injector = inject(Injector);
125+
132126
afterNextRender(() => {
127+
const fboTarget = injectFBO(
128+
() => {
129+
const { width, height, ...settings } = this.fbo();
130+
return { width, height, settings };
131+
},
132+
{ injector },
133+
);
134+
133135
untracked(() => {
134-
ref = this.viewContainerRef.createEmbeddedView(this.template, { $implicit: this.fboTarget });
136+
ref = this.viewContainerRef.createEmbeddedView(this.template, { $implicit: fboTarget });
135137
ref.detectChanges();
136138
});
137139
});

libs/soba/misc/src/lib/intersect.ts

Lines changed: 38 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,16 @@
1-
import { afterNextRender, Directive, ElementRef, inject, Injector, model, signal, WritableSignal } from '@angular/core';
1+
import {
2+
afterNextRender,
3+
Directive,
4+
effect,
5+
ElementRef,
6+
inject,
7+
Injector,
8+
model,
9+
signal,
10+
WritableSignal,
11+
} from '@angular/core';
212
import { addAfterEffect, addEffect, resolveRef } from 'angular-three';
313
import { assertInjector } from 'ngxtension/assert-injector';
4-
import { injectAutoEffect } from 'ngxtension/auto-effect';
514
import { Object3D } from 'three';
615

716
export function injectIntersect<TObject extends Object3D>(
@@ -12,34 +21,30 @@ export function injectIntersect<TObject extends Object3D>(
1221
let check = false;
1322
let temp = false;
1423

15-
const autoEffect = injectAutoEffect();
24+
effect((onCleanup) => {
25+
const obj = resolveRef(object());
26+
if (!obj) return;
1627

17-
afterNextRender(() => {
18-
autoEffect(() => {
19-
const obj = resolveRef(object());
20-
if (!obj) return;
21-
22-
// Stamp out frustum check pre-emptively
23-
const unsub1 = addEffect(() => {
24-
check = false;
25-
return true;
26-
});
27-
28-
// If the object is inside the frustum three will call onRender
29-
const oldOnRender = obj.onBeforeRender.bind(obj);
30-
obj.onBeforeRender = () => (check = true);
31-
32-
// Compare the check value against the temp value, if it differs set state
33-
const unsub2 = addAfterEffect(() => {
34-
if (check !== temp) source.set((temp = check));
35-
return true;
36-
});
37-
38-
return () => {
39-
obj.onBeforeRender = oldOnRender;
40-
unsub1();
41-
unsub2();
42-
};
28+
// Stamp out frustum check pre-emptively
29+
const unsub1 = addEffect(() => {
30+
check = false;
31+
return true;
32+
});
33+
34+
// If the object is inside the frustum three will call onRender
35+
const oldOnRender = obj.onBeforeRender.bind(obj);
36+
obj.onBeforeRender = () => (check = true);
37+
38+
// Compare the check value against the temp value, if it differs set state
39+
const unsub2 = addAfterEffect(() => {
40+
if (check !== temp) source.set((temp = check));
41+
return true;
42+
});
43+
44+
onCleanup(() => {
45+
obj.onBeforeRender = oldOnRender;
46+
unsub1();
47+
unsub2();
4348
});
4449
});
4550

@@ -53,6 +58,9 @@ export class NgtsIntersect {
5358

5459
constructor() {
5560
const host = inject<ElementRef<Object3D>>(ElementRef);
56-
injectIntersect(() => host, { source: this.intersect });
61+
const injector = inject(Injector);
62+
afterNextRender(() => {
63+
injectIntersect(() => host, { source: this.intersect, injector });
64+
});
5765
}
5866
}

libs/soba/misc/src/lib/sampler.ts

Lines changed: 63 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ import {
33
ChangeDetectionStrategy,
44
Component,
55
CUSTOM_ELEMENTS_SCHEMA,
6+
effect,
67
ElementRef,
8+
inject,
79
Injector,
810
input,
911
signal,
@@ -69,75 +71,71 @@ export function injectSurfaceSampler(
6971
})(),
7072
);
7173

72-
const autoEffect = injectAutoEffect();
73-
74-
afterNextRender(() => {
75-
autoEffect(
76-
() => {
77-
const currentMesh = resolveRef(mesh());
78-
if (!currentMesh) return;
79-
80-
const localState = getLocalState(currentMesh);
81-
if (!localState) return;
82-
83-
const nonObjects = localState.nonObjects();
84-
if (
85-
!nonObjects ||
86-
!nonObjects.length ||
87-
nonObjects.every((nonObject) => !(nonObject as BufferGeometry).isBufferGeometry)
88-
) {
89-
return;
90-
}
74+
effect(
75+
() => {
76+
const currentMesh = resolveRef(mesh());
77+
if (!currentMesh) return;
9178

92-
const sampler = new MeshSurfaceSampler(currentMesh);
79+
const localState = getLocalState(currentMesh);
80+
if (!localState) return;
9381

94-
const { weight, count = 16, transform, instanceMesh } = options();
82+
const nonObjects = localState.nonObjects();
83+
if (
84+
!nonObjects ||
85+
!nonObjects.length ||
86+
nonObjects.every((nonObject) => !(nonObject as BufferGeometry).isBufferGeometry)
87+
) {
88+
return;
89+
}
9590

96-
if (weight) {
97-
sampler.setWeightAttribute(weight);
98-
}
99-
100-
sampler.build();
91+
const sampler = new MeshSurfaceSampler(currentMesh);
10192

102-
const position = new Vector3();
103-
const normal = new Vector3();
104-
const color = new Color();
105-
const dummy = new Object3D();
106-
const instance = resolveRef(instanceMesh);
93+
const { weight, count = 16, transform, instanceMesh } = options();
10794

108-
currentMesh.updateMatrixWorld(true);
95+
if (weight) {
96+
sampler.setWeightAttribute(weight);
97+
}
10998

110-
for (let i = 0; i < count; i++) {
111-
sampler.sample(position, normal, color);
99+
sampler.build();
112100

113-
if (typeof transform === 'function') {
114-
transform({ dummy, sampledMesh: currentMesh, position, normal, color }, i);
115-
} else {
116-
dummy.position.copy(position);
117-
}
101+
const position = new Vector3();
102+
const normal = new Vector3();
103+
const color = new Color();
104+
const dummy = new Object3D();
105+
const instance = resolveRef(instanceMesh);
118106

119-
dummy.updateMatrix();
107+
currentMesh.updateMatrixWorld(true);
120108

121-
if (instance) {
122-
instance.setMatrixAt(i, dummy.matrix);
123-
}
109+
for (let i = 0; i < count; i++) {
110+
sampler.sample(position, normal, color);
124111

125-
dummy.matrix.toArray(untracked(buffer).array, i * 16);
112+
if (typeof transform === 'function') {
113+
transform({ dummy, sampledMesh: currentMesh, position, normal, color }, i);
114+
} else {
115+
dummy.position.copy(position);
126116
}
127117

118+
dummy.updateMatrix();
119+
128120
if (instance) {
129-
checkUpdate(instance.instanceMatrix);
121+
instance.setMatrixAt(i, dummy.matrix);
130122
}
131123

132-
checkUpdate(buffer);
124+
dummy.matrix.toArray(untracked(buffer).array, i * 16);
125+
}
133126

134-
buffer.set(
135-
new InstancedBufferAttribute(untracked(buffer).array, untracked(buffer).itemSize).copy(untracked(buffer)),
136-
);
137-
},
138-
{ allowSignalWrites: true },
139-
);
140-
});
127+
if (instance) {
128+
checkUpdate(instance.instanceMatrix);
129+
}
130+
131+
checkUpdate(buffer);
132+
133+
buffer.set(
134+
new InstancedBufferAttribute(untracked(buffer).array, untracked(buffer).itemSize).copy(untracked(buffer)),
135+
);
136+
},
137+
{ allowSignalWrites: true },
138+
);
141139

142140
return buffer.asReadonly();
143141
});
@@ -192,6 +190,7 @@ export class NgtsSampler {
192190
constructor() {
193191
extend({ Group });
194192
const autoEffect = injectAutoEffect();
193+
const injector = inject(Injector);
195194

196195
afterNextRender(() => {
197196
autoEffect(
@@ -212,13 +211,17 @@ export class NgtsSampler {
212211
},
213212
{ allowSignalWrites: true },
214213
);
215-
});
216214

217-
injectSurfaceSampler(this.meshToSample, () => ({
218-
count: this.options().count,
219-
transform: this.options().transform,
220-
weight: this.options().weight,
221-
instanceMesh: this.instancedToSample(),
222-
}));
215+
injectSurfaceSampler(
216+
this.meshToSample,
217+
() => ({
218+
count: this.options().count,
219+
transform: this.options().transform,
220+
weight: this.options().weight,
221+
instanceMesh: this.instancedToSample(),
222+
}),
223+
{ injector },
224+
);
225+
});
223226
}
224227
}

libs/soba/src/performances/instances.stories.ts

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { ChangeDetectionStrategy, Component, CUSTOM_ELEMENTS_SCHEMA, input, Signal, viewChild } from '@angular/core';
22
import { Meta } from '@storybook/angular';
3-
import { injectBeforeRender, injectObjectEvents, omit, pick } from 'angular-three';
3+
import { injectBeforeRender, NgtObjectEvents, omit, pick } from 'angular-three';
44
import { NgtsOrbitControls } from 'angular-three-soba/controls';
55
import { injectGLTF } from 'angular-three-soba/loaders';
66
import { NgtsInstance, NgtsInstances } from 'angular-three-soba/performances';
@@ -30,12 +30,17 @@ type Data = (typeof data)[number];
3030
standalone: true,
3131
template: `
3232
<ngt-group [parameters]="parameters()">
33-
<ngts-instance />
33+
<ngts-instance
34+
#instance
35+
[ngtObjectEvents]="instance.positionMeshRef()"
36+
(pointerover)="$event.stopPropagation(); this.hovered = true"
37+
(pointerout)="this.hovered = false"
38+
/>
3439
</ngt-group>
3540
`,
3641
schemas: [CUSTOM_ELEMENTS_SCHEMA],
3742
changeDetection: ChangeDetectionStrategy.OnPush,
38-
imports: [NgtsInstance],
43+
imports: [NgtsInstance, NgtObjectEvents],
3944
})
4045
class Shoe {
4146
data = input.required<Data>();
@@ -51,16 +56,6 @@ class Shoe {
5156
hovered = false;
5257

5358
constructor() {
54-
injectObjectEvents(() => this.instance().positionMeshRef(), {
55-
pointerover: (event) => {
56-
event.stopPropagation();
57-
this.hovered = true;
58-
},
59-
pointerout: () => {
60-
this.hovered = false;
61-
},
62-
});
63-
6459
const color = new Color();
6560

6661
injectBeforeRender(({ clock }) => {

0 commit comments

Comments
 (0)