ECSpresso tracks component changes using a per-system monotonic sequence. Each markChanged call increments a global counter and stamps the component with a unique sequence number. Each system tracks the highest sequence it has seen; on its next execution, it only processes marks with a sequence greater than its last-seen value. This means each mark is processed exactly once per system, and marks expire after a single update cycle.
Components are automatically marked as changed when added via spawn(), addComponent(), or addComponents(). For in-place mutations, call markChanged explicitly:
const position = world.entityManager.getComponent(entity.id, 'position');
if (position) {
position.x += 10;
world.markChanged(entity.id, 'position');
}
Add changed to a query definition to filter entities to only those whose specified components changed since the system last ran:
world.addSystem('render-sync')
.addQuery('moved', {
with: ['position', 'sprite'],
changed: ['position'], // Only entities whose position changed this tick
})
.setProcess(({ queries }) => {
for (const entity of queries.moved) {
syncSpritePosition(entity);
}
});
When multiple components are listed in changed, entities matching any of them are included (OR semantics).
For manual change detection outside of system queries:
const em = ecs.entityManager;
if (em.getChangeSeq(entity.id, 'localTransform') > ecs.changeThreshold) {
// Component changed since last system execution (or since last update if between updates)
}
Deferred marking: ecs.commands.markChanged(entity.id, 'position') queues a mark for command buffer playback.
Built-in plugin usage: Movement marks localTransform (fixedUpdate) → Transform propagation reads localTransform changed, writes+marks worldTransform (postUpdate) → Renderer reads worldTransform changed (render).