UNPKG

@woosh/meep-engine

Version:

Pure JavaScript game engine. Fully featured and production ready.

228 lines (196 loc) 5.22 kB
// import { assert } from "../assert.js"; import { returnZero } from "../function/returnZero.js"; import { current_time_in_seconds } from "../time/current_time_in_seconds.js"; import { Cache } from "./Cache.js"; /** * @template R */ class Record { /** * @template R * @param {R} value * @param {number} time */ constructor(value, time) { this.value = value; this.time = time; this.failed = false; this.weight = 0; } } /** * In seconds * @readonly * @type {number} */ const DEFAULT_TIME_TO_LIVE = Infinity; /** * @template T * @param {Record<T>} record * @returns {number} */ function record_get_value_weight(record) { return record.weight; } /** * Asynchronous cache capable of resolving its own values by keys * Modelled on Guava's LoadingCache concept * @template K * @template V * @class * * @author Alex Goldring * @copyright Company Named Limited (c) 2025 */ export class LoadingCache { /** * @type {Cache<K,Record<Promise<V>>>} */ #internal /** * Time until records are marked as invalid, invalid records are not served back and trigger re-loading of the value * In seconds * @type {number} */ #timeToLive = DEFAULT_TIME_TO_LIVE; /** * @type {function(key:K):Promise<V>} */ #load /** * * @type {boolean} */ #policyRetryFailed = true; /** * * @type {function(V): number} */ #value_weigher; /** * @see {@link Cache} for more details on what each parameter means * @param [maxWeight] * @param [keyWeigher] * @param [valueWeigher] * @param [keyHashFunction] * @param [keyEqualityFunction] * @param [capacity] * @param {number} [timeToLive] in seconds, default is 10 seconds * @param {function(key:K):Promise<V>} load * @param {boolean} [retryFailed] */ constructor({ maxWeight, keyWeigher, valueWeigher = returnZero, keyHashFunction, keyEqualityFunction, capacity, timeToLive = DEFAULT_TIME_TO_LIVE, load, retryFailed = true }) { assert.isFunction(load, 'load'); this.#internal = new Cache({ maxWeight, keyWeigher, valueWeigher: record_get_value_weight, keyHashFunction, keyEqualityFunction, capacity, }); this.#timeToLive = timeToLive; this.#load = load; this.#policyRetryFailed = retryFailed; this.#value_weigher = valueWeigher; } /** * * @param {K} key * @returns {boolean} */ invalidate(key) { return this.#internal.remove(key); } /** * */ clear() { this.#internal.clear(); } /** * * @param {K} key * @param {number} timestamp * @return {Record<Promise<V>>} */ #load_value(key, timestamp) { let promise; try { promise = this.#load(key); } catch (e) { promise = Promise.reject(e); } const record = new Record(promise, timestamp); this.#internal.put(key, record); promise.then( (value) => { // re-score value based on actual data record.weight = this.#value_weigher(value); this.#internal.updateElementWeight(key); }, () => { // mark as failure record.failed = true; } ); return record; } /** * Load new value for the key (happens asynchronously) * @param {K} key * @returns {Promise<V>} */ refresh(key) { const record = this.#load_value(key, current_time_in_seconds()); return record.value; } /** * Directly insert value into the cache * @param {K} key * @param {V} value */ put(key, value) { this.#internal.put(key, new Record(Promise.resolve(value), current_time_in_seconds())); } /** * * @param {K} key * @returns {boolean} */ contains(key) { return this.#internal.contains(key); } /** * * @param {K} key * @return {Promise<V>} */ async get(key) { const currentTime = current_time_in_seconds(); /** * * @type {Record<Promise<V>>} */ let record = this.#internal.get(key); if (record === null || (record.time + this.#timeToLive) < currentTime // timeout || (record.failed && this.#policyRetryFailed) // load failed and we're configured to retry ) { // record needs to be loaded record = this.#load_value(key, currentTime); } return record.value; } }