feat: build procedural lifecycle-safe drive scene
This commit is contained in:
130
src/scene/resources.ts
Normal file
130
src/scene/resources.ts
Normal file
@@ -0,0 +1,130 @@
|
||||
export type SceneResourceKind =
|
||||
| 'geometry'
|
||||
| 'material'
|
||||
| 'texture'
|
||||
| 'target'
|
||||
| 'pass'
|
||||
| 'renderer'
|
||||
| 'other';
|
||||
|
||||
export interface DisposableResource {
|
||||
dispose(): void;
|
||||
}
|
||||
|
||||
export interface DisconnectableResource {
|
||||
disconnect(): void;
|
||||
}
|
||||
|
||||
export interface SceneResourceSnapshot {
|
||||
disposed: boolean;
|
||||
owned: Readonly<Record<SceneResourceKind, number>>;
|
||||
listenerCount: number;
|
||||
observerCount: number;
|
||||
timerCount: number;
|
||||
rafCount: 0 | 1;
|
||||
}
|
||||
|
||||
const resourceKinds: readonly SceneResourceKind[] = [
|
||||
'geometry', 'material', 'texture', 'target', 'pass', 'renderer', 'other',
|
||||
];
|
||||
|
||||
export class SceneResourceBag {
|
||||
private disposed = false;
|
||||
private readonly resources = new Map<DisposableResource, SceneResourceKind>();
|
||||
private readonly listeners = new Set<() => void>();
|
||||
private readonly observers = new Set<DisconnectableResource>();
|
||||
private readonly timers = new Map<number, (id: number) => void>();
|
||||
private raf: { id: number; cancel: (id: number) => void } | null = null;
|
||||
|
||||
own<T extends DisposableResource>(kind: SceneResourceKind, resource: T): T {
|
||||
if (this.disposed) {
|
||||
resource.dispose();
|
||||
return resource;
|
||||
}
|
||||
if (!this.resources.has(resource)) this.resources.set(resource, kind);
|
||||
return resource;
|
||||
}
|
||||
|
||||
listen(
|
||||
target: EventTarget,
|
||||
type: string,
|
||||
listener: EventListenerOrEventListenerObject,
|
||||
options?: AddEventListenerOptions | boolean,
|
||||
): () => void {
|
||||
if (this.disposed) return () => undefined;
|
||||
target.addEventListener(type, listener, options);
|
||||
let active = true;
|
||||
const remove = (): void => {
|
||||
if (!active) return;
|
||||
active = false;
|
||||
target.removeEventListener(type, listener, options);
|
||||
this.listeners.delete(remove);
|
||||
};
|
||||
this.listeners.add(remove);
|
||||
return remove;
|
||||
}
|
||||
|
||||
observe<T extends DisconnectableResource>(observer: T): T {
|
||||
if (this.disposed) observer.disconnect();
|
||||
else this.observers.add(observer);
|
||||
return observer;
|
||||
}
|
||||
|
||||
timer(id: number, clear: (id: number) => void): number {
|
||||
if (this.disposed) clear(id);
|
||||
else this.timers.set(id, clear);
|
||||
return id;
|
||||
}
|
||||
|
||||
releaseTimer(id: number): void {
|
||||
this.timers.delete(id);
|
||||
}
|
||||
|
||||
animationFrame(id: number, cancel: (id: number) => void): number {
|
||||
if (this.disposed) {
|
||||
cancel(id);
|
||||
return id;
|
||||
}
|
||||
if (this.raf) this.raf.cancel(this.raf.id);
|
||||
this.raf = { id, cancel };
|
||||
return id;
|
||||
}
|
||||
|
||||
releaseAnimationFrame(id: number): void {
|
||||
if (this.raf?.id === id) this.raf = null;
|
||||
}
|
||||
|
||||
cancelAnimationFrame(): void {
|
||||
if (!this.raf) return;
|
||||
const current = this.raf;
|
||||
this.raf = null;
|
||||
current.cancel(current.id);
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
if (this.disposed) return;
|
||||
this.disposed = true;
|
||||
this.cancelAnimationFrame();
|
||||
for (const [id, clear] of this.timers) clear(id);
|
||||
this.timers.clear();
|
||||
for (const remove of [...this.listeners]) remove();
|
||||
this.listeners.clear();
|
||||
for (const observer of this.observers) observer.disconnect();
|
||||
this.observers.clear();
|
||||
for (const resource of this.resources.keys()) resource.dispose();
|
||||
this.resources.clear();
|
||||
}
|
||||
|
||||
snapshot(): SceneResourceSnapshot {
|
||||
const owned = Object.fromEntries(resourceKinds.map((kind) => [kind, 0])) as Record<SceneResourceKind, number>;
|
||||
for (const kind of this.resources.values()) owned[kind] += 1;
|
||||
return {
|
||||
disposed: this.disposed,
|
||||
owned,
|
||||
listenerCount: this.listeners.size,
|
||||
observerCount: this.observers.size,
|
||||
timerCount: this.timers.size,
|
||||
rafCount: this.raf ? 1 : 0,
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user