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 路径、比较器以及未来扩展开关集中到一个对象里,推荐改用选项对象入口: