Skip to content

Commit 9315c30

Browse files
Chau TranChau Tran
Chau Tran
authored and
Chau Tran
committed
fix(core): remove createSignal and use signal directly. queue all signal updates in renderer in microtask
1 parent 8ea354c commit 9315c30

File tree

10 files changed

+67
-79
lines changed

10 files changed

+67
-79
lines changed

libs/angular-three/src/lib/di/ref.ts

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import {
2-
ApplicationRef,
32
ChangeDetectorRef,
43
DestroyRef,
54
ElementRef,
@@ -8,14 +7,14 @@ import {
87
computed,
98
inject,
109
runInInjectionContext,
10+
signal,
1111
untracked,
1212
} from '@angular/core';
1313
import { NgtInstanceNode } from '../types';
1414
import { assertInjectionContext } from '../utils/assert-in-injection-context';
1515
import { getLocalState } from '../utils/instance';
1616
import { is } from '../utils/is';
1717
import { safeDetectChanges } from '../utils/safe-detect-changes';
18-
import { createSignal } from '../utils/signal';
1918

2019
export type NgtInjectedRef<TElement> = ElementRef<TElement> & {
2120
/* consumers should use this for listenting to children changes on this ref */
@@ -29,13 +28,12 @@ export function injectNgtRef<TElement>(
2928
injector?: Injector
3029
): NgtInjectedRef<TElement> {
3130
injector = assertInjectionContext(injectNgtRef, injector);
31+
const ref = is.ref(initial) ? initial : new ElementRef<TElement>(initial as TElement);
32+
const signalRef = signal(ref.nativeElement);
33+
const readonlySignal = signalRef.asReadonly();
34+
const cached = new Map();
3235
return runInInjectionContext(injector, () => {
3336
const cdr = inject(ChangeDetectorRef);
34-
const appRef = inject(ApplicationRef);
35-
const ref = is.ref(initial) ? initial : new ElementRef<TElement>(initial as TElement);
36-
const signalRef = createSignal(ref.nativeElement);
37-
const readonlySignal = signalRef.asReadonly();
38-
const cached = new Map();
3937

4038
inject(DestroyRef).onDestroy(() => void cached.clear());
4139

@@ -60,9 +58,15 @@ export function injectNgtRef<TElement>(
6058
Object.defineProperty(ref, 'nativeElement', {
6159
set: (newElement) => {
6260
if (newElement !== untracked(signalRef)) {
63-
signalRef.set(newElement);
64-
// trigger CDR
61+
try {
62+
signalRef.set(newElement);
63+
} catch {
64+
requestAnimationFrame(() => {
65+
signalRef.set(newElement);
66+
});
67+
}
6568
requestAnimationFrame(() => void safeDetectChanges(cdr));
69+
// trigger CDR
6670
}
6771
},
6872
get: () => readonlySignal(),

libs/angular-three/src/lib/renderer/renderer.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
effect,
99
getDebugNode,
1010
inject,
11+
signal,
1112
untracked,
1213
type Renderer2,
1314
type RendererType2,
@@ -18,7 +19,6 @@ import { NgtStore } from '../stores/store';
1819
import type { NgtAnyRecord } from '../types';
1920
import { getLocalState, prepare } from '../utils/instance';
2021
import { is } from '../utils/is';
21-
import { createSignal } from '../utils/signal';
2222
import { NGT_COMPOUND_PREFIXES } from './di';
2323
import { NgtRendererClassId } from './enums';
2424
import { NgtRendererStore, type NgtRendererNode, type NgtRendererState } from './store';
@@ -117,7 +117,7 @@ export class NgtRenderer implements Renderer2 {
117117
{ __ngt_renderer__: { rawValue: undefined } },
118118
// NOTE: we assign this manually to a raw value node
119119
// because we say it is a 'three' node but we're not using prepare()
120-
{ __ngt__: { isRaw: true, parent: createSignal(null) } }
120+
{ __ngt__: { isRaw: true, parent: signal(null) } }
121121
)
122122
);
123123
}

libs/angular-three/src/lib/renderer/store.ts

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { NgtArgs } from '../directives/args';
44
import type { NgtCommonDirective } from '../directives/common';
55
import { NgtParent } from '../directives/parent';
66
import { NgtStore } from '../stores/store';
7-
import type { NgtAnyRecord } from '../types';
7+
import type { NgtAnyRecord, NgtInstanceNode } from '../types';
88
import { applyProps } from '../utils/apply-props';
99
import { getLocalState } from '../utils/instance';
1010
import { is } from '../utils/is';
@@ -207,6 +207,7 @@ export class NgtRendererStore {
207207
}
208208

209209
applyProps(node, { [name]: value });
210+
this.#updateNativeProps(node, name, value);
210211
}
211212

212213
applyProperty(node: NgtRendererNode, name: string, value: any) {
@@ -245,6 +246,7 @@ export class NgtRendererStore {
245246
value = compound[NgtCompoundClassId.props][name];
246247
}
247248
applyProps(node, { [name]: value });
249+
this.#updateNativeProps(node, name, value);
248250
}
249251

250252
isCompound(name: string) {
@@ -407,7 +409,15 @@ export class NgtRendererStore {
407409
}
408410
}
409411

410-
#firstNonInjectedDirective<T extends NgtCommonDirective>(dir: Type<T>) {
412+
#updateNativeProps(node: NgtInstanceNode, name: string, value: any) {
413+
const localState = getLocalState(node);
414+
if (!localState || !localState.nativeProps) return;
415+
queueMicrotask(() => {
416+
localState.nativeProps.set({ [name]: value });
417+
});
418+
}
419+
420+
#firstNonInjectedDirective<T extends NgtCommonDirective>(dir: Type<T>) {
411421
let directive: T | undefined;
412422

413423
let i = this.#comments.length - 1;
@@ -434,7 +444,7 @@ export class NgtRendererStore {
434444
return directive;
435445
}
436446

437-
#tryGetPortalStore() {
447+
#tryGetPortalStore() {
438448
let store: NgtStore | undefined;
439449
// we only care about the portal states because NgtStore only differs per Portal
440450
let i = this.portals.length - 1;

libs/angular-three/src/lib/renderer/utils.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,9 @@ export function attachThreeChild(parent: NgtInstanceNode, child: NgtInstanceNode
7575
// attach
7676
if (cLS.isRaw) {
7777
if (cLS.parent) {
78-
cLS.parent.set(parent);
78+
queueMicrotask(() => {
79+
cLS.parent.set(parent);
80+
});
7981
}
8082
// at this point we don't have rawValue yet, so we bail and wait until the Renderer recalls attach
8183
if (child.__ngt_renderer__[NgtRendererClassId.rawValue] === undefined) return;
@@ -94,7 +96,9 @@ export function attachThreeChild(parent: NgtInstanceNode, child: NgtInstanceNode
9496
pLS.add(child, added ? 'objects' : 'nonObjects');
9597

9698
if (cLS.parent) {
97-
cLS.parent.set(parent);
99+
queueMicrotask(() => {
100+
cLS.parent.set(parent);
101+
});
98102
}
99103

100104
if (cLS.afterAttach) cLS.afterAttach.emit({ parent, node: child });
@@ -108,7 +112,9 @@ export function removeThreeChild(parent: NgtInstanceNode, child: NgtInstanceNode
108112
const cLS = getLocalState(child);
109113

110114
// clear parent ref
111-
cLS.parent?.set(null);
115+
queueMicrotask(() => {
116+
cLS.parent?.set(null);
117+
});
112118

113119
// remove child from parent
114120
if (untracked(pLS.objects)) pLS.remove(child, 'objects');

libs/angular-three/src/lib/stores/signal.store.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,13 @@ import {
33
Injectable,
44
Optional,
55
computed,
6+
signal,
67
untracked,
78
type CreateComputedOptions,
89
type Signal,
910
type WritableSignal,
1011
} from '@angular/core';
1112
import type { NgtAnyRecord } from '../types';
12-
import { createSignal } from '../utils/signal';
1313

1414
const STORE_COMPUTED_KEY = '__ngt_store_computed__' as const;
1515

@@ -25,7 +25,7 @@ export class NgtSignalStore<TState extends object> {
2525
initialState: Partial<TState> = {} as unknown as Partial<TState>
2626
) {
2727
initialState ??= {};
28-
this.#state = createSignal(Object.assign(initialState, { __ngt_dummy_state__: Date.now() }) as TState);
28+
this.#state = signal(Object.assign(initialState, { __ngt_dummy_state__: Date.now() }) as TState);
2929
this.state = this.#state.asReadonly();
3030
}
3131

libs/angular-three/src/lib/utils/apply-props.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -88,8 +88,6 @@ export function applyProps(instance: NgtInstanceNode, props: NgtAnyRecord): NgtI
8888
// auto-convert srgb
8989
if (!THREE.ColorManagement && !rootState?.linear && isColor) targetProp.convertSRGBToLinear();
9090
}
91-
92-
localState?.nativeProps?.set({ [key]: targetProp });
9391
}
9492
// else just overwrite the value
9593
else {
@@ -107,7 +105,6 @@ export function applyProps(instance: NgtInstanceNode, props: NgtAnyRecord): NgtI
107105
else texture.encoding = rootState.gl.outputEncoding;
108106
}
109107
}
110-
localState?.nativeProps?.set({ [key]: value });
111108
}
112109

113110
checkUpdate(targetProp);

libs/angular-three/src/lib/utils/instance.ts

Lines changed: 21 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
1-
import { untracked } from '@angular/core';
1+
import { signal, untracked } from '@angular/core';
22
import { NgtSignalStore } from '../stores/signal.store';
33
import type { NgtAnyRecord, NgtInstanceLocalState, NgtInstanceNode } from '../types';
4-
import { createSignal } from './signal';
54
import { checkUpdate } from './update';
65

76
export function getLocalState<TInstance extends object = NgtAnyRecord>(
@@ -25,37 +24,41 @@ export function prepare<TInstance extends object = NgtAnyRecord>(
2524

2625
if (localState?.primitive || !instance.__ngt__) {
2726
const {
28-
objects = createSignal<NgtInstanceNode[]>([]),
29-
nonObjects = createSignal<NgtInstanceNode[]>([]),
27+
objects = signal<NgtInstanceNode[]>([]),
28+
nonObjects = signal<NgtInstanceNode[]>([]),
3029
...rest
3130
} = localState || {};
3231

3332
instance.__ngt__ = {
3433
previousAttach: null,
3534
store: null,
36-
parent: createSignal(null),
35+
parent: signal(null),
3736
memoized: {},
3837
eventCount: 0,
3938
handlers: {},
4039
objects,
4140
nonObjects,
4241
nativeProps: new NgtSignalStore({}),
4342
add: (object, type) => {
44-
const current = untracked(instance.__ngt__[type]);
45-
const foundIndex = current.indexOf((obj: NgtInstanceNode) => obj === object);
46-
if (foundIndex > -1) {
47-
// if we add an object with the same reference, then we switch it out
48-
// and update the BehaviorSubject
49-
current.splice(foundIndex, 1, object);
50-
instance.__ngt__[type].set(current);
51-
} else {
52-
instance.__ngt__[type].update((prev) => [...prev, object]);
53-
}
54-
notifyAncestors(untracked(instance.__ngt__.parent));
43+
queueMicrotask(() => {
44+
const current = untracked(instance.__ngt__[type]);
45+
const foundIndex = current.indexOf((obj: NgtInstanceNode) => obj === object);
46+
if (foundIndex > -1) {
47+
// if we add an object with the same reference, then we switch it out
48+
// and update the BehaviorSubject
49+
current.splice(foundIndex, 1, object);
50+
instance.__ngt__[type].set(current);
51+
} else {
52+
instance.__ngt__[type].update((prev) => [...prev, object]);
53+
}
54+
notifyAncestors(untracked(instance.__ngt__.parent));
55+
});
5556
},
5657
remove: (object, type) => {
57-
instance.__ngt__[type].update((prev) => prev.filter((o) => o !== object));
58-
notifyAncestors(untracked(instance.__ngt__.parent));
58+
queueMicrotask(() => {
59+
instance.__ngt__[type].update((prev) => prev.filter((o) => o !== object));
60+
notifyAncestors(untracked(instance.__ngt__.parent));
61+
});
5962
},
6063
...rest,
6164
} as NgtInstanceLocalState;

libs/angular-three/src/lib/utils/signal.ts

Lines changed: 0 additions & 26 deletions
This file was deleted.

libs/soba/loaders/src/progress/progress.ts

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,20 @@
1-
import { ChangeDetectorRef, Injector, inject, runInInjectionContext } from '@angular/core';
2-
import { assertInjectionContext, createSignal, safeDetectChanges } from 'angular-three';
1+
import { ChangeDetectorRef, Injector, inject, runInInjectionContext, signal } from '@angular/core';
2+
import { assertInjectionContext, safeDetectChanges } from 'angular-three';
33
import * as THREE from 'three';
44

55
export function injectNgtsProgress(injector?: Injector) {
66
injector = assertInjectionContext(injectNgtsProgress, injector);
77
return runInInjectionContext(injector, () => {
88
const cdr = inject(ChangeDetectorRef);
99

10-
const progress = createSignal<{
10+
const progress = signal<{
1111
errors: string[];
1212
active: boolean;
1313
progress: number;
1414
item: string;
1515
loaded: number;
1616
total: number;
17-
}>({
18-
errors: [],
19-
active: false,
20-
progress: 0,
21-
item: '',
22-
loaded: 0,
23-
total: 0,
24-
});
17+
}>({ errors: [], active: false, progress: 0, item: '', loaded: 0, total: 0 });
2518

2619
let saveLastTotalLoaded = 0;
2720

libs/soba/staging/src/stage/stage.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,9 @@ import {
1010
Input,
1111
OnChanges,
1212
Output,
13+
signal,
1314
} from '@angular/core';
14-
import { createSignal, extend, NgtArgs, NgtSignalStore, safeDetectChanges } from 'angular-three';
15+
import { extend, NgtArgs, NgtSignalStore, safeDetectChanges } from 'angular-three';
1516
import { AmbientLight, Group, PointLight, SpotLight, Vector2 } from 'three';
1617
import { NgtsAccumulativeShadows } from '../accumulative-shadows/accumulative-shadows';
1718
import { NgtsRandomizedLights } from '../accumulative-shadows/randomized-lights';
@@ -235,7 +236,7 @@ export class NgtsStage extends NgtSignalStore<NgtsStageProps> {
235236

236237
@Output() centered = new EventEmitter() as NgtsCenter['centered'];
237238

238-
readonly boundingState = createSignal({ radius: 0, width: 0, height: 0, depth: 0 });
239+
readonly boundingState = signal({ radius: 0, width: 0, height: 0, depth: 0 });
239240

240241
readonly #preset = this.select('preset');
241242
readonly #environment = this.select('environment');

0 commit comments

Comments
 (0)