mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-05-06 16:16:44 +08:00
- 新增游戏内容配置系统完整文档,包含 YAML 配置、JSON Schema 结构描述 - 添加推荐目录结构和配置示例,支持怪物、物品、技能等静态内容管理 - 实现 Source Generator 自动生成配置类型、表包装和注册访问辅助功能 - 集成 VS Code 插件提供配置浏览、raw 编辑、schema 打开和校验功能 - 添加生成查询辅助,为顶层标量字段生成 FindBy* 与 TryFindFirstBy* 方法 - 实现开发期热重载功能,支持配置文件修改后自动刷新运行时表 - 添加跨表引用校验,支持 x-gframework-ref-table 声明的引用关系检查 - 新增集成测试验证生成器自动拾取 schema 并支持强类型访问入口 - 添加 IsExternalInit 类型支持低版本 .NET 框架的 init-only setter 功能
152 lines
4.9 KiB
C#
152 lines
4.9 KiB
C#
using System;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Threading.Tasks;
|
|
using GFramework.Core.Architectures;
|
|
using GFramework.Game.Config;
|
|
using GFramework.Game.Config.Generated;
|
|
using NUnit.Framework;
|
|
|
|
namespace GFramework.Game.Tests.Config;
|
|
|
|
/// <summary>
|
|
/// 验证在 <see cref="Architecture" /> 初始化流程中可以注册配置注册表、执行加载并通过生成的表访问器读取数据。
|
|
/// </summary>
|
|
[TestFixture]
|
|
public class ArchitectureConfigIntegrationTests
|
|
{
|
|
/// <summary>
|
|
/// 架构初始化期间,通过 <see cref="YamlConfigLoader" /> 注册生成表,
|
|
/// 并将 <see cref="ConfigRegistry" /> 作为 utility 暴露给架构上下文读取。
|
|
/// </summary>
|
|
[Test]
|
|
public async Task ConfigLoaderCanRunDuringArchitectureInitialization()
|
|
{
|
|
var rootPath = CreateTempConfigRoot();
|
|
ConsumerArchitecture? architecture = null;
|
|
var initialized = false;
|
|
try
|
|
{
|
|
architecture = new ConsumerArchitecture(rootPath);
|
|
await architecture.InitializeAsync();
|
|
initialized = true;
|
|
|
|
var table = architecture.MonsterTable;
|
|
|
|
Assert.Multiple(() =>
|
|
{
|
|
Assert.That(table.Get(1).Name, Is.EqualTo("Slime"));
|
|
Assert.That(table.Get(2).Hp, Is.EqualTo(30));
|
|
Assert.That(table.FindByFaction("dungeon").Select(static config => config.Name),
|
|
Is.EquivalentTo(new[] { "Slime", "Goblin" }));
|
|
Assert.That(architecture.Registry.TryGetMonsterTable(out var retrieved), Is.True);
|
|
Assert.That(retrieved, Is.Not.Null);
|
|
Assert.That(retrieved!.Get(1).Name, Is.EqualTo("Slime"));
|
|
Assert.That(architecture.Context.GetUtility<ConfigRegistry>(), Is.SameAs(architecture.Registry));
|
|
});
|
|
}
|
|
finally
|
|
{
|
|
if (architecture is not null && initialized)
|
|
{
|
|
await architecture.DestroyAsync();
|
|
}
|
|
|
|
DeleteDirectoryIfExists(rootPath);
|
|
}
|
|
}
|
|
|
|
private static string CreateTempConfigRoot()
|
|
{
|
|
var rootPath = Path.Combine(Path.GetTempPath(), "GFramework.ConfigArchitecture", Guid.NewGuid().ToString("N"));
|
|
Directory.CreateDirectory(rootPath);
|
|
Directory.CreateDirectory(Path.Combine(rootPath, "schemas"));
|
|
Directory.CreateDirectory(Path.Combine(rootPath, "monster"));
|
|
File.WriteAllText(Path.Combine(rootPath, "schemas", "monster.schema.json"), MonsterSchemaJson);
|
|
File.WriteAllText(Path.Combine(rootPath, "monster", "slime.yaml"), MonsterSlimeYaml);
|
|
File.WriteAllText(Path.Combine(rootPath, "monster", "goblin.yaml"), MonsterGoblinYaml);
|
|
return rootPath;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 最佳努力尝试删除临时目录。
|
|
/// </summary>
|
|
private static void DeleteDirectoryIfExists(string path)
|
|
{
|
|
if (!Directory.Exists(path))
|
|
{
|
|
return;
|
|
}
|
|
|
|
try
|
|
{
|
|
Directory.Delete(path, true);
|
|
}
|
|
catch (IOException)
|
|
{
|
|
// Ignored: cleanup is best effort and should not fail the test.
|
|
}
|
|
}
|
|
|
|
private const string MonsterSchemaJson = @"{
|
|
""title"": ""Monster Config"",
|
|
""description"": ""Defines one monster entry for the generated consumer integration test."",
|
|
""type"": ""object"",
|
|
""required"": [
|
|
""id"",
|
|
""name"",
|
|
""hp"",
|
|
""faction""
|
|
],
|
|
""properties"": {
|
|
""id"": {
|
|
""type"": ""integer"",
|
|
""description"": ""Monster identifier.""
|
|
},
|
|
""name"": {
|
|
""type"": ""string"",
|
|
""description"": ""Monster display name.""
|
|
},
|
|
""hp"": {
|
|
""type"": ""integer"",
|
|
""description"": ""Monster base health.""
|
|
},
|
|
""faction"": {
|
|
""type"": ""string"",
|
|
""description"": ""Used by the integration test to validate generated non-unique queries.""
|
|
}
|
|
}
|
|
}";
|
|
|
|
private const string MonsterSlimeYaml =
|
|
"id: 1\nname: Slime\nhp: 10\nfaction: dungeon\n";
|
|
|
|
private const string MonsterGoblinYaml =
|
|
"id: 2\nname: Goblin\nhp: 30\nfaction: dungeon\n";
|
|
|
|
private sealed class ConsumerArchitecture : Architecture
|
|
{
|
|
private readonly string _configRoot;
|
|
|
|
public ConfigRegistry Registry { get; }
|
|
|
|
public MonsterTable MonsterTable { get; private set; } = null!;
|
|
|
|
public ConsumerArchitecture(string configRoot)
|
|
{
|
|
_configRoot = configRoot ?? throw new ArgumentNullException(nameof(configRoot));
|
|
Registry = new ConfigRegistry();
|
|
}
|
|
|
|
protected override void OnInitialize()
|
|
{
|
|
RegisterUtility(Registry);
|
|
|
|
var loader = new YamlConfigLoader(_configRoot)
|
|
.RegisterMonsterTable();
|
|
loader.LoadAsync(Registry).GetAwaiter().GetResult();
|
|
MonsterTable = Registry.GetMonsterTable();
|
|
}
|
|
}
|
|
}
|