// 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.
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Threading.Tasks;
using GFramework.Core.Abstractions.Storage;
using GFramework.Core.Utility;
using GFramework.Game.Abstractions.Data;
using GFramework.Game.Storage;
namespace GFramework.Game.Data;
///
/// 基于槽位的存档仓库实现
///
/// 存档数据类型
public class SaveRepository : AbstractContextUtility, ISaveRepository
where TSaveData : class, IData, new()
{
private readonly SaveConfiguration _config;
private readonly Dictionary> _migrations = new();
private readonly object _migrationsLock = new();
private readonly IStorage _rootStorage;
///
/// 初始化存档仓库
///
/// 存储实例
/// 存档配置
public SaveRepository(IStorage storage, SaveConfiguration config)
{
ArgumentNullException.ThrowIfNull(storage);
ArgumentNullException.ThrowIfNull(config);
_config = config;
_rootStorage = new ScopedStorage(storage, config.SaveRoot);
}
///
/// 注册存档迁移器,使仓库在加载旧版本存档时自动执行升级。
///
/// 要注册的存档迁移器。
/// 当前存档仓库实例,支持链式调用。
/// 为 。
///
/// 未实现 ,无法使用版本化迁移。
/// 或者同一个源版本已经注册过迁移器,导致迁移链配置存在歧义。
///
/// 迁移器的目标版本不大于源版本。
///
/// 迁移注册表是可变共享状态。注册与加载可以并发发生,因此所有访问都通过
/// 串行化,避免读写竞争和“部分可见”的迁移链。
///
public ISaveRepository RegisterMigration(ISaveMigration migration)
{
ArgumentNullException.ThrowIfNull(migration);
EnsureVersionedSaveType();
if (migration.ToVersion <= migration.FromVersion)
{
throw new ArgumentException(
$"Migration for {typeof(TSaveData).Name} must advance the version number.",
nameof(migration));
}
lock (_migrationsLock)
{
if (_migrations.ContainsKey(migration.FromVersion))
{
throw new InvalidOperationException(
$"Duplicate save migration registration for {typeof(TSaveData).Name} from version {migration.FromVersion}.");
}
_migrations.Add(migration.FromVersion, migration);
}
return this;
}
///
/// 检查指定槽位是否存在存档
///
/// 存档槽位编号
/// 如果存档存在返回true,否则返回false
public async Task ExistsAsync(int slot)
{
var storage = GetSlotStorage(slot);
return await storage.ExistsAsync(_config.SaveFileName);
}
///
/// 加载指定槽位的存档
///
/// 存档槽位编号
/// 存档数据对象,如果不存在则返回新实例
public async Task LoadAsync(int slot)
{
var storage = GetSlotStorage(slot);
if (await storage.ExistsAsync(_config.SaveFileName))
{
var loaded = await storage.ReadAsync(_config.SaveFileName);
return await MigrateIfNeededAsync(slot, storage, loaded);
}
return new TSaveData();
}
///
/// 保存存档到指定槽位
///
/// 存档槽位编号
/// 要保存的存档数据
public async Task SaveAsync(int slot, TSaveData data)
{
var slotPath = $"{_config.SaveSlotPrefix}{slot}";
// 确保槽位目录存在
if (!await _rootStorage.DirectoryExistsAsync(slotPath))
await _rootStorage.CreateDirectoryAsync(slotPath);
var storage = GetSlotStorage(slot);
await storage.WriteAsync(_config.SaveFileName, data);
}
///
/// 删除指定槽位的存档
///
/// 存档槽位编号
public async Task DeleteAsync(int slot)
{
var storage = GetSlotStorage(slot);
await storage.DeleteAsync(_config.SaveFileName);
}
///
/// 列出所有有效的存档槽位
///
/// 包含所有有效存档槽位编号的只读列表,按升序排列
public async Task> ListSlotsAsync()
{
// 列举所有槽位目录
var directories = await _rootStorage.ListDirectoriesAsync();
var slots = new List();
foreach (var dirName in directories)
{
// 检查目录名是否符合槽位前缀
if (!dirName.StartsWith(_config.SaveSlotPrefix, StringComparison.Ordinal))
continue;
// 提取槽位编号
var slotStr = dirName[_config.SaveSlotPrefix.Length..];
if (!int.TryParse(slotStr, CultureInfo.InvariantCulture, out var slot))
continue;
// 直接检查存档文件是否存在,避免重复创建 ScopedStorage
var saveFilePath = $"{dirName}/{_config.SaveFileName}";
if (await _rootStorage.ExistsAsync(saveFilePath))
slots.Add(slot);
}
return slots.OrderBy(x => x).ToList();
}
///
/// 获取指定槽位的存储对象
///
/// 存档槽位编号
/// 对应槽位的存储对象
private IStorage GetSlotStorage(int slot)
{
return new ScopedStorage(_rootStorage, $"{_config.SaveSlotPrefix}{slot}");
}
///
/// 在加载旧版本存档时按注册顺序执行迁移,并在成功后自动回写升级结果。
///
/// 当前加载的存档槽位。
/// 对应槽位的存储对象。
/// 原始加载出来的存档数据。
/// 迁移后的最新存档;如果无需迁移则返回原始对象。
///
/// 当前运行时缺少必要的迁移链、读取到更高版本的存档,或迁移器返回了非法版本。
///
private async Task MigrateIfNeededAsync(int slot, IStorage storage, TSaveData data)
{
if (data is not IVersionedData versionedData)
{
return data;
}
var latestTemplate = new TSaveData();
if (latestTemplate is not IVersionedData latestVersionedData)
{
return data;
}
var currentVersion = versionedData.Version;
var targetVersion = latestVersionedData.Version;
if (currentVersion > targetVersion)
{
throw new InvalidOperationException(
$"Save slot {slot} for {typeof(TSaveData).Name} is version {currentVersion}, " +
$"which is newer than the current runtime version {targetVersion}.");
}
if (currentVersion == targetVersion)
{
return data;
}
EnsureVersionedSaveType();
var migrated = data;
// 迁移链按“当前版本 -> 下一个已注册迁移器”推进;任何缺口都表示运行时无法安全解释旧存档。
// 读取迁移表时使用同一把锁,保证并发注册不会让加载线程看到不一致的链路状态。
while (currentVersion < targetVersion)
{
ISaveMigration? migration;
lock (_migrationsLock)
{
_migrations.TryGetValue(currentVersion, out migration);
}
if (migration is null)
{
throw new InvalidOperationException(
$"No save migration is registered for {typeof(TSaveData).Name} from version {currentVersion}.");
}
migrated = migration.Migrate(migrated) ??
throw new InvalidOperationException(
$"Save migration for {typeof(TSaveData).Name} from version {currentVersion} returned null.");
if (migrated is not IVersionedData migratedVersionedData)
{
throw new InvalidOperationException(
$"Save migration for {typeof(TSaveData).Name} must return data implementing {nameof(IVersionedData)}.");
}
// 显式校验迁移器声明与实际结果,避免版本号不前进导致死循环或把旧数据错误回写为“已升级”。
if (migration.ToVersion != migratedVersionedData.Version)
{
throw new InvalidOperationException(
$"Save migration for {typeof(TSaveData).Name} declared target version {migration.ToVersion} " +
$"but returned version {migratedVersionedData.Version}.");
}
if (migratedVersionedData.Version <= currentVersion)
{
throw new InvalidOperationException(
$"Save migration for {typeof(TSaveData).Name} must advance beyond version {currentVersion}.");
}
if (migratedVersionedData.Version > targetVersion)
{
throw new InvalidOperationException(
$"Save migration for {typeof(TSaveData).Name} produced version {migratedVersionedData.Version}, " +
$"which exceeds the current runtime version {targetVersion}.");
}
currentVersion = migratedVersionedData.Version;
}
await storage.WriteAsync(_config.SaveFileName, migrated);
return migrated;
}
///
/// 验证当前存档类型支持基于版本号的迁移流程。
///
///
/// 未实现 。
///
private static void EnsureVersionedSaveType()
{
if (!typeof(IVersionedData).IsAssignableFrom(typeof(TSaveData)))
{
throw new InvalidOperationException(
$"{typeof(TSaveData).Name} must implement {nameof(IVersionedData)} to use save migrations.");
}
}
///
/// 初始化逻辑
///
protected override void OnInit()
{
}
}