From 19c0830a7db35ec2616bd2154be9237ad34536f8 Mon Sep 17 00:00:00 2001 From: GeWuYou <95328647+GeWuYou@users.noreply.github.com> Date: Tue, 27 Jan 2026 21:58:30 +0800 Subject: [PATCH] =?UTF-8?q?feat(serializer):=20=E6=B7=BB=E5=8A=A0=E8=BF=90?= =?UTF-8?q?=E8=A1=8C=E6=97=B6=E7=B1=BB=E5=9E=8B=E5=BA=8F=E5=88=97=E5=8C=96?= =?UTF-8?q?=E5=99=A8=E6=8E=A5=E5=8F=A3=E5=B9=B6=E9=87=8D=E6=9E=84=E5=BA=8F?= =?UTF-8?q?=E5=88=97=E5=8C=96=E6=A8=A1=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 IRuntimeTypeSerializer 接口支持运行时类型序列化 - 将序列化器接口从 Game 模块迁移到 Core 模块 - 更新 JsonSerializer 的命名空间引用 - 为 Godot 音频和图形设置类添加持久化应用接口实现 - 重构设置模型的初始化和去重逻辑 - 移除批量加载所有设置的功能 - 优化设置应用过程为异步执行 - 在存储接口中添加异步删除方法 - 为各种存储实现添加异步删除功能 - 为 Godot 文件存储添加标准文件系统路径删除支持 - 删除废弃的设置数据基类实现 --- .../serializer/IRuntimeTypeSerializer.cs | 37 ++++++++++++++ .../serializer/ISerializer.cs | 2 +- .../storage/IStorage.cs | 7 +++ .../setting/IPersistentApplyAbleSettings.cs | 20 ++++++++ .../setting/ISettingsPersistence.cs | 5 -- .../setting/SettingsData.cs | 35 ------------- GFramework.Game/serializer/JsonSerializer.cs | 2 +- GFramework.Game/setting/SettingsModel.cs | 50 +++++++++++++++++-- .../setting/SettingsPersistence.cs | 34 +------------ GFramework.Game/setting/SettingsSystem.cs | 33 ++++++------ GFramework.Game/storage/FileStorage.cs | 47 +++++++++-------- GFramework.Game/storage/ScopedStorage.cs | 10 ++++ .../setting/GodotAudioSettings.cs | 17 ++++--- .../setting/GodotGraphicsSettings.cs | 7 ++- GFramework.Godot/storage/GodotFileStorage.cs | 14 +++++- 15 files changed, 195 insertions(+), 125 deletions(-) create mode 100644 GFramework.Core.Abstractions/serializer/IRuntimeTypeSerializer.cs rename {GFramework.Game.Abstractions => GFramework.Core.Abstractions}/serializer/ISerializer.cs (94%) create mode 100644 GFramework.Game.Abstractions/setting/IPersistentApplyAbleSettings.cs delete mode 100644 GFramework.Game.Abstractions/setting/SettingsData.cs diff --git a/GFramework.Core.Abstractions/serializer/IRuntimeTypeSerializer.cs b/GFramework.Core.Abstractions/serializer/IRuntimeTypeSerializer.cs new file mode 100644 index 0000000..9545de7 --- /dev/null +++ b/GFramework.Core.Abstractions/serializer/IRuntimeTypeSerializer.cs @@ -0,0 +1,37 @@ +// Copyright (c) 2026 GeWuYou +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +namespace GFramework.Core.Abstractions.serializer; + +/// +/// 运行时类型序列化器接口,继承自ISerializer接口 +/// 提供基于运行时类型的对象序列化和反序列化功能 +/// +public interface IRuntimeTypeSerializer : ISerializer +{ + /// + /// 将指定对象序列化为字符串 + /// + /// 要序列化的对象 + /// 对象的运行时类型 + /// 序列化后的字符串表示 + string Serialize(object obj, Type type); + + /// + /// 将字符串数据反序列化为指定类型的对象 + /// + /// 要反序列化的字符串数据 + /// 目标对象的运行时类型 + /// 反序列化后的对象实例 + object Deserialize(string data, Type type); +} \ No newline at end of file diff --git a/GFramework.Game.Abstractions/serializer/ISerializer.cs b/GFramework.Core.Abstractions/serializer/ISerializer.cs similarity index 94% rename from GFramework.Game.Abstractions/serializer/ISerializer.cs rename to GFramework.Core.Abstractions/serializer/ISerializer.cs index f562b2d..dade5cb 100644 --- a/GFramework.Game.Abstractions/serializer/ISerializer.cs +++ b/GFramework.Core.Abstractions/serializer/ISerializer.cs @@ -1,6 +1,6 @@ using GFramework.Core.Abstractions.utility; -namespace GFramework.Game.Abstractions.serializer; +namespace GFramework.Core.Abstractions.serializer; /// /// 定义序列化器接口,提供对象序列化和反序列化的通用方法 diff --git a/GFramework.Core.Abstractions/storage/IStorage.cs b/GFramework.Core.Abstractions/storage/IStorage.cs index 8216ca8..1d833e6 100644 --- a/GFramework.Core.Abstractions/storage/IStorage.cs +++ b/GFramework.Core.Abstractions/storage/IStorage.cs @@ -68,4 +68,11 @@ public interface IStorage : IUtility /// /// 要删除的键 void Delete(string key); + + /// + /// 异步删除指定键的存储项 + /// + /// 要删除的键 + /// 表示异步操作的Task + Task DeleteAsync(string key); } \ No newline at end of file diff --git a/GFramework.Game.Abstractions/setting/IPersistentApplyAbleSettings.cs b/GFramework.Game.Abstractions/setting/IPersistentApplyAbleSettings.cs new file mode 100644 index 0000000..05479e5 --- /dev/null +++ b/GFramework.Game.Abstractions/setting/IPersistentApplyAbleSettings.cs @@ -0,0 +1,20 @@ +// Copyright (c) 2026 GeWuYou +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +namespace GFramework.Game.Abstractions.setting; + +/// +/// 可持久化的应用设置接口 +/// 同时具备数据持久化和应用逻辑能力 +/// +public interface IPersistentApplyAbleSettings : ISettingsData, IApplyAbleSettings; \ No newline at end of file diff --git a/GFramework.Game.Abstractions/setting/ISettingsPersistence.cs b/GFramework.Game.Abstractions/setting/ISettingsPersistence.cs index 2c19640..56673db 100644 --- a/GFramework.Game.Abstractions/setting/ISettingsPersistence.cs +++ b/GFramework.Game.Abstractions/setting/ISettingsPersistence.cs @@ -32,9 +32,4 @@ public interface ISettingsPersistence : IContextUtility /// 保存所有设置数据 /// Task SaveAllAsync(IEnumerable allData); - - /// - /// 加载所有已知类型的设置数据 - /// - Task> LoadAllAsync(IEnumerable knownTypes); } \ No newline at end of file diff --git a/GFramework.Game.Abstractions/setting/SettingsData.cs b/GFramework.Game.Abstractions/setting/SettingsData.cs deleted file mode 100644 index 996e2e5..0000000 --- a/GFramework.Game.Abstractions/setting/SettingsData.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System.Reflection; - -namespace GFramework.Game.Abstractions.setting; - -/// -/// 设置数据抽象基类,提供默认的 Reset() 实现 -/// -public abstract class SettingsData : ISettingsData -{ - /// - /// 重置设置为默认值 - /// 使用反射将所有属性重置为它们的默认值 - /// - public virtual void Reset() - { - var properties = GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance); - foreach (var prop in properties) - { - if (!prop.CanWrite || !prop.CanRead) continue; - - var defaultValue = GetDefaultValue(prop.PropertyType); - prop.SetValue(this, defaultValue); - } - } - - /// - /// 获取指定类型的默认值 - /// - /// 要获取默认值的类型 - /// 类型的默认值 - private static object? GetDefaultValue(Type type) - { - return type.IsValueType ? Activator.CreateInstance(type) : null; - } -} \ No newline at end of file diff --git a/GFramework.Game/serializer/JsonSerializer.cs b/GFramework.Game/serializer/JsonSerializer.cs index 30aaafb..925ce31 100644 --- a/GFramework.Game/serializer/JsonSerializer.cs +++ b/GFramework.Game/serializer/JsonSerializer.cs @@ -1,4 +1,4 @@ -using GFramework.Game.Abstractions.serializer; +using GFramework.Core.Abstractions.serializer; using Newtonsoft.Json; namespace GFramework.Game.serializer; diff --git a/GFramework.Game/setting/SettingsModel.cs b/GFramework.Game/setting/SettingsModel.cs index 64add79..ce63df4 100644 --- a/GFramework.Game/setting/SettingsModel.cs +++ b/GFramework.Game/setting/SettingsModel.cs @@ -1,4 +1,5 @@ -using GFramework.Core.model; +using GFramework.Core.extensions; +using GFramework.Core.model; using GFramework.Game.Abstractions.setting; namespace GFramework.Game.setting; @@ -10,6 +11,7 @@ public class SettingsModel : AbstractModel, ISettingsModel { private readonly Dictionary _applicators = new(); private readonly Dictionary _dataSettings = new(); + private ISettingsPersistence? _persistence; /// /// 获取或创建数据设置 @@ -40,6 +42,13 @@ public class SettingsModel : AbstractModel, ISettingsModel { var type = typeof(T); _applicators[type] = applicator; + + // 如果这个应用设置同时也是数据设置,也注册到数据字典中 + if (applicator is ISettingsData data) + { + _dataSettings[type] = data; + } + return this; } @@ -88,9 +97,41 @@ public class SettingsModel : AbstractModel, ISettingsModel /// 包含所有设置节的可枚举集合 public IEnumerable All() { - // 合并数据设置和应用器设置的所有值 - return _dataSettings.Values - .Concat(_applicators.Values.Cast()); + // 使用 HashSet 去重(避免同时实现两个接口的设置被重复返回) + var sections = new HashSet(); + + foreach (var applicator in _applicators.Values) + sections.Add(applicator); + + foreach (var data in _dataSettings.Values) + sections.Add(data); + + return sections; + } + + /// + /// 初始化并加载指定类型的设置数据 + /// + public async Task InitializeAsync(params Type[] settingTypes) + { + foreach (var type in settingTypes) + { + if (!typeof(ISettingsData).IsAssignableFrom(type) || + !type.IsClass || + type.GetConstructor(Type.EmptyTypes) == null) + continue; + + // 使用反射调用泛型方法 LoadAsync + var method = typeof(ISettingsPersistence) + .GetMethod(nameof(ISettingsPersistence.LoadAsync))! + .MakeGenericMethod(type); + + var task = (Task)method.Invoke(_persistence, null)!; + await task; + + var loaded = (ISettingsData)((dynamic)task).Result; + _dataSettings[type] = loaded; + } } @@ -99,5 +140,6 @@ public class SettingsModel : AbstractModel, ISettingsModel /// protected override void OnInit() { + _persistence = this.GetUtility(); } } \ No newline at end of file diff --git a/GFramework.Game/setting/SettingsPersistence.cs b/GFramework.Game/setting/SettingsPersistence.cs index 7990082..63a3edf 100644 --- a/GFramework.Game/setting/SettingsPersistence.cs +++ b/GFramework.Game/setting/SettingsPersistence.cs @@ -64,7 +64,7 @@ public class SettingsPersistence : AbstractContextUtility, ISettingsPersistence public async Task DeleteAsync() where T : class, ISettingsData { var key = GetKey(); - _storage.Delete(key); + await _storage.DeleteAsync(key); this.SendEvent(new SettingsDeletedEvent(typeof(T))); await Task.CompletedTask; } @@ -86,38 +86,6 @@ public class SettingsPersistence : AbstractContextUtility, ISettingsPersistence this.SendEvent(new SettingsBatchSavedEvent(dataList)); } - /// - /// 异步加载所有已知类型的设置数据 - /// - /// 已知设置数据类型的集合 - /// 类型与对应设置数据的字典映射 - public async Task> LoadAllAsync(IEnumerable knownTypes) - { - var result = new Dictionary(); - var allSettings = new List(); - - foreach (var type in knownTypes) - { - var key = GetKey(type); - - if (!await _storage.ExistsAsync(key)) continue; - // 使用反射调用泛型方法 - var method = typeof(IStorage) - .GetMethod(nameof(IStorage.ReadAsync))! - .MakeGenericMethod(type); - - var task = (Task)method.Invoke(_storage, [key])!; - await task; - - var loaded = (ISettingsData)((dynamic)task).Result; - result[type] = loaded; - allSettings.Add(loaded); - } - - this.SendEvent(new SettingsAllLoadedEvent(allSettings)); - return result; - } - protected override void OnInit() { _storage = this.GetUtility()!; diff --git a/GFramework.Game/setting/SettingsSystem.cs b/GFramework.Game/setting/SettingsSystem.cs index fd2318c..a132a08 100644 --- a/GFramework.Game/setting/SettingsSystem.cs +++ b/GFramework.Game/setting/SettingsSystem.cs @@ -16,12 +16,10 @@ public class SettingsSystem : AbstractSystem, ISettingsSystem /// 应用所有设置配置 /// /// 完成的任务 - public Task ApplyAll() + public async Task ApplyAll() { // 遍历所有设置配置并尝试应用 - foreach (var section in _model.All()) TryApply(section); - - return Task.CompletedTask; + foreach (var section in _model.All()) await TryApplyAsync(section); } /// @@ -39,13 +37,12 @@ public class SettingsSystem : AbstractSystem, ISettingsSystem /// /// 设置配置类型 /// 完成的任务 - public Task Apply(Type settingsType) + public async Task Apply(Type settingsType) { - if (!_model.TryGet(settingsType, out var section)) - return Task.CompletedTask; - - TryApply(section); - return Task.CompletedTask; + if (_model.TryGet(settingsType, out var section)) + { + await TryApplyAsync(section); + } } /// @@ -53,14 +50,16 @@ public class SettingsSystem : AbstractSystem, ISettingsSystem /// /// 设置配置类型集合 /// 完成的任务 - public Task Apply(IEnumerable settingsTypes) + public async Task Apply(IEnumerable settingsTypes) { // 去重后遍历设置类型,获取并应用对应的设置配置 foreach (var type in settingsTypes.Distinct()) + { if (_model.TryGet(type, out var section)) - TryApply(section); - - return Task.CompletedTask; + { + await TryApplyAsync(section); + } + } } /// @@ -75,20 +74,20 @@ public class SettingsSystem : AbstractSystem, ISettingsSystem /// 尝试应用可应用的设置配置 /// /// 设置配置对象 - private void TryApply(ISettingsSection section) + private async Task TryApplyAsync(ISettingsSection section) { if (section is not IApplyAbleSettings applyAbleSettings) return; + this.SendEvent(new SettingsApplyingEvent(section)); try { - applyAbleSettings.Apply(); + await applyAbleSettings.Apply(); this.SendEvent(new SettingsAppliedEvent(section, true)); } catch (Exception ex) { this.SendEvent(new SettingsAppliedEvent(section, false, ex)); - throw; } } } \ No newline at end of file diff --git a/GFramework.Game/storage/FileStorage.cs b/GFramework.Game/storage/FileStorage.cs index e740497..3d019b1 100644 --- a/GFramework.Game/storage/FileStorage.cs +++ b/GFramework.Game/storage/FileStorage.cs @@ -1,7 +1,7 @@ using System.Collections.Concurrent; using System.IO; using System.Text; -using GFramework.Game.Abstractions.serializer; +using GFramework.Core.Abstractions.serializer; using GFramework.Game.Abstractions.storage; namespace GFramework.Game.storage; @@ -33,26 +33,6 @@ public sealed class FileStorage : IFileStorage Directory.CreateDirectory(_rootPath); } - #region Delete - - /// - /// 删除指定键的存储项 - /// - /// 存储键 - public void Delete(string key) - { - var path = ToPath(key); - var keyLock = _keyLocks.GetOrAdd(path, _ => new object()); - - lock (keyLock) - { - if (File.Exists(path)) - File.Delete(path); - } - } - - #endregion - /// /// 清理文件段字符串,将其中的无效文件名字符替换为下划线 /// @@ -105,6 +85,31 @@ public sealed class FileStorage : IFileStorage #endregion + #region Delete + + /// + /// 删除指定键的存储项 + /// + /// 存储键 + public void Delete(string key) + { + var path = ToPath(key); + var keyLock = _keyLocks.GetOrAdd(path, _ => new object()); + + lock (keyLock) + { + if (File.Exists(path)) + File.Delete(path); + } + } + + public Task DeleteAsync(string key) + { + return Task.Run(() => Delete(key)); + } + + #endregion + #region Exists /// diff --git a/GFramework.Game/storage/ScopedStorage.cs b/GFramework.Game/storage/ScopedStorage.cs index 9498ac2..0a82ca7 100644 --- a/GFramework.Game/storage/ScopedStorage.cs +++ b/GFramework.Game/storage/ScopedStorage.cs @@ -95,6 +95,16 @@ public sealed class ScopedStorage(IStorage inner, string prefix) : IScopedStorag inner.Delete(Key(key)); } + /// + /// 异步删除指定键 + /// + /// 要删除的键 + /// 异步操作任务 + public async Task DeleteAsync(string key) + { + await inner.DeleteAsync(Key(key)); + } + /// /// 为给定的键添加前缀 /// diff --git a/GFramework.Godot/setting/GodotAudioSettings.cs b/GFramework.Godot/setting/GodotAudioSettings.cs index b33aa4b..7c1bbef 100644 --- a/GFramework.Godot/setting/GodotAudioSettings.cs +++ b/GFramework.Godot/setting/GodotAudioSettings.cs @@ -6,10 +6,10 @@ namespace GFramework.Godot.setting; /// /// Godot音频设置实现类,用于应用音频配置到Godot音频系统 /// -/// 音频设置对象,包含主音量、背景音乐音量和音效音量 +/// 音频设置对象,包含主音量、背景音乐音量和音效音量 /// 音频总线映射对象,定义了不同音频类型的总线名称 -public class GodotAudioSettings(AudioSettings audioSettings, AudioBusMapSettings audioBusMapSettings) - : IApplyAbleSettings +public class GodotAudioSettings(AudioSettings settings, AudioBusMapSettings audioBusMapSettings) + : IPersistentApplyAbleSettings { /// /// 应用音频设置到Godot音频系统 @@ -17,12 +17,17 @@ public class GodotAudioSettings(AudioSettings audioSettings, AudioBusMapSettings /// 表示异步操作的任务 public Task Apply() { - SetBus(audioBusMapSettings.Master, audioSettings.MasterVolume); - SetBus(audioBusMapSettings.Bgm, audioSettings.BgmVolume); - SetBus(audioBusMapSettings.Sfx, audioSettings.SfxVolume); + SetBus(audioBusMapSettings.Master, settings.MasterVolume); + SetBus(audioBusMapSettings.Bgm, settings.BgmVolume); + SetBus(audioBusMapSettings.Sfx, settings.SfxVolume); return Task.CompletedTask; } + public void Reset() + { + audioBusMapSettings.Reset(); + } + /// /// 设置指定音频总线的音量 /// diff --git a/GFramework.Godot/setting/GodotGraphicsSettings.cs b/GFramework.Godot/setting/GodotGraphicsSettings.cs index 0e27c91..6c8c9fe 100644 --- a/GFramework.Godot/setting/GodotGraphicsSettings.cs +++ b/GFramework.Godot/setting/GodotGraphicsSettings.cs @@ -7,7 +7,7 @@ namespace GFramework.Godot.setting; /// Godot图形设置应用器 /// /// 图形设置配置对象 -public class GodotGraphicsSettings(GraphicsSettings settings) : IApplyAbleSettings +public class GodotGraphicsSettings(GraphicsSettings settings) : IPersistentApplyAbleSettings { /// /// 应用图形设置到Godot引擎 @@ -40,4 +40,9 @@ public class GodotGraphicsSettings(GraphicsSettings settings) : IApplyAbleSettin await Task.CompletedTask; } + + public void Reset() + { + settings.Reset(); + } } \ No newline at end of file diff --git a/GFramework.Godot/storage/GodotFileStorage.cs b/GFramework.Godot/storage/GodotFileStorage.cs index cff6d56..7ac1568 100644 --- a/GFramework.Godot/storage/GodotFileStorage.cs +++ b/GFramework.Godot/storage/GodotFileStorage.cs @@ -1,8 +1,8 @@ using System.Collections.Concurrent; using System.IO; using System.Text; +using GFramework.Core.Abstractions.serializer; using GFramework.Core.Abstractions.storage; -using GFramework.Game.Abstractions.serializer; using GFramework.Godot.extensions; using Godot; using FileAccess = Godot.FileAccess; @@ -44,6 +44,7 @@ public sealed class GodotFileStorage : IStorage lock (keyLock) { + // 处理Godot文件系统路径的删除操作 if (path.IsGodotPath()) { if (FileAccess.FileExists(path)) @@ -53,6 +54,7 @@ public sealed class GodotFileStorage : IStorage throw new IOException($"Failed to delete Godot file: {path}, error: {err}"); } } + // 处理标准文件系统路径的删除操作 else { if (File.Exists(path)) File.Delete(path); @@ -63,6 +65,16 @@ public sealed class GodotFileStorage : IStorage _keyLocks.TryRemove(path, out _); } + /// + /// 异步删除指定键对应的文件 + /// + /// 存储键 + /// 异步任务 + public async Task DeleteAsync(string key) + { + await Task.Run(() => Delete(key)); + } + #endregion #region Helpers