UNPKG

@giro3d/giro3d

Version:

A JS/WebGL framework for 3D geospatial data visualization

258 lines (222 loc) 6.3 kB
/* * Copyright (c) 2015-2018, IGN France. * Copyright (c) 2018-2026, Giro3D team. * SPDX-License-Identifier: MIT */ import { LRUCache } from 'lru-cache'; import type MemoryUsage from './MemoryUsage'; import { isMemoryUsage, type GetMemoryUsageContext } from './MemoryUsage'; /** * The options for a cache entry. */ export interface CacheOptions { /** * The time to live of this entry, in milliseconds. */ ttl?: number; /** * The entry size, in bytes. It does not have to be an exact value, but * it helps the cache determine when to remove entries to save memory. */ size?: number; /** * A optional callback called when the entry is deleted from the cache. */ onDelete?: (entry: unknown) => void; } /** * The default max number of entries. */ export const DEFAULT_MAX_ENTRIES = 8192; /** * The default TTL (time to live), in milliseconds. */ export const DEFAULT_TTL: number = 240_000; // 240 seconds /** * The default capacity, in bytes. */ export const DEFAULT_CAPACITY: number = 536_870_912; // 512 MB export interface CacheConfiguration { /** * The default TTL (time to live) of entries, in milliseconds. * Can be overriden for each entry (see {@link CacheOptions}). * @defaultValue {@link DEFAULT_TTL} */ ttl?: number; /** * The capacity, in bytes, of the cache. * @defaultValue {@link DEFAULT_CAPACITY} */ byteCapacity?: number; /** * The capacity, in number of entries, of the cache. * @defaultValue {@link DEFAULT_MAX_ENTRIES} */ maxNumberOfEntries?: number; } /** * The cache. * */ export class Cache implements MemoryUsage { public readonly isMemoryUsage = true as const; private readonly _deleteHandlers: Map<string, (entry: object) => void>; private _lru: LRUCache<string, object>; private _enabled: boolean; /** * Constructs a cache. * * @param opts - The options. */ public constructor(opts?: CacheConfiguration) { this._deleteHandlers = new Map(); this._enabled = true; this._lru = this.createLRUCache(opts); } private createLRUCache(opts?: CacheConfiguration): LRUCache<string, object> { return new LRUCache<string, object>({ ttl: opts?.ttl ?? DEFAULT_TTL, ttlResolution: 1000, // 1 second updateAgeOnGet: true, maxSize: opts?.byteCapacity ?? DEFAULT_CAPACITY, max: opts?.maxNumberOfEntries ?? DEFAULT_MAX_ENTRIES, allowStale: false, dispose: (value, key): void => { this.onDisposed(key, value); }, }); } /** * Configure the cache with the specified configuration. The cache must be * empty otherwise this method will throw an error. */ public configure(config: CacheConfiguration): void { if (this.count > 0) { throw new Error('cannot configure the cache as it is not empty.'); } this._lru = this.createLRUCache(config); } public getMemoryUsage(context: GetMemoryUsageContext): void { this._lru.forEach(e => { if (isMemoryUsage(e)) { e.getMemoryUsage(context); } }); } /** * Enables or disables the cache. */ public get enabled(): boolean { return this._enabled; } public set enabled(v: boolean) { this._enabled = v; } /** * Gets or sets the default TTL (time to live) of the cache. */ public get defaultTtl(): number { return this._lru.ttl; } public set defaultTtl(v: number) { this._lru.ttl = v; } /** * Gets the maximum size of the cache, in bytes. */ public get maxSize(): number { return this._lru.maxSize; } /** * Gets the maximum number of entries. */ public get capacity(): number { return this._lru.max; } /** * Gets the number of entries. */ public get count(): number { return this._lru.size; } /** * Gets the size of entries, in bytes */ public get size(): number { return this._lru.calculatedSize; } /** * Returns an array of entries. */ public entries(): Array<unknown> { return [...this._lru.entries()]; } private onDisposed(key: string, value: object): void { const handler = this._deleteHandlers.get(key); if (handler) { this._deleteHandlers.delete(key); handler(value); } } /** * Removes stale entries. */ public purge(): void { this._lru.purgeStale(); } /** * Returns the entry with the specified key, or `undefined` if no entry matches this key. * * @param key - The entry key. * @returns The entry, or `undefined`. */ public get(key: string): unknown | undefined { if (!this.enabled) { return undefined; } return this._lru.get(key); } /** * Stores an entry in the cache, or replaces an existing entry with the same key. * * @param key - The key. * @param value - The value. * @param options - The options. */ public set<T extends object>(key: string, value: T, options: CacheOptions = {}): T { if (!this.enabled) { return value; } if (typeof key !== 'string') { throw new Error('the cache expects strings as keys.'); } this._lru.set(key, value, { ttl: options.ttl ?? this.defaultTtl, size: options.size ?? 1024, // Use a default size if not provided }); if (options.onDelete) { this._deleteHandlers.set(key, options.onDelete); } return value; } /** * Deletes an entry. * * @param key - The key. * @returns `true` if the entry was deleted, `false` otherwise. */ public delete(key: string): boolean { return this._lru.delete(key); } /** * Clears the cache. * */ public clear(): void { this._lru.clear(); } } /** * A global singleton cache. */ export const GlobalCache: Cache = new Cache();