Shadop.Archmage 0.18.0

dotnet add package Shadop.Archmage --version 0.18.0
                    
NuGet\Install-Package Shadop.Archmage -Version 0.18.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Shadop.Archmage" Version="0.18.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Shadop.Archmage" Version="0.18.0" />
                    
Directory.Packages.props
<PackageReference Include="Shadop.Archmage" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Shadop.Archmage --version 0.18.0
                    
#r "nuget: Shadop.Archmage, 0.18.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Shadop.Archmage@0.18.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Shadop.Archmage&version=0.18.0
                    
Install as a Cake Addin
#tool nuget:?package=Shadop.Archmage&version=0.18.0
                    
Install as a Cake Tool

C# SDK Overview

The C# SDK is the runtime library through which C# applications load and access config data exported by Archmage.

Archmage is a configuration solution for game development: specifications for how to structure config data, define fields, and fill in each value; pipelines that export runtime data and generate strongly-typed code; multi-language SDKs for loading and accessing that data at runtime; and a collaborative editing workflow for teams.

The SDK is built around the concept of an Atlas — a registry that maps named keys to configurations. Each key is associated with one or more JSON files. At runtime, the SDK reads these files, deserializes them into instances of generated C# types, resolves cross-table references, and calls post-load hooks.

Key features

  • I18n — multi-language text management with automatic fallback
  • XRef — cross-table reference resolution via IAtlas.BindRefs
  • Duration — nanosecond precision; formats as human-readable strings such as 1s200ms
  • MinMax — random value selection within a range
  • WeightedPool — weighted random selection with probability proportional to item weight
  • Variants — switch an item to an alternative data set at load time via WithVariant
  • Whitelist/Blacklist — load only a subset of atlas items
  • Layered overrides — merge files with matching relative paths from additional override sources (a directory path or a custom file system) into the base configs, field by field, at load time
  • Synchronous and asynchronous loading — progress reporting, cancellation, and pluggable strategies for parallel loading
  • Pluggable file system — load from embedded resources, in-memory data, or any other source via IFS
  • Versioning — VCS metadata (branch, commit, timestamp, etc.), when present in atlas.json, is available on the loaded atlas
  • Unity support — built-in adapters for Addressables, Resources, and StreamingAssets; Inspector dropdowns for config ID fields, populated from the loaded atlas, for easy selection

Requirements

Unity

  • Unity 6000.3 or later
  • com.unity.nuget.newtonsoft-json 3.2.2

.NET

  • net8.0, netstandard2.1 or later
  • Newtonsoft.Json 13.0.4

Installation

Unity

Via GitHub — In the Package Manager window, click + → Add package from git URL, and enter:

https://github.com/shadowopera/sdk-cs.git?path=unity/dev.shadop.archmage

Or via OpenUPM:

openupm add dev.shadop.archmage

Unity Signature Warning: Unity may display a "Missing Signature" warning. This is expected for OpenUPM packages. Archmage is safe to use — the warning does not affect functionality. Simply proceed with your development as usual.

If your project uses .asmdef files, add the following assembly references:

  • Shadop.Archmage.Sdk
  • Shadop.Archmage.Sdk.Unity
  • Shadop.Archmage.Sdk.Unity.Addressables (optional, only if using Addressables)
  • Shadop.Archmage.Sdk.Unity.Editor (optional)

.NET (via NuGet)

dotnet add package Shadop.Archmage

Getting Started

Unity (Addressables)

A complete working example is in ConfLoader.cs, covering Addressables, Resources, and StreamingAssets — with sync/async variants, concurrent loading, and I18n setup. For Inspector integration, see ArchmageEditorTools.cs, which demonstrates config ID dropdown wiring.

The recommended starting point:

using Shadop.Archmage.Sdk;

// ConfigAtlas is generated by Archmage
var atlas = new ConfigAtlas();
var options = new AtlasOptions()
    .WithLogger(new UnityAtlasLogger())
    .WithJsonSettings(UnityJsonSettingsFactory.Create())
    .WithFS(new UnityAddressablesFS());

await Archmage.LoadAtlasAsync(
    "Assets/Configs/atlas.json", "Assets/Configs", atlas, options);

.NET

using Shadop.Archmage.Sdk;

// ConfigAtlas is generated by Archmage
var atlas = new ConfigAtlas();
Archmage.LoadAtlas("configs/atlas.json", "configs/", atlas);

Concepts

atlas.json is generated by Archmage. It declares how each config key maps to its JSON files using one of three strategies:

