flow+perf: indexing, ticking, flat components, etc.
This commit is contained in:
parent
b5698cd392
commit
51bdda5eb9
|
@ -142,6 +142,7 @@ export default class Component {
|
|||
for (const key in defaults) {
|
||||
this[`$$${key}`] = defaults[key];
|
||||
}
|
||||
Component.ecs.markChange(this.entity, {[Component.constructor.componentName]: values})
|
||||
}
|
||||
toNet(recipient, data) {
|
||||
return data || Component.constructor.filterDefaults(this);
|
||||
|
|
|
@ -19,6 +19,7 @@ export default class Alive extends Component {
|
|||
this.$$dead = true;
|
||||
const {Ticking} = ecs.get(this.entity);
|
||||
if (Ticking) {
|
||||
this.$$death.context.entity = ecs.get(this.entity);
|
||||
const ticker = this.$$death.ticker();
|
||||
ecs.addDestructionDependency(this.entity.id, ticker);
|
||||
Ticking.add(ticker);
|
||||
|
@ -35,7 +36,6 @@ export default class Alive extends Component {
|
|||
instance.deathScript,
|
||||
{
|
||||
ecs: this.ecs,
|
||||
entity: this.ecs.get(instance.entity),
|
||||
},
|
||||
);
|
||||
if (0 === instance.maxHealth) {
|
||||
|
|
|
@ -2,11 +2,13 @@ import Component from '@/ecs/component.js';
|
|||
|
||||
export default class Behaving extends Component {
|
||||
instanceFromSchema() {
|
||||
const {ecs} = this;
|
||||
return class BehavingInstance extends super.instanceFromSchema() {
|
||||
$$routineInstances = {};
|
||||
tick(elapsed) {
|
||||
const routine = this.$$routineInstances[this.currentRoutine];
|
||||
if (routine) {
|
||||
routine.context.entity = ecs.get(this.entity);
|
||||
routine.tick(elapsed);
|
||||
}
|
||||
}
|
||||
|
@ -20,12 +22,7 @@ export default class Behaving extends Component {
|
|||
const promises = [];
|
||||
for (const key in instance.routines) {
|
||||
promises.push(
|
||||
this.ecs.readScript(
|
||||
instance.routines[key],
|
||||
{
|
||||
entity: this.ecs.get(instance.entity),
|
||||
},
|
||||
)
|
||||
this.ecs.readScript(instance.routines[key])
|
||||
.then((script) => {
|
||||
instance.$$routineInstances[key] = script;
|
||||
}),
|
||||
|
|
|
@ -229,7 +229,7 @@ export default class Collider extends Component {
|
|||
this.$$aabb = {x0: Infinity, x1: -Infinity, y0: Infinity, y1: -Infinity};
|
||||
this.$$aabbs = [];
|
||||
const {bodies} = this;
|
||||
const {Direction: {direction = 0} = {}} = ecs.get(this.entity);
|
||||
const {Direction: {direction = 0} = {}} = ecs.get(this.entity) || {};
|
||||
for (const body of bodies) {
|
||||
let x0 = Infinity, x1 = -Infinity, y0 = Infinity, y1 = -Infinity;
|
||||
for (const point of transform(body.points, {rotation: direction})) {
|
||||
|
|
|
@ -8,7 +8,8 @@ export default class Interactive extends Component {
|
|||
interact(initiator) {
|
||||
const script = this.$$interact.clone();
|
||||
script.context.initiator = initiator;
|
||||
const {Ticking} = ecs.get(this.entity);
|
||||
script.context.subject = ecs.get(this.entity);
|
||||
const {Ticking} = script.context.subject;
|
||||
Ticking.add(script.ticker());
|
||||
}
|
||||
get interacting() {
|
||||
|
@ -28,7 +29,6 @@ export default class Interactive extends Component {
|
|||
instance.interactScript,
|
||||
{
|
||||
ecs: this.ecs,
|
||||
subject: this.ecs.get(instance.entity),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
|
@ -130,10 +130,19 @@ export default class Ecs {
|
|||
}
|
||||
}
|
||||
|
||||
applyDeferredChanges(entityId) {
|
||||
const changes = this.deferredChanges[entityId];
|
||||
delete this.deferredChanges[entityId];
|
||||
for (const components of changes) {
|
||||
this.markChange(entityId, components);
|
||||
}
|
||||
}
|
||||
|
||||
attach(entityIds) {
|
||||
for (const entityId of entityIds) {
|
||||
this.$$detached.delete(entityId);
|
||||
this.$$reindexing.add(entityId);
|
||||
this.applyDeferredChanges(entityId);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -190,6 +199,7 @@ export default class Ecs {
|
|||
for (const components of componentsList) {
|
||||
specificsList.push([this.$$caret, components]);
|
||||
this.$$detached.add(this.$$caret);
|
||||
this.deferredChanges[this.$$caret] = [];
|
||||
this.$$caret += 1;
|
||||
}
|
||||
return this.createManySpecific(specificsList);
|
||||
|
@ -213,8 +223,6 @@ export default class Ecs {
|
|||
}
|
||||
}
|
||||
entityIds.add(entityId);
|
||||
this.$$reindexing.add(entityId);
|
||||
this.rebuild(entityId, () => componentNames);
|
||||
for (const componentName of componentNames) {
|
||||
if (!creating[componentName]) {
|
||||
creating[componentName] = [];
|
||||
|
@ -229,15 +237,13 @@ export default class Ecs {
|
|||
}
|
||||
await Promise.all(promises);
|
||||
for (let i = 0; i < specificsList.length; i++) {
|
||||
const [entityId] = specificsList[i];
|
||||
const [entityId, components] = specificsList[i];
|
||||
this.$$reindexing.add(entityId);
|
||||
this.rebuild(entityId, () => Object.keys(components));
|
||||
if (this.$$detached.has(entityId)) {
|
||||
continue;
|
||||
}
|
||||
const changes = this.deferredChanges[entityId];
|
||||
delete this.deferredChanges[entityId];
|
||||
for (const components of changes) {
|
||||
this.markChange(entityId, components);
|
||||
}
|
||||
this.applyDeferredChanges(entityId);
|
||||
}
|
||||
return entityIds;
|
||||
}
|
||||
|
@ -354,7 +360,6 @@ export default class Ecs {
|
|||
const inserting = {};
|
||||
const unique = new Set();
|
||||
for (const [entityId, components] of entities) {
|
||||
this.rebuild(entityId, (componentNames) => [...new Set(componentNames.concat(Object.keys(components)))]);
|
||||
const diff = {};
|
||||
for (const componentName in components) {
|
||||
if (!inserting[componentName]) {
|
||||
|
@ -363,7 +368,6 @@ export default class Ecs {
|
|||
diff[componentName] = {};
|
||||
inserting[componentName].push([entityId, components[componentName]]);
|
||||
}
|
||||
this.$$reindexing.add(entityId);
|
||||
unique.add(entityId);
|
||||
this.markChange(entityId, diff);
|
||||
}
|
||||
|
@ -372,12 +376,13 @@ export default class Ecs {
|
|||
promises.push(this.Components[componentName].insertMany(inserting[componentName]));
|
||||
}
|
||||
await Promise.all(promises);
|
||||
for (const [entityId, components] of entities) {
|
||||
this.$$reindexing.add(entityId);
|
||||
this.rebuild(entityId, (componentNames) => [...new Set(componentNames.concat(Object.keys(components)))]);
|
||||
}
|
||||
}
|
||||
|
||||
markChange(entityId, components) {
|
||||
if (this.$$detached.has(entityId)) {
|
||||
return;
|
||||
}
|
||||
if (this.deferredChanges[entityId]) {
|
||||
this.deferredChanges[entityId].push(components);
|
||||
return;
|
||||
|
@ -486,7 +491,6 @@ export default class Ecs {
|
|||
const removing = {};
|
||||
const unique = new Set();
|
||||
for (const [entityId, components] of entities) {
|
||||
this.$$reindexing.add(entityId);
|
||||
unique.add(entityId);
|
||||
const diff = {};
|
||||
for (const componentName of components) {
|
||||
|
@ -497,11 +501,14 @@ export default class Ecs {
|
|||
removing[componentName].push(entityId);
|
||||
}
|
||||
this.markChange(entityId, diff);
|
||||
this.rebuild(entityId, (componentNames) => componentNames.filter((type) => !components.includes(type)));
|
||||
}
|
||||
for (const componentName in removing) {
|
||||
this.Components[componentName].destroyMany(removing[componentName]);
|
||||
}
|
||||
for (const [entityId, components] of entities) {
|
||||
this.$$reindexing.add(entityId);
|
||||
this.rebuild(entityId, (componentNames) => componentNames.filter((type) => !components.includes(type)));
|
||||
}
|
||||
}
|
||||
|
||||
static serialize(ecs, view) {
|
||||
|
@ -522,6 +529,22 @@ export default class Ecs {
|
|||
}
|
||||
|
||||
tick(elapsed) {
|
||||
// tick systems
|
||||
for (const systemName in this.Systems) {
|
||||
const System = this.Systems[systemName];
|
||||
if (!System.active) {
|
||||
continue;
|
||||
}
|
||||
if (!System.frequency) {
|
||||
System.tick(elapsed);
|
||||
continue;
|
||||
}
|
||||
System.elapsed += elapsed;
|
||||
while (System.elapsed >= System.frequency) {
|
||||
System.tick(System.frequency);
|
||||
System.elapsed -= System.frequency;
|
||||
}
|
||||
}
|
||||
// destroy entities
|
||||
const destroying = new Set();
|
||||
for (const [entityId, {promises}] of this.$$destructionDependencies) {
|
||||
|
@ -541,22 +564,6 @@ export default class Ecs {
|
|||
this.$$deindexing.clear();
|
||||
this.reindex(this.$$reindexing);
|
||||
this.$$reindexing.clear();
|
||||
// tick systems
|
||||
for (const systemName in this.Systems) {
|
||||
const System = this.Systems[systemName];
|
||||
if (!System.active) {
|
||||
continue;
|
||||
}
|
||||
if (!System.frequency) {
|
||||
System.tick(elapsed);
|
||||
continue;
|
||||
}
|
||||
System.elapsed += elapsed;
|
||||
while (System.elapsed >= System.frequency) {
|
||||
System.tick(System.frequency);
|
||||
System.elapsed -= System.frequency;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
|
|
|
@ -210,19 +210,23 @@ test('skips indexing detached entities', async () => {
|
|||
},
|
||||
},
|
||||
});
|
||||
const {$$index: index} = ecs.system('Indexer').queries.default;
|
||||
const {$$map: map} = ecs.system('Indexer').queries.default;
|
||||
ecs.system('Indexer').active = true;
|
||||
const attached = await ecs.create({Empty: {}});
|
||||
expect(Array.from(index))
|
||||
ecs.tick(0);
|
||||
expect(Array.from(map.keys()))
|
||||
.to.deep.equal([attached]);
|
||||
ecs.destroyMany(new Set([attached]));
|
||||
expect(Array.from(index))
|
||||
ecs.tick(0);
|
||||
expect(Array.from(map.keys()))
|
||||
.to.deep.equal([]);
|
||||
const detached = await ecs.createDetached({Empty: {}});
|
||||
expect(Array.from(index))
|
||||
ecs.tick(0);
|
||||
expect(Array.from(map.keys()))
|
||||
.to.deep.equal([]);
|
||||
ecs.destroyMany(new Set([detached]));
|
||||
expect(Array.from(index))
|
||||
ecs.tick(0);
|
||||
expect(Array.from(map.keys()))
|
||||
.to.deep.equal([]);
|
||||
});
|
||||
|
||||
|
@ -336,11 +340,11 @@ test('applies creation patches', async () => {
|
|||
.to.equal(64);
|
||||
});
|
||||
|
||||
test('applies update patches', () => {
|
||||
test('applies update patches', async () => {
|
||||
const ecs = new Ecs({Components: {Position}});
|
||||
ecs.createSpecific(16, {Position: {x: 64}});
|
||||
ecs.apply({16: {Position: {x: 128}}});
|
||||
expect(Array.from(ecs.entities).length)
|
||||
await ecs.createSpecific(16, {Position: {x: 64}});
|
||||
await ecs.apply({16: {Position: {x: 128}}});
|
||||
expect(Object.keys(ecs.$$entities).length)
|
||||
.to.equal(1);
|
||||
expect(ecs.get(16).Position.x)
|
||||
.to.equal(128);
|
||||
|
@ -364,9 +368,9 @@ test('applies component deletion patches', async () => {
|
|||
.to.deep.equal(['Position']);
|
||||
});
|
||||
|
||||
test('calculates entity size', () => {
|
||||
test('calculates entity size', async () => {
|
||||
const ecs = new Ecs({Components: {Empty, Position}});
|
||||
ecs.createSpecific(1, {Empty: {}, Position: {}});
|
||||
await ecs.createSpecific(1, {Empty: {}, Position: {}});
|
||||
// ID + # of components + Empty + Position + x + y + z
|
||||
// 4 + 2 + 2 + 4 + 2 + 4 + 4 + 4 + 4 = 30
|
||||
expect(ecs.get(1).size())
|
||||
|
@ -375,8 +379,8 @@ test('calculates entity size', () => {
|
|||
|
||||
test('serializes and deserializes', async () => {
|
||||
const ecs = new Ecs({Components: {Empty, Name, Position}});
|
||||
ecs.createSpecific(1, {Empty: {}, Position: {x: 64}});
|
||||
ecs.createSpecific(16, {Name: {name: 'foobar'}, Position: {x: 128}});
|
||||
await ecs.createSpecific(1, {Empty: {}, Position: {x: 64}});
|
||||
await ecs.createSpecific(16, {Name: {name: 'foobar'}, Position: {x: 128}});
|
||||
expect(ecs.toJSON())
|
||||
.to.deep.equal({
|
||||
entities: {
|
||||
|
@ -408,8 +412,8 @@ test('serializes and deserializes', async () => {
|
|||
|
||||
test('deserializes from compatible ECS', async () => {
|
||||
const ecs = new Ecs({Components: {Empty, Name, Position}});
|
||||
ecs.createSpecific(1, {Empty: {}, Position: {x: 64}});
|
||||
ecs.createSpecific(16, {Name: {name: 'foobar'}, Position: {x: 128}});
|
||||
await ecs.createSpecific(1, {Empty: {}, Position: {x: 64}});
|
||||
await ecs.createSpecific(16, {Name: {name: 'foobar'}, Position: {x: 128}});
|
||||
const view = Ecs.serialize(ecs);
|
||||
const deserialized = await Ecs.deserialize(
|
||||
new Ecs({Components: {Empty, Name}}),
|
||||
|
|
|
@ -23,6 +23,9 @@ export default class EntityFactory {
|
|||
static componentNames = sorted;
|
||||
constructor(id) {
|
||||
this.id = id;
|
||||
for (const type of sorted) {
|
||||
this[type] = Components[type].get(id);
|
||||
}
|
||||
}
|
||||
size() {
|
||||
let size = 0;
|
||||
|
@ -34,15 +37,6 @@ export default class EntityFactory {
|
|||
return size + 4 + 2;
|
||||
}
|
||||
}
|
||||
const properties = {};
|
||||
for (const type of sorted) {
|
||||
properties[type] = {};
|
||||
const get = Components[type].get.bind(Components[type]);
|
||||
properties[type].get = function() {
|
||||
return get(this.id);
|
||||
};
|
||||
}
|
||||
Object.defineProperties(Entity.prototype, properties);
|
||||
Entity.prototype.updateAttachments = new Function('update', `
|
||||
${
|
||||
sorted
|
||||
|
|
|
@ -54,9 +54,11 @@ export default class MaintainColliderHash extends System {
|
|||
|
||||
within(query) {
|
||||
const within = new Set();
|
||||
if (this.hash) {
|
||||
for (const id of this.hash.within(query)) {
|
||||
within.add(this.ecs.get(id));
|
||||
}
|
||||
}
|
||||
return within;
|
||||
}
|
||||
|
||||
|
|
|
@ -32,10 +32,8 @@ test('emits particles over time', async () => {
|
|||
current.onValue(resolve);
|
||||
}))
|
||||
.to.deep.include({id: 1});
|
||||
expect(ecs.get(1))
|
||||
.to.not.be.undefined;
|
||||
expect(ecs.get(2))
|
||||
.to.be.undefined;
|
||||
expect(Array.from(ecs.$$detached))
|
||||
.to.deep.equal([2]);
|
||||
emitter.tick(0.06);
|
||||
expect(await new Promise((resolve) => {
|
||||
current.onValue(resolve);
|
||||
|
@ -47,6 +45,6 @@ test('emits particles over time', async () => {
|
|||
current.onValue(resolve);
|
||||
}))
|
||||
.to.deep.include({id: 2});
|
||||
expect(ecs.get(2))
|
||||
.to.not.be.undefined;
|
||||
expect(Array.from(ecs.$$detached))
|
||||
.to.deep.equal([]);
|
||||
});
|
||||
|
|
|
@ -28,8 +28,6 @@ addEventListener('message', (particle) => {
|
|||
.onEnd(() => {});
|
||||
});
|
||||
|
||||
const memory = new Set();
|
||||
|
||||
let last = performance.now();
|
||||
function tick(now) {
|
||||
const elapsed = (now - last) / 1000;
|
||||
|
@ -40,25 +38,11 @@ function tick(now) {
|
|||
}
|
||||
ecs.tick(elapsed);
|
||||
emitter.tick(elapsed);
|
||||
const update = {};
|
||||
for (const id in ecs.diff) {
|
||||
if (false === ecs.diff[id]) {
|
||||
memory.delete(id);
|
||||
update[id] = false;
|
||||
if ('1' in ecs.diff) {
|
||||
delete ecs.diff['1'];
|
||||
}
|
||||
else if (!memory.has(id)) {
|
||||
update[id] = ecs.$$entities[id].toJSON();
|
||||
}
|
||||
else if (ecs.diff[id]) {
|
||||
update[id] = ecs.diff[id];
|
||||
}
|
||||
memory.add(id);
|
||||
}
|
||||
if ('1' in update) {
|
||||
delete update['1'];
|
||||
}
|
||||
if (Object.keys(update).length > 0) {
|
||||
postMessage(update);
|
||||
if (Object.keys(ecs.diff).length > 0) {
|
||||
postMessage(ecs.diff);
|
||||
}
|
||||
ecs.setClean();
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user