From 4f966f9f50564df96762360126f8ad3c6be9e798 Mon Sep 17 00:00:00 2001 From: GeWuYou <95328647+GeWuYou@users.noreply.github.com> Date: Mon, 6 Apr 2026 15:55:58 +0800 Subject: [PATCH] =?UTF-8?q?docs(game):=20=E6=B7=BB=E5=8A=A0=E6=B8=B8?= =?UTF-8?q?=E6=88=8F=E5=86=85=E5=AE=B9=E9=85=8D=E7=BD=AE=E7=B3=BB=E7=BB=9F?= =?UTF-8?q?=E6=96=87=E6=A1=A3=E5=92=8C=E7=94=9F=E6=88=90=E5=99=A8=E5=8A=9F?= =?UTF-8?q?=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加了 AI-First 配置系统完整文档,涵盖 YAML 配置、JSON Schema 结构、目录组织 - 实现了 Source Generator 自动生成配置类型、表包装、单表注册和访问辅助代码 - 提供了项目级聚合注册目录和配置浏览、校验、表单编辑等工具支持 - 集成了运行时只读查询、热重载、跨表引用校验等核心功能 - 添加了 VS Code 插件支持配置浏览、raw 编辑、schema 打开和递归校验功能 --- ...GeneratedConfigConsumerIntegrationTests.cs | 4 +- .../Config/SchemaConfigGeneratorTests.cs | 9 +++- .../GeneratedConfigCatalog.g.txt | 36 ++++++++++++- .../Config/SchemaConfigGenerator.cs | 52 ++++++++++++++++++- docs/zh-CN/game/config-system.md | 17 ++++++ 5 files changed, 112 insertions(+), 6 deletions(-) diff --git a/GFramework.Game.Tests/Config/GeneratedConfigConsumerIntegrationTests.cs b/GFramework.Game.Tests/Config/GeneratedConfigConsumerIntegrationTests.cs index 65289810..3483592f 100644 --- a/GFramework.Game.Tests/Config/GeneratedConfigConsumerIntegrationTests.cs +++ b/GFramework.Game.Tests/Config/GeneratedConfigConsumerIntegrationTests.cs @@ -100,7 +100,9 @@ public class GeneratedConfigConsumerIntegrationTests Assert.Multiple(() => { - Assert.That(GeneratedConfigCatalog.Tables.Count, Is.EqualTo(1)); + Assert.That( + GeneratedConfigCatalog.Tables.Select(static metadata => metadata.TableName), + Does.Contain("monster")); Assert.That(GeneratedConfigCatalog.TryGetByTableName("monster", out var catalogEntry), Is.True); Assert.That(catalogEntry.ConfigDomain, Is.EqualTo("monster")); Assert.That(catalogEntry.ConfigRelativePath, Is.EqualTo("monster")); diff --git a/GFramework.SourceGenerators.Tests/Config/SchemaConfigGeneratorTests.cs b/GFramework.SourceGenerators.Tests/Config/SchemaConfigGeneratorTests.cs index 1f3b24b1..14b39fb2 100644 --- a/GFramework.SourceGenerators.Tests/Config/SchemaConfigGeneratorTests.cs +++ b/GFramework.SourceGenerators.Tests/Config/SchemaConfigGeneratorTests.cs @@ -450,9 +450,14 @@ public class SchemaConfigGeneratorTests Assert.Multiple(() => { Assert.That(catalogSource, Does.Contain("public static class GeneratedConfigCatalog")); + Assert.That(catalogSource, Does.Contain("public sealed class GeneratedConfigRegistrationOptions")); Assert.That(catalogSource, Does.Contain("public static class GeneratedConfigRegistrationExtensions")); - Assert.That(catalogSource, Does.Contain("loader.RegisterItemTable();")); - Assert.That(catalogSource, Does.Contain("loader.RegisterMonsterTable();")); + Assert.That(catalogSource, Does.Contain("public global::System.Collections.Generic.IEqualityComparer? ItemComparer { get; init; }")); + Assert.That(catalogSource, Does.Contain("public global::System.Collections.Generic.IEqualityComparer? MonsterComparer { get; init; }")); + Assert.That(catalogSource, Does.Contain("return RegisterAllGeneratedConfigTables(loader, options: null);")); + Assert.That(catalogSource, Does.Contain("GeneratedConfigRegistrationOptions? options")); + Assert.That(catalogSource, Does.Contain("loader.RegisterItemTable(options.ItemComparer);")); + Assert.That(catalogSource, Does.Contain("loader.RegisterMonsterTable(options.MonsterComparer);")); Assert.That(catalogSource, Does.Contain("ItemConfigBindings.Metadata.TableName")); Assert.That(catalogSource, Does.Contain("MonsterConfigBindings.Metadata.TableName")); Assert.That(catalogSource, Does.Contain("public static bool TryGetByTableName(string tableName, out TableMetadata metadata)")); diff --git a/GFramework.SourceGenerators.Tests/Config/snapshots/SchemaConfigGenerator/GeneratedConfigCatalog.g.txt b/GFramework.SourceGenerators.Tests/Config/snapshots/SchemaConfigGenerator/GeneratedConfigCatalog.g.txt index d62e1889..66fc5c07 100644 --- a/GFramework.SourceGenerators.Tests/Config/snapshots/SchemaConfigGenerator/GeneratedConfigCatalog.g.txt +++ b/GFramework.SourceGenerators.Tests/Config/snapshots/SchemaConfigGenerator/GeneratedConfigCatalog.g.txt @@ -90,6 +90,17 @@ public static class GeneratedConfigCatalog } } +/// +/// Captures optional per-table registration overrides for the generated aggregate registration entry point. +/// +public sealed class GeneratedConfigRegistrationOptions +{ + /// + /// Gets or sets the optional key comparer forwarded to MonsterConfigBindings.RegisterMonsterTable(global::GFramework.Game.Config.YamlConfigLoader, global::System.Collections.Generic.IEqualityComparer?) when aggregate registration runs. + /// + public global::System.Collections.Generic.IEqualityComparer? MonsterComparer { get; init; } +} + /// /// Provides a single extension method that registers every generated config table discovered in the current consumer project. /// @@ -109,7 +120,28 @@ public static class GeneratedConfigRegistrationExtensions throw new global::System.ArgumentNullException(nameof(loader)); } - loader.RegisterMonsterTable(); + return RegisterAllGeneratedConfigTables(loader, options: null); + } + + /// + /// Registers all generated config tables while preserving optional per-table overrides such as custom key comparers. + /// + /// Target YAML config loader. + /// Optional per-table overrides for aggregate registration; when null, all tables use their default comparer behavior. + /// The same loader instance after all generated table registrations have been applied. + /// When is null. + public static global::GFramework.Game.Config.YamlConfigLoader RegisterAllGeneratedConfigTables( + this global::GFramework.Game.Config.YamlConfigLoader loader, + GeneratedConfigRegistrationOptions? options) + { + if (loader is null) + { + throw new global::System.ArgumentNullException(nameof(loader)); + } + + options ??= new GeneratedConfigRegistrationOptions(); + + loader.RegisterMonsterTable(options.MonsterComparer); return loader; } -} \ No newline at end of file +} diff --git a/GFramework.SourceGenerators/Config/SchemaConfigGenerator.cs b/GFramework.SourceGenerators/Config/SchemaConfigGenerator.cs index 12af7bbd..880e628a 100644 --- a/GFramework.SourceGenerators/Config/SchemaConfigGenerator.cs +++ b/GFramework.SourceGenerators/Config/SchemaConfigGenerator.cs @@ -1082,6 +1082,31 @@ public sealed class SchemaConfigGenerator : IIncrementalGenerator builder.AppendLine(" metadata = default;"); builder.AppendLine(" return false;"); builder.AppendLine(" }"); + builder.AppendLine("}"); + builder.AppendLine(); + builder.AppendLine("/// "); + builder.AppendLine( + "/// Captures optional per-table registration overrides for the generated aggregate registration entry point."); + builder.AppendLine("/// "); + builder.AppendLine("public sealed class GeneratedConfigRegistrationOptions"); + builder.AppendLine("{"); + + for (var index = 0; index < schemas.Count; index++) + { + var schema = schemas[index]; + builder.AppendLine(" /// "); + builder.AppendLine( + $" /// Gets or sets the optional key comparer forwarded to {schema.EntityName}ConfigBindings.Register{schema.EntityName}Table(global::GFramework.Game.Config.YamlConfigLoader, global::System.Collections.Generic.IEqualityComparer<{schema.KeyClrType}>?) when aggregate registration runs."); + builder.AppendLine(" /// "); + builder.AppendLine( + $" public global::System.Collections.Generic.IEqualityComparer<{schema.KeyClrType}>? {schema.EntityName}Comparer {{ get; init; }}"); + + if (index < schemas.Count - 1) + { + builder.AppendLine(); + } + } + builder.AppendLine("}"); builder.AppendLine(); builder.AppendLine("/// "); @@ -1107,10 +1132,35 @@ public sealed class SchemaConfigGenerator : IIncrementalGenerator builder.AppendLine(" throw new global::System.ArgumentNullException(nameof(loader));"); builder.AppendLine(" }"); builder.AppendLine(); + builder.AppendLine(" return RegisterAllGeneratedConfigTables(loader, options: null);"); + builder.AppendLine(" }"); + builder.AppendLine(); + builder.AppendLine(" /// "); + builder.AppendLine( + " /// Registers all generated config tables while preserving optional per-table overrides such as custom key comparers."); + builder.AppendLine(" /// "); + builder.AppendLine(" /// Target YAML config loader."); + builder.AppendLine( + " /// Optional per-table overrides for aggregate registration; when null, all tables use their default comparer behavior."); + builder.AppendLine(" /// The same loader instance after all generated table registrations have been applied."); + builder.AppendLine( + " /// When is null."); + builder.AppendLine( + " public static global::GFramework.Game.Config.YamlConfigLoader RegisterAllGeneratedConfigTables("); + builder.AppendLine(" this global::GFramework.Game.Config.YamlConfigLoader loader,"); + builder.AppendLine(" GeneratedConfigRegistrationOptions? options)"); + builder.AppendLine(" {"); + builder.AppendLine(" if (loader is null)"); + builder.AppendLine(" {"); + builder.AppendLine(" throw new global::System.ArgumentNullException(nameof(loader));"); + builder.AppendLine(" }"); + builder.AppendLine(); + builder.AppendLine(" options ??= new GeneratedConfigRegistrationOptions();"); + builder.AppendLine(); foreach (var schema in schemas) { - builder.AppendLine($" loader.Register{schema.EntityName}Table();"); + builder.AppendLine($" loader.Register{schema.EntityName}Table(options.{schema.EntityName}Comparer);"); } builder.AppendLine(" return loader;"); diff --git a/docs/zh-CN/game/config-system.md b/docs/zh-CN/game/config-system.md index 30446960..b8595c55 100644 --- a/docs/zh-CN/game/config-system.md +++ b/docs/zh-CN/game/config-system.md @@ -454,6 +454,23 @@ if (GeneratedConfigCatalog.TryGetByTableName("monster", out var metadata)) } ``` +如果你需要为某些表保留自定义 key comparer,也可以继续走聚合注册入口,而不是被迫退回逐表手写: + +```csharp +var loader = new YamlConfigLoader("config-root") + .RegisterAllGeneratedConfigTables( + new GeneratedConfigRegistrationOptions + { + ItemComparer = StringComparer.OrdinalIgnoreCase + }); +``` + +这里的规则是: + +- 未显式配置 comparer 的表,仍然使用各自 `Register{Entity}Table()` 的默认行为 +- 需要自定义 comparer 的表,可以通过 `GeneratedConfigRegistrationOptions` 按表覆盖 +- 如果项目希望继续完全手写某张表的注册流程,逐表 `Register*Table(...)` 入口仍然保留,作为兼容逃生通道 + 如果你需要自定义目录、表名或 key selector,仍然可以直接调用 `YamlConfigLoader.RegisterTable(...)` 原始重载。 如果你希望把 schema 路径、比较器以及未来扩展开关集中到一个对象里,推荐改用选项对象入口: