mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-05-07 00:39:00 +08:00
- 新增 Generator 与 Tooling 的 anyOf 和坏形状回归覆盖,补齐组合关键字与未知 type 拒绝 - 修复 VS Code 配置工具的 object-array 直属项收集与 contains 文案一致性问题 - 更新 README、Game 文档与工具说明,明确 additionalProperties 显式 false 边界与最小接入路径 - 补充 ai-plan 跟踪与 trace,记录 PR 306 open threads 收口结果和验证摘要
7.4 KiB
7.4 KiB
title, description
| title | description |
|---|---|
| 设置系统 | 以当前 SettingsModel、SettingsSystem 与相关测试为准,说明设置数据、applicator、迁移和持久化的真实接法。 |
设置系统
GFramework.Game 的设置系统负责三件事:
- 管理
ISettingsData实例的生命周期 - 管理设置 applicator,并把设置真正作用到运行时环境
- 在初始化时加载、迁移、保存和重置设置
当前默认 owner 是:
SettingsModel<TRepository>SettingsSystem
而不是只靠若干 Get<T>() / Register(...) 辅助方法就能自动完成一切的模型。
与 AI-First 配置系统的边界
如果你关注的是“配置内容最后怎么变成运行时设置”,这里也需要先分清职责:
- 配置 schema 的正式支持边界,仍以
GFramework.GameRuntime 和GFramework.Game.SourceGenerators当前共享的 schema 子集为准 UnifiedSettingsDataRepository、SettingsModel<TRepository>和SettingsSystem负责设置数据的加载、迁移、保存与应用,不负责放宽oneOf、anyOf、非false的additionalProperties(例如省略或true)等配置边界- 一旦配置设计开始依赖更复杂的 schema shape,应直接回到 配置系统 与 raw YAML / schema 本体处理,再决定设置层怎么消费这些结果
当前公开入口
ISettingsData
设置数据对象需要同时承担:
- 默认值持有者
- 版本化 section
- 从已加载数据回填到当前实例的入口
当前接口组合是:
public interface ISettingsData : IResettable, IVersionedData, ILoadableFrom<ISettingsData>;
这意味着一个设置数据类型至少要处理:
Reset()VersionLastModifiedLoadFrom(ISettingsData source)
IResetApplyAbleSettings
applicator 的职责不是保存数据,而是把设置结果作用到实际运行时对象。
它当前需要暴露:
DataDataTypeReset()ApplyAsync()
典型场景包括:
- 把音量设置同步到音频系统
- 把画质设置同步到窗口或渲染配置
- 把语言设置同步到本地化服务
SettingsModel<TRepository>
这是当前设置系统的核心编排器。按当前源码,它负责:
GetData<T>()- 返回某个设置类型的唯一实例
RegisterApplicator(...)- 注册 applicator,并把其
Data一并纳入模型管理
- 注册 applicator,并把其
RegisterMigration(...)- 注册同一设置类型的前进式迁移链
InitializeAsync()- 从 repository 读取所有设置、执行迁移、回填到当前实例
SaveAllAsync()- 持久化所有已登记的设置数据
ApplyAllAsync()- 依次应用所有 applicator
Reset<T>() / ResetAll()- 重置单个或全部设置
SettingsSystem
SettingsSystem 是面向业务代码更直接的一层系统封装:
ApplyAll()Apply<T>()SaveAll()Reset<T>()ResetAll()
它自己不持有独立设置状态,而是把工作委托给 ISettingsModel,并在应用时补发 settings 相关事件。
初始化与迁移的真实语义
SettingsModel<TRepository>.InitializeAsync() 会按更完整的初始化与迁移顺序工作:
- 它会先调用
ISettingsDataRepository.LoadAllAsync() - 再逐个匹配当前模型里已经登记的设置类型
- 如果读到了旧版本设置,会以“当前内存实例声明的
Version”为目标版本执行迁移 - 迁移完成后通过
LoadFrom(...)回填到现有实例,而不是直接替换对象引用
当前测试还确认了几个关键边界:
- 同一设置类型的同一个
FromVersion不能重复注册迁移器 - 注册新迁移器后,类型级迁移缓存会失效并重建,不会继续使用旧快照
- 如果迁移链缺口导致无法安全升级,模型会保留当前内存中的最新实例,而不是把不完整的旧数据覆盖进来
- 单个设置 section 初始化失败时,模型会记录错误并继续处理其他 section
这套语义更接近“尽量保证运行时实例总是可用”,而不是“任意旧设置都必须成功导入”。
最小接入路径
当前最常见的接法是:
- 准备一个
IStorage - 准备一个
IRuntimeTypeSerializer - 注册
ISettingsDataRepository - 注册
IDataLocationProvider - 创建并注册
SettingsModel<TRepository> - 注册 applicator
- 注册
SettingsSystem
示意代码:
using GFramework.Core.Abstractions.Storage;
using GFramework.Game.Abstractions.Data;
using GFramework.Game.Abstractions.Setting;
using GFramework.Game.Data;
using GFramework.Game.Serializer;
using GFramework.Game.Setting;
using GFramework.Game.Storage;
var serializer = new JsonSerializer();
var storage = new FileStorage("GameData", serializer, ".json");
var repository = new UnifiedSettingsDataRepository(
storage,
serializer,
new DataRepositoryOptions
{
BasePath = "settings",
AutoBackup = true
});
architecture.RegisterUtility<IStorage>(storage);
architecture.RegisterUtility<IRuntimeTypeSerializer>(serializer);
architecture.RegisterUtility<ISettingsDataRepository>(repository);
// 此处注册项目侧的 IDataLocationProvider 实现,用于把设置类型映射到 section key。
var settingsModel = new SettingsModel<ISettingsDataRepository>(null, null);
// 在注册到架构前,继续补 applicator 与 migration。
architecture.RegisterModel<ISettingsModel>(settingsModel);
architecture.RegisterSystem<ISettingsSystem>(new SettingsSystem());
启动阶段通常是:
await settingsModel.InitializeAsync();
await settingsModel.ApplyAllAsync();
退出或显式保存时:
await settingsModel.SaveAllAsync();
GetData<T>() 和 RegisterApplicator(...) 的分工
这两个入口经常被混用,但职责不同:
GetData<T>()- 只保证某个设置数据实例存在,并在 repository / location provider 已就绪时把类型注册回去
RegisterApplicator(...)- 同时注册 applicator 和 applicator 绑定的
Data
- 同时注册 applicator 和 applicator 绑定的
如果一个设置类型需要真正作用到运行时对象,推荐让它通过 applicator 进入模型;这样 ApplyAllAsync()、ResetAll() 和
SettingsSystem 才能完整覆盖到它。
与 repository 的关系
设置系统默认不是直接写文件,而是依赖 ISettingsDataRepository。
当前仓库里更推荐的默认实现是 UnifiedSettingsDataRepository,原因很直接:
- 多个设置 section 会被聚合到同一份统一文件
- 启动时能一次性
LoadAllAsync() AutoBackup针对整个统一文件生效,更贴近“设置快照”的真实语义
如果你的项目明确需要“一类设置一个独立文件”,才考虑回到通用 DataRepository 路径。
当前边界
SettingsModel<TRepository>负责数据生命周期,SettingsSystem负责系统级调用入口;两者不要混成一个巨型服务- applicator 决定“怎么把数据应用到宿主”,repository 决定“怎么保存数据”,两层职责不要互相侵入
- 设置迁移和存档迁移是两条不同管线;后者看 数据与存档系统 里的
SaveRepository<TSaveData> - 如果某个配置 shape 已经超出 Runtime / Generator 共享支持子集,settings repository 和
SettingsModel也不会替代配置系统去放宽它;应回到 配置系统 与 raw YAML / schema 设计处理