Document the justanyproject comparison, query-command-event boundaries, reversible mod registry rules, package validation gates, and verified EventBus 2.0 state.
12 KiB
justanyproject 代码框架组织审计
审计日期:2026-08-24 源项目:
D:\UnityBuilds\justanyproject源基线:main/b336715,工作树干净,Unity2022.3.62f3证据范围:AGENTS.md、Assets/Scripts、Assets/Tests、asmdef、Packages/manifest.json和相关模块文档。本文是源码与工程结构审计,不代表重新执行过源项目的 Unity 编译、EditMode、PlayMode 或运行时验收。
1. 当前组织概况
源项目的第一方运行时代码主要位于 Assets/Scripts:共 564 个 C# 文件,按 24 个业务目录组织。规模最大的三个目录是 EndlessMode(205)、CombatRuntime(82)和 Bootstrap(41)。
项目共有 12 个第一方 asmdef,其中大部分基础模块已有独立程序集;但 SeedsKey.Game 仍引用 28 个程序集,并承载大量未拆分目录。测试分为 EditMode 与 PlayMode 两个程序集,二者都直接引用多个生产程序集。
当前框架可以概括为:
Entry / AppBootstrapper
|
Bootstrap + AppCompositionRoot
|
全局 Manager / 领域系统 / Scene Composition Root
|
CombatRuntime / EndlessMode / StrategicMap / 其它业务模块
|
ShrinkEventBus / ShrinkDataSaver / ShrinkNetwork / ShrinkModFramework
目录层面的领域划分已经清楚,但编译边界、对象所有权和运行时入口仍处于新旧模式并存状态:显式组合根、窄接口和结构化 facade 已出现,业务单例、静态入口和事件协调仍然大量存在。
2. 值得迁移的经验
2.1 显式 Composition Root
AppCompositionRoot 和 EndlessBattleCompositionRoot 把关键依赖集中在可检查的绑定边界。后者通过序列化字段表达场景依赖,并提供 ValidateRequiredBindings() 汇总缺失项。这比在业务对象内部临时查找单例、场景对象或 prefab 子节点更容易审计。
AppScope、SessionScope、SceneScope 目前只是借用关系和已释放状态的标记:它们会拒绝失效父作用域,但不持有资源集合,也不会级联或反向释放对象。因此可迁移的是“显式作用域边界”,不是现有实现本身。
ShrinkSDK 的承载方式:
- App 级组合继续由
ShrinkAppLoaderBootstrapper、Composition Profile 与ShrinkAppLoaderHost表达。 - Session/Scene/Mod 生命周期继续映射为
ShrinkContextfiber、派生 context 和可逆 effect,不再增加第二套 Scope 容器。 - 场景组件仍应使用显式序列化引用和独立校验结果,不允许运行时静默补建或猜路径。
2.2 命令、查询和事实事件分离
源项目正在把跨模块调用收敛为三类:
- 查询依赖窄的只读接口,例如
ISquadReadOnly、IInventorySlotReadOnly。 - 改变状态的操作经过 command facade,并返回
Succeeded / Failed / Rejected等结构化结果。 - EventBus 只广播已经发生的事实;请求事件仅作为有清理计划的兼容桥。
GameSessionFlowFacade 和 SceneTransitionFlowFacade 还处理了重复请求拒绝、异常转结果以及 finally 恢复运行状态。这些行为使调用方不必从日志或事件顺序猜测执行结果。
ShrinkSDK 的承载方式:
ShrinkCommandExecutionResult、Network RPC 结果、Context 事务诊断继续作为状态变更的明确结果面。- Context key 和 App service 应优先发布消费者真正需要的窄契约;只读消费者不应获得完整可写服务对象。
- EventBus bridge 只传递事实、通知或明确的异步请求合同,不用事件绕开 Command/Network 的返回值、权限和失败语义。
2.3 Owner-aware 内容注册表
ContentRegistry<T> 对内容 id、owner、覆盖优先级和冻结阶段有明确规则:
- 新增内容必须使用 owner namespace。
- 同目标、同优先级覆盖直接报冲突。
- 覆盖目标必须存在。
- 启动完成后冻结,运行阶段只读。
这套规则适合“启动时装载、运行时固定”的游戏内容。ShrinkModFramework 现已把其中可兼容热替换的部分落地:基础项强制使用 <ownerModId>:<localKey>,覆盖目标和优先级显式声明,同目标同优先级直接拒绝,owner 卸载时自动恢复下一个覆盖或基础项。
没有复制冻结模型。ShrinkModFramework 支持 revision 热替换和失败回滚,注册表必须随组件 effect 撤回;全局冻结会与此生命周期冲突。模组通过 RegisterContent / OverrideContent 写入,查询只暴露 IReadOnlyShrinkModRegistry<T>,owner 由当前 ShrinkModContext 决定。
2.4 把架构约束写成门禁
源项目的 EditMode architecture gate 会检查:
- 只读接口没有混入写操作。
- 已迁移调用方没有重新引入 Service Locator。
- command facade 返回结构化结果。
- 事实事件只在状态变更成功后发布。
- 兼容请求事件受 allowlist 约束。
方向正确,但部分测试通过读取 C# 源文本并匹配字符串实现,重命名、格式化或等价重构都可能导致误报。ShrinkSDK 应优先使用编译期类型检查、Cecil/程序集元数据、package.json/asmdef 图检查和真实 UPM 消费测试;只有无法从语义层验证的临时迁移约束才使用源码文本门禁。
2.5 文档按所有权分层
源项目使用总索引说明模块所有权和主流程,再由目录 README 说明模块内部细节,同时把高代价故障沉淀到 KNOWN_PITFALLS.md。这种结构比一份不断膨胀的全局文档更容易维护。
ShrinkSDK 继续保持:
DESIGN.md只描述当前架构和跨包约束。- 包级用法、API 和限制放到各包 README。
- 已完成迁移与旧地图进入
Docs/Archive。 - 经验文档必须区分源码事实、工具验证和真实运行时验证。
3. 不应迁移的部分
3.1 大总程序集
SeedsKey.Game 的 28 个程序集引用和大量目录归属,使很多“模块”只有文件夹边界,没有独立编译边界。它适合渐进式游戏工程重构,不适合作为 SDK 包组织模板。
ShrinkSDK 必须继续以 UPM 包为发布边界、asmdef 为编译边界,主包不得反向依赖集成包。
3.2 业务单例与全局初始化入口
源项目仍有 32 个包含 BaseManager<T> 的文件、10 个运行时自动初始化文件,以及大量静态 Instance/EventBus 调用。它们说明迁移需要兼容面,不说明 SDK 应复制这种全局所有权模型。
ShrinkSDK 的静态入口只能是默认实例 facade;真实生命周期由实例、Context fiber 和 effect 拥有。
3.3 字符串场景名和补救式 Bootstrap
源项目的主流程依赖 Entry -> MainMenu -> ...、字符串场景名和 DevBooter 补齐孤立场景依赖。它们是具体游戏的流程合同,不属于通用 SDK。
SDK 可以提供组合、诊断和校验能力,但不应内置游戏场景名、玩法 ready 顺序或自动修复缺失场景对象。
3.4 游戏内容类型
存档槽、库存、法杖、战斗、房间、无尽模式和 Steam Workshop 的具体状态都应留在游戏层。SDK 只提供生命周期、注册、存储、传输和扩展机制。
4. 对 ShrinkSDK 的实际结论
这次审计没有发现需要从源项目复制的新容器或通用框架。ShrinkSDK 当前在包边界、可逆生命周期、热替换回滚、生成式注册和真实 UPM 消费验证上已经更严格。有效经验应表现为边界规则,而不是移植源项目实现:
- 在组合入口集中显式绑定并一次性报告缺失项。
- 查询依赖窄的只读能力;状态变更使用 command/RPC 并返回结构化结果。
- 事件表达事实,不作为无结果的跨模块命令通道。
- 可扩展注册表必须记录 owner,冲突要显式拒绝,卸载必须可逆。
- 架构规则进入自动化门禁,但优先验证程序集和包语义,避免长期依赖源码字符串测试。
- 文档中的数量、测试结果和运行时状态必须标注快照日期与证据来源。
上述规则已同步进入 DESIGN.md,并完成两项实现:
ShrinkModFramework 0.2.0使用只读查询接口与 owner-bound 写命令,支持 namespace、优先级覆盖、冲突拒绝和卸载恢复。- UPM Consumer Validation 新增内部包循环和主包反向依赖 Integration 包的门禁,并支持
-GraphOnly快速检查。
EventBus 2.0、Context、ModFramework 和 UPM Consumer Validation 继续作为这些规则的主要承载面,没有新增一套并行容器或 Service Locator。
5. 当前验证边界
- 已读取并对照源项目当前 git 基线、目录、asmdef、Composition、Modding 和 architecture gate 源码。
- 已读取并对照 ShrinkSDK 当前 App、Context、Mod registry、包清单与 UPM 消费验证脚本。
- 本次没有修改 Unity 序列化资产,也没有修改源项目
justanyproject。 - ShrinkSDK Unity Editor 已确认
productName=ShrinkSDK,重新编译为 0 error / 0 warning。 ShrinkModFramework.TestsEditMode 为 13/13,通过 namespace、覆盖优先级、冲突、只读 API 边界和真实外部 DLL 热替换回滚。dotnet build ShrinkSDK.sln /m:1为 0 warning / 0 error。- UPM Consumer Validation 的
-GraphOnly通过;仓库外临时 Unity 工程成功加载 20 个包和 42 个 asmdef。本次该轮使用-SkipTests,包内 EditMode 覆盖由上述定向 Test Runner 承担。 - 没有重新运行
justanyproject测试、ShrinkSDK PlayMode、Player Build 或运行时场景;附录 A 的历史结果仍不是本次验证。
附录 A:既有 EventBus 2.0 落地记录
以下内容保留此前审计推动的 EventBus 2.0 设计与验证记录。它描述 ShrinkSDK 当前工作树中的既有改造,不是本次重新执行的结果。
A.1 统一事件模型
ShrinkSDK 使用一个事件协议和多个命名 Bus 实例:
game -> Unity 主线程
scene:id -> Scene/Context 生命周期
mod:id -> 模组专属线程或宿主指定调度
server -> Inline 或专属线程
world:id -> ECS playback 后进入对应 Bus
公共契约为 IShrinkEvent、IShrinkCancelableEvent、IShrinkResultEvent<TResult> 和 IShrinkEventBus.Post/PostAsync/Attach。订阅通过 [ShrinkEventSubscriber]、[ShrinkSubscribe] 声明,实例生命周期由 App、Context、Mod 或 Mono scope 接管。
A.2 调度与生成
ShrinkBusOptions 固定 Bus 创建时的调度策略:
Inline:调用线程直接执行。MainThread:Unity 环境切换到 UniTask PlayerLoop。DedicatedThread:单线程串行状态机。TaskPool:适合并行计算和 IO。
跨线程 Post 表示投递;需要等待完成、取消或结果时使用 PostAsync。队列容量、关闭排空、取消和异常策略由 options 明确配置。
生产注册由 Unity ILPostProcessor 或纯 .NET Roslyn Generator 生成强类型调用,不使用 MethodInfo.Invoke 或 Delegate.DynamicInvoke。没有生成合同的外部 DLL 不自动进入反射扫描。ECS/Burst 通过 ShrinkEcsEventQueue<T> 的 NativeQueue writer/playback 接入,Job 不直接调用托管 delegate 或 UniTask。
A.3 参考边界
NeoForge 的 owner/lifecycle、稳定优先级、取消事件和并行事件约束可用于定义行为;MessagePipe 的专用 broker、稳定 handler 快照和低分配发布路径可用于实现参考。ShrinkSDK 不复制 NeoForge 的双协议模型,也不引入 MessagePipe 的 DI 或第二套 Publisher/Subscriber API。
A.4 历史验证快照
此前工作记录的结果为:Unity EditMode 206/206、EventBus PlayMode 1/1,纯 .NET smoke 覆盖 Inline、DedicatedThread、实例与静态生成绑定;UI Toolkit 调试窗口完成结构读回和截图检查;Windows x64 Development IL2CPP Player 构建成功。
此前记录的 Play Mode benchmark 三轮中位数为:8 handler class 25.32M publish/s、8 handler struct 35.02M publish/s、30 handler 10.82M publish/s,同步 struct Post 为 0 B / 10,000,000 次。真实 Burst Job 性能、其它平台 AOT 和设备侧吞吐仍属于发布前门禁;这些历史数字本次未重新运行确认。