Files
Workspace/Assets/Modules/ShrinkDataSaver/Runtime/ShrinkSettings.cs
T
cneicy d74c2f08ca feat(packages): 内置 SDK 包并完善 ContextLoader 集成
- 将 ShrinkEventBus、ShrinkDataSaver 及其 EventBus 集成从 gitlink 转为仓库直接维护的完整 UPM 包,补齐运行时、编辑器工具、测试与文档
- 新增 Command 和 Network 的 App 集成组件,支持 ContextLoader 服务发布、可逆注销及 Network Loopback 生命周期管理
- 更新 Starter 与演示组合逻辑,缺失模块时可注册、已有兼容安装器时可覆盖,并补充宿主启动断言
- 升级内部包依赖与 Shared CodeGen 包定义,放宽 Integration.App 包的 Git 忽略规则
- 将独立服务器生成器改为基于已编译程序集的语义扫描,支持 partial、复杂泛型、命名冲突检测及模板 SHA-256 覆写保护
- 新增 Network 语义扫描、模板保护和 App 组件生命周期测试
- 新增真实 UPM 消费工程验证脚本,校验内部版本一致性、程序集加载及 EditMode 测试
- 重构当前架构文档并归档已完成的 Cordis 迁移与旧代码地图
2026-08-18 18:06:34 +08:00

181 lines
6.0 KiB
C#

using System;
using System.Collections.Generic;
using System.Threading;
using Cysharp.Threading.Tasks;
using Newtonsoft.Json.Linq;
using UnityEngine;
namespace ShrinkDataSaver
{
[Serializable]
internal class SettingsData
{
public Dictionary<string, JToken> Values = new();
}
public static class ShrinkSettings
{
private const string BackupPrimarySuffix = ".bak1";
private const string BackupSecondarySuffix = ".bak2";
private static SettingsData _data = new();
private static IStorageProvider _storage;
private static string _filePath;
private static CancellationTokenSource _debounceCts;
private static readonly Dictionary<string, List<Action<object>>> _watchers = new();
public static event Action<string, object> OnChanged;
internal static void Initialize(IStorageProvider storage, string filePath)
{
_storage = storage; _filePath = filePath;
}
public static void Set<T>(string key, T value)
{
_data.Values[key] = JToken.FromObject(value);
FireChanged(key, value);
ScheduleWrite().Forget();
}
public static T Get<T>(string key, T defaultValue = default)
{
if (_data.Values.TryGetValue(key, out var token))
{
try { return token.ToObject<T>(); } catch { /* ignore */ }
}
return defaultValue;
}
public static bool Has(string key) => _data.Values.ContainsKey(key);
public static IReadOnlyDictionary<string, JToken> GetAllRaw() => _data.Values;
public static void Remove(string key)
{
if (_data.Values.Remove(key))
{
FireChanged(key, null);
ScheduleWrite().Forget();
}
}
public static void Watch<T>(string key, Action<T> callback)
{
if (!_watchers.ContainsKey(key)) _watchers[key] = new List<Action<object>>();
_watchers[key].Add(raw => callback((T)Convert.ChangeType(raw, typeof(T))));
}
public static void Unwatch(string key, Action<object> callback)
{
if (_watchers.TryGetValue(key, out var list)) list.Remove(callback);
}
public static async UniTask SaveAsync(CancellationToken ct = default)
{
_debounceCts?.Cancel();
if (_storage == null || string.IsNullOrWhiteSpace(_filePath))
{
// 未初始化(独立宿主/单测环境)时跳过持久化,与 LoadAsync 的守卫保持一致
return;
}
var bytes = DataSerializer.Serialize(_data);
await _storage.WriteAsync(_filePath, bytes, ct);
}
public static async UniTask LoadAsync(CancellationToken ct = default)
{
if (_storage == null || string.IsNullOrWhiteSpace(_filePath))
{
_data = new SettingsData();
return;
}
var loadedData = await TryLoadWithFallbackAsync(ct);
_data = loadedData ?? new SettingsData();
}
internal static void ResetForTesting()
{
_data = new SettingsData();
_debounceCts?.Cancel();
_debounceCts = null;
_watchers.Clear();
OnChanged = null;
}
private static void FireChanged(string key, object value)
{
OnChanged?.Invoke(key, value);
if (_watchers.TryGetValue(key, out var list))
foreach (var cb in list)
try { cb(value); } catch (Exception e) { Debug.LogException(e); }
}
private static async UniTaskVoid ScheduleWrite()
{
_debounceCts?.Cancel();
_debounceCts = new CancellationTokenSource();
var token = _debounceCts.Token;
try
{
var delay = (int)(ShrinkDataSaverSettings.Instance.settingsWriteDebounceSeconds * 1000);
await UniTask.Delay(delay, cancellationToken: token);
await SaveAsync(token);
}
catch (OperationCanceledException) { }
catch (Exception e) { Debug.LogException(e); }
}
private static async UniTask<SettingsData> TryLoadWithFallbackAsync(CancellationToken ct)
{
var primaryPath = _filePath;
Exception lastError = null;
foreach (var candidatePath in EnumerateCandidatePaths(primaryPath))
{
if (!await _storage.ExistsAsync(candidatePath, ct))
{
continue;
}
try
{
var bytes = await _storage.ReadAsync(candidatePath, ct);
var loaded = DataSerializer.Deserialize<SettingsData>(bytes) ?? new SettingsData();
if (!string.Equals(candidatePath, primaryPath, StringComparison.Ordinal))
{
Debug.LogWarning($"[ShrinkDataSaver] Settings 主文件损坏或缺失,已从备份恢复:{candidatePath}");
await _storage.WriteAsync(primaryPath, bytes, ct);
}
return loaded;
}
catch (Exception ex)
{
lastError = ex;
Debug.LogWarning($"[ShrinkDataSaver] 读取 Settings 副本失败:{candidatePath} / {ex.Message}");
}
}
if (lastError != null)
{
Debug.LogWarning("[ShrinkDataSaver] 所有 Settings 副本均不可用,已回退为空设置。");
}
return new SettingsData();
}
private static IEnumerable<string> EnumerateCandidatePaths(string primaryPath)
{
yield return primaryPath;
yield return primaryPath + BackupPrimarySuffix;
yield return primaryPath + BackupSecondarySuffix;
}
}
}