UNPKG

@giro3d/giro3d

Version:

A JS/WebGL framework for 3D geospatial data visualization

157 lines (137 loc) 4.32 kB
/* * 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;