import {compose, EventEmitter} from '@avocado/core'; import {QuadTree, Rectangle, Vector} from '@avocado/math'; import {Synchronized} from '@avocado/state'; import {EntityCreatePacket} from '../packets/entity-create.packet'; import {EntityRemovePacket} from '../packets/entity-remove.packet'; import {Entity} from '../index'; const decorate = compose( EventEmitter, Synchronized, ); export class EntityList extends decorate(class {}) { constructor() { super(); this._afterDestructionTickers = []; this._entities = {}; this._entitiesJustAdded = []; this._entitiesJustRemoved = []; this._entityTickers = [] this._flatEntities = []; this._quadTree = new QuadTree(); } *[Symbol.iterator]() { for (const uuid in this._entities) { const entity = this._entities[uuid]; yield entity; } } acceptPacket(packet) { if (packet instanceof EntityCreatePacket) { if (!this.findEntity(packet.data.uuid)) { const entity = new Entity(packet.data); entity.instanceUuid = packet.data.uuid; this.addEntity(entity); } } } addEntity(entity) { const uuid = entity.instanceUuid; this._entities[uuid] = entity; this._flatEntities.push(entity); this._entityTickers.push(entity.tick); if (AVOCADO_SERVER) { this._entitiesJustAdded.push(entity); } entity.setIntoList(this); entity.once('destroy', () => { this.removeEntity(entity); // In the process of destroying, allow entities to specify tickers that // must live on past destruction. const tickers = entity.invokeHookFlat('afterDestructionTickers'); for (let i = 0; i < tickers.length; i++) { const ticker = tickers[i]; this._afterDestructionTickers.push(ticker); } }); this.emit('entityAdded', entity); } destroy() { for (let i = 0; i < this._flatEntities.length; i++) { this._flatEntities[i].destroy(); } } findEntity(uuid) { if (uuid in this._entities) { return this._entities[uuid]; } } packetsForUpdate(force = false) { const packets = []; if (!force) { for (let i = 0; i < this._entitiesJustAdded.length; i++) { const entity = this._entitiesJustAdded[i]; packets.push(new EntityCreatePacket(entity.toJSON(), entity)); } this._entitiesJustAdded = []; } for (let i = 0; i < this._flatEntities.length; i++) { const entityPackets = this._flatEntities[i].packetsForUpdate(force); for (let j = 0; j < entityPackets.length; j++) { packets.push(entityPackets[j]); } } if (!force) { for (let i = 0; i < this._entitiesJustRemoved.length; i++) { const entity = this._entitiesJustRemoved[i]; packets.push(new EntityRemovePacket({}, entity)); } this._entitiesJustRemoved = []; } return packets; } get quadTree() { return this._quadTree; } removeEntity(entity) { const uuid = entity.instanceUuid; if (!(uuid in this._entities)) { return; } if (AVOCADO_SERVER) { this._entitiesJustRemoved.push(entity); } delete this._entities[uuid]; this._flatEntities.splice(this._flatEntities.indexOf(entity), 1); this._entityTickers.splice(this._entityTickers.indexOf(entity.tick), 1); this.emit('entityRemoved', entity); } tick(elapsed) { // Run after destruction tickers. if (this._afterDestructionTickers.length > 0) { this.tickAfterDestructionTickers(elapsed); } // Run normal tickers. this.tickEntities(elapsed) } tickAfterDestructionTickers(elapsed) { const finishedTickers = []; for (let i = 0; i < this._afterDestructionTickers.length; ++i) { const ticker = this._afterDestructionTickers[i]; if (ticker(elapsed)) { finishedTickers.push(ticker); } } for (let i = 0; i < finishedTickers.length; ++i) { const ticker = finishedTickers[i]; const index = this._afterDestructionTickers.indexOf(ticker); this._afterDestructionTickers.splice(index, 1); } } tickEntities(elapsed) { for (let i = 0; i < this._entityTickers.length; i++) { this._entityTickers[i](elapsed); } } visibleEntities(query) { const entities = []; const entitiesChecked = []; const quadTree = this._quadTree; const nodes = quadTree.search(query); // Check all nodes. for (let i = 0; i < nodes.length; ++i) { const node = nodes[i]; const entity = node.data[2]; const aabb = node.data[3]; if (-1 === entitiesChecked.indexOf(entity)) { entitiesChecked.push(entity); // Make sure the AABB is actually in the query due to expansion. if (Rectangle.intersects(query, aabb)) { entities.push(entity); } } } return entities; } } export {EntityListView} from './view';