UNPKG

@woosh/meep-engine

Version:

Pure JavaScript game engine. Fully featured and production ready.

324 lines (248 loc) • 7.29 kB
import { combine_hash } from "../../core/collection/array/combine_hash.js"; import { computeHashArray } from "../../core/collection/array/computeHashArray.js"; import { isArrayEqual } from "../../core/collection/array/isArrayEqual.js"; import { KeyValuePair } from "../../core/collection/KeyValuePair.js"; import { computeStringHash } from "../../core/primitives/strings/computeStringHash.js"; /** * * @param {KeyValuePair<string,string>} pair */ function computeStringPairHash(pair) { return combine_hash( computeStringHash(pair.key), computeStringHash(pair.value) ); } /** * * @param {KeyValuePair<string, string>} a * @param {KeyValuePair<string, string>} b * @returns */ function byKey(a, b) { const a_key = a.key; const b_key = b.key; if (a_key === b_key) { return 0; } const a_key_type = typeof a_key; const b_key_type = typeof b_key; const type_diff = a_key_type.localeCompare(b_key_type); if (type_diff !== 0) { return type_diff; } if (a_key_type !== "string") { // unexpected types return 0; } return a_key.localeCompare(b_key); } export class HTMLElementCacheKey { constructor() { /** * * @type {KeyValuePair[]} */ this.attributes = []; /** * * @type {KeyValuePair[]} */ this.style = []; /** * * @type {string} */ this.tag = ""; /** * * @type {number} * @private */ this.__hash = -1; } /** * * @param {string} name * @param {string} value */ setAttribute(name, value) { const existing = this.#getAttributeContainer(name); if (existing !== undefined) { if (existing.value === value) { // no change return; } existing.value = value; } else { this.attributes.push(new KeyValuePair(name, value)); } this.update(); } /** * * @param {string} name * @returns {string|null} */ getAttribute(name) { const container = this.#getAttributeContainer(name); if (container === undefined) { return null; } return container.value; } /** * * @param {string} name * @returns {undefined|KeyValuePair} */ #getAttributeContainer(name) { const attributes = this.attributes; const attribute_count = attributes.length; for (let i = 0; i < attribute_count; i++) { const attribute = attributes[i]; if (attribute.key === name) { return attribute; } } // not found return undefined; } /** * Required for equality and hash to work consistently */ #sort() { this.attributes.sort(byKey); this.style.sort(byKey); } update() { this.#sort(); this.#updateHash(); } toJSON() { const attributes = {}; const attributeCount = this.attributes.length; for (let i = 0; i < attributeCount; i++) { const attribute = this.attributes[i]; attributes[attribute.key] = attribute.value; } const styles = {}; const styleCount = this.style.length; for (let i = 0; i < styleCount; i++) { const style = this.style[i]; styles[style.key] = style.value; } return { attributes, styles }; } /** * * @param {HTMLElement} el */ initializeFromElement(el) { this.tag = el.tagName.toLowerCase(); const l = el.classList.length; const classList = []; for (let i = 0; i < l; i++) { const c = el.classList[i]; classList.push(c); } let i = 0; //read style /** * * @type {CSSStyleDeclaration} */ const style = el.style; const styleCount = style.length; for (let i = 0; i < styleCount; i++) { /** * * @type {string} */ const s = style.item(i); /** * * @type {KeyValuePair<String, String>} */ const pair = new KeyValuePair(s, style.getPropertyValue(s)); this.style[i] = pair; } this.style.splice(i, this.style.length - i); //read attributes const attributeNames = el.getAttributeNames(); const attributeCount = attributeNames.length; for (i = 0; i < attributeCount; i++) { const attributeName = attributeNames[i]; const value = el.getAttribute(attributeName); const pair = new KeyValuePair(attributeName, value); this.attributes[i] = pair; } this.attributes.splice(i, this.attributes.length - i); this.update(); } #updateHash() { //compute style hash const styleHash = computeHashArray(this.style, computeStringPairHash); //compute attribute hash const attributeHash = computeHashArray(this.attributes, computeStringPairHash); const tagHash = computeStringHash(this.tag); this.__hash = combine_hash( styleHash, attributeHash, tagHash ); } hash() { return this.__hash; } /** * * @param {HTMLElementCacheKey} other * @returns {boolean} */ equals(other) { return this.tag === other.tag && isArrayEqual(this.style, other.style) && isArrayEqual(this.attributes, other.attributes) ; } reset() { const attributes = this.attributes; attributes.splice(0, attributes.length); const style = this.style; style.splice(0, style.length); } /** * * @param {HTMLElement} el */ static fromElement(el) { const key = new HTMLElementCacheKey(); key.initializeFromElement(el); return key; } /** * @template T * @returns {HTMLElement|T} */ createElement() { const el = document.createElement(this.tag); const styles = this.style; const stylesSize = styles.length; const styleDeclaration = el.style; for (let i = 0; i < stylesSize; i++) { const c = styles[i]; styleDeclaration.setProperty(c.key, c.value); } const attributes = this.attributes; const attributeCount = attributes.length; for (let i = 0; i < attributeCount; i++) { const attribute = attributes[i]; el.setAttribute(attribute.key, attribute.value); } return el; } }