@woosh/meep-engine
Version:
Pure JavaScript game engine. Fully featured and production ready.
239 lines (184 loc) • 5.42 kB
JavaScript
import { min2 } from "../../../core/math/min2.js";
import { ResourceAccessKind } from "../../../core/model/ResourceAccessKind.js";
import { ResourceAccessSpecification } from "../../../core/model/ResourceAccessSpecification.js";
import { System } from "../System.js";
import { Transform } from "../transform/Transform.js";
import { TransformAttachment, TransformAttachmentFlags } from "./TransformAttachment.js";
/**
* @readonly
* @type {number}
*/
const QUEUE_ITERATION_COUNT = 32;
class UpdateContext {
constructor() {
/**
*
* @type {TransformAttachment|null}
*/
this.attachment = null;
/**
*
* @type {Transform|null}
*/
this.transform = null;
/**
*
* @type {number}
*/
this.entity = -1;
/**
*
* @type {Transform|null}
*/
this.parent_transform = null;
/**
*
* @type {EntityComponentDataset|null}
*/
this.ecd = null;
}
toString() {
return `UpdateContext{ attachment:${this.attachment}, entity:${this.entity} }`;
}
update() {
this.transform.multiplyTransforms(
this.parent_transform,
this.attachment.transform
);
}
/**
*
* @returns {boolean}
*/
bind_parent() {
const parent_transform = this.ecd.getComponent(this.attachment.parent, Transform);
if (parent_transform === undefined) {
return false;
}
this.parent_transform = parent_transform;
return true;
}
link() {
const t_parent = this.parent_transform;
t_parent.subscribe(this.update, this);
const t_attachment = this.attachment.transform;
t_attachment.subscribe(this.update, this);
}
unlink() {
const transform = this.parent_transform;
transform.unsubscribe(this.update, this);
const t_attachment = this.attachment.transform;
t_attachment.unsubscribe(this.update, this);
}
}
export class TransformAttachmentSystem extends System {
/**
*
* @type {UpdateContext[]}
* @private
*/
__contexts = [];
/**
*
* @type {UpdateContext[]}
* @private
*/
__queue = [];
__queue_size = 0;
__queue_cursor = 0;
constructor() {
super();
this.dependencies = [TransformAttachment, Transform];
this.components_used = [
ResourceAccessSpecification.from(TransformAttachment, ResourceAccessKind.Read),
ResourceAccessSpecification.from(Transform, ResourceAccessKind.Read | ResourceAccessKind.Write),
];
}
/**
*
* @param {UpdateContext} ctx
* @private
*/
__finalize_link(ctx) {
ctx.link();
this.__contexts[ctx.entity] = ctx;
if ((ctx.attachment.flags & TransformAttachmentFlags.Immediate) !== 0) {
ctx.update();
}
}
/**
*
* @param {UpdateContext} ctx
* @private
*/
__enqueue(ctx) {
this.__queue[this.__queue_size++] = ctx;
}
/**
*
* @param {number} entity
* @returns {boolean}
* @private
*/
__dequeue_entity(entity) {
for (let i = 0; i < this.__queue_size; i++) {
const ctx = this.__queue[i];
if (ctx.entity === entity) {
this.__queue.splice(i, 1);
this.__queue_size--;
return true;
}
}
return false;
}
/**
*
* @param {TransformAttachment} attachment
* @param {Transform} transform
* @param {number} entity
*/
link(attachment, transform, entity) {
const ctx = new UpdateContext();
ctx.attachment = attachment;
ctx.transform = transform;
ctx.entity = entity;
ctx.ecd = this.entityManager.dataset;
if (ctx.bind_parent()) {
this.__finalize_link(ctx);
} else {
// failed to bind parent, queue up instead
this.__enqueue(ctx);
}
}
/**
*
* @param {TransformAttachment} attachment
* @param {Transform} transform
* @param {number} entity
*/
unlink(attachment, transform, entity) {
const ctx = this.__contexts[entity];
if (ctx !== undefined) {
delete this.__contexts[entity];
ctx.unlink();
} else {
// no context found, check the queue
this.__dequeue_entity(entity);
}
}
update(timeDelta) {
const step_count = min2(this.__queue_size, QUEUE_ITERATION_COUNT);
for (let i = 0; i < step_count; i++) {
const index = this.__queue_cursor % this.__queue_size;
const ctx = this.__queue[index];
if (ctx.bind_parent()) {
// parent obtained
this.__finalize_link(ctx);
this.__queue.splice(index, 1);
this.__queue_size--;
} else {
this.__queue_cursor++;
}
}
}
}