Strategy Shape Behavior
unique key → "file.json" Deserializes one file into the config object
variant key → { "/": "file.json", "alt": "file-alt.json" } Selects one variant by case; "/" is the default
many key → ["a.json", "b.json"] Deserializes and merges multiple files in order

Example atlas.json:

{
    "unique": {
        "hero": "hero.json",
        "item": "clutter/item.json"
    },
    "variant": {
        "game": { "/": "game.json", "hard": "game_hard.json" }
    },
    "many": {
        "weapon": [ "vtbl/weapon-sword.json", "vtbl/weapon-staff.json" ]
    }
}

Loading Configs

Loading proceeds in the following steps:

  1. Parse atlas.json
  2. Apply AtlasModifier (if set)
  3. For each item: read files → deserialize → apply overrides
  4. BindRefs() — resolve cross-table references
  5. OnLoaded() — post-load initialization

AtlasOptions

Configure loading via the fluent AtlasOptions builder:

var opts = new AtlasOptions()
    // custom logger (default: stderr; silent in Unity)
    .WithLogger(myLogger)
    // replace the default filesystem (System.IO)
    .WithFS(myFS)
    // load only these keys
    .WithWhitelist(new[] { "hero", "item" })
    // skip these keys
    .WithBlacklist(new[] { "debug" })
    // select a variant
    .WithVariant("game", "hard")
    // add an override directory
    .WithOverrideRoot("configs/override/")
    // add an override filesystem
    .WithOverrideFS(embeddedFS)
    // mutate atlas.json after parsing
    .WithAtlasModifier(atlasJson => { ... })
    // custom Newtonsoft.Json settings
    .WithJsonSettings(customSettings);

Whitelist / Blacklist — If a non-empty whitelist is set, only listed keys are loaded (blacklist is ignored). All keys must exist in the atlas or an exception is thrown.

Variant selection — A variant-mapped key loads its "/" variant unless WithVariant selects another one. The variant in use is recorded in AtlasItem.Variant.

Override layers — Each WithOverrideRoot / WithOverrideFS call adds another override source. When loading an item, each override source is checked in the order they were added; any matching file is deserialized and its fields applied on top of the base data. This is useful for environment-specific patches.

Field-level merge rules during override processing:

Value in override Behavior
null Resets the target field to its default value or raises an exception
JSON object Recursively merges — only fields present in the override are updated, others remain unchanged
Any other value Overwrites the field

Custom load strategy — By default, items are loaded one by one in alphabetical order. WithLoadStrategy and WithAsyncLoadStrategy let you take control of that loop — for example to load items in parallel:

// Parallel sync
var opts = new AtlasOptions().WithLoadStrategy((items, load) =>
    Parallel.ForEach(items, kvp => load(kvp.Key, kvp.Value)));

// Parallel async
var opts = new AtlasOptions().WithAsyncLoadStrategy(async (items, loadAsync, ct) =>
    await Task.WhenAll(items.Select(kvp => loadAsync(kvp.Key, kvp.Value, ct))));

Custom File System

Both WithFS and WithOverrideFS accept an IFS implementation. The default filesystem reads from System.IO. You can supply a custom IFS to replace it or to use as an override source — for example to load from embedded resources or an in-memory dictionary:

class EmbeddedFS : IFS
{
    public bool DirectoryExists(string path) => true;
    public bool FileExists(string path) => /* check assembly resources */;
    public byte[] ReadAllBytes(string path) => /* load from resources */;
    public Task<byte[]> ReadAllBytesAsync(string path, CancellationToken ct) => /* async load */;
}

var opts = new AtlasOptions().WithFS(new EmbeddedFS());

Special Types

I18n — Localization

I18n holds per-language translations and falls back to a default language when a key is missing.

var i18n = new I18n(fallbackLanguage: "en");
i18n.MergeL10nFile("l10n/en.json", "en");
i18n.MergeL10nFile("l10n/zh-CN.json", "zh-CN");

i18n.Text("ui.ok", "zh-CN");  // → "确认"
i18n.Text("ui.ok", "ja");     // → falls back to "OK"

In generated config classes, localized fields are typed as L10n. In JSON they are represented as strings (e.g., "ui.ok"); accessing .Text on an L10n field looks up that key in a shared I18n instance. Set L10n.GetI18n and L10n.GetPreferredLanguage to configure the lookup before use.

L10n.GetI18n = () => i18n;
L10n.GetPreferredLanguage = () => "zh-CN";

