-
Notifications
You must be signed in to change notification settings - Fork 0
Source Generators
ExpandedLib.Generators is a Roslyn source-generator project (netstandard2.0) that removes two
kinds of boilerplate at compile time: typed accessors for config classes, and typed lang keys. Both
run automatically on build - there is nothing to invoke.
The project sets IncludeBuildOutput=false and is consumed as an analyzer, so it ships no
runtime assembly. Outside this repository it comes in the exlib-testing bundle under
analyzers/; wire it with <Analyzer Include="analyzers/ExpandedLib.Generators.dll" />.
Triggers on: a class tagged [ExConfigRegister(fileName, modId)] (see Config System).
Emits: a static partial class (default name = your type name with Config -> Values, override
with AccessorName) containing:
-
public const string ConfigFileName- the file name you passed. - A private
ExConfigRegister<T>backing store, withLegacyFileNamesinitialised and a staticMigrationsmember forwarded if your config type declares one. -
public static void Load(ICoreAPI api)- calls the store'sLoad; ifManageable = true, also registers withExConfigProfiles. -
public static void Edit(Action<T> mutate)andpublic static void Save(). - One
public staticread-only getter per public, non-static, readable property (exceptConfigVersion), forwarding to the live config.
So this:
[ExConfigRegister("ex_values.json", "iiex", LegacyFileNames = ["lpex_values.json"], Manageable = true)]
public class IiexConfig : IExVersionedConfig
{
public string? ConfigVersion { get; set; }
public static readonly ExConfigMigration[] Migrations = [ /* ... */ ];
public float PumpWaterPerSecond { get; set; } = 16.67f;
}generates roughly:
public static partial class IiexValues
{
public const string ConfigFileName = "ex_values.json";
private static readonly ExConfigRegister<IiexConfig> _store =
new(ConfigFileName, "iiex", IiexConfig.Migrations) { LegacyFileNames = ["lpex_values.json"] };
private static IiexConfig _config => _store.Config;
public static void Load(ICoreAPI api) { _store.Load(api); ExConfigProfiles.Register(_store); }
public static void Edit(Action<IiexConfig> mutate) { mutate(_store.Config); _store.Save(); }
public static void Save() => _store.Save();
public static float PumpWaterPerSecond => _config.PumpWaterPerSecond;
}Triggers on: an assets/{domain}/lang/en.json supplied to the compiler as an AdditionalFiles
item. There is no attribute and nothing to tag - the file is the input.
Emits: a {Domain}Lang class of public const string members, one per key in that file, so a
mistyped key is a compile error instead of a raw key rendered in the player's UI. English is the
source of truth; the other locales are never read by the generator.
A bare key k emits the value "{domain}:{k}". A key that is already domain-qualified - a vanilla
override such as "game:placefailure-..." - emits verbatim, so an override still resolves to the
domain it overrides.
The class is emitted into the consuming project's RootNamespace, so it resolves from any file in
that project without a using.
The consuming csproj must feed it, which is the step most easily missed - with no
AdditionalFiles the generator runs, finds nothing and emits nothing, and the only symptom is that
{Domain}Lang does not exist. Setting <AssetDomain> (see Getting Started) does
this for you: the ExpandedLib package's own build/ExpandedLib.targets carries
<AdditionalFiles Include="$(ModAssetsRoot)\$(AssetDomain)\lang\en.json" />conditioned on $(AssetDomain) being set, so a mod project declares its domain and nothing else.
Only the primary domain is fed: a mod packing a second tree (an absorbed mod's assets, or a
game: override) should not emit typed constants for someone else's keys.
- Generated code is re-emitted every build, so adding a config property or a lang key is instant - no boilerplate to duplicate or keep in sync.
- The generator targets
netstandard2.0(a Roslyn requirement); if you fork it, mind the usual netstandard2.0 source-generator constraints (no newer BCL APIs).
-
Config System - the runtime side of
[ExConfigRegister]. -
Registries -
[BlockRegister]/[ItemRegister]registration. -
Testing Harness - the
exlib-testingbundle these ship in.
| Id | Severity | Generator | Reported when |
|---|---|---|---|
EXLIB0001 |
Error | ExConfigGenerator |
A class carries [ExRecipeProfile] with no companion [ExConfigRegister], so no accessor is generated at all. |
EXLIB0002 |
Error | ExConfigGenerator |
A class's [ExRecipeProfile] shape is invalid (missing catalogue property, missing DefaultCatalogue, missing or unregistered level property). Reported at the attribute's location alongside the #error the generated accessor still carries - the #error is what stops the build; this diagnostic only gives an IDE somewhere to navigate to. |
EXLIB0003 |
Warning | ExLangKeyGenerator |
The consuming project sets $(AssetDomain) but assets/{domain}/lang/en.json did not yield a parsed lang file - missing from the AdditionalFiles items, or present but malformed - so {Domain}Lang is not generated. Not reported for a project with no $(AssetDomain) at all. |
EXLIB0004 |
Warning | ExLangKeyGenerator |
Two lang keys sanitise to the same member name; the one that sorts later gets a numeric suffix instead of the plain name. |
Expanded Library · framework mod for Vintage Story
exlib - Blocks
- Block Entities
- Block Networks
- Multiblock Structures
- Production Machines
- Construction (RCC)
- Migrations & Healing
exlib - Extending our mods
exlib - Registration
- Registries
- Modules
- Code-First Definitions
- Config System
- Commands
- Recipe Costs
- Source Generators
- Checks
exlib - Utilities
exlib.testing