Skip to content

Component Bundles

Bundles are reusable templates for spawning entities with a predefined set of components. They provide:

  • Consistent entity creation: define once, spawn anywhere
  • Default values: optional components can have default factories
  • Required components: mark components that must be supplied at spawn time
  • Cached spawn path: each bundle compiles a spawn routine for its target component set, so repeated spawns avoid rebuilding the same component list

Creating a Bundle

Create bundles through the world with a declarative definition:

teal
local playerBundle: tecs.Bundle = world:newBundle("Player", {
    required = { Transform, Health },
    with = {
        [Velocity] = function(): Velocity return Velocity(0, 0) end,
        [PlayerTag] = true,
    },
})

required is an ordered array of component types. with is a map keyed by component type, where the value is either a factory function or true.

Each component type can only appear once in a bundle (across both required and with). Passing components that aren't part of the bundle at spawn time is not supported.

required

Components listed in required must be supplied by the caller at spawn time. They are strictly positional: the order in the required array determines the argument order at bundle:spawn(...).

teal
local enemyBundle: tecs.Bundle = world:newBundle("Enemy", {
    required = { Transform, Health, Damage },
})

-- Spawn: arguments match declaration order
local id: integer = enemyBundle:spawn(
    Transform(100, 200),
    Health(50),
    Damage(10)
)

Use required when the component's value varies per entity (position, health, stats, etc.).

with

Components listed in with are automatically created at spawn time. The with table is a map keyed by component type; each value is either a factory function that returns an instance, or true for the component's default constructor.

With a factory:

teal
local bulletBundle: tecs.Bundle = world:newBundle("Bullet", {
    required = { Transform },
    with = {
        [Velocity] = function(): Velocity return Velocity(100, 0) end,
        [Damage] = function(): Damage return Damage(25) end,
    },
})

The factory runs on every spawn to produce a fresh instance. Use this when with-default components need specific initial values.

With true (default constructor):

teal
local treeBundle: tecs.Bundle = world:newBundle("Tree", {
    required = { Transform },
    with = {
        [StaticTag] = true,
        [Collidable] = true,
    },
})

true calls the component's default constructor (Component()). This is useful for tag components or components whose zero-initialized state is the desired default.

With-default components cannot be overridden at spawn time. If you need a spawn-time value, move the component to required instead.

World methods

These methods are available on every World.

MethodDescription
world:newBundleCreate and register a bundle.
world:spawnBundleSpawn an entity from a registered bundle by name.
world:getBundleReturn one registered bundle by name.
world:getBundlesReturn all registered bundles.

world:newBundle

Creates and registers a bundle for spawning entities with a predefined set of components.

teal
function World:newBundle(name: string, def?: BundleDef): Bundle

Parameters:

  • name: Unique bundle name.
  • def: Optional bundle definition with required and with fields.

Returns:

  • The registered bundle.

world:spawnBundle

Spawns an entity from a registered bundle by name. Required components are passed positionally in the order declared in the bundle. Components from with use their registered factory and cannot be overridden at spawn time.

teal
function World:spawnBundle(name: string, ...: Component): integer

Parameters:

  • name: Bundle name.
  • ...: Required components, in declaration order.

Returns:

  • The entity ID.

world:getBundle

Returns a registered bundle by name.

teal
function World:getBundle(name: string): Bundle

Parameters:

  • name: Bundle name.

Returns:

  • The bundle, or nil if not found.

world:getBundles

Returns all registered bundles as a map keyed by bundle name.

teal
function World:getBundles(): {string: Bundle}

Returns:

  • Map of bundle name to bundle.

Spawning from a Bundle

There are two ways to spawn from a bundle:

Via the bundle object

teal
-- Required components in the order they were declared.
local entityId: integer = playerBundle:spawn(
    Transform(100, 200),
    Health(100)
)

Via the world by name

teal
local entityId: integer = world:spawnBundle("Player",
    Transform(100, 200),
    Health(100)
)

bundle:spawn(...) and world:spawnBundle(name, ...) follow the same instant-vs-staged rules as world:spawn: outside a deferred scope the entity is placed in its archetype before the call returns; inside a scope (query iteration, world:defer(), a batch op callback) the entity is staged and applies when the scope closes. The returned id is usable immediately in either case.

teal
-- Outside a scope: placed right away.
local id: integer = playerBundle:spawn(Transform(0, 0), Health(100))
assert(world:isAlive(id))

-- Inside a scope (e.g. a query loop), follow-up mutations stage in order.
for _archetype, _len, _entities in someQuery:iter() do
    local newId: integer = playerBundle:spawn(Transform(0, 0), Health(100))
    world:set(newId, CustomTag)  -- staged; applies when the loop exits
end

Looking up Bundles

Get a single bundle by name:

teal
local bundle: tecs.Bundle = world:getBundle("Player")

Get all registered bundles:

teal
local bundles: {string: tecs.Bundle} = world:getBundles()

for name, bundle in pairs(bundles) do
    print(name)
    print("Required: " .. table.concat(bundle.required, ", "))
    print("Defaulted: " .. table.concat(bundle.defaulted, ", "))
end

Bundle record

Each bundle exposes these fields:

FieldTypeDescription
namestringThe bundle's registered name.
required{string}Names of components required at spawn time, in declaration order.
defaulted{string}Names of components filled in by factories.
spawn(...)functionSpawn an entity from this bundle; returns the id.