// Then in your code:
string label = hero.Name.Text;

XRef — Cross-table Reference

XRef<V, T> pairs a config ID (CfgId) with a resolved reference (Ref) set during BindRefs.

// In generated config class:
public XRef<HeroCfgId, HeroCfg> Boss { get; set; }

// After loading:
var boss = atlas.HeroTable[1].Boss.Ref;   // resolved object

Duration

A nanosecond-precision duration type. It serializes as a compact integer array in JSON (e.g., [0, 5] = 5 seconds).

Duration d = Duration.Second * 90 + Duration.Millisecond * 500;
d.ToString();      // "1m30s500ms"
d.Seconds();       // 90.5
d.Milliseconds();  // 90500
d.ToTimeSpan();    // TimeSpan

Arithmetic operators (+, -, *, /, %) and comparisons are supported.

Rgba

A color type with R, G, B, A byte channels. In Unity, .ToColor() converts it to UnityEngine.Color.

var color = Rgba.Parse("#FF8000");   // R=255, G=128, B=0, A=255
color.ToString();                    // "#FF8000"

MinMax

MinMax<T> is a range bounded by Min and Max. The Sample extension methods draw a random value from the range. T may be any integer type, float, double, or Duration.

WeightedPool

WeightedPool<T> holds parallel Items and Weights arrays. The Sample / SampleIndex extension methods draw an item (or its index) at random with probability proportional to its weight.

Vec

Vec2<T>, Vec3<T>, Vec4<T> are typed vectors. Fields are accessed as .X, .Y, .Z, .W.

Tup

Tup1–Tup7 are heterogeneous tuples. They serialize as JSON objects with keys item0, item1, etc. (0-based). Fields are accessed as .Item0, .Item1, etc., and deconstruction is supported.

Data Versioning

atlas.json can carry a version block with VCS metadata (branch, commit ID, timestamp, author). After loading, it is available on the atlas:

{
    "version": {
        "branch": "main",
        "id": "a1b2c3d4e5f6...",
        "shortId": "a1b2c3d",
        "timestamp": "2025-01-01T00:00:00Z"
    },
    ...
}
var ver = atlas.DataVersion;   // VersionInfo?, null if not present
ver?.Branch   // "main"
ver?.ShortID  // "a1b2c3d"

See Also


Development

Project Structure

sdk-cs/
├── src/Archmage/
│   ├── Sdk/                        # C# runtime source (canonical)
│   │   └── Unity/                  # Unity adapters (FS, logger, JSON settings, type extensions)
│   │       └── Addressables/       # Addressables FS adapter
│   └── Editor/Unity/               # Inspector dropdowns for config ID fields
├── unity/
│   ├── ArchmageDev/                # Unity demo & development project
│   └── dev.shadop.archmage/        # Unity package (OpenUPM)
├── tests/                          # xunit.v3 tests
│   ├── Conf/                       # Generated config code
│   ├── testdata/                   # atlas.json and config JSON
│   ├── override/                   # Override-layer JSON
│   └── golden/                     # Expected DumpAtlas output
├── scripts/                        # Unity sync, version bump, release
│   └── rsync-unity.sh              # src/ → unity/dev.shadop.archmage/ sync
└── docs/                           # Documentation site (Starlight)

Build & Test

dotnet build src/Archmage/Archmage.csproj
dotnet test tests/Archmage.Tests.csproj
dotnet test tests/Archmage.Tests.csproj --filter "FullyQualifiedName~TestName"
UPDATE_GOLDEN=1 dotnet test tests/Archmage.Tests.csproj   # regenerate golden files

License

Apache 2.0. See LICENSE for details.

Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
.NET Core netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.1 is compatible. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.18.0 0 9/24/2026
0.17.0 99 9/9/2026
0.16.0 126 7/31/2026
0.15.2 131 7/11/2026
0.15.1 137 6/15/2026
0.15.0 123 6/11/2026
0.14.0 124 5/31/2026
0.13.1 124 5/23/2026
0.13.0 117 5/23/2026
0.12.0 114 5/7/2026
0.11.0 122 4/24/2026
0.10.0 129 4/10/2026
0.9.0 133 4/7/2026
0.8.0 124 4/5/2026
0.7.0 125 4/1/2026
0.6.0 129 3/29/2026
0.5.0 122 3/22/2026
0.4.0 126 3/18/2026
0.3.1 136 3/17/2026
0.3.0 122 3/17/2026
Loading failed