Skip to content

The generator pipeline

ICodeGenerator, the twelve domain generators and the order they run in, and the four ways to change what a sync writes without forking the plugin.

All documentation — Extending
On this page

A sync builds one ProjectModel and then runs a list of generators over it. Each returns files; the pipeline writes them. That list is the seam — you can skip an entry, replace one, append your own, or post-process everything.

ICodeGenerator

Member
string IdUnique, and how a config names you for a skip or a replacement. Typically the class name.
List<GeneratedFile> Generate(ProjectModel model, GeneratorOptions options, List<Diagnostic> diagnostics)

diagnostics is a mutable list you append to — it is not a channel and there is no callback. Append rather than throw: a generator that throws takes the whole sync with it, and one bad enum is not worth a failed sync.

GeneratedFile

Member
RelativePathWithin the output directory — Enums/Gender.cs, Data/Character/RynZhao.json. Forward slashes, on every platform.
Content
IsJsonWhether the writer applies code formatting

GeneratorOptions

MemberDefault
NamespaceTaleShard.GeneratedGenerators append their own sub-namespace — .Enums, .Entities
OutputPath./outputEach generator makes its own subdirectory under it
MediaOutputPathMediaRelative to the output. The editor’s folder hierarchy is preserved inside it.

Diagnostic

Severity · Code · Message, with Info, Warning and Error factories and a formatted ToString.

DiagnosticSeverity is Info · Warning · Error — the last meaning generation cannot be correct.

Give a code, not just a message — ENUM001 is the shape. It is what makes a diagnostic matchable rather than merely readable, and it is the same argument as the runtime’s own diagnostic codes.

The twelve domains, in order

Order is dependency order, not preference: several read model properties an earlier domain populated. A generator you insert has to respect it.

#IdProduces
1TagDomainTags
2EnumDomainOne C# file per enum, plus the enum registry
3MediaDomainThe media constants class and the lookup index
4EntityDomainThe entity base, per-definition classes, per-item JSON, the loader, the store and the project database
5TraitDomainOne class per unique trait code
6TableDomainA typed row class, per-table JSON and a typed loader
7CommandDomainThe command handler partial and the dispatcher implementation
8VariableDomainThe variables class and its store
9NodeTypeDomainOne typed class per node type the project declares
10BoardDomainThe board registry and the boards class
11LocalizationDomainPer-language JSON, the localization registry, per-language UI strings, and project settings
12ScaffoldDomainThe session, bootstrapper, behaviours and data facade — plus the copy of runtime.json the game plays from

Three of those are worth a sentence more:

TriggerDomain does not exist. Triggers are options on a reusable enum, so EnumDomain emits them as a C# enum plus a registry entry.

NodeTypeDomain is why game code never matches on a type name. A user-authored node type becomes a real class with one member per declared field and a list per repeater.

TableDomain evaluates nothing. The sync server materializes spreadsheet cells — computed values, with entity and asset references already resolved — so no formula evaluation and no id-to-code resolution happen at generation time.

BoardDomain reads the board tree — names, folders, order — from codegen.json and board content from runtime.json. Two files, two purposes.

The three toggles

The sync window has three checkboxes, and they are the ordinary way to turn generators off — no code required. Each owns a set of generators, and turning one off adds their ids to the skip list.

ToggleOwnsTurn it off when
Data structuresThe ten that produce typed C# for project content — enums, entities, traits, variables, commands, tags, media, localization, node types, tablesYou only want the data files
Runtime integrationBoardDomain — board loading and the registries that wire data inNever, for a project using the dialogue runtime
Unity bootstrapScaffoldDomain — the scene-level scaffoldingYou have your own game initialization

Unity bootstrap is the one integrators actually turn off. It is the only layer whose output is opinionated about how your game starts; the other two produce the code everything else is written against.

Changing the list

GeneratorPipelineConfig is the whole surface, and the four members do not overlap.

Member
IGeneratorProfile? ProfileThe base set. Null means the default profile.
HashSet<string> SkipGeneratorsIds to exclude
Dictionary<string, ICodeGenerator> ReplacementGeneratorsBy id. A replacement keeps the original’s position.
List<ICodeGenerator> AdditionalGeneratorsAppended after the profile’s
List<Func<List<GeneratedFile>, List<GeneratedFile>>> PostProcessorsRun in order, after every generator

A post-processor receives the full list of generated files and returns a list. That is the place to stamp a header, drop a file, or rewrite a path across everything at once.

IGeneratorProfile

A named, ordered set of generators: TargetEngine and GetGenerators().

The seam is live — the pipeline runs whatever GetGenerators returns, and the sync service builds a config around it.

IOutputWriter

void WriteFiles(List<GeneratedFile> files, string outputPath) — where generated files land. Replaceable for a build that writes somewhere other than the file system.

See also

Post-sync hooks run after the whole sync, on the finished output. Reach for a hook when you want to react to a sync, and for this pipeline when you want to change what it produces.

Written for
Unity developers · Advanced integrators
Last updated