Component is the typed state unit of the ECS. Every entity is stored as the union of its component fields, and every archetype table schema is derived from component types.
The implementation lives in src/archetype/core/component.py.
Contract¶
Component extends lancedb.pydantic.LanceModel and adds the small set of methods the rest of the runtime depends on:
class Component(LanceModel):
@classmethod
def get_type_by_name(cls, name: str) -> type["Component"]: ...
@classmethod
def from_dict(cls, data: dict[str, Any]) -> "Component": ...
def to_payload(self) -> dict[str, Any]: ...
@classmethod
def get_prefix(cls) -> str: ...
@classmethod
def to_pyarrow_schema(cls) -> pa.Schema: ...
@classmethod
def get_prefixed_schema(cls) -> pa.Schema: ...
def to_row_dict(self) -> dict[str, Any]: ...
At runtime, components serve three roles:
- define typed entity fields,
- generate Arrow-compatible schema fragments,
- serialize themselves into command payloads and archetype rows.
Defining a Component¶
Define components as subclasses with typed model fields:
from archetype.core.component import Component
class Position(Component):
x: float = 0.0
y: float = 0.0
class Velocity(Component):
dx: float = 0.0
dy: float = 0.0
The runtime does not attach behavior to components. Behavior lives in processors; components are schema-bearing data models.
Column Model¶
Component fields are flattened into archetype table columns using a stable prefix:
Position.get_prefix() # "position__"
Position(x=1.0, y=2.0).to_row_dict()
# {"position__x": 1.0, "position__y": 2.0}
The prefix is always lowercase_class_name + "__".
This naming convention is part of the storage contract:
Component.get_prefixed_schema()renames every field in the component schema,Archetype.get_archetype_schema()merges prefixed component schemas with base metadata columns,- processors read and write component state through those prefixed column names.
Example archetype row shape:
world_id | run_id | entity_id | tick | is_active | position__x | position__y
Serialization Paths¶
Command payloads¶
to_payload() includes the concrete class name:
Position(x=1, y=2).to_payload()
# {"type": "Position", "x": 1.0, "y": 2.0}
from_dict() reverses that process:
Component.from_dict({"type": "Position", "x": 1, "y": 2})
# Position(x=1.0, y=2.0)
Two details matter:
Component.from_dict(...)requires a"type"key when called on the base class,- subclass resolution walks the full subclass tree, not only direct subclasses.
That is the mechanism the command layer relies on when components cross API or broker boundaries.
Storage rows¶
to_row_dict() emits only prefixed component fields. Archetype-level metadata is added by Archetype.to_row_dict() and later stamped by the updater/world pipeline.
Relationship to Archetypes¶
An archetype is defined by the exact set of component types attached to an entity.
The connection points are explicit in code:
Archetype.sig_from_components()converts component instances into a canonical signature,Archetype.get_archetype_schema()combinesComponent.get_prefixed_schema()for every type in that signature,Archetype.to_row_dict()merges base columns with each component'smodel_dump().
Because signatures are sorted by component type name, component order is not semantically meaningful:
[Position(), Velocity()]
[Velocity(), Position()]
Both produce the same archetype signature.
Relationship to Worlds¶
AsyncWorld and SyncWorld treat components as the unit of structural change:
create_entity([...])assigns an entity to the signature implied by its components,add_components(...)migrates the entity to a new archetype,remove_components(...)migrates it again by removing schema columns associated with those types.
Component add/remove is not an in-place schema edit. It is an archetype transition.
Practical Implications¶
- Keep components focused on data shape, not behavior.
- Treat the class name as part of the serialization contract because payload round-trips use it.
- Treat prefixed field names as part of the storage and processor contract.
- Expect component composition to determine which processors can run and which archetype table stores the entity.
Further Reading¶
- Archetypes -- how component sets define signatures, schemas, and table names
- Processors -- how processors declare component requirements and transform DataFrames
- Worlds -- entity creation, component add/remove, and archetype migration
- Data Flow -- the command pipeline for submitting component mutations externally
Source References¶
src/archetype/core/component.pysrc/archetype/core/archetype.pysrc/archetype/core/interfaces.pysrc/archetype/core/aio/async_world.py