@woosh/meep-engine
Version:
Pure JavaScript game engine. Fully featured and production ready.
324 lines (248 loc) • 7.29 kB
JavaScript
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;
}
}