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 迁移与旧代码地图
This commit is contained in:
2026-08-18 18:06:34 +08:00
parent 517c4cf46e
commit d74c2f08ca
240 changed files with 13647 additions and 545 deletions
@@ -0,0 +1,15 @@
{
"name": "ShrinkDataSaver.Editor",
"rootNamespace": "ShrinkDataSaver.Editor",
"references": [
"ShrinkDataSaver.Runtime",
"UniTask"
],
"includePlatforms": [
"Editor"
],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"autoReferenced": false
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 78cf16cc0efe432a939df0e90a4cdb14
timeCreated: 1773047512
@@ -0,0 +1,378 @@
#if UNITY_EDITOR
using System;
using System.IO;
using System.Linq;
using Cysharp.Threading.Tasks;
using Newtonsoft.Json.Linq;
using UnityEditor;
using UnityEngine;
namespace ShrinkDataSaver.Editor
{
public class ShrinkDataSaverEditorWindow : EditorWindow
{
private int _selectedTab;
private readonly string[] _tabs = { "设置", "存档槽", "工具" };
private Vector2 _settingsScroll;
private Vector2 _slotsScroll;
private string _setKey = "";
private string _setValue = "";
private int _setType; // 0=string, 1=int, 2=float, 3=bool
private readonly string[] _typeNames = { "字符串", "整数", "浮点数", "布尔值" };
private int _saveSlot;
private string _slotName = "新存档";
private bool _encrypt;
private string _encryptKey = "";
private SaveMeta[] _metaCache;
[MenuItem("ShrinkSDK/存档/数据查看器")]
public static void ShowWindow()
{
var w = GetWindow<ShrinkDataSaverEditorWindow>("ShrinkDataSaver");
w.minSize = new Vector2(460, 500);
}
private void OnGUI()
{
DrawHeader();
_selectedTab = GUILayout.Toolbar(_selectedTab, _tabs, EditorStyles.toolbarButton);
EditorGUILayout.Space(4);
switch (_selectedTab)
{
case 0: DrawSettingsTab(); break;
case 1: DrawSaveSlotsTab(); break;
case 2: DrawToolsTab(); break;
}
}
private void OnInspectorUpdate() => Repaint();
private void DrawHeader()
{
EditorGUILayout.BeginHorizontal(EditorStyles.toolbar);
GUILayout.Label("ShrinkDataSaver", EditorStyles.boldLabel);
GUILayout.FlexibleSpace();
var color = GUI.color;
GUI.color = Application.isPlaying ? Color.green : Color.gray;
GUILayout.Label(Application.isPlaying ? "● 运行中" : "○ 未运行", EditorStyles.miniLabel);
GUI.color = color;
EditorGUILayout.EndHorizontal();
}
private void DrawSettingsTab()
{
if (!Application.isPlaying)
{
EditorGUILayout.HelpBox("设置查看器仅在运行模式下可用。", MessageType.Info);
DrawSettingsFilePath();
return;
}
EditorGUILayout.BeginHorizontal();
if (GUILayout.Button("重新加载", EditorStyles.miniButton))
ShrinkSettings.LoadAsync().Forget();
if (GUILayout.Button("立即保存", EditorStyles.miniButton))
ShrinkSettings.SaveAsync().Forget();
EditorGUILayout.EndHorizontal();
EditorGUILayout.Space(6);
var allSettings = ShrinkSettings.GetAllRaw();
EditorGUILayout.LabelField($"当前设置项({allSettings.Count} 项)", EditorStyles.boldLabel);
if (allSettings.Count == 0)
{
EditorGUILayout.HelpBox("暂无设置项。", MessageType.None);
}
else
{
_settingsScroll = EditorGUILayout.BeginScrollView(_settingsScroll, GUILayout.MaxHeight(240));
string keyToRemove = null;
foreach (var kvp in allSettings.OrderBy(k => k.Key))
{
EditorGUILayout.BeginHorizontal("box");
EditorGUILayout.LabelField(kvp.Key, EditorStyles.boldLabel, GUILayout.Width(140));
var valueStr = FormatJToken(kvp.Value);
EditorGUILayout.LabelField(valueStr, EditorStyles.wordWrappedMiniLabel);
var prevColor = GUI.backgroundColor;
GUI.backgroundColor = new Color(1f, 0.6f, 0.6f);
if (GUILayout.Button("删除", EditorStyles.miniButton, GUILayout.Width(40)))
keyToRemove = kvp.Key;
GUI.backgroundColor = prevColor;
EditorGUILayout.EndHorizontal();
}
EditorGUILayout.EndScrollView();
if (keyToRemove != null)
ShrinkSettings.Remove(keyToRemove);
}
EditorGUILayout.Space(8);
EditorGUILayout.LabelField("新增 / 修改", EditorStyles.boldLabel);
_setKey = EditorGUILayout.TextField("键", _setKey);
_setType = EditorGUILayout.Popup("类型", _setType, _typeNames);
_setValue = EditorGUILayout.TextField("值", _setValue);
EditorGUI.BeginDisabledGroup(string.IsNullOrWhiteSpace(_setKey));
if (GUILayout.Button("设置", EditorStyles.miniButton))
SetTypedValue(_setKey, _setValue, _setType);
EditorGUI.EndDisabledGroup();
}
private static void SetTypedValue(string key, string value, int type)
{
switch (type)
{
case 0: // string
ShrinkSettings.Set(key, value);
break;
case 1: // int
if (int.TryParse(value, out var intVal))
ShrinkSettings.Set(key, intVal);
else
Debug.LogWarning($"[ShrinkDataSaver] 无法将 \"{value}\" 解析为整数。");
break;
case 2: // float
if (float.TryParse(value, out var floatVal))
ShrinkSettings.Set(key, floatVal);
else
Debug.LogWarning($"[ShrinkDataSaver] 无法将 \"{value}\" 解析为浮点数。");
break;
case 3: // bool
if (bool.TryParse(value, out var boolVal))
ShrinkSettings.Set(key, boolVal);
else
ShrinkSettings.Set(key, value is "1" or "true" or "True");
break;
}
}
private static string FormatJToken(JToken token)
{
if (token == null) return "null";
return token.Type switch
{
JTokenType.String => $"\"{token}\"",
JTokenType.Boolean => token.Value<bool>() ? "true" : "false",
JTokenType.Array or JTokenType.Object => token.ToString(Newtonsoft.Json.Formatting.None),
_ => token.ToString()
};
}
private void DrawSettingsFilePath()
{
var cfg = ShrinkDataSaverSettings.Instance;
var root = string.IsNullOrEmpty(cfg.customSavePath)
? Application.persistentDataPath
: cfg.customSavePath;
EditorGUILayout.LabelField("设置文件路径:", Path.Combine(root, cfg.settingsFileName),
EditorStyles.wordWrappedMiniLabel);
if (GUILayout.Button("打开目录", EditorStyles.miniButton))
EditorUtility.RevealInFinder(root);
}
private void DrawSaveSlotsTab()
{
if (!Application.isPlaying)
{
EditorGUILayout.HelpBox("存档操作仅在运行模式下可用。", MessageType.Info);
DrawSavesFolderPath();
return;
}
EditorGUILayout.LabelField("快速操作", EditorStyles.boldLabel);
_saveSlot = EditorGUILayout.IntField("槽位索引", _saveSlot);
_slotName = EditorGUILayout.TextField("存档名称", _slotName);
_encrypt = EditorGUILayout.Toggle("加密", _encrypt);
if (_encrypt)
_encryptKey = EditorGUILayout.TextField("密钥", _encryptKey);
EditorGUILayout.BeginHorizontal();
if (GUILayout.Button("保存", EditorStyles.miniButton))
{
ShrinkSave.SaveSlotAsync(_saveSlot, new SaveOptions
{
SlotName = _slotName,
Encrypt = _encrypt,
EncryptionKey = _encryptKey
}).Forget();
RefreshMetaDelayed().Forget();
}
if (GUILayout.Button("加载", EditorStyles.miniButton))
{
ShrinkSave.LoadSlotAsync(_saveSlot, _encrypt ? _encryptKey : null).Forget();
}
if (GUILayout.Button("删除", EditorStyles.miniButton))
{
if (EditorUtility.DisplayDialog("删除存档",
$"确认删除槽位 {_saveSlot}", "删除", "取消"))
{
ShrinkSave.DeleteSlotAsync(_saveSlot).Forget();
RefreshMetaDelayed().Forget();
}
}
EditorGUILayout.EndHorizontal();
EditorGUILayout.Space(8);
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField("所有存档", EditorStyles.boldLabel);
if (GUILayout.Button("刷新", EditorStyles.miniButton, GUILayout.Width(50)))
RefreshMeta().Forget();
EditorGUILayout.EndHorizontal();
if (_metaCache == null)
{
EditorGUILayout.HelpBox("点击「刷新」加载存档列表。", MessageType.None);
}
else if (_metaCache.Length == 0)
{
EditorGUILayout.HelpBox("暂无存档。", MessageType.None);
}
else
{
DrawMetaList();
}
}
private async UniTask RefreshMeta()
{
_metaCache = await ShrinkSave.GetAllMetaAsync();
Repaint();
}
private async UniTaskVoid RefreshMetaDelayed()
{
await UniTask.Delay(300);
await RefreshMeta();
}
private void DrawMetaList()
{
_slotsScroll = EditorGUILayout.BeginScrollView(_slotsScroll, GUILayout.MaxHeight(360));
foreach (var meta in _metaCache)
{
EditorGUILayout.BeginVertical("box");
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField($"槽位 {meta.SlotIndex} — {meta.SlotName}", EditorStyles.boldLabel);
GUILayout.FlexibleSpace();
if (GUILayout.Button("加载", EditorStyles.miniButton, GUILayout.Width(40)))
{
var key = meta.IsEncrypted ? _encryptKey : null;
ShrinkSave.LoadSlotAsync(meta.SlotIndex, key).Forget();
}
var prevColor = GUI.backgroundColor;
GUI.backgroundColor = new Color(1f, 0.6f, 0.6f);
if (GUILayout.Button("删除", EditorStyles.miniButton, GUILayout.Width(40)))
{
if (EditorUtility.DisplayDialog("删除存档",
$"确认删除槽位 {meta.SlotIndex}{meta.SlotName})?", "删除", "取消"))
{
ShrinkSave.DeleteSlotAsync(meta.SlotIndex).Forget();
RefreshMetaDelayed().Forget();
}
}
GUI.backgroundColor = prevColor;
EditorGUILayout.EndHorizontal();
EditorGUILayout.LabelField("最后修改", meta.LastModifiedTime.ToString("yyyy-MM-dd HH:mm:ss"));
EditorGUILayout.LabelField("游戏时长", TimeSpan.FromSeconds(meta.PlaytimeSeconds).ToString(@"hh\:mm\:ss"));
EditorGUILayout.LabelField("存档版本", meta.SaveVersion.ToString());
EditorGUILayout.LabelField("已加密", meta.IsEncrypted ? "是" : "否");
EditorGUILayout.LabelField("截图", meta.ScreenshotBase64 != null ? "有" : "无");
EditorGUILayout.EndVertical();
EditorGUILayout.Space(2);
}
EditorGUILayout.EndScrollView();
}
private void DrawSavesFolderPath()
{
var cfg = ShrinkDataSaverSettings.Instance;
var root = string.IsNullOrEmpty(cfg.customSavePath)
? Application.persistentDataPath
: cfg.customSavePath;
var saves = Path.Combine(root, "saves");
EditorGUILayout.LabelField("存档目录:", saves, EditorStyles.wordWrappedMiniLabel);
if (GUILayout.Button("打开目录", EditorStyles.miniButton))
EditorUtility.RevealInFinder(saves);
}
private void DrawToolsTab()
{
EditorGUILayout.LabelField("实用工具", EditorStyles.boldLabel);
if (GUILayout.Button("打开持久化数据目录"))
EditorUtility.RevealInFinder(Application.persistentDataPath);
EditorGUILayout.Space(8);
EditorGUILayout.LabelField("配置资源", EditorStyles.boldLabel);
var settings = ShrinkDataSaverSettings.Instance;
if (settings)
{
EditorGUILayout.ObjectField("资源文件", settings, typeof(ShrinkDataSaverSettings), false);
}
else
{
EditorGUILayout.HelpBox(
"未找到 ShrinkDataSaverSettings 资源文件。\n" +
"请通过菜单 Assets → Create → ShrinkDataSaver → Settings 创建。",
MessageType.Warning);
}
EditorGUILayout.Space(8);
EditorGUILayout.LabelField("危险操作", EditorStyles.boldLabel);
GUI.backgroundColor = new Color(1f, 0.4f, 0.4f);
if (GUILayout.Button("删除所有存档文件"))
{
var cfg = ShrinkDataSaverSettings.Instance;
var root = string.IsNullOrEmpty(cfg?.customSavePath)
? Application.persistentDataPath
: cfg.customSavePath;
var saves = Path.Combine(root, "saves");
if (EditorUtility.DisplayDialog("删除所有存档",
$"将删除以下目录中的所有内容:\n{saves}\n\n此操作不可撤销!",
"全部删除", "取消"))
{
if (Directory.Exists(saves))
Directory.Delete(saves, true);
_metaCache = null;
Debug.Log("[ShrinkDataSaver] 所有存档文件已删除。");
}
}
GUI.backgroundColor = Color.white;
}
}
}
#endif
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 0c285b35e7094111b6ea986ea5af391e
timeCreated: 1773047478