Files
IC.Rebuild/diagnostics/2026-10-11-network-manager.md
T

52 lines
5.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 0.1.13:统一收发观察与独立日志订阅
日期:2026 年 10 月 11 日。用户将本轮工作切到收发包管理器。交付 M1 的只读管理器与日志接入;M4 主动发送、编码器、排队和请求响应事务仍未实现。
## 已接入的能力
- `Runtime/NetworkManager.cs` 管理原有两个 Zone Hook,复用 SharedModuleResource。第一个模块订阅启动 Hook,最后一个模块订阅释放 Hook;关闭 36 只释放记录器自己的订阅,其他模块可以独立持有管理器。
- `Core/NativePacketCapture.cs` 继续负责原有原生长度契约和有界复制,本轮未修改签名、ABI、载荷边界、读取时点、版本限制和原函数参数。
- `Core/NetworkPipeline.cs` 提供 Received、BeforeSend、AfterSend 三种只读观察。每个订阅可独立限定 Opcode,并通过 IDisposable 取消;一个订阅抛异常不会阻断后续订阅或改变原函数调用次数与返回值。
- `Core/PacketRecorder.cs` 将消息写入现有 PacketBuffer。36 保留收包和发送入口两类记录,不额外把发送后事件写成第二条上行记录;暂停、方向过滤和 Opcode 过滤仅作用于记录器。
- 网络面板显示订阅数、收包/发送入口/发送返回的观察计数、订阅异常及最近原生发送返回码。日志暂停时观察仍继续,因此暂停不再等于停止网络内存复制;关闭 36 且无其他消费者时才释放 Hook。
## 接口契约
模块在自己的生命周期入口调用,例如:
```csharp
network.Observe(scope, NetworkPhase.Received, observation =>
{
var message = observation.Message;
// message.Payload is ReadOnlySpan<byte>; only copy/process owned data here.
}, opcode: 338);
```
订阅的建立和释放与现有模块启停一样在框架生命周期代码中执行,不能从原生回调中释放 Hook。回调在原生调用线程同步执行,同一消息同一阶段按注册顺序回调,多个原生线程之间不承诺全局顺序。回调不得操作线程绑定游戏/UI 服务、等待 Framework、主动发送或阻塞;耗时任务由消费者基于自持有数据安排。
上行流程:在发送入口复制一次 → BeforeSend → 原函数恰好一次 → AfterSend。后者包含同一个 NetworkMessage 和原生 byte 返回码;发送前捕获的订阅集合用于两个阶段,发送中途新增的订阅从下次调用开始。原函数抛出时不重试、不伪造 AfterSend。原生返回码未解释为服务端确认。
下行流程:原函数先执行一次,返回非零后才读取 holder → Received;返回零时不读消息。原生读取失败仍保留既有元数据降级或错误计数。捕获和订阅异常处理不包裹原函数,避免异常路径重复调用。
每条 NetworkMessage 自持有不可变载荷副本,外部只有只读 Span,不能取得原生指针或底层可写数组;发送前后共享相同观察 ID 和消息实例。消费者可保留消息,不依赖原生发送对象或接收 holder 的生命周期。无对应观察订阅时跳过复制;有 Opcode 限定时在读头后筛选。
取消后不再进入该订阅的后续分发;已经通过活动检查、开始执行的回调可能结束在取消之后,取消不会强行中止它。释放过程中不持有订阅锁调用消费者;一个回调取消其他尚未执行的订阅时,后者会跳过。插件先释放所有模块作用域再关闭分发器。
## 记录兼容与来源
PacketRecord 新增可选 Context,包含观察 ID、NativeZone 来源、RawIpcPayload 阶段、观察阶段与可用的原生返回码。日志时间使用捕获快照的时间,仍保留独立的缓冲区记录序号。旧 JSONL 缺少 Context 时为 null,界面明确显示阶段未知,不把旧数据伪标为转换后状态。
NativeZone 表示采集入口,不保证实际发送者是游戏本体:其他插件经同一原生入口发送的数据也可能出现。当前管理器不主动发送,也未加入来源归因。RawIpcPayload 是排除 IPC 头的原始游戏载荷,不代表状态 ID 等字段已经转换。编号 8 继续使用自己的转换后 SetStatus 入口。
普通日志仍记录 BeforeSend,因此上行记录里的原生返回码为空;发送后的原始返回码通过 AfterSend 订阅和面板提供。下行记录可以附带接收返回码。观察 ID 只在本次插件实例内关联,不是服务器请求 ID,也不能跨重载用于业务匹配。
## 验证
Release 构建通过,0 警告、0 错误;77 项离线测试通过。新增七组检查针对调用顺序/次数、复制寿命、原生返回码、捕获与订阅异常、接收失败时不读取、日志过滤与独立订阅、取消/重复订阅/卸载、共享 Hook 持有者和新记录字段往返。
复用原 capture-018.jsonl:891 条(534 上行、357 下行)经新分发器与记录器回放后,载荷字节、方向、Opcode、版本、目标、完整性和读取问题逐条一致;发送后观察 534 次,订阅错误与读取错误均为 0。回放使用托管原函数代理返回 1,不代表真实原生发送已实测,更没有重发这些样本。报告见 `2026-10-11-capture-018-manager.json`。
原文件解析仍为全部具名、679 条有限字段解析、0 条已支持格式拒绝、8 组 Ping 配对。原文件及 Opcode 缓存未修改。回放按新观察时间生成临时内存记录;原文件的时间和序号只读保留,Ping/RTT 分析仍用原文件。
现场只需一次聚焦检查:重新加载后开启 36,正常移动,确认管理器计数与日志增加;暂停记录时日志停止增加而管理器继续;解除暂停后恢复;关闭 36 后在当前没有其他消费者的情况下订阅数为 0。性能、切区及卸载仍需现场验收,不要求重新提供泛化抓包。