UNPKG

@itwin/core-bentley

Version:

Bentley JavaScript core components

350 lines • 11.6 kB
"use strict"; /*--------------------------------------------------------------------------------------------- * Copyright (c) Bentley Systems, Incorporated. All rights reserved. * See LICENSE.md in the project root for license terms and full copyright notice. *--------------------------------------------------------------------------------------------*/ /** @packageDocumentation * @module Collections */ Object.defineProperty(exports, "__esModule", { value: true }); exports.LRUDictionary = exports.LRUMap = exports.LRUCache = exports.Entry = void 0; const Dictionary_1 = require("./Dictionary"); /** * Derived from: * Licensed under MIT. Copyright (c) 2010 Rasmus Andersson <http://hunch.se/> * See README.md at https://github.com/rsms/js-lru for details. */ /** An entry holds the key and value, and pointers to any older and newer entries. * @public */ class Entry { key; value; newer; older; constructor(key, value) { this.key = key; this.value = value; } } exports.Entry = Entry; class EntryIterator { _entry; constructor(oldestEntry) { this._entry = oldestEntry; } next() { const ent = this._entry; if (!ent) return { done: true, value: undefined }; this._entry = ent.newer; const val = [ent.key, ent.value]; return { done: false, value: val }; } } class KeyIterator { _entry; constructor(oldestEntry) { this._entry = oldestEntry; } next() { const ent = this._entry; if (!ent) return { done: true, value: undefined }; this._entry = ent.newer; return { done: false, value: ent.key }; } } class ValueIterator { _entry; constructor(oldestEntry) { this._entry = oldestEntry; } next() { const ent = this._entry; if (!ent) return { done: true, value: undefined }; this._entry = ent.newer; return { done: false, value: ent.value }; } } /** * A mapping of a key/value pairs, where the size of the cache can be limited. * * When entries are inserted, if the cache is "full", the * least-recently-used (LRU) value is dropped. When entries are retrieved, they are moved to the front of the LRU list. * * Illustration of the design: * * ``` * * entry entry entry entry * ______ ______ ______ ______ * | head |.newer => | |.newer => | |.newer => | tail | * | A | | B | | C | | D | * |______| <= older.|______| <= older.|______| <= older.|______| * * removed <-- <-- <-- <-- <-- <-- <-- <-- <-- <-- <-- added * ``` * @public */ class LRUCache { _container; /** Current number of items */ size; /** Maximum number of items this cache can hold */ limit; /** Least recently-used entry. Invalidated when cache is modified. */ oldest; /** Most recently-used entry. Invalidated when cache is modified. */ newest; /** * Construct a new LRUCache to hold up to `limit` entries. */ constructor(limit, container) { this.size = 0; this.limit = limit; this.oldest = this.newest = undefined; this._container = container; } markEntryAsUsed(entry) { if (entry === this.newest) return; // Already the most recently used entry, so no need to update the list // HEAD--------------TAIL // <.older .newer> // <--- add direction -- // A B C <D> E if (entry.newer) { if (entry === this.oldest) { this.oldest = entry.newer; } entry.newer.older = entry.older; // C <-- E. } if (entry.older) { entry.older.newer = entry.newer; // C. --> E } entry.newer = undefined; // D --x entry.older = this.newest; // D. --> E if (this.newest) { this.newest.newer = entry; // E. <-- D } this.newest = entry; } /** Replace all values in this cache with key-value pairs (2-element Arrays) from provided iterable. */ assign(entries) { let entry; let limit = this.limit || Number.MAX_VALUE; this._container.clear(); const it = entries[Symbol.iterator](); for (let itv = it.next(); !itv.done; itv = it.next()) { const e = new Entry(itv.value[0], itv.value[1]); this._container.set(e.key, e); if (!entry) { this.oldest = e; } else { entry.newer = e; e.older = entry; } entry = e; if (limit-- === 0) { throw new Error("overflow"); } } this.newest = entry; this.size = this._container.size; } /** Get and register recent use of <key>. * Returns the value associated with <key> or undefined if not in cache. */ get(key) { // First, find our cache entry const entry = this._container.get(key); if (!entry) return; // Not cached. Sorry. // As <key> was found in the cache, register it as being requested recently this.markEntryAsUsed(entry); return entry.value; } /** Put <value> into the cache associated with <key>. Replaces any existing entry with the same key. * @returns `this`. */ set(key, value) { let entry = this._container.get(key); if (entry) { // update existing entry.value = value; this.markEntryAsUsed(entry); return this; } // new entry this._container.set(key, (entry = new Entry(key, value))); if (this.newest) { // link previous tail to the new tail (entry) this.newest.newer = entry; entry.older = this.newest; } else { // we're first in this.oldest = entry; } // add new entry to the end of the linked list -- it is now the freshest entry. this.newest = entry; ++this.size; if (this.size > this.limit) { // we hit the limit -- remove the head this.shift(); } return this; } /** Purge the least recently used (oldest) entry from the cache. * @returns The removed entry or undefined if the cache was empty. */ shift() { const entry = this.oldest; if (entry) { if (entry.newer) { // advance the list this.oldest = entry.newer; this.oldest.older = undefined; } else { // the cache is exhausted this.oldest = undefined; this.newest = undefined; } // Remove last strong reference to <entry> and remove links from the purged // entry being returned: entry.newer = entry.older = undefined; this._container.delete(entry.key); --this.size; return [entry.key, entry.value]; } return undefined; } /** Access value for `key` without registering recent use. Useful if you do not * want to change the state of the cache, but only "peek" at it. * @returns The value associated with `key` if found, or undefined if not found. */ find(key) { const e = this._container.get(key); return e ? e.value : undefined; } /** Check if there's a value for key in the cache without registering recent use. */ has(key) { return this._container.has(key); } /** Remove entry `key` from cache and return its value. * @returns The removed value, or undefined if not found. */ delete(key) { const entry = this._container.get(key); if (!entry) return; this._container.delete(entry.key); if (entry.newer && entry.older) { // re-link the older entry with the newer entry entry.older.newer = entry.newer; entry.newer.older = entry.older; } else if (entry.newer) { // remove the link to us entry.newer.older = undefined; // link the newer entry to head this.oldest = entry.newer; } else if (entry.older) { // remove the link to us entry.older.newer = undefined; // link the newer entry to head this.newest = entry.older; } else { // if(entry.older === undefined && entry.newer === undefined) { this.oldest = this.newest = undefined; } this.size--; return entry.value; } /** Removes all entries */ clear() { // Note: clearing links should be safe, as we don't expose live links to user this.oldest = this.newest = undefined; this.size = 0; this._container.clear(); } /** Returns an iterator over all keys, starting with the oldest. */ keys() { return this.oldest ? new KeyIterator(this.oldest) : undefined; } /** Returns an iterator over all values, starting with the oldest. */ values() { return this.oldest ? new ValueIterator(this.oldest) : undefined; } /** Returns an iterator over all entries, starting with the oldest. */ entries() { return this.oldest ? new EntryIterator(this.oldest) : undefined; } /** Call `fun` for each entry, starting with the oldest entry. */ forEach(fun, thisObj) { if (typeof thisObj !== "object") { thisObj = this; // eslint-disable-line @typescript-eslint/no-this-alias } let entry = this.oldest; while (entry) { fun.call(thisObj, entry.value, entry.key, this); entry = entry.newer; } } /** Returns a JSON (array) representation */ toJSON() { const s = new Array(this.size); let i = 0; let entry = this.oldest; while (entry) { s[i++] = { key: entry.key, value: entry.value }; entry = entry.newer; } return s; } /** Returns a String representation */ toString() { let s = ""; let entry = this.oldest; while (entry) { s += `${String(entry.key)}:${entry.value}`; entry = entry.newer; if (entry) { s += " < "; } } return s; } } exports.LRUCache = LRUCache; /** A [[LRUCache]] using a standard Map as its internal storage. * @public */ class LRUMap extends LRUCache { /** * Construct a new LRUMap to hold up to `limit` entries. */ constructor(limit) { super(limit, new Map()); } } exports.LRUMap = LRUMap; /** A [[LRUCache]] using a [[Dictionary]] as its internal storage, permitting custom key comparison logic. * @public */ class LRUDictionary extends LRUCache { /** * Construct a new LRUDictionary to hold up to `limit` entries. * @param limit The maximum number of entries permitted in the dictionary. * @param compareKeys The function used to compare keys within the dictionary. */ constructor(limit, compareKeys) { super(limit, new Dictionary_1.Dictionary(compareKeys)); } } exports.LRUDictionary = LRUDictionary; //# sourceMappingURL=LRUMap.js.map