Generated Multi-Component Accessors
A source-generated
Valuesstruct andReadAll/WriteAllmethods that read or set 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/Set per component. For archetypes with four or five components that clutters
game-loop code with repetitive boilerplate. The ArchetypeAccessorGenerator emits named multi-component accessors
directly on the archetype class so one call reads, or sets, every declared component.
βοΈ How it works (in brief)
For any [Archetype] class declared partial, the source generator emits a nested Values struct β one field per
Comp<T> the archetype declares, named to match it β plus static ReadAll(tx, id) and WriteAll(tx, id, in values)
methods. ReadAll calls tx.Open once, then entity.Read by handle for every component, and returns the copies.
WriteAll calls tx.OpenMut once, then entity.Set by handle for every component. Child archetypes get a Values
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.Values v = Unit.ReadAll(tx, id);
float x = v.Pos.X;
float dx = v.Vel.Dx;
// βββ Inherited archetype β parent components included, parent-first βββ
Soldier.Values s = Soldier.ReadAll(tx, soldierId);
float sx = s.Pos.X; // from Unit
int hp = s.Health.Current; // own
// βββ Change some fields, then set every component in one call βββ
var u = Unit.ReadAll(tx, id);
u.Pos.X = 999;
u.Vel.Dx = 42;
Unit.WriteAll(tx, id, u);
tx.Commit();
β οΈ Guarantees & limits
- The archetype class must be declared
partial; if it isn't, the generator silently skips it β noValues/ReadAll/WriteAllare emitted, and no diagnostic is raised. Valuesholds copies, likeEntityRef.Read: it is an ordinary struct, valid after the transaction, and changing it changes nothing untilWriteAll(rule EP-03).- Cost is one
Open/OpenMut(~90β100 ns warm, see the CRUD page) plus one copy per component;Versionedfields additionally pay the per-Setcopy-on-write allocation. - Generated field names match the
Comp<T>declarations exactly β there is no positionalC1/C2form to disambiguate. WriteAllsets every component, which marks every one dirty and copies-on-write everyVersionedone; when only a subset changes,Setthose throughEntityRefMutdirectly.
π§ͺ Tests
- EntitySpawnTests β
ReadAll/WriteAllround-trip, inherited-archetype field inclusion, change-then-verify-persisted
π Related
- Source:
src/Typhon.Generators/ArchetypeAccessorGenerator.cs - Parent feature: Entity Lifecycle & CRUD API