UNPKG

@woosh/meep-engine

Version:

Pure JavaScript game engine. Fully featured and production ready.

240 lines (185 loc) • 5.94 kB
import { assert } from "../assert.js"; import { Deque } from "../collection/queue/Deque.js"; const MAXIMUM_CAPACITY = 2147483647; /** * The initial percent of the maximum weighted capacity dedicated to the main space. * @type {number} */ const PERCENT_MAIN = 0.99; /** * The percent of the maximum weighted capacity dedicated to the main's protected space. * @type {number} */ const PERCENT_MAIN_PROTECTED = 0.8; /** * The difference in hit rates that restarts the climber. * @type {number} */ const HILL_CLIMBER_RESTART_THRESHOLD = 0.05; /** * The percent of the total size to adapt the window by * @type {number} */ const HILL_CLIMBER_STEP_PERCENT = 0.0625; /** * The rate to decrease the step size to adapt by * @type {number} */ const HILL_CLIMBER_STEP_DECAY_RATE = 0.98; let random_counter = 0; /** * @template K,V */ class Node { /** * @template K,V */ constructor() { } /** * @returns {Node<K,V>} */ getNextInAccessOrder() { } /** * @returns {number} */ getPolicyWeight() { } makeMainProbation() { } } /** * UNTESTED, DO NOT USE. * Use {@link Cache} or {@link LoadingCache} instead * @template K,V */ export class CacheV2 { /** * @template K,V */ constructor() { /** * * @type {Deque<Node<K,V>>} * @private */ this.__accessOrderWindowDeque = new Deque(); /** * * @type {Deque<Node<K,V>>} * @private */ this.__accessOrderProbationDeque = new Deque(); this.__windowWeightedSize = 0; this.__window_maximum = 0; this.__maximum_weight = 0; } /** * @returns {number} * @protected */ weightedSize() { throw new Error('Not implemented'); } /** * @returns {FrequencySketch} * @protected */ frequencySketch() { throw new Error('Not implemented'); } setMaximum(value) { } setWindowMaximum(value) { this.__window_maximum = value; } setMainProtectedMaximum(value) { } /** * * @param value */ setStepSize(value) { } setHitsInSample(value) { } setMissesInSample(value) { } /** * Sets the maximum weighted size of the cache. The caller may need to perform a maintenance cycle * to eagerly evicts entries until the cache shrinks to the appropriate size. * @param {number} maximum */ setMaximumSize(maximum) { assert.isNonNegativeInteger(maximum, 'maximum'); if (maximum === this.__maximum_weight) { return; } const max = Math.min(maximum, MAXIMUM_CAPACITY); const window = max - (PERCENT_MAIN * max); const mainProtected = PERCENT_MAIN_PROTECTED * (max - window); this.setMaximum(max); this.setWindowMaximum(window); this.setMainProtectedMaximum(mainProtected); this.setHitsInSample(0); this.setMissesInSample(0); this.setStepSize(-HILL_CLIMBER_STEP_PERCENT * max); if (this.weightedSize() >= (max >>> 1)) { // lazily initialize when close to the maximum size this.frequencySketch().ensureCapacity(max); } } /** * Evicts entries if the cache exceeds the maximum. * @private */ evictEntries() { const evicted_count = this.evictFromWindow(); this.evictFromMain(evicted_count); } /** * Determines if the candidate should be accepted into the main space, as determined by its * frequency relative to the victim. A small amount of randomness is used to protect against hash * collision attacks, where the victim's frequency is artificially raised so that no new entries * are admitted. * * @param {number} candidate_hash the key for the entry being proposed for long term retention * @param {number} victim_hash the key for the entry chosen by the eviction policy for replacement * @returns {boolean} */ admit(candidate_hash, victim_hash) { const frequencySketch = this.frequencySketch(); const victim_frequency = frequencySketch.frequency(victim_hash); const candidate_frequency = frequencySketch.frequency(candidate_hash); if (candidate_frequency > victim_frequency) { return true; } else if (candidate_frequency <= 5) { // The maximum frequency is 15 and halved to 7 after a reset to age the history. An attack // exploits that a hot candidate is rejected in favor of a hot victim. The threshold of a warm // candidate reduces the number of random acceptances to minimize the impact on the hit rate. return false; } // if we got here, frequencies are identical, we arbitrate randomly return (random_counter++ & 127) === 0; } evictFromWindow() { let candidates = 0; let node = this.__accessOrderWindowDeque.getFirst(); while (this.__windowWeightedSize > this.__window_maximum && node !== undefined) { const next = node.getNextInAccessOrder(); const node_policy_weight = node.getPolicyWeight(); if (node_policy_weight !== 0) { node.makeMainProbation(); this.__accessOrderWindowDeque.remove(node); this.__accessOrderProbationDeque.addLast(node); candidates++; this.__windowWeightedSize -= node_policy_weight; } node = next; } return candidates; } evictFromMain() { } }