@woosh/meep-engine
Version:
Pure JavaScript game engine. Fully featured and production ready.
312 lines (222 loc) • 7.01 kB
JavaScript
import { DomElementBinding } from "./DomElementBinding.js";
import { EnginePlugin } from "../../plugin/EnginePlugin.js";
import { array_remove_first } from "../../../core/collection/array/array_remove_first.js";
export class DomElementProcessorManager extends EnginePlugin {
constructor() {
super();
this.id = "dom-element-processor-manager";
/**
*
* @type {Element|null}
*/
this.__root = null;
/**
*
* @type {DomElementBindingDescription[]}
* @private
*/
this.__descriptors = [];
/**
*
* @type {DomElementBinding[]}
* @private
*/
this.__bindings = [];
/**
*
* @type {Map<Element, DomElementBinding[]>}
* @private
*/
this.__element_to_bindings = new Map();
/**
*
* @type {function}
* @private
*/
this.__bound_mutation_callback = this.__mutation_callback.bind(this);
/**
*
* @type {MutationObserver}
* @private
*/
this.__mutation_observer = new MutationObserver(this.__bound_mutation_callback);
}
/**
*
* @param {DomElementBindingDescription} descriptor
*/
register(descriptor) {
this.__descriptors.push(descriptor);
}
/**
*
* @param {DomElementBindingDescription} descriptor
* @return {boolean}
*/
deregister(descriptor) {
if (array_remove_first(this.__descriptors, descriptor)) {
// element removed
// TODO cleanup whatever effects have been applied to elements matching the descriptor
return true;
} else {
// not found
return false;
}
}
/**
*
* @param {sequence<MutationRecord>} mutations_list
* @param {MutationObserver} observer
* @private
*/
__mutation_callback(mutations_list, observer) {
for (const mutation of mutations_list) {
if (mutation.type === 'childList') {
const added_nodes = mutation.addedNodes;
const removed_nodes = mutation.removedNodes;
const added_nodes_count = added_nodes.length;
for (let i = 0; i < added_nodes_count; i++) {
const node = added_nodes[i];
this.__handleAttachedElement(node);
}
const removed_nodes_count = removed_nodes.length;
for (let i = 0; i < removed_nodes_count; i++) {
const node = removed_nodes[i];
this.__handleRemovedElement(node);
}
}
}
}
/**
*
* @param {Element} element
* @param {DomElementBindingDescription} descriptor
* @private
*/
__bind(element, descriptor) {
const binding = new DomElementBinding();
binding.element = element;
binding.description = descriptor;
const processor = binding.description.processor(element);
processor.initialize(this);
binding.processor = processor;
binding.bind();
let bindings = this.__element_to_bindings.get(element);
if (bindings === undefined) {
bindings = [];
this.__element_to_bindings.set(element, bindings);
}
bindings.push(binding);
}
/**
*
* @param {Element} element
* @private
*/
__handleAttachedElement(element) {
if (!(element instanceof Element)) {
return;
}
const descriptors = this.__descriptors;
const n = descriptors.length;
for (let i = 0; i < n; i++) {
const descriptor = descriptors[i];
const selector = descriptor.selector;
if (element.matches(selector)) {
this.__bind(element, descriptor);
}
const matching_elements = element.querySelectorAll(selector);
const match_count = matching_elements.length;
for (let j = 0; j < match_count; j++) {
const match = matching_elements[j];
this.__bind(match, descriptor);
}
}
}
/**
*
* @param {Element} element
* @private
*/
__handleRemovedElement(element) {
/**
*
* @type {DomElementBinding[]}
*/
const bindings = this.__element_to_bindings.get(element);
if (bindings !== undefined) {
this.__element_to_bindings.delete(element);
const n = bindings.length;
for (let i = 0; i < n; i++) {
const binding = bindings[i];
binding.unbind();
}
}
const child_nodes = element.children;
if (child_nodes !== undefined) {
const child_nodes_count = child_nodes.length;
for (let i = 0; i < child_nodes_count; i++) {
const child = child_nodes[i];
this.__handleRemovedElement(child);
}
}
}
/**
*
* @return {Engine|null}
*/
getEngine() {
return this.engine;
}
/**
*
* @param {number} timeDelta in seconds
* @private
*/
__handle_frame(timeDelta) {
this.__element_to_bindings.forEach(this.__handle_frame_entry, this);
}
/**
*
* @param {DomElementBinding[]} value
* @param {Element} key
* @private
*/
__handle_frame_entry(value, key) {
const n = value.length;
for (let i = 0; i < n; i++) {
const binding = value[i];
binding.processor.handleFrame();
}
}
async startup() {
const view = this.getEngine().viewStack;
this.link(view.el);
return super.startup();
}
async shutdown() {
this.unlink();
return super.shutdown();
}
/**
*
* @param {Element} root
*/
link(root) {
this.__root = root;
this.__mutation_observer.observe(this.__root, {
childList: true,
subtree: true,
attributes: false,
characterData: false,
attributeOldValue: false
});
this.__handleAttachedElement(root);
this.getEngine().graphics.on.preRender.add(this.__handle_frame, this);
}
unlink() {
this.__mutation_observer.disconnect();
this.__root = null;
this.getEngine().graphics.on.preRender.remove(this.__handle_frame, this);
}
}