mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-05-14 06:34:30 +08:00
Compare commits
13 Commits
7e45197698
...
1e5ca14620
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1e5ca14620 | ||
|
|
2b70734357 | ||
|
|
56ed66976b | ||
|
|
5aefd77ad0 | ||
|
|
36507bbc52 | ||
|
|
6ff07ad3d9 | ||
|
|
63f563cd49 | ||
|
|
2187f179c3 | ||
|
|
a8447a68a4 | ||
|
|
5b9c879320 | ||
|
|
982249151e | ||
|
|
5b7c555472 | ||
|
|
66395739dc |
@ -40,6 +40,18 @@
|
||||
|
||||
如果 `ai-libs/` 与当前源码或测试冲突,以当前仓库实现为准,并在文档里写明迁移或兼容边界。
|
||||
|
||||
## 公开文档边界
|
||||
|
||||
- `README.md` 与 `docs/**` 面向框架使用者,不面向治理执行者。
|
||||
- 不要把 inventory、覆盖基线、恢复点、批处理阈值、review 线程、待补审计波次等内部治理信息写进公开页面。
|
||||
- XML、README、测试与 `ai-libs/` 证据可以驱动文档决策,但公开页面只能输出读者真正需要的内容:
|
||||
- 模块边界
|
||||
- 最小接入路径
|
||||
- 推荐阅读顺序
|
||||
- 源码 / XML / API 的入口提示
|
||||
- 如果确实需要保留治理基线、盘点结果或后续治理计划,把它写到 `ai-plan/**` 或其他 contributor-only 记录里。
|
||||
- 当页面需要引用 XML 文档时,写“应优先查看哪些类型、命名空间或契约,以及为什么”,不要写覆盖数量、盘点日期或“已覆盖 / 未覆盖”状态。
|
||||
|
||||
## Markdown 规则
|
||||
|
||||
### 泛型与 HTML 转义
|
||||
@ -83,6 +95,12 @@ description: 1-2 句话描述当前页面解决什么问题
|
||||
3. 再补 API reference
|
||||
4. 最后才补教程
|
||||
|
||||
## 用户页检查点
|
||||
|
||||
- 用户读完页面后,应知道怎么采用、该看哪几个入口,而不是知道当前治理批次做到哪一轮。
|
||||
- 如果一段内容删掉日期、数量、基线、治理术语后就失去价值,它大概率不该出现在公开文档里。
|
||||
- 表格优先表达“何时看什么、解决什么问题”,不要表达“当前盘点覆盖到哪里”。
|
||||
|
||||
## 验证清单
|
||||
|
||||
- [ ] frontmatter 正确
|
||||
|
||||
@ -173,6 +173,10 @@ Use this exact priority:
|
||||
|
||||
- Prefer correcting the adoption path over expanding page count.
|
||||
- Do not copy wording from outdated docs just to keep page volume.
|
||||
- Public docs must stay reader-facing. Do not write inventory, coverage baseline, recovery-point, batch-metric, review
|
||||
backlog, or audit-wave wording into `README.md` or `docs/**`.
|
||||
- If XML or audit evidence is relevant, translate it into reader guidance such as “which types to inspect first” or
|
||||
“which entry points define the contract”, instead of exposing counts, dates, or governance status.
|
||||
- Escape generics outside code blocks.
|
||||
- Keep internal links real and current.
|
||||
- Mark code blocks with explicit languages.
|
||||
|
||||
@ -320,6 +320,11 @@ bash scripts/validate-csharp-naming.sh
|
||||
|
||||
- Update the relevant `README.md` or `docs/` page when behavior, setup steps, architecture guidance, or user-facing
|
||||
examples change.
|
||||
- Public documentation under `README.md` and `docs/**` MUST stay reader-facing. Do not publish governance-only content
|
||||
such as inventory tables, coverage baselines, review queues, batch metrics, recovery points, trace summaries, or
|
||||
“this still needs a later audit wave” notes in those user-facing pages.
|
||||
- Governance-only material such as XML audit snapshots, documentation remediation baselines, backlog status, and
|
||||
recovery metadata belongs in `ai-plan/**` or other contributor-only artifacts, not in public docs.
|
||||
- Treat `ai-libs/` as a read-only third-party source reference area.
|
||||
- Code under `ai-libs/**` exists for comparison, tracing, design study, and behavior verification; do not modify it
|
||||
unless the user explicitly asks to sync or update that third-party snapshot.
|
||||
@ -331,6 +336,9 @@ bash scripts/validate-csharp-naming.sh
|
||||
- The main documentation site lives under `docs/`, with Chinese content under `docs/zh-CN/`.
|
||||
- Keep code samples, package names, and command examples aligned with the current repository state.
|
||||
- Prefer documenting behavior and design intent, not only API surface.
|
||||
- When a public page references XML docs or API coverage, convert that evidence into reader-facing guidance: explain
|
||||
which types, namespaces, or entry points readers should inspect and why, instead of exposing audit counts or
|
||||
governance terminology.
|
||||
- When a feature is added, removed, renamed, or substantially refactored, contributors MUST update or create the
|
||||
corresponding user-facing integration documentation in `docs/zh-CN/` in the same change.
|
||||
- For integration-oriented features such as the AI-First config system, documentation MUST cover:
|
||||
|
||||
@ -39,7 +39,7 @@ public class ArchitectureConfigIntegrationTests
|
||||
try
|
||||
{
|
||||
architecture = new ConsumerArchitecture(rootPath);
|
||||
await architecture.InitializeAsync();
|
||||
await architecture.InitializeAsync().ConfigureAwait(false);
|
||||
initialized = true;
|
||||
|
||||
var table = architecture.MonsterTable;
|
||||
@ -63,7 +63,7 @@ public class ArchitectureConfigIntegrationTests
|
||||
{
|
||||
if (architecture is not null && initialized)
|
||||
{
|
||||
await architecture.DestroyAsync();
|
||||
await architecture.DestroyAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
DeleteDirectoryIfExists(rootPath);
|
||||
@ -83,7 +83,7 @@ public class ArchitectureConfigIntegrationTests
|
||||
try
|
||||
{
|
||||
architecture = new ConsumerArchitecture(rootPath);
|
||||
await architecture.InitializeAsync();
|
||||
await architecture.InitializeAsync().ConfigureAwait(false);
|
||||
initialized = true;
|
||||
|
||||
Assert.Multiple(() =>
|
||||
@ -97,7 +97,7 @@ public class ArchitectureConfigIntegrationTests
|
||||
{
|
||||
if (architecture is not null && initialized)
|
||||
{
|
||||
await architecture.DestroyAsync();
|
||||
await architecture.DestroyAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
DeleteDirectoryIfExists(rootPath);
|
||||
@ -119,16 +119,16 @@ public class ArchitectureConfigIntegrationTests
|
||||
var module = CreateModule(rootPath);
|
||||
|
||||
firstArchitecture = new ModuleOnlyArchitecture(module);
|
||||
await firstArchitecture.InitializeAsync();
|
||||
await firstArchitecture.InitializeAsync().ConfigureAwait(false);
|
||||
var wasInitializedBeforeDestroy = module.IsInitialized;
|
||||
await firstArchitecture.DestroyAsync();
|
||||
await firstArchitecture.DestroyAsync().ConfigureAwait(false);
|
||||
firstDestroyed = true;
|
||||
firstArchitecture = null;
|
||||
GameContext.Clear();
|
||||
|
||||
var secondArchitecture = new ModuleOnlyArchitecture(module);
|
||||
var exception =
|
||||
Assert.ThrowsAsync<InvalidOperationException>(async () => await secondArchitecture.InitializeAsync());
|
||||
Assert.ThrowsAsync<InvalidOperationException>(async () => await secondArchitecture.InitializeAsync().ConfigureAwait(false));
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
@ -141,7 +141,7 @@ public class ArchitectureConfigIntegrationTests
|
||||
{
|
||||
if (firstArchitecture is not null && !firstDestroyed)
|
||||
{
|
||||
await firstArchitecture.DestroyAsync();
|
||||
await firstArchitecture.DestroyAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
DeleteDirectoryIfExists(rootPath);
|
||||
@ -203,7 +203,7 @@ public class ArchitectureConfigIntegrationTests
|
||||
var module = CreateModule(rootPath);
|
||||
|
||||
readyArchitecture = new ReadyOnlyArchitecture();
|
||||
await readyArchitecture.InitializeAsync();
|
||||
await readyArchitecture.InitializeAsync().ConfigureAwait(false);
|
||||
readyArchitectureInitialized = true;
|
||||
|
||||
var exception = Assert.Throws<InvalidOperationException>(() => readyArchitecture.InstallModule(module));
|
||||
@ -216,13 +216,13 @@ public class ArchitectureConfigIntegrationTests
|
||||
Assert.That(module.IsInitialized, Is.False);
|
||||
});
|
||||
|
||||
await readyArchitecture.DestroyAsync();
|
||||
await readyArchitecture.DestroyAsync().ConfigureAwait(false);
|
||||
readyArchitectureInitialized = false;
|
||||
readyArchitecture = null;
|
||||
GameContext.Clear();
|
||||
|
||||
retryArchitecture = new ModuleOnlyArchitecture(module);
|
||||
await retryArchitecture.InitializeAsync();
|
||||
await retryArchitecture.InitializeAsync().ConfigureAwait(false);
|
||||
retryArchitectureInitialized = true;
|
||||
|
||||
Assert.Multiple(() =>
|
||||
@ -235,12 +235,12 @@ public class ArchitectureConfigIntegrationTests
|
||||
{
|
||||
if (retryArchitecture is not null && retryArchitectureInitialized)
|
||||
{
|
||||
await retryArchitecture.DestroyAsync();
|
||||
await retryArchitecture.DestroyAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (readyArchitecture is not null && readyArchitectureInitialized)
|
||||
{
|
||||
await readyArchitecture.DestroyAsync();
|
||||
await readyArchitecture.DestroyAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
DeleteDirectoryIfExists(rootPath);
|
||||
|
||||
@ -50,7 +50,7 @@ public class GameConfigBootstrapTests
|
||||
var registry = new ConfigRegistry();
|
||||
using var bootstrap = CreateBootstrap(registry);
|
||||
|
||||
await bootstrap.InitializeAsync();
|
||||
await bootstrap.InitializeAsync().ConfigureAwait(false);
|
||||
|
||||
var monsterTable = registry.GetMonsterTable();
|
||||
|
||||
@ -74,7 +74,7 @@ public class GameConfigBootstrapTests
|
||||
CreateMonsterFiles();
|
||||
|
||||
using var bootstrap = CreateBootstrap();
|
||||
await bootstrap.InitializeAsync();
|
||||
await bootstrap.InitializeAsync().ConfigureAwait(false);
|
||||
|
||||
var reloadTaskSource = new TaskCompletionSource<string>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
bootstrap.StartHotReload(
|
||||
@ -95,7 +95,7 @@ public class GameConfigBootstrapTests
|
||||
faction: dungeon
|
||||
""");
|
||||
|
||||
var tableName = await WaitForTaskWithinAsync(reloadTaskSource.Task, TimeSpan.FromSeconds(5));
|
||||
var tableName = await WaitForTaskWithinAsync(reloadTaskSource.Task, TimeSpan.FromSeconds(5)).ConfigureAwait(false);
|
||||
var monsterTable = bootstrap.Registry.GetMonsterTable();
|
||||
|
||||
Assert.Multiple(() =>
|
||||
@ -163,11 +163,11 @@ public class GameConfigBootstrapTests
|
||||
Is.True,
|
||||
"The first initialization attempt did not reach the guarded lifecycle section.");
|
||||
|
||||
var secondCallerException = Assert.ThrowsAsync<InvalidOperationException>(async () => await bootstrap.InitializeAsync());
|
||||
var secondCallerException = Assert.ThrowsAsync<InvalidOperationException>(async () => await bootstrap.InitializeAsync().ConfigureAwait(false));
|
||||
|
||||
continueInitialization.Set();
|
||||
|
||||
Assert.DoesNotThrowAsync(async () => await firstInitializeTask);
|
||||
Assert.DoesNotThrowAsync(async () => await firstInitializeTask.ConfigureAwait(false));
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
@ -202,7 +202,7 @@ public class GameConfigBootstrapTests
|
||||
})
|
||||
});
|
||||
|
||||
var exception = Assert.ThrowsAsync<ArgumentOutOfRangeException>(async () => await bootstrap.InitializeAsync());
|
||||
var exception = Assert.ThrowsAsync<ArgumentOutOfRangeException>(async () => await bootstrap.InitializeAsync().ConfigureAwait(false));
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
@ -311,12 +311,12 @@ public class GameConfigBootstrapTests
|
||||
/// <returns>任务结果。</returns>
|
||||
private static async Task<T> WaitForTaskWithinAsync<T>(Task<T> task, TimeSpan timeout)
|
||||
{
|
||||
var completedTask = await Task.WhenAny(task, Task.Delay(timeout));
|
||||
var completedTask = await Task.WhenAny(task, Task.Delay(timeout)).ConfigureAwait(false);
|
||||
if (!ReferenceEquals(completedTask, task))
|
||||
{
|
||||
Assert.Fail($"Timed out after {timeout} while waiting for file watcher notification.");
|
||||
}
|
||||
|
||||
return await task;
|
||||
return await task.ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
@ -50,65 +50,12 @@ public class GeneratedConfigConsumerIntegrationTests
|
||||
var loader = new YamlConfigLoader(_rootPath)
|
||||
.RegisterAllGeneratedConfigTables();
|
||||
|
||||
await loader.LoadAsync(registry);
|
||||
await loader.LoadAsync(registry).ConfigureAwait(false);
|
||||
|
||||
var monsterTable = registry.GetMonsterTable();
|
||||
var dungeonMonsters = monsterTable.FindByFaction("dungeon");
|
||||
var itemTable = registry.GetItemTable();
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(
|
||||
GeneratedConfigCatalog.Tables.Select(static metadata => metadata.TableName),
|
||||
Is.SupersetOf(new[] { "item", "monster" }));
|
||||
Assert.That(GeneratedConfigCatalog.TryGetByTableName("item", out var itemCatalogEntry), Is.True);
|
||||
Assert.That(itemCatalogEntry.ConfigDomain, Is.EqualTo("item"));
|
||||
Assert.That(itemCatalogEntry.ConfigRelativePath, Is.EqualTo("item"));
|
||||
Assert.That(itemCatalogEntry.SchemaRelativePath, Is.EqualTo("schemas/item.schema.json"));
|
||||
Assert.That(GeneratedConfigCatalog.TryGetByTableName("monster", out var catalogEntry), Is.True);
|
||||
Assert.That(catalogEntry.ConfigDomain, Is.EqualTo("monster"));
|
||||
Assert.That(catalogEntry.ConfigRelativePath, Is.EqualTo("monster"));
|
||||
Assert.That(catalogEntry.SchemaRelativePath, Is.EqualTo("schemas/monster.schema.json"));
|
||||
Assert.That(ItemConfigBindings.ConfigDomain, Is.EqualTo("item"));
|
||||
Assert.That(ItemConfigBindings.Metadata.TableName, Is.EqualTo("item"));
|
||||
Assert.That(MonsterConfigBindings.ConfigDomain, Is.EqualTo("monster"));
|
||||
Assert.That(MonsterConfigBindings.TableName, Is.EqualTo("monster"));
|
||||
Assert.That(MonsterConfigBindings.ConfigRelativePath, Is.EqualTo("monster"));
|
||||
Assert.That(MonsterConfigBindings.SchemaRelativePath, Is.EqualTo("schemas/monster.schema.json"));
|
||||
Assert.That(MonsterConfigBindings.Metadata.ConfigDomain, Is.EqualTo(MonsterConfigBindings.ConfigDomain));
|
||||
Assert.That(MonsterConfigBindings.Metadata.TableName, Is.EqualTo(MonsterConfigBindings.TableName));
|
||||
Assert.That(MonsterConfigBindings.Metadata.ConfigRelativePath,
|
||||
Is.EqualTo(MonsterConfigBindings.ConfigRelativePath));
|
||||
Assert.That(MonsterConfigBindings.Metadata.SchemaRelativePath,
|
||||
Is.EqualTo(MonsterConfigBindings.SchemaRelativePath));
|
||||
Assert.That(MonsterConfigBindings.References.All, Is.Empty);
|
||||
Assert.That(MonsterConfigBindings.References.TryGetByDisplayPath("dropItems", out _), Is.False);
|
||||
Assert.That(monsterTable.Count, Is.EqualTo(2));
|
||||
Assert.That(monsterTable.Get(1).Name, Is.EqualTo("Slime"));
|
||||
Assert.That(monsterTable.Get(2).Hp, Is.EqualTo(30));
|
||||
Assert.That(monsterTable.FindByName("Slime").Select(static config => config.Id), Is.EqualTo(new[] { 1 }));
|
||||
Assert.That(dungeonMonsters.Select(static config => config.Name),
|
||||
Is.EquivalentTo(new[] { "Slime", "Goblin" }));
|
||||
Assert.That(monsterTable.TryFindFirstByName("Goblin", out var goblin), Is.True);
|
||||
Assert.That(goblin, Is.Not.Null);
|
||||
Assert.That(goblin!.Id, Is.EqualTo(2));
|
||||
Assert.That(monsterTable.TryFindFirstByFaction("dungeon", out var firstDungeonMonster), Is.True);
|
||||
Assert.That(firstDungeonMonster, Is.Not.Null);
|
||||
Assert.That(firstDungeonMonster!.Name, Is.AnyOf("Slime", "Goblin"));
|
||||
Assert.That(monsterTable.TryFindFirstByFaction("forest", out var missingMonster), Is.False);
|
||||
Assert.That(missingMonster, Is.Null);
|
||||
Assert.That(registry.TryGetMonsterTable(out var generatedTable), Is.True);
|
||||
Assert.That(generatedTable, Is.Not.Null);
|
||||
Assert.That(generatedTable!.All().Select(static config => config.Name),
|
||||
Is.EquivalentTo(new[] { "Slime", "Goblin" }));
|
||||
Assert.That(itemTable.Count, Is.EqualTo(2));
|
||||
Assert.That(itemTable.Get("potion").Name, Is.EqualTo("Potion"));
|
||||
Assert.That(itemTable.FindByCategory("consumable").Select(static config => config.Id),
|
||||
Is.EquivalentTo(new[] { "potion", "ether" }));
|
||||
Assert.That(registry.TryGetItemTable(out var generatedItemTable), Is.True);
|
||||
Assert.That(generatedItemTable, Is.Not.Null);
|
||||
Assert.That(generatedItemTable!.Get("ether").Name, Is.EqualTo("Ether"));
|
||||
});
|
||||
AssertGeneratedBindingsLoadResults(registry, monsterTable, itemTable);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -181,7 +128,7 @@ public class GeneratedConfigConsumerIntegrationTests
|
||||
{
|
||||
IncludedConfigDomains = new[] { MonsterConfigBindings.ConfigDomain }
|
||||
});
|
||||
await domainLoader.LoadAsync(domainRegistry);
|
||||
await domainLoader.LoadAsync(domainRegistry).ConfigureAwait(false);
|
||||
|
||||
var tableNameRegistry = new ConfigRegistry();
|
||||
var tableNameLoader = new YamlConfigLoader(_rootPath)
|
||||
@ -190,7 +137,7 @@ public class GeneratedConfigConsumerIntegrationTests
|
||||
{
|
||||
IncludedTableNames = new[] { ItemConfigBindings.TableName }
|
||||
});
|
||||
await tableNameLoader.LoadAsync(tableNameRegistry);
|
||||
await tableNameLoader.LoadAsync(tableNameRegistry).ConfigureAwait(false);
|
||||
|
||||
var emptyAllowListRegistry = new ConfigRegistry();
|
||||
var emptyAllowListLoader = new YamlConfigLoader(_rootPath)
|
||||
@ -200,7 +147,7 @@ public class GeneratedConfigConsumerIntegrationTests
|
||||
IncludedConfigDomains = Array.Empty<string>(),
|
||||
IncludedTableNames = Array.Empty<string>()
|
||||
});
|
||||
await emptyAllowListLoader.LoadAsync(emptyAllowListRegistry);
|
||||
await emptyAllowListLoader.LoadAsync(emptyAllowListRegistry).ConfigureAwait(false);
|
||||
|
||||
var monsterDomain = MonsterConfigBindings.ConfigDomain;
|
||||
var predicateRegistry = new ConfigRegistry();
|
||||
@ -211,28 +158,13 @@ public class GeneratedConfigConsumerIntegrationTests
|
||||
TableFilter = metadata =>
|
||||
string.Equals(metadata.ConfigDomain, monsterDomain, StringComparison.Ordinal)
|
||||
});
|
||||
await predicateLoader.LoadAsync(predicateRegistry);
|
||||
await predicateLoader.LoadAsync(predicateRegistry).ConfigureAwait(false);
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(emptyAllowListRegistry.TryGetMonsterTable(out var emptyAllowListMonsterTable), Is.True);
|
||||
Assert.That(emptyAllowListMonsterTable, Is.Not.Null);
|
||||
Assert.That(emptyAllowListRegistry.TryGetItemTable(out var emptyAllowListItemTable), Is.True);
|
||||
Assert.That(emptyAllowListItemTable, Is.Not.Null);
|
||||
|
||||
Assert.That(domainRegistry.TryGetMonsterTable(out var domainMonsterTable), Is.True);
|
||||
Assert.That(domainMonsterTable, Is.Not.Null);
|
||||
Assert.That(domainRegistry.TryGetItemTable(out _), Is.False);
|
||||
|
||||
Assert.That(tableNameRegistry.TryGetMonsterTable(out _), Is.False);
|
||||
Assert.That(tableNameRegistry.TryGetItemTable(out var tableNameItemTable), Is.True);
|
||||
Assert.That(tableNameItemTable, Is.Not.Null);
|
||||
Assert.That(tableNameItemTable!.Get("potion").Name, Is.EqualTo("Potion"));
|
||||
|
||||
Assert.That(predicateRegistry.TryGetMonsterTable(out var predicateMonsterTable), Is.True);
|
||||
Assert.That(predicateMonsterTable, Is.Not.Null);
|
||||
Assert.That(predicateRegistry.TryGetItemTable(out _), Is.False);
|
||||
});
|
||||
AssertGeneratedRegistrationFilteringResults(
|
||||
domainRegistry,
|
||||
tableNameRegistry,
|
||||
emptyAllowListRegistry,
|
||||
predicateRegistry);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -270,7 +202,7 @@ public class GeneratedConfigConsumerIntegrationTests
|
||||
MonsterConfigBindings.ValidateYaml(_rootPath, "monster/generated.yaml", yaml));
|
||||
|
||||
Assert.DoesNotThrowAsync(async () =>
|
||||
await MonsterConfigBindings.ValidateYamlAsync(_rootPath, "monster/generated.yaml", yaml));
|
||||
await MonsterConfigBindings.ValidateYamlAsync(_rootPath, "monster/generated.yaml", yaml).ConfigureAwait(false));
|
||||
|
||||
var invalidYaml = """
|
||||
id: 3
|
||||
@ -282,7 +214,7 @@ public class GeneratedConfigConsumerIntegrationTests
|
||||
var exception = Assert.Throws<ConfigLoadException>(() =>
|
||||
MonsterConfigBindings.ValidateYaml(_rootPath, "monster/generated.yaml", invalidYaml));
|
||||
var asyncException = Assert.ThrowsAsync<ConfigLoadException>(async () =>
|
||||
await MonsterConfigBindings.ValidateYamlAsync(_rootPath, "monster/generated.yaml", invalidYaml));
|
||||
await MonsterConfigBindings.ValidateYamlAsync(_rootPath, "monster/generated.yaml", invalidYaml).ConfigureAwait(false));
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
@ -367,6 +299,138 @@ public class GeneratedConfigConsumerIntegrationTests
|
||||
""");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 统一断言生成绑定加载后的目录元数据、查询入口与强类型表包装结果,
|
||||
/// 以便缩短端到端测试主体并降低分析器对方法长度的告警。
|
||||
/// </summary>
|
||||
private static void AssertGeneratedBindingsLoadResults(
|
||||
ConfigRegistry registry,
|
||||
MonsterTable monsterTable,
|
||||
ItemTable itemTable)
|
||||
{
|
||||
AssertGeneratedCatalogMetadata();
|
||||
AssertGeneratedMonsterTableResults(registry, monsterTable);
|
||||
AssertGeneratedItemTableResults(registry, itemTable);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 断言消费者项目的生成目录元数据与静态绑定常量保持一致。
|
||||
/// </summary>
|
||||
private static void AssertGeneratedCatalogMetadata()
|
||||
{
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(
|
||||
GeneratedConfigCatalog.Tables.Select(static metadata => metadata.TableName),
|
||||
Is.SupersetOf(new[] { "item", "monster" }));
|
||||
Assert.That(GeneratedConfigCatalog.TryGetByTableName("item", out var itemCatalogEntry), Is.True);
|
||||
Assert.That(itemCatalogEntry.ConfigDomain, Is.EqualTo("item"));
|
||||
Assert.That(itemCatalogEntry.ConfigRelativePath, Is.EqualTo("item"));
|
||||
Assert.That(itemCatalogEntry.SchemaRelativePath, Is.EqualTo("schemas/item.schema.json"));
|
||||
Assert.That(GeneratedConfigCatalog.TryGetByTableName("monster", out var catalogEntry), Is.True);
|
||||
Assert.That(catalogEntry.ConfigDomain, Is.EqualTo("monster"));
|
||||
Assert.That(catalogEntry.ConfigRelativePath, Is.EqualTo("monster"));
|
||||
Assert.That(catalogEntry.SchemaRelativePath, Is.EqualTo("schemas/monster.schema.json"));
|
||||
Assert.That(ItemConfigBindings.ConfigDomain, Is.EqualTo("item"));
|
||||
Assert.That(ItemConfigBindings.Metadata.TableName, Is.EqualTo("item"));
|
||||
Assert.That(MonsterConfigBindings.ConfigDomain, Is.EqualTo("monster"));
|
||||
Assert.That(MonsterConfigBindings.TableName, Is.EqualTo("monster"));
|
||||
Assert.That(MonsterConfigBindings.ConfigRelativePath, Is.EqualTo("monster"));
|
||||
Assert.That(MonsterConfigBindings.SchemaRelativePath, Is.EqualTo("schemas/monster.schema.json"));
|
||||
Assert.That(MonsterConfigBindings.Metadata.ConfigDomain, Is.EqualTo(MonsterConfigBindings.ConfigDomain));
|
||||
Assert.That(MonsterConfigBindings.Metadata.TableName, Is.EqualTo(MonsterConfigBindings.TableName));
|
||||
Assert.That(MonsterConfigBindings.Metadata.ConfigRelativePath,
|
||||
Is.EqualTo(MonsterConfigBindings.ConfigRelativePath));
|
||||
Assert.That(MonsterConfigBindings.Metadata.SchemaRelativePath,
|
||||
Is.EqualTo(MonsterConfigBindings.SchemaRelativePath));
|
||||
Assert.That(MonsterConfigBindings.References.All, Is.Empty);
|
||||
Assert.That(MonsterConfigBindings.References.TryGetByDisplayPath("dropItems", out _), Is.False);
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 断言 monster 绑定在注册表中的查询辅助、索引查询与强类型访问入口都可用。
|
||||
/// </summary>
|
||||
private static void AssertGeneratedMonsterTableResults(
|
||||
ConfigRegistry registry,
|
||||
MonsterTable monsterTable)
|
||||
{
|
||||
var dungeonMonsters = monsterTable.FindByFaction("dungeon");
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(monsterTable.Count, Is.EqualTo(2));
|
||||
Assert.That(monsterTable.Get(1).Name, Is.EqualTo("Slime"));
|
||||
Assert.That(monsterTable.Get(2).Hp, Is.EqualTo(30));
|
||||
Assert.That(monsterTable.FindByName("Slime").Select(static config => config.Id), Is.EqualTo(new[] { 1 }));
|
||||
Assert.That(dungeonMonsters.Select(static config => config.Name),
|
||||
Is.EquivalentTo(new[] { "Slime", "Goblin" }));
|
||||
Assert.That(monsterTable.TryFindFirstByName("Goblin", out var goblin), Is.True);
|
||||
Assert.That(goblin, Is.Not.Null);
|
||||
Assert.That(goblin!.Id, Is.EqualTo(2));
|
||||
Assert.That(monsterTable.TryFindFirstByFaction("dungeon", out var firstDungeonMonster), Is.True);
|
||||
Assert.That(firstDungeonMonster, Is.Not.Null);
|
||||
Assert.That(firstDungeonMonster!.Name, Is.AnyOf("Slime", "Goblin"));
|
||||
Assert.That(monsterTable.TryFindFirstByFaction("forest", out var missingMonster), Is.False);
|
||||
Assert.That(missingMonster, Is.Null);
|
||||
Assert.That(registry.TryGetMonsterTable(out var generatedTable), Is.True);
|
||||
Assert.That(generatedTable, Is.Not.Null);
|
||||
Assert.That(generatedTable!.All().Select(static config => config.Name),
|
||||
Is.EquivalentTo(new[] { "Slime", "Goblin" }));
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 断言 item 绑定的强类型表包装与按分类查询在聚合注册路径下可正常工作。
|
||||
/// </summary>
|
||||
private static void AssertGeneratedItemTableResults(
|
||||
ConfigRegistry registry,
|
||||
ItemTable itemTable)
|
||||
{
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(itemTable.Count, Is.EqualTo(2));
|
||||
Assert.That(itemTable.Get("potion").Name, Is.EqualTo("Potion"));
|
||||
Assert.That(itemTable.FindByCategory("consumable").Select(static config => config.Id),
|
||||
Is.EquivalentTo(new[] { "potion", "ether" }));
|
||||
Assert.That(registry.TryGetItemTable(out var generatedItemTable), Is.True);
|
||||
Assert.That(generatedItemTable, Is.Not.Null);
|
||||
Assert.That(generatedItemTable!.Get("ether").Name, Is.EqualTo("Ether"));
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 汇总断言不同聚合注册筛选条件下的装载结果,
|
||||
/// 让测试主体聚焦于注册参数本身而不是展开大量重复断言。
|
||||
/// </summary>
|
||||
private static void AssertGeneratedRegistrationFilteringResults(
|
||||
ConfigRegistry domainRegistry,
|
||||
ConfigRegistry tableNameRegistry,
|
||||
ConfigRegistry emptyAllowListRegistry,
|
||||
ConfigRegistry predicateRegistry)
|
||||
{
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(emptyAllowListRegistry.TryGetMonsterTable(out var emptyAllowListMonsterTable), Is.True);
|
||||
Assert.That(emptyAllowListMonsterTable, Is.Not.Null);
|
||||
Assert.That(emptyAllowListRegistry.TryGetItemTable(out var emptyAllowListItemTable), Is.True);
|
||||
Assert.That(emptyAllowListItemTable, Is.Not.Null);
|
||||
|
||||
Assert.That(domainRegistry.TryGetMonsterTable(out var domainMonsterTable), Is.True);
|
||||
Assert.That(domainMonsterTable, Is.Not.Null);
|
||||
Assert.That(domainRegistry.TryGetItemTable(out _), Is.False);
|
||||
|
||||
Assert.That(tableNameRegistry.TryGetMonsterTable(out _), Is.False);
|
||||
Assert.That(tableNameRegistry.TryGetItemTable(out var tableNameItemTable), Is.True);
|
||||
Assert.That(tableNameItemTable, Is.Not.Null);
|
||||
Assert.That(tableNameItemTable!.Get("potion").Name, Is.EqualTo("Potion"));
|
||||
|
||||
Assert.That(predicateRegistry.TryGetMonsterTable(out var predicateMonsterTable), Is.True);
|
||||
Assert.That(predicateMonsterTable, Is.Not.Null);
|
||||
Assert.That(predicateRegistry.TryGetItemTable(out _), Is.False);
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 在临时消费者目录中创建 item schema 与 YAML 测试数据,用于验证多表聚合注册和筛选行为。
|
||||
/// </summary>
|
||||
|
||||
@ -90,7 +90,7 @@ public sealed class YamlConfigLoaderAllOfTests
|
||||
var loader = CreateMonsterRewardLoader();
|
||||
var registry = CreateRegistry();
|
||||
|
||||
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry));
|
||||
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry).ConfigureAwait(false));
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
@ -124,7 +124,7 @@ public sealed class YamlConfigLoaderAllOfTests
|
||||
var loader = CreateMonsterRewardLoader();
|
||||
var registry = CreateRegistry();
|
||||
|
||||
await loader.LoadAsync(registry);
|
||||
await loader.LoadAsync(registry).ConfigureAwait(false);
|
||||
|
||||
var table = registry.GetTable<int, MonsterAllOfConfigStub>("monster");
|
||||
var reward = table.Get(1).Reward;
|
||||
@ -165,7 +165,7 @@ public sealed class YamlConfigLoaderAllOfTests
|
||||
var loader = CreateMonsterRewardLoader();
|
||||
var registry = CreateRegistry();
|
||||
|
||||
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry));
|
||||
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry).ConfigureAwait(false));
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
@ -210,7 +210,10 @@ public sealed class YamlConfigLoaderAllOfTests
|
||||
}
|
||||
""");
|
||||
|
||||
ArgumentNullException.ThrowIfNull(_rootPath);
|
||||
if (_rootPath is null)
|
||||
{
|
||||
throw new InvalidOperationException("Root path is not initialized.");
|
||||
}
|
||||
|
||||
var loader = new YamlConfigLoader(_rootPath)
|
||||
.RegisterTable<int, MonsterTagConfigStub>(
|
||||
@ -220,7 +223,7 @@ public sealed class YamlConfigLoaderAllOfTests
|
||||
static config => config.Id);
|
||||
var registry = CreateRegistry();
|
||||
|
||||
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry));
|
||||
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry).ConfigureAwait(false));
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
@ -255,7 +258,7 @@ public sealed class YamlConfigLoaderAllOfTests
|
||||
var loader = CreateMonsterRewardLoader();
|
||||
var registry = CreateRegistry();
|
||||
|
||||
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry));
|
||||
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry).ConfigureAwait(false));
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
@ -295,7 +298,7 @@ public sealed class YamlConfigLoaderAllOfTests
|
||||
var loader = CreateMonsterRewardLoader();
|
||||
var registry = CreateRegistry();
|
||||
|
||||
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry));
|
||||
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry).ConfigureAwait(false));
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
@ -335,7 +338,7 @@ public sealed class YamlConfigLoaderAllOfTests
|
||||
var loader = CreateMonsterRewardLoader();
|
||||
var registry = CreateRegistry();
|
||||
|
||||
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry));
|
||||
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry).ConfigureAwait(false));
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
@ -375,7 +378,7 @@ public sealed class YamlConfigLoaderAllOfTests
|
||||
var loader = CreateMonsterRewardLoader();
|
||||
var registry = CreateRegistry();
|
||||
|
||||
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry));
|
||||
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry).ConfigureAwait(false));
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
@ -415,7 +418,7 @@ public sealed class YamlConfigLoaderAllOfTests
|
||||
var loader = CreateMonsterRewardLoader();
|
||||
var registry = CreateRegistry();
|
||||
|
||||
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry));
|
||||
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry).ConfigureAwait(false));
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
@ -455,7 +458,7 @@ public sealed class YamlConfigLoaderAllOfTests
|
||||
var loader = CreateMonsterRewardLoader();
|
||||
var registry = CreateRegistry();
|
||||
|
||||
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry));
|
||||
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry).ConfigureAwait(false));
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
@ -499,7 +502,7 @@ public sealed class YamlConfigLoaderAllOfTests
|
||||
var loader = CreateMonsterRewardLoader();
|
||||
var registry = CreateRegistry();
|
||||
|
||||
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry));
|
||||
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry).ConfigureAwait(false));
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
@ -519,7 +522,10 @@ public sealed class YamlConfigLoaderAllOfTests
|
||||
/// <param name="content">要写入的 YAML 或 schema 内容。</param>
|
||||
private void CreateConfigFile(string relativePath, string content)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(_rootPath);
|
||||
if (_rootPath is null)
|
||||
{
|
||||
throw new InvalidOperationException("Root path is not initialized.");
|
||||
}
|
||||
|
||||
var filePath = Path.Combine(_rootPath, relativePath.Replace('/', Path.DirectorySeparatorChar));
|
||||
var directoryPath = Path.GetDirectoryName(filePath);
|
||||
@ -606,7 +612,10 @@ public sealed class YamlConfigLoaderAllOfTests
|
||||
/// <returns>已注册测试表与 schema 路径的加载器。</returns>
|
||||
private YamlConfigLoader CreateMonsterRewardLoader()
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(_rootPath);
|
||||
if (_rootPath is null)
|
||||
{
|
||||
throw new InvalidOperationException("Root path is not initialized.");
|
||||
}
|
||||
|
||||
return new YamlConfigLoader(_rootPath)
|
||||
.RegisterTable<int, MonsterAllOfConfigStub>(
|
||||
|
||||
@ -75,7 +75,7 @@ public sealed class YamlConfigLoaderDependentRequiredTests
|
||||
var loader = CreateMonsterRewardLoader();
|
||||
var registry = CreateRegistry();
|
||||
|
||||
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry));
|
||||
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry).ConfigureAwait(false));
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
@ -124,7 +124,7 @@ public sealed class YamlConfigLoaderDependentRequiredTests
|
||||
var loader = CreateMonsterRewardLoader();
|
||||
var registry = CreateRegistry();
|
||||
|
||||
await loader.LoadAsync(registry);
|
||||
await loader.LoadAsync(registry).ConfigureAwait(false);
|
||||
|
||||
var table = registry.GetTable<int, MonsterRewardConfigStub>("monster");
|
||||
Assert.That(table.Count, Is.EqualTo(1));
|
||||
@ -169,7 +169,7 @@ public sealed class YamlConfigLoaderDependentRequiredTests
|
||||
var loader = CreateMonsterRewardLoader();
|
||||
var registry = CreateRegistry();
|
||||
|
||||
await loader.LoadAsync(registry);
|
||||
await loader.LoadAsync(registry).ConfigureAwait(false);
|
||||
|
||||
var table = registry.GetTable<int, MonsterRewardConfigStub>("monster");
|
||||
var reward = table.Get(1).Reward;
|
||||
@ -217,7 +217,7 @@ public sealed class YamlConfigLoaderDependentRequiredTests
|
||||
var loader = CreateMonsterRewardLoader();
|
||||
var registry = CreateRegistry();
|
||||
|
||||
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry));
|
||||
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry).ConfigureAwait(false));
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
@ -267,7 +267,7 @@ public sealed class YamlConfigLoaderDependentRequiredTests
|
||||
var loader = CreateCaseSensitiveRewardLoader();
|
||||
var registry = CreateRegistry();
|
||||
|
||||
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry));
|
||||
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry).ConfigureAwait(false));
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
@ -317,7 +317,7 @@ public sealed class YamlConfigLoaderDependentRequiredTests
|
||||
var loader = CreateMonsterRewardLoader();
|
||||
var registry = CreateRegistry();
|
||||
|
||||
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry));
|
||||
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry).ConfigureAwait(false));
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
|
||||
@ -74,7 +74,7 @@ public sealed class YamlConfigLoaderDependentSchemasTests
|
||||
var loader = CreateMonsterRewardLoader();
|
||||
var registry = CreateRegistry();
|
||||
|
||||
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry));
|
||||
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry).ConfigureAwait(false));
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
@ -106,7 +106,7 @@ public sealed class YamlConfigLoaderDependentSchemasTests
|
||||
var loader = CreateMonsterRewardLoader();
|
||||
var registry = CreateRegistry();
|
||||
|
||||
await loader.LoadAsync(registry);
|
||||
await loader.LoadAsync(registry).ConfigureAwait(false);
|
||||
|
||||
var table = registry.GetTable<int, MonsterDependentSchemasConfigStub>("monster");
|
||||
Assert.That(table.Count, Is.EqualTo(1));
|
||||
@ -133,7 +133,7 @@ public sealed class YamlConfigLoaderDependentSchemasTests
|
||||
var loader = CreateMonsterRewardLoader();
|
||||
var registry = CreateRegistry();
|
||||
|
||||
await loader.LoadAsync(registry);
|
||||
await loader.LoadAsync(registry).ConfigureAwait(false);
|
||||
|
||||
var table = registry.GetTable<int, MonsterDependentSchemasConfigStub>("monster");
|
||||
var reward = table.Get(1).Reward;
|
||||
@ -174,7 +174,7 @@ public sealed class YamlConfigLoaderDependentSchemasTests
|
||||
var loader = CreateMonsterRewardLoader();
|
||||
var registry = CreateRegistry();
|
||||
|
||||
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry));
|
||||
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry).ConfigureAwait(false));
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
@ -220,7 +220,7 @@ public sealed class YamlConfigLoaderDependentSchemasTests
|
||||
var loader = CreateMonsterRewardLoader();
|
||||
var registry = CreateRegistry();
|
||||
|
||||
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry));
|
||||
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry).ConfigureAwait(false));
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
@ -264,7 +264,7 @@ public sealed class YamlConfigLoaderDependentSchemasTests
|
||||
var loader = CreateMonsterRewardLoader();
|
||||
var registry = CreateRegistry();
|
||||
|
||||
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry));
|
||||
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry).ConfigureAwait(false));
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
@ -283,7 +283,10 @@ public sealed class YamlConfigLoaderDependentSchemasTests
|
||||
/// <param name="content">要写入的 YAML 或 schema 内容。</param>
|
||||
private void CreateConfigFile(string relativePath, string content)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(_rootPath);
|
||||
if (_rootPath is null)
|
||||
{
|
||||
throw new InvalidOperationException("Root path is not initialized.");
|
||||
}
|
||||
|
||||
var filePath = Path.Combine(_rootPath, relativePath.Replace('/', Path.DirectorySeparatorChar));
|
||||
var directoryPath = Path.GetDirectoryName(filePath);
|
||||
@ -353,7 +356,10 @@ public sealed class YamlConfigLoaderDependentSchemasTests
|
||||
/// <returns>已注册测试表与 schema 路径的加载器。</returns>
|
||||
private YamlConfigLoader CreateMonsterRewardLoader()
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(_rootPath);
|
||||
if (_rootPath is null)
|
||||
{
|
||||
throw new InvalidOperationException("Root path is not initialized.");
|
||||
}
|
||||
|
||||
return new YamlConfigLoader(_rootPath)
|
||||
.RegisterTable<int, MonsterDependentSchemasConfigStub>(
|
||||
|
||||
@ -75,7 +75,7 @@ public class YamlConfigLoaderEnumTests
|
||||
var loader = CreateLoader<MonsterRewardConfigStub>();
|
||||
var registry = new ConfigRegistry();
|
||||
|
||||
await loader.LoadAsync(registry);
|
||||
await loader.LoadAsync(registry).ConfigureAwait(false);
|
||||
|
||||
var table = registry.GetTable<int, MonsterRewardConfigStub>("monster");
|
||||
Assert.Multiple(() =>
|
||||
@ -127,7 +127,7 @@ public class YamlConfigLoaderEnumTests
|
||||
var loader = CreateLoader<MonsterRewardConfigStub>();
|
||||
var registry = new ConfigRegistry();
|
||||
|
||||
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry));
|
||||
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry).ConfigureAwait(false));
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
@ -176,7 +176,7 @@ public class YamlConfigLoaderEnumTests
|
||||
var loader = CreateLoader<MonsterDropItemIdsConfigStub>();
|
||||
var registry = new ConfigRegistry();
|
||||
|
||||
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry));
|
||||
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry).ConfigureAwait(false));
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
|
||||
@ -105,7 +105,7 @@ public sealed class YamlConfigLoaderIfThenElseTests
|
||||
var loader = CreateMonsterRewardLoader();
|
||||
var registry = CreateRegistry();
|
||||
|
||||
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry));
|
||||
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry).ConfigureAwait(false));
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
@ -137,7 +137,7 @@ public sealed class YamlConfigLoaderIfThenElseTests
|
||||
var loader = CreateMonsterRewardLoader();
|
||||
var registry = CreateRegistry();
|
||||
|
||||
await loader.LoadAsync(registry);
|
||||
await loader.LoadAsync(registry).ConfigureAwait(false);
|
||||
|
||||
var table = registry.GetTable<int, MonsterConditionalConfigStub>("monster");
|
||||
var reward = table.Get(1).Reward;
|
||||
@ -170,7 +170,7 @@ public sealed class YamlConfigLoaderIfThenElseTests
|
||||
var loader = CreateMonsterRewardLoader();
|
||||
var registry = CreateRegistry();
|
||||
|
||||
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry));
|
||||
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry).ConfigureAwait(false));
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
@ -202,7 +202,7 @@ public sealed class YamlConfigLoaderIfThenElseTests
|
||||
var loader = CreateMonsterRewardLoader();
|
||||
var registry = CreateRegistry();
|
||||
|
||||
await loader.LoadAsync(registry);
|
||||
await loader.LoadAsync(registry).ConfigureAwait(false);
|
||||
|
||||
var table = registry.GetTable<int, MonsterConditionalConfigStub>("monster");
|
||||
var reward = table.Get(1).Reward;
|
||||
@ -250,7 +250,10 @@ public sealed class YamlConfigLoaderIfThenElseTests
|
||||
}
|
||||
""");
|
||||
|
||||
ArgumentNullException.ThrowIfNull(_rootPath);
|
||||
if (_rootPath is null)
|
||||
{
|
||||
throw new InvalidOperationException("Root path is not initialized.");
|
||||
}
|
||||
|
||||
var loader = new YamlConfigLoader(_rootPath)
|
||||
.RegisterTable<int, MonsterTagConfigStub>(
|
||||
@ -260,7 +263,7 @@ public sealed class YamlConfigLoaderIfThenElseTests
|
||||
static config => config.Id);
|
||||
var registry = CreateRegistry();
|
||||
|
||||
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry));
|
||||
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry).ConfigureAwait(false));
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
@ -301,7 +304,7 @@ public sealed class YamlConfigLoaderIfThenElseTests
|
||||
var loader = CreateMonsterRewardLoader();
|
||||
var registry = CreateRegistry();
|
||||
|
||||
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry));
|
||||
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry).ConfigureAwait(false));
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
@ -342,7 +345,7 @@ public sealed class YamlConfigLoaderIfThenElseTests
|
||||
var loader = CreateMonsterRewardLoader();
|
||||
var registry = CreateRegistry();
|
||||
|
||||
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry));
|
||||
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry).ConfigureAwait(false));
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
@ -390,7 +393,7 @@ public sealed class YamlConfigLoaderIfThenElseTests
|
||||
var loader = CreateMonsterRewardLoader();
|
||||
var registry = CreateRegistry();
|
||||
|
||||
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry));
|
||||
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry).ConfigureAwait(false));
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
@ -409,7 +412,10 @@ public sealed class YamlConfigLoaderIfThenElseTests
|
||||
/// <param name="content">配置文件内容。</param>
|
||||
private void CreateConfigFile(string relativePath, string content)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(_rootPath);
|
||||
if (_rootPath is null)
|
||||
{
|
||||
throw new InvalidOperationException("Root path is not initialized.");
|
||||
}
|
||||
|
||||
var filePath = Path.Combine(_rootPath, relativePath.Replace('/', Path.DirectorySeparatorChar));
|
||||
var directoryPath = Path.GetDirectoryName(filePath);
|
||||
@ -496,7 +502,10 @@ public sealed class YamlConfigLoaderIfThenElseTests
|
||||
/// <returns>已注册测试表与 schema 路径的加载器。</returns>
|
||||
private YamlConfigLoader CreateMonsterRewardLoader()
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(_rootPath);
|
||||
if (_rootPath is null)
|
||||
{
|
||||
throw new InvalidOperationException("Root path is not initialized.");
|
||||
}
|
||||
|
||||
return new YamlConfigLoader(_rootPath)
|
||||
.RegisterTable<int, MonsterConditionalConfigStub>(
|
||||
|
||||
@ -70,7 +70,7 @@ public sealed class YamlConfigLoaderNegationTests
|
||||
var loader = CreateMonsterLoader();
|
||||
var registry = CreateRegistry();
|
||||
|
||||
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry));
|
||||
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry).ConfigureAwait(false));
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
@ -118,7 +118,7 @@ public sealed class YamlConfigLoaderNegationTests
|
||||
var loader = CreateMonsterLoader();
|
||||
var registry = CreateRegistry();
|
||||
|
||||
await loader.LoadAsync(registry);
|
||||
await loader.LoadAsync(registry).ConfigureAwait(false);
|
||||
|
||||
var table = registry.GetTable<int, MonsterConfigStub>("monster");
|
||||
|
||||
@ -172,7 +172,7 @@ public sealed class YamlConfigLoaderNegationTests
|
||||
var loader = CreateMonsterRewardLoader();
|
||||
var registry = CreateRegistry();
|
||||
|
||||
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry));
|
||||
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry).ConfigureAwait(false));
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
@ -227,7 +227,7 @@ public sealed class YamlConfigLoaderNegationTests
|
||||
var loader = CreateMonsterRewardLoader();
|
||||
var registry = CreateRegistry();
|
||||
|
||||
await loader.LoadAsync(registry);
|
||||
await loader.LoadAsync(registry).ConfigureAwait(false);
|
||||
|
||||
var table = registry.GetTable<int, MonsterRewardConfigStub>("monster");
|
||||
|
||||
@ -272,7 +272,7 @@ public sealed class YamlConfigLoaderNegationTests
|
||||
var loader = CreateMonsterLoader();
|
||||
var registry = CreateRegistry();
|
||||
|
||||
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry));
|
||||
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry).ConfigureAwait(false));
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
|
||||
@ -191,7 +191,10 @@ public sealed class YamlConfigSchemaValidatorTests
|
||||
string relativePath,
|
||||
string content)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(_rootPath);
|
||||
if (_rootPath is null)
|
||||
{
|
||||
throw new InvalidOperationException("Root path is not initialized.");
|
||||
}
|
||||
|
||||
var fullPath = Path.Combine(_rootPath, relativePath.Replace('/', Path.DirectorySeparatorChar));
|
||||
var directoryPath = Path.GetDirectoryName(fullPath);
|
||||
|
||||
@ -132,7 +132,7 @@ public sealed class YamlConfigTextValidatorTests
|
||||
"monster/generated.yaml",
|
||||
"""
|
||||
id: 1
|
||||
"""));
|
||||
""").ConfigureAwait(false));
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
|
||||
@ -1,114 +0,0 @@
|
||||
using System;
|
||||
using GFramework.Game.Abstractions.Data;
|
||||
using GFramework.Game.Abstractions.Enums;
|
||||
|
||||
namespace GFramework.Game.Tests.Data;
|
||||
|
||||
/// <summary>
|
||||
/// 为持久化测试提供稳定的测试数据位置实现。
|
||||
/// </summary>
|
||||
internal sealed class TestDataLocation : IDataLocation
|
||||
{
|
||||
/// <summary>
|
||||
/// 初始化测试数据位置。
|
||||
/// </summary>
|
||||
/// <param name="key">测试使用的存储键。</param>
|
||||
/// <param name="kinds">测试使用的存储类型。</param>
|
||||
/// <param name="namespaceValue">测试使用的命名空间。</param>
|
||||
/// <param name="metadata">附加测试元数据。</param>
|
||||
public TestDataLocation(
|
||||
string key,
|
||||
StorageKinds kinds = StorageKinds.Local,
|
||||
string? namespaceValue = null,
|
||||
IReadOnlyDictionary<string, string>? metadata = null)
|
||||
{
|
||||
Key = key;
|
||||
Kinds = kinds;
|
||||
Namespace = namespaceValue;
|
||||
Metadata = metadata;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取测试数据对应的存储键。
|
||||
/// </summary>
|
||||
public string Key { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取测试数据使用的存储类型。
|
||||
/// </summary>
|
||||
public StorageKinds Kinds { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取测试数据使用的命名空间。
|
||||
/// </summary>
|
||||
public string? Namespace { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取附加到测试位置上的元数据。
|
||||
/// </summary>
|
||||
public IReadOnlyDictionary<string, string>? Metadata { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 为基础存档仓库测试提供的简单存档模型。
|
||||
/// </summary>
|
||||
internal sealed class TestSaveData : IData
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取或设置测试存档中的名称字段。
|
||||
/// </summary>
|
||||
public string Name { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 为存档迁移测试提供的版本化存档模型。
|
||||
/// </summary>
|
||||
internal sealed class TestVersionedSaveData : IVersionedData
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取或设置测试存档中的名称字段。
|
||||
/// </summary>
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置测试存档中的等级字段。
|
||||
/// </summary>
|
||||
public int Level { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置测试存档中的经验字段。
|
||||
/// </summary>
|
||||
public int Experience { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置当前测试存档的版本号。
|
||||
/// </summary>
|
||||
public int Version { get; set; } = 3;
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置测试存档的最后修改时间。
|
||||
/// </summary>
|
||||
public DateTime LastModified { get; set; } = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 为通用持久化测试提供的简单数据模型。
|
||||
/// </summary>
|
||||
internal sealed class TestSimpleData : IData
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取或设置测试数据中的整数值。
|
||||
/// </summary>
|
||||
public int Value { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 为批量持久化测试提供的另一种数据模型,用于验证运行时类型不会在接口路径上退化。
|
||||
/// </summary>
|
||||
internal sealed class TestNamedData : IData
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取或设置测试数据中的名称值。
|
||||
/// </summary>
|
||||
public string Name { get; set; } = string.Empty;
|
||||
}
|
||||
@ -38,12 +38,12 @@ public class PersistenceTests
|
||||
using var storage = new FileStorage(root, new JsonSerializer(), ".json");
|
||||
|
||||
var saved = new TestSimpleData { Value = 5 };
|
||||
await storage.WriteAsync("folder/item", saved);
|
||||
await storage.WriteAsync("folder/item", saved).ConfigureAwait(false);
|
||||
|
||||
var loaded = await storage.ReadAsync<TestSimpleData>("folder/item");
|
||||
var loaded = await storage.ReadAsync<TestSimpleData>("folder/item").ConfigureAwait(false);
|
||||
Assert.That(loaded.Value, Is.EqualTo(saved.Value));
|
||||
|
||||
Assert.ThrowsAsync<ArgumentException>(async () => await storage.WriteAsync("../escape", new TestSimpleData()));
|
||||
Assert.ThrowsAsync<ArgumentException>(async () => await storage.WriteAsync("../escape", new TestSimpleData()).ConfigureAwait(false));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -108,7 +108,7 @@ public class PersistenceTests
|
||||
.RegisterMigration(new TestSaveMigrationV2ToV3());
|
||||
|
||||
var loaded = await repository.LoadAsync(1);
|
||||
var persisted = await storage.ReadAsync<TestVersionedSaveData>("saves/slot_1/save");
|
||||
var persisted = await storage.ReadAsync<TestVersionedSaveData>("saves/slot_1/save").ConfigureAwait(false);
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
@ -185,7 +185,7 @@ public class PersistenceTests
|
||||
var repository = new SaveRepository<TestVersionedSaveData>(storage, config)
|
||||
.RegisterMigration(new TestSaveMigrationV1ToV2());
|
||||
|
||||
var exception = Assert.ThrowsAsync<InvalidOperationException>(async () => await repository.LoadAsync(1));
|
||||
var exception = Assert.ThrowsAsync<InvalidOperationException>(async () => await repository.LoadAsync(1).ConfigureAwait(false));
|
||||
Assert.That(exception!.Message, Does.Contain("from version 2"));
|
||||
}
|
||||
|
||||
@ -218,8 +218,8 @@ public class PersistenceTests
|
||||
var repository = new SaveRepository<TestVersionedSaveData>(storage, config)
|
||||
.RegisterMigration(new TestSaveMigrationV1ToV2ReturningV3());
|
||||
|
||||
var exception = Assert.ThrowsAsync<InvalidOperationException>(async () => await repository.LoadAsync(1));
|
||||
var persisted = await storage.ReadAsync<TestVersionedSaveData>("saves/slot_1/save");
|
||||
var exception = Assert.ThrowsAsync<InvalidOperationException>(async () => await repository.LoadAsync(1).ConfigureAwait(false));
|
||||
var persisted = await storage.ReadAsync<TestVersionedSaveData>("saves/slot_1/save").ConfigureAwait(false);
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
@ -270,8 +270,8 @@ public class PersistenceTests
|
||||
repository.RegisterMigration(new TestSaveMigrationV2ToV3());
|
||||
continueMigration.Set();
|
||||
|
||||
var exception = Assert.ThrowsAsync<InvalidOperationException>(async () => await loadTask);
|
||||
var persisted = await storage.ReadAsync<TestVersionedSaveData>("saves/slot_1/save");
|
||||
var exception = Assert.ThrowsAsync<InvalidOperationException>(async () => await loadTask.ConfigureAwait(false));
|
||||
var persisted = await storage.ReadAsync<TestVersionedSaveData>("saves/slot_1/save").ConfigureAwait(false);
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
@ -593,13 +593,13 @@ public class PersistenceTests
|
||||
|
||||
throwingStorage.ThrowOnWrite = true;
|
||||
Assert.ThrowsAsync<InvalidOperationException>(
|
||||
async () => await repository.SaveAsync(primaryLocation, new TestSimpleData { Value = 99 }));
|
||||
async () => await repository.SaveAsync(primaryLocation, new TestSimpleData { Value = 99 }).ConfigureAwait(false));
|
||||
|
||||
var cachedAfterFailure = await repository.LoadAsync<TestSimpleData>(primaryLocation);
|
||||
var cachedAfterFailure = await repository.LoadAsync<TestSimpleData>(primaryLocation).ConfigureAwait(false);
|
||||
Assert.That(cachedAfterFailure.Value, Is.EqualTo(1));
|
||||
|
||||
throwingStorage.ThrowOnWrite = false;
|
||||
await repository.SaveAsync(secondaryLocation, new TestSimpleData { Value = 7 });
|
||||
await repository.SaveAsync(secondaryLocation, new TestSimpleData { Value = 7 }).ConfigureAwait(false);
|
||||
|
||||
using var verifyStorage = new FileStorage(root, new JsonSerializer(), ".json");
|
||||
var verifyRepository = new UnifiedSettingsDataRepository(
|
||||
@ -609,8 +609,8 @@ public class PersistenceTests
|
||||
verifyRepository.RegisterDataType(primaryLocation, typeof(TestSimpleData));
|
||||
verifyRepository.RegisterDataType(secondaryLocation, typeof(TestSimpleData));
|
||||
|
||||
var persistedPrimary = await verifyRepository.LoadAsync<TestSimpleData>(primaryLocation);
|
||||
var persistedSecondary = await verifyRepository.LoadAsync<TestSimpleData>(secondaryLocation);
|
||||
var persistedPrimary = await verifyRepository.LoadAsync<TestSimpleData>(primaryLocation).ConfigureAwait(false);
|
||||
var persistedSecondary = await verifyRepository.LoadAsync<TestSimpleData>(secondaryLocation).ConfigureAwait(false);
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
@ -655,12 +655,13 @@ public class PersistenceTests
|
||||
repository.RegisterDataType(secondaryLocation, typeof(TestSimpleData));
|
||||
|
||||
throwingStorage.ThrowOnWrite = true;
|
||||
Assert.ThrowsAsync<InvalidOperationException>(async () => await repository.DeleteAsync(secondaryLocation));
|
||||
Assert.ThrowsAsync<InvalidOperationException>(
|
||||
async () => await repository.DeleteAsync(secondaryLocation).ConfigureAwait(false));
|
||||
|
||||
Assert.That(await repository.ExistsAsync(secondaryLocation), Is.True);
|
||||
Assert.That(await repository.ExistsAsync(secondaryLocation).ConfigureAwait(false), Is.True);
|
||||
|
||||
throwingStorage.ThrowOnWrite = false;
|
||||
await repository.SaveAsync(primaryLocation, new TestSimpleData { Value = 9 });
|
||||
await repository.SaveAsync(primaryLocation, new TestSimpleData { Value = 9 }).ConfigureAwait(false);
|
||||
|
||||
using var verifyStorage = new FileStorage(root, new JsonSerializer(), ".json");
|
||||
var verifyRepository = new UnifiedSettingsDataRepository(
|
||||
@ -670,8 +671,8 @@ public class PersistenceTests
|
||||
verifyRepository.RegisterDataType(primaryLocation, typeof(TestSimpleData));
|
||||
verifyRepository.RegisterDataType(secondaryLocation, typeof(TestSimpleData));
|
||||
|
||||
var persistedPrimary = await verifyRepository.LoadAsync<TestSimpleData>(primaryLocation);
|
||||
var persistedSecondary = await verifyRepository.LoadAsync<TestSimpleData>(secondaryLocation);
|
||||
var persistedPrimary = await verifyRepository.LoadAsync<TestSimpleData>(primaryLocation).ConfigureAwait(false);
|
||||
var persistedSecondary = await verifyRepository.LoadAsync<TestSimpleData>(secondaryLocation).ConfigureAwait(false);
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
|
||||
49
GFramework.Game.Tests/Data/TestDataLocation.cs
Normal file
49
GFramework.Game.Tests/Data/TestDataLocation.cs
Normal file
@ -0,0 +1,49 @@
|
||||
using GFramework.Game.Abstractions.Data;
|
||||
using GFramework.Game.Abstractions.Enums;
|
||||
|
||||
namespace GFramework.Game.Tests.Data;
|
||||
|
||||
/// <summary>
|
||||
/// 为持久化测试提供稳定的测试数据位置实现。
|
||||
/// </summary>
|
||||
internal sealed class TestDataLocation : IDataLocation
|
||||
{
|
||||
/// <summary>
|
||||
/// 初始化测试数据位置。
|
||||
/// </summary>
|
||||
/// <param name="key">测试使用的存储键。</param>
|
||||
/// <param name="kinds">测试使用的存储类型。</param>
|
||||
/// <param name="namespaceValue">测试使用的命名空间。</param>
|
||||
/// <param name="metadata">附加测试元数据。</param>
|
||||
public TestDataLocation(
|
||||
string key,
|
||||
StorageKinds kinds = StorageKinds.Local,
|
||||
string? namespaceValue = null,
|
||||
IReadOnlyDictionary<string, string>? metadata = null)
|
||||
{
|
||||
Key = key;
|
||||
Kinds = kinds;
|
||||
Namespace = namespaceValue;
|
||||
Metadata = metadata;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取测试数据对应的存储键。
|
||||
/// </summary>
|
||||
public string Key { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取测试数据使用的存储类型。
|
||||
/// </summary>
|
||||
public StorageKinds Kinds { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取测试数据使用的命名空间。
|
||||
/// </summary>
|
||||
public string? Namespace { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取附加到测试位置上的元数据。
|
||||
/// </summary>
|
||||
public IReadOnlyDictionary<string, string>? Metadata { get; }
|
||||
}
|
||||
14
GFramework.Game.Tests/Data/TestNamedData.cs
Normal file
14
GFramework.Game.Tests/Data/TestNamedData.cs
Normal file
@ -0,0 +1,14 @@
|
||||
using GFramework.Game.Abstractions.Data;
|
||||
|
||||
namespace GFramework.Game.Tests.Data;
|
||||
|
||||
/// <summary>
|
||||
/// 为批量持久化测试提供的另一种数据模型,用于验证运行时类型不会在接口路径上退化。
|
||||
/// </summary>
|
||||
internal sealed class TestNamedData : IData
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取或设置测试数据中的名称值。
|
||||
/// </summary>
|
||||
public string Name { get; set; } = string.Empty;
|
||||
}
|
||||
14
GFramework.Game.Tests/Data/TestSaveData.cs
Normal file
14
GFramework.Game.Tests/Data/TestSaveData.cs
Normal file
@ -0,0 +1,14 @@
|
||||
using GFramework.Game.Abstractions.Data;
|
||||
|
||||
namespace GFramework.Game.Tests.Data;
|
||||
|
||||
/// <summary>
|
||||
/// 为基础存档仓库测试提供的简单存档模型。
|
||||
/// </summary>
|
||||
internal sealed class TestSaveData : IData
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取或设置测试存档中的名称字段。
|
||||
/// </summary>
|
||||
public string Name { get; set; } = string.Empty;
|
||||
}
|
||||
14
GFramework.Game.Tests/Data/TestSimpleData.cs
Normal file
14
GFramework.Game.Tests/Data/TestSimpleData.cs
Normal file
@ -0,0 +1,14 @@
|
||||
using GFramework.Game.Abstractions.Data;
|
||||
|
||||
namespace GFramework.Game.Tests.Data;
|
||||
|
||||
/// <summary>
|
||||
/// 为通用持久化测试提供的简单数据模型。
|
||||
/// </summary>
|
||||
internal sealed class TestSimpleData : IData
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取或设置测试数据中的整数值。
|
||||
/// </summary>
|
||||
public int Value { get; set; }
|
||||
}
|
||||
35
GFramework.Game.Tests/Data/TestVersionedSaveData.cs
Normal file
35
GFramework.Game.Tests/Data/TestVersionedSaveData.cs
Normal file
@ -0,0 +1,35 @@
|
||||
using System;
|
||||
using GFramework.Game.Abstractions.Data;
|
||||
|
||||
namespace GFramework.Game.Tests.Data;
|
||||
|
||||
/// <summary>
|
||||
/// 为存档迁移测试提供的版本化存档模型。
|
||||
/// </summary>
|
||||
internal sealed class TestVersionedSaveData : IVersionedData
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取或设置测试存档中的名称字段。
|
||||
/// </summary>
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置测试存档中的等级字段。
|
||||
/// </summary>
|
||||
public int Level { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置测试存档中的经验字段。
|
||||
/// </summary>
|
||||
public int Experience { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置当前测试存档的版本号。
|
||||
/// </summary>
|
||||
public int Version { get; set; } = 3;
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置测试存档的最后修改时间。
|
||||
/// </summary>
|
||||
public DateTime LastModified { get; set; } = DateTime.UtcNow;
|
||||
}
|
||||
@ -1,3 +1,4 @@
|
||||
using System.Globalization;
|
||||
using Newtonsoft.Json;
|
||||
using GameJsonSerializer = GFramework.Game.Serializer.JsonSerializer;
|
||||
|
||||
@ -182,8 +183,8 @@ public sealed class JsonSerializerTests
|
||||
var parts = raw.Split(':');
|
||||
return new CoordinateStub
|
||||
{
|
||||
X = int.Parse(parts[0]),
|
||||
Y = int.Parse(parts[1])
|
||||
X = int.Parse(parts[0], CultureInfo.InvariantCulture),
|
||||
Y = int.Parse(parts[1], CultureInfo.InvariantCulture)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@ -288,11 +288,11 @@ public class UnifiedSettingsDataRepository(
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(source);
|
||||
|
||||
// 反序列化后的运行时类型可能只是 IDictionary 实现;若底层仍是 Dictionary,则保留其 comparer,
|
||||
// 否则退回到按当前内容复制,避免因为 API 抽象化而改变持久化前后的键比较语义。
|
||||
// 反序列化后的运行时类型可能只是 IDictionary 实现;若底层仍是 Dictionary,则保留其 comparer。
|
||||
// 若 comparer 已因接口抽象而不可恢复,则显式回退到 Ordinal,避免让默认 comparer 语义继续隐式存在。
|
||||
var sections = source.Sections is Dictionary<string, string> dictionary
|
||||
? new Dictionary<string, string>(dictionary, dictionary.Comparer)
|
||||
: new Dictionary<string, string>(source.Sections);
|
||||
: new Dictionary<string, string>(source.Sections, StringComparer.Ordinal);
|
||||
|
||||
return new UnifiedSettingsFile
|
||||
{
|
||||
|
||||
@ -11,6 +11,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using GFramework.Core.Abstractions.Versioning;
|
||||
|
||||
@ -28,8 +29,11 @@ internal sealed class UnifiedSettingsFile : IVersioned
|
||||
/// <remarks>
|
||||
/// 这里公开为 <see cref="IDictionary{TKey,TValue}" /> 而不是具体的 <see cref="Dictionary{TKey,TValue}" />,
|
||||
/// 以避免暴露可替换的具体集合实现,同时继续兼容 Newtonsoft.Json 对字典对象的序列化与反序列化。
|
||||
/// 默认实例使用 <see cref="StringComparer.Ordinal" />;若调用方提供其他实现,仓库在可以识别底层
|
||||
/// <see cref="Dictionary{TKey,TValue}" /> comparer 时会保留原语义,否则克隆快照时会显式回退到
|
||||
/// <see cref="StringComparer.Ordinal" />。
|
||||
/// </remarks>
|
||||
public IDictionary<string, string> Sections { get; set; } = new Dictionary<string, string>();
|
||||
public IDictionary<string, string> Sections { get; set; } = new Dictionary<string, string>(StringComparer.Ordinal);
|
||||
|
||||
/// <summary>
|
||||
/// 配置文件版本号,用于版本控制和兼容性检查
|
||||
|
||||
@ -135,7 +135,7 @@ public class AutoRegisterExportedCollectionsGeneratorTests
|
||||
.WithLocation(0)
|
||||
.WithArguments("Values"));
|
||||
|
||||
await test.RunAsync();
|
||||
await test.RunAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -302,7 +302,7 @@ public class AutoRegisterExportedCollectionsGeneratorTests
|
||||
.WithLocation(0)
|
||||
.WithArguments("Register", "_registry", "Values"));
|
||||
|
||||
await test.RunAsync();
|
||||
await test.RunAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -500,7 +500,7 @@ public class AutoRegisterExportedCollectionsGeneratorTests
|
||||
.WithLocation(0)
|
||||
.WithArguments("_registry", "Values"));
|
||||
|
||||
await test.RunAsync();
|
||||
await test.RunAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -555,7 +555,7 @@ public class AutoRegisterExportedCollectionsGeneratorTests
|
||||
.WithLocation(0)
|
||||
.WithArguments("Register", "_registry", "Values"));
|
||||
|
||||
await test.RunAsync();
|
||||
await test.RunAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -610,7 +610,7 @@ public class AutoRegisterExportedCollectionsGeneratorTests
|
||||
.WithLocation(0)
|
||||
.WithArguments("Values"));
|
||||
|
||||
await test.RunAsync();
|
||||
await test.RunAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -694,7 +694,7 @@ public class AutoRegisterExportedCollectionsGeneratorTests
|
||||
""";
|
||||
}
|
||||
|
||||
private static Task VerifyDiagnosticsAsync(
|
||||
private static async Task VerifyDiagnosticsAsync(
|
||||
string source,
|
||||
bool skipGeneratedSourcesCheck = false,
|
||||
params DiagnosticResult[] expectedDiagnostics)
|
||||
@ -718,6 +718,6 @@ public class AutoRegisterExportedCollectionsGeneratorTests
|
||||
test.ExpectedDiagnostics.Add(expectedDiagnostic);
|
||||
}
|
||||
|
||||
return test.RunAsync();
|
||||
await test.RunAsync().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
@ -20,7 +20,7 @@ public sealed class AbstractArchitectureModuleInstallationTests
|
||||
var module = new RecordingGodotModule();
|
||||
|
||||
var exception = Assert.ThrowsAsync<InvalidOperationException>(async () =>
|
||||
await architecture.InstallGodotModuleForTestAsync(module));
|
||||
await architecture.InstallGodotModuleForTestAsync(module).ConfigureAwait(false));
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
|
||||
@ -197,7 +197,7 @@ public sealed class GodotYamlConfigLoaderTests
|
||||
var loader = CreateLoader(isEditor: false);
|
||||
|
||||
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () =>
|
||||
await loader.LoadAsync(new ConfigRegistry()));
|
||||
await loader.LoadAsync(new ConfigRegistry()).ConfigureAwait(false));
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
@ -225,7 +225,7 @@ public sealed class GodotYamlConfigLoaderTests
|
||||
configureLoader: static _ => { });
|
||||
|
||||
var exception = Assert.ThrowsAsync<ArgumentException>(async () =>
|
||||
await loader.LoadAsync(new ConfigRegistry()));
|
||||
await loader.LoadAsync(new ConfigRegistry()).ConfigureAwait(false));
|
||||
|
||||
Assert.That(exception!.ParamName, Is.EqualTo("relativePath"));
|
||||
}
|
||||
@ -254,7 +254,7 @@ public sealed class GodotYamlConfigLoaderTests
|
||||
configureLoader: static _ => { });
|
||||
|
||||
var exception = Assert.ThrowsAsync<ArgumentException>(async () =>
|
||||
await loader.LoadAsync(new ConfigRegistry()));
|
||||
await loader.LoadAsync(new ConfigRegistry()).ConfigureAwait(false));
|
||||
|
||||
Assert.That(exception!.ParamName, Is.EqualTo("relativePath"));
|
||||
}
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using GFramework.Godot.Text;
|
||||
|
||||
namespace GFramework.Godot.Tests.Text;
|
||||
@ -25,7 +26,7 @@ public sealed class RichTextMarkupTests
|
||||
[Test]
|
||||
public void Effect_Should_Sort_Environment_Parameters_By_Key()
|
||||
{
|
||||
var env = new Dictionary<string, object?>
|
||||
var env = new Dictionary<string, object?>(StringComparer.Ordinal)
|
||||
{
|
||||
["tick"] = 0.1f,
|
||||
["speed"] = 4
|
||||
@ -53,7 +54,7 @@ public sealed class RichTextMarkupTests
|
||||
[Test]
|
||||
public void Effect_Should_Reject_Invalid_Environment_Key_Tokens()
|
||||
{
|
||||
var env = new Dictionary<string, object?>
|
||||
var env = new Dictionary<string, object?>(StringComparer.Ordinal)
|
||||
{
|
||||
["bad key"] = 1
|
||||
};
|
||||
|
||||
@ -112,8 +112,8 @@ public abstract class AbstractArchitecture(
|
||||
// 在附加流程完成前先登记模块,保证后续任一步失败时仍能参与架构销毁阶段的清理。
|
||||
_extensions.Add(module);
|
||||
|
||||
// 等待锚点准备就绪,并保持 Godot 同步上下文,以便后续附加逻辑安全访问节点 API。
|
||||
await anchor.WaitUntilReadyAsync();
|
||||
// 显式保留 Godot 同步上下文,确保后续 AddChild 和 OnAttach 仍在节点可访问的主线程执行。
|
||||
await anchor.WaitUntilReadyAsync().ConfigureAwait(true);
|
||||
|
||||
// 延迟调用将扩展节点添加为锚点的子节点
|
||||
anchor.CallDeferred(Node.MethodName.AddChild, module.Node);
|
||||
|
||||
@ -104,7 +104,12 @@ internal sealed class GodotYamlConfigEnvironment
|
||||
|
||||
private static IReadOnlyList<GodotYamlConfigDirectoryEntry>? EnumerateDirectoryCore(string path)
|
||||
{
|
||||
if (!path.IsGodotPath())
|
||||
return path.IsGodotPath()
|
||||
? EnumerateGodotDirectory(path)
|
||||
: EnumerateFileSystemDirectory(path);
|
||||
}
|
||||
|
||||
private static IReadOnlyList<GodotYamlConfigDirectoryEntry>? EnumerateFileSystemDirectory(string path)
|
||||
{
|
||||
try
|
||||
{
|
||||
@ -120,25 +125,15 @@ internal sealed class GodotYamlConfigEnvironment
|
||||
Directory.Exists(entryPath)))
|
||||
.ToArray();
|
||||
}
|
||||
catch (IOException)
|
||||
catch (Exception ex) when (IsExpectedDirectoryEnumerationException(ex))
|
||||
{
|
||||
// 非 Godot 路径分支与公开契约保持一致:宿主无法访问目录时返回 null,而不是泄漏底层异常。
|
||||
return null;
|
||||
}
|
||||
catch (UnauthorizedAccessException)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
catch (ArgumentException)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
catch (NotSupportedException)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static IReadOnlyList<GodotYamlConfigDirectoryEntry>? EnumerateGodotDirectory(string path)
|
||||
{
|
||||
using var directory = DirAccess.Open(path);
|
||||
if (directory == null)
|
||||
{
|
||||
@ -170,9 +165,15 @@ internal sealed class GodotYamlConfigEnvironment
|
||||
// 目录枚举句柄必须成对结束,避免未来循环体扩展后在异常路径上遗留引擎状态。
|
||||
directory.ListDirEnd();
|
||||
}
|
||||
|
||||
return entries;
|
||||
}
|
||||
|
||||
private static bool IsExpectedDirectoryEnumerationException(Exception exception)
|
||||
{
|
||||
return exception is IOException or UnauthorizedAccessException or ArgumentException or NotSupportedException;
|
||||
}
|
||||
|
||||
private static bool FileExistsCore(string path)
|
||||
{
|
||||
return path.IsGodotPath()
|
||||
|
||||
@ -144,7 +144,8 @@ public abstract class SceneBehaviorBase<T> : ISceneBehavior
|
||||
public virtual async ValueTask OnPauseAsync()
|
||||
{
|
||||
if (_scene != null)
|
||||
await _scene.OnPauseAsync();
|
||||
// 暂停后紧接着会修改 Owner 的处理开关,必须回到 Godot 主线程继续执行。
|
||||
await _scene.OnPauseAsync().ConfigureAwait(true);
|
||||
|
||||
// 暂停处理
|
||||
Owner.SetProcess(false);
|
||||
@ -165,7 +166,8 @@ public abstract class SceneBehaviorBase<T> : ISceneBehavior
|
||||
return;
|
||||
|
||||
if (_scene != null)
|
||||
await _scene.OnResumeAsync();
|
||||
// 恢复完成后要立刻重新启用节点处理流程,因此显式保留当前同步上下文。
|
||||
await _scene.OnResumeAsync().ConfigureAwait(true);
|
||||
|
||||
// 恢复处理
|
||||
Owner.SetProcess(true);
|
||||
@ -198,7 +200,8 @@ public abstract class SceneBehaviorBase<T> : ISceneBehavior
|
||||
public virtual async ValueTask OnUnloadAsync()
|
||||
{
|
||||
if (_scene != null)
|
||||
await _scene.OnUnloadAsync();
|
||||
// 卸载后的 QueueFreeX 必须在 Godot 节点线程上调用,不能切走同步上下文。
|
||||
await _scene.OnUnloadAsync().ConfigureAwait(true);
|
||||
|
||||
// 释放节点
|
||||
Owner.QueueFreeX();
|
||||
|
||||
@ -6,32 +6,41 @@
|
||||
|
||||
## 当前恢复点
|
||||
|
||||
- 恢复点编号:`ANALYZER-WARNING-REDUCTION-RP-051`
|
||||
- 当前阶段:`Phase 51`
|
||||
- 恢复点编号:`ANALYZER-WARNING-REDUCTION-RP-058`
|
||||
- 当前阶段:`Phase 58`
|
||||
- 当前焦点:
|
||||
- `2026-04-24` 本轮已完成 `GFramework.Godot.SourceGenerators.Tests` warning 清理
|
||||
- 当前主线程切片从生成器实现转到对应测试项目,并已把 `GFramework.Godot.SourceGenerators.Tests` 从 `24` 个 warning 降到 `0`
|
||||
- 当前批次按 `origin/main` merge-base 计算的累计分支 diff 预计为 `23` 个文件,仍低于 `$gframework-batch-boot 75` 的主阈值
|
||||
- 当前工作树除未跟踪的 `.codex` 目录外,还存在与本批次无关的既有文档 / 跟踪文件修改;提交当前批次时必须只包含本 topic 相关文件
|
||||
- `2026-04-24` 使用 `$gframework-pr-review` 复核当前分支 PR #286 的 latest-head review threads、MegaLinter 与测试状态
|
||||
- 已确认最新 head 上唯一未解决的实质代码线程指向 `GFramework.Godot/Scene/SceneBehaviorBase.cs` 中 `OnPauseAsync` 的缩进异常,并顺带对齐 `OnResumeAsync`、`OnUnloadAsync`
|
||||
- `dotnet build GFramework.Godot/GFramework.Godot.csproj -c Release` 通过,结果为 `565 Warning(s)`、`0 Error(s)`;当前跟进只处理 PR review 指向的格式问题,不扩散到既有 warning 基线
|
||||
- `dotnet format GFramework.Godot/GFramework.Godot.csproj --verify-no-changes --no-restore --include GFramework.Godot/Scene/SceneBehaviorBase.cs` 已通过,当前文件不再残留格式差异
|
||||
|
||||
## 当前活跃事实
|
||||
|
||||
- 之前记录的 plain `dotnet build` `0 Warning(s)` 属于增量构建假阴性,不能再作为 warning 检查真值
|
||||
- 本轮已完成 `GFramework.Godot.SourceGenerators` warning 清理:clean `Release` build 从 9 个 warning 降至 0 个 warning
|
||||
- 当前已确认解决的文件包括 `BindNodeSignalGenerator.cs`、`GetNodeGenerator.cs`、`GodotProjectMetadataGenerator.cs`、`Registration/AutoRegisterExportedCollectionsGenerator.cs`
|
||||
- 本轮直接执行仓库根目录 `dotnet clean` 仍在 `ValidateSolutionConfiguration` 阶段失败,输出未提供具体 error 文本
|
||||
- 本轮直接执行仓库根目录 `dotnet build` 成功,并给出 `1184 warning(s)` 的真实输出
|
||||
- `GFramework.Godot.SourceGenerators.Tests` 已通过测试辅助模板抽取与 `ConfigureAwait(false)` 修正,当前 `Debug` / `Release` 构建均为 `0 Warning(s)`
|
||||
- 本轮已验证 `dotnet test GFramework.Godot.SourceGenerators.Tests/GFramework.Godot.SourceGenerators.Tests.csproj -c Release --no-build`,结果为 `Passed: 48`
|
||||
- 仓库根目录 `dotnet clean GFramework.sln -c Release` 仍在 `ValidateSolutionConfiguration` 阶段失败,项目级 `dotnet clean GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release` 也未能稳定提供 clean 基线
|
||||
- 当前整仓最近一次直接观测值仍是 `dotnet build GFramework.sln -c Release` 的 `116 warning(s)`
|
||||
- `RP-056` 已验证 `GeneratedConfigConsumerIntegrationTests.cs` 不再出现在项目 build warning 输出中
|
||||
- `RP-057` 已验证 `PersistenceTests.cs` 不再出现在 `dotnet build GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release --no-incremental` 的 warning 输出中
|
||||
- 本轮已验证 `dotnet test GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release --filter "FullyQualifiedName~UnifiedSettingsDataRepository_SaveAsync_When_Persist_Fails_Should_Keep_Cache_Consistent|FullyQualifiedName~UnifiedSettingsDataRepository_DeleteAsync_When_Persist_Fails_Should_Keep_Cache_Consistent"`,结果为 `Passed: 2`
|
||||
- `GFramework.Game.Tests` 当前剩余热点已经几乎完全集中到 `YamlConfigLoaderTests.cs` 这一高上下文文件
|
||||
- PR #286 当前标题为 `Fix/analyzer warning reduction batch`;最新抓取时间点的 PR 状态仍为 `OPEN`
|
||||
- 最新 reviewed commit 为 `2b707343577193fc9904517e6078149653e95698`,CodeRabbit 于 `2026-04-24T12:44:12Z` 给出 `CHANGES_REQUESTED`
|
||||
- latest-head review threads 中只有 `1` 个未解决线程,内容是 `SceneBehaviorBase.OnPauseAsync` 的缩进不一致;本地源码已修复并扩展到同段的 `OnResumeAsync` / `OnUnloadAsync`
|
||||
- MegaLinter 的 `dotnet-format` 详细问题与上述格式异常一致;本地 `dotnet format --verify-no-changes` 已通过
|
||||
- PR 上其余 nitpick 仅为可选建议或已明确留待后续批次处理,当前没有额外需要立即修复的 latest-head 代码线程
|
||||
|
||||
## 当前风险
|
||||
|
||||
- 如果后续继续依赖增量 `dotnet build`,容易再次把 warning 数量误判为 0
|
||||
- 缓解措施:每轮 warning 检查前先执行 `dotnet clean`,再执行目标 `dotnet build`
|
||||
- 仓库根目录 `dotnet clean` 目前仍然无法给出新的 clean 基线
|
||||
- 缓解措施:若下一轮继续做整仓 warning reduction,先定位 `dotnet clean` 的 solution-level 失败原因,或明确继续沿用用户确认的 `1193 warning(s)` clean 基线与本轮 `1184 warning(s)` direct build 观测值
|
||||
- 当前 worktree 已存在与本批次无关的未提交改动
|
||||
- 缓解措施:提交当前批次时只暂存 `GFramework.Godot.SourceGenerators.Tests` 与对应 `ai-plan` 文件,避免混入其他 topic 变更
|
||||
- 仓库根目录与 `GFramework.Game.Tests` 的 `dotnet clean` 目前都无法给出新的 clean 基线
|
||||
- 缓解措施:后续若继续整仓 warning reduction,需要单独定位 clean 失败原因,或明确继续沿用 direct build 观测值作为临时真值
|
||||
- 当前 worktree 仍存在未跟踪的 `.codex` 目录
|
||||
- 缓解措施:提交当前批次时只暂存 analyzer-warning-reduction 相关源码与 `ai-plan` 文件,避免把工作目录辅助文件混入提交
|
||||
- 下一轮若继续深入 `GFramework.Game.Tests`,很可能需要进入 `YamlConfigLoaderTests.cs` 这种高上下文大文件
|
||||
- 缓解措施:把它单独作为一个明确的新批次处理,不与其它 warning family 混批
|
||||
- PR 标题检查当前仍显示 `Inconclusive`
|
||||
- 缓解措施:如需让该检查转绿,需要单独更新 GitHub PR 标题;这不属于本地代码修改范围
|
||||
|
||||
## 活跃文档
|
||||
|
||||
@ -47,19 +56,31 @@
|
||||
|
||||
## 验证说明
|
||||
|
||||
- `dotnet clean`
|
||||
- `dotnet clean GFramework.sln -c Release`
|
||||
- 结果:失败;停在 solution `ValidateSolutionConfiguration`,`0 Warning(s)`、`0 Error(s)`,未输出更具体的 error 文本
|
||||
- `dotnet build`
|
||||
- 结果:成功;`1184 Warning(s)`、`0 Error(s)`
|
||||
- `dotnet build GFramework.Godot.SourceGenerators.Tests/GFramework.Godot.SourceGenerators.Tests.csproj`
|
||||
- 初始结果:成功;`24 Warning(s)`、`0 Error(s)`
|
||||
- 本轮收尾结果:成功;`0 Warning(s)`、`0 Error(s)`
|
||||
- `dotnet build GFramework.Godot.SourceGenerators.Tests/GFramework.Godot.SourceGenerators.Tests.csproj -c Release`
|
||||
- 结果:成功;`0 Warning(s)`、`0 Error(s)`
|
||||
- `dotnet test GFramework.Godot.SourceGenerators.Tests/GFramework.Godot.SourceGenerators.Tests.csproj -c Release --no-build`
|
||||
- 结果:成功;`Passed: 48`、`Failed: 0`
|
||||
- `dotnet build GFramework.sln -c Release`
|
||||
- 结果:成功;`116 Warning(s)`、`0 Error(s)`
|
||||
- `dotnet clean GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release`
|
||||
- 结果:失败;clean 阶段在 MSBuild 清理路径结束前返回 `0 Warning(s)`、`0 Error(s)`,未输出额外错误文本
|
||||
- `dotnet build GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release`
|
||||
- `RP-055` 收尾结果:成功;`63 Warning(s)`、`0 Error(s)`
|
||||
- `RP-056` 当前结果:成功;`59 Warning(s)`、`0 Error(s)`
|
||||
- `dotnet build GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release --no-incremental`
|
||||
- `RP-057` 热点重排前:成功;`253 Warning(s)`、`0 Error(s)`
|
||||
- `RP-057` 当前结果:成功;`249 Warning(s)`、`0 Error(s)`
|
||||
- `dotnet test GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release --filter "FullyQualifiedName~ArchitectureConfigIntegrationTests|FullyQualifiedName~GameConfigBootstrapTests|FullyQualifiedName~JsonSerializerTests"`
|
||||
- 结果:成功;`Passed: 19`、`Failed: 0`
|
||||
- `dotnet test GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release --filter "FullyQualifiedName~GeneratedConfigConsumerIntegrationTests"`
|
||||
- 结果:成功;`Passed: 4`、`Failed: 0`
|
||||
- `dotnet test GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release --filter "FullyQualifiedName~UnifiedSettingsDataRepository_SaveAsync_When_Persist_Fails_Should_Keep_Cache_Consistent|FullyQualifiedName~UnifiedSettingsDataRepository_DeleteAsync_When_Persist_Fails_Should_Keep_Cache_Consistent"`
|
||||
- 结果:成功;`Passed: 2`、`Failed: 0`
|
||||
- `dotnet build GFramework.Godot/GFramework.Godot.csproj -c Release`
|
||||
- 结果:成功;`565 Warning(s)`、`0 Error(s)`
|
||||
- `dotnet format GFramework.Godot/GFramework.Godot.csproj --verify-no-changes --no-restore --include GFramework.Godot/Scene/SceneBehaviorBase.cs`
|
||||
- 首次运行:失败;restore 阶段异常退出,未进入格式验证
|
||||
- 第二次运行(同命令追加 sandbox 提权):成功;workspace 仅提示加载 warning,无格式差异
|
||||
|
||||
## 下一步建议
|
||||
|
||||
1. 提交当前 `GFramework.Godot.SourceGenerators.Tests` 清理批次,并确认提交只包含本 topic 相关文件
|
||||
2. 如果继续 warning reduction,优先重新评估仓库根目录 `dotnet clean` 的 solution-level 失败,再决定是继续从整仓 `dotnet build` 输出挑热点,还是先修复 clean 基线采集问题
|
||||
1. 提交 `SceneBehaviorBase.cs` 与 `RP-058` tracking/trace 更新,清掉 PR #286 当前 latest-head 上的格式类 review thread
|
||||
2. 若继续 warning reduction 主线,应回到 `GFramework.Game.Tests/Config/YamlConfigLoaderTests.cs`,把它作为独立高上下文批次处理
|
||||
|
||||
@ -1,5 +1,189 @@
|
||||
# Analyzer Warning Reduction 追踪
|
||||
|
||||
# Analyzer Warning Reduction 追踪
|
||||
|
||||
## 2026-04-24 — RP-058
|
||||
|
||||
### 阶段:PR #286 latest-head review 格式跟进
|
||||
|
||||
- 触发背景:
|
||||
- 用户要求执行 `$gframework-pr-review`,需要以当前分支 PR 页面而不是本地记忆为准,重新核对 CodeRabbit、MegaLinter 和测试状态
|
||||
- 抓取脚本当前解析到的 PR 是 `#286`,最新 reviewed commit 为 `2b707343577193fc9904517e6078149653e95698`
|
||||
- 最新 head 上真正未解决的代码线程只剩 `GFramework.Godot/Scene/SceneBehaviorBase.cs:148` 的缩进问题;其余 nitpick 为可选建议或已留待后续批次
|
||||
- 主线程实施:
|
||||
- 运行 `python3 .agents/skills/gframework-pr-review/scripts/fetch_current_pr_review.py --json-output /tmp/current-pr-review.json`,确认 PR `OPEN`、测试 `2156/2156` 通过、MegaLinter 仅剩 `dotnet-format` 警告
|
||||
- 复核 `SceneBehaviorBase.cs` 后确认 `OnPauseAsync` 的方法签名与方法体缩进异常仍存在于本地源码;同段的 `OnResumeAsync`、`OnUnloadAsync` 也有同类偏差
|
||||
- 在不改变行为的前提下统一修正三个方法的缩进,保持现有 XML 注释、`ConfigureAwait(true)` 语义与 Godot 主线程说明不变
|
||||
- 更新 active tracking / trace,记录当前 PR review follow-up 已完成,本地剩余外部信号只剩 PR 标题检查
|
||||
- 验证里程碑:
|
||||
- `dotnet build GFramework.Godot/GFramework.Godot.csproj -c Release`
|
||||
- 结果:成功;`565 Warning(s)`、`0 Error(s)`
|
||||
- 结论:当前格式修复未引入编译错误;模块既有 warning 基线仍存在,但不属于本次 PR review 跟进范围
|
||||
- `dotnet format GFramework.Godot/GFramework.Godot.csproj --verify-no-changes --no-restore --include GFramework.Godot/Scene/SceneBehaviorBase.cs`
|
||||
- 首次运行:失败;sandbox 环境下在 build host / pipe 建立阶段报错,未进入真实格式比较
|
||||
- 提权复验:成功;仅提示 workspace load warning,无格式差异
|
||||
- 当前结论:
|
||||
- PR #286 当前 latest-head 上唯一未解决的实质代码 review thread 已在本地修复
|
||||
- MegaLinter 暴露的 `dotnet-format` 问题已被本地 `verify-no-changes` 复验覆盖
|
||||
- `Title check: Inconclusive` 仍然存在,但属于 GitHub PR 标题元数据问题,不能通过本地代码提交直接消除
|
||||
|
||||
## 2026-04-24 — RP-057
|
||||
|
||||
### 阶段:清理 `PersistenceTests.cs` 残余 `MA0004`
|
||||
|
||||
- 触发背景:
|
||||
- `RP-056` 提交后重新做非增量热点排序时,`GFramework.Game.Tests` 的剩余测试项目 warning 已明显收敛,只剩 `PersistenceTests.cs` 少量 `MA0004` 与 `YamlConfigLoaderTests.cs` 大量 warning
|
||||
- 为避免在同一轮直接进入 `YamlConfigLoaderTests.cs` 的大文件高上下文批次,先吃掉 `PersistenceTests.cs` 这个独立小切片
|
||||
- 主线程实施:
|
||||
- 在 `PersistenceTests.cs` 中为统一设置仓库失败缓存一致性相关测试补齐剩余 `.ConfigureAwait(false)`
|
||||
- 覆盖保存失败与删除失败两个测试场景中的缓存读取、存在性检查、后续保存和最终验证读取
|
||||
- 更新 active tracking / trace,明确下一批若继续推进应单独进入 `YamlConfigLoaderTests.cs`
|
||||
- 验证里程碑:
|
||||
- `dotnet build GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release --no-incremental`
|
||||
- 热点重排前:成功;`253 Warning(s)`、`0 Error(s)`
|
||||
- 修复后:成功;`249 Warning(s)`、`0 Error(s)`
|
||||
- 结论:`PersistenceTests.cs` 不再出现在 warning 输出中
|
||||
- `dotnet test GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release --filter "FullyQualifiedName~UnifiedSettingsDataRepository_SaveAsync_When_Persist_Fails_Should_Keep_Cache_Consistent|FullyQualifiedName~UnifiedSettingsDataRepository_DeleteAsync_When_Persist_Fails_Should_Keep_Cache_Consistent"`
|
||||
- 结果:成功;`Passed: 2`、`Failed: 0`
|
||||
- 当前结论:
|
||||
- `PersistenceTests.cs` 的残余 warning 已清零,`GFramework.Game.Tests` 剩余热点几乎全部压缩到了 `YamlConfigLoaderTests.cs`
|
||||
- 当前工作树投影下,分支体积为 `27` 个文件、`991` 行,仍低于 `$gframework-batch-boot 75`
|
||||
- 按 batch skill 的低风险边界,这一轮应在提交后收口;下一轮再把 `YamlConfigLoaderTests.cs` 作为单独批次处理
|
||||
|
||||
## 2026-04-24 — RP-056
|
||||
|
||||
### 阶段:修复 `GeneratedConfigConsumerIntegrationTests` 编译错误并清零该文件 warning
|
||||
|
||||
- 触发背景:
|
||||
- `RP-055` 继续推进时,`GeneratedConfigConsumerIntegrationTests.cs` 在 raw string `invalidYaml` 段落附近出现 `CS8999`,导致 `GFramework.Game.Tests` 暂时无法编译
|
||||
- 该文件同时仍是项目内少数残留 warning 热点之一,因此适合作为同一批次中的单文件收尾
|
||||
- 主线程实施:
|
||||
- 修复 `GeneratedConfigConsumerIntegrationTests.cs` 中损坏的 `CreateMonsterFiles` raw string 与方法边界,恢复文件可编译状态
|
||||
- 保留并整理上一轮已开始的 `.ConfigureAwait(false)` 与断言 helper 抽取
|
||||
- 继续将 `AssertGeneratedBindingsLoadResults` 再拆分为 catalog / monster / item 三个辅助方法,清除该文件剩余 `MA0051`
|
||||
- 更新 active tracking / trace,沿用 `merge-base(origin/main, HEAD)` 作为 `$gframework-batch-boot 75` 的唯一 stop-condition 口径
|
||||
- 验证里程碑:
|
||||
- `dotnet build GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release`
|
||||
- 结果:成功;`59 Warning(s)`、`0 Error(s)`
|
||||
- 结论:`GeneratedConfigConsumerIntegrationTests.cs` 不再出现在 warning 输出中
|
||||
- `dotnet test GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release --filter "FullyQualifiedName~GeneratedConfigConsumerIntegrationTests"`
|
||||
- 结果:成功;`Passed: 4`、`Failed: 0`
|
||||
- 当前结论:
|
||||
- `GFramework.Game.Tests` 已从 `RP-055` 收尾时的 `63 warning(s)` 进一步收敛到 `59 warning(s)`
|
||||
- 当前工作树投影下,分支体积为 `27` 个文件、`943` 行,仍低于 `$gframework-batch-boot 75`
|
||||
- 后续若继续自动推进,最自然的下一批将进入 `YamlConfigLoaderTests.cs` 这类高上下文大文件
|
||||
|
||||
## 2026-04-24 — RP-055
|
||||
|
||||
### 阶段:修正 stop-condition 口径并继续 `GFramework.Game.Tests` 小热点
|
||||
|
||||
- 触发背景:
|
||||
- `RP-054` 之后复核 batch stop-condition 时,发现之前一度把工作树 diff 错当成了 skill 要求的 branch diff
|
||||
- 按正确口径 `merge-base(origin/main, HEAD)` 计算,`RP-054` 提交后的真实分支体积是 `23` 个文件、`603` 行,因此仍可继续下一批
|
||||
- 当前剩余 warning 里,`ArchitectureConfigIntegrationTests`、`GameConfigBootstrapTests`、`JsonSerializerTests` 属于独立且低风险的小切片
|
||||
- 主线程实施:
|
||||
- 在 `ArchitectureConfigIntegrationTests.cs` 中补齐异步架构初始化 / 销毁和异常断言的 `.ConfigureAwait(false)`
|
||||
- 在 `GameConfigBootstrapTests.cs` 中补齐启动流程、并发初始化断言与 `WaitForTaskWithinAsync` 的 `.ConfigureAwait(false)`
|
||||
- 在 `JsonSerializerTests.cs` 中将坐标解析改为 `CultureInfo.InvariantCulture`
|
||||
- 顺手清理 `YamlConfigLoaderAllOfTests.cs` 与 `PersistenceTests.cs` 中上一批遗漏的字段态状态检查和异步等待 warning
|
||||
- 纠正 active tracking:明确 stop-condition 必须使用 `origin/main...HEAD` 的 merge-base 分支 diff,而不是工作树 diff
|
||||
- 验证里程碑:
|
||||
- `dotnet build GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release`
|
||||
- 并行误用 build/test 时:出现 `MSB3026` / `CS2012` 文件占用噪声,不计入代码结论
|
||||
- 串行复验:成功;`63 Warning(s)`、`0 Error(s)`
|
||||
- `dotnet test GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release --filter "FullyQualifiedName~ArchitectureConfigIntegrationTests|FullyQualifiedName~GameConfigBootstrapTests|FullyQualifiedName~JsonSerializerTests"`
|
||||
- 结果:成功;`Passed: 19`、`Failed: 0`
|
||||
- 当前结论:
|
||||
- `GFramework.Game.Tests` 已从上一批收尾时的 `71 warning(s)` 进一步降到 `63 warning(s)`
|
||||
- 这次提交后的分支体积投影为 `26` 个文件、`691` 行,仍低于 `$gframework-batch-boot 75`
|
||||
- 剩余热点越来越集中到 `YamlConfigLoaderTests.cs` 与 `GeneratedConfigConsumerIntegrationTests.cs`,后续继续时应把它们视为高上下文批次
|
||||
|
||||
## 2026-04-24 — RP-054
|
||||
|
||||
### 阶段:`GFramework.Game.Tests` 低风险测试 warning 批次(触发文件数停止阈值)
|
||||
|
||||
- 触发背景:
|
||||
- 用户要求“直接进入下一批”,继续沿 `$gframework-batch-boot 75` 自动推进 warning reduction
|
||||
- 以 `origin/main` 为基线时,上一批提交后分支累计 diff 仍只有 `8` 个文件,足够再落一个独立批次
|
||||
- 重新执行 `dotnet clean GFramework.sln -c Release` 仍停在 `ValidateSolutionConfiguration`,因此继续以直接 `dotnet build GFramework.sln -c Release` 的输出挑选低风险热点
|
||||
- 主线程实施:
|
||||
- 从整仓 `Release build` 的 `116 warning(s)` 入口观测值中,选择 `GFramework.Game.Tests` 的小型测试文件和 `PersistenceTestUtilities.cs` 作为当前批次,刻意避开 `YamlConfigLoaderTests.cs` 这类高上下文大文件
|
||||
- 在 `YamlConfigLoaderIfThenElseTests.cs`、`YamlConfigLoaderDependentSchemasTests.cs`、`YamlConfigLoaderDependentRequiredTests.cs`、`YamlConfigLoaderNegationTests.cs`、`YamlConfigLoaderAllOfTests.cs`、`YamlConfigLoaderEnumTests.cs`、`YamlConfigTextValidatorTests.cs`、`PersistenceTests.cs` 中补齐 `.ConfigureAwait(false)`,并把字段态 `_rootPath` 的 `ThrowIfNull` 改为显式 `InvalidOperationException`
|
||||
- 将 `PersistenceTestUtilities.cs` 拆分为 `TestDataLocation.cs`、`TestSaveData.cs`、`TestVersionedSaveData.cs`、`TestSimpleData.cs`、`TestNamedData.cs`,消除 `MA0048` 并对齐仓库的一文件一主类型风格
|
||||
- 在 `YamlConfigSchemaValidatorTests.cs` 中把字段态 `_rootPath` 的校验改成显式状态异常,避免继续触发 `MA0015`
|
||||
- 验证里程碑:
|
||||
- `dotnet clean GFramework.sln -c Release`
|
||||
- 结果:失败;停在 `ValidateSolutionConfiguration`,`0 Warning(s)`、`0 Error(s)`
|
||||
- `dotnet build GFramework.sln -c Release`
|
||||
- 结果:成功;`116 Warning(s)`、`0 Error(s)`
|
||||
- `dotnet clean GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release`
|
||||
- 结果:失败;clean 阶段提前结束,`0 Warning(s)`、`0 Error(s)`
|
||||
- `dotnet build GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release`
|
||||
- 第一轮批次后:成功;`80 Warning(s)`、`0 Error(s)`
|
||||
- 收尾修正后:成功;`71 Warning(s)`、`0 Error(s)`
|
||||
- `dotnet test GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release --filter "FullyQualifiedName~YamlConfigLoaderIfThenElseTests|FullyQualifiedName~YamlConfigLoaderDependentSchemasTests|FullyQualifiedName~YamlConfigLoaderDependentRequiredTests|FullyQualifiedName~YamlConfigLoaderNegationTests|FullyQualifiedName~YamlConfigLoaderAllOfTests|FullyQualifiedName~YamlConfigLoaderEnumTests|FullyQualifiedName~YamlConfigTextValidatorTests|FullyQualifiedName~YamlConfigSchemaValidatorTests|FullyQualifiedName~PersistenceTests"`
|
||||
- 结果:成功;`Passed: 63`、`Failed: 0`
|
||||
- 当前结论:
|
||||
- `GFramework.Game.Tests` 本轮入口热点已从 `116 warning(s)` 收敛到 `71 warning(s)`,且本轮 touched files 不再出现在 warning 输出中
|
||||
- 当前工作树相对 `origin/main` 的累计 diff 已达到 `76` 个文件、`986` 行,超过 `$gframework-batch-boot 75` 的主停止阈值
|
||||
- 按批处理技能规则,本轮必须在提交当前批次后停止;剩余候选应在新一轮里单独评估,尤其是 `YamlConfigLoaderTests.cs`
|
||||
|
||||
## 2026-04-24 — RP-053
|
||||
|
||||
### 阶段:`GFramework.Godot` / `GFramework.Godot.Tests` 小批次 warning 清理
|
||||
|
||||
- 触发背景:
|
||||
- 用户以 `$gframework-batch-boot 75` 要求继续按批次推进 analyzer warning reduction,并以 `origin/main` 作为累计分支 diff 基线
|
||||
- 当前 worktree `fix/analyzer-warning-reduction-batch` 相对 `origin/main` 的已提交分支 diff 为 `0` 个文件,具备继续落一个低风险 warning batch 的空间
|
||||
- solution-level `dotnet clean GFramework.sln -c Release` 仍在 `ValidateSolutionConfiguration` 阶段失败,因此本轮继续用直接 `dotnet build GFramework.sln -c Release` 建立热点观察值
|
||||
- 主线程实施:
|
||||
- 运行 `dotnet build GFramework.sln -c Release`,确认当前整仓观测值为 `1122 warning(s)`,并从输出中挑选 `GFramework.Godot` 的小范围热点作为本轮批次
|
||||
- 在 `GodotYamlConfigEnvironment.cs` 中按“普通文件系统 / Godot 路径”拆分目录枚举 helper,消除 `MA0051`
|
||||
- 在 `AbstractArchitecture.cs` 与 `SceneBehaviorBase.cs` 中将必须保留 Godot 主线程上下文的 await 显式改为 `.ConfigureAwait(true)`,清理 `MA0004` 并把线程意图写入注释
|
||||
- 在 `GFramework.Godot.Tests` 中补齐异步断言的 `.ConfigureAwait(false)`,并让 `RichTextMarkupTests` 的测试字典显式指定 `StringComparer.Ordinal`
|
||||
- 验证里程碑:
|
||||
- `dotnet clean GFramework.sln -c Release`
|
||||
- 结果:失败;停在 `ValidateSolutionConfiguration`,`0 Warning(s)`、`0 Error(s)`
|
||||
- `dotnet build GFramework.sln -c Release`
|
||||
- 结果:成功;`1122 Warning(s)`、`0 Error(s)`
|
||||
- `dotnet build GFramework.Godot/GFramework.Godot.csproj -c Release`
|
||||
- 第一轮修复后:成功;`12 Warning(s)`、`0 Error(s)`,仅剩 `MA0004`
|
||||
- 第二轮修复后:成功;`0 Warning(s)`、`0 Error(s)`
|
||||
- `dotnet test GFramework.Godot.Tests/GFramework.Godot.Tests.csproj -c Release --filter "FullyQualifiedName~AbstractArchitectureModuleInstallationTests|FullyQualifiedName~GodotYamlConfigLoaderTests|FullyQualifiedName~RichTextMarkupTests"`
|
||||
- 结果:成功;`Passed: 15`、`Failed: 0`
|
||||
- `dotnet build GFramework.Godot.Tests/GFramework.Godot.Tests.csproj -c Release`
|
||||
- 并行验证时:成功;`1 Warning(s)`、`0 Error(s)`;`MSB3026` 为与并行 `dotnet test` 竞争输出 DLL 的文件占用
|
||||
- 串行复验:成功;`0 Warning(s)`、`0 Error(s)`
|
||||
- 当前结论:
|
||||
- `GFramework.Godot` 与 `GFramework.Godot.Tests` 本轮直接涉及的 warning 已全部清零
|
||||
- 当前待提交代码批次相对 `origin/main` 的源码 diff 为 `6` 个文件、`107` 行,距离 `$gframework-batch-boot 75` 主停止阈值仍有充足余量
|
||||
- 继续推进的下一批候选将主要落在 `GFramework.Game` 等高 warning 基线模块,已不再属于当前同等级低风险切片,因此本轮在这里收口并进入提交
|
||||
|
||||
## 2026-04-24 — RP-052
|
||||
|
||||
### 阶段:PR review follow-up(comparer 契约 + `ConfigureAwait(false)` 收尾)
|
||||
|
||||
- 触发背景:
|
||||
- 当前分支 PR #283 的最新 review 中,`greptile-apps[bot]` 仍有一个未解决线程,指出 `UnifiedSettingsDataRepository.CloneFile` fallback 会静默丢失原 comparer
|
||||
- CodeRabbit 另指出 `AutoRegisterExportedCollectionsGeneratorTests.cs` 中还残留 5 处 `await test.RunAsync();`,与同项目其他测试文件的 `.ConfigureAwait(false)` 风格不一致
|
||||
- 主线程实施:
|
||||
- 复核 PR review JSON、`UnifiedSettingsDataRepository.cs`、`UnifiedSettingsFile.cs` 与 `AutoRegisterExportedCollectionsGeneratorTests.cs` 的当前代码,确认只有 comparer 契约线程仍属最新 head 上的实质问题
|
||||
- 将 `UnifiedSettingsFile.Sections` 的 XML 注释补充为显式 comparer 契约,并把默认字典初始化改为 `StringComparer.Ordinal`
|
||||
- 将 `CloneFile` fallback 从隐式默认 comparer 改为显式 `StringComparer.Ordinal`,并同步修正文档注释,避免继续暗含“保留原语义”的错误表述
|
||||
- 把 `AutoRegisterExportedCollectionsGeneratorTests` 中剩余的 5 处 `await test.RunAsync();` 统一为 `.ConfigureAwait(false)`,同时让 `VerifyDiagnosticsAsync` 内部也消费 `ConfigureAwait(false)`
|
||||
- 验证里程碑:
|
||||
- `dotnet build GFramework.Game/GFramework.Game.csproj -c Release`
|
||||
- 结果:成功;`533 Warning(s)`、`0 Error(s)`;`GFramework.Game` 仍有既有 warning 基线,本轮 follow-up 仅处理 PR review 指向的 comparer 契约与测试异步等待一致性
|
||||
- `dotnet build GFramework.Godot.SourceGenerators.Tests/GFramework.Godot.SourceGenerators.Tests.csproj -c Release`
|
||||
- 结果:成功;`0 Warning(s)`、`0 Error(s)`
|
||||
- `dotnet test GFramework.Godot.SourceGenerators.Tests/GFramework.Godot.SourceGenerators.Tests.csproj -c Release --no-build`
|
||||
- 首次并行复验:失败;`FileNotFoundException`,原因是 `--no-build` 测试在 Release DLL 落盘前启动
|
||||
- 串行复验:成功;`Passed: 48`、`Failed: 0`
|
||||
- 当前结论:
|
||||
- PR #283 当前仍打开的 comparer review thread 已在本地代码与 XML 注释层面得到对应修复
|
||||
- `AutoRegisterExportedCollectionsGeneratorTests` 的异步等待风格已与同项目其他测试保持一致
|
||||
- 当前改动已通过直接受影响测试项目的 Release build 与串行 Release test 复验,可进入提交阶段
|
||||
|
||||
## 2026-04-24 — RP-051
|
||||
|
||||
### 阶段:`GFramework.Godot.SourceGenerators.Tests` warning 清零
|
||||
|
||||
@ -0,0 +1,133 @@
|
||||
# Documentation Full Coverage Governance Status History RP-023 To RP-025
|
||||
|
||||
以下内容从 active tracking 中迁出,用于保留 `DOCUMENTATION-FULL-COVERAGE-GOV-RP-023` 到
|
||||
`DOCUMENTATION-FULL-COVERAGE-GOV-RP-025` 的批处理状态、验证细节与恢复背景。默认 `boot` 只需要读取 active
|
||||
tracking 中的最新摘要;若需要追溯 `2026-04-23` 到 `2026-04-24` 的治理细节,再回到本归档文件。
|
||||
|
||||
## 迁出的状态摘要
|
||||
|
||||
- `2026-04-23` 基于 PR `#272` 的 review follow-up 已完成:
|
||||
- 为 `docs/zh-CN/game/data.md` 补充 `UnifiedSettingsDataRepository` 的统一文件布局示例
|
||||
- 为 `GFramework.Godot.SourceGenerators/README.md` 补充手写 `_Ready()` / `_ExitTree()` 时显式调用生成方法的最小样例
|
||||
- 将过长的 active tracking / trace 瘦身,并把历史摘要迁回 `archive/`
|
||||
- `2026-04-23` 使用 `$gframework-pr-review` 重新抓取 PR `#272` 后,确认 latest-head review 当前仍有 1 条
|
||||
Greptile open thread,定位到 `docs/zh-CN/godot/setting.md:75` 的 inline code 误写成
|
||||
`SettingsModel<ISettingsDataRepository>`。
|
||||
- 结合当前 PR 已改动的 `docs/zh-CN/godot/storage.md` 做同类巡检后,确认 `SaveRepository<TSaveData>`
|
||||
也会在 VitePress code span 中按字面量渲染;两处现已在本地统一改为真实泛型写法。
|
||||
- `2026-04-23` 以 `origin/main`(`aa879d2`,`2026-04-23T17:51:41+08:00`)为批处理基线,对
|
||||
`README.md`、`GFramework.*` 与 `docs/zh-CN/**` 执行同类模式巡检,确认剩余热点仅位于
|
||||
`docs/zh-CN/core/functional.md` 与 `docs/zh-CN/tutorials/functional-programming.md` 共 8 处。
|
||||
- 上述 8 处 inline code 中的 `Option<T>`、`Result<T>`、`Nullable<T>` 已统一改为真实
|
||||
泛型写法,避免在 VitePress 中显示字面量 HTML entity。
|
||||
- `2026-04-23` 根据本轮使用反馈,已为 `.agents/skills/gframework-batch-boot/SKILL.md` 与
|
||||
`.agents/skills/README.md` 补充数字速记阈值语义:
|
||||
- `$gframework-batch-boot 75` 默认表示“当前分支全部提交相对远程 `origin/main` 接近 75 个分支 diff 文件时停止”
|
||||
- `$gframework-batch-boot 75 2000` 默认表示“当前分支全部提交相对远程 `origin/main` 接近 75 个文件或 2000 行变更时停止”
|
||||
- `75 | 2000` 仅作为可理解的 OR 写法保留,不再作为推荐写法,以避免与 shell pipe 混淆
|
||||
- `2026-04-23` 以 `origin/main`(`aa879d2`,`2026-04-23T17:51:41+08:00`)为批处理基线,对
|
||||
`docs/zh-CN/getting-started/index.md`、`core/index.md`、`game/index.md`、`source-generators/index.md`、
|
||||
`api-reference/index.md`、`abstractions/core-abstractions.md`、`abstractions/game-abstractions.md`
|
||||
做导航可达性修复,把仓库 README / 根 README 裸路径统一改为指向 GitHub `main` 分支的可点击链接。
|
||||
- 该批次不改变文档语义,只收口 docs 站点中的入口可达性;适合继续作为小步快跑的低风险治理模式。
|
||||
- `2026-04-23` 在同一基线下继续收口第二批专题页导航热点,已将 `core/cqrs.md`、`ecs/arch.md`、
|
||||
`abstractions/ecs-arch-abstractions.md`、`game/scene.md`、`game/ui.md` 和 6 个
|
||||
`source-generators/*.md` 专题页里的 README 裸路径统一改为 GitHub `main` blob 外链。
|
||||
- 截至提交 `8a11720`(`2026-04-23T21:01:28+08:00`),当前分支相对 `origin/main`(`aa879d2`)的累计 diff
|
||||
为 `24` 个文件、`264` 行,仍低于 `$gframework-batch-boot 75` 的停止阈值;但剩余命中已主要是正文语义性提及,不再适合作为同类批处理。
|
||||
- 当前剩余的托管侧信号是 GitHub `Title check` 对 PR 标题过泛的 inconclusive 提示;这属于 PR 元数据,不是本地
|
||||
文件缺陷。
|
||||
- `2026-04-24` 根据用户反馈完成一轮“公开文档边界”治理,并继续按 `$gframework-batch-boot 75` 向前推进:
|
||||
- 在 `AGENTS.md`、`.agents/skills/_shared/DOCUMENTATION_STANDARDS.md` 与
|
||||
`.agents/skills/gframework-doc-refresh/SKILL.md` 中新增硬约束,明确禁止把 inventory、覆盖基线、恢复点、
|
||||
review backlog、治理批次等 contributor-only 内容写进 `README.md` 与 `docs/**`
|
||||
- 将 `docs/zh-CN/core/index.md`、`core/cqrs.md`、`game/index.md`、
|
||||
`abstractions/core-abstractions.md`、`abstractions/game-abstractions.md`、`ecs/index.md`、
|
||||
`ecs/arch.md`、`abstractions/ecs-arch-abstractions.md` 中的 XML 覆盖表述改写为面向读者的“源码阅读入口”
|
||||
- 顺手收口 `api-reference/index.md`、`contributor/development-environment.md` 与
|
||||
`source-generators/*.md` 中的内部口吻用语,例如 `landing page`、`验证基线`、`目标类型基线`
|
||||
- focused validator 已覆盖本轮触达的 `13` 个公开文档页面并全部通过;站点构建 `cd docs && bun run build`
|
||||
已通过,仅保留既有大 chunk warning
|
||||
- 当前分支 `HEAD` 仍与 `origin/main`(`2de57f5`,`2026-04-23T23:03:40+08:00`)对齐;在提交本轮工作前,
|
||||
工作树待提交范围为 `16` 个文件、`224` changed lines,距离 `$gframework-batch-boot 75` 的停止阈值仍很远。
|
||||
- `2026-04-24` 继续在同一 stop condition 下执行第二个低风险批次,集中修复 `docs/zh-CN/core/*.md` 的历史 frontmatter 缺口:
|
||||
- 已为 `architecture.md`、`async-initialization.md`、`command.md`、`configuration.md`、`context.md`、
|
||||
`environment.md`、`events.md`、`extensions.md`、`functional.md`、`ioc.md`、`localization.md`、
|
||||
`logging.md`、`model.md`、`pause.md`、`pool.md`、`property.md`、`query.md`、`rule.md`、
|
||||
`state-management.md`、`system.md`、`utility.md` 补齐 frontmatter
|
||||
- 顺手修复 `core/ioc.md` 的 `xref:System.Threading.ReaderWriterLockSlim` 坏链,以及
|
||||
`core/state-management.md` 中 4 处缺少 `.md` 后缀的站内链接
|
||||
- 当前 `docs/zh-CN/core/*.md` 已全部具备 frontmatter;focused validator 对这 `21` 个页面全部通过,`bun run build`
|
||||
再次通过,仅保留既有大 chunk warning
|
||||
- 截至当前未提交工作树,`HEAD` 相对 `origin/main` 的累计 branch diff 仍为 `18` 个文件;新增待提交批次为 `21` 个文件、
|
||||
`126` changed lines,合并后仍显著低于 `$gframework-batch-boot 75` 的停止阈值。
|
||||
- `2026-04-24` 继续在同一 stop condition 下执行第三个低风险批次,集中清理 `docs/zh-CN` 其余“完全缺 frontmatter”的页面:
|
||||
- 已为 `best-practices/*.md` 中缺口页、`contributing.md`、`faq.md`、`game/config-system.md`、
|
||||
`getting-started/*.md`、`godot/coroutine.md`、6 个 `source-generators/*.md`、`troubleshooting.md`、
|
||||
`tutorials/advanced-patterns.md`、`tutorials/basic/index.md` 与 `tutorials/index.md` 补齐 frontmatter
|
||||
- 在同批次内修复 `best-practices/multiplayer.md` 的未闭合代码块、`source-generators/*.md` 中缺少 `.md`
|
||||
后缀的相对链接,以及 `troubleshooting.md` 里 3 处目录索引死链
|
||||
- 当前 `docs/zh-CN` 已不存在“完全缺 frontmatter”的页面;剩余 metadata 热点只剩
|
||||
`docs/zh-CN/index.md` 与 `docs/zh-CN/tutorials/basic/01-07.md` 共 `8` 个“已有 frontmatter 但缺 title /
|
||||
description”的页面
|
||||
- 本批次落地前,当前分支相对 `origin/main` 的累计 branch diff 为 `39` 个文件;连同本轮工作和 tracking / trace
|
||||
更新后,预计提交后累计 diff 约为 `63` 个文件,仍低于 `$gframework-batch-boot 75` 的停止阈值
|
||||
|
||||
## 迁出的验证记录
|
||||
|
||||
- `2026-04-24` `bash .agents/skills/gframework-doc-refresh/scripts/validate-all.sh docs/zh-CN`
|
||||
- 结果:失败;暴露出仓库既有的 `53` 个历史文档问题(大量缺少 frontmatter、既有坏链与未标语言代码块),不由本轮改动引入,因此本轮改用 focused validator 证明任务级结果。
|
||||
- `2026-04-24` `python3 .agents/skills/gframework-doc-refresh/scripts/scan_module_evidence.py Core`
|
||||
- 结果:通过;确认 `Core` 模块的 README、landing、topic 与 fallback docs 入口仍可解析。
|
||||
- `2026-04-24` `python3 .agents/skills/gframework-doc-refresh/scripts/scan_module_evidence.py Game`
|
||||
- 结果:通过;确认 `Game` 模块的 README、landing、topic 与 fallback docs 入口仍可解析。
|
||||
- `2026-04-24` focused validator(逐个校验本轮触达页面)
|
||||
- 结果:通过;`docs/zh-CN/abstractions/core-abstractions.md`、
|
||||
`abstractions/ecs-arch-abstractions.md`、`abstractions/game-abstractions.md`、
|
||||
`api-reference/index.md`、`contributor/development-environment.md`、`core/cqrs.md`、`core/index.md`、
|
||||
`ecs/arch.md`、`ecs/index.md`、`game/index.md`、
|
||||
`source-generators/bind-node-signal-generator.md`、
|
||||
`source-generators/cqrs-handler-registry-generator.md`、
|
||||
`source-generators/get-node-generator.md` 的 frontmatter / links / code blocks 全部通过。
|
||||
- `2026-04-24` `bun run build`(工作目录:`docs/`)
|
||||
- 结果:通过;仅保留既有大 chunk warning。
|
||||
- `2026-04-24` `python3 - <<'PY' ...`(检查 `docs/zh-CN/core/*.md` frontmatter)
|
||||
- 结果:通过;`docs/zh-CN/core/` 当前所有 Markdown 页面均已带 frontmatter。
|
||||
- `2026-04-24` focused validator(逐个校验 `docs/zh-CN/core/*.md` 的 `21` 个页面)
|
||||
- 结果:通过;过程中暴露并已修复 `core/ioc.md` 的 `ReaderWriterLockSlim` 坏链与
|
||||
`core/state-management.md` 的 4 处站内坏链;剩余仅为既有代码块语言 warning,不影响任务级通过。
|
||||
- `2026-04-24` `bun run build`(工作目录:`docs/`,第二次)
|
||||
- 结果:通过;frontmatter 与坏链修复后站点仍可正常构建,仅保留既有大 chunk warning。
|
||||
- `2026-04-24` focused validator(逐个校验第三批触达的 `22` 个页面)
|
||||
- 结果:通过;frontmatter、真实坏链与未闭合代码块问题均已修复,剩余仅为 `best-practices/architecture-patterns.md`、
|
||||
`best-practices/index.md`、`contributing.md`、`troubleshooting.md` 与 `tutorials/index.md` 的既有代码块语言 warning。
|
||||
- `2026-04-24` `bun run build`(工作目录:`docs/`,第三次)
|
||||
- 结果:通过;第三批 frontmatter 与链接修复后站点仍可正常构建,仅保留既有大 chunk warning。
|
||||
- `2026-04-23` `python3 .agents/skills/gframework-pr-review/scripts/fetch_current_pr_review.py --format json --json-output /tmp/current-pr-review.json`
|
||||
- 结果:通过;PR `#272` 处于 `OPEN`,latest head commit 存在 1 条 Greptile open thread,定位到
|
||||
`docs/zh-CN/godot/setting.md:75` 的 inline code HTML entity 渲染问题。
|
||||
- `2026-04-23` `rg -n '`[^`]*<[^`]*`|`[^`]*>[^`]*`' GFramework.Godot.SourceGenerators/README.md GFramework.Godot/README.md README.md docs/zh-CN/api-reference/index.md docs/zh-CN/game/data.md docs/zh-CN/game/serialization.md docs/zh-CN/game/setting.md docs/zh-CN/game/storage.md docs/zh-CN/godot/setting.md docs/zh-CN/godot/storage.md docs/zh-CN/source-generators/index.md`
|
||||
- 结果:命中 `docs/zh-CN/godot/setting.md:75` 与 `docs/zh-CN/godot/storage.md:102` 两处同类写法,均已修正。
|
||||
- `2026-04-23` `rg -n '`[^`]*<[^`]*`|`[^`]*>[^`]*`' README.md GFramework.* docs/zh-CN -g '*.md'`
|
||||
- 结果:命中 `docs/zh-CN/core/functional.md` 与 `docs/zh-CN/tutorials/functional-programming.md` 共 8 处,已全部修正。
|
||||
- `2026-04-23` `sed -n '1,260p' .agents/skills/gframework-batch-boot/SKILL.md` 与 `sed -n '1,220p' .agents/skills/README.md`
|
||||
- 结果:确认原文仅描述自然语言 stop condition,没有定义数字速记或多阈值 OR 语义;现已补齐。
|
||||
- `2026-04-23` `rg -n '`GFramework\\.[^`]+/README\\.md`|`docs/zh-CN/[^`]+\\.md`|仓库根 `README\\.md`' docs/zh-CN -g '*.md'`
|
||||
- 结果:确认 landing / API 导航页仍有一批裸路径仓库入口;本轮已先修复 `getting-started`、`core`、`game`、
|
||||
`source-generators`、`api-reference` 与两个 abstractions 页面。
|
||||
- `2026-04-23` `rg -n '`GFramework\\.[^`]+/README\\.md`|仓库根 `README\\.md`' docs/zh-CN -g '*.md'`
|
||||
- 结果:定位第二批专题页导航热点,已修复 `core/cqrs.md`、`ecs/arch.md`、`abstractions/ecs-arch-abstractions.md`、
|
||||
`game/scene.md`、`game/ui.md` 以及 6 个 `source-generators/*.md` 页面。
|
||||
- `2026-04-23` `bun run build`(工作目录:`docs/`)
|
||||
- 结果:通过;仓库 README 外链改为 GitHub `main` blob 后,不再触发 VitePress dead link;仅保留既有大 chunk warning。
|
||||
|
||||
## 迁出的下一步
|
||||
|
||||
1. 若继续执行 `$gframework-batch-boot 75`,优先处理 `docs/zh-CN/index.md` 与 `tutorials/basic/01-07.md` 这 `8`
|
||||
个“已有 frontmatter 但缺 `title` / `description`”的 metadata 缺口。
|
||||
2. 若后续继续扩展批处理 skill,可考虑再补充显式单位写法,例如 `75 files 2000 lines`,但当前默认速记已足够覆盖
|
||||
常见分支阈值场景。
|
||||
3. 若后续分支继续调整 `Game` persistence runtime、README 或公共 API,优先复核 `docs/zh-CN/game/data.md`、
|
||||
`storage.md`、`serialization.md`、`setting.md` 与 landing page 是否仍保持同一套职责边界。
|
||||
4. 若后续分支继续调整 `Godot` generator 接法,优先复核 `GFramework.Godot.SourceGenerators/README.md`、
|
||||
`docs/zh-CN/tutorials/godot-integration.md` 与相关专题页是否仍保持一致。
|
||||
@ -0,0 +1,167 @@
|
||||
# Documentation Full Coverage Governance Trace History RP-023 To RP-025
|
||||
|
||||
以下内容从 active trace 中迁出,用于保留 `DOCUMENTATION-FULL-COVERAGE-GOV-RP-023` 到
|
||||
`DOCUMENTATION-FULL-COVERAGE-GOV-RP-025` 的执行时间线、关键决策与主要验证结果。默认 `boot` 只需要读取
|
||||
active trace 中的最新恢复点;若需要追溯 `2026-04-23` 到 `2026-04-24` 的批处理执行顺序,再回到本归档文件。
|
||||
|
||||
## 2026-04-24
|
||||
|
||||
### 当前恢复点:RP-025
|
||||
|
||||
- 继续沿用 `$gframework-batch-boot 75`,基线保持 `origin/main`(`2de57f5`,`2026-04-23T23:03:40+08:00`)。
|
||||
- 本轮目标从“继续治理公开文档边界”切换为“清空 `docs/zh-CN` 中仍然完全缺 frontmatter 的页面,同时把触达页暴露的真实格式错误一并收口”。
|
||||
- 本轮执行的修复:
|
||||
- 为 `best-practices`、`getting-started`、`source-generators`、`tutorials` 等目录下共 `22` 个页面补齐
|
||||
`title` / `description` frontmatter
|
||||
- 修复 `docs/zh-CN/best-practices/multiplayer.md` 末尾缺失的代码块闭合符
|
||||
- 修复 `docs/zh-CN/source-generators/*.md` 与 `docs/zh-CN/troubleshooting.md` 中一组缺少 `.md` 后缀或目录索引写法不兼容当前 validator 的站内链接
|
||||
|
||||
### 当前决策(RP-025)
|
||||
|
||||
- 对文档批处理,优先选择“元数据缺口 + 顺手修复真实结构错误”的组合,而不扩成正文语义刷新或大规模 code fence language 治理。
|
||||
- 当 focused validator 暴露的是触达页上的真实错误(如坏链、未闭合代码块)时,同批次直接收口;仅把纯 warning 留给下一轮专门治理。
|
||||
- 本轮结束时,`docs/zh-CN` 已没有“完全缺 frontmatter”的页面;下一批最稳定的切片是 `docs/zh-CN/index.md` 与
|
||||
`docs/zh-CN/tutorials/basic/01-07.md` 这 `8` 个“已有 frontmatter 但缺 `title` / `description`”的页面。
|
||||
- 当前已提交分支 diff 仍为 `39` 个文件;将本轮工作连同 tracking / trace 提交后,预计累计 branch diff 约为 `63`
|
||||
个文件,仍低于 `$gframework-batch-boot 75` 的停止阈值。
|
||||
|
||||
### 当前验证(RP-025)
|
||||
|
||||
- frontmatter 缺口巡检:
|
||||
- `for f in $(find docs/zh-CN -type f -name '*.md' | sort); do if ! head -n 5 "$f" | grep -q '^---$'; then echo "$f"; fi; done`
|
||||
- 结果:本轮前命中 `22` 个页面,当前已全部补齐。
|
||||
- focused validator:
|
||||
- 逐个校验本轮触达的 `22` 个页面
|
||||
- 结果:通过;只剩 `best-practices/architecture-patterns.md`、`best-practices/index.md`、`contributing.md`、
|
||||
`troubleshooting.md` 与 `tutorials/index.md` 的既有代码块语言 warning。
|
||||
- 站点构建:
|
||||
- `bun run build`(工作目录:`docs/`)
|
||||
- 结果:通过;仅保留既有大 chunk warning。
|
||||
- 后续候选扫描:
|
||||
- `python3 - <<'PY' ...`(扫描已有 frontmatter 但缺 `title` / `description` 的页面)
|
||||
- 结果:命中 `docs/zh-CN/index.md` 与 `docs/zh-CN/tutorials/basic/01-07.md` 共 `8` 个页面,可作为下一批 metadata 修复入口。
|
||||
|
||||
### 下一步
|
||||
|
||||
1. 继续执行 `$gframework-batch-boot 75` 时,优先补齐 `docs/zh-CN/index.md` 与 `tutorials/basic/01-07.md` 的
|
||||
`title` / `description` 缺口。
|
||||
2. 若仍有余量,再按目录收口 `best-practices`、`contributing`、`troubleshooting`、`tutorials/index.md`
|
||||
的代码块语言 warning,而不是跨全站混做。
|
||||
|
||||
### 当前恢复点:RP-024
|
||||
|
||||
- 根据用户反馈,将本轮目标重定义为“清理公开文档中的治理盘点式内容,并把同类约束补进仓库规范与 doc-refresh skill”。
|
||||
- 用户随后补充明确使用 `$gframework-batch-boot 75`,因此继续沿用 `origin/main` 作为固定基线,并把 `75` changed
|
||||
files 作为主停止条件。
|
||||
- 本轮执行的修复:
|
||||
- 在 `AGENTS.md`、`.agents/skills/_shared/DOCUMENTATION_STANDARDS.md` 与
|
||||
`.agents/skills/gframework-doc-refresh/SKILL.md` 中新增公开文档边界规则,禁止把 inventory、覆盖基线、
|
||||
恢复点、review backlog 和治理批次写入 `README.md` 与 `docs/**`
|
||||
- 将 `docs/zh-CN/core/index.md`、`core/cqrs.md`、`game/index.md`、
|
||||
`abstractions/core-abstractions.md`、`abstractions/game-abstractions.md`、`ecs/index.md`、
|
||||
`ecs/arch.md`、`abstractions/ecs-arch-abstractions.md` 的 XML 覆盖 / inventory 段落改写成读者导向的源码阅读入口
|
||||
- 继续收口 `api-reference/index.md`、`contributor/development-environment.md` 与
|
||||
`source-generators/*.md` 中的内部术语,例如 `landing page`、`验证基线`、`目标类型基线`
|
||||
- 为 `docs/zh-CN/contributor/development-environment.md` 补齐 frontmatter,使其满足当前文档规范
|
||||
|
||||
### 当前决策(RP-024)
|
||||
|
||||
- 公开文档只承载采用路径、阅读入口、模块边界和可验证示例;治理盘点、覆盖状态和恢复点一律留在 `ai-plan/**`。
|
||||
- 当 XML 治理结果需要体现在公开文档里时,只输出“优先看哪些类型 / 命名空间 / 契约以及为什么”,不输出计数、日期或状态表。
|
||||
- `$gframework-batch-boot 75` 的基线采用 `origin/main`(`2de57f5`,`2026-04-23T23:03:40+08:00`)。
|
||||
- 由于当前 `HEAD` 仍与 `origin/main` 对齐,分支级 diff 暂时仍为 `0`;提交前工作树待提交范围为 `16` 个文件、
|
||||
`224` changed lines,因此本轮仍远低于 `75` 文件阈值。
|
||||
- 在完成“公开文档边界”收口后,继续沿同一阈值推进一个新的低风险批次:为 `docs/zh-CN/core/*.md` 历史页面补齐 frontmatter。
|
||||
- 当 validator 因本轮触达页面暴露真实坏链时,直接在同批次内修复;当只剩历史 warning(如缺少代码块语言标记)时,本轮停止扩张。
|
||||
|
||||
### 当前验证(RP-024)
|
||||
|
||||
- 同类治理内容巡检:
|
||||
- `rg -n 'XML Inventory|XML 覆盖基线|XML 状态|基线状态|盘点|治理优先级|审计入口|覆盖基线|恢复点|验证基线|目标类型基线|目标字段基线|类型审计|契约审计|源码 / API' docs/zh-CN README.md -g '*.md'`
|
||||
- 结果:公开页已无同类命中;剩余 `inventory` 命中仅来自正常代码示例变量名。
|
||||
- skill 自检:
|
||||
- `python3 .agents/skills/gframework-doc-refresh/scripts/scan_module_evidence.py Core`
|
||||
- `python3 .agents/skills/gframework-doc-refresh/scripts/scan_module_evidence.py Game`
|
||||
- 结果:均通过;代表模块的 README / docs 入口映射仍有效。
|
||||
- 全量 docs 校验:
|
||||
- `bash .agents/skills/gframework-doc-refresh/scripts/validate-all.sh docs/zh-CN`
|
||||
- 结果:失败;暴露 `53` 个仓库既有历史问题(缺少 frontmatter、坏链、未标语言代码块),不属于本轮改动。
|
||||
- focused validator:
|
||||
- 逐个校验本轮触达的 `13` 个公开文档页面
|
||||
- 结果:全部通过。
|
||||
- 站点构建:
|
||||
- `bun run build`(工作目录:`docs/`)
|
||||
- 结果:通过;仅保留既有大 chunk warning。
|
||||
- `core` frontmatter 波次:
|
||||
- 已补齐 `docs/zh-CN/core/*.md` 中 `21` 个历史页面的 frontmatter。
|
||||
- 过程中修复 `docs/zh-CN/core/ioc.md` 的 `ReaderWriterLockSlim` 坏链,以及
|
||||
`docs/zh-CN/core/state-management.md` 的 4 处缺少 `.md` 后缀的站内链接。
|
||||
- `python3 - <<'PY' ...` 检查结果为 `ALL_HAVE_FRONTMATTER`,说明 `docs/zh-CN/core/` 目录当前已无 frontmatter 缺口。
|
||||
- focused validator 对这 `21` 个页面全部通过;剩余输出仅为既有代码块语言 warning。
|
||||
- `bun run build` 在修复后再次通过。
|
||||
- 当前阈值状态:
|
||||
- `git diff --name-only origin/main...HEAD | wc -l` => `18`
|
||||
- `git diff --name-only | wc -l` => `21`
|
||||
- `git diff --numstat` 汇总 => `126` changed lines
|
||||
- 结论:当前已提交分支 diff 仍为 `18` 个文件,待提交新批次再增加 `21` 个文件;即使提交后也仍低于 `75` 文件阈值。
|
||||
|
||||
### 下一步
|
||||
|
||||
1. 继续执行 `$gframework-batch-boot 75` 时,优先按目录做历史 frontmatter / code fence language / 坏链修复,而不是把不同风格的文档语义刷新混成一批。
|
||||
2. 当前批次在 `core` 目录已经不再是“同样机械”的模式,后续若继续应转向其他目录或专门做代码块语言标记治理。
|
||||
|
||||
## 2026-04-23
|
||||
|
||||
### 当前恢复点:RP-023
|
||||
|
||||
- 按当前使用反馈继续执行 `documentation-full-coverage-governance` 下的 skill 文档治理。
|
||||
- 本轮目标定义为“继续沿用上一批的 GitHub 外链策略,收口专题页里的裸路径 README 入口”。
|
||||
- 本轮执行的修复:
|
||||
- 将 `docs/zh-CN/core/cqrs.md` 与 `ecs/arch.md` 的仓库 README 入口改为 GitHub `main` blob 外链
|
||||
- 将 `docs/zh-CN/abstractions/ecs-arch-abstractions.md`、`game/scene.md`、`game/ui.md` 的回跳 README 入口改为可点击链接
|
||||
- 将 `docs/zh-CN/source-generators/priority-generator.md`、`context-aware-generator.md`、
|
||||
`bind-node-signal-generator.md`、`godot-project-generator.md`、`get-node-generator.md`、
|
||||
`auto-register-exported-collections-generator.md` 的推荐阅读 README 入口改为可点击链接
|
||||
- 同步更新 active tracking / trace,记录第二批导航治理与新的恢复点
|
||||
|
||||
### 当前决策(RP-023)
|
||||
|
||||
- 继续使用 `origin/main` 作为 `$gframework-batch-boot 75` 的固定基线,并以“分支累计 diff 文件数”作为主状态指标。
|
||||
- 对文档治理类批次,优先选择“导航可达性 / 渲染一致性”这类不改变产品语义的低风险切片。
|
||||
- 在 docs 页面里出现仓库内 README 路径时,优先使用可点击的相对链接,而不是裸路径代码片段。
|
||||
- 当 docs 页需要跳转到 `docs/` 外部的 README 时,使用 GitHub `main` 分支 blob 外链,而不是跨出 `docs/` 根目录的相对路径。
|
||||
- 第二批继续沿用同一外链策略,避免在同一 docs surface 中混用“裸路径 / 相对死链 / GitHub 外链”三套入口风格。
|
||||
|
||||
### 当前验证(RP-023)
|
||||
|
||||
- 导航热点巡检:
|
||||
- `rg -n '`GFramework\\.[^`]+/README\\.md`|`docs/zh-CN/[^`]+\\.md`|仓库根 `README\\.md`' docs/zh-CN -g '*.md'`
|
||||
- 结果:命中 landing / API 导航页中的裸路径仓库入口,已按本轮批次收口 7 个页面。
|
||||
- 第二批专题页巡检:
|
||||
- `rg -n '`GFramework\\.[^`]+/README\\.md`|仓库根 `README\\.md`' docs/zh-CN -g '*.md'`
|
||||
- 结果:命中 `core/cqrs.md`、`ecs/arch.md`、`abstractions/ecs-arch-abstractions.md`、`game/scene.md`、
|
||||
`game/ui.md` 与 6 个 `source-generators/*.md` 专题页,均已修复。
|
||||
- 构建校验:
|
||||
- `bun run build`(工作目录:`docs/`)
|
||||
- 结果:通过;将仓库 README 跳转改为 GitHub `main` blob 外链后,不再触发 VitePress dead link;仅保留既有大 chunk warning。
|
||||
- 当前阈值状态:
|
||||
- `git diff --name-only origin/main...HEAD | wc -l` => `24`
|
||||
- `git diff --numstat origin/main...HEAD` 汇总 => `264` changed lines
|
||||
- 结论:尚未接近 `75` 文件阈值,但剩余命中主要是正文语义性提及,当前批次在低风险模板化导航治理上可先收口。
|
||||
|
||||
### 归档摘要(RP-022)
|
||||
|
||||
- 为 `.agents/skills/gframework-batch-boot/SKILL.md` 与 `.agents/skills/README.md` 补齐数字速记 stop condition 语义。
|
||||
- 明确 `$gframework-batch-boot 75` / `75 2000` 默认绑定 `origin/main` 累计 diff 口径。
|
||||
- 完成第一批 landing / API 导航页 README 外链治理,并通过 `docs/` 站点构建。
|
||||
|
||||
### 归档指针
|
||||
|
||||
- `ai-plan/public/documentation-full-coverage-governance/archive/todos/documentation-full-coverage-governance-validation-history-through-rp-007.md`
|
||||
- `ai-plan/public/documentation-full-coverage-governance/archive/todos/documentation-full-coverage-governance-status-history-through-rp-016.md`
|
||||
- `ai-plan/public/documentation-full-coverage-governance/archive/traces/documentation-full-coverage-governance-trace-history-through-rp-016.md`
|
||||
|
||||
### 下一步
|
||||
|
||||
1. 提交并推送本地修正后,再次抓取 PR `#272`,确认 Greptile open thread 是否已在新 head commit 上消失。
|
||||
2. 若继续执行文档治理批处理,优先排查剩余的非导航型裸路径引用、标题锚点与站内链接热点,而不是扩成跨模块大波次。
|
||||
@ -12,11 +12,12 @@
|
||||
|
||||
## 当前恢复点
|
||||
|
||||
- 恢复点编号:`DOCUMENTATION-FULL-COVERAGE-GOV-RP-023`
|
||||
- 恢复点编号:`DOCUMENTATION-FULL-COVERAGE-GOV-RP-026`
|
||||
- 当前阶段:`Phase 5 - Governance Maintenance`
|
||||
- 当前焦点:
|
||||
- 保持 landing page / API 导航页中的仓库 README 入口可点击,避免读者在 docs 站点里遇到裸路径文本
|
||||
- 继续按 `origin/main` 分支 diff 阈值做小批量文档治理,优先处理低风险导航 / 渲染热点
|
||||
- 收口 PR `#282` 的 latest-head review follow-up,保持 active tracking / trace 只承载当前恢复入口
|
||||
- 保持 `README.md` 与 `docs/**` 公开页面只承载读者需要的采用信息,不再混入 XML inventory、覆盖基线、恢复点或治理批次说明
|
||||
- 继续按 `$gframework-batch-boot 75` 的 `origin/main` 分支 diff 阈值做小批量文档治理,优先处理低风险 metadata 缺口、坏链与 Markdown 结构问题
|
||||
- 保持 `Game` persistence docs surface 与当前 `README`、源码、`PersistenceTests` 使用同一套 owner / adoption path 叙述
|
||||
- 保持 `GFramework.Godot.SourceGenerators/README.md` 与 `docs/zh-CN/tutorials/godot-integration.md` 在生命周期接法上的一致性
|
||||
- 保持 active tracking / trace 只承载当前恢复入口,把阶段细节留在 `archive/`
|
||||
@ -24,49 +25,27 @@
|
||||
## 当前状态摘要
|
||||
|
||||
- `Core`、`Ecs.Arch`、`Cqrs`、`Game`、`Godot` 五个模块族当前都已有 README / landing / topic / API 参考层级的已验证入口。
|
||||
- `2026-04-24` 使用 `$gframework-pr-review` 抓取 PR `#282` 后,确认 latest head commit
|
||||
`982249151ecf8acdff3e62e664034bf95dfacd75` 当前仍有 `3` 条 CodeRabbit 与 `1` 条 Greptile open thread;4 条建议均已在本地复核并纳入当前恢复点。
|
||||
- 本轮 PR follow-up 仅收口仍然成立的 review 项:
|
||||
- 将过长的 active tracking / trace 瘦身,并把 `RP-023` 到 `RP-025` 的细节迁入 `archive/`
|
||||
- 将 `docs/zh-CN/core/context.md` 的标题本地化为中文读者友好的写法
|
||||
- 统一 `docs/zh-CN/troubleshooting.md` 中 `/zh-CN/core/architecture` 与 `/zh-CN/faq` 的 `.md` 链接写法
|
||||
- `Game` persistence docs surface 当前以 `docs/zh-CN/game/data.md`、`storage.md`、`serialization.md`、`setting.md`
|
||||
作为最小巡检集合;若后续 README、runtime public API 或 `PersistenceTests` 变动,应优先复核这一组页面。
|
||||
- `Godot` runtime 与 generator 入口当前以 `GFramework.Godot/README.md`、
|
||||
`GFramework.Godot.SourceGenerators/README.md`、`docs/zh-CN/godot/index.md`、
|
||||
`docs/zh-CN/source-generators/index.md`、`docs/zh-CN/tutorials/godot-integration.md` 维持统一 owner / adoption path。
|
||||
- `2026-04-23` 基于 PR `#272` 的 review follow-up 已完成:
|
||||
- 为 `docs/zh-CN/game/data.md` 补充 `UnifiedSettingsDataRepository` 的统一文件布局示例
|
||||
- 为 `GFramework.Godot.SourceGenerators/README.md` 补充手写 `_Ready()` / `_ExitTree()` 时显式调用生成方法的最小样例
|
||||
- 将过长的 active tracking / trace 瘦身,并把历史摘要迁回 `archive/`
|
||||
- `2026-04-23` 使用 `$gframework-pr-review` 重新抓取 PR `#272` 后,确认 latest-head review 当前仍有 1 条
|
||||
Greptile open thread,定位到 `docs/zh-CN/godot/setting.md:75` 的 inline code 误写成
|
||||
`SettingsModel<ISettingsDataRepository>`。
|
||||
- 结合当前 PR 已改动的 `docs/zh-CN/godot/storage.md` 做同类巡检后,确认 `SaveRepository<TSaveData>`
|
||||
也会在 VitePress code span 中按字面量渲染;两处现已在本地统一改为真实泛型写法。
|
||||
- `2026-04-23` 以 `origin/main`(`aa879d2`,`2026-04-23T17:51:41+08:00`)为批处理基线,对
|
||||
`README.md`、`GFramework.*` 与 `docs/zh-CN/**` 执行同类模式巡检,确认剩余热点仅位于
|
||||
`docs/zh-CN/core/functional.md` 与 `docs/zh-CN/tutorials/functional-programming.md` 共 8 处。
|
||||
- 上述 8 处 inline code 中的 `Option<T>`、`Result<T>`、`Nullable<T>` 已统一改为真实
|
||||
泛型写法,避免在 VitePress 中显示字面量 HTML entity。
|
||||
- `2026-04-23` 根据本轮使用反馈,已为 `.agents/skills/gframework-batch-boot/SKILL.md` 与
|
||||
`.agents/skills/README.md` 补充数字速记阈值语义:
|
||||
- `$gframework-batch-boot 75` 默认表示“当前分支全部提交相对远程 `origin/main` 接近 75 个分支 diff 文件时停止”
|
||||
- `$gframework-batch-boot 75 2000` 默认表示“当前分支全部提交相对远程 `origin/main` 接近 75 个文件或 2000 行变更时停止”
|
||||
- `75 | 2000` 仅作为可理解的 OR 写法保留,不再作为推荐写法,以避免与 shell pipe 混淆
|
||||
- `2026-04-23` 以 `origin/main`(`aa879d2`,`2026-04-23T17:51:41+08:00`)为批处理基线,对
|
||||
`docs/zh-CN/getting-started/index.md`、`core/index.md`、`game/index.md`、`source-generators/index.md`、
|
||||
`api-reference/index.md`、`abstractions/core-abstractions.md`、`abstractions/game-abstractions.md`
|
||||
做导航可达性修复,把仓库 README / 根 README 裸路径统一改为指向 GitHub `main` 分支的可点击链接。
|
||||
- 该批次不改变文档语义,只收口 docs 站点中的入口可达性;适合继续作为小步快跑的低风险治理模式。
|
||||
- `2026-04-23` 在同一基线下继续收口第二批专题页导航热点,已将 `core/cqrs.md`、`ecs/arch.md`、
|
||||
`abstractions/ecs-arch-abstractions.md`、`game/scene.md`、`game/ui.md` 和 6 个
|
||||
`source-generators/*.md` 专题页里的 README 裸路径统一改为 GitHub `main` blob 外链。
|
||||
- 截至提交 `8a11720`(`2026-04-23T21:01:28+08:00`),当前分支相对 `origin/main`(`aa879d2`)的累计 diff
|
||||
为 `24` 个文件、`264` 行,仍低于 `$gframework-batch-boot 75` 的停止阈值;但剩余命中已主要是正文语义性提及,不再适合作为同类批处理。
|
||||
- 当前剩余的托管侧信号是 GitHub `Title check` 对 PR 标题过泛的 inconclusive 提示;这属于 PR 元数据,不是本地
|
||||
文件缺陷。
|
||||
- `2026-04-23` 到 `2026-04-24` 的批次细节、验证日志与旧恢复建议已迁入:
|
||||
`ai-plan/public/documentation-full-coverage-governance/archive/todos/documentation-full-coverage-governance-status-history-rp-023-to-rp-025-2026-04-24.md`
|
||||
|
||||
## 当前风险
|
||||
|
||||
- 当前 `Core` / `Core.Abstractions`、`Ecs.Arch`、`Cqrs`、`Game` 的 XML 治理仍以“类型声明级基线”为主,不等于成员级契约全审计。
|
||||
- 当前 `Core` / `Core.Abstractions`、`Ecs.Arch`、`Cqrs`、`Game` 的 XML 治理证据仍主要来自类型与入口级阅读,不等于成员级契约全审计;这类治理状态只应保留在 `ai-plan/**`,不应再暴露到公开文档。
|
||||
- `GFramework.Cqrs` 在当前 WSL / dotnet 环境下仍会读取失效的 fallback package folder,并在标准 build 中触发
|
||||
`MSB4276` / `MSB4018`;这是已知环境阻塞,不属于本轮文档回归。
|
||||
- 当前 WSL 会话里 `git.exe` 可解析但不能执行,应继续使用显式 `--git-dir` / `--work-tree` 绑定作为默认 Git 策略。
|
||||
- PR `#282` 的 `Title check` 仍可能提示标题过泛;这是 GitHub PR 元数据问题,不属于本地文件缺陷。
|
||||
|
||||
## 归档指针
|
||||
|
||||
@ -74,34 +53,27 @@
|
||||
`ai-plan/public/documentation-full-coverage-governance/archive/todos/documentation-full-coverage-governance-validation-history-through-rp-007.md`
|
||||
- 阶段状态归档(`RP-001` 到 `RP-016`):
|
||||
`ai-plan/public/documentation-full-coverage-governance/archive/todos/documentation-full-coverage-governance-status-history-through-rp-016.md`
|
||||
- 阶段状态归档(`RP-023` 到 `RP-025`):
|
||||
`ai-plan/public/documentation-full-coverage-governance/archive/todos/documentation-full-coverage-governance-status-history-rp-023-to-rp-025-2026-04-24.md`
|
||||
- 时间线归档(`RP-001` 到 `RP-016`):
|
||||
`ai-plan/public/documentation-full-coverage-governance/archive/traces/documentation-full-coverage-governance-trace-history-through-rp-016.md`
|
||||
- 时间线归档(`RP-023` 到 `RP-025`):
|
||||
`ai-plan/public/documentation-full-coverage-governance/archive/traces/documentation-full-coverage-governance-trace-history-rp-023-to-rp-025-2026-04-24.md`
|
||||
|
||||
## 最新验证
|
||||
|
||||
- `2026-04-23` `python3 .agents/skills/gframework-pr-review/scripts/fetch_current_pr_review.py --format json --json-output /tmp/current-pr-review.json`
|
||||
- 结果:通过;PR `#272` 处于 `OPEN`,latest head commit 存在 1 条 Greptile open thread,定位到
|
||||
`docs/zh-CN/godot/setting.md:75` 的 inline code HTML entity 渲染问题。
|
||||
- `2026-04-23` `rg -n '`[^`]*<[^`]*`|`[^`]*>[^`]*`' GFramework.Godot.SourceGenerators/README.md GFramework.Godot/README.md README.md docs/zh-CN/api-reference/index.md docs/zh-CN/game/data.md docs/zh-CN/game/serialization.md docs/zh-CN/game/setting.md docs/zh-CN/game/storage.md docs/zh-CN/godot/setting.md docs/zh-CN/godot/storage.md docs/zh-CN/source-generators/index.md`
|
||||
- 结果:命中 `docs/zh-CN/godot/setting.md:75` 与 `docs/zh-CN/godot/storage.md:102` 两处同类写法,均已修正。
|
||||
- `2026-04-23` `rg -n '`[^`]*<[^`]*`|`[^`]*>[^`]*`' README.md GFramework.* docs/zh-CN -g '*.md'`
|
||||
- 结果:命中 `docs/zh-CN/core/functional.md` 与 `docs/zh-CN/tutorials/functional-programming.md` 共 8 处,已全部修正。
|
||||
- `2026-04-23` `sed -n '1,260p' .agents/skills/gframework-batch-boot/SKILL.md` 与 `sed -n '1,220p' .agents/skills/README.md`
|
||||
- 结果:确认原文仅描述自然语言 stop condition,没有定义数字速记或多阈值 OR 语义;现已补齐。
|
||||
- `2026-04-23` `rg -n '`GFramework\\.[^`]+/README\\.md`|`docs/zh-CN/[^`]+\\.md`|仓库根 `README\\.md`' docs/zh-CN -g '*.md'`
|
||||
- 结果:确认 landing / API 导航页仍有一批裸路径仓库入口;本轮已先修复 `getting-started`、`core`、`game`、
|
||||
`source-generators`、`api-reference` 与两个 abstractions 页面。
|
||||
- `2026-04-23` `rg -n '`GFramework\\.[^`]+/README\\.md`|仓库根 `README\\.md`' docs/zh-CN -g '*.md'`
|
||||
- 结果:定位第二批专题页导航热点,已修复 `core/cqrs.md`、`ecs/arch.md`、`abstractions/ecs-arch-abstractions.md`、
|
||||
`game/scene.md`、`game/ui.md` 以及 6 个 `source-generators/*.md` 页面。
|
||||
- `2026-04-23` `bun run build`(工作目录:`docs/`)
|
||||
- 结果:通过;仓库 README 外链改为 GitHub `main` blob 后,不再触发 VitePress dead link;仅保留既有大 chunk warning。
|
||||
- `2026-04-24` `python3 .agents/skills/gframework-pr-review/scripts/fetch_current_pr_review.py --format json --json-output /tmp/current-pr-review.json`
|
||||
- 结果:通过;PR `#282` 处于 `OPEN`,latest head commit 有 `3` 条 CodeRabbit 与 `1` 条 Greptile open thread,测试汇总为 `2156 passed`,仅剩 `Title check` 的 inconclusive PR 元数据提示。
|
||||
- `2026-04-24` `rg -n --pcre2 '\\]\\(/zh-CN/[^)]+(?<!\\.md)\\)' docs/zh-CN/troubleshooting.md`
|
||||
- 结果:当前无命中;`/zh-CN/core/architecture` 与 `/zh-CN/faq` 已统一补成显式 `.md` 链接。
|
||||
- `2026-04-24` `bun run build`(工作目录:`docs/`)
|
||||
- 结果:通过;文档标题本地化、站内链接修正与 `ai-plan` 归档瘦身落地后站点仍可正常构建,仅保留既有大 chunk warning。
|
||||
|
||||
## 下一步
|
||||
|
||||
1. 若继续执行文档治理批处理,优先改做标题锚点、站内链接和少量非导航型裸路径引用的逐页复核,而不是继续按统一模板机械替换。
|
||||
2. 若后续继续扩展批处理 skill,可考虑再补充显式单位写法,例如 `75 files 2000 lines`,但当前默认速记已足够覆盖
|
||||
常见分支阈值场景。
|
||||
1. 若继续执行 `$gframework-batch-boot 75`,优先处理 `docs/zh-CN/index.md` 与 `tutorials/basic/01-07.md` 这 `8`
|
||||
个“已有 frontmatter 但缺 `title` / `description`”的 metadata 缺口。
|
||||
2. 推送当前 follow-up commit 后,再次执行 `$gframework-pr-review`,确认 PR `#282` 的 unresolved review threads 是否已在新 head commit 上消失。
|
||||
3. 若后续分支继续调整 `Game` persistence runtime、README 或公共 API,优先复核 `docs/zh-CN/game/data.md`、
|
||||
`storage.md`、`serialization.md`、`setting.md` 与 landing page 是否仍保持同一套职责边界。
|
||||
4. 若后续分支继续调整 `Godot` generator 接法,优先复核 `GFramework.Godot.SourceGenerators/README.md`、
|
||||
|
||||
@ -1,57 +1,43 @@
|
||||
# Documentation Full Coverage Governance Trace
|
||||
|
||||
## 2026-04-23
|
||||
## 2026-04-24
|
||||
|
||||
### 当前恢复点:RP-023
|
||||
### 当前恢复点:RP-026
|
||||
|
||||
- 按当前使用反馈继续执行 `documentation-full-coverage-governance` 下的 skill 文档治理。
|
||||
- 本轮目标定义为“继续沿用上一批的 GitHub 外链策略,收口专题页里的裸路径 README 入口”。
|
||||
- 本轮执行的修复:
|
||||
- 将 `docs/zh-CN/core/cqrs.md` 与 `ecs/arch.md` 的仓库 README 入口改为 GitHub `main` blob 外链
|
||||
- 将 `docs/zh-CN/abstractions/ecs-arch-abstractions.md`、`game/scene.md`、`game/ui.md` 的回跳 README 入口改为可点击链接
|
||||
- 将 `docs/zh-CN/source-generators/priority-generator.md`、`context-aware-generator.md`、
|
||||
`bind-node-signal-generator.md`、`godot-project-generator.md`、`get-node-generator.md`、
|
||||
`auto-register-exported-collections-generator.md` 的推荐阅读 README 入口改为可点击链接
|
||||
- 同步更新 active tracking / trace,记录第二批导航治理与新的恢复点
|
||||
- 使用 `$gframework-pr-review` 抓取 PR `#282`,确认 latest head commit
|
||||
`982249151ecf8acdff3e62e664034bf95dfacd75` 当前仍有 `3` 条 CodeRabbit 与 `1` 条 Greptile open thread。
|
||||
- 按“只处理 latest-head unresolved threads 中仍成立的问题”的原则,本轮仅收口 4 条本地可复现的 follow-up:
|
||||
- 将 `tracking.md` 与 `trace.md` 的活动入口瘦身,并把 `RP-023` 到 `RP-025` 的细节迁入新 archive 文件
|
||||
- 将 `docs/zh-CN/core/context.md` 的标题与主标题本地化为 `上下文(Context)`
|
||||
- 将 `docs/zh-CN/troubleshooting.md` 中 `/zh-CN/core/architecture` 与 `/zh-CN/faq` 统一补成显式 `.md` 链接
|
||||
|
||||
### 当前决策(RP-023)
|
||||
### 当前决策(RP-026)
|
||||
|
||||
- 继续使用 `origin/main` 作为 `$gframework-batch-boot 75` 的固定基线,并以“分支累计 diff 文件数”作为主状态指标。
|
||||
- 对文档治理类批次,优先选择“导航可达性 / 渲染一致性”这类不改变产品语义的低风险切片。
|
||||
- 在 docs 页面里出现仓库内 README 路径时,优先使用可点击的相对链接,而不是裸路径代码片段。
|
||||
- 当 docs 页需要跳转到 `docs/` 外部的 README 时,使用 GitHub `main` 分支 blob 外链,而不是跨出 `docs/` 根目录的相对路径。
|
||||
- 第二批继续沿用同一外链策略,避免在同一 docs surface 中混用“裸路径 / 相对死链 / GitHub 外链”三套入口风格。
|
||||
- PR review follow-up 继续遵守“先本地验证,再决定是否修复”;对已经过时或无法在当前分支复现的评论不做追随式修改。
|
||||
- active `ai-plan` 入口只保留当前恢复点、活动事实、风险、最新验证与下一步;批次细节统一迁入 `archive/`。
|
||||
- `docs/zh-CN` 页面应优先使用中文标题;同一帮助块中的绝对站内链接应保持一致的显式 `.md` 写法。
|
||||
|
||||
### 当前验证(RP-023)
|
||||
### 当前验证(RP-026)
|
||||
|
||||
- 导航热点巡检:
|
||||
- `rg -n '`GFramework\\.[^`]+/README\\.md`|`docs/zh-CN/[^`]+\\.md`|仓库根 `README\\.md`' docs/zh-CN -g '*.md'`
|
||||
- 结果:命中 landing / API 导航页中的裸路径仓库入口,已按本轮批次收口 7 个页面。
|
||||
- 第二批专题页巡检:
|
||||
- `rg -n '`GFramework\\.[^`]+/README\\.md`|仓库根 `README\\.md`' docs/zh-CN -g '*.md'`
|
||||
- 结果:命中 `core/cqrs.md`、`ecs/arch.md`、`abstractions/ecs-arch-abstractions.md`、`game/scene.md`、
|
||||
`game/ui.md` 与 6 个 `source-generators/*.md` 专题页,均已修复。
|
||||
- 构建校验:
|
||||
- PR review 抓取:
|
||||
- `python3 .agents/skills/gframework-pr-review/scripts/fetch_current_pr_review.py --format json --json-output /tmp/current-pr-review.json`
|
||||
- 结果:通过;PR `#282` 处于 `OPEN`,最新 review 线程与测试状态已成功解析,测试汇总为 `2156 passed`。
|
||||
- 链接巡检:
|
||||
- `rg -n --pcre2 '\\]\\(/zh-CN/[^)]+(?<!\\.md)\\)' docs/zh-CN/troubleshooting.md`
|
||||
- 结果:当前无命中;`/zh-CN/core/architecture` 与 `/zh-CN/faq` 已统一补成显式 `.md` 链接。
|
||||
- 站点构建:
|
||||
- `bun run build`(工作目录:`docs/`)
|
||||
- 结果:通过;将仓库 README 跳转改为 GitHub `main` blob 外链后,不再触发 VitePress dead link;仅保留既有大 chunk warning。
|
||||
- 当前阈值状态:
|
||||
- `git diff --name-only origin/main...HEAD | wc -l` => `24`
|
||||
- `git diff --numstat origin/main...HEAD` 汇总 => `264` changed lines
|
||||
- 结论:尚未接近 `75` 文件阈值,但剩余命中主要是正文语义性提及,当前批次在低风险模板化导航治理上可先收口。
|
||||
|
||||
### 归档摘要(RP-022)
|
||||
|
||||
- 为 `.agents/skills/gframework-batch-boot/SKILL.md` 与 `.agents/skills/README.md` 补齐数字速记 stop condition 语义。
|
||||
- 明确 `$gframework-batch-boot 75` / `75 2000` 默认绑定 `origin/main` 累计 diff 口径。
|
||||
- 完成第一批 landing / API 导航页 README 外链治理,并通过 `docs/` 站点构建。
|
||||
- 结果:通过;本轮文档与 `ai-plan` 调整后站点仍可正常构建,仅保留既有大 chunk warning。
|
||||
|
||||
### 归档指针
|
||||
|
||||
- `ai-plan/public/documentation-full-coverage-governance/archive/todos/documentation-full-coverage-governance-validation-history-through-rp-007.md`
|
||||
- `ai-plan/public/documentation-full-coverage-governance/archive/todos/documentation-full-coverage-governance-status-history-through-rp-016.md`
|
||||
- `ai-plan/public/documentation-full-coverage-governance/archive/todos/documentation-full-coverage-governance-status-history-rp-023-to-rp-025-2026-04-24.md`
|
||||
- `ai-plan/public/documentation-full-coverage-governance/archive/traces/documentation-full-coverage-governance-trace-history-through-rp-016.md`
|
||||
- `ai-plan/public/documentation-full-coverage-governance/archive/traces/documentation-full-coverage-governance-trace-history-rp-023-to-rp-025-2026-04-24.md`
|
||||
|
||||
### 下一步
|
||||
|
||||
1. 提交并推送本地修正后,再次抓取 PR `#272`,确认 Greptile open thread 是否已在新 head commit 上消失。
|
||||
2. 若继续执行文档治理批处理,优先排查剩余的非导航型裸路径引用、标题锚点与站内链接热点,而不是扩成跨模块大波次。
|
||||
1. 推送当前 follow-up commit 后,再次执行 `$gframework-pr-review`,确认 PR `#282` 的 unresolved review threads 是否已在新 head commit 上消失。
|
||||
2. 若继续执行 `$gframework-batch-boot 75`,优先处理 `docs/zh-CN/index.md` 与 `tutorials/basic/01-07.md` 这 `8` 个“已有 frontmatter 但缺 `title` / `description`”的 metadata 缺口。
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
---
|
||||
title: Core Abstractions
|
||||
description: GFramework.Core.Abstractions 的契约边界、包关系与 XML 阅读重点。
|
||||
description: GFramework.Core.Abstractions 的契约边界、包关系与源码阅读重点。
|
||||
---
|
||||
|
||||
# Core Abstractions
|
||||
@ -70,29 +70,28 @@ public sealed class DiagnosticsFeature
|
||||
|
||||
## XML 阅读重点
|
||||
|
||||
如果你在做契约审计、采用设计或扩展适配,优先核对这些类型族的 XML 文档:
|
||||
如果你在做契约确认、采用设计或扩展适配,优先核对这些类型族的 XML 文档:
|
||||
|
||||
- 架构与模块入口:`IArchitecture`、`IArchitectureContext`、`IServiceModule`
|
||||
- 运行时基础设施:`IIocContainer`、`ILogger`、`IResourceManager`、`IConfigurationManager`
|
||||
- 状态与并发能力:`IStateMachine`、`IStore`、`IAsyncKeyLockManager`、`ITimeProvider`
|
||||
- 迁移与组合边界:`ICommandExecutor`、`IQueryExecutor`、`ICqrsRuntime`
|
||||
|
||||
## XML 覆盖基线
|
||||
## 契约族阅读入口
|
||||
|
||||
下面这份 inventory 记录的是 `2026-04-22` 对 `GFramework.Core.Abstractions` 做的一轮轻量 XML 盘点结果:只统计公开 /
|
||||
内部类型声明是否带 XML 注释,用来建立契约层阅读入口;成员级参数、返回值、异常与生命周期说明仍需要后续波次继续细化。
|
||||
如果你要回到源码 XML 文档确认契约,请优先看下面这些族群:
|
||||
|
||||
| 契约族 | 基线状态 | 代表类型 | 阅读重点 |
|
||||
| --- | --- | --- | --- |
|
||||
| `Architectures/` | `12/12` 个类型声明已带 XML 注释 | `IArchitecture`、`IArchitectureContext`、`IArchitectureServices`、`IServiceModule` | 看架构上下文、服务访问面与模块安装 / 生命周期约束 |
|
||||
| `Lifecycle/` `Registries/` | `8/8` 个类型声明已带 XML 注释 | `ILifecycle`、`IAsyncInitializable`、`IRegistry<T, TR>`、`KeyValueRegistryBase<TKey, TValue>` | 看初始化 / 销毁阶段和注册表抽象边界 |
|
||||
| `Command/` `Query/` `Cqrs/` | `10/10` 个类型声明已带 XML 注释 | `ICommandExecutor`、`IAsyncCommand<TResult>`、`IQueryExecutor`、`ICqrsRuntime` | 看旧命令 / 查询接口与新请求模型之间的兼容和迁移边界 |
|
||||
| `Events/` `Property/` | `10/10` 个类型声明已带 XML 注释 | `IEventBus`、`IEventFilter<T>`、`IBindableProperty<T>`、`IReadonlyBindableProperty<T>` | 看事件传播、过滤、解绑对象和属性订阅语义 |
|
||||
| `State/` `StateManagement/` | `15/15` 个类型声明已带 XML 注释 | `IStateMachine`、`IAsyncState`、`IStore<TState>`、`IStoreMiddleware<TState>` | 看状态机契约与 Store 的 reducer / middleware / diagnostics 边界 |
|
||||
| `Coroutine/` `Time/` `Pause/` `Concurrency/` | `17/17` 个类型声明已带 XML 注释 | `IYieldInstruction`、`ICoroutineStatistics`、`ITimeProvider`、`IPauseStackManager`、`IAsyncKeyLockManager` | 看调度模型、时间源、暂停栈和异步锁契约 |
|
||||
| `Resource/` `Pool/` `Logging/` `Localization/` | `27/27` 个类型声明已带 XML 注释 | `IResourceManager`、`IObjectPoolSystem`、`ILogger`、`IStructuredLogger`、`ILocalizationManager` | 看资源 / 池化 / 日志 / 本地化这些基础设施的宿主责任 |
|
||||
| `Configuration/` `Environment/` `Data/` `Serializer/` `Storage/` `Versioning/` | `7/7` 个类型声明已带 XML 注释 | `IConfigurationManager`、`IEnvironment`、`ILoadableFrom<T>`、`ISerializer`、`IStorage`、`IVersioned` | 看配置、环境、序列化和持久化边界,以及谁负责具体实现 |
|
||||
| `Bases/` `Controller/` `Model/` `Systems/` `Utility/` `Rule/` `Enums/` `Properties/` | `19/19` 个类型声明已带 XML 注释 | `IPrioritized`、`IController`、`IModel`、`ISystem`、`IContextUtility`、`ArchitecturePhase` | 看基础角色接口、辅助值对象和架构属性键的复用方式 |
|
||||
| 契约族 | 代表类型 | 建议先确认什么 |
|
||||
| --- | --- | --- |
|
||||
| `Architectures/` | `IArchitecture`、`IArchitectureContext`、`IArchitectureServices`、`IServiceModule` | 架构上下文、服务访问面与模块安装 / 生命周期约束 |
|
||||
| `Lifecycle/` `Registries/` | `ILifecycle`、`IAsyncInitializable`、`IRegistry<T, TR>`、`KeyValueRegistryBase<TKey, TValue>` | 初始化 / 销毁阶段和注册表抽象边界 |
|
||||
| `Command/` `Query/` `Cqrs/` | `ICommandExecutor`、`IAsyncCommand<TResult>`、`IQueryExecutor`、`ICqrsRuntime` | 旧命令 / 查询接口与新请求模型之间的兼容和迁移边界 |
|
||||
| `Events/` `Property/` | `IEventBus`、`IEventFilter<T>`、`IBindableProperty<T>`、`IReadonlyBindableProperty<T>` | 事件传播、过滤、解绑对象和属性订阅语义 |
|
||||
| `State/` `StateManagement/` | `IStateMachine`、`IAsyncState`、`IStore<TState>`、`IStoreMiddleware<TState>` | 状态机契约与 Store 的 reducer / middleware / diagnostics 边界 |
|
||||
| `Coroutine/` `Time/` `Pause/` `Concurrency/` | `IYieldInstruction`、`ICoroutineStatistics`、`ITimeProvider`、`IPauseStackManager`、`IAsyncKeyLockManager` | 调度模型、时间源、暂停栈和异步锁契约 |
|
||||
| `Resource/` `Pool/` `Logging/` `Localization/` | `IResourceManager`、`IObjectPoolSystem`、`ILogger`、`IStructuredLogger`、`ILocalizationManager` | 资源 / 池化 / 日志 / 本地化这些基础设施的宿主责任 |
|
||||
| `Configuration/` `Environment/` `Data/` `Serializer/` `Storage/` `Versioning/` | `IConfigurationManager`、`IEnvironment`、`ILoadableFrom<T>`、`ISerializer`、`IStorage`、`IVersioned` | 配置、环境、序列化和持久化边界,以及谁负责具体实现 |
|
||||
| `Bases/` `Controller/` `Model/` `Systems/` `Utility/` `Rule/` `Enums/` `Properties/` | `IPrioritized`、`IController`、`IModel`、`ISystem`、`IContextUtility`、`ArchitecturePhase` | 基础角色接口、辅助值对象和架构属性键的复用方式 |
|
||||
|
||||
## 阅读顺序
|
||||
|
||||
|
||||
@ -32,13 +32,13 @@ description: GFramework.Ecs.Arch.Abstractions 的契约边界、包关系和最
|
||||
| `IArchSystemAdapter<T>` | 让 ECS 系统适配到 `ISystem` 生命周期 |
|
||||
| `ArchOptions` | 承载 `WorldCapacity`、`EnableStatistics`、`Priority` 等配置 |
|
||||
|
||||
## 类型族级 XML Inventory
|
||||
## 契约阅读入口
|
||||
|
||||
| 类型族 | 代表类型 | XML 状态 | 阅读重点 |
|
||||
| --- | --- | --- | --- |
|
||||
| 模块契约 | `IArchEcsModule` | 已覆盖 | 统一更新入口、宿主循环边界 |
|
||||
| 系统契约 | `IArchSystemAdapter<T>` | 已覆盖 | 只依赖更新接口而不绑定默认 runtime |
|
||||
| 配置对象 | `ArchOptions` | 已覆盖 | 共享配置字段与跨程序集采用边界 |
|
||||
| 类型族 | 代表类型 | 建议先确认什么 |
|
||||
| --- | --- | --- |
|
||||
| 模块契约 | `IArchEcsModule` | 统一更新入口、宿主循环边界 |
|
||||
| 系统契约 | `IArchSystemAdapter<T>` | 只依赖更新接口而不绑定默认 runtime |
|
||||
| 配置对象 | `ArchOptions` | 共享配置字段与跨程序集采用边界 |
|
||||
|
||||
## 最小接入路径
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
---
|
||||
title: Game Abstractions
|
||||
description: GFramework.Game.Abstractions 的契约边界、包关系与 XML 阅读重点。
|
||||
description: GFramework.Game.Abstractions 的契约边界、包关系与源码阅读重点。
|
||||
---
|
||||
|
||||
# Game Abstractions
|
||||
@ -37,19 +37,18 @@ description: GFramework.Game.Abstractions 的契约边界、包关系与 XML 阅
|
||||
| `Routing/` | `IRoute`、`IRouteContext`、`IRouteGuard<TRoute>`,作为 Scene / UI 共享的路由基础约定 |
|
||||
| `Storage/` `Asset/` `Enums/` | 文件存储角色、资源注册表,以及转场 / UI 层级 / 输入动作等跨层枚举 |
|
||||
|
||||
## XML 覆盖基线
|
||||
## 契约族阅读入口
|
||||
|
||||
下面这份 inventory 记录的是 `2026-04-23` 对 `GFramework.Game.Abstractions` 做的一轮轻量 XML 盘点结果:只统计公开 /
|
||||
内部类型声明是否带 XML 注释,用来建立契约层阅读入口;成员级参数、返回值、异常和生命周期说明仍需要后续 API 波次继续细化。
|
||||
如果你要回到源码 XML 文档确认契约,请优先看下面这些族群:
|
||||
|
||||
| 契约族 | 基线状态 | 代表类型 | 阅读重点 |
|
||||
| --- | --- | --- | --- |
|
||||
| `Config/` | `7/7` 个类型声明已带 XML 注释 | `IConfigLoader`、`IConfigRegistry`、`IConfigTable<TKey, TValue>`、`ConfigLoadException` | 看配置表注册、只读访问和失败诊断边界 |
|
||||
| `Data/` | `14/14` 个类型声明已带 XML 注释 | `IDataRepository`、`ISettingsDataRepository`、`ISaveRepository<TSaveData>`、`DataRepositoryOptions` | 看业务数据、统一设置文件、槽位存档与迁移契约 |
|
||||
| `Setting/` | `12/12` 个类型声明已带 XML 注释 | `ISettingsData`、`ISettingsModel`、`ISettingsSystem`、`LocalizationSettings` | 看设置生命周期、应用语义、迁移接口和内置设置对象 |
|
||||
| `Scene/` | `14/14` 个类型声明已带 XML 注释 | `IScene`、`ISceneRouter`、`ISceneFactory`、`SceneTransitionEvent` | 看场景行为、工厂 / root 边界和转场模型 |
|
||||
| `UI/` | `19/19` 个类型声明已带 XML 注释 | `IUiPage`、`IUiRouter`、`IUiFactory`、`UiInteractionProfile`、`UiTransitionHandlerOptions` | 看页面栈、层级 UI、输入动作和 UI 转场契约 |
|
||||
| `Routing/` `Storage/` `Asset/` `Enums/` | `13/13` 个类型声明已带 XML 注释 | `IRoute`、`IRouteContext`、`IFileStorage`、`IAssetRegistry<T>`、`UiLayer`、`SceneTransitionType` | 看公共路由上下文、存储角色、资源注册表与共享枚举 |
|
||||
| 契约族 | 代表类型 | 建议先确认什么 |
|
||||
| --- | --- | --- |
|
||||
| `Config/` | `IConfigLoader`、`IConfigRegistry`、`IConfigTable<TKey, TValue>`、`ConfigLoadException` | 配置表注册、只读访问和失败诊断边界 |
|
||||
| `Data/` | `IDataRepository`、`ISettingsDataRepository`、`ISaveRepository<TSaveData>`、`DataRepositoryOptions` | 业务数据、统一设置文件、槽位存档与迁移契约 |
|
||||
| `Setting/` | `ISettingsData`、`ISettingsModel`、`ISettingsSystem`、`LocalizationSettings` | 设置生命周期、应用语义、迁移接口和内置设置对象 |
|
||||
| `Scene/` | `IScene`、`ISceneRouter`、`ISceneFactory`、`SceneTransitionEvent` | 场景行为、工厂 / root 边界和转场模型 |
|
||||
| `UI/` | `IUiPage`、`IUiRouter`、`IUiFactory`、`UiInteractionProfile`、`UiTransitionHandlerOptions` | 页面栈、层级 UI、输入动作和 UI 转场契约 |
|
||||
| `Routing/` `Storage/` `Asset/` `Enums/` | `IRoute`、`IRouteContext`、`IFileStorage`、`IAssetRegistry<T>`、`UiLayer`、`SceneTransitionType` | 公共路由上下文、存储角色、资源注册表与共享枚举 |
|
||||
|
||||
## 最小接入路径
|
||||
|
||||
|
||||
@ -18,7 +18,7 @@ description: GFramework 的 API 阅读入口,按模块映射 README、专题
|
||||
|
||||
### 想确认“该装哪个包、先看哪类 API”
|
||||
|
||||
先读模块 README,再读对应 landing page:
|
||||
先读模块 README,再读对应栏目入口页:
|
||||
|
||||
- 入门入口:[`../getting-started/index.md`](../getting-started/index.md)
|
||||
- 根模块地图:仓库根 [`README.md`](https://github.com/GeWuYou/GFramework/blob/main/README.md)
|
||||
@ -51,7 +51,7 @@ description: GFramework 的 API 阅读入口,按模块映射 README、专题
|
||||
|
||||
### 先看教程和专题页的情况
|
||||
|
||||
- 你要的是最小接入路径,而不是逐个类型审计
|
||||
- 你要的是最小接入路径,而不是逐个类型展开阅读
|
||||
- 你想确认模块组合方式、目录约定和推荐接线顺序
|
||||
- 你在做从旧入口迁移到新入口的采用决策
|
||||
|
||||
|
||||
@ -1,3 +1,8 @@
|
||||
---
|
||||
title: 架构设计模式指南
|
||||
description: 围绕 GFramework 常见架构模式的职责划分、适用场景与组合建议。
|
||||
---
|
||||
|
||||
# 架构设计模式指南
|
||||
|
||||
> 全面介绍 GFramework 中的架构设计模式,帮助你构建清晰、可维护、可扩展的游戏架构。
|
||||
|
||||
@ -1,3 +1,8 @@
|
||||
---
|
||||
title: 错误处理最佳实践
|
||||
description: 总结 GFramework 项目中的错误建模、异常处理、日志记录与恢复策略。
|
||||
---
|
||||
|
||||
# 错误处理最佳实践
|
||||
|
||||
> 本指南介绍 GFramework 中的错误处理模式和最佳实践,帮助你构建健壮、可维护的游戏应用。
|
||||
|
||||
@ -1,3 +1,8 @@
|
||||
---
|
||||
title: 最佳实践
|
||||
description: 汇总使用 GFramework 时的架构、性能、错误处理与多人游戏等实践建议。
|
||||
---
|
||||
|
||||
# 最佳实践
|
||||
|
||||
本文档总结了使用 GFramework 的最佳实践和设计模式。
|
||||
|
||||
@ -1,3 +1,8 @@
|
||||
---
|
||||
title: 多人游戏架构指南
|
||||
description: 面向 GFramework 多人游戏项目的架构拆分、同步策略与网络优化建议。
|
||||
---
|
||||
|
||||
# 多人游戏架构指南
|
||||
|
||||
> 基于 GFramework 架构设计高性能、可扩展的多人游戏系统。
|
||||
@ -544,3 +549,4 @@ public class ClientPresentationSystem : AbstractSystem
|
||||
ShowKillFeed(e.KillerId, e.PlayerId);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@ -1,3 +1,8 @@
|
||||
---
|
||||
title: 性能优化指南
|
||||
description: 整理 GFramework 项目在对象池、事件、协程与资源管理上的性能优化建议。
|
||||
---
|
||||
|
||||
# 性能优化指南
|
||||
|
||||
> 全面的性能优化策略和最佳实践,帮助你构建高性能的游戏应用。
|
||||
|
||||
@ -1,3 +1,8 @@
|
||||
---
|
||||
title: 贡献指南
|
||||
description: 说明参与 GFramework 仓库贡献时的协作方式、提交流程与社区规范。
|
||||
---
|
||||
|
||||
# 贡献指南
|
||||
|
||||
欢迎为 GFramework 贡献代码!本指南将帮助你了解如何参与项目开发。
|
||||
|
||||
@ -1,12 +1,17 @@
|
||||
---
|
||||
title: 开发环境能力清单
|
||||
description: 说明 GFramework 当前开发和 AI 协作依赖的环境能力、推荐工具与刷新方式。
|
||||
---
|
||||
|
||||
# 开发环境能力清单
|
||||
|
||||
这份文档只记录对 `GFramework` 当前开发和 AI 协作真正有用的环境能力,不收录与本项目无关的系统工具。
|
||||
|
||||
如果某个工具没有出现在这里,默认表示它对当前仓库不是必需项,AI 也不应因为“系统里刚好装了”就优先使用它。
|
||||
|
||||
## 当前环境基线
|
||||
## 当前验证环境
|
||||
|
||||
当前仓库验证基线是:
|
||||
当前仓库主要在下面这组环境中验证:
|
||||
|
||||
- **运行环境**:WSL2
|
||||
- **发行版**:Ubuntu 24.04 LTS
|
||||
|
||||
@ -1,3 +1,8 @@
|
||||
---
|
||||
title: Architecture
|
||||
description: 说明 GFramework.Core 的 Architecture 入口、生命周期职责与最常用注册 API。
|
||||
---
|
||||
|
||||
# Architecture
|
||||
|
||||
`Architecture` 是 `GFramework.Core` 的运行时入口。它负责三件事:
|
||||
|
||||
@ -1,3 +1,8 @@
|
||||
---
|
||||
title: 异步初始化指南
|
||||
description: 说明 GFramework.Core 异步初始化接口、生命周期顺序与常见接入方式。
|
||||
---
|
||||
|
||||
# 异步初始化指南
|
||||
|
||||
## 概述
|
||||
|
||||
@ -1,3 +1,8 @@
|
||||
---
|
||||
title: Command
|
||||
description: 说明 GFramework.Core.Command 旧命令体系的兼容定位、可用基类与当前使用约束。
|
||||
---
|
||||
|
||||
# Command
|
||||
|
||||
本页只说明 `GFramework.Core.Command` 里的旧命令体系。
|
||||
|
||||
@ -1,3 +1,8 @@
|
||||
---
|
||||
title: Configuration 包使用说明
|
||||
description: 说明 GFramework.Core 的 Configuration 包、线程安全配置管理能力与核心接口。
|
||||
---
|
||||
|
||||
# Configuration 包使用说明
|
||||
|
||||
## 概述
|
||||
|
||||
@ -1,4 +1,9 @@
|
||||
# Context
|
||||
---
|
||||
title: 上下文(Context)
|
||||
description: 说明 IArchitectureContext 与 ArchitectureContext 的统一上下文入口和当前推荐用法。
|
||||
---
|
||||
|
||||
# 上下文(Context)
|
||||
|
||||
`IArchitectureContext` 是框架的统一上下文入口。
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
---
|
||||
title: CQRS
|
||||
description: Cqrs 模块族的运行时、契约层、生成器入口,以及 XML / API 阅读链路。
|
||||
description: Cqrs 模块族的运行时、契约层、生成器入口,以及源码与 API 阅读链路。
|
||||
---
|
||||
|
||||
# CQRS
|
||||
@ -169,17 +169,17 @@ RegisterCqrsPipelineBehavior<LoggingBehavior<,>>();
|
||||
- 在维护历史代码:允许继续使用旧 Command / Query
|
||||
- 在写新功能或新模块:优先使用 CQRS
|
||||
|
||||
## XML 覆盖基线
|
||||
## 源码阅读入口
|
||||
|
||||
下面这份 inventory 记录的是 `2026-04-22` 对 `Cqrs` 家族做的一轮轻量 XML 盘点结果:只统计当前运行时、契约层和生成器入口中的类型声明级 XML 覆盖,用来校对 README、landing page 与 API 入口,不把它表述成成员级契约全审计。
|
||||
如果你需要直接回到源码确认 CQRS 契约,建议按下面这几组入口阅读:
|
||||
|
||||
| 类型族 | 基线状态 | 代表类型 | 阅读重点 |
|
||||
| --- | --- | --- | --- |
|
||||
| `GFramework.Cqrs.Abstractions/Cqrs/` | `20/20` 个类型声明已带 XML 注释 | `ICqrsRuntime`、`ICqrsHandlerRegistrar`、`IPipelineBehavior<,>`、`IRequestHandler<,>`、`Unit` | 先看请求、处理器和 runtime seam 的最小契约 |
|
||||
| `GFramework.Cqrs/Command` `Query` `Notification` `Request` `Extensions` | `7/7` 个类型声明已带 XML 注释 | `CommandBase<TInput, TResponse>`、`QueryBase<TInput, TResponse>`、`NotificationBase<TInput>`、`ContextAwareCqrsExtensions` | 看业务侧常用基类和上下文发送入口 |
|
||||
| `GFramework.Cqrs/Cqrs/` | `12/12` 个类型声明已带 XML 注释 | `AbstractCommandHandler<,>`、`AbstractQueryHandler<,>`、`AbstractNotificationHandler<>`、`LoggingBehavior<,>` | 看默认处理器基类、上下文注入与行为管道 |
|
||||
| `GFramework.Cqrs` 根入口与 `Internal/` | `19/19` 个类型声明已带 XML 注释 | `CqrsRuntimeFactory`、`ICqrsHandlerRegistry`、`CqrsHandlerRegistryAttribute`、`CqrsReflectionFallbackAttribute`、`DefaultCqrsRegistrationService` | 看 runtime 创建入口、registry 协议、fallback 语义和程序集去重规则 |
|
||||
| `GFramework.Cqrs.SourceGenerators/Cqrs/` | `3/3` 个类型声明已带 XML 注释 | `CqrsHandlerRegistryGenerator`、`RuntimeTypeReferenceSpec`、`OrderedRegistrationKind` | 看生成注册器、精确 type lookup 和 fallback 诊断边界 |
|
||||
| 类型族 | 代表类型 | 建议先确认什么 |
|
||||
| --- | --- | --- |
|
||||
| `GFramework.Cqrs.Abstractions/Cqrs/` | `ICqrsRuntime`、`ICqrsHandlerRegistrar`、`IPipelineBehavior<,>`、`IRequestHandler<,>`、`Unit` | 请求、处理器和 runtime seam 的最小契约 |
|
||||
| `GFramework.Cqrs/Command` `Query` `Notification` `Request` `Extensions` | `CommandBase<TInput, TResponse>`、`QueryBase<TInput, TResponse>`、`NotificationBase<TInput>`、`ContextAwareCqrsExtensions` | 业务侧常用基类和上下文发送入口 |
|
||||
| `GFramework.Cqrs/Cqrs/` | `AbstractCommandHandler<,>`、`AbstractQueryHandler<,>`、`AbstractNotificationHandler<>`、`LoggingBehavior<,>` | 默认处理器基类、上下文注入与行为管道 |
|
||||
| `GFramework.Cqrs` 根入口与 `Internal/` | `CqrsRuntimeFactory`、`ICqrsHandlerRegistry`、`CqrsHandlerRegistryAttribute`、`CqrsReflectionFallbackAttribute`、`DefaultCqrsRegistrationService` | runtime 创建入口、registry 协议、fallback 语义和程序集去重规则 |
|
||||
| `GFramework.Cqrs.SourceGenerators/Cqrs/` | `CqrsHandlerRegistryGenerator`、`RuntimeTypeReferenceSpec`、`OrderedRegistrationKind` | 生成注册器、精确 type lookup 和 fallback 诊断边界 |
|
||||
|
||||
## 继续阅读
|
||||
|
||||
|
||||
@ -1,3 +1,8 @@
|
||||
---
|
||||
title: Environment 包使用说明
|
||||
description: 说明 GFramework.Core 的 Environment 包、运行时环境键值存储与核心接口。
|
||||
---
|
||||
|
||||
# Environment 包使用说明
|
||||
|
||||
## 概述
|
||||
|
||||
@ -1,3 +1,8 @@
|
||||
---
|
||||
title: Events
|
||||
description: 说明 GFramework.Core.Events 的轻量广播模型、安装方式与常用事件入口。
|
||||
---
|
||||
|
||||
# Events
|
||||
|
||||
`GFramework.Core.Events` 是架构内的轻量广播层。它适合表达“某件事已经发生”的运行时信号、模块间松耦合通知,
|
||||
|
||||
@ -1,3 +1,8 @@
|
||||
---
|
||||
title: Extensions 包使用说明
|
||||
description: 说明 GFramework.Core.Extensions 常用扩展方法的分类、用途与访问入口。
|
||||
---
|
||||
|
||||
# Extensions 包使用说明
|
||||
|
||||
## 概述
|
||||
|
||||
@ -1,3 +1,8 @@
|
||||
---
|
||||
title: 函数式编程指南
|
||||
description: 说明 GFramework.Core 的 Option、Result 与函数式工具在业务代码中的用法。
|
||||
---
|
||||
|
||||
# 函数式编程指南
|
||||
|
||||
## 概述
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
---
|
||||
title: Core
|
||||
description: GFramework.Core 与 GFramework.Core.Abstractions 的运行时入口、采用顺序和 XML 阅读导航。
|
||||
description: GFramework.Core 与 GFramework.Core.Abstractions 的运行时入口、采用顺序和源码阅读导航。
|
||||
---
|
||||
|
||||
# Core
|
||||
@ -82,22 +82,21 @@ dotnet add package GeWuYou.GFramework.Core.Abstractions
|
||||
|
||||
统一入口见 [`../api-reference/index.md`](../api-reference/index.md)。
|
||||
|
||||
## XML 覆盖基线
|
||||
## 源码阅读入口
|
||||
|
||||
下面这份 inventory 记录的是 `2026-04-22` 对 `GFramework.Core` 做的一轮轻量 XML 盘点结果:只统计顶层目录中的公开 /
|
||||
内部类型声明是否带 XML 注释,用来确认阅读入口和治理优先级;成员级 ``<param>``、``<returns>``、异常语义与线程说明仍需要继续细审。
|
||||
如果你准备直接回到源码和 XML 文档确认契约,建议按能力域分批阅读,而不是按文件数量排查:
|
||||
|
||||
| 类型族 | 基线状态 | 代表类型 | 阅读重点 |
|
||||
| --- | --- | --- | --- |
|
||||
| `Architectures/` | `16/16` 个类型声明已带 XML 注释 | `Architecture`、`ArchitectureContext`、`ArchitectureLifecycle`、`ArchitecturePhaseCoordinator` | 看架构启动、模块安装、阶段切换和上下文暴露边界 |
|
||||
| `Services/` | `6/6` 个类型声明已带 XML 注释 | `ServiceModuleManager`、`CommandExecutorModule`、`CqrsRuntimeModule` | 看服务模块的注册顺序、销毁语义和默认接线 |
|
||||
| `Command/` `Query/` | `15/15` 个类型声明已带 XML 注释 | `CommandExecutor`、`AsyncQueryExecutor`、`AbstractCommand<TInput>`、`AbstractQuery<TResult>` | 看旧入口兼容面与向 `CQRS` 迁移时还保留了哪些执行契约 |
|
||||
| `Events/` `Property/` | `19/19` 个类型声明已带 XML 注释 | `EventBus`、`EnhancedEventBus`、`BindableProperty<T>`、`OrEvent<T>` | 看事件传播、解绑约束和可绑定属性的订阅语义 |
|
||||
| `State/` `StateManagement/` | `10/10` 个类型声明已带 XML 注释 | `StateMachine`、`StateMachineSystem`、`Store<TState>`、`StoreBuilder<TState>` | 看状态切换、selector / middleware / dispatch 的单向流边界 |
|
||||
| `Coroutine/` `Time/` `Pause/` `Concurrency/` | `43/43` 个类型声明已带 XML 注释 | `CoroutineScheduler`、`CoroutineHandle`、`WaitForSecondsRealtime`、`PauseStackManager`、`AsyncKeyLockManager` | 看调度阶段、等待指令、时间源和暂停 / 锁的线程语义 |
|
||||
| `Resource/` `Pool/` | `8/8` 个类型声明已带 XML 注释 | `ResourceManager`、`AutoReleaseStrategy`、`ManualReleaseStrategy`、`AbstractObjectPoolSystem<TKey, TObject>` | 看资源句柄释放策略与对象池复用约束 |
|
||||
| `Logging/` `Localization/` `Configuration/` `Environment/` `Ioc/` | `31/31` 个类型声明已带 XML 注释 | `ConsoleLogger`、`CompositeLogger`、`LocalizationManager`、`ConfigurationManager`、`MicrosoftDiContainer` | 看日志组装、格式化 / filter、配置监听、环境对象与容器适配 |
|
||||
| `Model/` `Systems/` `Utility/` `Rule/` `Extensions/` `Functional/` | `34/34` 个类型声明已带 XML 注释 | `AbstractModel`、`AbstractSystem`、`NumericDisplayFormatter`、`ContextAwareBase`、`Result<T>` | 看默认基类、上下文感知 helper、数值格式化和通用扩展的使用边界 |
|
||||
| 类型族 | 代表类型 | 建议先确认什么 |
|
||||
| --- | --- | --- |
|
||||
| `Architectures/` | `Architecture`、`ArchitectureContext`、`ArchitectureLifecycle`、`ArchitecturePhaseCoordinator` | 架构启动、模块安装、阶段切换和上下文暴露边界 |
|
||||
| `Services/` | `ServiceModuleManager`、`CommandExecutorModule`、`CqrsRuntimeModule` | 服务模块的注册顺序、销毁语义和默认接线 |
|
||||
| `Command/` `Query/` | `CommandExecutor`、`AsyncQueryExecutor`、`AbstractCommand<TInput>`、`AbstractQuery<TResult>` | 旧入口兼容面,以及向 `CQRS` 迁移时保留的执行契约 |
|
||||
| `Events/` `Property/` | `EventBus`、`EnhancedEventBus`、`BindableProperty<T>`、`OrEvent<T>` | 事件传播、解绑约束和可绑定属性的订阅语义 |
|
||||
| `State/` `StateManagement/` | `StateMachine`、`StateMachineSystem`、`Store<TState>`、`StoreBuilder<TState>` | 状态切换,以及 selector / middleware / dispatch 的单向流边界 |
|
||||
| `Coroutine/` `Time/` `Pause/` `Concurrency/` | `CoroutineScheduler`、`CoroutineHandle`、`WaitForSecondsRealtime`、`PauseStackManager`、`AsyncKeyLockManager` | 调度阶段、等待指令、时间源,以及暂停 / 锁的线程语义 |
|
||||
| `Resource/` `Pool/` | `ResourceManager`、`AutoReleaseStrategy`、`ManualReleaseStrategy`、`AbstractObjectPoolSystem<TKey, TObject>` | 资源句柄释放策略与对象池复用约束 |
|
||||
| `Logging/` `Localization/` `Configuration/` `Environment/` `Ioc/` | `ConsoleLogger`、`CompositeLogger`、`LocalizationManager`、`ConfigurationManager`、`MicrosoftDiContainer` | 日志组装、格式化 / filter、配置监听、环境对象与容器适配 |
|
||||
| `Model/` `Systems/` `Utility/` `Rule/` `Extensions/` `Functional/` | `AbstractModel`、`AbstractSystem`、`NumericDisplayFormatter`、`ContextAwareBase`、`Result<T>` | 默认基类、上下文感知 helper、数值格式化和通用扩展的使用边界 |
|
||||
|
||||
## 最小接入路径
|
||||
|
||||
|
||||
@ -1,3 +1,8 @@
|
||||
---
|
||||
title: IoC 包使用说明
|
||||
description: 说明 GFramework.Core 的 IoC 容器、依赖注入职责与核心类型。
|
||||
---
|
||||
|
||||
# IoC 包使用说明
|
||||
|
||||
## 概述
|
||||
@ -441,7 +446,7 @@ public class IocContainer
|
||||
|
||||
### 线程安全机制
|
||||
|
||||
容器使用 [ReaderWriterLockSlim](xref:System.Threading.ReaderWriterLockSlim) 来确保线程安全操作,允许多个线程同时读取,但在写入时阻止其他线程访问。
|
||||
容器使用 `ReaderWriterLockSlim` 来确保线程安全操作,允许多个线程同时读取,但在写入时阻止其他线程访问。
|
||||
|
||||
### 注册流程
|
||||
|
||||
|
||||
@ -1,3 +1,8 @@
|
||||
---
|
||||
title: Localization 本地化系统
|
||||
description: 说明 GFramework.Core 本地化系统的核心接口、语言切换能力与使用场景。
|
||||
---
|
||||
|
||||
# Localization 本地化系统
|
||||
|
||||
## 概述
|
||||
|
||||
@ -1,3 +1,8 @@
|
||||
---
|
||||
title: Logging
|
||||
description: 说明 GFramework.Core.Logging 的日志接口、组合方式与常见使用入口。
|
||||
---
|
||||
|
||||
# Logging
|
||||
|
||||
`GFramework.Core.Logging` 是 Core runtime 的默认日志实现。只加载抽象层时,`LoggerFactoryResolver` 会退回
|
||||
|
||||
@ -1,3 +1,8 @@
|
||||
---
|
||||
title: Model 包使用说明
|
||||
description: 说明 GFramework.Core.Model 的模型职责、基类结构与常见生命周期入口。
|
||||
---
|
||||
|
||||
# Model 包使用说明
|
||||
|
||||
## 概述
|
||||
|
||||
@ -1,3 +1,8 @@
|
||||
---
|
||||
title: 暂停管理系统使用说明
|
||||
description: 说明 GFramework.Core 暂停管理系统的栈模型、作用域与协作方式。
|
||||
---
|
||||
|
||||
# 暂停管理系统使用说明
|
||||
|
||||
## 概述
|
||||
|
||||
@ -1,3 +1,8 @@
|
||||
---
|
||||
title: 对象池系统 (Object Pool System)
|
||||
description: 说明 GFramework.Core 对象池系统的核心组件、池化策略与生命周期管理。
|
||||
---
|
||||
|
||||
# 对象池系统 (Object Pool System)
|
||||
|
||||
## 概述
|
||||
|
||||
@ -1,3 +1,8 @@
|
||||
---
|
||||
title: Property
|
||||
description: 说明 GFramework.Core.Property 的可绑定属性模型、订阅方式与常见用法。
|
||||
---
|
||||
|
||||
# Property
|
||||
|
||||
`GFramework.Core.Property` 负责字段级响应式值。它最适合“一个字段变化就足以驱动视图或局部业务逻辑”的场景;
|
||||
|
||||
@ -1,3 +1,8 @@
|
||||
---
|
||||
title: Query
|
||||
description: 说明 GFramework.Core.Query 旧查询体系的兼容定位、可用基类与当前使用约束。
|
||||
---
|
||||
|
||||
# Query
|
||||
|
||||
本页说明 `GFramework.Core.Query` 里的旧查询体系。
|
||||
|
||||
@ -1,3 +1,8 @@
|
||||
---
|
||||
title: Rule 包使用说明
|
||||
description: 说明 GFramework.Core.Rule 中 IContextAware 规则接口与上下文访问约定。
|
||||
---
|
||||
|
||||
# Rule 包使用说明
|
||||
|
||||
## 概述
|
||||
|
||||
@ -1,3 +1,8 @@
|
||||
---
|
||||
title: State Management 包使用说明
|
||||
description: 说明 GFramework.Core.StateManagement 的 Store、Reducer 与状态容器用法。
|
||||
---
|
||||
|
||||
# State Management 包使用说明
|
||||
|
||||
## 概述
|
||||
@ -482,7 +487,7 @@ public partial class PlayerPanelController : IController
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [`property`](./property) - 字段级响应式属性
|
||||
- [`model`](./model) - Store 常见承载位置
|
||||
- [`events`](./events) - 组件间事件通信
|
||||
- [`state-machine-tutorial`](../tutorials/state-machine-tutorial) - 流程状态切换能力
|
||||
- [`property`](./property.md) - 字段级响应式属性
|
||||
- [`model`](./model.md) - Store 常见承载位置
|
||||
- [`events`](./events.md) - 组件间事件通信
|
||||
- [`state-machine-tutorial`](../tutorials/state-machine-tutorial.md) - 流程状态切换能力
|
||||
|
||||
@ -1,3 +1,8 @@
|
||||
---
|
||||
title: System 包使用说明
|
||||
description: 说明 GFramework.Core.System 的业务逻辑层职责、基类结构与协作方式。
|
||||
---
|
||||
|
||||
# System 包使用说明
|
||||
|
||||
## 概述
|
||||
|
||||
@ -1,3 +1,8 @@
|
||||
---
|
||||
title: Utility 包使用说明
|
||||
description: 说明 GFramework.Core.Utility 的工具组件定位、注册方式与使用场景。
|
||||
---
|
||||
|
||||
# Utility 包使用说明
|
||||
|
||||
## 概述
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
---
|
||||
title: Arch ECS 集成
|
||||
description: GFramework.Ecs.Arch 的默认运行时装配路径、系统桥接方式与 XML 阅读入口。
|
||||
description: GFramework.Ecs.Arch 的默认运行时装配路径、系统桥接方式与源码阅读入口。
|
||||
---
|
||||
|
||||
# Arch ECS 集成
|
||||
@ -127,14 +127,14 @@ ecsModule.Update(deltaTime);
|
||||
- `Priority` 影响 `ArchEcsModule` 作为服务模块的排序
|
||||
- `EnableStatistics` 目前保留在公开配置面上;采用时应以源码 XML 注释和实现行为为准,而不是依赖旧文档推断
|
||||
|
||||
## 类型族级 XML Inventory
|
||||
## 源码阅读入口
|
||||
|
||||
| 类型族 | 代表类型 | XML 状态 | 阅读重点 |
|
||||
| --- | --- | --- | --- |
|
||||
| 装配入口 | `ArchExtensions` | 已覆盖 | `UseArch(...)` 的时机、链式调用返回值 |
|
||||
| 服务模块 | `ArchEcsModule` | 已覆盖 | `World` 注册、系统收集、模块销毁顺序 |
|
||||
| 系统桥接层 | `ArchSystemAdapter<T>` | 已覆盖 | `OnArchInitialize` / `OnUpdate` / `OnArchDispose` |
|
||||
| 示例类型 | `Position`、`Velocity`、`MovementSystem` | 已覆盖 | 组件布局、查询写法、最小集成样例 |
|
||||
| 类型族 | 代表类型 | 建议先确认什么 |
|
||||
| --- | --- | --- |
|
||||
| 装配入口 | `ArchExtensions` | `UseArch(...)` 的时机、链式调用返回值 |
|
||||
| 服务模块 | `ArchEcsModule` | `World` 注册、系统收集、模块销毁顺序 |
|
||||
| 系统桥接层 | `ArchSystemAdapter<T>` | `OnArchInitialize` / `OnUpdate` / `OnArchDispose` |
|
||||
| 示例类型 | `Position`、`Velocity`、`MovementSystem` | 组件布局、查询写法和最小集成样例 |
|
||||
|
||||
## 相关入口
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
---
|
||||
title: ECS 系统集成
|
||||
description: GFramework 当前 ECS 模块族的包边界、采用顺序与 XML 阅读入口。
|
||||
description: GFramework 当前 ECS 模块族的包边界、采用顺序与源码阅读入口。
|
||||
---
|
||||
|
||||
# ECS 系统集成
|
||||
@ -118,16 +118,16 @@ public sealed class GameLoop
|
||||
3. 只想保留共享边界时继续读 [`../abstractions/ecs-arch-abstractions.md`](../abstractions/ecs-arch-abstractions.md)
|
||||
4. 统一查阅 README / docs / XML 入口时回到 [`../api-reference/index.md`](../api-reference/index.md)
|
||||
|
||||
## 类型族级 XML Inventory
|
||||
## 源码阅读入口
|
||||
|
||||
下表记录当前 `Ecs.Arch` family 的类型声明级 XML 基线,便于从 README、站内 landing 和源码之间建立一致的审计入口。
|
||||
如果你要从栏目入口页回到源码和 XML 文档,建议按下面的入口阅读:
|
||||
|
||||
| 包 | 类型族 | 代表类型 | XML 状态 | 阅读重点 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| `GFramework.Ecs.Arch` | 运行时装配与模块生命周期 | `ArchExtensions`、`ArchEcsModule` | 已覆盖 | `UseArch(...)` 的接入时机、`World` 注册、模块优先级 |
|
||||
| `GFramework.Ecs.Arch` | 系统桥接层 | `ArchSystemAdapter<T>` | 已覆盖 | GFramework `ISystem` 生命周期如何桥接到 Arch `ISystem<T>` |
|
||||
| `GFramework.Ecs.Arch` | 示例组件与系统 | `Position`、`Velocity`、`MovementSystem` | 已覆盖 | 查询写法、组件布局、最小可运行示例 |
|
||||
| `GFramework.Ecs.Arch.Abstractions` | 契约与配置对象 | `IArchEcsModule`、`IArchSystemAdapter<T>`、`ArchOptions` | 已覆盖 | 共享宿主循环、测试替身、跨程序集配置边界 |
|
||||
| 包 | 类型族 | 代表类型 | 建议先确认什么 |
|
||||
| --- | --- | --- | --- |
|
||||
| `GFramework.Ecs.Arch` | 运行时装配与模块生命周期 | `ArchExtensions`、`ArchEcsModule` | `UseArch(...)` 的接入时机、`World` 注册、模块优先级 |
|
||||
| `GFramework.Ecs.Arch` | 系统桥接层 | `ArchSystemAdapter<T>` | GFramework `ISystem` 生命周期如何桥接到 Arch `ISystem<T>` |
|
||||
| `GFramework.Ecs.Arch` | 示例组件与系统 | `Position`、`Velocity`、`MovementSystem` | 查询写法、组件布局和最小可运行示例 |
|
||||
| `GFramework.Ecs.Arch.Abstractions` | 契约与配置对象 | `IArchEcsModule`、`IArchSystemAdapter<T>`、`ArchOptions` | 共享宿主循环、测试替身和跨程序集配置边界 |
|
||||
|
||||
## 边界说明
|
||||
|
||||
|
||||
@ -1,3 +1,8 @@
|
||||
---
|
||||
title: 常见问题(FAQ)
|
||||
description: 汇总 GFramework 在安装、架构、命令、事件与性能方面的常见问题。
|
||||
---
|
||||
|
||||
# 常见问题(FAQ)
|
||||
|
||||
## 安装与配置
|
||||
|
||||
@ -1,3 +1,8 @@
|
||||
---
|
||||
title: 游戏内容配置系统
|
||||
description: 说明 GFramework.Game 配置系统的定位、目录约定、生成能力与最小采用路径。
|
||||
---
|
||||
|
||||
# 游戏内容配置系统
|
||||
|
||||
> 面向静态游戏内容的 AI-First 配表方案
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
---
|
||||
title: Game
|
||||
description: GFramework.Game family 的运行时入口、采用顺序与 XML 阅读基线。
|
||||
description: GFramework.Game family 的运行时入口、采用顺序与源码阅读导航。
|
||||
---
|
||||
|
||||
# Game
|
||||
@ -103,15 +103,15 @@ IStorage storage = new FileStorage("GameData", serializer);
|
||||
4. [setting](./setting.md)
|
||||
5. [scene](./scene.md) 或 [ui](./ui.md)
|
||||
|
||||
## Game Family XML 覆盖基线
|
||||
## 源码与 API 阅读入口
|
||||
|
||||
下面这份 inventory 记录的是 `2026-04-23` 对 `Game` family 做的一轮轻量 XML 盘点结果:只统计公开 / 内部类型声明是否带 XML 注释,用来建立 README / landing / API 阅读链路;成员级 `param`、`returns`、`exception` 与生命周期说明仍需要后续波次继续细化。
|
||||
如果你已经完成栏目入口页阅读,下一步通常不是看统计表,而是按模块角色回到源码和 XML 文档:
|
||||
|
||||
| 模块 | 基线状态 | 代表类型 | 阅读重点 |
|
||||
| --- | --- | --- | --- |
|
||||
| `GFramework.Game` | `56/56` 个类型声明已带 XML 注释 | `YamlConfigLoader`、`SettingsModel<TRepository>`、`SceneRouterBase`、`UiRouterBase` | 先看运行时默认实现、配置加载、设置编排和路由基类 |
|
||||
| `GFramework.Game.Abstractions` | `80/80` 个类型声明已带 XML 注释 | `IConfigRegistry`、`ISaveRepository<TSaveData>`、`ISettingsSystem`、`ISceneRouter`、`IUiRouter` | 再看契约层边界,决定项目哪些程序集只依赖接口 |
|
||||
| `GFramework.Game.SourceGenerators` | `2/2` 个类型声明已带 XML 注释 | `SchemaConfigGenerator`、`ConfigSchemaDiagnostics` | 最后看 schema 生成入口与诊断模型,确认配置系统的编译期链路 |
|
||||
| 模块 | 代表类型 | 建议先确认什么 |
|
||||
| --- | --- | --- |
|
||||
| `GFramework.Game` | `YamlConfigLoader`、`SettingsModel<TRepository>`、`SceneRouterBase`、`UiRouterBase` | 默认运行时实现、配置加载、设置编排和路由基类 |
|
||||
| `GFramework.Game.Abstractions` | `IConfigRegistry`、`ISaveRepository<TSaveData>`、`ISettingsSystem`、`ISceneRouter`、`IUiRouter` | 契约层边界,以及项目中哪些程序集只应依赖接口 |
|
||||
| `GFramework.Game.SourceGenerators` | `SchemaConfigGenerator`、`ConfigSchemaDiagnostics` | schema 生成入口与诊断模型,确认配置系统的编译期链路 |
|
||||
|
||||
## 与真实接法的关系
|
||||
|
||||
|
||||
@ -1,3 +1,8 @@
|
||||
---
|
||||
title: 入门指南
|
||||
description: 概览 GFramework 的模块组成、最小接入路径与继续阅读入口。
|
||||
---
|
||||
|
||||
# 入门指南
|
||||
|
||||
这一部分只回答三个问题:
|
||||
|
||||
@ -1,3 +1,8 @@
|
||||
---
|
||||
title: 安装配置
|
||||
description: 说明 GFramework 各运行时与 source generator 包的安装选择和配置方式。
|
||||
---
|
||||
|
||||
# 安装配置
|
||||
|
||||
GFramework 提供多种安装方式,您可以根据项目需求选择合适的包进行安装。
|
||||
|
||||
@ -1,3 +1,8 @@
|
||||
---
|
||||
title: 快速开始
|
||||
description: 通过只依赖 Core 的最小示例快速跑通 GFramework 基础架构。
|
||||
---
|
||||
|
||||
# 快速开始
|
||||
|
||||
本页给出一个只依赖 `Core` 的最小路径,用来确认你已经成功接入 `Architecture`、`Model`、`System` 与旧版命令执行器。
|
||||
|
||||
@ -1,3 +1,8 @@
|
||||
---
|
||||
title: Godot 协程系统
|
||||
description: 说明 GFramework.Godot.Coroutine 的宿主集成能力、阶段语义与使用方式。
|
||||
---
|
||||
|
||||
# Godot 协程系统
|
||||
|
||||
## 概述
|
||||
|
||||
@ -1,3 +1,8 @@
|
||||
---
|
||||
title: AutoRegisterModule 生成器
|
||||
description: 介绍 AutoRegisterModule 生成器如何为模块安装生成固定顺序的注册代码。
|
||||
---
|
||||
|
||||
# AutoRegisterModule 生成器
|
||||
|
||||
> 为架构模块生成固定顺序的组件注册代码,收敛 `Install(IArchitecture)` 样板。
|
||||
@ -149,5 +154,5 @@ partial class GameplayModule
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [源码生成器总览](./index)
|
||||
- [Context Get 注入生成器](./context-get-generator)
|
||||
- [源码生成器总览](./index.md)
|
||||
- [Context Get 注入生成器](./context-get-generator.md)
|
||||
|
||||
@ -1,3 +1,8 @@
|
||||
---
|
||||
title: AutoScene 生成器
|
||||
description: 介绍 AutoScene 生成器如何生成场景包装入口与统一场景键声明。
|
||||
---
|
||||
|
||||
# AutoScene 生成器
|
||||
|
||||
> 为场景根节点生成 `GetScene()` 样板,统一场景键声明与行为包装。
|
||||
@ -116,5 +121,5 @@ partial class GameplayRoot
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [源码生成器总览](./index)
|
||||
- [AutoUiPage 生成器](./auto-ui-page-generator)
|
||||
- [源码生成器总览](./index.md)
|
||||
- [AutoUiPage 生成器](./auto-ui-page-generator.md)
|
||||
|
||||
@ -1,3 +1,8 @@
|
||||
---
|
||||
title: AutoUiPage 生成器
|
||||
description: 介绍 AutoUiPage 生成器如何为 Godot UI 页面生成页面包装入口。
|
||||
---
|
||||
|
||||
# AutoUiPage 生成器
|
||||
|
||||
> 为 Godot UI 页面生成 `GetPage()` 样板,统一页面键与层级声明。
|
||||
@ -149,6 +154,6 @@ public partial class PauseMenu : Control
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [源码生成器总览](./index)
|
||||
- [GetNode 生成器](./get-node-generator)
|
||||
- [BindNodeSignal 生成器](./bind-node-signal-generator)
|
||||
- [源码生成器总览](./index.md)
|
||||
- [GetNode 生成器](./get-node-generator.md)
|
||||
- [BindNodeSignal 生成器](./bind-node-signal-generator.md)
|
||||
|
||||
@ -11,7 +11,7 @@ description: 说明 [BindNodeSignal] 当前生成什么、如何与 GetNode 协
|
||||
|
||||
- 特性来源:`GFramework.Godot.SourceGenerators.Abstractions`
|
||||
- 生成器实现:`GFramework.Godot.SourceGenerators`
|
||||
- 目标字段基线:`nodeFieldName` 指向的字段必须继承 `Godot.Node`
|
||||
- 使用前提:`nodeFieldName` 指向的字段必须继承 `Godot.Node`
|
||||
|
||||
## 最小用法
|
||||
|
||||
|
||||
@ -1,3 +1,8 @@
|
||||
---
|
||||
title: Context Get 注入生成器
|
||||
description: 介绍 Context Get 注入生成器的能力、依赖前提与使用方式。
|
||||
---
|
||||
|
||||
# Context Get 注入生成器
|
||||
|
||||
> 自动注入架构组件,消除样板代码
|
||||
|
||||
@ -116,7 +116,7 @@ RegisterCqrsHandlersFromAssemblies(
|
||||
|
||||
这条诊断的含义不是“某个 handler 写错了”,而是“当前 runtime 合同不足以安全承载这轮生成结果”。
|
||||
|
||||
## XML / API 阅读入口
|
||||
## 源码与 API 阅读入口
|
||||
|
||||
如果你要核对生成器对外暴露的契约,优先看这些类型:
|
||||
|
||||
|
||||
@ -1,3 +1,8 @@
|
||||
---
|
||||
title: 枚举扩展生成器
|
||||
description: 介绍枚举扩展生成器生成的 IsX 与 IsIn 方法及其典型用法。
|
||||
---
|
||||
|
||||
# 枚举扩展生成器
|
||||
|
||||
> 自动为枚举类型生成扩展方法
|
||||
@ -267,6 +272,6 @@ public class PlayerController
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [Source Generators 概述](./index)
|
||||
- [日志生成器](./logging-generator)
|
||||
- [ContextAware 生成器](./context-aware-generator)
|
||||
- [Source Generators 概述](./index.md)
|
||||
- [日志生成器](./logging-generator.md)
|
||||
- [ContextAware 生成器](./context-aware-generator.md)
|
||||
|
||||
@ -11,7 +11,7 @@ description: 说明 [GetNode] 当前生成什么、路径如何推断,以及 _
|
||||
|
||||
- 特性来源:`GFramework.Godot.SourceGenerators.Abstractions`
|
||||
- 生成器实现:`GFramework.Godot.SourceGenerators`
|
||||
- 目标类型基线:字段类型必须继承 `Godot.Node`
|
||||
- 使用前提:字段类型必须继承 `Godot.Node`
|
||||
|
||||
## 最小用法
|
||||
|
||||
|
||||
@ -1,3 +1,8 @@
|
||||
---
|
||||
title: 日志生成器
|
||||
description: 介绍日志生成器如何为标记类型生成 ILogger 字段并减少日志样板。
|
||||
---
|
||||
|
||||
# 日志生成器
|
||||
|
||||
> GFramework.Core.SourceGenerators 自动生成日志代码,减少样板代码
|
||||
@ -336,6 +341,6 @@ public static partial class StaticHelper
|
||||
|
||||
**相关文档**:
|
||||
|
||||
- [Source Generators 概述](./index)
|
||||
- [枚举扩展生成器](./enum-generator)
|
||||
- [ContextAware 生成器](./context-aware-generator)
|
||||
- [Source Generators 概述](./index.md)
|
||||
- [枚举扩展生成器](./enum-generator.md)
|
||||
- [ContextAware 生成器](./context-aware-generator.md)
|
||||
|
||||
@ -1,3 +1,8 @@
|
||||
---
|
||||
title: 故障排除与调试
|
||||
description: 提供 GFramework 常见安装、架构、事件与运行时问题的排查思路。
|
||||
---
|
||||
|
||||
# 故障排除与调试
|
||||
|
||||
本指南帮助你诊断和解决 GFramework 使用中的常见问题。
|
||||
@ -2047,12 +2052,12 @@ public class MyLoader : IResourceLoader
|
||||
|
||||
如果问题仍未解决:
|
||||
|
||||
1. 查看 [Core 文档](/zh-CN/core/) 了解更多细节
|
||||
2. 查看 [架构组件](/zh-CN/core/architecture) 了解架构设计
|
||||
3. 查看 [Godot 集成](/zh-CN/godot/) 了解 Godot 特定问题
|
||||
1. 查看 [Core 文档](/zh-CN/core/index.md) 了解更多细节
|
||||
2. 查看 [架构组件](/zh-CN/core/architecture.md) 了解架构设计
|
||||
3. 查看 [Godot 集成](/zh-CN/godot/index.md) 了解 Godot 特定问题
|
||||
4. 在 [GitHub Issues](https://github.com/GeWuYou/GFramework/issues) 提交问题
|
||||
5. 查看 [教程](/zh-CN/tutorials/) 中的示例代码
|
||||
6. 查看 [常见问题](/zh-CN/faq) 获取快速答案
|
||||
5. 查看 [教程](/zh-CN/tutorials/index.md) 中的示例代码
|
||||
6. 查看 [常见问题](/zh-CN/faq.md) 获取快速答案
|
||||
|
||||
---
|
||||
|
||||
|
||||
@ -1,3 +1,8 @@
|
||||
---
|
||||
title: 高级模式教程
|
||||
description: 通过高级模式示例说明 GFramework 中的复杂架构组织与扩展方式。
|
||||
---
|
||||
|
||||
# 高级模式教程
|
||||
|
||||
> 深入学习 GFramework 的高级特性和设计模式,构建更复杂和可维护的游戏系统。
|
||||
|
||||
@ -1,3 +1,8 @@
|
||||
---
|
||||
title: 基础教程:从零开始使用 GFramework
|
||||
description: 从零开始串联环境准备、项目搭建与核心概念的基础教程入口。
|
||||
---
|
||||
|
||||
# 基础教程:从零开始使用 GFramework
|
||||
|
||||
欢迎来到 GFramework 的基础教程!本教程将带你从零开始,创建一个完整的计数器应用,逐步掌握 GFramework 的核心概念和最佳实践。
|
||||
|
||||
@ -1,3 +1,8 @@
|
||||
---
|
||||
title: 教程
|
||||
description: 汇总 GFramework 的基础与进阶教程入口,帮助按学习阶段选择阅读路径。
|
||||
---
|
||||
|
||||
# 教程
|
||||
|
||||
欢迎来到 GFramework 教程中心!这里提供从入门到精进的完整学习路径,帮助你掌握这个面向游戏开发的模块化 C# 框架。
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user