On this page
Query callbacks
onEntitiesAdded and onEntitiesRemoved react to changes in a query's match set. Each callback receives one contiguous row range:
local physicsBodies <const> = world:newQuery({
include = {tecs.Transform2D, tecs.physics.RigidBody},
onEntitiesAdded = function(
archetype: tecs.ecs.Archetype,
firstRow: integer,
lastRow: integer,
count: integer
)
local transforms <const> = archetype:get(tecs.Transform2D)
local bodies <const> = archetype:get(tecs.physics.RigidBody)
reservePhysicsBodies(count)
for row = firstRow, lastRow do
attachBody(bodies[row], transforms[row])
end
end,
onEntitiesRemoved = function(
archetype: tecs.ecs.Archetype,
firstRow: integer,
lastRow: integer,
_count: integer
)
local bodies <const> = archetype:get(tecs.physics.RigidBody)
for row = firstRow, lastRow do
detachBody(bodies[row])
end
end,
})The inclusive bounds use one-based rows. count equals the range length, so a batch path can reserve external storage once.
A temporary query cannot define either callback.
Entities entering the query
onEntitiesAdded runs when entities first match the descriptor:
- A spawn places matching components.
- A component addition satisfies
include. - A component removal satisfies
exclude.
Moving between two archetypes that both match does not run the callback. The entity never left the query.
Use requires when one component always implies another. Use a callback when matching should trigger work in an external system or allocate a resource.
Entities leaving the query
onEntitiesRemoved runs before rows leave their archetype, so the callback can still read every included column. These changes remove a match:
- Despawn.
- Removal of an included component.
- Addition of an excluded component.
A one-component query reacts to that component alone. A wider descriptor can express a lifecycle boundary such as {Enemy, Stunned} or {Transform2D, RigidBody} without coordinating separate hooks.
Commit drain
Query callbacks run inside the commit drain. A drain applies changes in waves: despawns first, then spawns, then archetype moves. It applies batch mutations and sparse relationship writes between waves.
A callback may stage more work. The next wave applies that work:
- Structural calls through
set,remove,spawn,despawn, and thebatch*APIs do not change committed structure immediately. get,has, and queries see committed structure. They do not see staged additions, removals, spawns, or despawns.- A value-only
seton a committed entity writes immediately when no staged structural change blocks it, and it marks the component dirty. - The callback may read the supplied archetype and row range until it returns. Read needed values before staging changes to those entities.
A finite callback cascade settles through later waves. Tecs stops an unbounded cascade with an error after 64 waves.
The mutation model defines the complete ordering and visibility contract.
Archetype-local observers
archetype:addEntityObserver attaches lower-level hooks to one archetype, including activation, deactivation, moves, and destruction. It does not find other archetypes with the same signature. Query callbacks follow the query as new archetypes begin to match, so game and subsystem code should normally use them.