UNPKG

@woosh/meep-engine

Version:

Pure JavaScript game engine. Fully featured and production ready.

328 lines (265 loc) • 7.39 kB
import { assert } from "../../../../core/assert.js"; import { array_shrink_to_size } from "../../../../core/collection/array/array_shrink_to_size.js"; import { binarySearchHighIndex } from "../../../../core/collection/array/binarySearchHighIndex.js"; import Signal from "../../../../core/events/signal/Signal.js"; import { IllegalStateException } from "../../../../core/fsm/exceptions/IllegalStateException.js"; /** * @readonly * @enum {number} */ const IncrementalDeltaSetState = { Clear: 0, Building: 1, Ready: 2 }; /** * accelerated Set data structure optimized for incremental re-building * Always sorted * @template V */ export class IncrementalDeltaSet { /** * * @param {function(V,V):number} compare stable comparator */ constructor(compare) { assert.isFunction(compare, 'compare'); /** * * @type {V[]} * @private */ this.__elements = []; /** * * @type {number} * @private */ this.__elements_cursor = 0; /** * * @type {V[]} * @private */ this.__swap_elements = []; /** * * @type {number} * @private */ this.__swap_elements_count = 0; /** * * @type {function(V, V): number} * @private */ this.__compare = compare; /** * Current version of the state, each time the set is updated - version changes * @type {number} */ this.version = 0; /** * Number of currently held elements * @type {number} */ this.size = 0; /** * * @type {Signal} */ this.onUpdateFinished = new Signal(); /** * * @type {Signal} */ this.onAdded = new Signal(); /** * * @type {Signal} */ this.onRemoved = new Signal(); /** * * @type {IncrementalDeltaSetState|number} */ this.state = IncrementalDeltaSetState.Clear; } /** * * @return {V[]} */ get elements() { return this.__elements; } /** * * @param {(V)=>any} visitor * @param {*} [visitorContext] */ forEach(visitor, visitorContext) { const n = this.size; const elements = this.__elements; for (let i = 0; i < n; i++) { const el = elements[i]; visitor.call(visitorContext, el); } } /** * * @param {V} element * @returns {boolean} */ contains(element) { return this.__indexOf(element) !== -1; } /** * * @param {V} element * @return {number} * @private */ __indexOf(element) { const i = this.__indexExpected(element); if (this.__elements[i] === element) { return i; } else { return -1; } } /** * * @param {V} element * @return {number} * @private */ __indexExpected(element) { return binarySearchHighIndex(this.__elements, element, this.__compare, 0, this.size - 1); } /** * * @param {V} element * @return {boolean} */ forceAdd(element) { const i = this.__indexExpected(element); if (this.elements[i] === element) { return false; } this.__elements.splice(i, 0, element); this.size++; this.version++; return true; } /** * * @param {V} element * @returns {boolean} */ forceRemove(element) { if (this.state === IncrementalDeltaSetState.Building) { throw new IllegalStateException("Cannot remove during build"); } const i = this.__indexOf(element); if (i !== -1) { this.__elements.splice(i, 1); this.size--; this.version++; return true; } else { return false; } } clear() { if (this.size === 0) { // already empty return; } this.size = 0; this.state = IncrementalDeltaSetState.Clear; this.version++; } includeAll() { throw new Error('deprecated #includeAll'); } initializeUpdate() { // perform swap const swp_els = this.__elements; this.__elements = this.__swap_elements this.__swap_elements = swp_els; const swp_count = this.size; this.size = this.__swap_elements_count; this.__swap_elements_count = swp_count; this.__elements_cursor = 0; this.state = IncrementalDeltaSetState.Building; this.version++; } finalizeUpdate() { assert.equal(this.state, IncrementalDeltaSetState.Building, `Expected BUILDING state, instead got '${this.state}'`); const array_main_count = this.__elements_cursor; const array_previous_count = this.__swap_elements_count; this.size = array_main_count; // sort newly populated elements array const array_main = this.__elements; const array_previous = this.__swap_elements; const compare = this.__compare; const onAdded = this.onAdded; const onRemoved = this.onRemoved; array_shrink_to_size(array_main, array_main_count); array_main.sort(compare); let i0 = 0, i1 = 0; while (i0 < array_main_count && i1 < array_previous_count) { const el_0 = array_main[i0]; const el_1 = array_previous[i1]; const diff = compare(el_0, el_1); if (diff === 0) { i0++; i1++; // found match } else if (diff < 0) { i0++; // addition onAdded.send1(el_0); } else { i1++; // removal onRemoved.send1(el_1); } } // process remainders for (; i0 < array_main_count; i0++) { onAdded.send1(array_main[i0]); } for (; i1 < array_previous_count; i1++) { onRemoved.send1(array_previous[i1]); } this.state = IncrementalDeltaSetState.Ready; //dispatch notification this.onUpdateFinished.send0(); } /** * * @param {V} element */ push(element) { const elements = this.__elements; elements[this.__elements_cursor++] = element; } /** * * @param {IncrementalDeltaSet<V>} other */ pushOther(other) { const elements = other.__elements; const n = other.size; for (let i = 0; i < n; i++) { const el = elements[i]; this.push(el); } } } /** * @readonly * @type {boolean} */ IncrementalDeltaSet.prototype.isIncrementalDeltaSet = true;