@giro3d/giro3d
Version:
A JS/WebGL framework for 3D geospatial data visualization
157 lines (137 loc) • 4.32 kB
text/typescript
/*
* Copyright (c) 2015-2018, IGN France.
* Copyright (c) 2018-2026, Giro3D team.
* SPDX-License-Identifier: MIT
*/
import type { Texture } from 'three';
function isTexture(o: unknown): o is Texture {
return (o as Texture)?.isTexture;
}
class TextureState {
public readonly texture: Texture;
public inGpuMemory = false;
public constructor(texture: Texture) {
this.texture = texture;
texture.addEventListener('dispose', () => (this.inGpuMemory = false));
if (texture.isRenderTargetTexture) {
this.inGpuMemory = true;
} else {
const currentOnUpdate = texture.onUpdate;
const patchedOnUpdate = (): void => {
this.inGpuMemory = true;
currentOnUpdate?.call(texture, texture);
};
texture.onUpdate = patchedOnUpdate;
}
}
}
interface AllocatedItem {
name: string;
weakref: WeakRef<object>;
}
let allocated: AllocatedItem[] = [];
const textures: Map<number, TextureState> = new Map();
const FLUSH_EVERY_NTH = 100;
let enabled = false;
let counter = 0;
/**
* Utility to track memory allocations.
*
* This uses [`WeakRef`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakRef)
* internally to avoid holding a reference past its lifetime.
*
* @example
* // Enable the memory tracker (disabled by default).
* MemoryTracker.enable = true;
*
* const texture = new Texture();
*
* MemoryTracker.track(texture, 'my tracked texture');
*
* const allocated = MemoryTracker.getTrackedObjects();
*
* // allocated should be \{ Texture: [\{ name: 'my tracked texture', value: texture]\}
*/
class MemoryTracker {
/**
* Enables the tracking of allocated objects.
*/
public static set enable(v: boolean) {
if (enabled !== v) {
enabled = v;
if (!enabled) {
allocated.length = 0;
}
}
}
public static get enable(): boolean {
return enabled;
}
/**
* Registers an object to the memory tracker.
*
* @param obj - The object to track.
* @param name - The name of the tracked object. Does not have to be unique.
*/
public static track(obj: object, name: string): void {
if (enabled) {
allocated.push({ name, weakref: new WeakRef(obj) });
counter++;
if (isTexture(obj) && !textures.has(obj.id)) {
textures.set(obj.id, new TextureState(obj));
}
if (counter === FLUSH_EVERY_NTH) {
this.flush();
counter = 0;
}
}
}
/**
* Removes all invalid references.
*
*/
public static flush(): void {
const newArray = [];
let hasChanged = false;
for (const entry of allocated) {
const { weakref } = entry;
const value = weakref.deref();
if (value) {
newArray.push(entry);
} else {
hasChanged = true;
}
}
if (hasChanged) {
allocated = newArray;
}
}
/**
* Returns an array of all valid tracked objects (that have not been garbage collected).
*
* Important note: this array will hold actual references (dereferenced `WeakRef`s).
* They will no longer be removed by the garbage collector as long as values in this arrays
* exist ! You should make sure to empty this array when you are finished with it.
*
* @returns The tracked objects.
*/
public static getTrackedObjects(): Record<string, { name: string; value: object }[]> {
const map: Record<string, { name: string; value: object }[]> = {};
for (const entry of allocated) {
const { name, weakref } = entry;
const value = weakref.deref();
if (value) {
const key = value.constructor.name;
if (map[key] == null) {
map[key] = [];
}
map[key].push({ name, value });
}
}
return map;
}
public static getTrackedTextures(): TextureState[] {
return [...textures.values()];
}
}
export default MemoryTracker;