@woosh/meep-engine
Version:
Pure JavaScript game engine. Fully featured and production ready.
168 lines (128 loc) • 3.87 kB
JavaScript
import { CacheElement } from "../../cache/CacheElement.js";
import { HashMap } from "../../collection/map/HashMap.js";
import Signal from "../../events/signal/Signal.js";
/**
* @template Key, Value
*/
export class ImmutableObjectPool {
/**
* @readonly
* @type {Signal<Key,Value>}
*/
onRemoved = new Signal()
/**
*
* @type {HashMap<Key, CacheElement<Key,Value>[]>}
*/
data = new HashMap();
/**
*
* @type {CacheElement<Key,Value>|null}
* @private
*/
__first = null;
/**
*
* @type {CacheElement<Key,Value>|null}
* @private
*/
__last = null;
constructor({ capacity = 100, perKeyCapacity = 10 } = {}) {
/**
* How many items in total the pool can hold
* @type {number}
*/
this.capacity = capacity;
/**
* Maximum number of items that can be stored for the same key
* @type {number}
*/
this.perKeyCapacity = perKeyCapacity;
/**
* Current number of elements in the pool
* @type {number}
*/
this.size = 0;
}
get(key) {
// find the right bucket
const elements = this.data.get(key);
if (elements === undefined) {
return undefined;
}
let element;
if (elements.length > 1) {
element = elements.pop();
} else {
// last element
element = elements[0];
this.data.delete(key);
}
element.unlink();
this.size--;
return element.value;
}
/**
* Removed all elements from cache
* NOTE: {@link onRemoved} signal *does* get triggered for each element
*/
clear() {
while (this.__first !== null) {
this.__removeElement(this.__first);
}
}
/**
*
* @param {CacheElement<Key,Value>} el
* @private
*/
__removeElement(el) {
if (el === this.__first) {
this.__first = el.next;
}
if (el === this.__last) {
this.__last = el.previous;
}
el.unlink();
const key = el.key;
const elements = this.data.get(key);
const i = elements.indexOf(el);
elements.splice(i, 1);
if (elements.length === 0) {
this.data.delete(key);
}
this.size--;
this.onRemoved.send2(key, el.value);
}
add(key, value) {
let elements = this.data.get(key);
if (elements === undefined) {
// no elements for this key, create container
elements = [];
this.data.set(key, elements);
} else if (elements.length >= this.perKeyCapacity) {
// reached capacity for this key, discard element
return false;
}
if (this.size >= this.capacity) {
// at the capacity, need to evict one element
// eviction allows the pool to adapt to changing usage patterns, preventing it from getting saturated with values that are no longer relevant
this.__removeElement(this.__first);
}
const cache_element = new CacheElement();
cache_element.value = value;
cache_element.key = key;
// attach at the end of the list
cache_element.previous = this.__last;
if (this.__last !== null) {
this.__last.next = cache_element;
}
this.__last = cache_element;
if (this.__first === null) {
this.__first = cache_element;
}
elements.push(cache_element);
this.size++;
return true;
}
}