# Components Components hold entity data. A system binds component columns from each matching archetype, reads through `get`, and takes writable columns through `getMut`: ```teal local Transform2D = tecs.Transform2D local movers = world:newQuery({ include = {Transform2D, Velocity}, }) world:addSystem({ name = "game.Move", phase = tecs.ecs.phases.Update, run = function(dt: number) for archetype, length in movers:iter() do local transforms = archetype:getMut(Transform2D) local velocities = archetype:get(Velocity) for row = 1, length do transforms[row].x = transforms[row].x + velocities[row].x * dt transforms[row].y = transforms[row].y + velocities[row].y * dt end end end, }) ``` `getMut` marks the `Transform2D` column dirty. The renderer and other incremental consumers use that mark to skip unchanged columns. Component values belong to callers. Callers may replace them with `world:set` or mutate their fields through `getMut`; Tecs owns the storage and dirty marks around those values. ## Storage choices | Kind | Use | | --------------------------------------------------- | ------------------------------------------------------------ | | [Table](/modules/ecs/components/table-components) | Strings, nested tables, opaque handles, and other Lua values | | [FFI](/modules/ecs/components/ffi) | Fixed-size numeric fields in contiguous C structs | | [Scalar](/modules/ecs/components/scalar-components) | One `number`, `boolean`, or `string` per entity | | [Tag](/modules/ecs/components/tag-components) | Presence with no per-entity value | The engine uses the same factories. `Transform2D`, `Sprite`, `Tint`, `Material`, `Clip`, and `PointLight2D` use FFI storage; `Renderable2D` uses table-component registration as a presence marker. ## Entity access `world:get(entity, Component)` returns the component or `nil`. A scalar component returns its raw value: ```teal local transform = world:get(entity, tecs.Transform2D) local name = world:get(entity, tecs.ecs.Name) ``` Call `world:getMut` before an in-place write: ```teal local transform = world:getMut(entity, tecs.Transform2D) if transform then transform.x = transform.x + 10 end ``` Do not call `getMut` at a site that might only read. It declares mutation intent and defeats dirty-gated work even when no value changes. An FFI reference obtained through `world:get` remains writable because LuaJIT cannot make cdata const. If code writes through that reference, it must call `world:markComponentDirty(entity, Component)` explicitly. A spawn inside a deferred scope reserves an ID but does not place the entity until commit. `world:get` and `world:getMut` return `nil` for that staged entity. Pass its initial values to `world:spawn` instead. ## Adding and removing components Pass instances to `world:spawn` and `world:set`: ```teal local entity = world:spawn( tecs.ecs.Name("Frank"), tecs.Transform2D(100, 200) ) world:set(entity, tecs.ecs.Name("Grace")) world:remove(entity, tecs.ecs.Name) ``` `world:has(entity, Component)` tests presence. Relationship containers and instances add any-target and specific-target checks; see [Relationships](/modules/ecs/relationships/). Adding or removing a component changes the entity's archetype. Query iteration, query callbacks, explicit `world:defer()` scopes, and batch callbacks stage that structural change until the outermost scope closes. [Deferred operations](/modules/ecs/world#deferred-operations) covers the visibility rules. ## Component dependencies {#auto-dependencies-with-requires} `requires` declares components that must accompany another component. Tecs adds the full transitive closure in one archetype transition: ```teal local record Velocity is tecs.ecs.Component x: number y: number metamethod __call: function(self, x?: number, y?: number): Velocity end tecs.ecs.newFFIComponent({ name = "Velocity", container = Velocity, fields = { {"x", "float"}, {"y", "float"}, }, defaults = {0, 0}, requires = {tecs.Transform2D}, }) local entity = world:spawn(Velocity(10, 20)) assert(world:has(entity, tecs.Transform2D)) ``` A `requires` entry may hold a component type, which Tecs calls with no arguments, or a component instance shared by every automatic addition. `newComponent`, `newFFIComponent`, `newScalarComponent`, `newTagComponent`, and both relationship factories accept the option. `tecs.ecs.RelativeTransform2D` requires `tecs.Transform2D`, so a relative transform and the world transform it feeds enter the same archetype together. Use [query callbacks](/modules/ecs/queries/callbacks) for work that must run when a signature starts or stops matching. ## Transient state Set `transient = true` on components and relationships that hold runtime projections such as native handles or caches. Snapshots omit those columns but keep their entities. Rebuild the omitted values from durable components after load. `transient` and a custom `serialize` function conflict, so registration rejects that combination. [Component serialization](/modules/ecs/components/serialization) covers codecs and migrations. ## Module contents ### Submodules | Submodule | Description | | --- | --- | | [`Component bundles`](/modules/ecs/components/bundles/) | Reusable entity templates with world:newBundle and spawnBundle | | [`Component construction`](/modules/ecs/components/construction/) | Shared component construction model covering __call, new, fields, defaults, and the init hook | | [`Component serialization`](/modules/ecs/components/serialization/) | Component serialize and deserialize hooks, transient, and automatic FFI schema fingerprint migration | | [`Dirty tracking`](/modules/ecs/components/dirty-tracking/) | Per-archetype per-component dirty bits set by getMut and set, and the extractor that reads them | | [`FFI components`](/modules/ecs/components/ffi/) | FFI struct-backed components via newFFIComponent with C field types and defaults | | [`Scalar components`](/modules/ecs/components/scalar-components/) | Single-value number, boolean, or string components via newScalarComponent with fast SoA columns | | [`Table components`](/modules/ecs/components/table-components/) | Lua-table-backed components via newComponent with fields, init, custom __call, and new | | [`Tag components`](/modules/ecs/components/tag-components/) | Dataless presence tags via newTagComponent for flags, markers, and query filtering |