UNPKG

@woosh/meep-engine

Version:

Pure JavaScript game engine. Fully featured and production ready.

318 lines (230 loc) • 8.07 kB
import { System } from "../System.js"; import { Attachment } from "./Attachment.js"; import { Transform } from "../transform/Transform.js"; import { AttachmentSockets } from "../sockets/AttachmentSockets.js"; import { assert } from "../../../core/assert.js"; import Mesh from "../../graphics/ecs/mesh/Mesh.js"; import { BoneAttachmentBinding } from "./BoneAttachmentBinding.js"; import { TransformAttachmentBinding } from "./TransformAttachmentBinding.js"; import { ResourceAccessSpecification } from "../../../core/model/ResourceAccessSpecification.js"; import { ResourceAccessKind } from "../../../core/model/ResourceAccessKind.js"; export class AttachmentSystem extends System { dependencies = [Attachment, Transform]; components_used = [ ResourceAccessSpecification.from(AttachmentSockets, ResourceAccessKind.Read), ResourceAccessSpecification.from(Mesh, ResourceAccessKind.Read), ResourceAccessSpecification.from(Transform, ResourceAccessKind.Write) ]; /** * * @type {number[]} */ waiting = []; /** * * @type {Map<number,AttachmentBinding>} */ bindings = new Map(); /** * * @type {Map<number, AttachmentBinding[]>} */ parentBindingIndex = new Map(); /** * Are we waiting for something? * @param {number} entity * @returns {boolean} */ isWaiting(entity) { return this.waiting.indexOf(entity) !== -1; } /** * Attachment is missing something, wait for it * @param {number} entity */ wait(entity) { if (this.isWaiting(entity)) { //already waiting, do nothing return; } this.waiting.push(entity); } /** * Removed all wait records for a given entity * @param {number} entity * @returns {number} number of removed records */ clearAllWaitRecordsFor(entity) { let removedCount = 0; const waiting = this.waiting; let numRecords = waiting.length; for (let i = 0; i < numRecords; i++) { const entity = waiting[i]; if (entity === entity) { waiting.splice(i, 1); removedCount++; //update iterator i--; numRecords--; } } return removedCount; } /** * * @param {Attachment} attachment * @param {Transform} transform * @param {number} entity */ link(attachment, transform, entity) { this.wait(entity); this.processWaiting(); } processWaiting() { const em = this.entityManager; if (em === null) { return; } /** * * @type {EntityComponentDataset} */ const ecd = em.dataset; if (ecd === null) { return; } const waiting = this.waiting; let numRecords = waiting.length; for (let i = 0; i < numRecords; i++) { const entity = waiting[i]; /** * * @type {Attachment} */ const attachment = ecd.getComponent(entity, Attachment); /** * * @type {number} */ const parent = attachment.parent; if (!ecd.entityExists(parent)) { continue; } /** * * @type {AttachmentSockets} */ const sockets = ecd.getComponent(parent, AttachmentSockets); if (sockets === undefined) { //no sockets continue; } const parentTransform = ecd.getComponent(parent, Transform); if (parentTransform === undefined) { //no parent transform continue; } /** * * @type {AttachmentSocket|BoneAttachmentSocket} */ const attachmentSocket = sockets.get(attachment.socket); if (attachmentSocket === undefined) { //socket doesn't exist continue; } let binding; if (attachmentSocket.isBoneAttachmentSocket) { const boneName = attachmentSocket.boneName; assert.isString(boneName, 'boneName'); //try to get the bone const mesh = ecd.getComponent(parent, Mesh); if (mesh === undefined || !mesh.isLoaded) { continue; } const skeletonBone = mesh.getDescendantObjectByName(boneName); if (skeletonBone === null) { console.warn(`bone '${boneName}' not found`); //keep in the waiting list continue; } binding = new BoneAttachmentBinding(); binding.bone = skeletonBone; } else { //it's a transform socket binding = new TransformAttachmentBinding(); } binding.parentTransform = parentTransform; binding.parentEntity = parent; binding.attachedEntity = entity; binding.attachedTransform = ecd.getComponent(entity, Transform); binding.socket = attachmentSocket; binding.attachment = attachment; this.bindings.set(entity, binding); if (attachment.immediate) { binding.update(); } let parentBindings = this.parentBindingIndex.get(parent); if (parentBindings === undefined) { parentBindings = []; this.parentBindingIndex.set(parent, parentBindings); } parentBindings.push(binding); //update iterator this.waiting.splice(i, 1); binding.link(); i--; numRecords--; } } /** * * @param {Attachment} attachment * @param {Transform} transform * @param {number} entity */ unlink(attachment, transform, entity) { //clear wait queue this.clearAllWaitRecordsFor(entity); //remove potential binding this.bindings.delete(entity); const parent = attachment.parent; const parentBindings = this.parentBindingIndex.get(parent); if (parentBindings !== undefined) { const bindingCount = parentBindings.length; for (let i = 0; i < bindingCount; i++) { const attachmentBinding = parentBindings[i]; if (attachmentBinding.attachment === attachment) { if (bindingCount === 1) { // last binding, remove bucket this.parentBindingIndex.delete(parent); } else { // remove binding from the bucket parentBindings.splice(i, 1); } attachmentBinding.unlink(); break; } } } } __updateAll() { for (const [entity, bindings] of this.parentBindingIndex) { const n = bindings.length; for (let i = 0; i < n; i++) { const binding = bindings[i]; binding.update(); } } } update(timeDelta) { //process wait queue this.processWaiting(); const em = this.entityManager; const ecd = em.dataset; if (ecd === null) { return; } this.__updateAll(); } }