Generated Multi-Component Accessors
Source-generated zero-copy Refs/MutRefs structs reading or writing every archetype component in one call.
Status: β Implemented Β· Visibility: Public Β· Level: π΅ Core Β· Category: Ecs
π― What it solves
Reading or writing several components on the same entity through raw EntityRef calls needs N+1 lines β one
Open/OpenMut plus one Read/Write per component. For archetypes with four or five components that
clutters game-loop code with repetitive boilerplate, and a generic out T1, out T2, ... alternative would copy
data instead of returning zero-copy refs. The ArchetypeAccessorGenerator emits named, zero-copy multi-component
accessors directly on the archetype class so one call resolves every declared component.
βοΈ How it works (in brief)
For any [Archetype] class declared partial, the source generator emits nested Refs / MutRefs ref
structs β one ref readonly (or ref) field per Comp<T> the archetype declares, named to match the field β
plus static ReadAll(tx, id) / ReadWriteAll(tx, id) methods. Internally these call tx.Open/OpenMut exactly
once, then entity.Read/Write by handle for every component β the same O(1)-per-component cost as hand-written
code, just without the repetition. Child archetypes get a Refs/MutRefs that includes every inherited
component first, routed through the declaring parent class's Comp<T> handle for correct slot resolution.
π» Usage
[Archetype]
partial class Unit : Archetype<Unit> // 'partial' required β non-partial archetypes are silently skipped
{
public static readonly Comp<Position> Pos = Register<Position>();
public static readonly Comp<Velocity> Vel = Register<Velocity>();
}
[Archetype]
partial class Soldier : Archetype<Soldier, Unit>
{
public static readonly Comp<Health> Health = Register<Health>();
}
// βββ Read every component in one call βββ
Unit.Refs r = Unit.ReadAll(tx, id);
float x = r.Pos.X;
float dx = r.Vel.Dx;
// βββ Inherited archetype β parent components included, parent-first βββ
Soldier.Refs sr = Soldier.ReadAll(tx, soldierId);
float sx = sr.Pos.X; // from Unit
int hp = sr.Health.Current; // own
// βββ Write every component in one call βββ
Unit.MutRefs m = Unit.ReadWriteAll(tx, id);
m.Pos.X = 999;
m.Vel.Dx = 42;
tx.Commit();
β οΈ Guarantees & limits
- The archetype class must be declared
partial; if it isn't, the generator silently skips it β noRefs/MutRefs/ReadAll/ReadWriteAllare emitted, and no diagnostic is raised. Refs/MutRefsareref structβ stack-only, same lifetime constraints asEntityRef; cannot be stored in a field, boxed, or escape the call site.- Cost is one
Open/OpenMut(~350ns) plus N ref assignments (~1-5ns each forSingleVersion/Transient);Versionedfields additionally pay the per-Writecopy-on-write allocation. - Generated field names match the
Comp<T>declarations exactly β there is no positionalC1/C2form to disambiguate. ReadWriteAllopens the entity read-write and exposes every field mutably at once; there is no generated partial-write overload β useEntityRef.Writedirectly when only a subset of components needs mutation.
π§ͺ Tests
- EntitySpawnTests β
ReadAll/ReadWriteAllzero-copy round-trip, inherited-archetype field inclusion, mutate-then-verify-persisted
π Related
- Source:
src/Typhon.Generators/ArchetypeAccessorGenerator.cs - Parent feature: Entity Lifecycle & CRUD API