diff --git a/THIRD_PARTY_LICENSES.md b/THIRD_PARTY_LICENSES.md
index 959d6db..cd4977b 100644
--- a/THIRD_PARTY_LICENSES.md
+++ b/THIRD_PARTY_LICENSES.md
@@ -4,7 +4,7 @@ This project uses the following third-party libraries and components:
## MaaFramework
-- **Repository**: https://github.com/Ethernos-Studio/MaaFramework
+- **Repository**: https://github.com/MaaXYZ/MaaFramework
- **Path**: `third_party/maafw/`
- **License**: GNU Lesser General Public License v3 (LGPL-3.0)
- **License File**: `third_party/maafw/LICENSE.md`
@@ -106,7 +106,7 @@ This project links against MaaFramework as a shared library (DLL on Windows).
According to the LGPL-3.0 license:
1. The source code of MaaFramework is available at:
- https://github.com/Ethernos-Studio/MaaFramework
+ https://github.com/MaaXYZ/MaaFramework
2. Users can replace the MaaFramework DLL with a modified version
that is compatible with the LGPL-3.0 license.
@@ -158,7 +158,7 @@ In compliance with GNU licenses (AGPL-3.0, LGPL-3.0, LGPL-2.1+), source code
for the project and its LGPL dependencies can be obtained from:
- **AAM**: https://github.com/Ethernos-Studio/ArknightsAutoMachine
-- **MaaFramework**: https://github.com/Ethernos-Studio/MaaFramework
+- **MaaFramework**: https://github.com/MaaXYZ/MaaFramework
- **FFmpeg**: https://github.com/FFmpeg/FFmpeg
See `docs/licenses/SOURCE_CODE_ACCESS.md` in the distribution for details.
diff --git a/USER_AGREEMENT.md b/USER_AGREEMENT.md
index 03457c4..53554bb 100644
--- a/USER_AGREEMENT.md
+++ b/USER_AGREEMENT.md
@@ -1,4 +1,4 @@
-
+ @brief Arknights Auto Machine (AAM) 综合用户协议与技术边界声明
+ ============================================================================= -->
-# Arknights Auto Machine (AAM) 用户协议
+# Arknights Auto Machine (AAM) 综合用户协议与技术边界声明
-**版本**:v1.0.0
-**生效日期**:2026-04-05
-**版权所有**:Ethernos Studio (dhjs0000)
+**版本**:v2.2.0
+**生效日期**:2026-04-07
+**版权所有**:Ethernos Studio
**开源许可**:GNU Affero General Public License v3.0 (AGPL-3.0-only)
---
-## 1. 接受条款与风险提示
+## 前言:技术中立性与法律合规性的分离声明
-1.1 使用 AAM(以下简称"本软件")即表示您已阅读、理解并同意本协议所有条款。若不同意,请立即卸载并停止使用。
+**重要认知前提**:本软件基于通用计算机视觉技术与自动化控制框架构建,其技术组件(包括但不限于 **YOLO 实时目标检测**、ONNX 运行时、进程间通信、大语言模型接口)在无障碍辅助、自动化测试、工业视觉检测等领域具有合法且广泛的应用价值。然而,技术的中立性并不等同于特定场景下的合规性。
-1.2 **重要风险提示**:
-- 本软件通过 ADB 协议模拟人类操作行为,**必然违反**《明日方舟》用户协议第 4.3 条关于禁止自动化程序的规定
-- 使用本软件可能导致游戏账号受到**警告/临时封禁/永久封禁**等处罚
-- 鹰角网络保留随时更新反作弊机制的权利,本软件的规避检测能力**不具有持久性保证**
+您即将使用的 AAM 软件,尽管在技术实现上采用了**非侵入式架构**(仅通过屏幕捕获与模拟输入交互,不修改游戏内存或网络协议),且使用 **YOLOv8/v9 模型**仅进行**视觉层面的目标检测**(不读取游戏内部对象数据),但其应用于《明日方舟》游戏场景时,**必然构成对《鹰角网络游戏使用许可及服务协议》第 6.2 条第 16 款的实质性违反**。该条款明确禁止"通过非鹰角网络开发的或未经授权的第三方软件、插件、外挂、系统,使用鹰角网络游戏软件及鹰角网络游戏服务"。
-1.3 **AI 决策特别声明**:
-AAM L5 层可能接入第三方大语言模型(LLM)API(包括但不限于 Kimi、Claude 等)。AI 生成的操作指令可能存在**误判、延迟或异常行为**,开发者不对 AI 决策导致的游戏损失承担责任。
+本协议旨在详尽阐述技术架构的边界(包括 **YOLO 模型的输入输出限制**)、第三方依赖的合规义务、用户行为的法律风险,以及开发者的责任限制。通过签署本协议,您确认已充分理解技术实现细节与法律定性之间的分离关系,并自愿承担由此产生的全部风险。
---
-## 2. 知识产权与开源声明
+## 第一章 系统架构与技术边界详述
+
+### 1.1 分层架构的技术解剖与 YOLO 集成
+
+AAM 采用模块化分层设计,各层的功能边界与技术约束如下,**特别标注 YOLO 在视觉感知层的作用与限制**:
+
+**L0 Sensing 层(感知层)**
+本层负责与外部世界的原始交互。在 Android 平台上,它通过 **Android Debug Bridge (ADB) 协议**执行屏幕截图捕获与触控事件注入。技术边界声明:**本层不调用任何 Windows API 读取游戏进程内存(如 `ReadProcessMemory`),不注入 DLL 到游戏进程,不挂钩(Hook)系统或游戏的图形渲染 API(如 DirectX、OpenGL ES 拦截)**。共享内存(Shared Memory, SHM)仅用于 AAM Core (C++) 与 AAM Inference (Python) 进程间的内部数据交换,**绝不与《明日方舟》游戏进程共享内存空间**。
+
+**L1 Perception 层(感知处理层)**
+本层是 AAM 的视觉处理核心,采用**双轨架构**:
+
+- **传统 CV 轨道**:基于 **OpenCV 4.x**(Apache-2.0 许可证)进行模板匹配与 OCR(光学字符识别),用于识别固定 UI 元素(如费用数值、干员头像、按钮区域);
+- **YOLO 深度学习轨道**:基于 **Ultralytics YOLOv8/v9**(AGPL-3.0 许可证,与 AAM 许可证兼容)的 **ONNX 导出模型**,执行**实时目标检测**(Real-time Object Detection)。
+
+**YOLO 技术细节与边界**:
+- **输入**:屏幕截图(由 L0 层通过 ADB 捕获,16-33ms 延迟);
+- **检测类别**:敌方单位(按职业分类:领袖、重装、术师等)、我方干员部署位置( bounding box 坐标)、技能就绪状态图标、特殊机制物体(如装置、障碍物);
+- **模型局限**:**纯视觉识别,不读取游戏内部实体 ID 或属性数值**;依赖 GPU 加速(CUDA/DirectML),在低端设备上可能存在 50-200ms 的推理延迟;存在**误检/漏检风险**(如相似敌人外观混淆、密集重叠单位漏检);
+- **卫戍协议特定**:YOLO 用于识别**随机出现的敌人类型**(如领袖单位、特训敌人)的**视觉特征**(模型通过训练数据识别特定敌人的像素模式),但**无法预测**敌人的波次生成逻辑或领袖技能释放时机。
+
+**L2 Motor 层(执行层)**
+通过 **MaaFramework**(LGPL-3.0 许可证)提供的接口,将 L3/L5 层的决策转换为 ADB 命令序列,模拟人类手指的点击、滑动、长按等操作。操作执行引入随机延迟与坐标扰动("人性化模拟"),但**不构成对游戏客户端的代码注入或协议层伪造**。
+
+**L3 Tactical 层(战术决策层)**
+内置 **DSL(领域特定语言)编译器**,解析基于 **公开数据源**(PRTS Wiki、GamePress、官方攻略等社区维护的静态数据库)的关卡机制、敌人路线与属性数值,进行**预计算战术规划**。
+
+**卫戍协议特定修正**:对于「卫戍协议」模式,L3 层**无法使用静态数据库**(因地图随机选择、领袖随机、盟约组合多变),此时系统**降级为 YOLO 视觉反应模式**:L1-YOLO 层实时检测战场敌情(敌人位置、生命值状态),L3 层基于**视觉反馈**而非**预计算**生成战术指令(如"检测到领袖进入射程则部署干员")。这是一种**反应式(Reactive)**而非**预测式(Predictive)**的战术逻辑,**效率显著低于固定关卡的预计算模式**。
+
+**L4 State 层(状态管理层)**
+维护游戏状态机的快照序列化,使用 **Protocol Buffers**(BSD-3-Clause 许可证)进行数据持久化与进程间通信(通过 **gRPC**,Apache-2.0 许可证)。本层数据**仅反映通过屏幕识别(含 YOLO 检测结果)推断的状态,而非游戏内部的真实内存状态**。
+
+**L5 Strategy 层(战略决策层)**
+通过 **Bridge 层(gRPC / ZeroMQ, MPL-2.0 许可证)**接收 L4 层上传的结构化状态描述(含 YOLO 检测到的敌方单位列表),调用本地或远程的大语言模型(LLM)/视觉语言模型(VLM)API 生成高层战略建议。**在卫戍协议的多人模式下,L5 层仅接收本机阵地状态,无法获取其他玩家(同盟)的完整战场信息,可能导致协同策略失误**。
+
+**Data 层(数据层)**
+存储干员属性、敌人图鉴、固定关卡路线等**公开可用数据**,使用 **nlohmann/json**(MIT 许可证)进行解析。**明确排除**对游戏客户端私有资源包(.ab 文件)的逆向解包或加密协议分析。
+
+### 1.2 第三方依赖的技术栈与合规基础
+
+AAM 的构建与运行依赖以下关键第三方开源组件,其许可证合规性直接影响本软件的分发与使用:
+
+**核心自动化框架:MaaFramework**
+- **许可证**:GNU Lesser General Public License v3 (LGPL-3.0)
+- **交互方式**:动态链接(Shared Library/DLL)
+- **合规要点**:根据 LGPL-3.0 第 4 条,AAM 作为独立作品通过动态链接使用 MaaFramework,**不构成衍生作品**。用户有权获取 MaaFramework 的源代码( https://github.com/MaaXYZ/MaaFramework ),并有权替换 AAM 发行版中附带的 `MaaFramework.dll` 或 `libMaaFramework.so` 为自行修改的兼容版本。AAM 的 AGPL-3.0 许可证**不扩展至** MaaFramework 本身,但修改 MaaFramework 并分发的行为需遵守 LGPL-3.0。
-2.1 本软件著作权归 Ethernos Studio 所有,受《中华人民共和国著作权法》及国际版权公约保护。
+**实时目标检测:Ultralytics YOLOv8/v9**
+- **许可证**:GNU Affero General Public License v3.0 (AGPL-3.0)
+- **集成方式**:AAM 使用 YOLO 的 **ONNX 导出模型**(`yolov8n.onnx` 或定制训练模型)通过 **ONNX Runtime**(MIT 许可证)进行推理,**不直接链接 YOLOv8 的 Python 库或 C++ 源码**。
+- **合规要点**:尽管 AAM 使用 ONNX 模型而非直接集成 YOLO 代码,但 YOLO 模型的训练与导出流程受 AGPL-3.0 约束。AAM 提供的预训练模型(如针对《明日方舟》优化的 `.onnx` 文件)其权重参数**不构成软件源代码**,但如果您修改并重新分发 YOLO 训练代码本身,需遵守 AGPL-3.0。
+- **模型限制声明**:YOLO 模型仅用于**学术研究与无障碍辅助技术验证**,其检测精度受限于训练数据集(仅包含公开游戏截图),**不保证对所有敌人类型(特别是新活动首次出现的敌人)的识别准确率**。
-2.2 本软件以 **AGPL-3.0-only** 开源许可证发布。根据 AGPL:
-- 您有权自由使用、修改、分发本软件
-- **网络交互限制**:若您基于本软件提供在线服务(如云托管、SaaS 平台),必须向所有用户公开服务端源代码
-- **传染性**:任何集成/修改后的衍生作品必须沿用 AGPL 许可证开源
+**媒体处理:FFmpeg**
+- **许可证**:LGPL-2.1+ 或 GPL-2.0+(取决于编译配置)
+- **合规要点**:AAM 通过 vcpkg 引入 FFmpeg,仅使用其解码/编码功能(非 GPL 组件如 x264 可选)。用户有权获取 FFmpeg 源代码( https://github.com/FFmpeg/FFmpeg )并替换二进制文件。
-2.3 **Maa 兼容性声明**:
-AAM 实现了与 MaaAssistantArknights 的接口兼容,但**不包含** Maa 的受版权保护资源文件(如内置识别模型、GUI 素材)。Maa 相关资源受其原 AGPL 许可证约束。
+**基础设施库**:
+- **OpenCV**:Apache-2.0 许可证(专利授权条款)
+- **ONNX Runtime**:MIT 许可证(用于 YOLO 模型推理)
+- **gRPC**:Apache-2.0 许可证(RPC 框架)
+- **ZeroMQ/libzmq**:Mozilla Public License 2.0 (MPL-2.0)(文件级传染性,与 AAM 的 AGPL 兼容)
+- **Protocol Buffers**:BSD-3-Clause 许可证
+- **fmt, spdlog, cxxopts, cppzmq, nlohmann/json**:MIT 许可证(宽松,需保留版权声明)
+- **QuickJS**(MaaFramework 依赖):MIT 许可证
+- **tl-expected**:CC0-1.0(公共领域)
+
+上述依赖的许可证文本与源代码获取方式详见随软件分发的 `docs/licenses/` 目录及 `THIRD_PARTY_LICENSES.md` 文件。
---
-## 3. 禁止行为(绝对禁止清单)
+## 第二章 游戏特定性风险与技术迁移警告
+
+### 2.1 跨游戏适用的风险梯度
+
+**核心风险告知**:AAM 的 L0-L5 架构(含 **YOLO 视觉检测**)在技术上具有**跨游戏通用性**。若用户将此技术栈应用于含有**玩家间实时竞技(PvP)**机制的游戏(如第一人称射击游戏《Valorant》《CS2》、多人在线战术竞技游戏《英雄联盟》《DOTA2》),L1-YOLO 层的实时目标检测(自动瞄准、敌人位置追踪)与 L3 层的战术决策将立即构成**破坏竞技公平性的"人工智能外挂"**(如实时锁定敌方玩家头部坐标)。
+
+然而,AAM 的当前实现针对《明日方舟》的特定技术环境进行了**约束性适配**,其风险等级差异如下:
+
+| 技术维度 | 传统竞技游戏"外挂"(高风险) | AAM 在《明日方舟》中的实现(中低风险) |
+|---------|------------------|-------------------------|
+| **数据输入源** | 内存读取(DMA、Kernel Driver)、网络协议拦截(Man-in-the-Middle)、渲染层注入(Overlay Hook) | **仅通过 ADB 获取屏幕像素流**(YOLO 处理视觉帧),**零内存读取**,**零网络层注入** |
+| **预测/检测基础** | 实时计算敌方玩家位置、技能冷却、经济状态(非公开数据,需破解加密) | **固定关卡**:基于 **已公开的敌人数据库** 预计算;**卫戍协议**:YOLO **实时检测**已出现的敌人视觉特征(仅反应,不预测生成逻辑) |
+| **交互方式** | 内核级输入(驱动级鼠标移动、硬件级宏)、数据包伪造(瞬移、加速) | **仅模拟人类触控操作**(点击、滑动),通过 ADB 协议传输,**不涉及内核驱动或协议伪造** |
+| **联机影响** | 直接破坏 PvP 竞技公平性,影响其他玩家体验 | 游戏核心为**单人/协作 PvE**;「卫戍协议」为**协作对抗 AI**,YOLO 仅辅助识别,**不控制其他玩家阵地**,且 L3 层**无法处理**多人协同的复杂策略(如联防阶段决策) |
+| **经济系统** | 操控交易市场、自动套利、影响其他玩家经济利益 | **仅影响用户自身账号的本地资源消耗**,**不涉及玩家间交易操控** |
+
+### 2.2 《明日方舟》游戏性质的特定性依赖
+
+您明确理解并同意,AAM 当前被界定为"**高级自动化脚本**"而非"**竞技作弊工具**",**严格依赖于**《明日方舟》的以下技术特性:
+
+1. **半单机架构**:核心玩法(主线关卡、资源副本、剿灭作战、集成战略)基于**本地静态数据**运行,服务器仅作结果校验与数据持久化。L3 层的 DSL 编译器可解析**固定关卡配置**(如敌人行进路线、波次 timing、固定机制触发点)。
-使用本软件时,您**不得**:
+2. **卫戍协议的视觉辅助限制**:「卫戍协议」模式虽涉及多人协作与随机元素(随机领袖、随机地图、70秒决策时间限制),但其本质仍为**玩家协作对抗 AI(PvE)**。AAM 对此模式的支持**仅限于**:
+ - **L1-YOLO 实时检测**:识别当前战场上的敌人类型(领袖、特训敌人)与位置;
+ - **L3 反应式战术**:基于 YOLO 检测结果执行简单逻辑(如"费用充足且检测到领袖则部署银灰");
+ - **明确排除**:**无法预测**随机地图布局(因地图在开局后随机选择,非固定数据库可预载);**无法控制**其他玩家的阵地(同盟模式下仅能操作本机干员);**无法处理**"联防阶段"的跨阵地协同(因涉及其他玩家状态,超出 YOLO 视觉范围)。
-3.1 **商业利用**:出售本软件、提供代练服务、或以任何方式通过本软件直接/间接牟利(包括但不限于售卖"防封版"、提供付费挂机服务)
+3. **公开数据依赖**:Data 层使用的干员属性数据库、敌人图鉴、固定关卡路线均来源于**社区公开维护的数据集**(PRTS Wiki、Arknights Terra Wiki、GamePress 等),**非通过逆向工程(Reverse Engineering)或反编译获取的加密资产**。
-3.2 **逆向与破解**:反编译、反汇编、修改本软件二进制以绕过 AGPL 开源要求;开发"内存注入版""协议破解版"等增强模块
+4. **非竞技性(Non-PvP)**:截至本协议生效日,《明日方舟》**不存在**玩家间直接对抗的排位赛、竞技场或经济对抗系统。「卫戍协议」的同盟模式虽有多人元素,但为**协作性质**,且 AAM **不保证**在多人环境下的决策有效性(如因网络延迟导致的 70 秒决策超时、因无法观察友方阵地导致的部署冲突)。
-3.3 **数据滥用**:利用 AAM 的图像识别功能批量采集游戏数据并用于训练商业 AI 模型;将游戏截图(可能含个人信息)上传至未授权的第三方 AI 服务
+### 2.3 游戏机制变更的兼容性警告与风险升级
-3.4 **规避检测的恶意修改**:使用本软件配合硬件宏、虚拟机沙箱、或修改游戏客户端以逃避反作弊检测
+**关键风险提示**:若《明日方舟》在未来版本更新中引入以下机制,AAM 的技术性质可能发生**法律风险升级**,您有义务立即停止使用:
-3.5 **法律责任转嫁**:将本软件用于《刑法》第 285 条禁止的"破坏计算机信息系统"行为(如利用自动化工具实施游戏内诈骗、刷取虚拟货币并变现)
+- **玩家间实时竞技(PvP)模式**:如排位竞技场、玩家对战玩法;
+- **服务器端动态生成关卡**:如完全随机的地图布局、实时变化的敌人属性,使得 L3 层的静态数据库与 YOLO 的训练模型**同时失效**;
+- **玩家间自由交易市场**:如允许玩家间交易虚拟货币或物品;
+- **反作弊机制升级**:鹰角网络明确将"基于计算机视觉的自动化检测"(YOLO 实时识别)或"ADB 模拟触控"纳入行为检测范围,或开始封禁 ADB 调试接口的使用。
+
+在上述情况下,继续使用 AAM 将**从"违反服务协议的自动化脚本"升级为"破坏游戏公平性的作弊工具"**,可能导致**更严厉的账号处罚**(永久封禁且不予申诉),甚至可能涉及**不正当竞争**的法律风险。
---
-## 4. 隐私与数据处理
+## 第三章 许可证合规与开源义务
+
+### 3.1 AGPL-3.0 的刚性传染性与网络服务条款
+
+AAM 以 **GNU Affero General Public License v3.0 (AGPL-3.0-only)** 授权。该许可证在标准 GPL 基础上增加了**网络交互传染性条款**(第 13 条),对您的使用与分发行为施加以下义务:
+
+**源代码可得性**:无论以何种方式获得本软件(源码或二进制),您必须保留完整的源代码获取方式( https://github.com/Ethernos-Studio/ArknightsAutoMachine ),并允许接收者以不高于合理成本的价格获取。
+
+**网络服务传染性**:若您基于 AAM 提供**网络服务**(包括但不限于云托管的自动化服务、SaaS 平台、在线代练接口、远程挂机网页等),您必须向该服务的**所有用户**(包括通过网页界面交互的终端用户)公开**完整的服务端源代码**,无论其是否直接与您建立合同关系。此义务**不因您修改或不修改 AAM 源代码而免除**。
+
+**许可证不可变更**:任何包含或衍生自 AAM 的作品(包括但不限于插件、扩展、封装器、图形界面前端、自定义 YOLO 训练脚本)必须继续沿用 AGPL-3.0(或更高版本)许可证,**不得添加额外限制条款**(如禁止商业使用、禁止特定领域使用等),**亦不得降级为更宽松的许可证**(如 MIT、Apache-2.0、BSD 等)。
+
+### 3.2 第三方依赖的合规链条
+
+**MaaFramework (LGPL-3.0) 的动态链接合规**:
+AAM 通过动态链接(Windows 下为 `MaaFramework.dll`,Linux 下为 `libMaaFramework.so`)使用 MaaFramework。根据 LGPL-3.0 第 4(d) 条,您有权:
+1. 获取 MaaFramework 的完整源代码( https://github.com/MaaXYZ/MaaFramework );
+2. 使用修改后的兼容版本替换 AAM 发行版附带的 MaaFramework 二进制文件;
+3. 要求任何分发修改版 MaaFramework 的主体遵守 LGPL-3.0(但 AAM 本身不因此成为 LGPL 作品)。
+
+**Ultralytics YOLO (AGPL-3.0) 的模型合规**:
+AAM 分发预训练的 YOLO 模型文件(`.onnx` 格式)用于敌方单位检测。根据 AGPL-3.0:
+1. 若您修改 YOLO 的训练代码(如添加新的数据增强算法)并分发修改后的代码,需开源;
+2. 仅使用预训练模型进行推理(如 AAM 的做法)**不触发** AGPL 的源代码公开义务,但模型的训练脚本(如数据标注脚本、训练配置)若集成到 AAM 仓库中,需随 AAM 一同 AGPL 开源。
+
+**MIT/Apache/BSD 许可证的版权保留**:
+对于 OpenCV、ONNX Runtime、fmt、spdlog、gRPC、nlohmann/json 等宽松许可证依赖,您必须保留其原始版权声明与许可文本,**不得移除或篡改** `third_party/` 目录下的许可证文件。
+
+**ZeroMQ (MPL-2.0) 的文件级隔离**:
+ZeroMQ 采用文件级弱传染性许可证。AAM 通过独立文件(`cppzmq` 绑定)使用 ZeroMQ,**不构成与 AAM 的合并作品**,但修改 ZeroMQ 源代码文件本身需遵守 MPL-2.0。
+
+### 3.3 商业利用的禁止与 AGPL 的兼容性
+
+尽管 AGPL-3.0 允许商业使用,但**本协议第 6 条(禁止行为)明确禁止**您通过 AAM 提供付费代练、售卖"防封版"或构建闭源 SaaS 平台。
+**重要提示**:若您违反第 6 条进行商业牟利,您同时违反了 AGPL-3.0 的"相同方式共享"条款(因为您未向服务用户公开源代码),此时 **Ethernos Studio 有权追究您侵犯著作权的责任**,同时您也失去了 AGPL 授予您的技术使用许可。
+
+---
+
+## 第四章 隐私保护、数据流与 AI 决策风险
+
+### 4.1 本地优先的数据处理架构与 YOLO 隐私边界
+
+AAM 默认采用**端侧计算优先**架构,数据流如下:
+
+**L0-L4 层(C++ Core)**:
+- 屏幕截图(通过 ADB)仅在本地内存中处理,**不上传**至 Ethernos Studio 服务器;
+- **YOLO 推理**:在本地 GPU(NVIDIA CUDA 或 DirectML)上执行,**原始图像帧不离开本机**;
+- OCR 识别结果、YOLO 检测框坐标(bounding boxes)、游戏状态推断数据通过 **ZeroMQ (MPL-2.0)** 或 **gRPC (Apache-2.0)** 在本地进程间(Core ↔ Inference)传输,**不经过互联网**;
+- 操作日志(含 YOLO 检测结果摘要,如"检测到领袖:爱国者")仅本地保存,默认 **7 日后自动不可逆清除**。
+
+**L5 层(Python Inference)**:
+- 仅在您**主动启用** AI 决策功能时,结构化状态描述(含 YOLO 检测到的敌方列表、干员部署状态、费用数值,**不含原始屏幕截图**)通过 **HTTPS/TLS 1.3** 传输至您配置的第三方 LLM API(如 Moonshot AI、Anthropic Claude、OpenAI GPT 等);
+- **强烈建议**在配置文件中启用 `privacy_mode: strict`,该模式将自动模糊处理用户 ID、充值记录、社交关系图谱等敏感元数据,**仅传输最小必要信息**(如仅发送"领袖已出现"而非具体坐标)。
+
+### 4.2 第三方 AI 服务的隐私边界
+
+若您启用 L5 层:
+- 您需**自行审阅**所配置 API 提供商的隐私政策(如 Moonshot AI 的《用户协议》与《隐私政策》),Ethernos Studio **不对**第三方服务的数据泄露、模型训练数据滥用或跨境数据传输合规性承担责任;
+- 建议优先使用**本地部署**的开源大模型(如 Llama 3、Qwen 2.5)以避免云端数据暴露;
+- 明确禁止将包含其他玩家个人可识别信息(PII)的游戏截图(如多人模式中的玩家昵称、UID)传输至第三方 AI 服务。
+
+### 4.3 AI 决策与 YOLO 检测的局限性与风险披露
+
+**L1-YOLO 检测的固有局限**:
+- **误检/漏检**:YOLO 模型基于固定训练数据集(历史活动截图),对于**首次出现的新敌人**(如新一期「卫戍协议」的新领袖)可能无法识别或错误分类(如将"萨卡兹百夫长"误检为"萨卡兹刀兵");
+- **遮挡与重叠**:在敌人密集或特效遮挡(如全屏技能动画)时,YOLO 可能漏检关键单位,导致 L3 层战术失效;
+- **分辨率依赖**:YOLO 模型对特定屏幕分辨率(如 1920x1080、2560x1440)训练,若您使用非标分辨率(如 21:9 超宽屏),检测精度可能下降;
+- **卫戍协议特定**:70 秒决策时间限制下的**实时推理压力**(若 GPU 性能不足,YOLO 推理可能占用 1-2 秒,压缩后续决策时间);**多人模式下仅检测本机阵地**,无法观察友方部署导致的**协同失误**(如重复部署同类型干员)。
+
+**L3 层战术局限(卫戍协议特定)**:
+- **反应式延迟**:对于「卫戍协议」,L3 层基于 YOLO 检测结果采取**事后反应**(敌人出现后才响应),而非固定关卡的**事前预部署**,**胜率显著低于人工操作**;
+- **无法处理随机盟约**:L3 层无法理解复杂的"盟约"组合效果(如特定阵营增益),可能做出次优决策;
+- **无法处理联防阶段**:当进入"联防阶段"(合并失败玩家剩余敌人)时,AAM **无法识别**友方阵地的干员配置(因仅捕获本机屏幕),可能导致部署冲突或资源浪费。
+
+**L5 层 LLM/VLM 决策的固有局限**:
+- **幻觉(Hallucination)风险**:大语言模型可能错误解读 YOLO 检测结果(如将"领袖已进场"误判为"领袖已死亡"),生成完全错误的部署指令;
+- **上下文限制**:LLM 可能无法处理「卫戍协议」的长局记忆(如 20+ 回合的累积经济状态),导致后期决策失误。
+
+**开发者明确声明**:**不对因 YOLO 误检、L3 反应式战术局限、或 L5 层 AI 决策失误导致的游戏内损失承担责任**,包括但不限于错误部署高价值干员、误消耗珍稀道具、关卡失败导致的理智值损失或活动奖励遗漏。
+
+---
+
+## 第五章 责任限制与广泛范围免责声明
+
+### 5.1 无担保的"现状"提供(AS IS)
+
+**在适用法律允许的最大范围内**,本软件按"**现状**"(AS IS)及"**可用即提供**"(AS AVAILABLE)的基础提供,不附有任何形式的明示、默示或法定担保。Ethernos Studio 明确否定以下担保:
+
+- **适销性默示担保**:不保证本软件适合任何特定商业用途或通用目的,不保证其与特定操作系统版本(如未来版本的 Android 或 iOS)或硬件配置(特定分辨率、DPI 设置、GPU 型号)的兼容性;
+- **特定用途适合性默示担保**:不保证本软件满足您的特定游戏策略、胜率预期或效率指标,**不保证其能够完成任何特定关卡(特别是「卫戍协议」等随机性强的模式)的通关**;
+- **非侵权担保**:不保证使用本软件不会侵犯鹰角网络、Hypergryph、Ultralytics 或任何第三方的知识产权、合同权利或其他合法权益;
+- **技术缺陷担保**:明确告知本软件可能存在未发现的软件缺陷(Bug)、YOLO 模型误检、逻辑漏洞、内存泄漏或与特定反病毒软件的误报冲突。
+
+### 5.2 损失免责的广泛范围
+
+**在适用法律允许的最大范围内,且无论 Ethernos Studio 是否已被告知可能发生该等损失**,Ethernos Studio 及其创始人 dhjs0000 在任何情况下均不对以下损失承担责任:
+
+**直接损失**:
+- **账号与虚拟财产损失**:游戏账号被鹰角网络封禁(临时或永久)、信誉分降低、虚拟道具被回收、合成玉/源石/龙门币等虚拟货币被清零、游戏进度被回滚或删除;
+- **硬件与数据损失**:因运行 AAM(特别是 YOLO 的 GPU 高强度计算)导致的移动设备过热、电池损耗加速、显卡(若通过模拟器运行)损坏、操作系统崩溃、个人数据丢失或需要重装系统;
+- **AI 与视觉检测级联失误**:因依赖 L1-YOLO 误检(如漏检领袖单位)、L3 反应式战术局限(卫戍协议中的随机性应对失败)、L5 层 AI 误判导致的干员部署错误、费用管理失误、技能开启时机错误、关卡失败、活动奖励遗漏或游戏内经济系统损失。
+
+**间接与衍生损失**:
+- **机会成本损失**:因账号封禁导致的游戏内活动参与权丧失、排名奖励无法获取;
+- **数据恢复成本**:为恢复被封禁账号或丢失数据产生的律师费、申诉代理费、时间成本;
+- **替代软件采购成本**:因 AAM 失效(如 YOLO 模型过旧无法识别新敌人)而需购买的其他自动化工具费用;
+- **情感与机会成本**:因使用本软件产生的焦虑、失望、时间浪费或游戏体验降低。
-4.1 **本地优先原则**:
-AAM 默认在本地设备运行所有图像识别与逻辑推理(L0-L4 层)。游戏画面截图**不会**上传至 Ethernos Studio 服务器。
+**第三方行为后果**:
+- 因鹰角网络政策变更、反作弊系统升级(如开始检测 ADB 或视觉自动化行为)、游戏版本更新(如 Unity 引擎升级导致图像渲染变化使 YOLO 模型失效)导致的软件功能失效;
+- 因第三方 AI 服务(Kimi、Claude 等)中断、API 密钥失效、模型版本更新(如模型能力下降)导致的 L5 层决策质量降低。
-4.2 **LLM 服务数据流**:
-若您启用 L5 层 AI 决策功能:
-- 游戏画面截图/状态描述将发送至您配置的第三方 AI API(如 Kimi Code)
-- 您需自行审阅该 API 提供商的隐私政策,Ethernos Studio 不对第三方数据泄露负责
-- 建议在配置文件中启用 `privacy_mode: strict`,限制截图中的敏感信息(如 UID、充值记录)传输
+### 5.3 责任上限的刚性约束
-4.3 **数据留存**:
-AAM 仅在本地保存操作日志(用于调试),不会上传用户游戏数据。日志文件默认保存 7 天,到期自动清理。
+若适用法律强制要求 Ethernos Studio 承担赔偿责任(且本协议第 5.2 条的免责条款被有权机关认定为部分或全部无效),赔偿金额**在任何情况下均不超过**以下两者中的较高者:(1)您为获得本软件实际支付的金额(鉴于 AGPL 开源性质,该金额通常为 **0 元人民币**);(2)**人民币 50 元整**。
+
+### 5.4 用户自担风险的确认
+
+您明确确认并同意:**使用本软件参与《明日方舟》游戏的所有后果均由您独立承担**。您与鹰角网络之间的"猫鼠博弈"(即自动化操作与反作弊检测之间的技术对抗)风险**完全由您承担**。您不会因任何负面结果(包括但不限于账号封禁、虚拟财产损失、YOLO 误检导致的战术失败)向 Ethernos Studio 提出任何形式的索赔、诉讼、仲裁或投诉。
---
-## 5. 责任限制与免责声明
+## 第六章 禁止行为清单(零宽容政策)
+
+任何违反本条的行为将导致本协议**立即自动终止**,并可能触发 AGPL-3.0 许可证下的著作权侵权追责:
+
+**6.1 商业牟利行为**
+严禁出售 AAM 副本、提供付费代练/挂机服务、构建基于 AAM 的付费 SaaS 平台而不履行 AGPL 开源义务;严禁售卖所谓"防封版""内存注入版""协议破解版"或"YOLO 增强付费版"。
-5.1 **无担保声明**:
-本软件按"**现状**"(AS IS)提供,不附有任何明示或默示担保,包括但不限于:
-- 对游戏账号安全的担保
-- 对反作弊规避持久性的担保
-- 对 AI 决策准确性的担保
+**6.2 规避 AGPL 传染性的技术规避**
+通过动态链接之外的机制(如静态链接、内联汇编、代码混淆)将 AAM 或 YOLO 集成到闭源商业软件中,以规避 AGPL-3.0 的网络服务传染性要求。
-5.2 **损失免责**:
-在任何情况下,Ethernos Studio 及开发者 dhjs0000 均不对以下损失承担责任:
-- 游戏账号被封禁导致的虚拟财产损失
-- 因使用本软件导致的设备损坏、数据丢失
-- 依赖 AI 决策导致的游戏内资源损失(如错误部署干员导致关卡失败)
+**6.3 游戏机制恶意利用**
+利用 AAM 的自动化能力实施《中华人民共和国刑法》第 285 条、第 286 条禁止的"破坏计算机信息系统"行为,包括但不限于:
+- 利用自动化工具实施游戏内诈骗;
+- 非法刷取虚拟货币并变现(RMT,Real Money Trading);
+- 干扰游戏服务器正常运行(如通过高频 ADB 命令或 YOLO 高频截图造成服务器负载异常)。
-5.3 **责任上限**:
-若适用法律要求承担赔偿责任,赔偿金额**不超过**您为本软件实际支付的费用(鉴于 AGPL 开源性质,通常为 0 元)。
+**6.4 数据滥用与 AI 训练**
+利用 AAM 的图像采集功能(包括 YOLO 检测数据集)批量提取游戏数据并用于训练**商业闭源 AI 模型**;将包含用户 PII 的游戏截图传输至未获明确授权的第三方 AI 服务。
+
+**6.5 恶性反检测协同**
+将 AAM 与硬件宏脚本、内核级驱动(Kernel Driver)、虚拟机沙箱逃逸技术或修改后的游戏客户端协同使用,以逃避反作弊检测。
+
+**6.6 YOLO 模型的恶意修改与分发**
+训练包含恶意后门的 YOLO 模型(如故意误检特定敌人以诱导失败)并通过 AAM 社区分发;使用未经授权的第三方商业 YOLO 模型(可能涉及版权侵权)集成到 AAM 中。
---
-## 6. 协议更新与终止
+## 第七章 协议更新、终止与争议解决
-6.1 **更新权**:Ethernos Studio 保留随时修改本协议的权利。重大变更将在 GitHub Releases 页面公示 7 日,继续使用视为接受新条款。
+### 7.1 协议的动态更新权
+Ethernos Studio 保留在不事先通知的情况下单方面修改本协议的权利。若《明日方舟》服务条款更新(特别是反作弊政策收紧,如明确禁止视觉自动化)、游戏机制变更(如引入 PvP),或 YOLO 模型许可证(Ultralytics 政策变更),本协议将相应更新以反映**风险等级变化**,并在 GitHub Releases 页面及项目仓库公示不少于 **7 个自然日**。
-6.2 **自动终止**:
-若您违反第 3 条禁止行为,本协议自动终止,AGPL 赋予的开源权利**不受影响**(您仍可保留、修改代码,但失去获得技术支持的资格)。
+### 7.2 自动终止条件
+若您:
+(1)违反第 6 条任何禁止行为;
+(2)将 AAM 技术栈(含 YOLO 视觉检测)迁移至含 PvP 元素的其他游戏;
+(3)在《明日方舟》引入 PvP 模式后继续使用 AAM;
+本协议将在该等行为发生之时**自动终止**,无需另行通知。**协议终止不影响 AGPL-3.0 授予您的开源权利**(保留、修改、分发源代码的权利),但您永久失去获得技术支持与社区协助的资格。
-6.3 **用户终止权**:您可随时停止使用并卸载本软件, uninstall 行为即视为协议终止。
+### 7.3 法律适用
+本协议适用**中华人民共和国大陆地区法律**(不含冲突法规则)。
---
-## 7. 争议解决与适用法律
+## 第八章 最终确认条款(GUI 界面专用)
-7.1 本协议适用中华人民共和国法律(不含冲突法规则)。
+**强制性确认按钮文案**(用户必须滚动阅读全文后方可点击):
+
+> **"我已完整阅读、充分理解并接受《Arknights Auto Machine 综合用户协议》(v2.1.0-YOLO-Enhanced)的全部条款,包括:**
+>
+> **(1)技术架构理解**:我确认理解 AAM 的 L0-L5 分层架构,特别是 **L1-YOLO 层** 使用 Ultralytics YOLO 模型进行实时目标检测(识别敌人/干员位置)的技术边界与误检风险;我理解对于「卫戍协议」模式,YOLO 仅提供**反应式视觉辅助**(检测已出现的敌人),**无法预测**随机地图布局或处理多人协同策略;
+>
+> **(2)许可证合规义务**:我承诺遵守 AGPL-3.0 许可证(网络服务传染性),以及 MaaFramework (LGPL-3.0)、Ultralytics YOLO (AGPL-3.0)、ONNX Runtime (MIT) 等第三方依赖的合规要求;
+>
+> **(3)法律风险认知**:我明确知晓使用 AAM **必然违反**《鹰角网络游戏使用许可及服务协议》第 6.2 条第 16 款(禁止未经授权的第三方软件),可能导致账号从警告到永久封禁的处罚,并理解该工具若用于含 PvP 的游戏将立即构成"AI 外挂";
+>
+> **(4)YOLO 与 AI 风险自担**:我自愿承担 YOLO 模型误检(如漏检领袖单位、敌人分类错误)、卫戍协议 70 秒决策超时、以及 L5 层 AI 决策失误的全部风险,不会因任何损失向 Ethernos Studio 或 dhjs0000 提出索赔;
+>
+> **(5)停止使用承诺**:我承诺在《明日方舟》引入 PvP 模式或明确将视觉自动化(YOLO/ADB)纳入反作弊检测范围时,立即永久停止使用本软件。"**
---
-## 8. 其他条款
+## 第九章 扩展免责声明与风险隔离条款
+
+### 9.1 第三方平台与分发渠道免责
+
+**GitHub/Microsoft 平台免责**:
+AAM 源代码托管于 GitHub 平台(服务提供商:Microsoft Corporation)。您明确理解并同意:
+- Ethernos Studio **无法控制** GitHub 平台的服务可用性、内容审核政策或账号封禁行为;
+- 若 GitHub 因任何原因(包括但不限于鹰角网络或其关联方的投诉)删除 AAM 仓库、限制访问或封禁开发者账号,Ethernos Studio **不承担**因此导致的源代码获取权丧失、协作中断或社区支持终止的责任;
+- 您应在本地保留 AAM 源代码副本,并关注 GitHub Releases 页面的镜像备份信息。
+
+**包管理器与构建工具免责**:
+AAM 依赖 vcpkg(Microsoft)、pip(Python Software Foundation)、Poetry 等第三方包管理器获取依赖库。Ethernos Studio **不对**以下情况承担责任:
+- 包管理器服务器不可用、依赖版本冲突或供应链攻击(如恶意包注入);
+- vcpkg 端口定义变更导致的构建失败;
+- 依赖库(如 OpenCV、FFmpeg)发布的安全补丁与 AAM 代码的兼容性问题。
+
+**应用商店与分发平台免责**:
+若 AAM 的二进制分发版本通过任何第三方平台(如网盘、论坛、社交媒体)传播,Ethernos Studio **不保证**该等分发版本的完整性、安全性或未被篡改。建议仅从官方 GitHub Releases 页面获取经过 GPG 签名的发布包。
+
+### 9.2 社区贡献与第三方代码免责
+
+**Pull Request 与社区贡献**:
+AAM 接受社区贡献(Pull Request、Issue 报告、代码补丁)。您明确同意:
+- 社区贡献者(非 Ethernos Studio 核心维护者)提交的代码**未经**同等严格的审计与安全审查;
+- 合并至主分支的社区贡献代码若存在安全漏洞、性能缺陷或法律风险(如意外引入 GPL 不兼容的依赖),Ethernos Studio **在发现后会尽力修复,但不承担因此导致的损失**;
+- 您使用包含社区贡献代码的 AAM 版本时,应自行评估风险。
+
+**第三方插件与扩展**:
+AAM 的架构设计允许第三方开发插件(如自定义 YOLO 模型、L5 层策略模块)。Ethernos Studio **不对**以下情况承担责任:
+- 第三方插件的安全漏洞、恶意代码或数据窃取行为;
+- 第三方插件违反 AGPL-3.0 许可证导致的法律风险;
+- 第三方插件与 AAM 核心版本的兼容性问题。
+
+**Issue 与讨论区的信息风险**:
+GitHub Issues、Discussions 及社区聊天群组(QQ、Discord 等)中的信息(包括使用教程、配置文件示例、YOLO 模型下载链接)由社区成员发布,**未经 Ethernos Studio 审核**。您应自行判断该等信息的准确性与安全性,**不应**将未知来源的配置文件或模型文件直接应用于生产环境。
+
+### 9.3 技术演进与废弃声明
+
+**技术栈变更风险**:
+AAM 的技术栈(特别是 L1-YOLO 层)可能随计算机视觉领域的发展而演进(如从 YOLOv8 迁移至 YOLOv10、引入 Transformer-based 检测器)。您理解并同意:
+- 旧版本 YOLO 模型可能因技术迭代而被标记为废弃(Deprecated),不再接收训练数据更新;
+- 技术栈重大变更可能导致配置文件格式不兼容,您需要手动迁移配置;
+- Ethernos Studio **无义务**无限期维护旧版本的技术支持。
+
+**游戏引擎升级导致的失效**:
+若《明日方舟》升级游戏引擎、更换渲染管线或修改 UI 设计风格,可能导致:
+- YOLO 训练数据集失效,检测精度急剧下降;
+- ADB 截图协议变更或屏幕坐标系变化;
+- L3 层基于固定坐标的战术模板全部失效。
+Ethernos Studio **不保证**能够快速适配游戏重大更新,**不承诺**在特定时间内恢复软件功能。
+
+**维护者变更与项目终止**:
+Ethernos Studio 保留在任何时候以任何理由(包括但不限于开发者精力有限、法律风险增加、失去对《明日方舟》游戏的兴趣)**永久终止 AAM 项目**的权利。项目终止可能表现为:
+- 仓库归档(Archive),不再接受新功能开发;
+- 停止响应 Issues 与 Pull Requests;
+- 删除或隐藏历史发布版本。
+项目终止后,AGPL-3.0 许可证仍然有效,社区可继续 fork 维护,但 Ethernos Studio **不再承担任何维护义务**。
+
+### 9.4 跨境法律与出口管制合规
+
+**出口管制合规声明**:
+AAM 包含可能受出口管制法规约束的技术组件(如加密算法、GPU 加速库)。您确认:
+- 若您位于或受制裁国家/地区(如美国 OFAC 制裁清单),使用 AAM 可能违反当地法律;
+- 您不会将 AAM 源代码或二进制文件传输至受制裁实体或个人;
+- Ethernos Studio **不对**因出口管制法规导致的软件使用限制承担责任。
+
+**跨境数据传输**:
+若您启用 L5 层 AI 决策功能并配置非中国大陆地区的 LLM API(如 OpenAI、Anthropic),游戏状态数据将跨境传输至境外服务器。您应自行确保该等数据传输符合《中华人民共和国数据安全法》《个人信息保护法》等法规要求,Ethernos Studio **不提供**合规性保证。
+
+**司法管辖区冲突**:
+若您位于中华人民共和国大陆地区之外,使用 AAM 可能同时受当地法律(如欧盟 GDPR、美国 DMCA)约束。Ethernos Studio **不保证** AAM 符合所有司法管辖区的法律要求,您应自行评估当地法律风险。
+
+### 9.5 不可抗力与外部事件
+
+**不可抗力事件**:
+Ethernos Studio **不对**因不可抗力事件导致的软件无法使用或损失承担责任,包括但不限于:
+- 自然灾害(地震、洪水、火灾)导致的数据中心损毁;
+- 战争、武装冲突、恐怖袭击或社会动乱;
+- 政府行为、法律法规变更或行政命令(如游戏行业监管政策收紧);
+- 大规模网络攻击(DDoS、勒索软件)导致的服务中断;
+- 全球流行病或公共卫生事件导致的开发停滞。
+
+**游戏运营商行为**:
+您明确理解并同意:**鹰角网络作为游戏运营商,拥有绝对的技术与合同权力封禁使用 AAM 的账号**。Ethernos Studio **无法**也**不会**尝试通过法律手段干预鹰角网络的账号管理决策。若您的账号被封禁:
+- 您**无权**要求 Ethernos Studio 提供法律援助或申诉代理;
+- 您**不应**以 Ethernos Studio 为被告提起任何诉讼;
+- 您与鹰角网络之间的争议**完全独立于**本协议。
+
+**反作弊技术升级**:
+若鹰角网络引入新的反作弊技术(如基于 AI 的行为检测、硬件指纹识别、内核级驱动保护),导致 AAM 被检测或设备被标记,Ethernos Studio **不保证**能够开发规避措施,**不承担**因此导致的设备封禁或硬件黑名单损失。
+
+### 9.6 集体诉讼与代表诉讼放弃
+
+**集体诉讼放弃(Class Action Waiver)**:
+在适用法律允许的最大范围内,您同意**仅以个人名义**对 Ethernos Studio 提出索赔,不得作为原告或集体成员参与任何集体诉讼、代表诉讼、集团仲裁或私人检察长诉讼。您明确放弃任何通过集体诉讼获得救济的权利。
+
+**争议解决前置程序**:
+若您与 Ethernos Studio 发生争议,您同意在提起正式诉讼或仲裁前,首先通过以下方式尝试解决:
+1. 通过 GitHub Issues 提交书面投诉,描述争议事实与诉求;
+2. 给予 Ethernos Studio 不少于 30 个自然日的回应与协商期;
+3. 若协商未果,争议应提交**长沙仲裁委员会**按照其届时有效的仲裁规则进行仲裁,仲裁地点为**湖南省长沙市**,仲裁语言为**中文**。
+
+**仲裁裁决的终局性**:
+您同意仲裁裁决是终局的,对双方均有约束力,可在任何有管辖权的法院申请强制执行。
+
+### 9.7 可分割性条款
-8.1 **独立性**:若本协议某条款被有权机关认定为无效,不影响其他条款效力。
+若本协议任何条款被有管辖权的法院或仲裁机构认定为无效、不可执行或违法:
+- 该条款应在**最小必要范围内**被修改以使其有效且可执行,或若无法修改则予以删除;
+- 该条款的无效性**不影响**本协议其他条款的有效性与可执行性;
+- 双方应本着诚信原则协商替代条款,以尽可能接近原条款的经济与法律效果。
-8.2 **非代理关系**:本协议不构成您与 Ethernos Studio 之间的合伙、合资、雇佣或代理关系。
+### 9.8 完整协议条款
-8.3 **联系信息**:
-- 项目仓库:https://github.com/Ethernos-Studio/ArknightsAutoMachine
-- 官方邮箱:support@ethernos.net
-- 技术交流群:1070963958(QQ)
+本协议(包括所有引用的第三方许可证文件)构成您与 Ethernos Studio 之间关于 AAM 软件的**完整协议**,取代双方此前或同时就本协议主题达成的所有口头或书面谅解、协议、陈述或保证。
+
+您确认:在点击"接受"按钮或使用 AAM 软件之前,您已获得充分的机会阅读、理解并咨询法律顾问关于本协议全部条款的意见。您**不因**以下理由主张本协议无效或可撤销:
+- 未阅读或理解协议内容;
+- 未获得独立的法律建议;
+- 协议条款不公平或不合理。
+
+### 9.9 权利放弃与不弃权声明
+
+Ethernos Studio **未行使**或**延迟行使**本协议项下的任何权利、权力或救济,**不构成**对该等权利、权力或救济的放弃,也不妨碍日后行使该等权利、权力或救济。
+
+单次或部分行使任何权利、权力或救济**不妨碍**进一步行使该等或任何其他权利、权力或救济。
+
+Ethernos Studio 对您违反本协议行为的弃权**不构成**对后续相同或类似违约行为的弃权。
+
+### 9.10 转让限制
+
+未经 Ethernos Studio 事先书面同意,您不得转让、转移或分包本协议项下的任何权利或义务。任何未经同意的转让均为无效。
+
+Ethernos Studio 可**无需您同意**即转让本协议项下的全部或部分权利义务给关联公司或在合并、收购或资产出售中转让给继任者。
+
+### 9.11 关系界定
+
+本协议**不构成**您与 Ethernos Studio 之间建立以下关系:
+- 合伙或合资企业;
+- 雇佣或劳动关系;
+- 特许经营关系;
+- 代理或委托关系。
+
+双方均为独立缔约方,本协议**不产生**任何一方代表另一方行事的权利或授权。
+
+### 9.12 语言与解释
+
+**语言优先性**:
+本协议以**简体中文**起草。若本协议被翻译为其他语言,简体中文版本与翻译版本存在冲突时,**以简体中文版本为准**。
+
+**标题与解释**:
+本协议中的标题仅为方便阅读而设,**不影响**本协议的解释。本协议中的"包括"、"包含"等词语应理解为"包括但不限于"。
+
+---
+
+## 第十章 最终用户确认
+
+**强制性确认按钮文案**(用户必须滚动阅读全文后方可点击):
+
+**"我已完整阅读、充分理解并接受《Arknights Auto Machine 综合用户协议》(v2.1.0-YOLO-Enhanced)的全部条款,包括:**
+
+ - [] **(1)技术架构理解**:我确认理解 AAM 的 L0-L5 分层架构,特别是 **L1-YOLO 层** 使用 Ultralytics YOLO 模型进行实时目标检测的技术边界与误检风险;我理解对于「卫戍协议」模式,YOLO 仅提供**反应式视觉辅助**,**无法预测**随机地图布局或处理多人协同策略;
+
+ - [] **(2)许可证合规义务**:我承诺遵守 AGPL-3.0 许可证(网络服务传染性),以及 MaaFramework (LGPL-3.0)、Ultralytics YOLO (AGPL-3.0)、ONNX Runtime (MIT) 等第三方依赖的合规要求;我理解若基于 AAM 提供网络服务,必须向所有用户公开服务端源代码;
+
+ - [] **(3)法律风险认知**:我明确知晓使用 AAM **必然违反**《鹰角网络游戏使用许可及服务协议》第 6.2 条第 16 款,可能导致账号从警告到永久封禁的处罚;我理解该工具若用于含 PvP 的游戏将立即构成"AI 外挂";我承诺在《明日方舟》引入 PvP 模式或明确将视觉自动化纳入反作弊检测范围时,立即永久停止使用本软件;
+
+ - [] **(4)YOLO 与 AI 风险自担**:我自愿承担 YOLO 模型误检(如漏检领袖单位、敌人分类错误)、卫戍协议 70 秒决策超时、L5 层 AI 决策失误的全部风险;我理解 AAM **不保证**通关任何特定关卡,**不保证**胜率高于人工操作;
+
+ - [] **(5)第三方依赖风险**:我理解 AAM 依赖 GitHub、vcpkg、第三方 AI API 等外部服务,Ethernos Studio **不保证**该等服务的持续可用性;我理解社区贡献代码、第三方插件**未经**同等安全审计,使用风险自担;
+
+ - [] **(6)项目终止风险**:我理解 Ethernos Studio 可能因任何原因(法律风险、精力有限、失去兴趣)**永久终止 AAM 项目**,届时将不再提供技术支持,但我保留 AGPL-3.0 授予的开源权利;
+
+ - [] **(7)争议解决方式**:我理解任何争议应首先通过 GitHub Issues 协商解决,协商未果应提交**长沙仲裁委员会**仲裁,仲裁地点为湖南省长沙市,我放弃集体诉讼权利;
+
+ - [] **(8)责任限制确认**:我理解 Ethernos Studio 的责任上限为 **人民币 50 元或我实际支付金额(通常为 0 元)**;我同意不会因任何损失(包括但不限于账号封禁、虚拟财产损失、硬件损坏)向 Ethernos Studio 或 dhjs0000 提出索赔、诉讼或仲裁;
+
+ - [] **(9)完整协议确认**:我确认本协议构成我与 Ethernos Studio 之间的完整协议,我已获得充分机会阅读、理解并咨询法律顾问,我不以"未阅读"或"不理解"为由主张协议无效。"
---
-**确认按钮文案建议**(供 GUI 使用):
+**协议结束**
+
+---
-> **"我已阅读并理解《AAM 用户协议》。我知晓使用本工具可能导致明日方舟账号被封禁,并自愿承担全部风险。我同意遵守 AGPL-3.0 开源许可证义务。"**
+*本协议最后更新日期:2026-04-07*
+*版本:v2.2.0*
+*起草与维护:Ethernos Studio*
\ No newline at end of file
diff --git a/core/include/aam/l0/shm_transport.hpp b/core/include/aam/l0/shm_transport.hpp
new file mode 100644
index 0000000..fdd6b3e
--- /dev/null
+++ b/core/include/aam/l0/shm_transport.hpp
@@ -0,0 +1,990 @@
+// ==========================================================================
+// Copyright (C) 2026 Ethernos Studio
+// This file is part of Arknights Auto Machine (AAM).
+//
+// AAM is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published
+// by the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// AAM is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with AAM. If not, see .
+// ==========================================================================
+// @file shm_transport.hpp
+// @author dhjs0000
+// @brief L0 共享内存传输层实现
+// ==========================================================================
+// 版本: v0.2.0-alpha.3
+// 功能: 零拷贝共享内存传输,支持 Windows 命名共享内存和 POSIX shm
+// 依赖: C++23, Windows API / POSIX shm_open
+// 算法: 环形缓冲区 + 原子序列号 + 内存屏障
+// 性能: 单帧传输延迟 < 1μs,吞吐量 > 10GB/s
+// ==========================================================================
+
+#ifndef AAM_L0_SHM_TRANSPORT_HPP
+#define AAM_L0_SHM_TRANSPORT_HPP
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "aam/core/timer.hpp"
+#include "aam/l0/capture_backend.hpp"
+
+// 禁用 MSVC 结构填充警告
+#ifdef _WIN32
+# pragma warning(push)
+# pragma warning(disable : 4324) // 禁用"结构被填充"警告
+#endif
+
+// 平台特定头文件
+#ifdef _WIN32
+# ifndef NOMINMAX
+# define NOMINMAX
+# endif
+# ifndef WIN32_LEAN_AND_MEAN
+# define WIN32_LEAN_AND_MEAN
+# endif
+# include
+#else
+# include
+# include
+# include
+# include
+#endif
+
+namespace aam::l0
+{
+
+// ==========================================================================
+// 前向声明
+// ==========================================================================
+class SharedMemorySegment;
+struct ShmTransportConfig;
+struct ShmTransportStats;
+
+// ==========================================================================
+// 错误码定义
+// ==========================================================================
+
+/**
+ * @brief 共享内存传输错误码
+ * @details 使用强类型枚举确保类型安全
+ */
+enum class ShmTransportError : std::uint32_t
+{
+ // 通用错误 (0x0000 - 0x00FF)
+ Success = 0, ///< 操作成功
+ Unknown = 0x0001, ///< 未知错误
+ InvalidArgument = 0x0002, ///< 无效参数
+ OutOfMemory = 0x0003, ///< 内存不足
+ Timeout = 0x0004, ///< 操作超时
+ NotInitialized = 0x0005, ///< 未初始化
+ AlreadyExists = 0x0006, ///< 资源已存在
+ NotFound = 0x0007, ///< 资源未找到
+ PermissionDenied = 0x0008, ///< 权限不足
+
+ // 共享内存错误 (0x0100 - 0x01FF)
+ ShmCreateFailed = 0x0100, ///< 创建共享内存失败
+ ShmOpenFailed = 0x0101, ///< 打开共享内存失败
+ ShmMapFailed = 0x0102, ///< 映射共享内存失败
+ ShmUnmapFailed = 0x0103, ///< 解除映射失败
+ ShmTooSmall = 0x0104, ///< 共享内存空间不足
+ ShmCorrupted = 0x0105, ///< 共享内存数据损坏
+ ShmVersionMismatch = 0x0106, ///< 版本不匹配
+
+ // 同步错误 (0x0200 - 0x02FF)
+ SyncCreateFailed = 0x0200, ///< 创建同步对象失败
+ SyncWaitFailed = 0x0201, ///< 等待同步对象失败
+ SyncSignalFailed = 0x0202, ///< 信号通知失败
+ SyncTimeout = 0x0203, ///< 同步超时
+ SyncAbandoned = 0x0204, ///< 同步对象被放弃
+
+ // 传输错误 (0x0300 - 0x03FF)
+ TransportClosed = 0x0300, ///< 传输已关闭
+ TransportBusy = 0x0301, ///< 传输忙
+ BufferOverflow = 0x0302, ///< 缓冲区溢出
+ BufferUnderflow = 0x0303, ///< 缓冲区下溢
+ FrameTooLarge = 0x0304, ///< 帧数据过大
+ InvalidFrame = 0x0305, ///< 无效帧数据
+};
+
+/**
+ * @brief 获取 ShmTransportError 的错误类别
+ * @return 错误类别引用
+ */
+[[nodiscard]] const std::error_category& shm_transport_error_category() noexcept;
+
+/**
+ * @brief 创建 std::error_code
+ * @param e 错误码
+ * @return error_code 对象
+ */
+[[nodiscard]] inline std::error_code make_error_code(ShmTransportError e) noexcept
+{
+ return {static_cast(e), shm_transport_error_category()};
+}
+
+} // namespace aam::l0
+
+// ==========================================================================
+// std::error_code 特化
+// ==========================================================================
+template <>
+struct std::is_error_code_enum : std::true_type
+{
+};
+
+namespace aam::l0
+{
+
+// ==========================================================================
+// 共享内存控制结构(头部)- 必须在 ShmTransportConfig 之前定义
+// ==========================================================================
+
+// 帧头部大小常量
+constexpr std::size_t SHM_FRAME_HEADER_SIZE = 64; // alignas(64)
+constexpr std::size_t SHM_CONTROL_BLOCK_SIZE = 512; // 估算值,实际使用 sizeof
+
+/**
+ * @brief 共享内存帧头部
+ * @details 每帧数据的元数据,位于帧数据之前
+ */
+struct alignas(64) ShmFrameHeader
+{
+ static constexpr std::uint32_t MAGIC = 0x41414D46; ///< "AAMF"
+
+ std::uint32_t magic{MAGIC}; ///< 魔数,用于验证
+ std::uint32_t version{1}; ///< 结构版本
+ std::uint32_t sequence_number{0}; ///< 帧序列号
+ std::uint32_t frame_number{0}; ///< 帧编号
+ std::uint32_t sequence_id{0}; ///< 序列ID
+ std::uint32_t data_size{0}; ///< 实际数据大小
+
+ // 时间戳
+ std::uint64_t capture_timestamp_ns{0}; ///< 捕获时间戳(纳秒)
+ std::uint64_t write_timestamp_ns{0}; ///< 写入时间戳(纳秒)
+
+ // 帧元数据
+ std::uint32_t width{0}; ///< 图像宽度
+ std::uint32_t height{0}; ///< 图像高度
+ std::uint32_t stride{0}; ///< 行步长
+ std::uint32_t pixel_format{0}; ///< 像素格式(PixelFormat 枚举值)
+
+ // 校验
+ std::uint32_t checksum{0}; ///< 数据校验和(CRC32)
+ std::uint32_t reserved{0}; ///< 保留字段(对齐)
+
+ /**
+ * @brief 验证头部有效性
+ * @return true 如果头部有效
+ */
+ [[nodiscard]] bool is_valid() const noexcept
+ {
+ return magic == MAGIC && version == 1 && data_size > 0;
+ }
+
+ /**
+ * @brief 计算校验和
+ * @param data 帧数据指针
+ * @return CRC32 校验和
+ */
+ [[nodiscard]] std::uint32_t calculate_checksum(const std::byte* data) const noexcept;
+
+ /**
+ * @brief 验证数据完整性
+ * @param data 帧数据指针
+ * @return true 如果数据完整
+ */
+ [[nodiscard]] bool verify_data(const std::byte* data) const noexcept;
+};
+
+/**
+ * @brief 共享内存控制块
+ * @details 位于共享内存起始位置,管理整个传输状态
+ * @note 所有原子操作使用 acquire-release 语义保证跨进程可见性
+ */
+struct alignas(64) ShmControlBlock
+{
+ static constexpr std::uint32_t MAGIC = 0x41414D53; ///< "AAMS"
+ static constexpr std::uint32_t VERSION = 1;
+
+ // 魔数和版本(只读,创建时初始化)
+ std::uint32_t magic{MAGIC}; ///< 魔数
+ std::uint32_t version{VERSION}; ///< 协议版本
+ std::uint32_t header_size{0}; ///< 控制块大小
+ std::uint32_t flags{0}; ///< 标志位
+
+ // 缓冲区配置(只读,创建时初始化)
+ std::uint32_t buffer_count{0}; ///< 缓冲区数量
+ std::uint32_t max_frame_size{0}; ///< 最大帧大小
+ std::uint32_t metadata_size{0}; ///< 元数据区域大小
+ std::uint32_t frame_stride{0}; ///< 每帧对齐后的大小
+
+ // 序列号(原子操作)
+ alignas(64) std::atomic write_sequence{0}; ///< 写入序列号
+ alignas(64) std::atomic read_sequence{0}; ///< 读取序列号
+ alignas(64) std::atomic dropped_frames{0}; ///< 丢弃帧计数
+
+ // 状态标志(原子操作)
+ alignas(64) std::atomic state{0}; ///< 传输状态
+ alignas(64) std::atomic active_readers{0}; ///< 活跃读取者数
+ alignas(64) std::atomic active_writers{0}; ///< 活跃写入者数
+
+ // 统计信息(原子操作)
+ alignas(64) std::atomic total_frames_written{0}; ///< 总写入帧数
+ alignas(64) std::atomic total_frames_read{0}; ///< 总读取帧数
+ alignas(64) std::atomic total_bytes_written{0}; ///< 总写入字节数
+ alignas(64) std::atomic total_bytes_read{0}; ///< 总读取字节数
+
+ // 状态常量
+ static constexpr std::uint32_t STATE_INITIALIZED = 0x01; ///< 已初始化
+ static constexpr std::uint32_t STATE_ACTIVE = 0x02; ///< 活跃状态
+ static constexpr std::uint32_t STATE_SHUTDOWN = 0x04; ///< 已关闭
+ static constexpr std::uint32_t STATE_ERROR = 0x08; ///< 错误状态
+
+ /**
+ * @brief 验证控制块有效性
+ * @return true 如果控制块有效
+ */
+ [[nodiscard]] bool is_valid() const noexcept
+ {
+ return magic == MAGIC && version == VERSION;
+ }
+
+ /**
+ * @brief 获取当前写入索引
+ * @return 写入缓冲区索引
+ */
+ [[nodiscard]] std::uint32_t get_write_index() const noexcept
+ {
+ return static_cast(write_sequence.load(std::memory_order_relaxed) % buffer_count);
+ }
+
+ /**
+ * @brief 获取当前读取索引
+ * @return 读取缓冲区索引
+ */
+ [[nodiscard]] std::uint32_t get_read_index() const noexcept
+ {
+ return static_cast(read_sequence.load(std::memory_order_relaxed) % buffer_count);
+ }
+
+ /**
+ * @brief 检查是否有可读数据
+ * @return true 如果有未读数据
+ */
+ [[nodiscard]] bool has_readable_data() const noexcept
+ {
+ return read_sequence.load(std::memory_order_acquire) <
+ write_sequence.load(std::memory_order_acquire);
+ }
+
+ /**
+ * @brief 检查是否可写入
+ * @return true 如果有可用缓冲区
+ */
+ [[nodiscard]] bool is_writable() const noexcept
+ {
+ const std::uint64_t write_seq = write_sequence.load(std::memory_order_relaxed);
+ const std::uint64_t read_seq = read_sequence.load(std::memory_order_acquire);
+ return (write_seq - read_seq) < buffer_count;
+ }
+
+ /**
+ * @brief 获取可用缓冲区数量
+ * @return 可用缓冲区数量
+ */
+ [[nodiscard]] std::uint32_t available_buffers() const noexcept
+ {
+ const std::uint64_t write_seq = write_sequence.load(std::memory_order_relaxed);
+ const std::uint64_t read_seq = read_sequence.load(std::memory_order_acquire);
+ return static_cast(buffer_count - (write_seq - read_seq));
+ }
+
+ /**
+ * @brief 获取已用缓冲区数量
+ * @return 已用缓冲区数量
+ */
+ [[nodiscard]] std::uint32_t used_buffers() const noexcept
+ {
+ const std::uint64_t write_seq = write_sequence.load(std::memory_order_acquire);
+ const std::uint64_t read_seq = read_sequence.load(std::memory_order_relaxed);
+ return static_cast(write_seq - read_seq);
+ }
+};
+
+// ==========================================================================
+// 共享内存传输配置
+// ==========================================================================
+
+/**
+ * @brief 共享内存传输配置
+ * @details 定义共享内存传输的行为参数
+ */
+struct ShmTransportConfig
+{
+ // 共享内存名称(平台特定前缀自动添加)
+ std::string shm_name{"aam_frame_buffer"}; ///< 共享内存对象名称
+
+ // 缓冲区配置
+ std::size_t buffer_count{4}; ///< 帧缓冲区数量(环形)
+ std::size_t max_frame_size{1920 * 1080 * 4}; ///< 最大帧大小(字节)
+ std::size_t metadata_size{4096}; ///< 元数据区域大小(字节)
+
+ // 同步配置
+ core::Duration write_timeout{std::chrono::milliseconds(100)}; ///< 写入超时
+ core::Duration read_timeout{std::chrono::milliseconds(100)}; ///< 读取超时
+ bool non_blocking{false}; ///< 非阻塞模式
+
+ // 性能配置
+ bool use_cache_line_alignment{true}; ///< 使用缓存行对齐(避免伪共享)
+ bool prefetch_next_frame{true}; ///< 预取下一帧数据
+ bool enable_zero_copy{true}; ///< 启用零拷贝模式(跳过CRC校验)
+ bool enable_checksum{false}; ///< 启用CRC校验(默认关闭以提高性能)
+
+ // 调试配置
+ bool enable_stats{true}; ///< 启用统计信息
+ bool enable_tracing{false}; ///< 启用详细跟踪日志
+
+ /**
+ * @brief 计算所需共享内存总大小
+ * @return 总大小(字节)
+ * @complexity O(1)
+ */
+ [[nodiscard]] constexpr std::size_t calculate_total_size() const noexcept
+ {
+ // 头部:控制结构 + 元数据
+ const std::size_t header_size = sizeof(ShmControlBlock) + metadata_size;
+
+ // 帧缓冲区:每个帧需要头部 + 数据,对齐到缓存行
+ constexpr std::size_t cache_line = 64;
+ const std::size_t frame_header_size = sizeof(ShmFrameHeader);
+ const std::size_t aligned_frame_size =
+ ((frame_header_size + max_frame_size + cache_line - 1) / cache_line) * cache_line;
+
+ return header_size + (aligned_frame_size * buffer_count);
+ }
+
+ /**
+ * @brief 验证配置有效性
+ * @return 错误码或 void
+ */
+ [[nodiscard]] std::expected validate() const noexcept
+ {
+ if (shm_name.empty()) {
+ return std::unexpected(ShmTransportError::InvalidArgument);
+ }
+ if (buffer_count == 0 || buffer_count > 64) {
+ return std::unexpected(ShmTransportError::InvalidArgument);
+ }
+ if (max_frame_size == 0 || max_frame_size > (256 * 1024 * 1024)) {
+ return std::unexpected(ShmTransportError::InvalidArgument);
+ }
+ return {};
+ }
+};
+
+// ==========================================================================
+// 共享内存段(平台抽象)
+// ==========================================================================
+
+/**
+ * @brief 共享内存段抽象类
+ * @details 封装平台特定的共享内存操作
+ */
+class SharedMemorySegment
+{
+public:
+ // ==================================================================
+ // 类型别名
+ // ==================================================================
+ using Result = std::expected;
+
+ // ==================================================================
+ // 构造与析构
+ // ==================================================================
+ SharedMemorySegment() noexcept = default;
+ virtual ~SharedMemorySegment() = default;
+
+ // 禁用拷贝
+ SharedMemorySegment(const SharedMemorySegment&) = delete;
+ SharedMemorySegment& operator=(const SharedMemorySegment&) = delete;
+
+ // 允许移动
+ SharedMemorySegment(SharedMemorySegment&&) = default;
+ SharedMemorySegment& operator=(SharedMemorySegment&&) = default;
+
+ // ==================================================================
+ // 工厂方法
+ // ==================================================================
+
+ /**
+ * @brief 创建共享内存段(生产者)
+ * @param name 共享内存名称
+ * @param size 共享内存大小
+ * @return 成功返回 SharedMemorySegment 实例,失败返回错误码
+ * @complexity O(1),系统调用
+ */
+ [[nodiscard]] static std::expected, ShmTransportError>
+ Create(std::string_view name, std::size_t size);
+
+ /**
+ * @brief 打开现有共享内存段(消费者)
+ * @param name 共享内存名称
+ * @param size 共享内存大小(用于验证)
+ * @return 成功返回 SharedMemorySegment 实例,失败返回错误码
+ * @complexity O(1),系统调用
+ */
+ [[nodiscard]] static std::expected, ShmTransportError>
+ Open(std::string_view name, std::size_t size);
+
+ // ==================================================================
+ // 访问接口
+ // ==================================================================
+
+ /**
+ * @brief 获取共享内存基地址
+ * @return 共享内存起始地址
+ */
+ [[nodiscard]] virtual std::byte* data() const noexcept = 0;
+
+ /**
+ * @brief 获取共享内存大小
+ * @return 共享内存大小(字节)
+ */
+ [[nodiscard]] virtual std::size_t size() const noexcept = 0;
+
+ /**
+ * @brief 获取共享内存名称
+ * @return 共享内存名称
+ */
+ [[nodiscard]] virtual std::string_view name() const noexcept = 0;
+
+ /**
+ * @brief 检查是否有效
+ * @return true 如果共享内存有效映射
+ */
+ [[nodiscard]] virtual bool is_valid() const noexcept = 0;
+
+ /**
+ * @brief 刷新共享内存到磁盘(如果需要)
+ * @return 操作结果
+ */
+ [[nodiscard]] virtual Result flush() noexcept = 0;
+};
+
+// ==========================================================================
+// 共享内存传输统计
+// ==========================================================================
+
+/**
+ * @brief 共享内存传输统计信息
+ * @note 使用原子类型保证线程安全,避免锁竞争
+ */
+struct ShmTransportStats
+{
+ // 帧统计(受 stats_mutex_ 保护)
+ std::uint64_t frames_written{0}; ///< 已写入帧数
+ std::uint64_t frames_read{0}; ///< 已读取帧数
+ std::uint64_t frames_dropped{0}; ///< 丢弃帧数
+
+ // 字节统计(受 stats_mutex_ 保护)
+ std::uint64_t bytes_written{0}; ///< 已写入字节数
+ std::uint64_t bytes_read{0}; ///< 已读取字节数
+
+ // 延迟统计(纳秒,受 stats_mutex_ 保护)
+ core::Duration min_write_latency{core::Duration::max()}; ///< 最小写入延迟
+ core::Duration max_write_latency{core::Duration::zero()}; ///< 最大写入延迟
+ core::Duration avg_write_latency{core::Duration::zero()}; ///< 平均写入延迟
+
+ core::Duration min_read_latency{core::Duration::max()}; ///< 最小读取延迟
+ core::Duration max_read_latency{core::Duration::zero()}; ///< 最大读取延迟
+ core::Duration avg_read_latency{core::Duration::zero()}; ///< 平均读取延迟
+
+ // 传输统计(原子类型,无锁更新)
+ std::atomic write_timeouts{0}; ///< 写入超时次数
+ std::atomic read_timeouts{0}; ///< 读取超时次数
+ std::atomic checksum_errors{0}; ///< 校验和错误次数
+
+ // 时间戳(受 stats_mutex_ 保护)
+ core::Timestamp session_start; ///< 会话开始时间
+ core::Timestamp last_write_time; ///< 最后写入时间
+ core::Timestamp last_read_time; ///< 最后读取时间
+
+ // 显式定义拷贝构造函数(处理原子成员)
+ ShmTransportStats() = default;
+
+ ShmTransportStats(const ShmTransportStats& other)
+ : frames_written(other.frames_written)
+ , frames_read(other.frames_read)
+ , frames_dropped(other.frames_dropped)
+ , bytes_written(other.bytes_written)
+ , bytes_read(other.bytes_read)
+ , min_write_latency(other.min_write_latency)
+ , max_write_latency(other.max_write_latency)
+ , avg_write_latency(other.avg_write_latency)
+ , min_read_latency(other.min_read_latency)
+ , max_read_latency(other.max_read_latency)
+ , avg_read_latency(other.avg_read_latency)
+ , write_timeouts(other.write_timeouts.load(std::memory_order_relaxed))
+ , read_timeouts(other.read_timeouts.load(std::memory_order_relaxed))
+ , checksum_errors(other.checksum_errors.load(std::memory_order_relaxed))
+ , session_start(other.session_start)
+ , last_write_time(other.last_write_time)
+ , last_read_time(other.last_read_time)
+ {}
+
+ ShmTransportStats& operator=(const ShmTransportStats& other)
+ {
+ if (this != &other) {
+ frames_written = other.frames_written;
+ frames_read = other.frames_read;
+ frames_dropped = other.frames_dropped;
+ bytes_written = other.bytes_written;
+ bytes_read = other.bytes_read;
+ min_write_latency = other.min_write_latency;
+ max_write_latency = other.max_write_latency;
+ avg_write_latency = other.avg_write_latency;
+ min_read_latency = other.min_read_latency;
+ max_read_latency = other.max_read_latency;
+ avg_read_latency = other.avg_read_latency;
+ write_timeouts.store(other.write_timeouts.load(std::memory_order_relaxed), std::memory_order_relaxed);
+ read_timeouts.store(other.read_timeouts.load(std::memory_order_relaxed), std::memory_order_relaxed);
+ checksum_errors.store(other.checksum_errors.load(std::memory_order_relaxed), std::memory_order_relaxed);
+ session_start = other.session_start;
+ last_write_time = other.last_write_time;
+ last_read_time = other.last_read_time;
+ }
+ return *this;
+ }
+
+ /**
+ * @brief 计算丢帧率
+ * @return 丢帧率 [0.0, 1.0]
+ */
+ [[nodiscard]] double get_drop_rate() const noexcept
+ {
+ const std::uint64_t total = frames_written + frames_dropped;
+ return total > 0 ? static_cast(frames_dropped) / total : 0.0;
+ }
+
+ /**
+ * @brief 计算平均帧大小
+ * @return 平均帧大小(字节)
+ */
+ [[nodiscard]] std::uint64_t get_average_frame_size() const noexcept
+ {
+ return frames_written > 0 ? bytes_written / frames_written : 0;
+ }
+
+ /**
+ * @brief 计算吞吐量(字节/秒)
+ * @return 吞吐量估算
+ */
+ [[nodiscard]] double get_throughput_bytes_per_sec() const noexcept
+ {
+ const auto duration = core::Clock::now() - session_start;
+ const auto duration_sec = std::chrono::duration(duration).count();
+ return duration_sec > 0 ? static_cast(bytes_written) / duration_sec : 0.0;
+ }
+
+ /**
+ * @brief 更新写入延迟统计
+ * @param latency 写入延迟
+ */
+ void update_write_latency(core::Duration latency) noexcept
+ {
+ min_write_latency = std::min(min_write_latency, latency);
+ max_write_latency = std::max(max_write_latency, latency);
+ // 指数移动平均
+ const double alpha = 0.1;
+ const double new_ns = static_cast(latency.count());
+ const double old_ns = static_cast(avg_write_latency.count());
+ avg_write_latency = core::Duration(static_cast(
+ alpha * new_ns + (1.0 - alpha) * old_ns));
+ }
+
+ /**
+ * @brief 更新读取延迟统计
+ * @param latency 读取延迟
+ */
+ void update_read_latency(core::Duration latency) noexcept
+ {
+ min_read_latency = std::min(min_read_latency, latency);
+ max_read_latency = std::max(max_read_latency, latency);
+ // 指数移动平均
+ const double alpha = 0.1;
+ const double new_ns = static_cast(latency.count());
+ const double old_ns = static_cast(avg_read_latency.count());
+ avg_read_latency = core::Duration(static_cast(
+ alpha * new_ns + (1.0 - alpha) * old_ns));
+ }
+
+ /**
+ * @brief 重置统计信息
+ */
+ void reset() noexcept
+ {
+ *this = ShmTransportStats{};
+ session_start = core::Clock::now();
+ }
+};
+
+// ==========================================================================
+// 共享内存传输器
+// ==========================================================================
+
+/**
+ * @brief 共享内存传输器
+ * @details 提供零拷贝的帧数据传输,支持单生产者-多消费者模式
+ * @note 线程安全:写入操作是单生产者安全的,读取操作是多消费者安全的
+ * @warning 消费者必须在生产者初始化后才能连接
+ */
+class ShmTransport
+{
+public:
+ // ==================================================================
+ // 类型别名
+ // ==================================================================
+ using Result = std::expected;
+
+ // ==================================================================
+ // 构造与析构
+ // ==================================================================
+
+ /**
+ * @brief 默认构造函数
+ * @complexity O(1)
+ */
+ ShmTransport() noexcept = default;
+
+ /**
+ * @brief 析构函数
+ * @complexity O(1),自动关闭传输
+ */
+ ~ShmTransport() noexcept;
+
+ // 禁用拷贝
+ ShmTransport(const ShmTransport&) = delete;
+ ShmTransport& operator=(const ShmTransport&) = delete;
+
+ // 允许移动
+ ShmTransport(ShmTransport&& other) noexcept;
+ ShmTransport& operator=(ShmTransport&& other) noexcept;
+
+ // ==================================================================
+ // 生命周期管理(生产者)
+ // ==================================================================
+
+ /**
+ * @brief 初始化传输器(作为生产者)
+ * @param config 传输配置
+ * @return 成功返回 void,失败返回错误码
+ * @complexity O(1),创建共享内存和同步对象
+ * @thread_safety 线程安全,但应在启动消费者前调用
+ */
+ [[nodiscard]] Result InitializeProducer(const ShmTransportConfig& config);
+
+ /**
+ * @brief 初始化传输器(作为消费者)
+ * @param config 传输配置
+ * @return 成功返回 void,失败返回错误码
+ * @complexity O(1),打开共享内存
+ * @thread_safety 线程安全
+ * @note 必须在生产者初始化后调用
+ */
+ [[nodiscard]] Result InitializeConsumer(const ShmTransportConfig& config);
+
+ /**
+ * @brief 关闭传输器
+ * @return 成功返回 void,失败返回错误码
+ * @complexity O(1)
+ */
+ [[nodiscard]] Result Shutdown() noexcept;
+
+ /**
+ * @brief 检查是否已初始化
+ * @return true 如果已初始化
+ */
+ [[nodiscard]] bool IsInitialized() const noexcept;
+
+ /**
+ * @brief 检查是否为生产者模式
+ * @return true 如果是生产者
+ */
+ [[nodiscard]] bool IsProducer() const noexcept;
+
+ /**
+ * @brief 检查是否为消费者模式
+ * @return true 如果是消费者
+ */
+ [[nodiscard]] bool IsConsumer() const noexcept;
+
+ // ==================================================================
+ // 帧写入接口(生产者)
+ // ==================================================================
+
+ /**
+ * @brief 写入帧数据
+ * @param metadata 帧元数据
+ * @param data 帧数据
+ * @return 成功返回 void,失败返回错误码
+ * @complexity O(n),n为数据大小(内存拷贝)
+ * @thread_safety 单生产者安全
+ * @note 如果缓冲区满,根据配置可能阻塞或丢弃
+ */
+ [[nodiscard]] Result WriteFrame(const FrameMetadata& metadata, std::span data);
+
+ /**
+ * @brief 尝试写入帧数据(非阻塞)
+ * @param metadata 帧元数据
+ * @param data 帧数据
+ * @return 成功返回 true,缓冲区满返回 false,错误返回错误码
+ * @complexity O(n)
+ * @thread_safety 单生产者安全
+ */
+ [[nodiscard]] std::expected TryWriteFrame(
+ const FrameMetadata& metadata, std::span data);
+
+ /**
+ * @brief 写入帧数据(带超时)
+ * @param metadata 帧元数据
+ * @param data 帧数据
+ * @param timeout 超时时间
+ * @return 成功返回 true,超时返回 false,错误返回错误码
+ * @complexity O(n)
+ * @thread_safety 单生产者安全
+ */
+ [[nodiscard]] std::expected WriteFrameWithTimeout(
+ const FrameMetadata& metadata, std::span data, core::Duration timeout);
+
+ // ==================================================================
+ // 帧读取接口(消费者)
+ // ==================================================================
+
+ /**
+ * @brief 读取帧数据
+ * @param timeout 超时时间
+ * @return 成功返回帧数据,超时返回 nullopt,错误返回错误码
+ * @complexity O(n),n为数据大小(内存拷贝)
+ * @thread_safety 多消费者安全
+ */
+ [[nodiscard]] std::expected>>, ShmTransportError>
+ ReadFrame(core::Duration timeout);
+
+ /**
+ * @brief 尝试读取帧数据(非阻塞)
+ * @return 成功返回帧数据,无数据返回 nullopt,错误返回错误码
+ * @complexity O(n)
+ * @thread_safety 多消费者安全
+ */
+ [[nodiscard]] std::expected>>, ShmTransportError>
+ TryReadFrame();
+
+ /**
+ * @brief 读取帧数据(回调方式)
+ * @param timeout 超时时间
+ * @param callback 数据回调函数
+ * @return 成功返回 void,失败返回错误码
+ * @complexity O(n)
+ * @thread_safety 多消费者安全
+ * @note 回调方式避免额外内存分配
+ */
+ using ReadCallback = std::function)>;
+ [[nodiscard]] Result ReadFrameWithCallback(core::Duration timeout, ReadCallback callback);
+
+ // ==================================================================
+ // 零拷贝帧接口(高性能)
+ // ==================================================================
+
+ /**
+ * @brief 获取写入缓冲区(零拷贝)
+ * @param timeout 超时时间
+ * @return 成功返回可写入的缓冲区指针和元数据,失败返回错误码
+ * @complexity O(1),无内存拷贝
+ * @thread_safety 单生产者安全
+ * @note 调用者直接写入返回的缓冲区,然后通过 CommitWriteBuffer 提交
+ */
+ struct WriteBuffer {
+ std::byte* data{nullptr}; ///< 数据缓冲区指针
+ std::size_t capacity{0}; ///< 缓冲区容量
+ std::uint32_t buffer_index{0}; ///< 缓冲区索引(用于提交)
+ };
+ [[nodiscard]] std::expected, ShmTransportError>
+ AcquireWriteBuffer(core::Duration timeout);
+
+ /**
+ * @brief 提交写入的缓冲区
+ * @param buffer_index AcquireWriteBuffer 返回的 buffer_index
+ * @param metadata 帧元数据
+ * @param actual_size 实际写入的数据大小
+ * @return 成功返回 void,失败返回错误码
+ * @complexity O(1)
+ * @thread_safety 单生产者安全
+ */
+ [[nodiscard]] Result CommitWriteBuffer(
+ std::uint32_t buffer_index,
+ const FrameMetadata& metadata,
+ std::size_t actual_size);
+
+ /**
+ * @brief 获取读取缓冲区(零拷贝)
+ * @param timeout 超时时间
+ * @return 成功返回帧元数据和数据指针,失败返回错误码
+ * @complexity O(1),无内存拷贝
+ * @thread_safety 多消费者安全
+ * @note 调用者直接读取返回的指针,然后通过 ReleaseReadBuffer 释放
+ */
+ struct ReadBuffer {
+ FrameMetadata metadata; ///< 帧元数据
+ std::span data; ///< 数据视图(不拥有内存)
+ std::uint32_t buffer_index{0}; ///< 缓冲区索引(用于释放)
+ };
+ [[nodiscard]] std::expected, ShmTransportError>
+ AcquireReadBuffer(core::Duration timeout);
+
+ /**
+ * @brief 释放读取的缓冲区
+ * @param buffer_index AcquireReadBuffer 返回的 buffer_index
+ * @return 成功返回 void,失败返回错误码
+ * @complexity O(1)
+ * @thread_safety 多消费者安全
+ */
+ [[nodiscard]] Result ReleaseReadBuffer(std::uint32_t buffer_index);
+
+ /**
+ * @brief 尝试获取写入缓冲区(非阻塞)
+ * @return 成功返回缓冲区,无可用缓冲区返回 nullopt
+ */
+ [[nodiscard]] std::expected, ShmTransportError>
+ TryAcquireWriteBuffer();
+
+ /**
+ * @brief 尝试获取读取缓冲区(非阻塞)
+ * @return 成功返回缓冲区,无数据返回 nullopt
+ */
+ [[nodiscard]] std::expected, ShmTransportError>
+ TryAcquireReadBuffer();
+
+ // ==================================================================
+ // 状态查询
+ // ==================================================================
+
+ /**
+ * @brief 获取当前统计信息
+ * @return 统计信息副本
+ */
+ [[nodiscard]] ShmTransportStats GetStats() const noexcept;
+
+ /**
+ * @brief 获取缓冲区状态
+ * @return (已用缓冲区数, 总缓冲区数)
+ */
+ [[nodiscard]] std::pair GetBufferStatus() const noexcept;
+
+ /**
+ * @brief 获取配置
+ * @return 配置副本
+ */
+ [[nodiscard]] const ShmTransportConfig& GetConfig() const noexcept { return config_; }
+
+ /**
+ * @brief 获取控制块指针(调试用途)
+ * @return 控制块指针,未初始化返回 nullptr
+ */
+ [[nodiscard]] const ShmControlBlock* GetControlBlock() const noexcept { return control_block_; }
+
+private:
+ // ==================================================================
+ // 内部方法
+ // ==================================================================
+
+ /**
+ * @brief 更新统计信息
+ * @param is_write 是否为写入操作
+ * @param bytes 字节数
+ * @param latency 操作延迟
+ */
+ void update_stats(bool is_write, std::size_t bytes, core::Duration latency) noexcept;
+
+ /**
+ * @brief 获取帧头部地址
+ * @param index 缓冲区索引
+ * @return 帧头部指针
+ */
+ [[nodiscard]] ShmFrameHeader* get_frame_header(std::uint32_t index) noexcept;
+
+ /**
+ * @brief 获取帧数据缓冲区地址
+ * @param index 缓冲区索引
+ * @return 帧数据缓冲区起始地址
+ */
+ [[nodiscard]] std::byte* get_frame_buffer(std::uint32_t index) noexcept;
+
+ // ==================================================================
+ // 成员变量
+ // ==================================================================
+
+ ShmTransportConfig config_; ///< 传输配置
+ std::unique_ptr segment_; ///< 共享内存段
+ ShmControlBlock* control_block_{nullptr}; ///< 控制块指针
+ std::byte* frame_data_base_{nullptr}; ///< 帧数据基地址
+
+ // 状态
+ std::atomic initialized_{false}; ///< 初始化标志
+ bool is_producer_{false}; ///< 是否为生产者
+
+ // 统计
+ mutable std::mutex stats_mutex_; ///< 统计信息互斥锁
+ ShmTransportStats stats_; ///< 本地统计信息
+};
+
+// ==========================================================================
+// 辅助函数
+// ==========================================================================
+
+/**
+ * @brief 生成平台特定的共享内存名称
+ * @param name 基础名称
+ * @return 完整共享内存名称
+ */
+[[nodiscard]] std::string MakeShmName(std::string_view name);
+
+/**
+ * @brief 检查共享内存是否存在
+ * @param name 共享内存名称
+ * @return true 如果存在
+ */
+[[nodiscard]] bool ShmExists(std::string_view name);
+
+/**
+ * @brief 删除共享内存
+ * @param name 共享内存名称
+ * @return true 如果成功删除或不存在
+ */
+[[nodiscard]] bool ShmRemove(std::string_view name);
+
+} // namespace aam::l0
+
+// 恢复警告设置
+#ifdef _WIN32
+# pragma warning(pop)
+#endif
+
+#endif // AAM_L0_SHM_TRANSPORT_HPP
diff --git a/core/src/l0_sensing/shm_transport.cpp b/core/src/l0_sensing/shm_transport.cpp
new file mode 100644
index 0000000..3c86577
--- /dev/null
+++ b/core/src/l0_sensing/shm_transport.cpp
@@ -0,0 +1,1291 @@
+// ==========================================================================
+// Copyright (C) 2026 Ethernos Studio
+// This file is part of Arknights Auto Machine (AAM).
+//
+// AAM is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published
+// by the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// AAM is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with AAM. If not, see .
+// ==========================================================================
+// @file shm_transport.cpp
+// @author dhjs0000
+// @brief L0 共享内存传输层实现
+// ==========================================================================
+
+#include "aam/l0/shm_transport.hpp"
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+// 平台特定头文件
+#ifdef _WIN32
+# include
+#else
+# include
+# include
+# include
+# include
+#endif
+
+namespace aam::l0
+{
+
+// 使用 core 命名空间
+using namespace aam::core;
+
+// ==========================================================================
+// CRC32 查找表(IEEE 802.3 标准)
+// ==========================================================================
+namespace
+{
+// CRC32 查找表 - 使用 constexpr 确保编译期计算
+constexpr std::array generate_crc32_table() noexcept
+{
+ std::array table{};
+ for (std::uint32_t i = 0; i < 256; ++i) {
+ std::uint32_t crc = i;
+ for (std::uint32_t j = 0; j < 8; ++j) {
+ crc = (crc >> 1) ^ (0xEDB88320 & static_cast(-(static_cast(crc) & 1)));
+ }
+ table[i] = crc;
+ }
+ return table;
+}
+
+static constexpr auto g_crc32_table = generate_crc32_table();
+
+// 错误类别实现
+class ShmTransportErrorCategory : public std::error_category
+{
+public:
+ [[nodiscard]] const char* name() const noexcept override
+ {
+ return "shm_transport";
+ }
+
+ [[nodiscard]] std::string message(int ev) const override
+ {
+ switch (static_cast(ev)) {
+ case ShmTransportError::Success:
+ return "Success";
+ case ShmTransportError::Unknown:
+ return "Unknown error";
+ case ShmTransportError::InvalidArgument:
+ return "Invalid argument";
+ case ShmTransportError::OutOfMemory:
+ return "Out of memory";
+ case ShmTransportError::Timeout:
+ return "Operation timeout";
+ case ShmTransportError::NotInitialized:
+ return "Not initialized";
+ case ShmTransportError::AlreadyExists:
+ return "Resource already exists";
+ case ShmTransportError::NotFound:
+ return "Resource not found";
+ case ShmTransportError::PermissionDenied:
+ return "Permission denied";
+ case ShmTransportError::ShmCreateFailed:
+ return "Shared memory creation failed";
+ case ShmTransportError::ShmOpenFailed:
+ return "Shared memory open failed";
+ case ShmTransportError::ShmMapFailed:
+ return "Shared memory mapping failed";
+ case ShmTransportError::ShmUnmapFailed:
+ return "Shared memory unmapping failed";
+ case ShmTransportError::ShmTooSmall:
+ return "Shared memory too small";
+ case ShmTransportError::ShmCorrupted:
+ return "Shared memory data corrupted";
+ case ShmTransportError::ShmVersionMismatch:
+ return "Shared memory version mismatch";
+ case ShmTransportError::SyncCreateFailed:
+ return "Synchronization object creation failed";
+ case ShmTransportError::SyncWaitFailed:
+ return "Synchronization wait failed";
+ case ShmTransportError::SyncSignalFailed:
+ return "Synchronization signal failed";
+ case ShmTransportError::SyncTimeout:
+ return "Synchronization timeout";
+ case ShmTransportError::SyncAbandoned:
+ return "Synchronization object abandoned";
+ case ShmTransportError::TransportClosed:
+ return "Transport closed";
+ case ShmTransportError::TransportBusy:
+ return "Transport busy";
+ case ShmTransportError::BufferOverflow:
+ return "Buffer overflow";
+ case ShmTransportError::BufferUnderflow:
+ return "Buffer underflow";
+ case ShmTransportError::FrameTooLarge:
+ return "Frame too large";
+ case ShmTransportError::InvalidFrame:
+ return "Invalid frame data";
+ default:
+ return "Unknown shared memory transport error";
+ }
+ }
+
+ [[nodiscard]] std::error_condition default_error_condition(int ev) const noexcept override
+ {
+ switch (static_cast(ev)) {
+ case ShmTransportError::Success:
+ return std::errc{};
+ case ShmTransportError::InvalidArgument:
+ return std::errc::invalid_argument;
+ case ShmTransportError::OutOfMemory:
+ return std::errc::not_enough_memory;
+ case ShmTransportError::Timeout:
+ case ShmTransportError::SyncTimeout:
+ return std::errc::timed_out;
+ case ShmTransportError::NotFound:
+ return std::errc::no_such_file_or_directory;
+ case ShmTransportError::AlreadyExists:
+ return std::errc::file_exists;
+ case ShmTransportError::PermissionDenied:
+ return std::errc::permission_denied;
+ default:
+ return std::error_condition(ev, *this);
+ }
+ }
+};
+
+// 全局错误类别实例
+const ShmTransportErrorCategory g_shm_transport_error_category{};
+
+// 平台特定的共享内存名称前缀
+#ifdef _WIN32
+constexpr std::string_view SHM_NAME_PREFIX = "Local\\";
+#else
+constexpr std::string_view SHM_NAME_PREFIX = "/";
+#endif
+
+} // anonymous namespace
+
+// ==========================================================================
+// 错误类别接口实现
+// ==========================================================================
+
+const std::error_category& shm_transport_error_category() noexcept
+{
+ return g_shm_transport_error_category;
+}
+
+// ==========================================================================
+// ShmFrameHeader 实现
+// ==========================================================================
+
+std::uint32_t ShmFrameHeader::calculate_checksum(const std::byte* data) const noexcept
+{
+ if (data == nullptr || data_size == 0) {
+ return 0;
+ }
+
+ std::uint32_t crc = 0xFFFFFFFF;
+ for (std::uint32_t i = 0; i < data_size; ++i) {
+ const std::uint8_t byte = static_cast(data[i]);
+ crc = g_crc32_table[(crc ^ byte) & 0xFF] ^ (crc >> 8);
+ }
+ return crc ^ 0xFFFFFFFF;
+}
+
+bool ShmFrameHeader::verify_data(const std::byte* data) const noexcept
+{
+ if (data == nullptr || data_size == 0) {
+ return false;
+ }
+ return calculate_checksum(data) == checksum;
+}
+
+// ==========================================================================
+// Windows 共享内存段实现
+// ==========================================================================
+#ifdef _WIN32
+
+class WindowsSharedMemorySegment : public SharedMemorySegment
+{
+public:
+ WindowsSharedMemorySegment() noexcept = default;
+
+ ~WindowsSharedMemorySegment() override
+ {
+ cleanup();
+ }
+
+ WindowsSharedMemorySegment(const WindowsSharedMemorySegment&) = delete;
+ WindowsSharedMemorySegment& operator=(const WindowsSharedMemorySegment&) = delete;
+
+ WindowsSharedMemorySegment(WindowsSharedMemorySegment&& other) noexcept
+ : name_(std::move(other.name_)),
+ size_(other.size_),
+ handle_(other.handle_),
+ data_(other.data_)
+ {
+ other.handle_ = nullptr;
+ other.data_ = nullptr;
+ other.size_ = 0;
+ }
+
+ WindowsSharedMemorySegment& operator=(WindowsSharedMemorySegment&& other) noexcept
+ {
+ if (this != &other) {
+ cleanup();
+ name_ = std::move(other.name_);
+ size_ = other.size_;
+ handle_ = other.handle_;
+ data_ = other.data_;
+ other.handle_ = nullptr;
+ other.data_ = nullptr;
+ other.size_ = 0;
+ }
+ return *this;
+ }
+
+ [[nodiscard]] static std::expected, ShmTransportError>
+ Create(std::string_view name, std::size_t size)
+ {
+ if (name.empty() || size == 0) {
+ return std::unexpected(ShmTransportError::InvalidArgument);
+ }
+
+ auto segment = std::make_unique();
+ segment->name_ = std::string(name);
+ segment->size_ = size;
+
+ segment->handle_ = CreateFileMappingA(
+ INVALID_HANDLE_VALUE,
+ nullptr,
+ PAGE_READWRITE,
+ static_cast((size >> 32) & 0xFFFFFFFF),
+ static_cast(size & 0xFFFFFFFF),
+ segment->name_.c_str()
+ );
+
+ if (segment->handle_ == nullptr) {
+ return std::unexpected(ShmTransportError::ShmCreateFailed);
+ }
+
+ // 检查是否创建了新的共享内存还是打开了已存在的
+ // CreateFileMappingA 在打开已存在的映射时也会返回有效句柄
+ const DWORD create_error = GetLastError();
+ const bool already_exists = (create_error == ERROR_ALREADY_EXISTS);
+
+ segment->data_ = static_cast(MapViewOfFile(
+ segment->handle_,
+ FILE_MAP_ALL_ACCESS,
+ 0,
+ 0,
+ size
+ ));
+
+ if (segment->data_ == nullptr) {
+ // 映射失败时关闭句柄避免泄漏
+ CloseHandle(segment->handle_);
+ segment->handle_ = nullptr;
+ return std::unexpected(ShmTransportError::ShmMapFailed);
+ }
+
+ // 如果是已存在的映射,在映射成功后返回错误
+ // 注意:必须先关闭句柄和解除映射
+ if (already_exists) {
+ UnmapViewOfFile(segment->data_);
+ CloseHandle(segment->handle_);
+ segment->data_ = nullptr;
+ segment->handle_ = nullptr;
+ return std::unexpected(ShmTransportError::AlreadyExists);
+ }
+
+ std::memset(segment->data_, 0, size);
+ return segment;
+ }
+
+ [[nodiscard]] static std::expected, ShmTransportError>
+ Open(std::string_view name, std::size_t size)
+ {
+ if (name.empty() || size == 0) {
+ return std::unexpected(ShmTransportError::InvalidArgument);
+ }
+
+ auto segment = std::make_unique();
+ segment->name_ = std::string(name);
+ segment->size_ = size;
+
+ segment->handle_ = OpenFileMappingA(
+ FILE_MAP_ALL_ACCESS,
+ FALSE,
+ segment->name_.c_str()
+ );
+
+ if (segment->handle_ == nullptr) {
+ const DWORD error = GetLastError();
+ if (error == ERROR_FILE_NOT_FOUND) {
+ return std::unexpected(ShmTransportError::NotFound);
+ }
+ return std::unexpected(ShmTransportError::ShmOpenFailed);
+ }
+
+ segment->data_ = static_cast(MapViewOfFile(
+ segment->handle_,
+ FILE_MAP_ALL_ACCESS,
+ 0,
+ 0,
+ size
+ ));
+
+ if (segment->data_ == nullptr) {
+ return std::unexpected(ShmTransportError::ShmMapFailed);
+ }
+
+ return segment;
+ }
+
+ [[nodiscard]] std::byte* data() const noexcept override { return data_; }
+ [[nodiscard]] std::size_t size() const noexcept override { return size_; }
+ [[nodiscard]] std::string_view name() const noexcept override { return name_; }
+ [[nodiscard]] bool is_valid() const noexcept override { return handle_ != nullptr && data_ != nullptr; }
+
+ [[nodiscard]] Result flush() noexcept override
+ {
+ if (data_ == nullptr) {
+ return std::unexpected(ShmTransportError::NotInitialized);
+ }
+ if (!FlushViewOfFile(data_, size_)) {
+ return std::unexpected(ShmTransportError::Unknown);
+ }
+ return {};
+ }
+
+private:
+ void cleanup() noexcept
+ {
+ if (data_ != nullptr) {
+ UnmapViewOfFile(data_);
+ data_ = nullptr;
+ }
+ if (handle_ != nullptr) {
+ CloseHandle(handle_);
+ handle_ = nullptr;
+ }
+ }
+
+ std::string name_;
+ std::size_t size_{0};
+ HANDLE handle_{nullptr};
+ std::byte* data_{nullptr};
+};
+
+using PlatformSharedMemorySegment = WindowsSharedMemorySegment;
+
+// ==========================================================================
+// POSIX 共享内存段实现
+// ==========================================================================
+#else
+
+class PosixSharedMemorySegment : public SharedMemorySegment
+{
+public:
+ PosixSharedMemorySegment() noexcept = default;
+
+ ~PosixSharedMemorySegment() override
+ {
+ cleanup();
+ }
+
+ PosixSharedMemorySegment(const PosixSharedMemorySegment&) = delete;
+ PosixSharedMemorySegment& operator=(const PosixSharedMemorySegment&) = delete;
+
+ PosixSharedMemorySegment(PosixSharedMemorySegment&& other) noexcept
+ : name_(std::move(other.name_)),
+ size_(other.size_),
+ fd_(other.fd_),
+ data_(other.data_),
+ created_(other.created_)
+ {
+ other.fd_ = -1;
+ other.data_ = nullptr;
+ other.size_ = 0;
+ other.created_ = false;
+ }
+
+ PosixSharedMemorySegment& operator=(PosixSharedMemorySegment&& other) noexcept
+ {
+ if (this != &other) {
+ cleanup();
+ name_ = std::move(other.name_);
+ size_ = other.size_;
+ fd_ = other.fd_;
+ data_ = other.data_;
+ created_ = other.created_;
+ other.fd_ = -1;
+ other.data_ = nullptr;
+ other.size_ = 0;
+ other.created_ = false;
+ }
+ return *this;
+ }
+
+ [[nodiscard]] static std::expected, ShmTransportError>
+ Create(std::string_view name, std::size_t size)
+ {
+ if (name.empty() || size == 0) {
+ return std::unexpected(ShmTransportError::InvalidArgument);
+ }
+
+ auto segment = std::make_unique();
+ segment->name_ = std::string(name);
+ segment->size_ = size;
+ segment->created_ = true;
+
+ segment->fd_ = shm_open(segment->name_.c_str(), O_CREAT | O_EXCL | O_RDWR, 0666);
+ if (segment->fd_ == -1) {
+ if (errno == EEXIST) {
+ return std::unexpected(ShmTransportError::AlreadyExists);
+ }
+ return std::unexpected(ShmTransportError::ShmCreateFailed);
+ }
+
+ if (ftruncate(segment->fd_, static_cast(size)) == -1) {
+ shm_unlink(segment->name_.c_str());
+ return std::unexpected(ShmTransportError::ShmCreateFailed);
+ }
+
+ segment->data_ = static_cast(mmap(
+ nullptr,
+ size,
+ PROT_READ | PROT_WRITE,
+ MAP_SHARED,
+ segment->fd_,
+ 0
+ ));
+
+ if (segment->data_ == MAP_FAILED) {
+ shm_unlink(segment->name_.c_str());
+ segment->data_ = nullptr;
+ return std::unexpected(ShmTransportError::ShmMapFailed);
+ }
+
+ std::memset(segment->data_, 0, size);
+ return segment;
+ }
+
+ [[nodiscard]] static std::expected, ShmTransportError>
+ Open(std::string_view name, std::size_t size)
+ {
+ if (name.empty() || size == 0) {
+ return std::unexpected(ShmTransportError::InvalidArgument);
+ }
+
+ auto segment = std::make_unique();
+ segment->name_ = std::string(name);
+ segment->size_ = size;
+ segment->created_ = false;
+
+ segment->fd_ = shm_open(segment->name_.c_str(), O_RDWR, 0666);
+ if (segment->fd_ == -1) {
+ if (errno == ENOENT) {
+ return std::unexpected(ShmTransportError::NotFound);
+ }
+ return std::unexpected(ShmTransportError::ShmOpenFailed);
+ }
+
+ segment->data_ = static_cast(mmap(
+ nullptr,
+ size,
+ PROT_READ | PROT_WRITE,
+ MAP_SHARED,
+ segment->fd_,
+ 0
+ ));
+
+ if (segment->data_ == MAP_FAILED) {
+ segment->data_ = nullptr;
+ return std::unexpected(ShmTransportError::ShmMapFailed);
+ }
+
+ return segment;
+ }
+
+ [[nodiscard]] std::byte* data() const noexcept override { return data_; }
+ [[nodiscard]] std::size_t size() const noexcept override { return size_; }
+ [[nodiscard]] std::string_view name() const noexcept override { return name_; }
+ [[nodiscard]] bool is_valid() const noexcept override { return fd_ != -1 && data_ != nullptr; }
+
+ [[nodiscard]] Result flush() noexcept override
+ {
+ if (data_ == nullptr) {
+ return std::unexpected(ShmTransportError::NotInitialized);
+ }
+ if (msync(data_, size_, MS_SYNC) == -1) {
+ return std::unexpected(ShmTransportError::Unknown);
+ }
+ return {};
+ }
+
+private:
+ void cleanup() noexcept
+ {
+ if (data_ != nullptr) {
+ munmap(data_, size_);
+ data_ = nullptr;
+ }
+ if (fd_ != -1) {
+ close(fd_);
+ fd_ = -1;
+ }
+ if (created_ && !name_.empty()) {
+ shm_unlink(name_.c_str());
+ }
+ }
+
+ std::string name_;
+ std::size_t size_{0};
+ int fd_{-1};
+ std::byte* data_{nullptr};
+ bool created_{false};
+};
+
+using PlatformSharedMemorySegment = PosixSharedMemorySegment;
+
+#endif // _WIN32
+
+// ==========================================================================
+// SharedMemorySegment 工厂方法实现
+// ==========================================================================
+
+std::expected, ShmTransportError>
+SharedMemorySegment::Create(std::string_view name, std::size_t size)
+{
+ return PlatformSharedMemorySegment::Create(name, size);
+}
+
+std::expected, ShmTransportError>
+SharedMemorySegment::Open(std::string_view name, std::size_t size)
+{
+ return PlatformSharedMemorySegment::Open(name, size);
+}
+
+// ==========================================================================
+// ShmTransport 实现
+// ==========================================================================
+
+ShmTransport::~ShmTransport() noexcept
+{
+ [[maybe_unused]] auto result = Shutdown();
+}
+
+ShmTransport::ShmTransport(ShmTransport&& other) noexcept
+ : config_(std::move(other.config_)),
+ segment_(std::move(other.segment_)),
+ control_block_(other.control_block_),
+ frame_data_base_(other.frame_data_base_),
+ initialized_(other.initialized_.load()),
+ is_producer_(other.is_producer_),
+ stats_(std::move(other.stats_))
+{
+ other.control_block_ = nullptr;
+ other.frame_data_base_ = nullptr;
+ other.initialized_ = false;
+ other.is_producer_ = false;
+}
+
+ShmTransport& ShmTransport::operator=(ShmTransport&& other) noexcept
+{
+ if (this != &other) {
+ [[maybe_unused]] auto _ = Shutdown();
+ config_ = std::move(other.config_);
+ segment_ = std::move(other.segment_);
+ control_block_ = other.control_block_;
+ frame_data_base_ = other.frame_data_base_;
+ initialized_ = other.initialized_.load();
+ is_producer_ = other.is_producer_;
+ stats_ = std::move(other.stats_);
+ other.control_block_ = nullptr;
+ other.frame_data_base_ = nullptr;
+ other.initialized_ = false;
+ other.is_producer_ = false;
+ }
+ return *this;
+}
+
+ShmTransport::Result ShmTransport::InitializeProducer(const ShmTransportConfig& config)
+{
+ if (initialized_.load(std::memory_order_acquire)) {
+ return std::unexpected(ShmTransportError::AlreadyExists);
+ }
+
+ auto validation = config.validate();
+ if (!validation) {
+ return validation;
+ }
+
+ const std::string full_name = MakeShmName(config.shm_name);
+ const std::size_t total_size = config.calculate_total_size();
+
+ auto segment_result = SharedMemorySegment::Create(full_name, total_size);
+ if (!segment_result) {
+ return std::unexpected(segment_result.error());
+ }
+
+ segment_ = std::move(*segment_result);
+ config_ = config;
+ is_producer_ = true;
+
+ control_block_ = reinterpret_cast(segment_->data());
+ frame_data_base_ = segment_->data() + sizeof(ShmControlBlock) + config.metadata_size;
+
+ const std::size_t frame_header_size = sizeof(ShmFrameHeader);
+ const std::size_t aligned_frame_size =
+ ((frame_header_size + config.max_frame_size + 63) / 64) * 64;
+
+ control_block_->magic = ShmControlBlock::MAGIC;
+ control_block_->version = ShmControlBlock::VERSION;
+ control_block_->header_size = static_cast(sizeof(ShmControlBlock) + config.metadata_size);
+ control_block_->flags = 0;
+ control_block_->buffer_count = static_cast(config.buffer_count);
+ control_block_->max_frame_size = static_cast(config.max_frame_size);
+ control_block_->metadata_size = static_cast(config.metadata_size);
+ control_block_->frame_stride = static_cast(aligned_frame_size & 0xFFFFFFFFULL);
+
+ control_block_->write_sequence.store(0, std::memory_order_relaxed);
+ control_block_->read_sequence.store(0, std::memory_order_relaxed);
+ control_block_->dropped_frames.store(0, std::memory_order_relaxed);
+ control_block_->state.store(ShmControlBlock::STATE_INITIALIZED, std::memory_order_relaxed);
+ control_block_->active_readers.store(0, std::memory_order_relaxed);
+ control_block_->active_writers.store(1, std::memory_order_relaxed);
+ control_block_->total_frames_written.store(0, std::memory_order_relaxed);
+ control_block_->total_frames_read.store(0, std::memory_order_relaxed);
+ control_block_->total_bytes_written.store(0, std::memory_order_relaxed);
+ control_block_->total_bytes_read.store(0, std::memory_order_relaxed);
+
+ stats_.reset();
+ initialized_.store(true, std::memory_order_release);
+
+ return {};
+}
+
+ShmTransport::Result ShmTransport::InitializeConsumer(const ShmTransportConfig& config)
+{
+ if (initialized_.load(std::memory_order_acquire)) {
+ return std::unexpected(ShmTransportError::AlreadyExists);
+ }
+
+ auto validation = config.validate();
+ if (!validation) {
+ return validation;
+ }
+
+ const std::string full_name = MakeShmName(config.shm_name);
+ const std::size_t total_size = config.calculate_total_size();
+
+ auto segment_result = SharedMemorySegment::Open(full_name, total_size);
+ if (!segment_result) {
+ return std::unexpected(segment_result.error());
+ }
+
+ segment_ = std::move(*segment_result);
+ config_ = config;
+ is_producer_ = false;
+
+ control_block_ = reinterpret_cast(segment_->data());
+ frame_data_base_ = segment_->data() + sizeof(ShmControlBlock) + config.metadata_size;
+
+ if (!control_block_->is_valid()) {
+ segment_.reset();
+ control_block_ = nullptr;
+ frame_data_base_ = nullptr;
+ return std::unexpected(ShmTransportError::ShmCorrupted);
+ }
+
+ control_block_->active_readers.fetch_add(1, std::memory_order_relaxed);
+
+ stats_.reset();
+ initialized_.store(true, std::memory_order_release);
+
+ return {};
+}
+
+ShmTransport::Result ShmTransport::Shutdown() noexcept
+{
+ if (!initialized_.load(std::memory_order_acquire)) {
+ return {};
+ }
+
+ if (control_block_ != nullptr && !is_producer_) {
+ control_block_->active_readers.fetch_sub(1, std::memory_order_relaxed);
+ }
+
+ segment_.reset();
+ control_block_ = nullptr;
+ frame_data_base_ = nullptr;
+ initialized_.store(false, std::memory_order_release);
+ is_producer_ = false;
+
+ return {};
+}
+
+bool ShmTransport::IsInitialized() const noexcept
+{
+ return initialized_.load(std::memory_order_acquire);
+}
+
+bool ShmTransport::IsProducer() const noexcept
+{
+ return initialized_.load(std::memory_order_acquire) && is_producer_;
+}
+
+bool ShmTransport::IsConsumer() const noexcept
+{
+ return initialized_.load(std::memory_order_acquire) && !is_producer_;
+}
+
+ShmTransport::Result ShmTransport::WriteFrame(const FrameMetadata& metadata,
+ std::span data)
+{
+ // 前置校验:检查空数据
+ if (data.empty()) {
+ return std::unexpected(ShmTransportError::InvalidFrame);
+ }
+
+ // 如果启用零拷贝模式,使用零拷贝 API
+ if (config_.enable_zero_copy) {
+ auto buffer_result = AcquireWriteBuffer(config_.write_timeout);
+ if (!buffer_result) {
+ return std::unexpected(buffer_result.error());
+ }
+ if (!buffer_result->has_value()) {
+ return std::unexpected(ShmTransportError::Timeout);
+ }
+
+ auto& buffer = buffer_result->value();
+ if (data.size() > buffer.capacity) {
+ return std::unexpected(ShmTransportError::FrameTooLarge);
+ }
+
+ // 直接复制到共享内存缓冲区
+ std::memcpy(buffer.data, data.data(), data.size());
+
+ return CommitWriteBuffer(buffer.buffer_index, metadata, data.size());
+ }
+
+ // 非零拷贝模式:使用传统路径
+ auto result = WriteFrameWithTimeout(metadata, data, config_.write_timeout);
+ if (!result) {
+ return std::unexpected(result.error());
+ }
+ return {};
+}
+
+std::expected ShmTransport::TryWriteFrame(
+ const FrameMetadata& metadata, std::span data)
+{
+ if (!initialized_.load(std::memory_order_acquire) || !is_producer_) {
+ return std::unexpected(ShmTransportError::NotInitialized);
+ }
+
+ if (!metadata.is_valid()) {
+ return std::unexpected(ShmTransportError::InvalidFrame);
+ }
+
+ if (data.size() > config_.max_frame_size) {
+ return std::unexpected(ShmTransportError::FrameTooLarge);
+ }
+
+ if (!control_block_->is_writable()) {
+ return false;
+ }
+
+ const auto start_time = core::Clock::now();
+ const std::uint32_t write_index = control_block_->get_write_index();
+
+ ShmFrameHeader* header = get_frame_header(write_index);
+ std::byte* frame_data = get_frame_buffer(write_index) + sizeof(ShmFrameHeader);
+
+ header->magic = ShmFrameHeader::MAGIC;
+ header->version = 1;
+ header->sequence_number = static_cast(
+ control_block_->write_sequence.load(std::memory_order_relaxed));
+ header->frame_number = static_cast(metadata.frame_number);
+ header->sequence_id = static_cast(metadata.sequence_id);
+ header->data_size = static_cast(data.size());
+ header->capture_timestamp_ns = static_cast(
+ metadata.capture_timestamp.time_since_epoch().count());
+ header->write_timestamp_ns = static_cast(
+ core::Clock::now().time_since_epoch().count());
+ header->width = metadata.width;
+ header->height = metadata.height;
+ header->stride = metadata.stride;
+ header->pixel_format = static_cast(metadata.pixel_format);
+
+ std::memcpy(frame_data, data.data(), data.size());
+
+ // 仅在启用校验和时计算CRC
+ if (config_.enable_checksum) {
+ header->checksum = header->calculate_checksum(frame_data);
+ } else {
+ header->checksum = 0;
+ }
+
+ control_block_->write_sequence.fetch_add(1, std::memory_order_release);
+ control_block_->total_frames_written.fetch_add(1, std::memory_order_relaxed);
+ control_block_->total_bytes_written.fetch_add(data.size(), std::memory_order_relaxed);
+
+ const auto latency = core::Clock::now() - start_time;
+ update_stats(true, data.size(), latency);
+
+ return true;
+}
+
+std::expected ShmTransport::WriteFrameWithTimeout(
+ const FrameMetadata& metadata, std::span data, core::Duration timeout)
+{
+ if (!initialized_.load(std::memory_order_acquire) || !is_producer_) {
+ return std::unexpected(ShmTransportError::NotInitialized);
+ }
+
+ const auto deadline = core::Clock::now() + timeout;
+
+ while (core::Clock::now() < deadline) {
+ auto result = TryWriteFrame(metadata, data);
+ if (!result) {
+ return result;
+ }
+ if (*result) {
+ return true;
+ }
+ std::this_thread::yield();
+ }
+
+ control_block_->dropped_frames.fetch_add(1, std::memory_order_relaxed);
+ stats_.write_timeouts++;
+ return false;
+}
+
+std::expected>>, ShmTransportError>
+ShmTransport::ReadFrame(core::Duration timeout)
+{
+ if (!initialized_.load(std::memory_order_acquire) || is_producer_) {
+ return std::unexpected(ShmTransportError::NotInitialized);
+ }
+
+ // 如果启用零拷贝模式,使用零拷贝 API 读取
+ if (config_.enable_zero_copy) {
+ auto buffer_result = AcquireReadBuffer(timeout);
+ if (!buffer_result) {
+ return std::unexpected(buffer_result.error());
+ }
+ if (!buffer_result->has_value()) {
+ stats_.read_timeouts++;
+ return std::optional>>{};
+ }
+
+ const auto& buffer = buffer_result->value();
+
+ // 复制数据到本地缓冲区
+ std::vector data(buffer.data.size());
+ std::memcpy(data.data(), buffer.data.data(), buffer.data.size());
+
+ FrameMetadata metadata = buffer.metadata;
+
+ // 释放读取缓冲区
+ auto release_result = ReleaseReadBuffer(buffer.buffer_index);
+ if (!release_result.has_value()) {
+ return std::unexpected(release_result.error());
+ }
+
+ return std::make_optional(std::make_pair(std::move(metadata), std::move(data)));
+ }
+
+ // 非零拷贝模式:使用传统路径
+ const auto deadline = core::Clock::now() + timeout;
+
+ while (core::Clock::now() < deadline) {
+ auto result = TryReadFrame();
+ if (!result) {
+ return result;
+ }
+ if (result->has_value()) {
+ return result;
+ }
+ std::this_thread::yield();
+ }
+
+ stats_.read_timeouts++;
+ return std::optional>>{};
+}
+
+std::expected>>, ShmTransportError>
+ShmTransport::TryReadFrame()
+{
+ if (!initialized_.load(std::memory_order_acquire) || is_producer_) {
+ return std::unexpected(ShmTransportError::NotInitialized);
+ }
+
+ if (!control_block_->has_readable_data()) {
+ return std::optional>>{};
+ }
+
+ const auto start_time = core::Clock::now();
+ const std::uint32_t read_index = control_block_->get_read_index();
+
+ ShmFrameHeader* header = get_frame_header(read_index);
+ std::byte* frame_data = get_frame_buffer(read_index) + sizeof(ShmFrameHeader);
+
+ if (!header->is_valid()) {
+ return std::unexpected(ShmTransportError::ShmCorrupted);
+ }
+
+ // 仅在启用校验和时验证数据完整性
+ if (config_.enable_checksum && !header->verify_data(frame_data)) {
+ stats_.checksum_errors++;
+ control_block_->read_sequence.fetch_add(1, std::memory_order_release);
+ return std::unexpected(ShmTransportError::ShmCorrupted);
+ }
+
+ FrameMetadata metadata;
+ metadata.width = header->width;
+ metadata.height = header->height;
+ metadata.stride = header->stride;
+ metadata.pixel_format = static_cast(header->pixel_format);
+ metadata.capture_timestamp = core::Timestamp(std::chrono::nanoseconds(header->capture_timestamp_ns));
+ metadata.process_timestamp = core::Clock::now();
+ metadata.frame_number = header->frame_number;
+ metadata.sequence_id = header->sequence_id;
+ metadata.data_size = header->data_size;
+
+ std::vector data(header->data_size);
+ std::memcpy(data.data(), frame_data, header->data_size);
+
+ control_block_->read_sequence.fetch_add(1, std::memory_order_release);
+ control_block_->total_frames_read.fetch_add(1, std::memory_order_relaxed);
+ control_block_->total_bytes_read.fetch_add(header->data_size, std::memory_order_relaxed);
+
+ const auto latency = core::Clock::now() - start_time;
+ update_stats(false, header->data_size, latency);
+
+ return std::make_optional(std::make_pair(std::move(metadata), std::move(data)));
+}
+
+ShmTransport::Result ShmTransport::ReadFrameWithCallback(core::Duration timeout, ReadCallback callback)
+{
+ if (!callback) {
+ return std::unexpected(ShmTransportError::InvalidArgument);
+ }
+
+ auto result = ReadFrame(timeout);
+ if (!result) {
+ return std::unexpected(result.error());
+ }
+
+ if (result->has_value()) {
+ const auto& [metadata, data] = result->value();
+ callback(metadata, std::span(data.data(), data.size()));
+ }
+
+ return {};
+}
+
+ShmTransportStats ShmTransport::GetStats() const noexcept
+{
+ std::lock_guard lock(stats_mutex_);
+ ShmTransportStats result = stats_; // 使用显式拷贝构造函数
+
+ if (control_block_ != nullptr) {
+ result.frames_dropped = control_block_->dropped_frames.load(std::memory_order_relaxed);
+ }
+ return result;
+}
+
+std::pair ShmTransport::GetBufferStatus() const noexcept
+{
+ if (!initialized_.load(std::memory_order_acquire) || control_block_ == nullptr) {
+ return {0, 0};
+ }
+ return {control_block_->used_buffers(), control_block_->buffer_count};
+}
+
+void ShmTransport::update_stats(bool is_write, std::size_t bytes, core::Duration latency) noexcept
+{
+ std::lock_guard lock(stats_mutex_);
+ if (is_write) {
+ stats_.frames_written++;
+ stats_.bytes_written += bytes;
+ stats_.update_write_latency(latency);
+ stats_.last_write_time = core::Clock::now();
+ } else {
+ stats_.frames_read++;
+ stats_.bytes_read += bytes;
+ stats_.update_read_latency(latency);
+ stats_.last_read_time = core::Clock::now();
+ }
+}
+
+ShmFrameHeader* ShmTransport::get_frame_header(std::uint32_t index) noexcept
+{
+ return reinterpret_cast(get_frame_buffer(index));
+}
+
+std::byte* ShmTransport::get_frame_buffer(std::uint32_t index) noexcept
+{
+ if (control_block_ == nullptr || segment_ == nullptr) {
+ return nullptr;
+ }
+ const std::size_t header_size = sizeof(ShmControlBlock) + config_.metadata_size;
+ const std::size_t offset = header_size + (index * control_block_->frame_stride);
+ return segment_->data() + offset;
+}
+
+// ==========================================================================
+// 辅助函数实现
+// ==========================================================================
+
+std::string MakeShmName(std::string_view name)
+{
+#ifdef _WIN32
+ return std::string("Local\\") + std::string(name);
+#else
+ return std::string("/") + std::string(name);
+#endif
+}
+
+bool ShmExists(std::string_view name)
+{
+#ifdef _WIN32
+ const std::string full_name = MakeShmName(name);
+ HANDLE handle = OpenFileMappingA(FILE_MAP_READ, FALSE, full_name.c_str());
+ if (handle != nullptr) {
+ CloseHandle(handle);
+ return true;
+ }
+ return false;
+#else
+ const std::string full_name = MakeShmName(name);
+ int fd = shm_open(full_name.c_str(), O_RDONLY, 0666);
+ if (fd != -1) {
+ close(fd);
+ return true;
+ }
+ return false;
+#endif
+}
+
+bool ShmRemove([[maybe_unused]] std::string_view name)
+{
+#ifdef _WIN32
+ // Windows: 共享内存在最后一个句柄关闭后自动删除
+ (void)name;
+ return true;
+#else
+ const std::string full_name = MakeShmName(name);
+ return shm_unlink(full_name.c_str()) == 0 || errno == ENOENT;
+#endif
+}
+
+
+// ==========================================================================
+// 零拷贝API实现
+// ==========================================================================
+
+std::expected, ShmTransportError>
+ShmTransport::AcquireWriteBuffer(core::Duration timeout)
+{
+ if (!initialized_.load(std::memory_order_acquire) || !is_producer_) {
+ return std::unexpected(ShmTransportError::NotInitialized);
+ }
+
+ const auto deadline = core::Clock::now() + timeout;
+
+ while (core::Clock::now() < deadline) {
+ auto result = TryAcquireWriteBuffer();
+ if (!result) {
+ return result;
+ }
+ if (result->has_value()) {
+ return result;
+ }
+ std::this_thread::yield();
+ }
+
+ stats_.write_timeouts++;
+ return std::optional{};
+}
+
+std::expected, ShmTransportError>
+ShmTransport::TryAcquireWriteBuffer()
+{
+ if (!initialized_.load(std::memory_order_acquire) || !is_producer_) {
+ return std::unexpected(ShmTransportError::NotInitialized);
+ }
+
+ if (!control_block_->is_writable()) {
+ return std::optional{};
+ }
+
+ const std::uint32_t write_index = control_block_->get_write_index();
+ std::byte* frame_data = get_frame_buffer(write_index) + sizeof(ShmFrameHeader);
+
+ WriteBuffer buffer;
+ buffer.data = frame_data;
+ buffer.capacity = config_.max_frame_size;
+ buffer.buffer_index = write_index;
+
+ return std::make_optional(buffer);
+}
+
+ShmTransport::Result ShmTransport::CommitWriteBuffer(
+ std::uint32_t buffer_index,
+ const FrameMetadata& metadata,
+ std::size_t actual_size)
+{
+ if (!initialized_.load(std::memory_order_acquire) || !is_producer_) {
+ return std::unexpected(ShmTransportError::NotInitialized);
+ }
+
+ if (actual_size > config_.max_frame_size) {
+ return std::unexpected(ShmTransportError::FrameTooLarge);
+ }
+
+ const auto start_time = core::Clock::now();
+ ShmFrameHeader* header = get_frame_header(buffer_index);
+ std::byte* frame_data = get_frame_buffer(buffer_index) + sizeof(ShmFrameHeader);
+
+ // 填充帧头部(零拷贝模式下跳过CRC计算)
+ header->magic = ShmFrameHeader::MAGIC;
+ header->version = 1;
+ header->sequence_number = static_cast(
+ control_block_->write_sequence.load(std::memory_order_relaxed));
+ header->frame_number = static_cast(metadata.frame_number);
+ header->sequence_id = static_cast(metadata.sequence_id);
+ header->data_size = static_cast(actual_size);
+ header->capture_timestamp_ns = static_cast(
+ metadata.capture_timestamp.time_since_epoch().count());
+ header->write_timestamp_ns = static_cast(
+ core::Clock::now().time_since_epoch().count());
+ header->width = metadata.width;
+ header->height = metadata.height;
+ header->stride = metadata.stride;
+ header->pixel_format = static_cast(metadata.pixel_format);
+
+ // 仅在启用校验和时计算CRC
+ if (config_.enable_checksum) {
+ header->checksum = header->calculate_checksum(frame_data);
+ } else {
+ header->checksum = 0;
+ }
+
+ // 发布内存屏障,确保数据在序列号更新前可见
+ std::atomic_thread_fence(std::memory_order_release);
+
+ control_block_->write_sequence.fetch_add(1, std::memory_order_release);
+ control_block_->total_frames_written.fetch_add(1, std::memory_order_relaxed);
+ control_block_->total_bytes_written.fetch_add(actual_size, std::memory_order_relaxed);
+
+ const auto latency = core::Clock::now() - start_time;
+ update_stats(true, actual_size, latency);
+
+ return {};
+}
+
+std::expected, ShmTransportError>
+ShmTransport::AcquireReadBuffer(core::Duration timeout)
+{
+ if (!initialized_.load(std::memory_order_acquire) || is_producer_) {
+ return std::unexpected(ShmTransportError::NotInitialized);
+ }
+
+ const auto deadline = core::Clock::now() + timeout;
+
+ while (core::Clock::now() < deadline) {
+ auto result = TryAcquireReadBuffer();
+ if (!result) {
+ return result;
+ }
+ if (result->has_value()) {
+ return result;
+ }
+ std::this_thread::yield();
+ }
+
+ stats_.read_timeouts++;
+ return std::optional{};
+}
+
+std::expected, ShmTransportError>
+ShmTransport::TryAcquireReadBuffer()
+{
+ if (!initialized_.load(std::memory_order_acquire) || is_producer_) {
+ return std::unexpected(ShmTransportError::NotInitialized);
+ }
+
+ if (!control_block_->has_readable_data()) {
+ return std::optional{};
+ }
+
+ const std::uint32_t read_index = control_block_->get_read_index();
+ ShmFrameHeader* header = get_frame_header(read_index);
+ std::byte* frame_data = get_frame_buffer(read_index) + sizeof(ShmFrameHeader);
+
+ // 获取内存屏障,确保看到最新的数据
+ std::atomic_thread_fence(std::memory_order_acquire);
+
+ if (!header->is_valid()) {
+ return std::unexpected(ShmTransportError::ShmCorrupted);
+ }
+
+ // 仅在启用校验和时验证
+ if (config_.enable_checksum && !header->verify_data(frame_data)) {
+ stats_.checksum_errors++;
+ // 跳过损坏的帧
+ control_block_->read_sequence.fetch_add(1, std::memory_order_release);
+ return std::unexpected(ShmTransportError::ShmCorrupted);
+ }
+
+ FrameMetadata metadata;
+ metadata.width = header->width;
+ metadata.height = header->height;
+ metadata.stride = header->stride;
+ metadata.pixel_format = static_cast(header->pixel_format);
+ metadata.capture_timestamp = core::Timestamp(std::chrono::nanoseconds(header->capture_timestamp_ns));
+ metadata.process_timestamp = core::Clock::now();
+ metadata.frame_number = header->frame_number;
+ metadata.sequence_id = header->sequence_id;
+ metadata.data_size = header->data_size;
+
+ ReadBuffer buffer;
+ buffer.metadata = metadata;
+ buffer.data = std::span(frame_data, header->data_size);
+ buffer.buffer_index = read_index;
+
+ return std::make_optional(buffer);
+}
+
+ShmTransport::Result ShmTransport::ReleaseReadBuffer(std::uint32_t buffer_index)
+{
+ if (!initialized_.load(std::memory_order_acquire) || is_producer_) {
+ return std::unexpected(ShmTransportError::NotInitialized);
+ }
+
+ [[maybe_unused]] const auto start_time = core::Clock::now();
+ ShmFrameHeader* header = get_frame_header(buffer_index);
+
+ control_block_->read_sequence.fetch_add(1, std::memory_order_release);
+ control_block_->total_frames_read.fetch_add(1, std::memory_order_relaxed);
+ control_block_->total_bytes_read.fetch_add(header->data_size, std::memory_order_relaxed);
+
+ const auto latency = core::Clock::now() - start_time;
+ update_stats(false, header->data_size, latency);
+
+ return {};
+}
+
+} // namespace aam::l0
\ No newline at end of file
diff --git a/core/tests/CMakeLists.txt b/core/tests/CMakeLists.txt
index cd08c7d..502f969 100644
--- a/core/tests/CMakeLists.txt
+++ b/core/tests/CMakeLists.txt
@@ -29,6 +29,7 @@ set(CORE_TEST_SOURCES
test_memory_pool.cpp
test_timer.cpp
test_frame_buffer.cpp
+ test_shm_transport.cpp
)
# 创建测试可执行文件
diff --git a/core/tests/test_shm_transport.cpp b/core/tests/test_shm_transport.cpp
new file mode 100644
index 0000000..affff8c
--- /dev/null
+++ b/core/tests/test_shm_transport.cpp
@@ -0,0 +1,1431 @@
+// ==========================================================================
+// Copyright (C) 2026 Ethernos Studio
+// This file is part of Arknights Auto Machine (AAM).
+//
+// AAM is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published
+// by the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// AAM is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with AAM. If not, see .
+// ==========================================================================
+// @file test_shm_transport.cpp
+// @author dhjs0000
+// @brief 共享内存传输层单元测试
+// ==========================================================================
+
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "aam/l0/shm_transport.hpp"
+
+using namespace aam::l0;
+using namespace std::chrono_literals;
+
+// 使用 Clock 别名避免 core:: 前缀
+using Clock = aam::core::Clock;
+
+// ==========================================================================
+// 测试夹具
+// ==========================================================================
+
+class ShmTransportTest : public ::testing::Test
+{
+protected:
+ void SetUp() override
+ {
+ // 清理可能存在的旧共享内存
+ [[maybe_unused]] auto _ = ShmRemove(test_shm_name_);
+ }
+
+ void TearDown() override
+ {
+ // 清理测试创建的共享内存
+ [[maybe_unused]] auto _ = ShmRemove(test_shm_name_);
+ }
+
+ // 生成唯一测试名称
+ static std::string GenerateUniqueName()
+ {
+ static std::atomic counter{0};
+ const auto now = std::chrono::steady_clock::now().time_since_epoch().count();
+ return "aam_test_" + std::to_string(now) + "_" + std::to_string(counter.fetch_add(1));
+ }
+
+ const std::string test_shm_name_ = GenerateUniqueName();
+ static constexpr std::size_t kTestBufferCount = 16; // 增加缓冲区大小以支持多帧测试
+ static constexpr std::size_t kTestFrameSize = 1024 * 1024; // 1MB
+};
+
+// ==========================================================================
+// 基础功能测试
+// ==========================================================================
+
+TEST_F(ShmTransportTest, ConfigValidation)
+{
+ ShmTransportConfig config;
+
+ // 有效配置
+ config.shm_name = "test";
+ config.buffer_count = 4;
+ config.max_frame_size = 1024;
+ EXPECT_TRUE(config.validate().has_value());
+
+ // 无效:空名称
+ config.shm_name = "";
+ EXPECT_FALSE(config.validate().has_value());
+ config.shm_name = "test";
+
+ // 无效:零缓冲区
+ config.buffer_count = 0;
+ EXPECT_FALSE(config.validate().has_value());
+ config.buffer_count = 4;
+
+ // 无效:过多缓冲区
+ config.buffer_count = 100;
+ EXPECT_FALSE(config.validate().has_value());
+ config.buffer_count = 4;
+
+ // 无效:零帧大小
+ config.max_frame_size = 0;
+ EXPECT_FALSE(config.validate().has_value());
+ config.max_frame_size = 1024;
+
+ // 无效:过大帧大小
+ config.max_frame_size = 300 * 1024 * 1024; // 300MB
+ EXPECT_FALSE(config.validate().has_value());
+}
+
+TEST_F(ShmTransportTest, ConfigSizeCalculation)
+{
+ ShmTransportConfig config;
+ config.buffer_count = 4;
+ config.max_frame_size = 1024;
+ config.metadata_size = 4096;
+
+ const std::size_t total_size = config.calculate_total_size();
+
+ // 验证大小大于等于头部 + 缓冲区(考虑对齐后可能相等)
+ EXPECT_GE(total_size, sizeof(ShmControlBlock) + config.metadata_size);
+ EXPECT_GE(total_size, sizeof(ShmControlBlock) + config.metadata_size +
+ 4 * (sizeof(ShmFrameHeader) + 1024));
+}
+
+TEST_F(ShmTransportTest, ProducerInitialization)
+{
+ ShmTransport producer;
+
+ ShmTransportConfig config;
+ config.shm_name = test_shm_name_;
+ config.buffer_count = kTestBufferCount;
+ config.max_frame_size = kTestFrameSize;
+
+ // 初始化生产者
+ auto result = producer.InitializeProducer(config);
+ EXPECT_TRUE(result.has_value()) << "Producer initialization failed";
+ EXPECT_TRUE(producer.IsInitialized());
+ EXPECT_TRUE(producer.IsProducer());
+ EXPECT_FALSE(producer.IsConsumer());
+
+ // 获取控制块验证
+ const auto* control_block = producer.GetControlBlock();
+ ASSERT_NE(control_block, nullptr);
+ EXPECT_TRUE(control_block->is_valid());
+ EXPECT_EQ(control_block->buffer_count, kTestBufferCount);
+ EXPECT_EQ(control_block->max_frame_size, kTestFrameSize);
+
+ // 关闭
+ auto shutdown_result = producer.Shutdown();
+ EXPECT_TRUE(shutdown_result.has_value());
+ EXPECT_FALSE(producer.IsInitialized());
+}
+
+TEST_F(ShmTransportTest, ConsumerInitialization)
+{
+ // 先创建生产者
+ ShmTransport producer;
+ ShmTransportConfig config;
+ config.shm_name = test_shm_name_;
+ config.buffer_count = kTestBufferCount;
+ config.max_frame_size = kTestFrameSize;
+
+ ASSERT_TRUE(producer.InitializeProducer(config).has_value());
+
+ // 再创建消费者
+ ShmTransport consumer;
+ auto result = consumer.InitializeConsumer(config);
+ EXPECT_TRUE(result.has_value()) << "Consumer initialization failed";
+ EXPECT_TRUE(consumer.IsInitialized());
+ EXPECT_FALSE(consumer.IsProducer());
+ EXPECT_TRUE(consumer.IsConsumer());
+
+ // 验证控制块
+ const auto* control_block = consumer.GetControlBlock();
+ ASSERT_NE(control_block, nullptr);
+ EXPECT_TRUE(control_block->is_valid());
+}
+
+TEST_F(ShmTransportTest, ConsumerBeforeProducer)
+{
+ ShmTransport consumer;
+ ShmTransportConfig config;
+ config.shm_name = test_shm_name_;
+ config.buffer_count = kTestBufferCount;
+ config.max_frame_size = kTestFrameSize;
+
+ // 消费者先初始化应该失败(共享内存不存在)
+ auto result = consumer.InitializeConsumer(config);
+ EXPECT_FALSE(result.has_value());
+}
+
+TEST_F(ShmTransportTest, DoubleInitialization)
+{
+ ShmTransport transport;
+ ShmTransportConfig config;
+ config.shm_name = test_shm_name_;
+ config.buffer_count = kTestBufferCount;
+ config.max_frame_size = kTestFrameSize;
+
+ ASSERT_TRUE(transport.InitializeProducer(config).has_value());
+
+ // 重复初始化应该失败
+ auto result = transport.InitializeProducer(config);
+ EXPECT_FALSE(result.has_value());
+}
+
+// ==========================================================================
+// 帧传输测试
+// ==========================================================================
+
+TEST_F(ShmTransportTest, BasicFrameWriteRead)
+{
+ // 创建生产者
+ ShmTransport producer;
+ ShmTransportConfig config;
+ config.shm_name = test_shm_name_;
+ config.buffer_count = kTestBufferCount;
+ config.max_frame_size = kTestFrameSize;
+ config.write_timeout = 100ms;
+ config.read_timeout = 100ms;
+
+ ASSERT_TRUE(producer.InitializeProducer(config).has_value());
+
+ // 创建消费者
+ ShmTransport consumer;
+ ASSERT_TRUE(consumer.InitializeConsumer(config).has_value());
+
+ // 准备测试数据
+ FrameMetadata write_metadata;
+ write_metadata.width = 1920;
+ write_metadata.height = 1080;
+ write_metadata.stride = 1920 * 3;
+ write_metadata.pixel_format = PixelFormat::RGB24;
+ write_metadata.capture_timestamp = Clock::now();
+ write_metadata.process_timestamp = Clock::now();
+ write_metadata.frame_number = 1;
+ write_metadata.sequence_id = 1;
+ write_metadata.data_size = 100;
+
+ std::vector write_data(100);
+ for (size_t i = 0; i < write_data.size(); ++i) {
+ write_data[i] = static_cast(i & 0xFF);
+ }
+
+ // 写入帧
+ auto write_result = producer.WriteFrame(write_metadata, write_data);
+ EXPECT_TRUE(write_result.has_value());
+
+ // 读取帧
+ auto read_result = consumer.ReadFrame(100ms);
+ ASSERT_TRUE(read_result.has_value());
+ ASSERT_TRUE(read_result->has_value());
+
+ const auto& [read_metadata, read_data] = read_result->value();
+
+ // 验证元数据
+ EXPECT_EQ(read_metadata.width, write_metadata.width);
+ EXPECT_EQ(read_metadata.height, write_metadata.height);
+ EXPECT_EQ(read_metadata.stride, write_metadata.stride);
+ EXPECT_EQ(read_metadata.pixel_format, write_metadata.pixel_format);
+ EXPECT_EQ(read_metadata.frame_number, write_metadata.frame_number);
+ EXPECT_EQ(read_metadata.data_size, write_metadata.data_size);
+
+ // 验证数据
+ ASSERT_EQ(read_data.size(), write_data.size());
+ EXPECT_EQ(std::memcmp(read_data.data(), write_data.data(), write_data.size()), 0);
+}
+
+TEST_F(ShmTransportTest, MultipleFrames)
+{
+ ShmTransport producer;
+ ShmTransportConfig config;
+ config.shm_name = test_shm_name_;
+ config.buffer_count = kTestBufferCount;
+ config.max_frame_size = kTestFrameSize;
+
+ ASSERT_TRUE(producer.InitializeProducer(config).has_value());
+
+ ShmTransport consumer;
+ ASSERT_TRUE(consumer.InitializeConsumer(config).has_value());
+
+ const int num_frames = 10;
+
+ // 写入多帧
+ for (int i = 0; i < num_frames; ++i) {
+ FrameMetadata metadata;
+ metadata.width = 1920;
+ metadata.height = 1080;
+ metadata.stride = 1920 * 3;
+ metadata.pixel_format = PixelFormat::RGB24;
+ metadata.capture_timestamp = Clock::now();
+ metadata.process_timestamp = Clock::now();
+ metadata.frame_number = i;
+ metadata.sequence_id = i;
+ metadata.data_size = 100;
+
+ std::vector data(100);
+ data[0] = static_cast(i);
+
+ auto result = producer.WriteFrame(metadata, data);
+ ASSERT_TRUE(result.has_value()) << "Failed to write frame " << i;
+ }
+
+ // 读取多帧
+ for (int i = 0; i < num_frames; ++i) {
+ auto result = consumer.ReadFrame(100ms);
+ ASSERT_TRUE(result.has_value()) << "Failed to read frame " << i;
+ ASSERT_TRUE(result->has_value()) << "No frame " << i << " available";
+
+ const auto& [metadata, data] = result->value();
+ EXPECT_EQ(metadata.frame_number, i);
+ EXPECT_EQ(static_cast(data[0]), i);
+ }
+}
+
+TEST_F(ShmTransportTest, TryWriteFrameNonBlocking)
+{
+ ShmTransport producer;
+ ShmTransportConfig config;
+ config.shm_name = test_shm_name_;
+ config.buffer_count = 2; // 小缓冲区
+ config.max_frame_size = 1024;
+
+ ASSERT_TRUE(producer.InitializeProducer(config).has_value());
+
+ FrameMetadata metadata;
+ metadata.width = 100;
+ metadata.height = 100;
+ metadata.stride = 300;
+ metadata.pixel_format = PixelFormat::RGB24;
+ metadata.capture_timestamp = Clock::now();
+ metadata.process_timestamp = Clock::now();
+ metadata.frame_number = 0;
+ metadata.data_size = 100;
+
+ std::vector data(100);
+
+ // 填满缓冲区
+ for (size_t i = 0; i < config.buffer_count; ++i) {
+ auto result = producer.TryWriteFrame(metadata, data);
+ ASSERT_TRUE(result.has_value());
+ EXPECT_TRUE(*result);
+ }
+
+ // 再次写入应该失败(缓冲区满)
+ auto result = producer.TryWriteFrame(metadata, data);
+ ASSERT_TRUE(result.has_value());
+ EXPECT_FALSE(*result);
+}
+
+// ==========================================================================
+// WriteFrameWithTimeout 超时路径测试
+// ==========================================================================
+TEST_F(ShmTransportTest, WriteFrameWithTimeout_BufferFullTimesOut)
+{
+ ShmTransport producer;
+
+ ShmTransportConfig config;
+ config.shm_name = test_shm_name_;
+ config.buffer_count = kTestBufferCount;
+ config.max_frame_size = kTestFrameSize;
+ config.enable_zero_copy = false; // 禁用零拷贝以使用传统路径
+
+ auto init_result = producer.InitializeProducer(config);
+ ASSERT_TRUE(init_result.has_value()) << "Producer initialization failed";
+ ASSERT_TRUE(producer.IsInitialized());
+
+ // 获取初始统计值
+ const auto initial_stats = producer.GetStats();
+
+ // 填满缓冲区
+ for (uint32_t i = 0; i < kTestBufferCount; ++i)
+ {
+ FrameMetadata metadata{};
+ metadata.frame_number = i;
+ metadata.width = 100;
+ metadata.height = 100;
+ metadata.stride = 300;
+ metadata.pixel_format = PixelFormat::RGB24;
+ metadata.capture_timestamp = Clock::now();
+ metadata.process_timestamp = Clock::now();
+ metadata.data_size = static_cast(kTestFrameSize);
+
+ std::vector frame_data(kTestFrameSize, static_cast(i));
+
+ auto write_result = producer.TryWriteFrame(metadata, frame_data);
+ ASSERT_TRUE(write_result.has_value()) << "TryWriteFrame error at frame " << i;
+ ASSERT_TRUE(write_result.value()) << "Failed to write frame " << i << " while filling buffer";
+ }
+
+ // 此时环形缓冲区已满,带超时的写入应该失败并更新统计
+ FrameMetadata timeout_metadata{};
+ timeout_metadata.frame_number = kTestBufferCount;
+ timeout_metadata.width = 100;
+ timeout_metadata.height = 100;
+ timeout_metadata.stride = 300;
+ timeout_metadata.pixel_format = PixelFormat::RGB24;
+ timeout_metadata.capture_timestamp = Clock::now();
+ timeout_metadata.process_timestamp = Clock::now();
+ timeout_metadata.data_size = static_cast(kTestFrameSize);
+
+ std::vector timeout_frame(kTestFrameSize, static_cast(0xFF));
+
+ const auto timeout = std::chrono::milliseconds(1);
+ auto write_result = producer.WriteFrameWithTimeout(timeout_metadata, timeout_frame, timeout);
+
+ ASSERT_TRUE(write_result.has_value()) << "WriteFrameWithTimeout returned error: " << static_cast(write_result.error());
+ EXPECT_FALSE(write_result.value()) << "WriteFrameWithTimeout should return false when buffer is full";
+
+ const auto stats = producer.GetStats();
+ EXPECT_EQ(stats.write_timeouts.load() - initial_stats.write_timeouts.load(), 1u);
+}
+
+TEST_F(ShmTransportTest, TryReadFrameNonBlocking)
+{
+ ShmTransport producer;
+ ShmTransportConfig config;
+ config.shm_name = test_shm_name_;
+ config.buffer_count = kTestBufferCount;
+ config.max_frame_size = kTestFrameSize;
+
+ ASSERT_TRUE(producer.InitializeProducer(config).has_value());
+
+ ShmTransport consumer;
+ ASSERT_TRUE(consumer.InitializeConsumer(config).has_value());
+
+ // 尝试读取空缓冲区
+ auto result = consumer.TryReadFrame();
+ ASSERT_TRUE(result.has_value());
+ EXPECT_FALSE(result->has_value()); // 无数据
+
+ // 写入一帧
+ FrameMetadata metadata;
+ metadata.width = 100;
+ metadata.height = 100;
+ metadata.stride = 300;
+ metadata.pixel_format = PixelFormat::RGB24;
+ metadata.capture_timestamp = Clock::now();
+ metadata.process_timestamp = Clock::now();
+ metadata.frame_number = 0;
+ metadata.data_size = 100;
+
+ std::vector data(100);
+
+ ASSERT_TRUE(producer.WriteFrame(metadata, data).has_value());
+
+ // 现在应该能读取
+ result = consumer.TryReadFrame();
+ ASSERT_TRUE(result.has_value());
+ ASSERT_TRUE(result->has_value());
+}
+
+TEST_F(ShmTransportTest, ReadFrameWithCallback)
+{
+ ShmTransport producer;
+ ShmTransportConfig config;
+ config.shm_name = test_shm_name_;
+ config.buffer_count = kTestBufferCount;
+ config.max_frame_size = kTestFrameSize;
+
+ ASSERT_TRUE(producer.InitializeProducer(config).has_value());
+
+ ShmTransport consumer;
+ ASSERT_TRUE(consumer.InitializeConsumer(config).has_value());
+
+ // 准备测试数据
+ FrameMetadata write_metadata;
+ write_metadata.width = 1920;
+ write_metadata.height = 1080;
+ write_metadata.stride = 1920 * 3;
+ write_metadata.pixel_format = PixelFormat::RGB24;
+ write_metadata.capture_timestamp = Clock::now();
+ write_metadata.process_timestamp = Clock::now();
+ write_metadata.frame_number = 42;
+ write_metadata.data_size = 100;
+
+ std::vector write_data(100);
+ for (size_t i = 0; i < write_data.size(); ++i) {
+ write_data[i] = static_cast(i & 0xFF);
+ }
+
+ ASSERT_TRUE(producer.WriteFrame(write_metadata, write_data).has_value());
+
+ // 使用回调读取
+ bool callback_called = false;
+ FrameMetadata received_metadata;
+ std::vector received_data;
+
+ auto callback = [&](const FrameMetadata& metadata, std::span data) {
+ callback_called = true;
+ received_metadata = metadata;
+ received_data.assign(data.begin(), data.end());
+ };
+
+ auto result = consumer.ReadFrameWithCallback(100ms, callback);
+ EXPECT_TRUE(result.has_value());
+ EXPECT_TRUE(callback_called);
+
+ // 验证数据
+ EXPECT_EQ(received_metadata.frame_number, write_metadata.frame_number);
+ EXPECT_EQ(received_data.size(), write_data.size());
+ EXPECT_EQ(std::memcmp(received_data.data(), write_data.data(), write_data.size()), 0);
+}
+
+// ==========================================================================
+// 统计信息测试
+// ==========================================================================
+
+TEST_F(ShmTransportTest, TransportStats)
+{
+ ShmTransport producer;
+ ShmTransportConfig config;
+ config.shm_name = test_shm_name_;
+ config.buffer_count = kTestBufferCount;
+ config.max_frame_size = kTestFrameSize;
+
+ ASSERT_TRUE(producer.InitializeProducer(config).has_value());
+
+ ShmTransport consumer;
+ ASSERT_TRUE(consumer.InitializeConsumer(config).has_value());
+
+ // 初始统计应为零
+ auto stats = consumer.GetStats();
+ EXPECT_EQ(stats.frames_written, 0);
+ EXPECT_EQ(stats.frames_read, 0);
+
+ // 写入几帧
+ FrameMetadata metadata;
+ metadata.width = 100;
+ metadata.height = 100;
+ metadata.stride = 300;
+ metadata.pixel_format = PixelFormat::RGB24;
+ metadata.capture_timestamp = Clock::now();
+ metadata.process_timestamp = Clock::now();
+ metadata.data_size = 100;
+
+ std::vector data(100);
+
+ for (int i = 0; i < 5; ++i) {
+ metadata.frame_number = i;
+ ASSERT_TRUE(producer.WriteFrame(metadata, data).has_value());
+ }
+
+ // 读取几帧
+ for (int i = 0; i < 3; ++i) {
+ auto result = consumer.ReadFrame(100ms);
+ ASSERT_TRUE(result.has_value());
+ ASSERT_TRUE(result->has_value());
+ }
+
+ // 检查消费者统计
+ stats = consumer.GetStats();
+ EXPECT_EQ(stats.frames_read, 3);
+ EXPECT_EQ(stats.bytes_read, 300);
+
+ // 检查缓冲区状态(允许一定误差,因为并发可能导致顺序变化)
+ auto [used, total] = consumer.GetBufferStatus();
+ EXPECT_EQ(total, kTestBufferCount);
+ EXPECT_GE(used, 1); // 至少还有1帧未读
+ EXPECT_LE(used, 2); // 最多2帧未读
+}
+
+TEST_F(ShmTransportTest, StatsDropRate)
+{
+ ShmTransportStats stats;
+ stats.frames_written = 100;
+ stats.frames_dropped = 10;
+
+ // 丢帧率 = dropped / (written + dropped) = 10 / 110
+ EXPECT_DOUBLE_EQ(stats.get_drop_rate(), 10.0 / 110.0);
+
+ stats.frames_dropped = 0;
+ EXPECT_DOUBLE_EQ(stats.get_drop_rate(), 0.0);
+
+ // 测试 10% 丢帧率的情况:10 dropped / 100 written = 10/110
+ stats.frames_written = 90;
+ stats.frames_dropped = 10;
+ EXPECT_DOUBLE_EQ(stats.get_drop_rate(), 0.1); // 10/100 = 10%
+}
+
+TEST_F(ShmTransportTest, StatsThroughput)
+{
+ ShmTransportStats stats;
+ stats.session_start = Clock::now() - std::chrono::seconds(1);
+ stats.bytes_written = 1000000; // 1MB
+
+ // 吞吐量应该约为 1MB/s
+ double throughput = stats.get_throughput_bytes_per_sec();
+ EXPECT_GT(throughput, 900000.0); // 允许 10% 误差
+ EXPECT_LT(throughput, 1100000.0);
+}
+
+// ==========================================================================
+// 边界条件测试
+// ==========================================================================
+
+TEST_F(ShmTransportTest, EmptyDataWrite)
+{
+ ShmTransport producer;
+ ShmTransportConfig config;
+ config.shm_name = test_shm_name_;
+ config.buffer_count = kTestBufferCount;
+ config.max_frame_size = kTestFrameSize;
+
+ ASSERT_TRUE(producer.InitializeProducer(config).has_value());
+
+ FrameMetadata metadata;
+ metadata.width = 100;
+ metadata.height = 100;
+ metadata.stride = 300;
+ metadata.pixel_format = PixelFormat::RGB24;
+ metadata.capture_timestamp = Clock::now();
+ metadata.process_timestamp = Clock::now();
+ metadata.data_size = 0; // 空数据
+
+ std::vector empty_data;
+
+ // 空数据应该被拒绝(无效帧)
+ auto result = producer.WriteFrame(metadata, empty_data);
+ EXPECT_FALSE(result.has_value());
+}
+
+TEST_F(ShmTransportTest, OversizedFrame)
+{
+ ShmTransport producer;
+ ShmTransportConfig config;
+ config.shm_name = test_shm_name_;
+ config.buffer_count = kTestBufferCount;
+ config.max_frame_size = 1024; // 1KB 限制
+
+ ASSERT_TRUE(producer.InitializeProducer(config).has_value());
+
+ FrameMetadata metadata;
+ metadata.width = 100;
+ metadata.height = 100;
+ metadata.stride = 300;
+ metadata.pixel_format = PixelFormat::RGB24;
+ metadata.capture_timestamp = Clock::now();
+ metadata.process_timestamp = Clock::now();
+ metadata.data_size = 2048; // 超过限制
+
+ std::vector large_data(2048);
+
+ // 超大帧应该被拒绝
+ auto result = producer.WriteFrame(metadata, large_data);
+ EXPECT_FALSE(result.has_value());
+}
+
+TEST_F(ShmTransportTest, TimeoutTest)
+{
+ ShmTransport producer;
+ ShmTransportConfig config;
+ config.shm_name = test_shm_name_;
+ config.buffer_count = 2;
+ config.max_frame_size = kTestFrameSize;
+
+ ASSERT_TRUE(producer.InitializeProducer(config).has_value());
+
+ ShmTransport consumer;
+ ASSERT_TRUE(consumer.InitializeConsumer(config).has_value());
+
+ // 尝试读取不存在的帧(应该超时)
+ auto start = std::chrono::steady_clock::now();
+ auto result = consumer.ReadFrame(50ms);
+ auto elapsed = std::chrono::steady_clock::now() - start;
+
+ EXPECT_TRUE(result.has_value());
+ EXPECT_FALSE(result->has_value()); // 无数据
+ EXPECT_GE(elapsed, 50ms);
+}
+
+// ==========================================================================
+// 并发测试
+// ==========================================================================
+
+TEST_F(ShmTransportTest, ConcurrentWriteRead)
+{
+ ShmTransport producer;
+ ShmTransportConfig config;
+ config.shm_name = test_shm_name_;
+ config.buffer_count = 16;
+ config.max_frame_size = kTestFrameSize;
+
+ ASSERT_TRUE(producer.InitializeProducer(config).has_value());
+
+ ShmTransport consumer;
+ ASSERT_TRUE(consumer.InitializeConsumer(config).has_value());
+
+ constexpr int num_frames = 1000;
+ std::atomic frames_read{0};
+ std::atomic producer_done{false};
+
+ // 生产者线程
+ std::thread producer_thread([&]() {
+ FrameMetadata metadata;
+ metadata.width = 100;
+ metadata.height = 100;
+ metadata.stride = 300;
+ metadata.pixel_format = PixelFormat::RGB24;
+ metadata.capture_timestamp = Clock::now();
+ metadata.process_timestamp = Clock::now();
+ metadata.data_size = 100;
+
+ std::vector data(100);
+
+ for (int i = 0; i < num_frames; ++i) {
+ metadata.frame_number = i;
+ data[0] = static_cast(i & 0xFF);
+
+ auto result = producer.WriteFrameWithTimeout(metadata, data, 1s);
+ ASSERT_TRUE(result.has_value());
+ EXPECT_TRUE(*result);
+ }
+ producer_done = true;
+ });
+
+ // 消费者线程
+ std::thread consumer_thread([&]() {
+ while (frames_read < num_frames) {
+ auto result = consumer.ReadFrame(100ms);
+ if (result && result->has_value()) {
+ frames_read.fetch_add(1);
+ }
+ if (producer_done && !result->has_value()) {
+ break;
+ }
+ }
+ });
+
+ producer_thread.join();
+ consumer_thread.join();
+
+ EXPECT_EQ(frames_read, num_frames);
+}
+
+// ==========================================================================
+// 控制块测试
+// ==========================================================================
+
+TEST_F(ShmTransportTest, ControlBlockValidation)
+{
+ // ShmControlBlock 默认构造时 magic = MAGIC, version = VERSION
+ // 所以默认就是有效的
+ ShmControlBlock block;
+ EXPECT_TRUE(block.is_valid()); // 默认构造有效
+
+ // 修改 magic 后应该无效
+ block.magic = 0;
+ EXPECT_FALSE(block.is_valid());
+
+ // 恢复 magic 但修改 version
+ block.magic = ShmControlBlock::MAGIC;
+ block.version = 999; // 错误版本
+ EXPECT_FALSE(block.is_valid());
+}
+
+TEST_F(ShmTransportTest, ControlBlockBufferStatus)
+{
+ ShmControlBlock block;
+ block.buffer_count = 4;
+ block.write_sequence.store(2, std::memory_order_relaxed);
+ block.read_sequence.store(0, std::memory_order_relaxed);
+
+ EXPECT_EQ(block.used_buffers(), 2);
+ EXPECT_EQ(block.available_buffers(), 2);
+ EXPECT_TRUE(block.is_writable());
+ EXPECT_TRUE(block.has_readable_data());
+
+ block.read_sequence.store(2, std::memory_order_relaxed);
+ EXPECT_EQ(block.used_buffers(), 0);
+ EXPECT_FALSE(block.has_readable_data());
+}
+
+// ==========================================================================
+// 环形缓冲区溢出测试
+// ==========================================================================
+TEST_F(ShmTransportTest, DroppedFramesOnRingOverflow)
+{
+ // 选择一个很小的 ring 大小,方便在测试中触发溢出
+ const size_t kRingCapacityFrames = 4;
+ const size_t kFramesToWrite = 10; // 明显大于 capacity,确保发生溢出
+
+ ShmTransportConfig config;
+ config.shm_name = test_shm_name_;
+ config.buffer_count = static_cast(kRingCapacityFrames);
+ config.max_frame_size = kTestFrameSize;
+ config.enable_zero_copy = false; // 使用传统路径以测试超时丢帧
+
+ // 初始化生产者(不启动消费者,这样不会有消费,ring 会被写满后溢出)
+ ShmTransport producer;
+ auto init_result = producer.InitializeProducer(config);
+ ASSERT_TRUE(init_result.has_value()) << "Producer initialization failed";
+
+ // 获取初始统计值
+ const auto initial_stats = producer.GetStats();
+
+ FrameMetadata metadata{};
+ metadata.width = 100;
+ metadata.height = 100;
+ metadata.stride = 100;
+ metadata.pixel_format = PixelFormat::RGB24;
+ metadata.data_size = static_cast(kTestFrameSize);
+
+ std::vector frame_data(kTestFrameSize, static_cast(0xAB));
+
+ // 首先填满缓冲区
+ size_t successful_writes = 0;
+ for (size_t i = 0; i < kRingCapacityFrames; ++i)
+ {
+ metadata.frame_number = static_cast(i);
+ auto result = producer.TryWriteFrame(metadata, frame_data);
+ ASSERT_TRUE(result.has_value()) << "WriteFrame failed at frame " << i;
+ if (result.value()) {
+ successful_writes++;
+ }
+ }
+ EXPECT_EQ(successful_writes, kRingCapacityFrames);
+
+ // 使用带超时的写入,触发丢帧计数
+ size_t timeout_count = 0;
+ for (size_t i = kRingCapacityFrames; i < kFramesToWrite; ++i)
+ {
+ metadata.frame_number = static_cast(i);
+ auto result = producer.WriteFrameWithTimeout(metadata, frame_data, std::chrono::milliseconds(1));
+ ASSERT_TRUE(result.has_value()) << "WriteFrameWithTimeout error at frame " << i;
+ if (!result.value()) {
+ timeout_count++;
+ }
+ }
+
+ // 验证:超时次数应该等于尝试写入的额外帧数
+ EXPECT_EQ(timeout_count, kFramesToWrite - kRingCapacityFrames);
+
+ // 获取最终统计值并验证丢帧统计
+ const auto final_stats = producer.GetStats();
+ EXPECT_EQ(final_stats.write_timeouts.load() - initial_stats.write_timeouts.load(), timeout_count);
+}
+
+// ==========================================================================
+// 辅助函数测试
+// ==========================================================================
+
+TEST_F(ShmTransportTest, MakeShmName)
+{
+ // Windows: 添加 Local\ 前缀
+ // POSIX: 添加 / 前缀
+ auto name1 = MakeShmName("test");
+ EXPECT_FALSE(name1.empty());
+
+ auto name2 = MakeShmName("");
+ EXPECT_FALSE(name2.empty());
+
+ // 重复调用应返回相同结果
+ auto name3 = MakeShmName("test");
+ EXPECT_EQ(name1, name3);
+}
+
+TEST_F(ShmTransportTest, ShmExistsAndRemove)
+{
+ // 不存在的共享内存
+ EXPECT_FALSE(ShmExists("nonexistent_shm_12345"));
+
+ // 创建共享内存
+ ShmTransport producer;
+ ShmTransportConfig config;
+ config.shm_name = test_shm_name_;
+ config.buffer_count = kTestBufferCount;
+ config.max_frame_size = kTestFrameSize;
+
+ ASSERT_TRUE(producer.InitializeProducer(config).has_value());
+
+ // 现在应该存在
+ EXPECT_TRUE(ShmExists(test_shm_name_));
+
+ // 关闭生产者
+ ASSERT_TRUE(producer.Shutdown().has_value());
+
+ // 删除共享内存
+ EXPECT_TRUE(ShmRemove(test_shm_name_));
+}
+
+// ==========================================================================
+// 帧头部测试
+// ==========================================================================
+
+TEST_F(ShmTransportTest, FrameHeaderValidation)
+{
+ ShmFrameHeader header;
+ EXPECT_FALSE(header.is_valid()); // 默认构造无效
+
+ header.magic = ShmFrameHeader::MAGIC;
+ header.version = 1;
+ header.data_size = 100;
+ EXPECT_TRUE(header.is_valid());
+
+ header.magic = 0;
+ EXPECT_FALSE(header.is_valid());
+}
+
+TEST_F(ShmTransportTest, FrameHeaderChecksum)
+{
+ ShmFrameHeader header;
+ header.data_size = 10;
+
+ std::array data{
+ std::byte{0}, std::byte{1}, std::byte{2}, std::byte{3}, std::byte{4},
+ std::byte{5}, std::byte{6}, std::byte{7}, std::byte{8}, std::byte{9}
+ };
+
+ header.checksum = header.calculate_checksum(data.data());
+ EXPECT_TRUE(header.verify_data(data.data()));
+
+ // 修改数据
+ data[0] = std::byte{99};
+ EXPECT_FALSE(header.verify_data(data.data()));
+}
+
+// ==========================================================================
+// 校验和端到端集成测试
+// ==========================================================================
+
+// 集成测试:启用校验和时应能检测到数据损坏
+TEST_F(ShmTransportTest, ChecksumEnabledDetectsCorruption)
+{
+ // 安排:创建启用校验和的传输
+ ShmTransportConfig config;
+ config.shm_name = test_shm_name_;
+ config.buffer_count = kTestBufferCount;
+ config.max_frame_size = kTestFrameSize;
+ config.enable_checksum = true; // 启用校验和
+
+ ShmTransport producer;
+ ASSERT_TRUE(producer.InitializeProducer(config).has_value());
+
+ ShmTransport consumer;
+ ASSERT_TRUE(consumer.InitializeConsumer(config).has_value());
+
+ const std::string payload = "0123456789ABCDEFGHIJ"; // 20 bytes
+ FrameMetadata metadata{};
+ metadata.width = 100;
+ metadata.height = 100;
+ metadata.stride = 100;
+ metadata.pixel_format = PixelFormat::RGB24;
+ metadata.frame_number = 1;
+ metadata.data_size = static_cast(payload.size());
+
+ // 写入一帧
+ auto write_result = producer.WriteFrame(metadata, std::as_bytes(std::span(payload)));
+ ASSERT_TRUE(write_result.has_value()) << "WriteFrame failed";
+
+ // 在读取前损坏底层共享内存数据
+ // 使用零拷贝API获取帧数据指针并修改
+ auto read_buffer = consumer.TryAcquireReadBuffer();
+ ASSERT_TRUE(read_buffer.has_value());
+ ASSERT_TRUE(read_buffer->has_value());
+
+ // 获取数据指针并损坏第一个字节
+ auto& buffer = read_buffer->value();
+ ASSERT_GE(buffer.data.size(), 1u);
+ const_cast(buffer.data.data())[0] ^= std::byte{0xFF}; // 损坏数据
+
+ // 释放缓冲区(不调用ReleaseReadBuffer,因为我们损坏了数据)
+ // 重新初始化消费者以读取损坏的数据
+ ShmTransport consumer2;
+ ASSERT_TRUE(consumer2.InitializeConsumer(config).has_value());
+
+ // 尝试读取损坏的帧
+ auto read_result = consumer2.ReadFrame(100ms);
+
+ // 断言:传输必须检测到损坏并返回ShmCorrupted错误
+ EXPECT_FALSE(read_result.has_value());
+ EXPECT_EQ(read_result.error(), ShmTransportError::ShmCorrupted);
+
+ // 验证校验和错误计数增加
+ const auto stats = consumer2.GetStats();
+ EXPECT_EQ(stats.checksum_errors.load(), 1u);
+}
+
+// 集成测试:禁用校验和时不应检测到数据损坏
+TEST_F(ShmTransportTest, ChecksumDisabledDoesNotDetectCorruption)
+{
+ // 安排:创建禁用校验和的传输
+ ShmTransportConfig config;
+ config.shm_name = test_shm_name_;
+ config.buffer_count = kTestBufferCount;
+ config.max_frame_size = kTestFrameSize;
+ config.enable_checksum = false; // 禁用校验和
+
+ ShmTransport producer;
+ ASSERT_TRUE(producer.InitializeProducer(config).has_value());
+
+ ShmTransport consumer;
+ ASSERT_TRUE(consumer.InitializeConsumer(config).has_value());
+
+ const std::string payload = "0123456789ABCDEFGHIJ"; // 20 bytes
+ FrameMetadata metadata{};
+ metadata.width = 100;
+ metadata.height = 100;
+ metadata.stride = 100;
+ metadata.pixel_format = PixelFormat::RGB24;
+ metadata.frame_number = 1;
+ metadata.data_size = static_cast(payload.size());
+
+ // 写入一帧
+ auto write_result = producer.WriteFrame(metadata, std::as_bytes(std::span(payload)));
+ ASSERT_TRUE(write_result.has_value()) << "WriteFrame failed";
+
+ // 使用零拷贝 API 获取数据指针并损坏第一个字节
+ auto read_buffer = consumer.TryAcquireReadBuffer();
+ ASSERT_TRUE(read_buffer.has_value());
+ ASSERT_TRUE(read_buffer->has_value());
+
+ auto& buffer = read_buffer->value();
+ ASSERT_GE(buffer.data.size(), 1u);
+ const_cast(buffer.data.data())[0] ^= std::byte{0xFF}; // 损坏数据
+
+ // 在禁用校验和的情况下,直接读取损坏的数据不应报错
+ // 由于我们已经通过零拷贝获取了缓冲区,可以直接验证数据
+ // 注意:损坏的数据应该与原始数据不同
+ EXPECT_NE(buffer.data[0], std::as_bytes(std::span(payload))[0]);
+
+ // 释放缓冲区
+ [[maybe_unused]] auto _ = consumer.ReleaseReadBuffer(buffer.buffer_index);
+
+ // 验证校验和错误计数为0(禁用校验和时不应检测错误)
+ const auto stats = consumer.GetStats();
+ EXPECT_EQ(stats.checksum_errors.load(), 0u);
+}
+
+// ==========================================================================
+// 性能基准测试(记录吞吐量,仅做正确性校验,不对性能做硬性要求)
+// ==========================================================================
+
+TEST_F(ShmTransportTest, ThroughputBenchmark)
+{
+ ShmTransport producer;
+ ShmTransportConfig config;
+ config.shm_name = test_shm_name_;
+ config.buffer_count = 16;
+ config.max_frame_size = 1920 * 1080 * 4; // 4K RGBA
+
+ ASSERT_TRUE(producer.InitializeProducer(config).has_value());
+
+ ShmTransport consumer;
+ ASSERT_TRUE(consumer.InitializeConsumer(config).has_value());
+
+ constexpr int num_frames = 100;
+ constexpr std::size_t frame_size = 1920 * 1080 * 3; // 1080p RGB24
+
+ FrameMetadata metadata;
+ metadata.width = 1920;
+ metadata.height = 1080;
+ metadata.stride = 1920 * 3;
+ metadata.pixel_format = PixelFormat::RGB24;
+ metadata.capture_timestamp = Clock::now();
+ metadata.process_timestamp = Clock::now();
+ metadata.data_size = static_cast(frame_size);
+
+ std::vector data(frame_size);
+
+ // 预热
+ for (int i = 0; i < 10; ++i) {
+ [[maybe_unused]] auto _ = producer.WriteFrame(metadata, data);
+ [[maybe_unused]] auto _2 = consumer.ReadFrame(100ms);
+ }
+
+ // 基准测试 - 交替写入和读取以避免缓冲区溢出
+ auto start = std::chrono::high_resolution_clock::now();
+
+ for (int i = 0; i < num_frames; ++i) {
+ metadata.frame_number = i;
+ auto write_result = producer.WriteFrame(metadata, data);
+ ASSERT_TRUE(write_result.has_value());
+
+ // 每写入一帧立即读取,保持缓冲区不溢出
+ auto read_result = consumer.ReadFrame(100ms);
+ ASSERT_TRUE(read_result.has_value());
+ ASSERT_TRUE(read_result->has_value());
+ }
+
+ auto end = std::chrono::high_resolution_clock::now();
+ auto duration = std::chrono::duration_cast(end - start);
+
+ // 计算吞吐量
+ double total_bytes = static_cast(num_frames * frame_size);
+ double seconds = duration.count() / 1'000'000.0;
+ double throughput_mbps = (total_bytes / seconds) / (1024.0 * 1024.0);
+
+ // 仅校验吞吐量为正,避免在不同机器/构建配置下因绝对阈值导致用例不稳定
+ GTEST_LOG_(INFO) << "ShmTransport throughput: " << throughput_mbps << " MiB/s";
+ EXPECT_GT(throughput_mbps, 0.0);
+}
+
+// ==========================================================================
+// 零拷贝延迟基准测试 - 使用真正的零拷贝API
+// ==========================================================================
+TEST_F(ShmTransportTest, ZeroCopyLatencyBenchmark)
+{
+ ShmTransport producer;
+ ShmTransportConfig config;
+ config.shm_name = test_shm_name_;
+ config.buffer_count = 8;
+ config.max_frame_size = 1024 * 1024; // 1MB 帧
+ config.enable_checksum = false; // 禁用CRC校验以提高性能
+
+ ASSERT_TRUE(producer.InitializeProducer(config).has_value());
+
+ ShmTransport consumer;
+ ASSERT_TRUE(consumer.InitializeConsumer(config).has_value());
+
+ constexpr int num_frames = 1000;
+ constexpr std::size_t frame_size = 1024 * 1024; // 1MB
+
+ FrameMetadata metadata;
+ metadata.width = 1024;
+ metadata.height = 1024;
+ metadata.stride = 1024;
+ metadata.pixel_format = PixelFormat::RGB24;
+ metadata.data_size = static_cast(frame_size);
+
+ // 测试1: 零拷贝写入吞吐量(生产者端测量)
+ // 使用消费者线程边读边写,避免缓冲区溢出
+ {
+ std::atomic write_count{0};
+ std::atomic writer_done{false};
+
+ // 启动消费者线程清空缓冲区
+ std::thread consumer_thread([&]() {
+ while (!writer_done.load(std::memory_order_acquire) || write_count.load(std::memory_order_acquire) > 0) {
+ auto read_result = consumer.TryAcquireReadBuffer();
+ if (read_result && read_result->has_value()) {
+ [[maybe_unused]] auto _ = consumer.ReleaseReadBuffer(read_result->value().buffer_index);
+ write_count.fetch_sub(1, std::memory_order_relaxed);
+ } else {
+ std::this_thread::yield();
+ }
+ }
+ });
+
+ auto start = std::chrono::high_resolution_clock::now();
+ for (int i = 0; i < num_frames; ++i) {
+ // 使用带超时的API等待缓冲区可用
+ auto buffer_result = producer.AcquireWriteBuffer(100ms);
+ ASSERT_TRUE(buffer_result.has_value());
+ ASSERT_TRUE(buffer_result->has_value());
+
+ auto& buffer = buffer_result->value();
+ // 直接写入共享内存(零拷贝)
+ std::memset(buffer.data, static_cast(i & 0xFF), frame_size);
+
+ metadata.frame_number = i;
+ auto commit_result = producer.CommitWriteBuffer(buffer.buffer_index, metadata, frame_size);
+ ASSERT_TRUE(commit_result.has_value());
+
+ write_count.fetch_add(1, std::memory_order_relaxed);
+ }
+ auto end = std::chrono::high_resolution_clock::now();
+ auto duration_ns = std::chrono::duration_cast(end - start).count();
+
+ writer_done.store(true, std::memory_order_release);
+ consumer_thread.join();
+
+ double total_mb = static_cast(num_frames * frame_size) / (1024.0 * 1024.0);
+ double seconds = duration_ns / 1'000'000'000.0;
+ double write_mbps = total_mb / seconds;
+ double write_latency_ns = static_cast(duration_ns) / num_frames;
+
+ std::cout << "[ZeroCopy] Write Throughput: " << write_mbps << " MB/s" << std::endl;
+ std::cout << "[ZeroCopy] Write Latency: " << write_latency_ns << " ns/frame" << std::endl;
+ }
+
+ // 测试2: 零拷贝读取吞吐量(消费者端测量)
+ {
+ // 先填充缓冲区(使用生产者线程边写边读避免阻塞)
+ std::atomic written{0};
+ std::atomic fill_done{false};
+
+ std::thread fill_consumer([&]() {
+ while (!fill_done.load(std::memory_order_acquire) || written.load(std::memory_order_acquire) > 0) {
+ auto read_result = consumer.TryAcquireReadBuffer();
+ if (read_result && read_result->has_value()) {
+ [[maybe_unused]] auto _ = consumer.ReleaseReadBuffer(read_result->value().buffer_index);
+ written.fetch_sub(1, std::memory_order_relaxed);
+ } else {
+ std::this_thread::yield();
+ }
+ }
+ });
+
+ for (int i = 0; i < num_frames; ++i) {
+ auto buffer_result = producer.AcquireWriteBuffer(100ms);
+ ASSERT_TRUE(buffer_result.has_value());
+ ASSERT_TRUE(buffer_result->has_value());
+
+ auto& buffer = buffer_result->value();
+ std::memset(buffer.data, static_cast(i & 0xFF), frame_size);
+ metadata.frame_number = i;
+ [[maybe_unused]] auto _ = producer.CommitWriteBuffer(buffer.buffer_index, metadata, frame_size);
+ written.fetch_add(1, std::memory_order_relaxed);
+ }
+
+ fill_done.store(true, std::memory_order_release);
+ fill_consumer.join();
+
+ // 重新填充用于读取测试
+ for (int i = 0; i < std::min(num_frames, 8); ++i) {
+ auto buffer_result = producer.AcquireWriteBuffer(100ms);
+ if (buffer_result && buffer_result->has_value()) {
+ auto& buffer = buffer_result->value();
+ std::memset(buffer.data, static_cast(i & 0xFF), frame_size);
+ metadata.frame_number = i;
+ [[maybe_unused]] auto _ = producer.CommitWriteBuffer(buffer.buffer_index, metadata, frame_size);
+ }
+ }
+
+ auto start = std::chrono::high_resolution_clock::now();
+ int read_count = 0;
+ while (read_count < num_frames) {
+ auto result = consumer.TryAcquireReadBuffer();
+ if (result && result->has_value()) {
+ // 直接读取共享内存(零拷贝)- 验证数据
+ auto& buffer = result->value();
+ EXPECT_EQ(buffer.data.size(), frame_size);
+
+ // 释放缓冲区
+ [[maybe_unused]] auto _ = consumer.ReleaseReadBuffer(buffer.buffer_index);
+ read_count++;
+
+ // 补充一帧数据保持缓冲区满
+ auto wb = producer.TryAcquireWriteBuffer();
+ if (wb && wb->has_value()) {
+ std::memset(wb->value().data, static_cast(read_count & 0xFF), frame_size);
+ metadata.frame_number = read_count;
+ [[maybe_unused]] auto _c = producer.CommitWriteBuffer(wb->value().buffer_index, metadata, frame_size);
+ }
+ }
+ }
+ auto end = std::chrono::high_resolution_clock::now();
+ auto duration_ns = std::chrono::duration_cast(end - start).count();
+
+ double total_mb = static_cast(num_frames * frame_size) / (1024.0 * 1024.0);
+ double seconds = duration_ns / 1'000'000'000.0;
+ double read_mbps = total_mb / seconds;
+ double read_latency_ns = static_cast(duration_ns) / num_frames;
+
+ std::cout << "[ZeroCopy] Read Throughput: " << read_mbps << " MB/s" << std::endl;
+ std::cout << "[ZeroCopy] Read Latency: " << read_latency_ns << " ns/frame" << std::endl;
+
+ // 仅记录性能指标,不做硬性断言以避免CI环境抖动导致失败
+ GTEST_LOG_(INFO) << "ZeroCopy read throughput: " << read_mbps << " MB/s";
+ GTEST_LOG_(INFO) << "ZeroCopy read latency: " << read_latency_ns << " ns/frame";
+ }
+
+ // 测试3: 并发零拷贝读写吞吐量(双工模式)
+ {
+ std::atomic write_count{0};
+ std::atomic read_count{0};
+ std::atomic start_flag{false};
+ std::atomic writer_done{false};
+
+ std::thread writer([&]() {
+ while (!start_flag.load(std::memory_order_acquire)) {
+ std::this_thread::yield();
+ }
+ for (int i = 0; i < num_frames; ++i) {
+ auto buffer_result = producer.AcquireWriteBuffer(100ms);
+ if (buffer_result && buffer_result->has_value()) {
+ auto& buffer = buffer_result->value();
+ std::memset(buffer.data, static_cast(i & 0xFF), frame_size);
+ metadata.frame_number = i;
+ if (producer.CommitWriteBuffer(buffer.buffer_index, metadata, frame_size).has_value()) {
+ write_count.fetch_add(1, std::memory_order_relaxed);
+ }
+ }
+ }
+ writer_done.store(true, std::memory_order_release);
+ });
+
+ std::thread reader([&]() {
+ while (!start_flag.load(std::memory_order_acquire)) {
+ std::this_thread::yield();
+ }
+ int local_read = 0;
+ while (local_read < num_frames) {
+ auto result = consumer.TryAcquireReadBuffer();
+ if (result && result->has_value()) {
+ if (consumer.ReleaseReadBuffer(result->value().buffer_index).has_value()) {
+ read_count.fetch_add(1, std::memory_order_relaxed);
+ local_read++;
+ }
+ } else if (writer_done.load(std::memory_order_acquire) &&
+ read_count.load(std::memory_order_acquire) >= write_count.load(std::memory_order_acquire)) {
+ // 写入完成且已读完所有数据
+ break;
+ } else {
+ std::this_thread::yield();
+ }
+ }
+ });
+
+ auto start = std::chrono::high_resolution_clock::now();
+ start_flag.store(true, std::memory_order_release);
+
+ writer.join();
+ reader.join();
+
+ auto end = std::chrono::high_resolution_clock::now();
+ auto duration_ns = std::chrono::duration_cast(end - start).count();
+
+ double total_mb = static_cast((write_count + read_count) * frame_size) / (1024.0 * 1024.0);
+ double seconds = duration_ns / 1'000'000'000.0;
+ double duplex_mbps = total_mb / seconds;
+
+ std::cout << "[ZeroCopy] Duplex Throughput: " << duplex_mbps << " MB/s" << std::endl;
+ std::cout << "[ZeroCopy] Frames written: " << write_count << ", read: " << read_count << std::endl;
+
+ EXPECT_EQ(write_count, num_frames);
+ EXPECT_EQ(read_count, num_frames);
+ }
+}
+
+// ==========================================================================
+// 微秒级单帧延迟测试(使用零拷贝API)
+// ==========================================================================
+TEST_F(ShmTransportTest, MicrosecondLatencyTest)
+{
+ // NOTE:
+ // This test is extremely sensitive to system load, CPU scaling, and
+ // virtualization. To avoid CI flakiness, it is gated by an environment
+ // variable and will be skipped unless explicitly enabled.
+ const char* perf_env = std::getenv("SHM_TRANSPORT_PERF_TEST");
+ if (!perf_env || std::strcmp(perf_env, "1") != 0)
+ {
+ GTEST_SKIP() << "Skipping MicrosecondLatencyTest; "
+ << "enable with SHM_TRANSPORT_PERF_TEST=1 in a dedicated "
+ << "performance environment.";
+ }
+
+ ShmTransport producer;
+ ShmTransportConfig config;
+ config.shm_name = test_shm_name_;
+ config.buffer_count = 4;
+ config.max_frame_size = 1024 * 1024; // 1MB 小帧
+ config.enable_checksum = false; // 禁用CRC校验以提高性能
+
+ ASSERT_TRUE(producer.InitializeProducer(config).has_value());
+
+ ShmTransport consumer;
+ ASSERT_TRUE(consumer.InitializeConsumer(config).has_value());
+
+ constexpr int num_samples = 100;
+ constexpr std::size_t frame_size = 1024 * 1024; // 1MB
+
+ FrameMetadata metadata;
+ metadata.width = 1024;
+ metadata.height = 1024;
+ metadata.stride = 1024;
+ metadata.pixel_format = PixelFormat::RGB24;
+ metadata.data_size = static_cast(frame_size);
+
+ // 预热
+ for (int i = 0; i < 10; ++i) {
+ auto wb = producer.TryAcquireWriteBuffer();
+ if (wb && wb->has_value()) {
+ [[maybe_unused]] auto _ = producer.CommitWriteBuffer(wb->value().buffer_index, metadata, 1024);
+ }
+ auto rb = consumer.TryAcquireReadBuffer();
+ if (rb && rb->has_value()) {
+ [[maybe_unused]] auto _ = consumer.ReleaseReadBuffer(rb->value().buffer_index);
+ }
+ }
+
+ // 测量单帧传输延迟(写+读往返)- 使用零拷贝API
+ std::vector latencies_us;
+ latencies_us.reserve(num_samples);
+
+ for (int i = 0; i < num_samples; ++i) {
+ auto start = std::chrono::high_resolution_clock::now();
+
+ // 零拷贝写入
+ metadata.frame_number = i;
+ auto write_buffer = producer.TryAcquireWriteBuffer();
+ ASSERT_TRUE(write_buffer.has_value());
+ ASSERT_TRUE(write_buffer->has_value());
+
+ // 直接写入共享内存
+ std::memset(write_buffer->value().data, static_cast(i & 0xFF), 1024);
+
+ auto write_result = producer.CommitWriteBuffer(write_buffer->value().buffer_index, metadata, 1024);
+ ASSERT_TRUE(write_result.has_value());
+
+ // 零拷贝读取
+ auto read_buffer = consumer.TryAcquireReadBuffer();
+ ASSERT_TRUE(read_buffer.has_value());
+ ASSERT_TRUE(read_buffer->has_value());
+
+ auto read_result = consumer.ReleaseReadBuffer(read_buffer->value().buffer_index);
+ ASSERT_TRUE(read_result.has_value());
+
+ auto end = std::chrono::high_resolution_clock::now();
+
+ double latency_us = std::chrono::duration_cast(end - start).count() / 1000.0;
+ latencies_us.push_back(latency_us);
+ }
+
+ // 计算统计信息
+ std::sort(latencies_us.begin(), latencies_us.end());
+ double min_latency = latencies_us.front();
+ double max_latency = latencies_us.back();
+ double median_latency = latencies_us[num_samples / 2];
+ double avg_latency = std::accumulate(latencies_us.begin(), latencies_us.end(), 0.0) / num_samples;
+
+ // 计算P99
+ int p99_index = static_cast(num_samples * 0.99);
+ double p99_latency = latencies_us[p99_index];
+
+ std::cout << "[MicroLatency] Min: " << min_latency << " us" << std::endl;
+ std::cout << "[MicroLatency] Max: " << max_latency << " us" << std::endl;
+ std::cout << "[MicroLatency] Median: " << median_latency << " us" << std::endl;
+ std::cout << "[MicroLatency] Average: " << avg_latency << " us" << std::endl;
+ std::cout << "[MicroLatency] P99: " << p99_latency << " us" << std::endl;
+
+ // 仅在性能测试环境中验证严格延迟目标
+ // 使用零拷贝API,P99延迟应该 < 10μs(对于1KB数据)
+ EXPECT_LT(p99_latency, 10.0);
+ EXPECT_LT(median_latency, 5.0);
+}
diff --git a/develop_plan/ROADMAP.md b/develop_plan/ROADMAP.md
index 557c08b..cf04371 100644
--- a/develop_plan/ROADMAP.md
+++ b/develop_plan/ROADMAP.md
@@ -92,7 +92,7 @@
### v0.2.0-alpha.3:传输层
-- [ ] `src/l0_sensing/shm_transport.cpp`:共享内存传输
+- [x] `src/l0_sensing/shm_transport.cpp`:共享内存传输
### v0.2.0-alpha.4:基础设施