Skip to main content

Change detection

The world counts frames in ticks. world.tick() ends one tick and starts the next. Three query filters look at what happened during the current tick:

FilterMatches entities that, this tick...
Added(T)were given a T they didn't have before
Changed(T)had their T replaced with addComponent, or marked with markChanged
Removed(T)lost their T and are still alive
try world.addComponent(player, Health{ .hp = 100 });

var fresh = world.query(.{clutch.Added(Health)});
while (fresh.next()) |view| { ... } // visits `player`

world.tick(); // next tick: `player` no longer matches Added(Health)

The single-entity versions are wasAdded, wasChanged and wasRemoved.

Marking changes​

Clutch doesn't notice writes made through pointers. After changing a component through get or view.get, call markChanged if anything filters on Changed:

world.get(player, Health).?.hp -= 10;
world.markChanged(player, Health);

Despawned entities never match Removed. Neither does a new entity that reuses a despawned entity's id.

Opting out​

Tracking costs a little memory and time on every add and remove. Components that nothing filters on can opt out:

const Particle = struct {
pub const track_changes = false;
life: f32,
};

Adding and removing such a component is about 20% faster. Using Added, Changed, Removed or markChanged with it becomes a compile error that names the reason.