diff --git a/GFramework.Core.Abstractions/data/ILoadableFrom.cs b/GFramework.Core.Abstractions/data/ILoadableFrom.cs index 5bf3e8f..9b696a5 100644 --- a/GFramework.Core.Abstractions/data/ILoadableFrom.cs +++ b/GFramework.Core.Abstractions/data/ILoadableFrom.cs @@ -13,7 +13,15 @@ namespace GFramework.Core.Abstractions.data; +/// +/// 定义从指定类型数据源加载数据的接口 +/// +/// 数据源的类型 public interface ILoadableFrom { + /// + /// 从指定的数据源加载数据到当前对象 + /// + /// 用作数据源的对象,类型为T void LoadFrom(T source); } \ No newline at end of file diff --git a/GFramework.Game.Abstractions/data/IDataRepository.cs b/GFramework.Game.Abstractions/data/IDataRepository.cs index 3012e57..a94a34c 100644 --- a/GFramework.Game.Abstractions/data/IDataRepository.cs +++ b/GFramework.Game.Abstractions/data/IDataRepository.cs @@ -29,6 +29,7 @@ public interface IDataRepository : IUtility Task LoadAsync(IDataLocation location) where T : class, IData, new(); + /// /// 异步保存数据到指定位置 /// diff --git a/GFramework.Game.Abstractions/data/ISettingsDataRepository.cs b/GFramework.Game.Abstractions/data/ISettingsDataRepository.cs new file mode 100644 index 0000000..d1615d5 --- /dev/null +++ b/GFramework.Game.Abstractions/data/ISettingsDataRepository.cs @@ -0,0 +1,34 @@ +// 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.data; + +/// +/// 定义设置数据仓库接口,用于管理应用程序设置数据的存储和检索 +/// +/// +/// 该接口继承自IDataRepository,专门用于处理配置设置相关的数据操作 +/// +public interface ISettingsDataRepository : IDataRepository +{ + /// + /// 异步加载所有设置项 + /// + /// + /// 返回一个包含所有设置键值对的字典,其中键为设置名称,值为对应的设置数据对象 + /// + /// + /// 此方法将从数据源中异步读取所有可用的设置项,并将其组织成字典格式返回 + /// + Task> LoadAllAsync(); +} \ No newline at end of file diff --git a/GFramework.Game.Abstractions/setting/ISettingsModel.cs b/GFramework.Game.Abstractions/setting/ISettingsModel.cs index c081fab..133da94 100644 --- a/GFramework.Game.Abstractions/setting/ISettingsModel.cs +++ b/GFramework.Game.Abstractions/setting/ISettingsModel.cs @@ -35,9 +35,19 @@ public interface ISettingsModel : IModel /// /// 注册设置应用器 /// + /// 设置数据类型,必须实现ISettingsData接口且具有无参构造函数 /// 要注册的设置应用器 /// 当前设置模型实例,支持链式调用 - ISettingsModel RegisterApplicator(IResetApplyAbleSettings applicator); + ISettingsModel RegisterApplicator(IResetApplyAbleSettings applicator) where T : class, ISettingsData, new(); + + + /// + /// 获取指定类型的设置应用器 + /// + /// 要获取的设置应用器类型,必须继承自IResetApplyAbleSettings + /// 设置应用器实例,如果不存在则返回null + T? GetApplicator() where T : class, IResetApplyAbleSettings; + /// /// 获取所有设置应用器 @@ -80,8 +90,15 @@ public interface ISettingsModel : IModel /// 异步操作任务 Task ApplyAllAsync(); + /// + /// 重置指定类型的设置 + /// + /// 要重置的设置类型,必须实现IResettable接口并具有无参构造函数 + void Reset() where T : class, ISettingsData, new(); + + /// /// 重置所有设置数据与应用器 /// void ResetAll(); -} +} \ No newline at end of file diff --git a/GFramework.Game.Abstractions/setting/ISettingsSystem.cs b/GFramework.Game.Abstractions/setting/ISettingsSystem.cs index bc61ab7..7f3ab5d 100644 --- a/GFramework.Game.Abstractions/setting/ISettingsSystem.cs +++ b/GFramework.Game.Abstractions/setting/ISettingsSystem.cs @@ -16,9 +16,9 @@ public interface ISettingsSystem : ISystem /// /// 应用指定类型的设置(泛型版本) /// - /// 设置类型,必须是class且实现IApplyAbleSettings接口 + /// 设置类型,必须是class且实现IResetApplyAbleSettings接口 /// 表示异步操作的任务 - Task Apply() where T : class, IApplyAbleSettings; + Task Apply() where T : class, IResetApplyAbleSettings; /// /// 保存所有设置 @@ -31,7 +31,7 @@ public interface ISettingsSystem : ISystem /// /// 设置类型,必须继承自class并实现IPersistentApplyAbleSettings接口 /// 表示异步操作的任务 - Task Reset() where T : class, IResetApplyAbleSettings, new(); + Task Reset() where T : class, ISettingsData, IResetApplyAbleSettings, new(); /// /// 重置所有设置 diff --git a/GFramework.Game.Abstractions/setting/data/AudioSettings.cs b/GFramework.Game.Abstractions/setting/data/AudioSettings.cs index feed789..ba54d0b 100644 --- a/GFramework.Game.Abstractions/setting/data/AudioSettings.cs +++ b/GFramework.Game.Abstractions/setting/data/AudioSettings.cs @@ -34,10 +34,29 @@ public class AudioSettings : ISettingsData /// /// 获取或设置设置数据的版本号 /// - public int Version { get; set; } = 1; - + public int Version { get; private set; } = 1; + /// /// 获取设置数据最后修改的时间 /// public DateTime LastModified { get; } = DateTime.Now; -} + + /// + /// 从指定的数据源加载音频设置 + /// + /// 包含设置数据的源对象 + public void LoadFrom(ISettingsData source) + { + // 检查数据源是否为音频设置类型 + if (source is not AudioSettings audioSettings) + { + return; + } + + // 将源数据中的各个音量设置复制到当前对象 + MasterVolume = audioSettings.MasterVolume; + BgmVolume = audioSettings.BgmVolume; + SfxVolume = audioSettings.SfxVolume; + Version = audioSettings.Version; + } +} \ No newline at end of file diff --git a/GFramework.Game.Abstractions/setting/data/GraphicsSettings.cs b/GFramework.Game.Abstractions/setting/data/GraphicsSettings.cs index 53c9a0a..9d5f367 100644 --- a/GFramework.Game.Abstractions/setting/data/GraphicsSettings.cs +++ b/GFramework.Game.Abstractions/setting/data/GraphicsSettings.cs @@ -33,10 +33,29 @@ public class GraphicsSettings : ISettingsData /// /// 获取或设置设置数据的版本号 /// - public int Version { get; set; } = 1; - + public int Version { get; private set; } = 1; + /// /// 获取设置数据最后修改的时间 /// public DateTime LastModified { get; } = DateTime.Now; + + /// + /// 从指定的数据源加载图形设置 + /// + /// 要从中加载设置的源数据对象 + public void LoadFrom(ISettingsData source) + { + // 检查源数据是否为GraphicsSettings类型,如果不是则直接返回 + if (source is not GraphicsSettings settings) + { + return; + } + + // 将源设置中的属性值复制到当前对象 + Fullscreen = settings.Fullscreen; + ResolutionWidth = settings.ResolutionWidth; + ResolutionHeight = settings.ResolutionHeight; + Version = settings.Version; + } } \ No newline at end of file diff --git a/GFramework.Game.Abstractions/setting/data/LocalizationSettings.cs b/GFramework.Game.Abstractions/setting/data/LocalizationSettings.cs index 2947490..ff0b49f 100644 --- a/GFramework.Game.Abstractions/setting/data/LocalizationSettings.cs +++ b/GFramework.Game.Abstractions/setting/data/LocalizationSettings.cs @@ -37,10 +37,29 @@ public class LocalizationSettings : ISettingsData /// /// 获取或设置设置数据的版本号 /// - public int Version { get; set; } = 1; - + public int Version { get; private set; } = 1; + /// /// 获取设置数据最后修改的时间 /// public DateTime LastModified { get; } = DateTime.Now; + + /// + /// 从指定的数据源加载本地化设置 + /// + /// 要从中加载设置的源对象 + /// + /// 该方法仅处理类型为LocalizationSettings的对象, + /// 如果源对象不是LocalizationSettings类型,则直接返回不执行任何操作 + /// + public void LoadFrom(ISettingsData source) + { + if (source is not LocalizationSettings settings) + { + return; + } + + Language = settings.Language; + Version = settings.Version; + } } \ No newline at end of file diff --git a/GFramework.Game/data/UnifiedSettingsRepository.cs b/GFramework.Game/data/UnifiedSettingsDataRepository.cs similarity index 75% rename from GFramework.Game/data/UnifiedSettingsRepository.cs rename to GFramework.Game/data/UnifiedSettingsDataRepository.cs index 0ea6ccc..1de78fd 100644 --- a/GFramework.Game/data/UnifiedSettingsRepository.cs +++ b/GFramework.Game/data/UnifiedSettingsDataRepository.cs @@ -23,16 +23,16 @@ namespace GFramework.Game.data; /// /// 使用单一文件存储所有设置数据的仓库实现 /// -public class UnifiedSettingsRepository( +public class UnifiedSettingsDataRepository( IStorage? storage, IRuntimeTypeSerializer? serializer, DataRepositoryOptions? options = null, string fileName = "settings.json") - : AbstractContextUtility, IDataRepository + : AbstractContextUtility, ISettingsDataRepository { - private UnifiedSettingsFile? _file; private readonly SemaphoreSlim _lock = new(1, 1); private readonly DataRepositoryOptions _options = options ?? new DataRepositoryOptions(); + private UnifiedSettingsFile? _file; private bool _loaded; private IRuntimeTypeSerializer? _serializer = serializer; private IStorage? _storage = storage; @@ -45,16 +45,16 @@ public class UnifiedSettingsRepository( private UnifiedSettingsFile File => _file ?? throw new InvalidOperationException("UnifiedSettingsFile not set."); - - protected override void OnInit() - { - _storage ??= this.GetUtility()!; - _serializer ??= this.GetUtility()!; - } // ========================= // IDataRepository // ========================= + /// + /// 异步加载指定位置的数据 + /// + /// 数据类型,必须继承自IData接口 + /// 数据位置信息 + /// 加载的数据对象 public async Task LoadAsync(IDataLocation location) where T : class, IData, new() { @@ -66,6 +66,13 @@ public class UnifiedSettingsRepository( return result; } + /// + /// 异步保存数据到指定位置 + /// + /// 数据类型,必须继承自IData接口 + /// 数据位置信息 + /// 要保存的数据对象 + /// 异步操作任务 public async Task SaveAsync(IDataLocation location, T data) where T : class, IData { @@ -81,13 +88,22 @@ public class UnifiedSettingsRepository( this.SendEvent(new DataSavedEvent(data)); } + /// + /// 检查指定位置的数据是否存在 + /// + /// 数据位置信息 + /// 如果数据存在则返回true,否则返回false public async Task ExistsAsync(IDataLocation location) { await EnsureLoadedAsync(); return File.Sections.ContainsKey(location.Key); } - + /// + /// 删除指定位置的数据 + /// + /// 数据位置信息 + /// 异步操作任务 public async Task DeleteAsync(IDataLocation location) { await EnsureLoadedAsync(); @@ -101,7 +117,11 @@ public class UnifiedSettingsRepository( } } - + /// + /// 批量保存多个数据项到存储 + /// + /// 包含数据位置和数据对象的枚举集合 + /// 异步操作任务 public async Task SaveAllAsync( IEnumerable<(IDataLocation location, IData data)> dataList) { @@ -120,6 +140,24 @@ public class UnifiedSettingsRepository( this.SendEvent(new DataBatchSavedEvent(valueTuples.ToList())); } + /// + /// 加载所有存储的数据项 + /// + /// 包含所有数据项的字典,键为数据位置键,值为数据对象 + public async Task> LoadAllAsync() + { + await EnsureLoadedAsync(); + return File.Sections.ToDictionary( + kv => kv.Key, + kv => Serializer.Deserialize(kv.Value) + ); + } + + protected override void OnInit() + { + _storage ??= this.GetUtility()!; + _serializer ??= this.GetUtility()!; + } // ========================= // Internals @@ -154,7 +192,6 @@ public class UnifiedSettingsRepository( } } - /// /// 将缓存中的所有数据保存到统一文件 /// diff --git a/GFramework.Game/setting/SettingsModel.cs b/GFramework.Game/setting/SettingsModel.cs index 2231144..f892fbf 100644 --- a/GFramework.Game/setting/SettingsModel.cs +++ b/GFramework.Game/setting/SettingsModel.cs @@ -14,38 +14,30 @@ namespace GFramework.Game.setting; /// - 编排 Settings Applicator 的 Apply 行为 /// public class SettingsModel : AbstractModel, ISettingsModel - where TRepository : class, IDataRepository + where TRepository : class, ISettingsDataRepository { private static readonly ILogger Log = LoggerFactoryResolver.Provider.CreateLogger(nameof(SettingsModel)); + private readonly ConcurrentDictionary _applicators = new(); + // ========================= // Fields // ========================= private readonly ConcurrentDictionary _data = new(); - private readonly ConcurrentBag _applicators = new(); - private readonly ConcurrentDictionary<(Type type, int from), ISettingsMigration> _migrations = new(); private readonly ConcurrentDictionary> _migrationCache = new(); - - private IDataRepository? _repository; + private readonly ConcurrentDictionary<(Type type, int from), ISettingsMigration> _migrations = new(); private IDataLocationProvider? _locationProvider; - private IDataRepository Repository => - _repository ?? throw new InvalidOperationException("IDataRepository not initialized."); + private ISettingsDataRepository? _repository; + + private ISettingsDataRepository DataRepository => + _repository ?? throw new InvalidOperationException("ISettingsDataRepository not initialized."); private IDataLocationProvider LocationProvider => _locationProvider ?? throw new InvalidOperationException("IDataLocationProvider not initialized."); // ========================= - // Init - // ========================= - - protected override void OnInit() - { - _repository ??= this.GetUtility()!; - _locationProvider ??= this.GetUtility()!; - } - // ========================= // Data access // ========================= @@ -57,7 +49,7 @@ public class SettingsModel : AbstractModel, ISettingsModel return (T)_data.GetOrAdd(typeof(T), _ => new T()); } - + public IEnumerable AllData() { return _data.Values; @@ -70,9 +62,10 @@ public class SettingsModel : AbstractModel, ISettingsModel /// /// 注册设置应用器 /// - public ISettingsModel RegisterApplicator(IResetApplyAbleSettings applicator) + public ISettingsModel RegisterApplicator(IResetApplyAbleSettings applicator) + where T : class, ISettingsData, new() { - _applicators.Add(applicator); + _applicators[typeof(T)] = applicator; return this; } @@ -81,7 +74,7 @@ public class SettingsModel : AbstractModel, ISettingsModel /// public IEnumerable AllApplicators() { - return _applicators; + return _applicators.Values; } // ========================= @@ -94,6 +87,135 @@ public class SettingsModel : AbstractModel, ISettingsModel return this; } + // ========================= + // Lifecycle + // ========================= + + /// + /// 初始化设置模型: + /// - 加载所有已存在的 Settings Data + /// - 执行必要的迁移 + /// + public async Task InitializeAsync() + { + IDictionary allData; + + try + { + allData = await DataRepository.LoadAllAsync(); + } + catch (Exception ex) + { + Log.Error("Failed to load unified settings file.", ex); + return; + } + + foreach (var data in _data.Values) + { + try + { + var type = data.GetType(); + var location = LocationProvider.GetLocation(type); + + if (!allData.TryGetValue(location.Key, out var raw)) + continue; + + if (raw is not ISettingsData loaded) + continue; + + var migrated = MigrateIfNeeded(loaded); + + // 回填(不替换实例) + data.LoadFrom(migrated); + } + catch (Exception ex) + { + Log.Error($"Failed to initialize settings data: {data.GetType().Name}", ex); + } + } + } + + + /// + /// 将所有 Settings Data 持久化 + /// + public async Task SaveAllAsync() + { + foreach (var data in _data.Values) + { + try + { + var location = LocationProvider.GetLocation(data.GetType()); + await DataRepository.SaveAsync(location, data); + } + catch (Exception ex) + { + Log.Error($"Failed to save settings data: {data.GetType().Name}", ex); + } + } + } + + /// + /// 应用所有设置 + /// + public async Task ApplyAllAsync() + { + foreach (var applicator in _applicators) + { + try + { + await applicator.Value.Apply(); + } + catch (Exception ex) + { + Log.Error($"Failed to apply settings: {applicator.GetType().Name}", ex); + } + } + } + + /// + /// 重置指定类型的可重置对象 + /// + /// 要重置的对象类型,必须是class类型,实现IResettable接口,并具有无参构造函数 + public void Reset() where T : class, ISettingsData, new() + { + var data = GetData(); + data.Reset(); + } + + /// + /// 重置所有设置 + /// + public void ResetAll() + { + foreach (var data in _data.Values) + data.Reset(); + + foreach (var applicator in _applicators) + applicator.Value.Reset(); + } + + /// + /// 获取指定类型的设置应用器 + /// + /// 要获取的设置应用器类型,必须继承自IResetApplyAbleSettings + /// 设置应用器实例,如果不存在则返回null + public T? GetApplicator() where T : class, IResetApplyAbleSettings + { + return _applicators.TryGetValue(typeof(T), out var app) + ? (T)app + : null; + } + // ========================= + // Init + // ========================= + + protected override void OnInit() + { + _repository ??= this.GetUtility()!; + _locationProvider ??= this.GetUtility()!; + } + private ISettingsData MigrateIfNeeded(ISettingsData data) { if (data is not IVersionedData versioned) @@ -119,88 +241,4 @@ public class SettingsModel : AbstractModel, ISettingsModel return current; } - - // ========================= - // Lifecycle - // ========================= - - /// - /// 初始化设置模型: - /// - 加载所有已存在的 Settings Data - /// - 执行必要的迁移 - /// - public async Task InitializeAsync() - { - foreach (var data in _data.Values) - { - try - { - var type = data.GetType(); - var location = LocationProvider.GetLocation(type); - - if (!await Repository.ExistsAsync(location)) - continue; - - var loaded = await Repository.LoadAsync(location); - var migrated = MigrateIfNeeded(loaded); - - // 回填数据(不替换实例) - data.LoadFrom(migrated); - } - catch (Exception ex) - { - Log.Error($"Failed to initialize settings data: {data.GetType().Name}", ex); - } - } - } - - /// - /// 将所有 Settings Data 持久化 - /// - public async Task SaveAllAsync() - { - foreach (var data in _data.Values) - { - try - { - var location = LocationProvider.GetLocation(data.GetType()); - await Repository.SaveAsync(location, data); - } - catch (Exception ex) - { - Log.Error($"Failed to save settings data: {data.GetType().Name}", ex); - } - } - } - - /// - /// 应用所有设置 - /// - public async Task ApplyAllAsync() - { - foreach (var applicator in _applicators) - { - try - { - await applicator.Apply(); - } - catch (Exception ex) - { - Log.Error($"Failed to apply settings: {applicator.GetType().Name}", ex); - } - } - } - - /// - /// 重置所有设置 - /// - public void ResetAll() - { - foreach (var data in _data.Values) - data.Reset(); - - foreach (var applicator in _applicators) - applicator.Reset(); - } - } \ No newline at end of file diff --git a/GFramework.Game/setting/SettingsSystem.cs b/GFramework.Game/setting/SettingsSystem.cs index c01322e..2646328 100644 --- a/GFramework.Game/setting/SettingsSystem.cs +++ b/GFramework.Game/setting/SettingsSystem.cs @@ -1,6 +1,5 @@ using GFramework.Core.extensions; using GFramework.Core.system; -using GFramework.Game.Abstractions.data; using GFramework.Game.Abstractions.setting; using GFramework.Game.setting.events; @@ -9,12 +8,9 @@ namespace GFramework.Game.setting; /// /// 设置系统,负责管理和应用各种设置配置 /// -public class SettingsSystem(IDataRepository? repository) - : AbstractSystem, ISettingsSystem where TRepository : class, IDataRepository +public class SettingsSystem : AbstractSystem, ISettingsSystem { private ISettingsModel _model = null!; - private IDataRepository? _repository = repository; - private IDataRepository Repository => _repository ?? throw new InvalidOperationException("Repository is not set"); /// /// 应用所有设置配置 @@ -29,9 +25,9 @@ public class SettingsSystem(IDataRepository? repository) /// /// 应用指定类型的设置配置 /// - /// 设置配置类型,必须是类且实现ISettingsSection接口 + /// 设置配置类型,必须是类且实现IResetApplyAbleSettings接口 /// 完成的任务 - public Task Apply() where T : class, IApplyAbleSettings + public Task Apply() where T : class, IResetApplyAbleSettings { var applicator = _model.GetApplicator(); return applicator != null @@ -45,7 +41,7 @@ public class SettingsSystem(IDataRepository? repository) /// 完成的任务 public async Task SaveAll() { - await Repository.SaveAllAsync(_model.AllData()); + await _model.SaveAllAsync(); } /// @@ -63,7 +59,7 @@ public class SettingsSystem(IDataRepository? repository) /// /// 设置类型,必须实现IPersistentApplyAbleSettings接口且具有无参构造函数 /// 异步任务 - public async Task Reset() where T : class, IResetApplyAbleSettings, new() + public async Task Reset() where T : class, ISettingsData, IResetApplyAbleSettings, new() { _model.Reset(); await Apply(); @@ -76,7 +72,6 @@ public class SettingsSystem(IDataRepository? repository) protected override void OnInit() { _model = this.GetModel()!; - _repository ??= this.GetUtility()!; } ///