- 将 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 迁移与旧代码地图
227 lines
15 KiB
Markdown
227 lines
15 KiB
Markdown
# ShrinkSDK 当前架构
|
|
|
|
> 当前基线:2026-08-18
|
|
> 本文是仓库唯一的当前架构文档。实现与本文冲突时,以源码、`package.json`、asmdef 和自动化验证结果为准,并应在同一变更中修正文档。迁移过程与旧代码地图只保存在 `Docs/Archive/`,不再作为当前设计依据。
|
|
|
|
## 1. 项目定位
|
|
|
|
ShrinkSDK 是以 Unity Package Manager 包为发布边界的 SDK 单仓库,不是单个游戏项目。仓库直接拥有 `Assets/Modules/` 下全部包源码;模块目录不再是嵌套 Git 仓库或 gitlink。一次干净克隆应能获得完整源码,Unity 只需要恢复外部 UPM 依赖。
|
|
|
|
它提供四层能力:
|
|
|
|
1. 基础模块:EventBus、DataSaver、Command、Network、Tutorial。
|
|
2. 动态组合内核:ShrinkContext.Core,以可逆效应、响应式依赖和 fiber 生命周期承载模块装卸。
|
|
3. 应用与扩展宿主:ShrinkApp、Starter、ModFramework。
|
|
4. 边界适配与生成工具:`*.Integration.*`、共享 IL 后处理器、独立服务器生成器。
|
|
|
|
当前默认应用主路径是 `ContextLoader`。`ClassicHost` 和旧 installer 只作为兼容面保留,不再承载新架构能力。
|
|
|
|
## 2. 开发原则
|
|
|
|
### 2.1 包是发布边界,asmdef 是编译边界
|
|
|
|
- 每个 `Assets/Modules/<Module>/package.json` 都必须能被独立 UPM 消费。
|
|
- 主包不反向依赖集成包;跨模块行为放入 `*.Integration.*` 或 Context adapter。
|
|
- asmdef 只声明实际编译依赖,不依靠根工程中偶然存在的程序集。
|
|
- 内部包依赖版本必须等于被依赖包自身的 `version`,不接受“根工程能编译所以先放着”的漂移。
|
|
|
|
### 2.2 声明契约,生成注册,避免运行时全域扫描
|
|
|
|
网络消息、订阅者、命令、事件订阅和 App 安装器使用 Attribute 声明。`ShrinkShared.CodeGen` 在 Unity 编译期写入程序集注册表,运行时读取注册表;实例对象和外部 DLL 走显式实例注册。
|
|
|
|
独立服务器生成器是编辑器工具,不参与运行时注册。它读取 Unity 已编译的 Player 程序集元数据,而不是用正则解析 C# 文本。因此 `partial`、命名空间、复杂泛型和真实继承关系都由编译器语义决定。可导出的网络消息必须显式声明 `[ShrinkNetworkMessage(opcode, route)]`;单独手写 `RegisterMessage<T>()` 不是可移植的生成输入。
|
|
|
|
### 2.3 静态门面负责易用性,可实例化内核负责组合
|
|
|
|
`EventBus`、`ShrinkApp`、`ShrinkNetworkRuntime.Default` 等静态入口是默认根的易用门面,不是生命周期所有者。真实服务可实例化,并由 Context 组件安装、发布、撤回。门面注册也必须被视为可逆效应,组件退役时按实例注销,不能留下失效的全局引用。
|
|
|
|
### 2.4 运行时变更必须可回滚、可解释
|
|
|
|
组件通过 `ShrinkCtx.Effect/EffectAsync/EffectInverse` 记录逆操作,通过 `Set/Get` 提供和消费能力。加载失败、依赖消失、热替换失败都应恢复到上一个已提交组合。诊断必须能回答:哪个 fiber 在等待什么、当前 provider 是谁、哪次事务失败、是否完成恢复。
|
|
|
|
### 2.5 生成内容与人工内容分区
|
|
|
|
- `GeneratedServers/*/Generated/*.g.cs` 是可重建合同,不承载手写业务。
|
|
- 服务器模板文件由哈希清单管理。重新生成前先验证全部现有文件仍等于上次生成版本;任何人工修改都会让整个复制阶段在写入前中止。
|
|
- 业务扩展应放在非生成文件或独立模块中,不通过修改生成产物维持。
|
|
|
|
## 3. 仓库结构
|
|
|
|
```text
|
|
ShrinkSDK/
|
|
|-- Assets/Modules/ 19 个根仓库直接跟踪的 UPM 包
|
|
|-- Assets/Scenes/ 示例与验收场景
|
|
|-- Assets/Resources/ 当前应用配置与组合 Profile
|
|
|-- GeneratedServers/ 独立 .NET 宿主、生成合同与烟测工程
|
|
|-- GeneratedModSdk/ Mod SDK 导出物
|
|
|-- Packages/ 根 Unity 工程依赖
|
|
|-- Tools/UpmConsumerValidation/ 干净 UPM 消费工程验证
|
|
|-- Docs/Archive/ 已完成迁移与过期地图,仅供追溯
|
|
|-- DESIGN.md 唯一当前架构文档
|
|
|-- NETWORK_PITFALLS.md 网络实现经验记录,不是架构基线
|
|
`-- .planning/threads/ 工作线程恢复记录,不是产品文档
|
|
```
|
|
|
|
## 4. 包清单
|
|
|
|
| 包 | 版本 | 职责 |
|
|
|---|---:|---|
|
|
| `com.cneicy.shrink-eventbus` | 1.3.0 | 类型安全事件总线、优先级派发、编译期自动注册 |
|
|
| `com.cneicy.shrink-datasaver` | 2.2.0 | 多槽位存档、设置、迁移、加密、原子写入与备份 |
|
|
| `com.cneicy.shrink-command` | 0.2.0 | 路径式命令、权限与同步/异步执行 |
|
|
| `com.cneicy.shrink-network` | 0.2.0 | 消息、RPC、权限、诊断、TCP/KCP/Loopback 与服务器生成 |
|
|
| `com.cneicy.shrink-mod-framework` | 0.1.0 | 模组发现、依赖、生命周期、外部 DLL revision 与 Harmony lease |
|
|
| `com.cneicy.shrink-tutorial` | 0.1.0 | 数据驱动引导、遮罩、锚点、触发与持久化 |
|
|
| `com.cneicy.shrink-context-core` | 0.1.0 | 可逆效应、coeffect、fiber、声明式 loader 与诊断 |
|
|
| `com.cneicy.shrink-app-core` | 0.1.1 | App 设置、服务门面、ClassicHost 兼容面与宿主协议 |
|
|
| `com.cneicy.shrink-app-starter-basic` | 0.1.0 | 默认 Context 组合根、配置资产和示例入口 |
|
|
| `com.cneicy.shrink-context-app-adapter` | 0.1.0 | ContextLoader 宿主、Profile/JSON、诊断与基准 |
|
|
| `com.cneicy.shrink-context-eventbus-adapter` | 0.1.0 | EventBus 注册/订阅的可逆效应包装 |
|
|
| `com.cneicy.shrink-datasaver-integration-eventbus` | 2.1.0 | DataSaver 事件桥 |
|
|
| `com.cneicy.shrink-datasaver-integration-app` | 0.1.0 | DataSaver App installer/原生 Context 组件 |
|
|
| `com.cneicy.shrink-command-integration-eventbus` | 0.1.1 | 命令请求与生命周期事件桥 |
|
|
| `com.cneicy.shrink-command-integration-network` | 0.1.0 | `command/execute` RPC 桥 |
|
|
| `com.cneicy.shrink-command-integration-app` | 0.1.0 | Command App installer/原生 Context 组件 |
|
|
| `com.cneicy.shrink-network-integration-eventbus` | 0.1.1 | 网络事件广播、裁决结果与 delta 去重 |
|
|
| `com.cneicy.shrink-network-integration-app` | 0.1.0 | Network App installer/原生 Context 组件 |
|
|
| `com.cneicy.shrink-shared-codegen` | 0.1.0 | App、Command、Network 共用的 Editor-only IL 后处理注册表生成器 |
|
|
|
|
`ShrinkShared.CodeGen` 不反向引用业务 asmdef,只按程序集名与类型全名读取 Cecil 元数据,因此业务包可以依赖它而不形成包循环。
|
|
|
|
## 5. 总体分层
|
|
|
|
```text
|
|
Starter / Composition Profile
|
|
|
|
|
ShrinkAppLoaderBootstrapper ---- ClassicHost (compatibility only)
|
|
|
|
|
ShrinkContextLoader + ShrinkContextRuntime
|
|
|
|
|
native module components ---- integration components
|
|
| |
|
|
Command / DataSaver / Network / EventBus / Tutorial
|
|
|
|
|
transport, storage, external files, generated .NET server
|
|
```
|
|
|
|
依赖方向只能向下。功能主包不知道 App、Context 或 EventBus 桥;适配包引用双方并负责把副作用变成可撤回效应。
|
|
|
|
## 6. ContextLoader 当前完成态
|
|
|
|
Cordis 迁移的阶段 0 至阶段 5 已进入当前架构,不再是待办计划。
|
|
|
|
### 6.1 核心语义
|
|
|
|
- `ShrinkCtx.Effect/EffectAsync`:前向执行产生逆操作,按 LIFO 回滚;前向未完成前不会执行逆操作。
|
|
- `ShrinkCtx.Set/Get`:provider 安装和撤回触发依赖者响应;同值但不同 provider uid 仍会触发重载。
|
|
- `ShrinkContextRuntime.Use`:组件实例化为 fiber,状态按 `Loading -> Active -> Unloading -> Inactive` 惯性转换。
|
|
- 退役 provider 时先排空依赖者,再执行 provider 的逆操作。
|
|
- `IShrinkIterativeComponent` 支持分步效应和部分回滚。
|
|
- `Provide` 声明限制组件写入能力;未声明、未激活和策略拒绝是三种不同错误。
|
|
|
|
### 6.2 声明式协调
|
|
|
|
`ShrinkContextLoader.ApplyAsync` 根据 `ShrinkLoaderEntry` 的期望集合增量协调:
|
|
|
|
- 新条目创建 fiber;消失或禁用的条目退役。
|
|
- 组件、配置或 isolate 改变时重建。
|
|
- intercept 元数据可原位更新,不改变 provider uid 或 fiber generation。
|
|
- 事务失败时恢复旧条目、旧 provider 关系和已提交视图。
|
|
- `key -> inject fibers` 倒排索引限制通知候选,再按 realm 精确过滤。
|
|
|
|
### 6.3 隔离、访问与边界
|
|
|
|
`isolate` 创建独立解析域;`intercept` 将访问元数据交给领域策略,当前可用于 DataSaver 只读能力。它们是组合与能力介导机制,不是不可信 DLL 的安全沙箱,也不能撤回已经发送的网络数据或外部文件写入。
|
|
|
|
### 6.4 App 默认组合
|
|
|
|
`ShrinkAppLoaderBootstrapper.DefaultComposition` 接入 Basic Starter 的七个声明式模块:Command、DataSaver、Network 三个原生组件,Command-Network 集成,以及 Command/DataSaver/Network 的 EventBus 集成。
|
|
|
|
`ShrinkAppCompositionProfile` 用 ScriptableObject/JSON 决定条目启用、显式排除、isolate 与 intercept;组件工厂仍由代码注册,配置文件不能按任意类型名反射实例化对象。`ShrinkSDK/Cordis/诊断与组合` 显示 fiber、等待依赖、provider target、事务和外部程序集 revision。
|
|
|
|
`ShrinkApp.IsRunning` 与 `ShrinkApp.Services` 已接回 LoaderHost。原生组件停用时使用 `TryUnregister(instance)` 撤回对应门面。旧 `IShrinkAppModuleInstaller` 适配仍可用于 ClassicHost,但默认 ContextLoader 不再通过它启动核心模块。
|
|
|
|
## 7. 功能模块与组合关系
|
|
|
|
### 7.1 EventBus
|
|
|
|
静态 `EventBus` 委托给 `IShrinkEventBus` 实例。监听按 phase 和数字优先级稳定归并;订阅句柄可单独释放,实例注册可整体撤销。Context 适配器把二者登记为效应,保证 fiber 卸载自动退订。
|
|
|
|
### 7.2 DataSaver
|
|
|
|
`ShrinkSave`、`ShrinkSettings` 和 `ShrinkDataSaverRuntime` 构成运行时入口。存储层以临时文件原子替换主文件并轮换 `.bak1/.bak2`;损坏时尝试备份恢复。Context 原生组件发布 reader/writer 能力并在停用时做补偿式收尾。
|
|
|
|
### 7.3 Command
|
|
|
|
`ShrinkCommandService` 解析字面量与贪婪参数路径,支持来源、权限和同步/Task/UniTask 返回。静态命令使用编译期注册表,实例命令显式注册。网络桥只在 Command 与 Network 两个服务键均可用时激活。
|
|
|
|
### 7.4 Network
|
|
|
|
`ShrinkNetworkService` 组合 serializer、message registry、router 与 transport。包头包含协议/Schema 版本、请求 token、会话 token、route、kind 和 payload。Unity 主线程派发通过有界 dispatch queue;独立服务器使用 inline scheduler。
|
|
|
|
网络合同必须同时满足:
|
|
|
|
- 类型实现 `IShrinkNetworkMessage`、`IShrinkNetworkRequest` 或继承 `ShrinkRpcResponseBase`。
|
|
- 类型显式声明 `[ShrinkNetworkMessage(opcode, route)]`。
|
|
- opcode 和 route 在导出集合内唯一。
|
|
- 导出到无命名空间的服务器合同后,简单类型名仍唯一;不同命名空间的同名类型会明确报错,不再静默丢弃。
|
|
|
|
`[ShrinkNetworkStateSync]` 标记 JoinRequest、JoinResponse、Command、StateDelta、LeaveNotice 和 Heartbeat。`[ShrinkNetworkEvent]` 进一步区分广播、远端裁决和增量事件。
|
|
|
|
### 7.5 ModFramework
|
|
|
|
`ShrinkModContextHost` 将模组生命周期纳入 Context apply。热替换单位是 `ModId + revision`;外部 DLL 用 SHA-256 标识 revision,失败或损坏 revision 不会成为 current,旧组合会恢复。Harmony、Registry 与 Network handler 使用可逆 lease 清理。
|
|
|
|
Mono 中已加载程序集不能真正卸载。系统只回滚组件实例与效应,并通过 `ShrinkModDiagnostics` 暴露 current/history、累计载入字节和软阈值,达到阈值时建议 Domain Reload 或进程重启。
|
|
|
|
## 8. 编译期注册与服务器生成
|
|
|
|
### 8.1 Unity 运行时注册
|
|
|
|
`ShrinkShared.CodeGen/Editor/ShrinkRegistryILPostProcessor.cs` 为引用方程序集生成 EventBus、Command、Network 和 App 注册信息。运行时只消费程序集级注册表;外部 DLL 和运行时对象使用实例注册入口。核心程序集自身不被共享织入器回写,避免程序集自引用。
|
|
|
|
### 8.2 独立服务器生成
|
|
|
|
菜单入口:
|
|
|
|
- `ShrinkSDK/Network/生成完整独立服务器工程`
|
|
- `ShrinkSDK/Network/刷新独立服务器 Generated 合同`
|
|
|
|
完整生成分两步:
|
|
|
|
1. 复制 `ServerProjectTemplate` 到 `GeneratedServers/ShrinkNetwork.ServerHost`。模板清单记录每个托管文件的 SHA-256;复制前完成全量冲突检查,检测到人工修改时零写入退出。
|
|
2. 从 Unity `CompilationPipeline` 的 Player 程序集取得已加载类型,通过 `CustomAttributeData`、接口、基类、属性和泛型元数据生成合同、handler、模块与扫描报告。
|
|
|
|
语义扫描天然合并 partial 类型,并只导出公开实例 `get/set` 属性。复杂泛型递归格式化;命名空间不同但简单名相同的消息或依赖类型会中止生成并列出所有来源。`Generated/*.g.cs` 仍是生成器所有的可替换区域。
|
|
|
|
服务器宿主是 .NET 8 控制台工程,包含 TCP/KCP 服务端、协议窗口、会话令牌、登录/刷新、诊断摘要和 `server.properties`。模板提供运行骨架,业务裁决仍需在非生成代码中实现。
|
|
|
|
## 9. 验证门槛
|
|
|
|
一次涉及包边界、生成器或组合根的变更至少经过:
|
|
|
|
1. 内部版本一致性检查:全部 `com.cneicy.*` 依赖等于仓库包版本。
|
|
2. Unity 编译:目标 asmdef 和根项目无编译错误。
|
|
3. EditMode 测试:功能测试、Context 生命周期、语义扫描和模板覆写保护。
|
|
4. 真实 UPM 消费:`Tools/UpmConsumerValidation/Validate-UpmConsumer.ps1` 创建仓库外形态的临时 Unity 工程,通过 `file:` 安装全部 19 个包,启用 testables,验证包注册、程序集加载并运行 EditMode 测试。
|
|
5. 独立宿主:生成工程与 RuntimeSmoke 按变更范围构建或运行。
|
|
6. 涉及真实生命周期时,仍需在目标场景执行 Play Mode 验收;源码检查和 EditMode 不能替代该路径。
|
|
|
|
## 10. 已知边界
|
|
|
|
- ClassicHost、旧 installer 和静态门面仍是兼容层,暂未删除。
|
|
- Context 的 intercept 不是进程级沙箱;外部程序集 revision 不可从 Mono 卸载。
|
|
- 某些第三方或领域副作用只能补偿,不能保证物理撤回。
|
|
- 服务器生成器输出可编译、可注册的合同和扩展点,不推断完整业务规则。
|
|
- `GeneratedServers/` 中的认证与协议窗口是基础设施,不等于完整账号体系、密钥轮换、审计、trace、dashboard 或故障演练。
|
|
- Profile 的 isolate 变化通过重建条目生效;运行中 fiber 原地迁移 realm 不在当前范围。
|
|
|
|
## 11. 文档治理
|
|
|
|
- 当前架构只更新本文。
|
|
- 包级使用方式和 API 示例放在各包 README。
|
|
- `NETWORK_PITFALLS.md` 记录实现经验,不描述当前模块清单。
|
|
- `Docs/Archive/CORDIS_MIGRATION.completed.md` 保存迁移论证、阶段记录和历史验收数据。
|
|
- `Docs/Archive/codebase-map-2026-05-23/` 保存迁移前代码地图,其中关于 Git 状态、模块规模和测试覆盖的描述均已过期。
|
|
- `.planning/threads/` 只用于恢复工作上下文;其中历史 Notes 不得覆盖本文和当前源码。
|