@woosh/meep-engine
Version:
Pure JavaScript game engine. Fully featured and production ready.
240 lines (185 loc) • 5.94 kB
JavaScript
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() {
}
}