This (absolutely massive) PR introduces a bunch of improvements to the core ECS including:
1) Simplified query interface
2) Improved iteration performance (#148)
3) Not filters (#125)
4) Topic registration (#138)
Simplified query interface
Filters (attached, detached and changed, custom filters) have been removed. Change detection should be event oriented – checking each component to determine if it is attached or detached is just too slow. The old strategy also required that we defer operations an additional tick so that entities flagged for attach/detach had a chance to get picked up by a filter.
This means queries will always look like this:
query(A, B, C)
instead of this:
query(A, attached(B), C)
Now, when a component is attached/detached, or an entity is spawned/destroyed, the corresponding operation happens immediately at the beginning of the next tick. World and Storage expose events (signals) to detect these events, and a couple of new built-in effects were introduced that expose a simple API for iterating modified entities, which are described below.
Triggers
The simplest way to detect a change is with a trigger. A trigger is an effect that yields entity-component pairs where the entity was modified in some way during the previous tick.
onAttach(Velocity).forEach((entity, v) => {
// `v` was attached to `entity` last tick
});
onDetach(Freeze).forEach((entity, v) => {
// `v` was detached from `entity` last tick
});
Monitors
Monitors are slightly more complex. They monitor a query and yield entities that become eligible (or ineligible) for iteration by the query.
onInsert(queries.spaceships).forEach(entity => {
// entity now matches the spaceship query
});
onRemove(queries.lightSources).forEach(entity => {
// entity no longer matches the lightSources query
});
I haven't done much performance testing on triggers/monitors (or effects for that matter), but I plan on doing that soon. I assume they are okay right now but could still use some work.
Improved iteration performance
Iteration speed was improved around 100% with the removal of component filters. Even more exciting perf gains were made with the introduction of forEach and a new, "lower level" way of iterating queries manually.
Queries can be manually iterated for maximum performance (read in the Crysis voice) using a for..of loop:
for (const [entities, [p, v]] of queries.moving) {
for (let i = 0; i < entities.length; i++) {
p[i].x += v[i].x
}
}
Using these new iteration methods, Javelin is able to compete with some of the faster JS ECS libraries while (hopefully) remaining fairly easy to understand and use.
Not filters
A query can now exclude entities with certain component(s) using query.not().
const root = query(Node).not(Parent)
Topic registration
Topics can now be registered with a world. Registered topics are auto-flushed at the beginning of each tick.
This (absolutely massive) PR introduces a bunch of improvements to the core ECS including: 1) Simplified query interface 2) Improved iteration performance (#148) 3) Not filters (#125) 4) Topic registration (#138)
Simplified query interface
Filters (
attached
,detached
andchanged
, custom filters) have been removed. Change detection should be event oriented – checking each component to determine if it is attached or detached is just too slow. The old strategy also required that we defer operations an additional tick so that entities flagged for attach/detach had a chance to get picked up by a filter.This means queries will always look like this:
instead of this:
Now, when a component is attached/detached, or an entity is spawned/destroyed, the corresponding operation happens immediately at the beginning of the next tick.
World
andStorage
expose events (signals) to detect these events, and a couple of new built-in effects were introduced that expose a simple API for iterating modified entities, which are described below.Triggers
The simplest way to detect a change is with a trigger. A trigger is an effect that yields entity-component pairs where the entity was modified in some way during the previous tick.
Monitors
Monitors are slightly more complex. They monitor a query and yield entities that become eligible (or ineligible) for iteration by the query.
You can see some examples of Triggers/Monitors (and other effects) in the updated "space junk" example here: https://github.com/3mcd/javelin/blob/ab3dea578ad49f44f89566e5e42ff663a2bb0d99/docs-src/static/examples/space-junk.js.
I haven't done much performance testing on triggers/monitors (or effects for that matter), but I plan on doing that soon. I assume they are okay right now but could still use some work.
Improved iteration performance
Iteration speed was improved around 100% with the removal of component filters. Even more exciting perf gains were made with the introduction of
forEach
and a new, "lower level" way of iterating queries manually.forEach
Manually iterate a query
Queries can be manually iterated for maximum performance (read in the Crysis voice) using a for..of loop:
Using these new iteration methods, Javelin is able to compete with some of the faster JS ECS libraries while (hopefully) remaining fairly easy to understand and use.
Not filters
A query can now exclude entities with certain component(s) using
query.not()
.Topic registration
Topics can now be registered with a world. Registered topics are auto-flushed at the beginning of each tick.
To-do