Skip to content

Architecture

DNTOF edited this page Aug 24, 2026 · 1 revision

架构概览

模块划分

SLDataAPI/
├── Plugin.cs               # 入口:生命周期、事件订阅、服务编排
├── Config.cs               # 配置类(LabAPI 自动加载 config.yml)
├── Log.cs                  # 日志门面(LabAPI Logger 封装)
├── Data/                   # 数据模型与 DTO
├── Control/                # 控制接口业务逻辑 + 鉴权 + WS 协议
├── Services/               # HTTP 服务、数据采集、主线程派发等基础设施
├── Voice/                  # 语音转发 + 录音取证
├── Map/                    # 地图布局采集与导出
├── Integrations/           # EXILED 互操作桥 + 第三方插件探测
└── Capture/                # Harmony 补丁:控制台输出捕获

线程模型

线程 职责 关键约束
Unity 主线程 游戏状态读写、MEC 协程、语音解码、录音入队 所有 Unity/Mirror API 必须在此执行
HTTP 工作线程池 HttpServer 连接处理、控制端点业务、BuildJson 序列化 不碰 Unity 对象;通过 MainThreadExecutor 派发游戏操作
WS 控制读循环 每连接一个工作线程,阻塞读 WS 帧 call 分发到线程池;心跳由协议层 ping 处理
WS 控制线程池 call 的 ControlController.Handle 执行 与 HTTP 共享 MainThreadExecutor
语音写盘线程 录音 WAV 追加写入 主线程只入队;独占本局轨道表

关键设计模式

快照原子替换(M-01)

DataCollector.UpdateData()(主线程)构建完整 ServerData 后一次性赋值 CachedData = fresh。 HTTP 线程 BuildJson() 只读 volatile 引用——消除字段撕裂。

主线程派发 + 取消标志

MainThreadExecutor.RunOnMainThread<T>() 用 MEC Timing.CallDelayed(0f) 派发到主线程, 同步等待结果(默认 5s)。超时后置取消标志,迟到 action 跳过执行——防止重试导致双重执行。

开轨控制消息(S-01)

录音频道的开轨操作作为控制消息与音频帧进同一队列串行处理, 彻底消除共享字典竞态导致的纯静音回归。

数据流

玩家语音包 ──→ VoiceTransceiver.ServerReceiveMessage
                    │ (主线程)
                    ▼
            HandleIncoming()
             ├─ Opus 解码 → float PCM
             ├─ VoiceRecorder.HandlePcm → 入队 → 写盘线程 → 频道轨 WAV
             └─ Clients.Count > 0 ? → PcmPacket 入队 → WS 客户端推送
                    
平台轮询 ──→ GET /get_sl_data (HTTP)
                    │ (HTTP 线程)
                    ▼
            BuildJson() → 读快照 → 核弹实时值(主线程派发) → JSON

平台 WS ──→ ws://host:8081/control (长连接)
                    │ (HTTP 线程 + 线程池)
                    ▼
            call{path,body} → ControlController.Handle(path, body)
                                   │ (主线程派发)
                                   ▼
                              游戏状态读写 → result{status,json}

Clone this wiki locally