mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-05-06 16:16:44 +08:00
docs(game): 添加游戏内容配置系统详细文档
- 新增游戏内容配置系统完整文档,涵盖 YAML 配置、JSON Schema 结构、目录组织等 - 添加 Schema 示例和 YAML 示例,展示怪物和物品配置的具体格式 - 提供推荐接入模板,包括目录结构、csproj 配置、启动帮助器和运行时读取模板 - 完善运行时接入说明,介绍配置加载、注册和访问的完整流程 - 增加热重载功能说明,提供开发期自动刷新配置的实现方式 - 补充 VS Code 工具支持说明,提供配置浏览、编辑和校验功能介绍 - 添加生成器接入约定和当前限制说明,确保开发者了解系统边界 - 生成项目级配置目录和注册扩展方法,简化多表注册流程
This commit is contained in:
parent
015cac6eb5
commit
5190ba2359
@ -111,6 +111,42 @@ public class GeneratedConfigConsumerIntegrationTests
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证项目级生成目录既能按配置域枚举元数据,也能直接复用聚合注册筛选规则产出启动诊断视图。
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void GeneratedConfigCatalog_Should_Expose_Domain_And_Registration_Diagnostic_Views()
|
||||
{
|
||||
Assert.That(GeneratedConfigCatalog.TryGetByTableName("monster", out var monsterMetadata), Is.True);
|
||||
Assert.That(GeneratedConfigCatalog.TryGetByTableName("item", out var itemMetadata), Is.True);
|
||||
|
||||
var monsterDomainTables = GeneratedConfigCatalog.GetTablesInConfigDomain(MonsterConfigBindings.ConfigDomain);
|
||||
var missingDomainTables = GeneratedConfigCatalog.GetTablesInConfigDomain("missing");
|
||||
var itemOnlyRegistrationTables = GeneratedConfigCatalog.GetTablesForRegistration(
|
||||
new GeneratedConfigRegistrationOptions
|
||||
{
|
||||
IncludedTableNames = new[] { ItemConfigBindings.TableName }
|
||||
});
|
||||
var monsterOnlyOptions = new GeneratedConfigRegistrationOptions
|
||||
{
|
||||
IncludedConfigDomains = new[] { MonsterConfigBindings.ConfigDomain }
|
||||
};
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(monsterDomainTables.Select(static metadata => metadata.TableName),
|
||||
Is.EqualTo(new[] { MonsterConfigBindings.TableName }));
|
||||
Assert.That(missingDomainTables, Is.Empty);
|
||||
Assert.That(itemOnlyRegistrationTables.Select(static metadata => metadata.TableName),
|
||||
Is.EqualTo(new[] { ItemConfigBindings.TableName }));
|
||||
Assert.That(GeneratedConfigCatalog.GetTablesForRegistration().Select(static metadata => metadata.TableName),
|
||||
Is.SupersetOf(new[] { ItemConfigBindings.TableName, MonsterConfigBindings.TableName }));
|
||||
Assert.That(GeneratedConfigCatalog.MatchesRegistrationOptions(monsterMetadata, monsterOnlyOptions), Is.True);
|
||||
Assert.That(GeneratedConfigCatalog.MatchesRegistrationOptions(itemMetadata, monsterOnlyOptions), Is.False);
|
||||
Assert.That(GeneratedConfigCatalog.MatchesRegistrationOptions(monsterMetadata, options: null), Is.True);
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证聚合注册入口可以通过生成配置域、表名集合和自定义谓词收敛多表项目的启动粒度。
|
||||
/// </summary>
|
||||
|
||||
@ -877,17 +877,26 @@ public class SchemaConfigGeneratorTests
|
||||
"public global::System.Collections.Generic.IEqualityComparer<int>? 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(effectiveOptions.ItemComparer);"));
|
||||
Assert.That(catalogSource,
|
||||
Does.Contain("if (ShouldRegisterTable(GeneratedConfigCatalog.Tables[0], options))"));
|
||||
Assert.That(catalogSource, Does.Contain("loader.RegisterItemTable(options.ItemComparer);"));
|
||||
Assert.That(catalogSource,
|
||||
Does.Contain("if (ShouldRegisterTable(GeneratedConfigCatalog.Tables[1], options))"));
|
||||
Assert.That(catalogSource, Does.Contain("loader.RegisterMonsterTable(options.MonsterComparer);"));
|
||||
Does.Contain(
|
||||
"if (GeneratedConfigCatalog.MatchesRegistrationOptions(GeneratedConfigCatalog.Tables[1], effectiveOptions))"));
|
||||
Assert.That(catalogSource, Does.Contain("loader.RegisterMonsterTable(effectiveOptions.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)"));
|
||||
Assert.That(catalogSource, Does.Contain("private static bool ShouldRegisterTable("));
|
||||
Assert.That(catalogSource,
|
||||
Does.Contain(
|
||||
"public static global::System.Collections.Generic.IReadOnlyList<TableMetadata> GetTablesInConfigDomain(string configDomain)"));
|
||||
Assert.That(catalogSource,
|
||||
Does.Contain(
|
||||
"public static global::System.Collections.Generic.IReadOnlyList<TableMetadata> GetTablesForRegistration(GeneratedConfigRegistrationOptions? options = null)"));
|
||||
Assert.That(catalogSource,
|
||||
Does.Contain("public static bool MatchesRegistrationOptions("));
|
||||
Assert.That(catalogSource,
|
||||
Does.Contain(
|
||||
"if (GeneratedConfigCatalog.MatchesRegistrationOptions(GeneratedConfigCatalog.Tables[0], effectiveOptions))"));
|
||||
Assert.That(catalogSource, Does.Contain("private static bool MatchesOptionalAllowList("));
|
||||
});
|
||||
}
|
||||
|
||||
@ -88,6 +88,106 @@ public static class GeneratedConfigCatalog
|
||||
metadata = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resolves the generated table metadata entries that belong to the specified logical config domain.
|
||||
/// </summary>
|
||||
/// <param name="configDomain">Logical config domain derived from the schema base name.</param>
|
||||
/// <returns>A deterministic metadata snapshot for the requested config domain, or an empty list when no generated table belongs to that domain.</returns>
|
||||
/// <exception cref="global::System.ArgumentNullException">When <paramref name="configDomain"/> is null.</exception>
|
||||
public static global::System.Collections.Generic.IReadOnlyList<TableMetadata> GetTablesInConfigDomain(string configDomain)
|
||||
{
|
||||
if (configDomain is null)
|
||||
{
|
||||
throw new global::System.ArgumentNullException(nameof(configDomain));
|
||||
}
|
||||
|
||||
var matchedTables = new global::System.Collections.Generic.List<TableMetadata>();
|
||||
foreach (var metadata in Tables)
|
||||
{
|
||||
if (string.Equals(metadata.ConfigDomain, configDomain, global::System.StringComparison.Ordinal))
|
||||
{
|
||||
matchedTables.Add(metadata);
|
||||
}
|
||||
}
|
||||
|
||||
return matchedTables.Count == 0 ? global::System.Array.Empty<TableMetadata>() : matchedTables.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resolves the generated table metadata entries that aggregate registration would currently include under the supplied filters.
|
||||
/// </summary>
|
||||
/// <param name="options">Optional aggregate registration filters and comparer overrides. When null, every generated table remains eligible.</param>
|
||||
/// <returns>A deterministic metadata snapshot in the same order used by <see cref="GeneratedConfigRegistrationExtensions.RegisterAllGeneratedConfigTables(global::GFramework.Game.Config.YamlConfigLoader, GeneratedConfigRegistrationOptions?)" />.</returns>
|
||||
public static global::System.Collections.Generic.IReadOnlyList<TableMetadata> GetTablesForRegistration(GeneratedConfigRegistrationOptions? options = null)
|
||||
{
|
||||
var matchedTables = new global::System.Collections.Generic.List<TableMetadata>();
|
||||
foreach (var metadata in Tables)
|
||||
{
|
||||
if (MatchesRegistrationOptions(metadata, options))
|
||||
{
|
||||
matchedTables.Add(metadata);
|
||||
}
|
||||
}
|
||||
|
||||
return matchedTables.Count == 0 ? global::System.Array.Empty<TableMetadata>() : matchedTables.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Evaluates whether one generated table metadata entry remains eligible under the supplied aggregate registration filters.
|
||||
/// </summary>
|
||||
/// <param name="metadata">Generated table metadata under evaluation.</param>
|
||||
/// <param name="options">Optional aggregate registration filters and comparer overrides. When null, the metadata entry is always eligible.</param>
|
||||
/// <returns><see langword="true" /> when the generated table would be included by aggregate registration; otherwise <see langword="false" />.</returns>
|
||||
public static bool MatchesRegistrationOptions(
|
||||
TableMetadata metadata,
|
||||
GeneratedConfigRegistrationOptions? options)
|
||||
{
|
||||
if (options is null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// Apply cheap generated allow-lists before invoking the optional caller predicate so startup diagnostics stay aligned with real registration.
|
||||
if (!MatchesOptionalAllowList(options.IncludedConfigDomains, metadata.ConfigDomain))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!MatchesOptionalAllowList(options.IncludedTableNames, metadata.TableName))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return options.TableFilter?.Invoke(metadata) ?? true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Treats a null or empty allow-list as an unrestricted match, and otherwise performs ordinal string comparison against the generated metadata value.
|
||||
/// </summary>
|
||||
/// <param name="allowedValues">Optional caller-supplied allow-list.</param>
|
||||
/// <param name="candidate">Generated metadata value being evaluated.</param>
|
||||
/// <returns><see langword="true" /> when the value should remain eligible for registration; otherwise <see langword="false" />.</returns>
|
||||
private static bool MatchesOptionalAllowList(
|
||||
global::System.Collections.Generic.IReadOnlyCollection<string>? allowedValues,
|
||||
string candidate)
|
||||
{
|
||||
if (allowedValues is null || allowedValues.Count == 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
foreach (var allowedValue in allowedValues)
|
||||
{
|
||||
if (allowedValue is not null &&
|
||||
string.Equals(allowedValue, candidate, global::System.StringComparison.Ordinal))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -154,64 +254,13 @@ public static class GeneratedConfigRegistrationExtensions
|
||||
throw new global::System.ArgumentNullException(nameof(loader));
|
||||
}
|
||||
|
||||
options ??= new GeneratedConfigRegistrationOptions();
|
||||
var effectiveOptions = options ?? new GeneratedConfigRegistrationOptions();
|
||||
|
||||
if (ShouldRegisterTable(GeneratedConfigCatalog.Tables[0], options))
|
||||
if (GeneratedConfigCatalog.MatchesRegistrationOptions(GeneratedConfigCatalog.Tables[0], effectiveOptions))
|
||||
{
|
||||
loader.RegisterMonsterTable(options.MonsterComparer);
|
||||
loader.RegisterMonsterTable(effectiveOptions.MonsterComparer);
|
||||
}
|
||||
|
||||
return loader;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies the generated registration filters in a deterministic order so bootstrap code can narrow aggregate registration without hand-writing per-table calls.
|
||||
/// </summary>
|
||||
/// <param name="metadata">Generated table metadata under consideration.</param>
|
||||
/// <param name="options">Aggregate registration options supplied by the caller.</param>
|
||||
/// <returns><see langword="true" /> when the generated table should be registered; otherwise <see langword="false" />.</returns>
|
||||
private static bool ShouldRegisterTable(
|
||||
GeneratedConfigCatalog.TableMetadata metadata,
|
||||
GeneratedConfigRegistrationOptions options)
|
||||
{
|
||||
// Apply cheap generated allow-lists before invoking the optional caller predicate so startup filtering stays predictable.
|
||||
if (!MatchesOptionalAllowList(options.IncludedConfigDomains, metadata.ConfigDomain))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!MatchesOptionalAllowList(options.IncludedTableNames, metadata.TableName))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return options.TableFilter?.Invoke(metadata) ?? true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Treats a null or empty allow-list as an unrestricted match, and otherwise performs ordinal string comparison against the generated metadata value.
|
||||
/// </summary>
|
||||
/// <param name="allowedValues">Optional caller-supplied allow-list.</param>
|
||||
/// <param name="candidate">Generated metadata value being evaluated.</param>
|
||||
/// <returns><see langword="true" /> when the value should remain eligible for registration; otherwise <see langword="false" />.</returns>
|
||||
private static bool MatchesOptionalAllowList(
|
||||
global::System.Collections.Generic.IReadOnlyCollection<string>? allowedValues,
|
||||
string candidate)
|
||||
{
|
||||
if (allowedValues is null || allowedValues.Count == 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
foreach (var allowedValue in allowedValues)
|
||||
{
|
||||
if (allowedValue is not null &&
|
||||
string.Equals(allowedValue, candidate, global::System.StringComparison.Ordinal))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1240,6 +1240,125 @@ public sealed class SchemaConfigGenerator : IIncrementalGenerator
|
||||
builder.AppendLine(" metadata = default;");
|
||||
builder.AppendLine(" return false;");
|
||||
builder.AppendLine(" }");
|
||||
builder.AppendLine();
|
||||
builder.AppendLine(" /// <summary>");
|
||||
builder.AppendLine(
|
||||
" /// Resolves the generated table metadata entries that belong to the specified logical config domain.");
|
||||
builder.AppendLine(" /// </summary>");
|
||||
builder.AppendLine(" /// <param name=\"configDomain\">Logical config domain derived from the schema base name.</param>");
|
||||
builder.AppendLine(
|
||||
" /// <returns>A deterministic metadata snapshot for the requested config domain, or an empty list when no generated table belongs to that domain.</returns>");
|
||||
builder.AppendLine(
|
||||
" /// <exception cref=\"global::System.ArgumentNullException\">When <paramref name=\"configDomain\"/> is null.</exception>");
|
||||
builder.AppendLine(
|
||||
" public static global::System.Collections.Generic.IReadOnlyList<TableMetadata> GetTablesInConfigDomain(string configDomain)");
|
||||
builder.AppendLine(" {");
|
||||
builder.AppendLine(" if (configDomain is null)");
|
||||
builder.AppendLine(" {");
|
||||
builder.AppendLine(" throw new global::System.ArgumentNullException(nameof(configDomain));");
|
||||
builder.AppendLine(" }");
|
||||
builder.AppendLine();
|
||||
builder.AppendLine(" var matchedTables = new global::System.Collections.Generic.List<TableMetadata>();");
|
||||
builder.AppendLine(" foreach (var metadata in Tables)");
|
||||
builder.AppendLine(" {");
|
||||
builder.AppendLine(
|
||||
" if (string.Equals(metadata.ConfigDomain, configDomain, global::System.StringComparison.Ordinal))");
|
||||
builder.AppendLine(" {");
|
||||
builder.AppendLine(" matchedTables.Add(metadata);");
|
||||
builder.AppendLine(" }");
|
||||
builder.AppendLine(" }");
|
||||
builder.AppendLine();
|
||||
builder.AppendLine(
|
||||
" return matchedTables.Count == 0 ? global::System.Array.Empty<TableMetadata>() : matchedTables.ToArray();");
|
||||
builder.AppendLine(" }");
|
||||
builder.AppendLine();
|
||||
builder.AppendLine(" /// <summary>");
|
||||
builder.AppendLine(
|
||||
" /// Resolves the generated table metadata entries that aggregate registration would currently include under the supplied filters.");
|
||||
builder.AppendLine(" /// </summary>");
|
||||
builder.AppendLine(
|
||||
" /// <param name=\"options\">Optional aggregate registration filters and comparer overrides. When null, every generated table remains eligible.</param>");
|
||||
builder.AppendLine(
|
||||
" /// <returns>A deterministic metadata snapshot in the same order used by <see cref=\"GeneratedConfigRegistrationExtensions.RegisterAllGeneratedConfigTables(global::GFramework.Game.Config.YamlConfigLoader, GeneratedConfigRegistrationOptions?)\" />.</returns>");
|
||||
builder.AppendLine(
|
||||
" public static global::System.Collections.Generic.IReadOnlyList<TableMetadata> GetTablesForRegistration(GeneratedConfigRegistrationOptions? options = null)");
|
||||
builder.AppendLine(" {");
|
||||
builder.AppendLine(" var matchedTables = new global::System.Collections.Generic.List<TableMetadata>();");
|
||||
builder.AppendLine(" foreach (var metadata in Tables)");
|
||||
builder.AppendLine(" {");
|
||||
builder.AppendLine(" if (MatchesRegistrationOptions(metadata, options))");
|
||||
builder.AppendLine(" {");
|
||||
builder.AppendLine(" matchedTables.Add(metadata);");
|
||||
builder.AppendLine(" }");
|
||||
builder.AppendLine(" }");
|
||||
builder.AppendLine();
|
||||
builder.AppendLine(
|
||||
" return matchedTables.Count == 0 ? global::System.Array.Empty<TableMetadata>() : matchedTables.ToArray();");
|
||||
builder.AppendLine(" }");
|
||||
builder.AppendLine();
|
||||
builder.AppendLine(" /// <summary>");
|
||||
builder.AppendLine(
|
||||
" /// Evaluates whether one generated table metadata entry remains eligible under the supplied aggregate registration filters.");
|
||||
builder.AppendLine(" /// </summary>");
|
||||
builder.AppendLine(" /// <param name=\"metadata\">Generated table metadata under evaluation.</param>");
|
||||
builder.AppendLine(
|
||||
" /// <param name=\"options\">Optional aggregate registration filters and comparer overrides. When null, the metadata entry is always eligible.</param>");
|
||||
builder.AppendLine(
|
||||
" /// <returns><see langword=\"true\" /> when the generated table would be included by aggregate registration; otherwise <see langword=\"false\" />.</returns>");
|
||||
builder.AppendLine(" public static bool MatchesRegistrationOptions(");
|
||||
builder.AppendLine(" TableMetadata metadata,");
|
||||
builder.AppendLine(" GeneratedConfigRegistrationOptions? options)");
|
||||
builder.AppendLine(" {");
|
||||
builder.AppendLine(" if (options is null)");
|
||||
builder.AppendLine(" {");
|
||||
builder.AppendLine(" return true;");
|
||||
builder.AppendLine(" }");
|
||||
builder.AppendLine();
|
||||
builder.AppendLine(
|
||||
" // Apply cheap generated allow-lists before invoking the optional caller predicate so startup diagnostics stay aligned with real registration.");
|
||||
builder.AppendLine(
|
||||
" if (!MatchesOptionalAllowList(options.IncludedConfigDomains, metadata.ConfigDomain))");
|
||||
builder.AppendLine(" {");
|
||||
builder.AppendLine(" return false;");
|
||||
builder.AppendLine(" }");
|
||||
builder.AppendLine();
|
||||
builder.AppendLine(" if (!MatchesOptionalAllowList(options.IncludedTableNames, metadata.TableName))");
|
||||
builder.AppendLine(" {");
|
||||
builder.AppendLine(" return false;");
|
||||
builder.AppendLine(" }");
|
||||
builder.AppendLine();
|
||||
builder.AppendLine(" return options.TableFilter?.Invoke(metadata) ?? true;");
|
||||
builder.AppendLine(" }");
|
||||
builder.AppendLine();
|
||||
builder.AppendLine(" /// <summary>");
|
||||
builder.AppendLine(
|
||||
" /// Treats a null or empty allow-list as an unrestricted match, and otherwise performs ordinal string comparison against the generated metadata value.");
|
||||
builder.AppendLine(" /// </summary>");
|
||||
builder.AppendLine(" /// <param name=\"allowedValues\">Optional caller-supplied allow-list.</param>");
|
||||
builder.AppendLine(" /// <param name=\"candidate\">Generated metadata value being evaluated.</param>");
|
||||
builder.AppendLine(
|
||||
" /// <returns><see langword=\"true\" /> when the value should remain eligible for registration; otherwise <see langword=\"false\" />.</returns>");
|
||||
builder.AppendLine(" private static bool MatchesOptionalAllowList(");
|
||||
builder.AppendLine(" global::System.Collections.Generic.IReadOnlyCollection<string>? allowedValues,");
|
||||
builder.AppendLine(" string candidate)");
|
||||
builder.AppendLine(" {");
|
||||
builder.AppendLine(" if (allowedValues is null || allowedValues.Count == 0)");
|
||||
builder.AppendLine(" {");
|
||||
builder.AppendLine(" return true;");
|
||||
builder.AppendLine(" }");
|
||||
builder.AppendLine();
|
||||
builder.AppendLine(" foreach (var allowedValue in allowedValues)");
|
||||
builder.AppendLine(" {");
|
||||
builder.AppendLine(" if (allowedValue is not null &&");
|
||||
builder.AppendLine(
|
||||
" string.Equals(allowedValue, candidate, global::System.StringComparison.Ordinal))");
|
||||
builder.AppendLine(" {");
|
||||
builder.AppendLine(" return true;");
|
||||
builder.AppendLine(" }");
|
||||
builder.AppendLine(" }");
|
||||
builder.AppendLine();
|
||||
builder.AppendLine(" return false;");
|
||||
builder.AppendLine(" }");
|
||||
builder.AppendLine("}");
|
||||
builder.AppendLine();
|
||||
builder.AppendLine("/// <summary>");
|
||||
@ -1340,83 +1459,23 @@ public sealed class SchemaConfigGenerator : IIncrementalGenerator
|
||||
builder.AppendLine(" throw new global::System.ArgumentNullException(nameof(loader));");
|
||||
builder.AppendLine(" }");
|
||||
builder.AppendLine();
|
||||
builder.AppendLine(" options ??= new GeneratedConfigRegistrationOptions();");
|
||||
builder.AppendLine(" var effectiveOptions = options ?? new GeneratedConfigRegistrationOptions();");
|
||||
builder.AppendLine();
|
||||
|
||||
for (var index = 0; index < schemas.Count; index++)
|
||||
{
|
||||
var schema = schemas[index];
|
||||
builder.AppendLine(
|
||||
$" if (ShouldRegisterTable(GeneratedConfigCatalog.Tables[{index.ToString(CultureInfo.InvariantCulture)}], options))");
|
||||
$" if (GeneratedConfigCatalog.MatchesRegistrationOptions(GeneratedConfigCatalog.Tables[{index.ToString(CultureInfo.InvariantCulture)}], effectiveOptions))");
|
||||
builder.AppendLine(" {");
|
||||
builder.AppendLine(
|
||||
$" loader.Register{schema.EntityName}Table(options.{schema.EntityName}Comparer);");
|
||||
$" loader.Register{schema.EntityName}Table(effectiveOptions.{schema.EntityName}Comparer);");
|
||||
builder.AppendLine(" }");
|
||||
builder.AppendLine();
|
||||
}
|
||||
|
||||
builder.AppendLine(" return loader;");
|
||||
builder.AppendLine(" }");
|
||||
builder.AppendLine();
|
||||
builder.AppendLine(" /// <summary>");
|
||||
builder.AppendLine(
|
||||
" /// Applies the generated registration filters in a deterministic order so bootstrap code can narrow aggregate registration without hand-writing per-table calls.");
|
||||
builder.AppendLine(" /// </summary>");
|
||||
builder.AppendLine(" /// <param name=\"metadata\">Generated table metadata under consideration.</param>");
|
||||
builder.AppendLine(
|
||||
" /// <param name=\"options\">Aggregate registration options supplied by the caller.</param>");
|
||||
builder.AppendLine(
|
||||
" /// <returns><see langword=\"true\" /> when the generated table should be registered; otherwise <see langword=\"false\" />.</returns>");
|
||||
builder.AppendLine(
|
||||
" private static bool ShouldRegisterTable(");
|
||||
builder.AppendLine(" GeneratedConfigCatalog.TableMetadata metadata,");
|
||||
builder.AppendLine(" GeneratedConfigRegistrationOptions options)");
|
||||
builder.AppendLine(" {");
|
||||
builder.AppendLine(
|
||||
" // Apply cheap generated allow-lists before invoking the optional caller predicate so startup filtering stays predictable.");
|
||||
builder.AppendLine(
|
||||
" if (!MatchesOptionalAllowList(options.IncludedConfigDomains, metadata.ConfigDomain))");
|
||||
builder.AppendLine(" {");
|
||||
builder.AppendLine(" return false;");
|
||||
builder.AppendLine(" }");
|
||||
builder.AppendLine();
|
||||
builder.AppendLine(" if (!MatchesOptionalAllowList(options.IncludedTableNames, metadata.TableName))");
|
||||
builder.AppendLine(" {");
|
||||
builder.AppendLine(" return false;");
|
||||
builder.AppendLine(" }");
|
||||
builder.AppendLine();
|
||||
builder.AppendLine(" return options.TableFilter?.Invoke(metadata) ?? true;");
|
||||
builder.AppendLine(" }");
|
||||
builder.AppendLine();
|
||||
builder.AppendLine(" /// <summary>");
|
||||
builder.AppendLine(
|
||||
" /// Treats a null or empty allow-list as an unrestricted match, and otherwise performs ordinal string comparison against the generated metadata value.");
|
||||
builder.AppendLine(" /// </summary>");
|
||||
builder.AppendLine(" /// <param name=\"allowedValues\">Optional caller-supplied allow-list.</param>");
|
||||
builder.AppendLine(" /// <param name=\"candidate\">Generated metadata value being evaluated.</param>");
|
||||
builder.AppendLine(
|
||||
" /// <returns><see langword=\"true\" /> when the value should remain eligible for registration; otherwise <see langword=\"false\" />.</returns>");
|
||||
builder.AppendLine(" private static bool MatchesOptionalAllowList(");
|
||||
builder.AppendLine(" global::System.Collections.Generic.IReadOnlyCollection<string>? allowedValues,");
|
||||
builder.AppendLine(" string candidate)");
|
||||
builder.AppendLine(" {");
|
||||
builder.AppendLine(" if (allowedValues is null || allowedValues.Count == 0)");
|
||||
builder.AppendLine(" {");
|
||||
builder.AppendLine(" return true;");
|
||||
builder.AppendLine(" }");
|
||||
builder.AppendLine();
|
||||
builder.AppendLine(" foreach (var allowedValue in allowedValues)");
|
||||
builder.AppendLine(" {");
|
||||
builder.AppendLine(" if (allowedValue is not null &&");
|
||||
builder.AppendLine(
|
||||
" string.Equals(allowedValue, candidate, global::System.StringComparison.Ordinal))");
|
||||
builder.AppendLine(" {");
|
||||
builder.AppendLine(" return true;");
|
||||
builder.AppendLine(" }");
|
||||
builder.AppendLine(" }");
|
||||
builder.AppendLine();
|
||||
builder.AppendLine(" return false;");
|
||||
builder.AppendLine(" }");
|
||||
builder.AppendLine("}");
|
||||
return builder.ToString().TrimEnd();
|
||||
}
|
||||
|
||||
@ -547,6 +547,15 @@ if (GeneratedConfigCatalog.TryGetByTableName("monster", out var metadata))
|
||||
}
|
||||
```
|
||||
|
||||
如果你希望先按配置域聚合出一组候选表,再决定是否进入启动链路,也可以直接查询目录:
|
||||
|
||||
```csharp
|
||||
foreach (var metadata in GeneratedConfigCatalog.GetTablesInConfigDomain(MonsterConfigBindings.ConfigDomain))
|
||||
{
|
||||
Console.WriteLine(metadata.TableName);
|
||||
}
|
||||
```
|
||||
|
||||
如果你需要为某些表保留自定义 key comparer,也可以继续走聚合注册入口,而不是被迫退回逐表手写:
|
||||
|
||||
```csharp
|
||||
@ -581,10 +590,28 @@ var loader = new YamlConfigLoader("config-root")
|
||||
});
|
||||
```
|
||||
|
||||
如果你想在真正调用 `RegisterAllGeneratedConfigTables(...)` 之前,先把“这次会注册哪些表”打到日志里,推荐直接复用同一份 options 做启动诊断,而不是手写一套平行筛选逻辑:
|
||||
|
||||
```csharp
|
||||
var registrationOptions = new GeneratedConfigRegistrationOptions
|
||||
{
|
||||
IncludedConfigDomains = new[] { MonsterConfigBindings.ConfigDomain }
|
||||
};
|
||||
|
||||
foreach (var metadata in GeneratedConfigCatalog.GetTablesForRegistration(registrationOptions))
|
||||
{
|
||||
Console.WriteLine($"Registering {metadata.TableName}");
|
||||
}
|
||||
|
||||
var loader = new YamlConfigLoader("config-root")
|
||||
.RegisterAllGeneratedConfigTables(registrationOptions);
|
||||
```
|
||||
|
||||
这里的规则是:
|
||||
|
||||
- `IncludedConfigDomains` 与 `IncludedTableNames` 都按 `StringComparison.Ordinal` 做白名单匹配;传 `null` 或空集合表示“不限制”
|
||||
- `TableFilter` 会在上述白名单通过后执行,适合继续按 schema 路径、配置目录等元数据做更细的启动裁剪
|
||||
- `GeneratedConfigCatalog.GetTablesForRegistration(...)` 与 `RegisterAllGeneratedConfigTables(...)` 复用同一套筛选规则,便于在启动日志和真实注册之间保持一致
|
||||
- 未显式配置 comparer 的表,仍然使用各自 `Register{Entity}Table()` 的默认行为
|
||||
- 需要自定义 comparer 的表,可以通过 `GeneratedConfigRegistrationOptions` 按表覆盖
|
||||
- 当前 `ConfigDomain` 约定仍与生成表名保持一致,但建议优先引用 `*ConfigBindings.ConfigDomain`,为后续更细的分组策略保留稳定入口
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user