refactor: RIP Listed

This commit is contained in:
cha0s 2021-02-05 01:30:39 -06:00
parent f84a607023
commit 211670fd1c
8 changed files with 74 additions and 179 deletions

View File

@ -64,7 +64,7 @@ export default (latus) => class EntityList extends decorate(JsonResource) {
}
}
addEntity(entity) {
async addEntity(entity) {
const uuid = entity.instanceUuid;
// Already exists?
if (this.#entities[uuid]) {
@ -76,7 +76,9 @@ export default (latus) => class EntityList extends decorate(JsonResource) {
if ('client' !== process.env.SIDE) {
this.#informedEntities.set(entity, []);
}
entity.attachToList(this);
// eslint-disable-next-line no-param-reassign
entity.list = this;
entity.emit('addedToList');
entity.once('destroying', () => this.onEntityDestroying(entity));
this.emit('entityAdded', entity);
}
@ -179,7 +181,9 @@ export default (latus) => class EntityList extends decorate(JsonResource) {
if ('client' !== process.env.SIDE) {
this.#informedEntities.delete(entity);
}
entity.detachFromList();
// eslint-disable-next-line no-param-reassign
entity.list = null;
entity.emit('removedFromList', this);
delete this.#entities[uuid];
this.#flatEntities.splice(this.#flatEntities.indexOf(entity), 1);
this.#entityTickers.splice(this.#entityTickers.indexOf(entity.tick), 1);

View File

@ -1,123 +0,0 @@
import {Rectangle} from '@avocado/math';
import {Trait} from '@avocado/traits';
export default () => class Listed extends Trait {
#quadTreeAabb = [];
#quadTreeNodes = [];
addQuadTreeNodes() {
const {list} = this.entity;
if (!list) {
return;
}
const aabb = this.entity.visibleAabb;
if (Rectangle.isNull(aabb)) {
return;
}
// Expand the AABB so we don't have to update it every single tick.
const expandedAabb = Rectangle.expand(aabb, [32, 32]);
this.#quadTreeAabb = expandedAabb;
const points = Rectangle.toPoints(expandedAabb);
this.#quadTreeNodes = points.map((point) => [...point, this.entity, aabb]);
// Add points to quad tree.
const {quadTree} = list;
for (let i = 0; i < this.#quadTreeNodes.length; i++) {
quadTree.add(this.#quadTreeNodes[i]);
}
}
static behaviorTypes() {
return {
detachFromList: {
type: 'void',
label: 'Detach from list.',
},
attachToList: {
type: 'void',
label: 'Attach to $1.',
args: [
['list', {
type: 'entity-list',
}],
],
},
};
}
destroy() {
this.entity.detachFromList();
}
listeners() {
return {
visibleAabbChanged: () => {
this.resetQuadTreeNodes();
},
traitAdded: () => {
this.resetQuadTreeNodes();
},
};
}
async load(json) {
await super.load(json);
this.entity.list = null;
}
methods() {
return {
detachFromList: () => {
const {list} = this.entity;
if (!list) {
return;
}
this.removeQuadTreeNodes();
this.entity.list = null;
this.entity.emit('removedFromList', list);
},
attachToList: (list) => {
this.entity.list = list;
this.addQuadTreeNodes();
this.entity.emit('addedToList');
},
};
}
removeQuadTreeNodes() {
const {list} = this.entity;
if (!list) {
return;
}
if (this.#quadTreeNodes.length > 0) {
const {quadTree} = list;
for (let i = 0; i < this.#quadTreeNodes.length; i++) {
quadTree.remove(this.#quadTreeNodes[i]);
}
this.#quadTreeAabb = [];
this.#quadTreeNodes = [];
}
}
resetQuadTreeNodes() {
if ('client' !== process.env.SIDE) {
const aabb = this.entity.visibleAabb;
if (
this.#quadTreeAabb.length > 0
&& Rectangle.isInside(this.#quadTreeAabb, aabb)
) {
return;
}
this.removeQuadTreeNodes();
this.addQuadTreeNodes();
}
}
};

View File

@ -1,43 +0,0 @@
import {Latus} from '@latus/core';
import {expect} from 'chai';
let latus;
let Entity;
let EntityList;
beforeEach(async () => {
latus = Latus.mock({
'@avocado/entity': require('../src'),
'@avocado/resource': require('@avocado/resource'),
'@avocado/traits': require('@avocado/traits'),
});
await Promise.all(latus.invokeFlat('@latus/core/starting'));
({Entity, EntityList} = latus.get('%resources'));
});
describe('Listed', () => {
let entity;
beforeEach(async () => {
entity = await Entity.load({
traits: {
Listed: {},
},
});
});
it('exists', async () => {
expect(entity.is('Listed')).to.be.true;
});
it('can be added to list quadtree', async () => {
entity.isVisible = true;
entity.visibleAabb = [0, 0, 16, 16];
const list = new EntityList();
entity.attachToList(list);
expect(list.visibleEntities([-16, -16, 32, 32])).to.have.lengthOf(1);
});
it('will be removed from quadtree on destroy', async () => {
entity.isVisible = true;
entity.visibleAabb = [0, 0, 16, 16];
const list = new EntityList();
entity.attachToList(list);
entity.emit('destroyed');
expect(list.visibleEntities([-16, -16, 32, 32])).to.have.lengthOf(0);
});
});

View File

@ -20,14 +20,12 @@ describe('Spawner', () => {
beforeEach(async () => {
entity = await Entity.load({
traits: {
Listed: {},
Spawner: {
params: {
spawns: {
testy: {
traits: {
Alive: {},
Listed: {},
Positioned: {},
},
},
@ -61,7 +59,6 @@ describe('Spawner', () => {
it('can spawn from arbitrary JSON', async () => {
const spawned = await entity.spawnRaw({
traits: {
Listed: {},
Mobile: {},
},
});
@ -69,7 +66,6 @@ describe('Spawner', () => {
const spawned2 = await entity.spawnRawAt(
{
traits: {
Listed: {},
Mobile: {},
},
},
@ -82,7 +78,6 @@ describe('Spawner', () => {
for (let i = 0; i < COUNT; ++i) {
await entity.spawnRaw({
traits: {
Listed: {},
Mobile: {},
},
});

View File

@ -29,6 +29,10 @@ export default () => class Visible extends decorate(Trait) {
#container;
#quadTreeAabb = [];
#quadTreeNodes = [];
#rawVisibleAabb = [0, 0, 0, 0];
#scheduledBoundingBoxUpdate = true;
@ -55,6 +59,27 @@ export default () => class Visible extends decorate(Trait) {
}
}
addQuadTreeNodes() {
const {list} = this.entity;
if (!list) {
return;
}
const aabb = this.entity.visibleAabb;
if (Rectangle.isNull(aabb)) {
return;
}
// Expand the AABB so we don't have to update it every single tick.
const expandedAabb = Rectangle.expand(aabb, [32, 32]);
this.#quadTreeAabb = expandedAabb;
const points = Rectangle.toPoints(expandedAabb);
this.#quadTreeNodes = points.map((point) => [...point, this.entity, aabb]);
// Add points to quad tree.
const {quadTree} = list;
for (let i = 0; i < this.#quadTreeNodes.length; i++) {
quadTree.add(this.#quadTreeNodes[i]);
}
}
static behaviorTypes() {
return {
updateVisibleBoundingBox: {
@ -125,6 +150,7 @@ export default () => class Visible extends decorate(Trait) {
if (this.#container) {
this.#container.destroy();
}
this.removeQuadTreeNodes(this.entity.list);
}
get container() {
@ -155,6 +181,10 @@ export default () => class Visible extends decorate(Trait) {
listeners() {
return {
addedToList: () => {
this.addQuadTreeNodes();
},
isVisibleChanged: () => {
if (!this.#container) {
return;
@ -173,6 +203,10 @@ export default () => class Visible extends decorate(Trait) {
this.translateVisibleAabb();
},
removedFromList: (list) => {
this.removeQuadTreeNodes(list);
},
rotationChanged: () => {
if (!this.#container) {
return;
@ -181,6 +215,7 @@ export default () => class Visible extends decorate(Trait) {
},
traitAdded: () => {
this.resetQuadTreeNodes();
this.synchronizePosition();
},
@ -188,6 +223,10 @@ export default () => class Visible extends decorate(Trait) {
this.#scheduledBoundingBoxUpdate = true;
},
visibleAabbChanged: () => {
this.resetQuadTreeNodes();
},
visibleScaleChanged: () => {
this.#visibleScale = Vector.copy(this.entity.visibleScale);
if (this.#container) {
@ -258,10 +297,38 @@ export default () => class Visible extends decorate(Trait) {
this.entity.visibleScale = scale;
}
removeQuadTreeNodes(list) {
if (!list) {
return;
}
if (this.#quadTreeNodes.length > 0) {
const {quadTree} = list;
for (let i = 0; i < this.#quadTreeNodes.length; i++) {
quadTree.remove(this.#quadTreeNodes[i]);
}
this.#quadTreeAabb = [];
this.#quadTreeNodes = [];
}
}
renderTick() {
this.synchronizePosition();
}
resetQuadTreeNodes() {
if ('client' !== process.env.SIDE) {
const aabb = this.entity.visibleAabb;
if (
this.#quadTreeAabb.length > 0
&& Rectangle.isInside(this.#quadTreeAabb, aabb)
) {
return;
}
this.removeQuadTreeNodes(this.entity.list);
this.addQuadTreeNodes();
}
}
synchronizePosition() {
if (!this.#container) {
return;

View File

@ -33,7 +33,6 @@
"deepmerge": "^4.2.2",
"immutable": "^4.0.0-rc.12",
"kefir": "^3.8.8",
"lodash.get": "^4.4.2",
"matter-js": "0.14.2",
"poly-decomp": "0.3.0",
"proton-engine": "^4.2.1"

View File

@ -3,7 +3,6 @@ import {Ticker} from '@avocado/timing';
import {Trait} from '@avocado/traits';
import {compose} from '@latus/core';
import merge from 'deepmerge';
import get from 'lodash.get';
import K from 'kefir';
import Proton from '../proton';
@ -88,7 +87,6 @@ export default (latus) => class Emitter extends decorate(Trait) {
async emitParticleJson(json) {
const {Entity} = latus.get('%resources');
const isListed = get(json, 'traits.Emitted.params.listed', true);
return this.entity.emitParticleEntity(
await Entity.load(
merge(
@ -96,7 +94,6 @@ export default (latus) => class Emitter extends decorate(Trait) {
traits: {
Positioned: {},
Visible: {},
...(isListed ? {Listed: {}} : {}),
},
},
json,

View File

@ -26,7 +26,6 @@ describe('Layer', () => {
entity = await Entity.load({
traits: {
Layered: {},
Listed: {},
},
});
const entityList = new EntityList();