On this page
  1. Query grouping
  2. Group traversal

Query grouping

Grouping keeps matching archetypes with the same integer key together. Use it when each group needs expensive setup that should not run for every entity.

local Kind <const> = {
    Textured = 1,
    Shaded = 2,
    Flat = 3,
}

local renderables <const> = world:newQuery({
    include = {tecs.Transform2D, tecs.gfx.Renderable2D},
    groupBy = function(archetype: tecs.ecs.Archetype): integer
        if archetype:get(tecs.gfx.Sprite) then
            return Kind.Textured
        end
        if archetype:get(tecs.gfx.Material) then
            return Kind.Shaded
        end
        return Kind.Flat
    end,
})

Tecs computes the key when an archetype begins matching and caches it. groupBy must therefore depend only on the component signature, never on row values or mutable external state.

Group traversal

groups() yields active keys in sorted order. group(key) yields the nonempty archetypes for one key:

for kind in renderables:groups() do
    beginBatch(kind)

    for archetype, length in renderables:group(kind) do
        local transforms <const> = archetype:get(tecs.Transform2D)

        for row = 1, length do
            drawRow(transforms[row])
        end
    end

    endBatch()
end

An empty group disappears from groups() until one of its archetypes fills again.

getGroup(archetype) returns the cached key. getGroupCount(key) sums the entities in that group without visiting rows, which supports two-pass buffer layout:

local offset = 0
local offsets: {integer: integer} = {}

for key in renderables:groups() do
    offsets[key] = offset
    offset = offset + renderables:getGroupCount(key)
end
Grouped iteration opens the same deferred scope as query:iter(). Use a separate cursor for any traversal that may stop early, and close it after the loop. Nested grouped traversals need one cursor each.