Source Generation
Already know ECS? mentions this in passing: Wyrd’s dispatch, query overloads, and serializers are generated at compile time by Roslyn incremental generators, not resolved through reflection at runtime. Here’s what each one actually does.
What gets generated
Section titled “What gets generated”- Query dispatch: every distinct
.With<...>/.Without<...>/.Has<...>/.Any<...>chain gets its own cachedArchetypeQueryandForEach/ParallelForEachoverloads on the exact tuple type it was written against, see Queries. The same generator fills in aQuerySystem’s dispatch betweenDefineQueryandUpdate, and builds the reads/writes/ordering metadata the scheduler reads, see Scheduling. - Query arity: the
With/Without/Has/Anyoverloads for two, three, and more type arguments, plus theArchetypeQuery/ArchetypeFiltertypes those chains build against, are templated once for every supported arity, independent of anything your code actually calls. - Debug names: implementing
IComponent,ITag, orIRelationon a struct is itself what registers a human-readable debug name for it, disambiguated by its containing type when two types share a name. - Persistence: the binary codec generates a
MemoryPackFormatterper component with managed fields, hand-rolling the read/write calls rather than going through MemoryPack’s generic path. The JSON codec and the shared tag/discriminator registration generate the equivalent registration calls, honoring[StableName]/[RenamedFrom]where you’ve set them.
Why generation instead of reflection
Section titled “Why generation instead of reflection”The core library is IsAotCompatible, and three separate smoke-test projects publish with Native AOT and actually run, not just build. That only holds because none of the generated code touches reflection: everything is closed generic types and delegates resolved at compile time, a cached ArchetypeQuery or a generated formatter, not a runtime dictionary keyed by Type. The cost that would otherwise show up as a lookup or a boxing allocation per entity happens once, at compile time, instead.
Generation triggers on declaration
Section titled “Generation triggers on declaration”The generators ship packaged as analyzers inside Wyrd.Ecs itself, and the persistence packages do the same for their own generators. Declaring an IComponent/ITag/IRelation struct, or writing a .ForEach/QuerySystem, is what triggers generation directly, the same act that makes a type usable is the act that generates its dispatch and serialization code.