diff --git a/GFramework.Game.Tests/Config/GeneratedConfigConsumerIntegrationTests.cs b/GFramework.Game.Tests/Config/GeneratedConfigConsumerIntegrationTests.cs index 0b071777..b44a1cd5 100644 --- a/GFramework.Game.Tests/Config/GeneratedConfigConsumerIntegrationTests.cs +++ b/GFramework.Game.Tests/Config/GeneratedConfigConsumerIntegrationTests.cs @@ -127,10 +127,21 @@ public class GeneratedConfigConsumerIntegrationTests { IncludedTableNames = new[] { ItemConfigBindings.TableName } }); + var predicateOnlyRegistrationTables = GeneratedConfigCatalog.GetTablesForRegistration( + new GeneratedConfigRegistrationOptions + { + TableFilter = static metadata => + string.Equals(metadata.TableName, MonsterConfigBindings.TableName, StringComparison.Ordinal) + }); var monsterOnlyOptions = new GeneratedConfigRegistrationOptions { IncludedConfigDomains = new[] { MonsterConfigBindings.ConfigDomain } }; + var predicateOnlyOptions = new GeneratedConfigRegistrationOptions + { + TableFilter = static metadata => + string.Equals(metadata.TableName, MonsterConfigBindings.TableName, StringComparison.Ordinal) + }; Assert.Multiple(() => { @@ -139,10 +150,14 @@ public class GeneratedConfigConsumerIntegrationTests Assert.That(missingDomainTables, Is.Empty); Assert.That(itemOnlyRegistrationTables.Select(static metadata => metadata.TableName), Is.EqualTo(new[] { ItemConfigBindings.TableName })); + Assert.That(predicateOnlyRegistrationTables.Select(static metadata => metadata.TableName), + Is.EqualTo(new[] { MonsterConfigBindings.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, predicateOnlyOptions), Is.True); + Assert.That(GeneratedConfigCatalog.MatchesRegistrationOptions(itemMetadata, predicateOnlyOptions), Is.False); Assert.That(GeneratedConfigCatalog.MatchesRegistrationOptions(monsterMetadata, options: null), Is.True); }); } diff --git a/docs/zh-CN/game/config-system.md b/docs/zh-CN/game/config-system.md index 503693d9..cf6b7eda 100644 --- a/docs/zh-CN/game/config-system.md +++ b/docs/zh-CN/game/config-system.md @@ -404,7 +404,7 @@ if (monsterTable.TryFindFirstByFaction("dungeon", out var firstDungeonMonster)) ### Architecture 推荐接入模板 -如果你的项目已经基于 `GFramework.Core.Architectures.Architecture` 组织初始化流程,推荐把配置系统接到 `OnInitialize()` 阶段,并把 `GameConfigBootstrap.Registry` 注册为 utility: +如果你的项目已经基于 `GFramework.Core.Architectures.Architecture` 组织初始化流程,并且当前宿主没有提供更上层的异步启动入口,可以把配置系统接到 `OnInitialize()` 阶段,并把 `GameConfigBootstrap.Registry` 注册为 utility: ```csharp using GFramework.Core.Architectures; @@ -440,6 +440,12 @@ public sealed class GameArchitecture : Architecture } ``` +这个模板里的 `.GetAwaiter().GetResult()` 只是“同步 `OnInitialize()` 到异步配置加载”的桥接写法,不应被理解为无条件推荐: + +- 如果宿主已经提供异步组合根、启动器或更早的异步初始化阶段,优先在那里直接 `await _configBootstrap.InitializeAsync()` +- 只有在 `Architecture` 只暴露同步 `OnInitialize()`,且当前线程不存在需要恢复的 `SynchronizationContext` 时,才适合使用这类同步桥接 +- 在 UI 线程、ASP.NET Classic 等存在活动 `SynchronizationContext` 的环境中,不要直接阻塞等待异步初始化;应把配置初始化前移到异步入口,或改为由不受该上下文约束的启动线程完成 + 初始化完成后,业务组件可以继续通过架构上下文读取 utility,再走生成的强类型入口: ```csharp @@ -590,7 +596,7 @@ var loader = new YamlConfigLoader("config-root") }); ``` -如果你想在真正调用 `RegisterAllGeneratedConfigTables(...)` 之前,先把“这次会注册哪些表”打到日志里,推荐直接复用同一份 options 做启动诊断,而不是手写一套平行筛选逻辑: +如果你想在真正调用 `RegisterAllGeneratedConfigTables(...)` 之前,先把“这次会注册哪些表”输出到日志中,推荐直接复用同一份 options 做启动诊断,而不是手写一套平行筛选逻辑: ```csharp var registrationOptions = new GeneratedConfigRegistrationOptions @@ -610,7 +616,7 @@ var loader = new YamlConfigLoader("config-root") 这里的规则是: - `IncludedConfigDomains` 与 `IncludedTableNames` 都按 `StringComparison.Ordinal` 做白名单匹配;传 `null` 或空集合表示“不限制” -- `TableFilter` 会在上述白名单通过后执行,适合继续按 schema 路径、配置目录等元数据做更细的启动裁剪 +- `TableFilter` 会在上述白名单通过后执行,适合继续按 schema 路径、配置目录等元数据做更细粒度的启动裁剪 - `GeneratedConfigCatalog.GetTablesForRegistration(...)` 与 `RegisterAllGeneratedConfigTables(...)` 复用同一套筛选规则,便于在启动日志和真实注册之间保持一致 - 未显式配置 comparer 的表,仍然使用各自 `Register{Entity}Table()` 的默认行为 - 需要自定义 comparer 的表,可以通过 `GeneratedConfigRegistrationOptions` 按表覆盖