avocado-old/packages/entity/list/index.js
2019-05-03 14:53:54 -05:00

184 lines
4.8 KiB
JavaScript

import * as I from 'immutable';
import mapValues from 'lodash.mapvalues';
import {compose, EventEmitter} from '@avocado/core';
import {QuadTree, Rectangle, Vector} from '@avocado/math';
import {Synchronized} from '@avocado/state';
import {Entity} from '../index';
const decorate = compose(
EventEmitter,
Synchronized,
);
export class EntityList extends decorate(class {}) {
constructor() {
super();
this._afterDestructionTickers = [];
this._dirtyEntities = [];
this._entities = {};
this._flatEntities = [];
this._quadTree = new QuadTree();
}
*[Symbol.iterator]() {
for (const uuid in this._entities) {
const entity = this._entities[uuid];
yield entity;
}
}
addEntity(entity) {
const uuid = entity.instanceUuid;
this._entities[uuid] = entity;
this._flatEntities.push(entity);
this.state = this.state.set(uuid, entity.state);
entity.addTrait('listed');
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 (const entity of this) {
entity.destroy();
}
}
findEntity(uuid) {
if (uuid in this._entities) {
return this._entities[uuid];
}
}
markEntityDirty(entity) {
if (-1 === this._dirtyEntities.indexOf(entity)) {
this._dirtyEntities.push(entity);
}
}
patchStateStep(uuid, step) {
const entity = this._entities[uuid];
if ('/' === step.path) {
switch (step.op) {
case 'add':
// New entity. Create with patch as traits.
const newEntity = (new Entity()).fromJSON({
traits: step.value,
});
newEntity.instanceUuid = uuid;
this.addEntity(newEntity);
break;
case 'remove':
// Maybe already destroyed on the client?
if (entity && entity.is('existent')) {
entity.destroy();
}
break;
}
return;
}
if ('replace' === step.op && entity) {
// Exists; patch.
entity.patchState([step]);
}
}
get quadTree() {
return this._quadTree;
}
removeEntity(entity) {
const uuid = entity.instanceUuid;
delete this._entities[uuid];
const index = this._flatEntities.indexOf(entity);
this._flatEntities.splice(index, 1);
this.state = this.state.delete(uuid);
this.emit('entityRemoved', entity);
if (entity.is('listed')) {
entity.removeTrait('listed');
}
}
tick(elapsed) {
// Run after destruction tickers.
if (this._afterDestructionTickers.length > 0) {
this.tickAfterDestructionTickers(elapsed);
}
// Run normal tickers.
for (let i = 0; i < this._flatEntities.length; i++) {
const entity = this._flatEntities[i];
entity.tick(elapsed);
}
// Update state.
if (AVOCADO_SERVER) {
this.tickMutateState();
}
}
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);
}
}
tickMutateState(state) {
this.state = this.state.withMutations((state) => {
for (let i = 0; i < this._dirtyEntities.length; i++) {
const entity = this._dirtyEntities[i];
state.set(entity.$$avocado_property_instanceUuid, entity.state);
}
});
this._dirtyEntities = [];
}
visibleEntities(query) {
const entities = [];
const entitiesTrack = [];
const quadTree = this._quadTree;
const nodes = quadTree.search(query);
// First, uniqueify.
for (let i = 0; i < nodes.length; ++i) {
const node = nodes[i];
const entity = node.data[2];
if (-1 === entitiesTrack.indexOf(entity)) {
entitiesTrack.push(entity);
}
}
for (let i = 0; i < entitiesTrack.length; i++) {
const entity = entitiesTrack[i];
// Make sure they're actually in the query due to expanded AABB.
const visibleAabb = entity.visibleAabb;
if (!Rectangle.intersects(query, visibleAabb)) {
continue;
}
entities.push(entity);
}
// Hitting multiple points for each entity can return duplicates.
return entities;
}
}
export {EntityListView} from './view';