Skip to content

feat(host,core): Add USB transport + binary protocol for CAN/UART/IMU - #5

Merged
qzhhhi merged 4 commits into
dev/v3from
feature/protocol
Dec 20, 2025
Merged

feat(host,core): Add USB transport + binary protocol for CAN/UART/IMU#5
qzhhhi merged 4 commits into
dev/v3from
feature/protocol

Conversation

@qzhhhi

@qzhhhiqzhhhi commented Dec 19, 2025

Copy link
Copy Markdown
Member
  • Define public data IDs and view types (CAN/UART/IMU) with callback interface
  • Introduce fixed little-endian bitfield utility and stack allocator/verify/assert helpers
  • Implement protocol layouts plus serializer and coroutine-based incremental deserializer
  • Add host-side transport abstraction and libusb bulk USB implementation
  • Add stream-based transmit buffer and protocol Handler to bridge transport <-> callbacks
  • Provide AllianceBoard convenience wrapper for common board usage

本 PR 大幅扩展并重构了 librmcs 的核心与主机端,新增完整的二进制协议栈(CAN/UART/IMU)、USB 传输实现、序列化/反序列化流水线、协程栈分配器与多项主机工具。主要变更点如下:

核心数据与回调

  • 新增公共数据类型与回调接口(librmcs::data):DataId(uint8_t 枚举)、CanDataView、UartDataView、AccelerometerDataView、GyroscopeDataView,以及 IDataCallback(CAN/UART 返回 bool 表示 id 是否被处理,IMU 回调为 void)。

协议位域、布局与常量

  • 新增小端定长位域工具 librmcs::core::utility::Bitfield/BitfieldMember(编译期布局校验、零开销读写)。
  • 在 librmcs::core::protocol 中定义协议字段布局与头结构(FieldHeader / FieldHeaderExtended、CanHeader(标准/扩展)、UartHeader(含扩展长度)、ImuHeader 及 IMU 载荷)。
  • 新增常量 kProtocolBufferSize = 1023。

序列化 / 发送缓冲

  • 引入 Serializer 与 ISerializeBuffer,提供 write_can / write_uart / write_imu_*,返回 SerializeResult 并做严格尺寸与参数校验。
  • 新增主机侧 StreamBuffer(实现 ISerializeBuffer):懒获取传输缓冲、自动 finalize/提交、提供 allocate 与 allocate_up_to,用于高效批量发送。

反序列化 / 接收处理

  • 新增 Deserializer 与 IDeserializeCallback:流式增量解析、错误回调与 discard 模式,内部使用 PeekBytesAwaiter 保证连续访问。
  • 反序列化实现为协程驱动:新增 coroutine::LifoTask 系列与基于栈的帧分配(LifoContext、InlineLifoContext、LifoStackedPromise、LifoTask),用于逐字段解析与异步等待。

主机传输抽象与 USB 实现

  • 新增传输抽象 librmcs::host::transport::ITransport / ITransportBuffer(固定缓冲区大小契约),及 create_usb_transport 工厂。
  • 基于 libusb 的 Usb 实现:设备枚举/选择与匹配(支持序列号)、接口申领、传输/接收传输池、TransferWrapper 封装、事件线程、接收回调重提交、传输缓冲池管理及日志与错误处理。对外暴露 acquire_transmit_buffer / transmit / release_transmit_buffer / receive。

协议处理器与便捷板级 API

  • 新增 host::protocol::Handler:创建并管理 USB transport,桥接 Deserializer 与 data 回调,提供 start_transmit() 返回就地 PacketBuilder(固定内置存储),内部使用 StreamBuffer+Serializer 完成写入。
  • 新增 agent::AllianceBoard:实现 IDataCallback,封装 Handler 并提供链式 PacketBuilder API(canX_transmit / uartX_transmit)以及按 ID 路由的可覆写接收 hook,便于上层板级集成。

协程栈分配器与任务

  • 新增基于栈的协程帧分配与 LifoTask(自定义 operator new/delete、Awaiter、FinalAwaiter),用于低开销的增量协程解析与受控栈分配。

基础工具与基础设施

  • 新增 StackAllocator(调试模式含 LIFO 校验)、verify 宏(LIBRMCS_VERIFY / LIBRMCS_VERIFY_LIKELY)、断言工具(assert/assert_always/assert_failed)。
  • 主机侧工具:单例 Logger(分级、格式化)、跨平台小工具 cross_os.hpp、FinalAction RAII、无锁 SPSC RingBuffer(批量操作)。
  • 构建与打包:新增 host/CMakeLists.txt(支持静态/共享构建、libusb 依赖、LTO、安装与 UNIX 下 CPack 配置)与 host/CMakePresets.json。

对外 API 与兼容性影响

  • 本次合并新增并暴露大量公共类型与接口(data 类型、protocol 位域/头、Serializer/Deserializer 接口、协程任务类型与栈分配相关类型、ITransport/ITransportBuffer 与 USB 工厂、Handler/PacketBuilder、AllianceBoard、StreamBuffer、工具类等),公共 API 面显著扩大。

建议重点评审项

  • Bitfield 的小端/对齐/跨平台假设与边界实现;
  • 协程栈式帧分配(LifoStackedPromise/LifoTask)在异常、栈耗尽与协程交接场景下的安全性与资源回收;
  • libusb 实现中的设备匹配、接口申领、receive 路径的错误处理与 transfer 重提交策略;
  • StreamBuffer/PacketBuilder 的缓冲语义、所有权与生命周期边界(特别是 transmit/释放语义);
  • Serializer/Deserializer 的边界检查、extended header 分支和错误恢复策略;
  • CMake 与打包配置的跨平台兼容性与依赖声明。

总体结论

  • 该 PR 为 librmcs 引入了完整的主机—设备二进制协议栈(含 USB 传输)、高效序列化/反序列化流水线与便捷板级 API,功能完备且偏向零拷贝与高性能,但显著扩大了公共接口与实现复杂性,需针对上文列出的关键安全性、错误路径与跨平台假设逐项仔细评审。

- Define public data IDs and view types (CAN/UART/IMU) with callback interface
- Introduce fixed little-endian bitfield utility and stack allocator/verify/assert helpers
- Implement protocol layouts plus serializer and coroutine-based incremental deserializer
- Add host-side transport abstraction and libusb bulk USB implementation
- Add stream-based transmit buffer and protocol Handler to bridge transport <-> callbacks
- Provide AllianceBoard convenience wrapper for common board usage
@coderabbitai

coderabbitaiBot commented Dec 19, 2025

Copy link
Copy Markdown

Walkthrough

新增完整协议栈:类型化数据视图与回调、基于栈的 LIFO 协程任务与栈分配、位域协议定义、序列化/反序列化、libusb 传输实现、主机协议处理与上层 Agent API,以及相关工具与构建配置。

Changes

Cohort / File(s)变更摘要
数据类型与回调
core/include/librmcs/data/datas.hpp
新增 DataId 枚举、CanDataView / UartDataView / AccelerometerDataView / GyroscopeDataView 及抽象接口 IDataCallback(CAN/UART 返回 bool,IMU 回调为 void)。
协程 LIFO 框架
core/src/coroutine/lifo.hpp
新增 LifoContextInlineLifoContextLifoStackedPromiseLifoTask(含 void 特化),实现基于栈的协程帧分配与 awaiting 语义。
协议常量与位域布局
core/src/protocol/constant.hpp, core/src/protocol/protocol.hpp
新增常量 kProtocolBufferSize = 1023;通过 utility::Bitfield 定义 FieldHeader/CanHeader/UartHeader/ImuHeader 及标准/扩展布局与 IMU 负载。
序列化器
core/src/protocol/serializer.hpp
新增 ISerializeBufferSerializer,提供 write_can/write_uart/write_imu_* 接口,含头部选择、大小计算与返回式错误码。
反序列化器与回调接口
core/src/protocol/deserializer.hpp, core/src/protocol/deserializer.cpp
新增 IDeserializeCallbackDeserializer;实现协程驱动的流式解析与 per-field 处理(process_streamprocess_can_fieldprocess_uart_fieldprocess_imu_field)、缓冲/错误上报与 PeekBytesAwaiter。
位域与工具库
core/src/utility/bitfield.hpp, core/src/utility/assert.hpp, core/src/utility/stack_allocator.hpp, core/src/utility/verify.hpp
新增通用 Bitfield 模板与 BitfieldMember、断言工具 assert/assert_always/assert_failed、可切换调试行为的栈分配器(StackAllocator)及验证宏 LIBRMCS_VERIFY / LIBRMCS_VERIFY_LIKELY
主机侧传输抽象
host/src/transport/transport.hpp
新增 ITransportBuffer / ITransport 抽象与工厂 create_usb_transport,约定固定大小缓冲区与传输/接收契约。
libusb 传输实现
host/src/transport/usb.cpp
新增 libusb 实现 Usb(传输/接收池、TransferWrapper、事件线程、设备枚举、回调、资源清理)并导出工厂函数。
主机协议处理与流缓冲
host/include/librmcs/protocol/handler.hpp, host/src/protocol/handler.cpp, host/src/protocol/stream_buffer.hpp
新增 Handler 与其 PacketBuilder(对接传输与反序列化);新增 StreamBuffer 实现 ISerializeBuffer,负责缓冲分配与提交。
Agent 与上层 API
host/include/librmcs/agent/alliance_board.hpp
新增 AllianceBoard(实现 data::IDataCallback),提供链式 PacketBuilder 发送 API(CAN/UART)与可覆写的接收钩子。
主机工具与并发结构
host/src/logging/logging.hpp, host/src/utility/cross_os.hpp, host/src/utility/final_action.hpp, host/src/utility/ring_buffer.hpp
新增单例 Logger、跨平台宏与检测函数、FinalAction RAII 工具与无锁 SPSC RingBuffer<T> 模板。
构建与预设
host/CMakeLists.txt, host/CMakePresets.json
添加完整 CMake 构建脚本(静态/共享选项、平台依赖、CPack 打包规则)及 CMake 预设(Linux/Ninja/Debug)。

Sequence Diagram(s)

sequenceDiagram
participant Agent as AllianceBoard
participant Handler as host::protocol::Handler
participant USB as host::transport::Usb
participant Deser as core::protocol::Deserializer
participant Callback as IDeserializeCallback / IDataCallback
Agent->>Handler: 构造(usb_vid,pid,serial, IDataCallback)
Handler->>USB: create_usb_transport -> 初始化 Usb
USB->>Deser: receive 回调(feed span)
Deser->>Deser: 协程 Peek/consume 字节 -> 解析 FieldHeader
alt CAN
Deser->>Callback: can_deserialized_callback(FieldId, CanDataView)
Callback->>Agent: IDataCallback::can_receive_callback(...)
else UART
Deser->>Callback: uart_deserialized_callback(FieldId, UartDataView)
Callback->>Agent: IDataCallback::uart_receive_callback(...)
else IMU
Deser->>Callback: accelerometer/gyroscope_deserialized_callback(...)
end
Agent->>Handler: start_transmit() -> PacketBuilder
Agent->>PacketBuilder: canX_transmit / uartX_transmit / imu_transmit
PacketBuilder->>Handler: 使用 StreamBuffer + Serializer 写入缓冲
Handler->>USB: transmit(buffer, size)
USB->>USB: libusb_submit_transfer -> 完成回调释放 buffer
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 分钟

需重点复核的区域:

  • core/src/coroutine/lifo.hpp:promise/operator new/delete、协程帧分配与栈生命周期。
  • core/src/utility/bitfield.hpp:位偏移/跨字节读写、类型映射与边界条件。
  • host/src/transport/usb.cpp:libusb 设备枚举、传输池、事件线程、回调与清理路径。
  • core/src/protocol/deserializer.{hpp,cpp}:流式协程解析、PeekBytesAwaiter、错误/丢弃模式。
  • core/src/protocol/serializer.hpp 与 host/src/protocol/stream_buffer.hpp:大小计算、头部编码、缓冲切换与对齐/边界校验。
  • host/CMakeLists.txt:平台条件、链接依赖与打包配置。

🐰 栈上跃步轻又快,字节成河向前来,
协程缝合每一帧,位域拼出通信牌,
USB 风里带回声,主机处理不言败,
小兔鼓掌声叽喳,协议新版我来栽,
数据飞舞,邮路开。

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 8.21% which is insufficient. The required threshold is 80.00%.You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ Passed标题准确概括了拉取请求的主要变更:添加USB传输和二进制协议支持,涵盖CAN/UART/IMU数据处理和主机-设备通信的核心功能。
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/protocol

Comment @coderabbitai help to get the list of available commands and usage tips.

@qzhhhi

Copy link
Copy Markdown
MemberAuthor

@coderabbitai review

@coderabbitai

Copy link
Copy Markdown
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@qzhhhiqzhhhi moved this from Todo to In progress in RMCS Slave SDKDec 19, 2025

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 13

🧹 Nitpick comments (20)
host/src/logging/logging.hpp (3)

39-42: 移除 constexpr 以避免误导。

get_instance() 被标记为 constexpr,但包含静态局部变量,实际上无法在编译期求值。虽然 C++20+ 语法上允许,但 constexpr 在此处并无实际意义,可能误导读者期望编译期单例初始化。

🔎 建议的修改
- static constexpr Logger& get_instance() noexcept {+ static Logger& get_instance() noexcept {
static Logger logger{};
return logger;
}

140-158: Lambda 上的 constexpr 无实际作用。

print_prefix 中的 lambda 标记为 constexpr(第 141 行),但由于 level 是运行时参数,lambda 始终在运行时求值。虽然语法正确且不影响功能,但 constexpr 在此处无实际意义。

🔎 建议的修改
 static void print_prefix(Level level) {
- std::string_view level_text = [level]() constexpr -> std::string_view {+ std::string_view level_text = [level]() -> std::string_view {
if (level == Level::TRACE)
return "trace";

或考虑使用 switch 语句代替 lambda,提高可读性:

 static void print_prefix(Level level) {
- std::string_view level_text = [level]() constexpr -> std::string_view {- if (level == Level::TRACE)- return "trace";- else if (level == Level::DEBUG)- return "debug";- else if (level == Level::INFO)- return "info";- else if (level == Level::WARN)- return "warn";- else if (level == Level::ERR)- return "error";- else if (level == Level::CRITICAL)- return "critical";- else- std::unreachable();- }();+ std::string_view level_text;+ switch (level) {+ case Level::TRACE: level_text = "trace"; break;+ case Level::DEBUG: level_text = "debug"; break;+ case Level::INFO: level_text = "info"; break;+ case Level::WARN: level_text = "warn"; break;+ case Level::ERR: level_text = "error"; break;+ case Level::CRITICAL: level_text = "critical"; break;+ case Level::OFF: level_text = "off"; break;+ }
std::print(std::cerr, "[librmcs] [{}] ", level_text);
}

45-45: 考虑 should_log 的可见性。

should_log 方法声明为 public,但仅在内部使用。如果不打算作为公共 API 供用户在记录日志前检查是否需要执行昂贵的计算,建议将其移至 private 区域以减少 API 表面。

如果确实希望用户使用此方法来避免不必要的计算,建议添加文档说明用法。

host/src/utility/ring_buffer.hpp (3)

33-33: 移除构造函数结束括号后的多余分号

构造函数定义的结束括号后有一个不必要的分号。

🔎 建议的修复
- };+ }

106-138: 批量构造逻辑正确,但存在命名不一致

批量构造的实现正确:

  • 内存序符合生产者模式(in_ 使用 relaxedout_ 使用 acquire
  • 环形缓冲区的回绕处理正确(通过两个切片)
  • 使用 release 发布构造的元素

但第 120 行的变量命名使用了 writeable(英式拼写),而方法名是 writable()(美式拼写),存在不一致。

🔎 建议:统一命名风格
- auto writeable = max_size() - (in - out);+ auto writable_count = max_size() - (in - out);- if (count > writeable)- count = writeable;+ if (count > writable_count)+ count = writable_count;

214-221: 逻辑正确,但格式可改进

单元素弹出的逻辑正确,但第 219 行的格式不够清晰:返回类型 boolrequires 子句在同一行,影响可读性。

🔎 建议:改进格式
 template <typename F>
- requires requires(F f, T t) { f(std::move(t)); } bool pop_front(F&& callback_functor) {+ requires requires(F f, T t) { f(std::move(t)); }+ bool pop_front(F&& callback_functor) {
return pop_front_n(std::forward<F>(callback_functor), 1);
}
host/CMakePresets.json (1)

1-13: 配置合理,可考虑添加 Release 预设

当前仅定义了 Debug 构建类型。如果后续需要发布构建或性能测试,建议添加 Release 或 RelWithDebInfo 预设。

🔎 可选:添加 Release 预设
 "configurePresets": [
{
"name": "linux",
"generator": "Ninja",
"binaryDir": "${sourceDir}/build",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug"
}
+ },+ {+ "name": "linux-release",+ "generator": "Ninja",+ "binaryDir": "${sourceDir}/build-release",+ "cacheVariables": {+ "CMAKE_BUILD_TYPE": "Release"+ }
}
]
core/src/utility/stack_allocator.hpp (1)

83-87: kMaxAllocs = 16 限制可能过于严格

对于复杂的协程链,16 层分配限制可能不足。根据相关代码片段 (core/src/coroutine/lifo.hpp),协程帧使用此分配器。建议考虑增大此值或使其可配置。

🔎 可选:增大限制或使其可配置
 private:
- static constexpr std::size_t kMaxAllocs = 16;+ static constexpr std::size_t kMaxAllocs = 64; // 或考虑模板参数化
void* lifo_check_stack_[kMaxAllocs]{};
std::size_t lifo_check_depth_ = 0;
};
host/src/protocol/stream_buffer.hpp (1)

104-106: 文档与代码常量名不一致。

文档注释中提到 kTransportBufferSize,但代码中实际使用的是 kProtocolBufferSize(见第 123、175 行)。建议统一文档描述以避免混淆。

host/src/transport/transport.hpp (2)

26-31: 文档与代码常量名不一致。

第 26 行注释提到 kTransportBufferSize,但第 31 行实际使用 kProtocolBufferSize。第 38 行同样如此。建议统一命名以保持一致性。


131-132: 建议使用 std::optional<uint16_t> 替代 int32_t 表示 USB PID。

USB 产品 ID (PID) 在 USB 标准中是 16 位无符号值,应使用 uint16_t 而非 int32_t。当前代码使用 product_id >= 0 作为哨兵值表示"任意 PID",这隐含了 -1 作为特殊值。改用 std::optional<uint16_t> 会使意图更加明确:空值表示任意 PID,有值表示指定的 PID。

core/include/librmcs/data/datas.hpp (2)

32-43: span 成员的生命周期依赖。

CanDataView::can_dataUartDataView::uart_data 是视图类型,调用方必须确保底层数据在视图使用期间保持有效。这在回调场景中是合理的(回调期间数据有效),但建议在文档中明确说明此约束。


71-71: 命名空间后多余分号。

C++ 标准中命名空间闭合括号后不需要分号。虽然无害,但建议移除以保持代码风格一致。

🔎 建议修复
-}; // namespace librmcs::data+} // namespace librmcs::data
core/src/protocol/protocol.hpp (1)

1-105: 结构良好,协议定义清晰。

Bitfield 布局设计合理,通过多重继承组合不同的 header 布局,层次分明。

有几个小的改进建议:

  1. ImuAccelerometerPayloadImuGyroscopePayload(第 93-103 行)结构完全相同,可以考虑提取一个通用的 ImuVec3Payload 类型,或者使用类型别名减少重复。

  2. 第 33、38 行的魔法数字 8 + 138 + 29 建议添加注释说明计算逻辑,或使用命名常量提高可读性。

host/CMakeLists.txt (1)

74-76: 硬编码的 libusb 包含路径降低了可移植性。

/usr/include/libusb-1.0 是硬编码路径,在不同 Linux 发行版或自定义安装位置下可能无效。

🔎 建议使用 pkg-config 查找 libusb
 elseif(UNIX)
- target_include_directories(${PROJECT_NAME} SYSTEM PRIVATE /usr/include/libusb-1.0)- target_link_libraries(${PROJECT_NAME} PUBLIC usb-1.0)+ find_package(PkgConfig REQUIRED)+ pkg_check_modules(LIBUSB REQUIRED libusb-1.0)+ target_include_directories(${PROJECT_NAME} SYSTEM PRIVATE ${LIBUSB_INCLUDE_DIRS})+ target_link_libraries(${PROJECT_NAME} PUBLIC ${LIBUSB_LIBRARIES})
find_package(Threads REQUIRED)
target_link_libraries(${PROJECT_NAME} PUBLIC Threads::Threads)
endif()
host/src/transport/usb.cpp (1)

206-208: 建议使用 std::unique_ptr 替代裸指针管理数组。

使用 new[] 配合手动 delete[] 容易遗漏,且在异常路径上可能泄漏。

🔎 使用智能指针管理
- auto device_descriptors = new libusb_device_descriptor[device_count];- utility::FinalAction free_device_descriptors{- [&device_descriptors]() { delete[] device_descriptors; }};+ auto device_descriptors = std::make_unique<libusb_device_descriptor[]>(device_count);

后续代码中将 device_descriptors 改为 device_descriptors.get() 即可。

host/include/librmcs/agent/alliance_board.hpp (2)

10-11: 硬编码的 USB VID 限制了重用性。

构造函数中硬编码了 0xa11c 作为 vendor ID。如果需要支持不同的硬件变体,建议提供可配置的构造函数。

🔎 建议添加可配置构造函数
 public:
- AllianceBoard()- : handler_(0xa11c, -1, nullptr, *this) {}+ static constexpr uint16_t kDefaultVendorId = 0xa11c;++ AllianceBoard(uint16_t vid = kDefaultVendorId, int32_t pid = -1, const char* serial = nullptr)+ : handler_(vid, pid, serial, *this) {}

74-77: 空回调函数使用 [[maybe_unused]] 属性更为规范。

当前使用 (void)data; 抑制未使用参数警告是有效的,但使用 C++17 的 [[maybe_unused]] 属性更加现代化和清晰。

🔎 使用 [[maybe_unused]] 属性
- virtual void can0_receive_callback(const librmcs::data::CanDataView& data) { (void)data; }+ virtual void can0_receive_callback([[maybe_unused]] const librmcs::data::CanDataView& data) {}

其他回调函数同理。

Also applies to: 90-94

core/src/coroutine/lifo.hpp (1)

266-266: 命名空间结束处有多余的分号。

虽然这不会导致编译错误,但不符合 C++ 惯例。

🔎 移除多余分号
-}; // namespace librmcs::core::coroutine+} // namespace librmcs::core::coroutine
core/src/utility/bitfield.hpp (1)

144-147: 格式不一致:requires 子句与函数声明在同一行

其他方法(如第 126-127 行、132-134 行)将 requires 子句单独放在一行。建议保持一致的代码风格。

🔎 建议的格式调整
 template <is_bitfield_member Member>
- requires(check_member<Member>()) constexpr void set(typename Member::ValueType value) noexcept {+ requires(check_member<Member>())+ constexpr void set(typename Member::ValueType value) noexcept {
set<Member>(value, data);
}
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 543130f and a4c8615.

📒 Files selected for processing (23)
  • core/include/librmcs/data/datas.hpp (1 hunks)
  • core/src/coroutine/lifo.hpp (1 hunks)
  • core/src/protocol/constant.hpp (1 hunks)
  • core/src/protocol/deserializer.cpp (1 hunks)
  • core/src/protocol/deserializer.hpp (1 hunks)
  • core/src/protocol/protocol.hpp (1 hunks)
  • core/src/protocol/serializer.hpp (1 hunks)
  • core/src/utility/assert.hpp (1 hunks)
  • core/src/utility/bitfield.hpp (1 hunks)
  • core/src/utility/stack_allocator.hpp (1 hunks)
  • core/src/utility/verify.hpp (1 hunks)
  • host/CMakeLists.txt (1 hunks)
  • host/CMakePresets.json (1 hunks)
  • host/include/librmcs/agent/alliance_board.hpp (1 hunks)
  • host/include/librmcs/protocol/handler.hpp (1 hunks)
  • host/src/logging/logging.hpp (1 hunks)
  • host/src/protocol/handler.cpp (1 hunks)
  • host/src/protocol/stream_buffer.hpp (1 hunks)
  • host/src/transport/transport.hpp (1 hunks)
  • host/src/transport/usb.cpp (1 hunks)
  • host/src/utility/cross_os.hpp (1 hunks)
  • host/src/utility/final_action.hpp (1 hunks)
  • host/src/utility/ring_buffer.hpp (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (10)
core/src/protocol/deserializer.cpp (4)
core/src/coroutine/lifo.hpp (3)
  • assert (251-251)
  • assert (257-257)
  • static_cast (66-66)
core/src/protocol/deserializer.hpp (5)
  • assert (110-149)
  • assert (155-172)
  • assert (190-198)
  • id (20-20)
  • id (22-22)
core/src/utility/assert.hpp (2)
  • assert (38-47)
  • assert (39-39)
core/include/librmcs/data/datas.hpp (2)
  • id (62-62)
  • id (64-64)
core/src/utility/assert.hpp (3)
core/src/coroutine/lifo.hpp (6)
  • assert (251-251)
  • assert (257-257)
  • noreturn (117-119)
  • noreturn (148-148)
  • noreturn (200-202)
  • noreturn (229-229)
core/src/protocol/deserializer.hpp (3)
  • assert (110-149)
  • assert (155-172)
  • assert (190-198)
host/src/protocol/stream_buffer.hpp (2)
  • assert (194-207)
  • assert (209-219)
core/src/utility/stack_allocator.hpp (2)
core/src/coroutine/lifo.hpp (11)
  • n (21-21)
  • n (21-21)
  • n (76-87)
  • n (76-76)
  • n (91-93)
  • n (91-91)
  • static_cast (66-66)
  • p (23-25)
  • p (23-23)
  • p (96-108)
  • p (96-96)
core/src/utility/assert.hpp (2)
  • assert_always (32-36)
  • assert_always (32-33)
host/include/librmcs/protocol/handler.hpp (4)
host/include/librmcs/agent/alliance_board.hpp (2)
  • PacketBuilder (56-57)
  • PacketBuilder (56-56)
host/src/protocol/handler.cpp (7)
  • PacketBuilder (114-120)
  • PacketBuilder (122-124)
  • view (85-85)
  • view (89-89)
  • Handler (146-148)
  • Handler (150-151)
  • Handler (161-161)
core/src/protocol/serializer.hpp (17)
  • field_id (31-74)
  • field_id (31-31)
  • field_id (76-109)
  • field_id (76-76)
  • field_id (162-165)
  • field_id (162-162)
  • field_id (167-170)
  • field_id (167-167)
  • field_id (184-201)
  • field_id (184-184)
  • field_id (203-220)
  • field_id (204-204)
  • field_id (224-235)
  • view (111-134)
  • view (111-111)
  • view (136-159)
  • view (136-136)
host/src/transport/usb.cpp (2)
  • callback (107-115)
  • callback (107-107)
host/src/protocol/stream_buffer.hpp (2)
core/src/protocol/serializer.hpp (1)
  • size (21-21)
core/src/utility/assert.hpp (2)
  • assert (38-47)
  • assert (39-39)
core/src/protocol/serializer.hpp (1)
core/src/utility/bitfield.hpp (2)
  • Ref (153-154)
  • Ref (153-153)
host/src/transport/usb.cpp (3)
host/src/logging/logging.hpp (2)
  • get_logger (161-161)
  • get_logger (161-161)
host/src/utility/cross_os.hpp (2)
  • is_linux (11-17)
  • is_linux (11-11)
host/src/utility/ring_buffer.hpp (6)
  • value (166-168)
  • value (166-166)
  • value (173-175)
  • value (173-173)
  • n (236-247)
  • n (236-236)
host/include/librmcs/agent/alliance_board.hpp (3)
core/include/librmcs/data/datas.hpp (4)
  • data (66-66)
  • data (68-68)
  • id (62-62)
  • id (64-64)
core/src/protocol/deserializer.hpp (4)
  • data (24-24)
  • data (26-26)
  • id (20-20)
  • id (22-22)
host/src/protocol/handler.cpp (8)
  • data (42-44)
  • data (42-42)
  • data (46-48)
  • data (46-46)
  • id (30-34)
  • id (30-31)
  • id (36-40)
  • id (36-37)
core/src/protocol/deserializer.hpp (2)
core/include/librmcs/data/datas.hpp (4)
  • id (62-62)
  • id (64-64)
  • data (66-66)
  • data (68-68)
core/src/protocol/deserializer.cpp (2)
  • process_stream (14-59)
  • process_stream (14-14)
core/src/coroutine/lifo.hpp (2)
core/src/utility/stack_allocator.hpp (8)
  • n (26-37)
  • n (26-26)
  • n (56-68)
  • n (56-56)
  • p (39-42)
  • p (39-39)
  • p (70-81)
  • p (70-70)
core/src/utility/assert.hpp (4)
  • noreturn (19-30)
  • assert_failed (20-20)
  • assert (38-47)
  • assert (39-39)
🪛 Cppcheck (2.18.0)
host/src/protocol/handler.cpp

[error] 104-104: Found an exit path from function with non-void return type that has missing return statement

(missingReturn)

🔇 Additional comments (59)
host/src/utility/ring_buffer.hpp (12)

49-53: 实现正确

容量计算正确,mask + 1 返回实际的环形缓冲区大小(2 的幂)。


55-63: 内存序正确

对于消费者侧的可读元素查询,内存序使用正确:

  • in_ 使用 acquire 确保能看到生产者的写入
  • out_ 使用 relaxed 是安全的,因为只有消费者线程修改它

65-74: 内存序正确

对于生产者侧的可写槽位查询,内存序使用正确:

  • out_ 使用 acquire 确保能看到消费者的进度
  • in_ 使用 relaxed 是安全的,因为只有生产者线程修改它

76-89: 实现正确

peek_front() 实现正确:

  • 内存序符合消费者模式
  • 正确使用 std::launder 获取 placement new 后的有效指针
  • 空缓冲区时返回 nullptr

91-104: 实现正确

peek_back() 实现正确:

  • 使用 (in - 1) & mask 正确获取最后一个元素
  • 内存序正确
  • 正确使用 std::launder

140-148: 实现正确

正确使用 std::forward 转发参数,调用 emplace_back_n 实现单元素构造。


150-160: 实现正确

通过生成器批量推送的实现正确,正确地将生成器包装在 lambda 中调用 emplace_back_n


162-175: 实现正确

拷贝和移动重载实现正确,分别使用拷贝构造和移动构造。


177-212: 批量弹出实现正确

pop_front_n() 实现正确:

  • 内存序符合消费者模式(in_ 使用 acquireout_ 使用 relaxed
  • 正确使用 std::launder 获取对象指针
  • 元素被移动到回调函数后通过 std::destroy_at 销毁
  • 使用 release 向生产者发布已释放的空间
  • 回绕处理正确

223-229: 实现正确且简洁

通过调用 pop_front_n 并传入空操作 lambda 来清空缓冲区,这是一个简洁且正确的实现。


231-247: 实现正确

标准的位操作算法,正确地将数值向上舍入到下一个 2 的幂。正确处理了 32 位和 64 位 size_t。构造函数确保传入的值至少为 2,满足 "n > 0" 的假设。


249-254: 数据成员设计正确

私有成员设计合理:

  • Storage 使用 alignas(T) 确保正确的对齐
  • 原子索引正确初始化为 0
  • 基于掩码的索引对 2 的幂大小很高效
host/src/utility/cross_os.hpp (2)

11-25: 平台检测函数实现正确

is_linux()is_windows() 使用编译期常量,实现简洁有效。


27-33: ALWAYS_INLINE 宏实现良好

涵盖主流编译器并提供了合理的 fallback。

core/src/utility/assert.hpp (2)

38-47: assert 函数设计合理

Release 模式下使用 [[assume(condition)]] 为编译器提供优化提示是好的实践。注意 [[assume]] 也是 C++23 属性。


19-30: 项目已强制要求 C++23 标准,无需额外配置

当前项目在 ./host/CMakeLists.txt 中已明确设置 CMAKE_CXX_STANDARD 23CMAKE_CXX_STANDARD_REQUIRED True,确保了编译必须使用 C++23。代码中对 std::unreachable()std::println() 的使用完全符合项目要求,并通过预处理器正确条件编译相关头文件。

core/src/utility/stack_allocator.hpp (1)

14-46: Release 模式分配器实现正确

BasicStackAllocator<false> 实现了标准的栈式分配语义,对齐处理正确。deallocate 忽略 n 参数符合栈分配器的设计意图。

core/src/protocol/constant.hpp (1)

7-7: 缓冲区大小 1023 是否有特殊考量?

kProtocolBufferSize = 1023 不是常见的 2 的幂次方大小。如果这是有意为之(例如为协议头预留 1 字节),建议添加注释说明原因;否则考虑使用 1024。

core/src/utility/verify.hpp (1)

3-15: LGTM!

宏定义实现正确:

  • 使用 do-while(false) 惯用法确保宏在所有上下文中安全展开
  • [[unlikely]] 属性正确应用于失败分支,有助于分支预测优化
host/src/utility/final_action.hpp (1)

7-28: LGTM! RAII 作用域守卫实现正确。

实现遵循了标准的 scope guard 模式。如果项目已经依赖 GSL,可以考虑使用 gsl::finally,不过自定义实现同样可行且零依赖。

host/src/protocol/stream_buffer.hpp (3)

69-93: LGTM! 缓冲区生命周期管理正确。

  • 构造函数正确绑定 transport 引用
  • 移动构造函数正确转移所有权并置空源对象指针
  • 析构函数确保待发送数据被提交,防止数据丢失

121-137: 分配逻辑正确。

allocate() 方法正确处理了以下场景:

  • 首次分配时延迟初始化缓冲区
  • 空间不足时自动提交当前缓冲区并获取新缓冲区
  • 资源耗尽时返回空 span

173-191: allocate_up_to 实现正确。

弹性分配逻辑合理,返回 [min_size, max_size] 范围内尽可能大的可用空间。

host/src/transport/transport.hpp (3)

29-43: LGTM! 缓冲区接口设计合理。

使用固定大小的 std::span 作为 BufferSpanType 在编译期保证了缓冲区尺寸,避免了运行时检查开销。


63-129: LGTM! 传输接口设计清晰。

接口职责分明:

  • 缓冲区生命周期管理(acquire/release/transmit)
  • 接收回调机制
  • 所有权语义通过 std::unique_ptr 明确表达

107-128: receive() 的单次调用约束已通过运行时检查实现。

代码已在第110-111行实现了所请求的保护措施,通过 std::logic_error 异常抛出明确的诊断信息,防止违反"仅调用一次"的约束。无需进一步改进。

Likely an incorrect or invalid review comment.

core/include/librmcs/data/datas.hpp (1)

57-69: LGTM! 回调接口设计合理。

  • CAN/UART 回调返回 bool 用于指示 ID 是否有效,符合多路复用场景
  • IMU 回调返回 void,因为 IMU 数据不需要 ID 验证

接口语义清晰,适合协议处理层使用。

core/src/protocol/deserializer.hpp (6)

16-29: 接口设计良好。

IDeserializeCallback 接口定义清晰,包含虚析构函数和必要的纯虚回调方法。接口设计符合标准的回调模式。


31-39: 构造函数和析构函数实现正确。

构造函数正确初始化回调引用并启动协程任务,析构函数通过调用 finish_transfer() 确保正确清理状态。


43-70: feed() 方法实现正确。

快速路径和慢速路径的逻辑清晰,正确处理了部分数据缓冲和协程恢复。


72-85: finish_transfer() 逻辑正确。

正确处理了传输结束时的两种情况:干净边界(等待字段首字节)和截断字段(触发错误并恢复协程)。


99-179: PeekBytesAwaiter 实现正确。

Awaiter 正确实现了三种情况:快速路径(直接从输入读取)、慢速路径(跨块拼接到缓冲区)和挂起等待更多数据。await_ready() 中的逻辑虽然复杂但处理得当。


190-232: 消费方法和状态管理实现正确。

consume_peeked_partial 正确使用 memmove 处理重叠内存。pending_bytes_buffer_ 对齐到 max_align_t 确保了任意数据类型的正确对齐。

core/src/protocol/deserializer.cpp (4)

14-59: process_stream() 主循环实现正确。

正确处理了普通字段头和扩展字段头的解析,以及到各字段处理器的分发。未知字段 ID 会正确触发 deserializing_error()


61-111: process_can_field() 实现正确。

正确解析了标准和扩展 CAN 头,正确计算了数据长度(dlc + 1),并遵循了 peek_bytes 的扩展窗口契约。


113-147: process_uart_field() 实现正确。

正确处理了标准和扩展长度的 UART 数据解析,并在 Line 131-133 正确验证了数据长度不会超出缓冲区大小。


149-191: process_imu_field() 实现正确。

正确处理了加速度计和陀螺仪两种 IMU 载荷类型,并在解析头部后正确调用了 consume_peeked()。未知载荷类型会正确返回 false 触发错误。

host/include/librmcs/protocol/handler.hpp (2)

11-34: PacketBuilder 设计合理。

使用内联存储和类型擦除的 Pimpl 模式可以避免在公共头文件中暴露实现细节。不可复制和不可移动的设计确保了资源的正确管理。


36-38: usb_pid 类型需要确认。

USB Product ID 通常是 16 位无符号值(uint16_t),但这里使用了 int32_t。如果这是有意为之(例如使用 -1 作为哨兵值),请添加注释说明;否则建议改为 uint16_t 以保持一致性。

core/src/protocol/serializer.hpp (5)

17-22: ISerializeBuffer 接口设计简洁。

返回 std::span<std::byte>allocate() 方法允许调用者直接写入缓冲区,避免了不必要的复制。


31-74: write_can() 实现正确。

正确处理了标准和扩展 CAN 头的序列化,data_length_code 的计算与反序列化逻辑对称一致。


76-109: write_uart() 实现正确。

扩展长度阈值(> 4 字节)与标准头 DataLengthCode 的容量匹配,序列化逻辑与反序列化对称。


111-159: IMU 写入方法实现正确。

加速度计和陀螺仪的序列化遵循一致的模式,正确设置了载荷类型枚举。


172-182: 该评论中的游标推进问题已不存在。

经过代码验证,write_field_headercursor += 1 是有意设计,而非错误。

协议设计原理:

  • FieldHeaderExtended 占 2 字节,分布:Byte 0 bits 0-3(Id)+ Byte 0 bits 4-7(IdExtended 高 4 位)+ Byte 1 bits 0-3(IdExtended 低 4 位)
  • 后续的 CanHeaderExtended 从 Byte 1 开始写入,在同一 byte 内使用不同的 bit 范围:Byte 1 bits 4-7(标志位)
  • 这是有意的字节级别复用:两个结构在 Byte 1 上共享空间但使用不同的 bit 位置

验证根据:
required_can_size 中的计算 (field_header_bytes + can_header_bytes - 1) 确认了这一设计。减去 1 是因为游标只推进了 1 字节(不是 2 字节),而后续头部恰好从该位置开始,形成 1 字节的逻辑重叠但无实际数据冲突。

host/src/protocol/handler.cpp (4)

18-57: Handler::Impl 实现正确。

正确实现了 IDeserializeCallback 接口,适当地记录了未预期字段 ID 的错误。接收回调的设置简洁清晰。


61-110: PacketBuilderImpl 实现正确。

移动构造函数正确地重新初始化了 serializer_process_result 的返回值语义明确:用户错误返回 false,内部/瞬态问题返回 true(已记录日志)。


114-166: HandlerPacketBuilder 的构造/析构实现正确。

正确使用了 static_assert 验证存储大小和对齐,std::construct_at/std::destroy_at 配合 std::launder 进行放置构造/析构。移动语义使用 std::exchange 正确实现。


94-106: 静态分析警告已验证为误报。

已确认 core::utility::assert_failed()core/src/utility/assert.hpp (第 19 行) 标记为 [[noreturn]]。由于此函数永不返回,process_result() 函数的所有代码路径均有效:三个 enum 值分别处理(返回 true、true、false),第四个路径调用 assert_failed() 并终止执行。无需修改代码。

host/src/transport/usb.cpp (2)

440-443: 接收失败时调用 std::terminate() 过于激进。

TODO 注释已指出此问题。在生产环境中,建议通过错误回调或状态标志通知上层,允许应用程序优雅处理断开连接。

当前实现会导致整个进程终止。如果这是临时方案,建议尽快实现更优雅的错误处理机制。


496-497: RingBuffer 使用双互斥锁设计是安全的。

utility::RingBuffer 是标准的无锁单生产者-单消费者(SPSC)实现,基于 Linux kfifo 设计,使用原子操作(std::atomic)和适当的内存序列(acquire/release)保证线程安全。两个独立的互斥锁分别保护 push 和 pop 操作,这与 RingBuffer 的 SPSC 设计完全兼容,可以有效地在 API 层面强制单生产者-单消费者的使用规范。无数据竞争风险。

core/src/coroutine/lifo.hpp (2)

117-119: get_return_object_on_allocation_failure 标记为 [[noreturn]] 是正确的。

该函数调用 utility::assert_failed() 后永不返回,返回类型仅用于满足编译器对函数签名的要求。这是处理协程分配失败的合理方式。

Also applies to: 200-202


72-109: LifoStackedPromise 的帧分配策略设计精巧。

通过在帧末尾附加 LifoContext* 指针作为 trailer,使得 operator delete 能够在不持有额外上下文的情况下正确回收内存。使用 std::launder 处理类型转换符合严格别名规则。

模板参数 static_contextnullptr 时使用动态上下文,非空时使用静态上下文,提供了良好的灵活性。

core/src/utility/bitfield.hpp (7)

1-12: LGTM!

包含文件和命名空间结构合理,BitfieldMemberTag 作为空标签类型用于 concept 约束是惯用的 C++ 模式。


14-24: LGTM!

类型映射逻辑正确,嵌套 conditional_t 是编译期类型选择的惯用方式。当 BitWidth > 64 时返回 void,在 BitfieldMemberstatic_assert 中会被正确捕获。


25-37: LGTM!

BitfieldMember 的约束设计合理:

  • std::is_trivial_v 确保值类型可以安全地进行位操作
  • sizeof(ValueT) <= sizeof(std::uint64_t) 与最大 64 位宽度一致
  • is_bitfield_member concept 使用 std::derived_from 正确实现类型检测

94-116: LGTM!

check_member 的编译期验证逻辑完备:

  • 确保字段不越界 (first_bit < size_in_bits, bit_width <= size_in_bits - first_bit)
  • last_byte = (last_bit + 7) / 8 正确实现向上取整,处理跨字节边界的情况
  • BestWordForSpan 选择最小适用的无符号整型以优化性能

152-184: LGTM!

RefCRef 辅助类设计简洁:

  • 使用 explicit 构造函数防止隐式转换
  • CRef 仅提供读取接口,遵循 const 正确性
  • 将指针封装为私有成员,接口保持最小化

186-214: LGTM!

read_bits 实现正确:

  • 小端字节序累积:accum |= (Word(b) << (8 * i)) 正确处理多字节跨越
  • 掩码计算处理了全宽度边界情况(第 209-210 行),避免了移位未定义行为
  • C++20 中 static_cast 转换有符号类型时行为明确(二进制补码保证)

216-249: LGTM!

write_bits 实现正确:

  • 读-修改-写模式正确保留了相邻位
  • accum &= ~(full_mask << inner_offset) 清除目标位
  • Word(value) & full_mask 在写入前截断超出 bit_width 的值——这是零开销设计的一部分,符合文档中描述的"不执行运行时边界检查"

整体来看,这是一个设计良好的编译期类型安全的位域工具,适用于协议头和二进制数据格式的处理。

Comment threadcore/src/coroutine/lifo.hpp Outdated
Comment threadcore/src/utility/assert.hpp
Comment threadcore/src/utility/stack_allocator.hpp
Comment threadhost/CMakeLists.txt
Comment threadhost/CMakeLists.txt
Comment threadhost/src/logging/logging.hpp
Comment threadhost/src/transport/usb.cpp
Comment threadhost/src/transport/usb.cpp
Comment threadhost/src/utility/cross_os.hpp
Comment threadhost/src/utility/ring_buffer.hpp Outdated
@qzhhhi

Copy link
Copy Markdown
MemberAuthor

@coderabbitai review

@coderabbitai

Copy link
Copy Markdown
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

♻️ Duplicate comments (8)
host/src/logging/logging.hpp (2)

13-21: Level::OFF 可能导致未定义行为。

如果将 Level::OFF 传递给日志方法(例如 log(Level::OFF, "msg")),会在 print_prefix 的第 153 行触发 std::unreachable(),导致未定义行为。

建议在 log_internal 开头显式处理 Level::OFF 并提前返回,或在 print_prefix 中为 OFF 添加分支。

🔎 建议的修复方案

log_internal 开始处添加早期返回:

 template <typename... Args>
void log_internal(Level level, std::format_string<Args...> fmt, Args&&... args) {
+ if (level == Level::OFF)+ return;
if (!should_log(level))
return;

对另一个 log_internal 重载做相同处理。


27-157: 缺少线程同步机制。

Logger 类没有任何同步措施(如 mutex),多线程并发调用日志方法时,输出会交错混杂,影响日志可读性。虽然 C++ 标准流对单个字符写入是线程安全的,但完整的格式化输出(前缀 + 消息)会被其他线程中断。

建议添加 std::mutex 保护 log_internal 方法。

🔎 建议的修复

在 Logger 类中添加互斥锁:

+#include <mutex>+
class Logger {
public:
static constexpr Level logging_level = Level::LIBRMCS_LOGGING_LEVEL;
+private:+ mutable std::mutex log_mutex_;+
private:
- constexpr Logger() noexcept = default;+ Logger() noexcept = default;
template <typename... Args>
void log_internal(Level level, std::format_string<Args...> fmt, Args&&... args) {
if (!should_log(level))
return;
+ std::lock_guard<std::mutex> lock(log_mutex_);
print_prefix(level);
std::println(std::cerr, fmt, std::forward<Args>(args)...);
}
template <typename T>
void log_internal(Level level, const T& msg) {
if (!should_log(level))
return;
+ std::lock_guard<std::mutex> lock(log_mutex_);
print_prefix(level);
std::cerr << msg << '\n';
}

注意:添加 mutex 后需要移除构造函数的 constexpr 修饰符。

host/CMakeLists.txt (1)

61-66: Debug 模式强制开启优化可能影响调试体验。

此问题在之前的审查中已提出。在 Debug 配置下强制使用 -O3//O2 优化会导致调试器难以设置断点、变量值可能被优化掉。建议移除此配置或使用 RelWithDebInfo

host/src/transport/usb.cpp (2)

54-55: libusb_attach_kernel_driver 使用硬编码接口号与 detach 不一致。

此问题在之前的审查中已提出。libusb_detach_kernel_driver(第 175 行)使用 target_interface_,但 attach 使用硬编码的 0

🔎 建议修复
 if constexpr (utility::is_linux())
- libusb_attach_kernel_driver(libusb_device_handle_, 0);+ libusb_attach_kernel_driver(libusb_device_handle_, target_interface_);

343-347: 事件循环终止条件可能导致过早退出。

此问题在之前的审查中已提出。建议结合 stop_handling_events_ 作为主要终止条件。

core/src/utility/assert.hpp (1)

13-15: 取消定义 assert 宏可能导致兼容性问题。

此问题在之前的审查中已提出。#undef assert 会影响包含此头文件后的所有代码,可能导致依赖标准 assert 宏的代码出现意外行为。

core/src/coroutine/lifo.hpp (1)

184-187: result() 方法可被多次调用,但会移动结果值。

result() 使用 std::move 返回结果,第二次调用将返回已移动的值。考虑添加断言或文档说明此方法只能调用一次。

host/include/librmcs/agent/alliance_board.hpp (1)

98-103: IMU 回调函数缺少 virtual 关键字,派生类无法重写。

accelerometer_receive_callbackgyroscope_receive_callback 标记了 override(从 IDataCallback 继承),但在 AllianceBoard 中未标记为 virtual,导致派生类无法覆盖这些方法。这与 CAN/UART 回调的设计不一致。

🔎 建议的修复
- void accelerometer_receive_callback(const librmcs::data::AccelerometerDataView& data) override {+ virtual void accelerometer_receive_callback(const librmcs::data::AccelerometerDataView& data) override {
(void)data;
}
- void gyroscope_receive_callback(const librmcs::data::GyroscopeDataView& data) override {+ virtual void gyroscope_receive_callback(const librmcs::data::GyroscopeDataView& data) override {
(void)data;
}

这样派生类就可以像覆盖 CAN/UART 回调一样覆盖 IMU 回调。

🧹 Nitpick comments (10)
host/src/logging/logging.hpp (1)

138-156: Lambda 的 constexpr 修饰符冗余。

print_prefix 中的 lambda(第 139 行)被标记为 constexpr,但由于 level 参数是运行时值,该 lambda 无法在编译期求值。constexpr 修饰符在此处不提供实际性能优势,可能会误导读者以为存在编译期优化。

建议移除 lambda 的 constexpr 修饰符以提高代码清晰度。

🔎 建议的调整
- std::string_view level_text = [level]() constexpr -> std::string_view {+ std::string_view level_text = [level]() -> std::string_view {
if (level == Level::TRACE)
return "trace";
// ... 其余分支保持不变
}();
host/CMakeLists.txt (1)

74-79: 硬编码 libusb 路径可能在某些系统上失败。

UNIX 分支直接使用 /usr/include/libusb-1.0 硬编码路径,但在某些发行版(如 Arch Linux、NixOS)上路径可能不同。建议使用 pkg-configfind_package 来定位 libusb。

🔎 建议使用 pkg-config 统一处理
-elseif(UNIX)- target_include_directories(${PROJECT_NAME} SYSTEM PRIVATE /usr/include/libusb-1.0)- target_link_libraries(${PROJECT_NAME} PUBLIC usb-1.0)+elseif(UNIX)+ find_package(PkgConfig REQUIRED)+ pkg_check_modules(LIBUSB REQUIRED libusb-1.0)+ target_include_directories(${PROJECT_NAME} SYSTEM PRIVATE ${LIBUSB_INCLUDE_DIRS})+ target_link_libraries(${PROJECT_NAME} PUBLIC ${LIBUSB_LIBRARIES})
find_package(Threads REQUIRED)
target_link_libraries(${PROJECT_NAME} PUBLIC Threads::Threads)
endif()
core/src/utility/assert.hpp (1)

19-30: assert_failed 在 Release 模式下调用 std::unreachable() 可能隐藏真实错误。

NDEBUG 定义时,assert_failed 调用 std::unreachable(),这意味着如果断言实际失败,程序将进入未定义行为而非可预测的终止。虽然这对性能优化有益,但可能使生产环境中的错误更难诊断。

建议考虑在 assert_always 中即使在 Release 模式下也保留终止行为,仅在 assert 中使用 [[assume]] 优化。

core/src/coroutine/lifo.hpp (2)

60-70: 考虑为 context() 添加 noexcept 以保持一致性。

context() 方法返回引用且不会抛出异常,建议添加 noexcept 说明符以与其他访问器方法保持一致。

🔎 可选改进
- constexpr LifoContext& context() { return static_cast<LifoContext&>(*this); }+ constexpr LifoContext& context() noexcept { return static_cast<LifoContext&>(*this); }

111-113: 考虑放宽 default_constructible 约束。

当前 requires 子句要求 T 满足 std::is_default_constructible_v<T>,但 result_ 成员仅通过 return_value(T value) 移动赋值,从未默认构造。可以移除此约束以支持更多类型。

🔎 可选改进
 template <typename T = void, LifoContext* static_context = nullptr>
requires(
- std::is_same_v<T, void> || (std::is_move_assignable_v<T> && std::is_default_constructible_v<T>))+ std::is_same_v<T, void> || std::is_move_assignable_v<T>)
class LifoTask {

或者改用 std::is_move_constructible_v<T> 以匹配 await_resume()result() 中的移动构造语义。

host/include/librmcs/agent/alliance_board.hpp (1)

10-11: 考虑为魔数添加命名常量以提高可读性。

构造函数中的 0xa11c-1 参数含义不明确。建议定义命名常量(如 kAllianceBoardVendorIdkAutoDetectDevice)或添加注释说明这些值的用途。

🔎 可选改进
+ // 0xa11c: Alliance 板供应商 ID; -1: 自动检测设备
AllianceBoard()
: handler_(0xa11c, -1, nullptr, *this) {}

或定义常量:

staticconstexpruint16_tkVendorId = 0xa11c;
staticconstexprintkAutoDetect = -1;
AllianceBoard()
: handler_(kVendorId, kAutoDetect, nullptr, *this) {}
core/src/utility/stack_allocator.hpp (3)

49-88: 调试版本的 LIFO 验证实现正确,建议记录深度限制。

调试版本通过以下机制有效验证了 LIFO 约束:

  • 使用固定大小的检查栈追踪分配顺序(行 65-66)
  • 在释放时验证指针匹配栈顶(行 74)
  • 验证释放的指针位置与预期对齐后的位置一致(行 78)

关于 kMaxAllocs = 16 的限制:
此限制对于大多数协程嵌套场景应该足够,但建议在代码注释中明确说明此限制的合理性,或者考虑通过模板参数使其可配置。


28-29: 考虑提取重复的对齐计算逻辑。

对齐计算 (n + align - 1) & ~(align - 1) 在两处重复:

  • 行 28-29:非调试版本的 allocate()
  • 行 76-77:调试版本的 deallocate()

虽然这段代码很简洁,但可以考虑提取为 constexpr 辅助函数以提高可维护性。

🔎 可选的重构建议
+namespace detail {+constexpr std::size_t align_up(std::size_t n, std::size_t align) noexcept {+ return (n + align - 1) & ~(align - 1);+}+} // namespace detail+
template <>
class BasicStackAllocator<false> {
public:
// ...
constexpr void* allocate(std::size_t n) noexcept {
constexpr std::size_t align = alignof(std::max_align_t);
- n = (n + align - 1) & ~(align - 1);+ n = detail::align_up(n, align);
// ...
}
// ...

调试版本也可以类似修改。

Also applies to: 76-77


40-41: 未使用参数的处理方式可以更现代化。

行 41 使用 (void)n; 来抑制未使用参数的警告。虽然这是传统做法且完全有效,但 C++17 提供了更符合现代习惯的 [[maybe_unused]] 属性。

🔎 可选的现代化改进
- constexpr void deallocate(void* p, size_t n) noexcept {- (void)n;+ constexpr void deallocate(void* p, [[maybe_unused]] size_t n) noexcept {
top_ = static_cast<std::byte*>(p);
}
host/src/utility/ring_buffer.hpp (1)

219-219: 可选:改善格式以提高可读性

返回类型 boolrequires 子句在同一行,建议将返回类型移到单独一行以提高可读性。

🔎 建议的格式调整
 template <typename F>
- requires requires(F f, T t) { f(std::move(t)); } bool pop_front(F&& callback_functor) {+ requires requires(F f, T t) { f(std::move(t)); }+ bool pop_front(F&& callback_functor) {
return pop_front_n(std::forward<F>(callback_functor), 1);
}
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a4c8615 and ab8dfbf.

📒 Files selected for processing (11)
  • core/src/coroutine/lifo.hpp (1 hunks)
  • core/src/protocol/deserializer.cpp (1 hunks)
  • core/src/protocol/deserializer.hpp (1 hunks)
  • core/src/utility/assert.hpp (1 hunks)
  • core/src/utility/stack_allocator.hpp (1 hunks)
  • host/CMakeLists.txt (1 hunks)
  • host/include/librmcs/agent/alliance_board.hpp (1 hunks)
  • host/src/logging/logging.hpp (1 hunks)
  • host/src/transport/usb.cpp (1 hunks)
  • host/src/utility/final_action.hpp (1 hunks)
  • host/src/utility/ring_buffer.hpp (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • host/src/utility/final_action.hpp
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-12-20T04:28:53.374Z
Learnt from: qzhhhi
Repo: Alliance-Algorithm/librmcs PR: 4
File: core/src/protocol/deserializer.cpp:38-58
Timestamp: 2025-12-20T04:28:53.374Z
Learning: In core/src/protocol/deserializer.cpp, when an unknown FieldId is encountered in the default switch branch, the code does not explicitly consume peeked bytes before calling deserializing_error(). This is intentional: deserializing_error() resets input_cursor_ to input_end_ and pending_bytes_ to 0, causing the next peek_bytes() to suspend the coroutine until finish_transfer() resets the error state. The assertion at line 22 will not fail because the coroutine suspends before re-entering that path.

Applied to files:

  • core/src/protocol/deserializer.hpp
📚 Learning: 2025-12-20T04:28:53.374Z
Learnt from: qzhhhi
Repo: Alliance-Algorithm/librmcs PR: 4
File: core/src/protocol/deserializer.cpp:38-58
Timestamp: 2025-12-20T04:28:53.374Z
Learning: In core/src/protocol/deserializer.cpp, for the default switch case handling an unknown FieldId, keep the current behavior: do not eagerly consume any peeked bytes before invoking deserializing_error(). This path intentionally relies on deserializing_error() resetting input_cursor_ to input_end_ and pending_bytes_ to 0, so that the next peek_bytes() suspends the coroutine until finish_transfer() clears the error state. Ensure this rationale is documented with a comment near the default branch and add a targeted test or regression note to prevent future reintroduction of a byte-consumption in this path. If future changes modify this behavior, verify that the coroutine suspension and error-reset semantics remain consistent and that the existing assertion at the relevant line remains valid.

Applied to files:

  • core/src/protocol/deserializer.cpp
🧬 Code graph analysis (6)
core/src/protocol/deserializer.hpp (3)
core/include/librmcs/data/datas.hpp (2)
  • id (62-62)
  • id (64-64)
core/src/utility/assert.hpp (2)
  • assert (38-46)
  • assert (39-39)
core/src/protocol/serializer.hpp (15)
  • field_id (31-74)
  • field_id (31-31)
  • field_id (76-109)
  • field_id (76-76)
  • field_id (162-165)
  • field_id (162-162)
  • field_id (167-170)
  • field_id (167-167)
  • field_id (184-201)
  • field_id (184-184)
  • field_id (203-220)
  • field_id (204-204)
  • field_id (224-235)
  • field_id (224-224)
  • size (21-21)
core/src/protocol/deserializer.cpp (5)
core/src/coroutine/lifo.hpp (3)
  • assert (253-253)
  • assert (259-259)
  • static_cast (66-66)
core/src/utility/assert.hpp (2)
  • assert (38-46)
  • assert (39-39)
core/include/librmcs/data/datas.hpp (2)
  • id (62-62)
  • id (64-64)
host/src/protocol/handler.cpp (4)
  • id (30-34)
  • id (30-31)
  • id (36-40)
  • id (36-37)
host/include/librmcs/protocol/handler.hpp (2)
  • field_id (20-20)
  • field_id (22-22)
host/src/transport/usb.cpp (4)
host/src/logging/logging.hpp (2)
  • get_logger (159-159)
  • get_logger (159-159)
host/src/utility/cross_os.hpp (2)
  • is_linux (11-17)
  • is_linux (11-11)
host/src/utility/ring_buffer.hpp (6)
  • value (166-168)
  • value (166-166)
  • value (173-175)
  • value (173-173)
  • n (236-247)
  • n (236-236)
host/src/transport/transport.hpp (3)
  • buffer (91-91)
  • buffer (105-105)
  • callback (128-128)
core/src/coroutine/lifo.hpp (2)
core/src/utility/stack_allocator.hpp (8)
  • n (27-38)
  • n (27-27)
  • n (57-69)
  • n (57-57)
  • p (40-43)
  • p (40-40)
  • p (71-82)
  • p (71-71)
core/src/utility/assert.hpp (4)
  • noreturn (19-30)
  • assert_failed (20-20)
  • assert (38-46)
  • assert (39-39)
host/include/librmcs/agent/alliance_board.hpp (3)
core/src/protocol/deserializer.hpp (4)
  • data (24-24)
  • data (26-26)
  • id (20-20)
  • id (22-22)
core/include/librmcs/data/datas.hpp (4)
  • data (66-66)
  • data (68-68)
  • id (62-62)
  • id (64-64)
host/src/protocol/handler.cpp (8)
  • data (42-44)
  • data (42-42)
  • data (46-48)
  • data (46-46)
  • id (30-34)
  • id (30-31)
  • id (36-40)
  • id (36-37)
core/src/utility/stack_allocator.hpp (2)
core/src/coroutine/lifo.hpp (11)
  • n (21-21)
  • n (21-21)
  • n (76-87)
  • n (76-76)
  • n (91-93)
  • n (91-91)
  • static_cast (66-66)
  • p (23-25)
  • p (23-23)
  • p (96-108)
  • p (96-96)
core/src/utility/assert.hpp (2)
  • assert_always (32-36)
  • assert_always (32-33)
🔇 Additional comments (26)
host/src/logging/logging.hpp (1)

1-161: 整体设计合理,输出流问题已修复。

Logger 的整体设计符合最佳实践:

  • 单例模式实现正确(使用线程安全的静态局部变量)
  • 编译期日志级别过滤(should_logconstexpr
  • 格式化和原始日志 API 清晰分离
  • 正确使用 std::format_string 提供类型安全
  • 已修复:之前评审中提到的输出流不一致问题已解决,现在两个 log_internal 重载都统一输出到 std::cerr(第 126 和 135 行)

整体实现质量较高,在修复上述关键问题(Level::OFF 和线程同步)后即可投入使用。

host/src/transport/usb.cpp (2)

67-78: LGTM!

acquire_transmit_buffer 实现正确,线程安全地从环形缓冲区获取传输包装器,并正确处理无可用缓冲区的情况。


80-97: LGTM!

transmit 方法正确验证大小限制,提交传输并在成功时释放所有权给 libusb。

core/src/utility/assert.hpp (1)

38-46: LGTM!

assert 函数在 Debug 模式下正确调用 assert_always,在 Release 模式下使用 [[assume(condition)]] 提示编译器进行优化。这是 C++23 的标准用法。

core/src/protocol/deserializer.hpp (4)

16-29: LGTM!

IDeserializeCallback 接口设计清晰,为 CAN、UART、加速度计和陀螺仪数据提供了明确的回调方法,并包含错误回调。与 core/include/librmcs/data/datas.hpp 中的 IDataCallback 接口对应良好。


43-70: feed() 方法的快速路径优化设计合理。

第 53-56 行的快速路径避免了不必要的内存拷贝,当输入缓冲区已满足请求时直接恢复协程。逻辑清晰且高效。


181-188: 契约注释对调用者很有帮助。

明确说明了 peek_bytes 的使用契约:返回的指针仅在下次 peek_bytes 调用前有效,且只允许"扩展窗口"。这对于避免悬空指针问题至关重要。


200-212: consume_peeked_partial 使用 memmove 正确处理重叠内存。

当需要部分消费已缓存的字节时,使用 std::memmove 而非 memcpy 是正确的,因为源和目标内存区域可能重叠。

core/src/protocol/deserializer.cpp (4)

14-59: process_stream() 协程主循环设计合理。

正确处理了扩展字段头、不同字段类型的分发,以及错误恢复(通过 enter_discard_mode)。第 54 行的 default 分支不消费字节是故意为之的设计,根据 learnings,这允许错误状态在 finish_transfer() 时正确重置。


61-111: LGTM!

process_can_field 正确处理标准和扩展 CAN ID、远程传输帧以及可选的数据载荷。peek_bytes 的使用遵循契约,每次消费后才进行新的 peek。


113-146: LGTM!

process_uart_field 正确处理标准和扩展长度,并在第 131 行验证长度不超过缓冲区大小,防止潜在的缓冲区溢出。


148-190: LGTM!

process_imu_field 正确解析加速度计和陀螺仪数据,使用 switch 处理不同载荷类型,并在未知类型时返回 false 触发错误处理。

core/src/coroutine/lifo.hpp (3)

16-58: LIFO 上下文设计合理。

LifoContext 的实现正确地封装了栈分配器和协程暂停/恢复逻辑。resume() 方法在恢复协程前清除 waiting_coroutine_,符合 LIFO 语义。


72-109: 自定义分配器实现正确。

LifoStackedPromise 的 trailer 模式正确实现了动态上下文的恢复。对齐计算、std::launder 的使用以及静态/动态上下文的分支逻辑都是正确的。


196-266: void 特化实现正确。

LifoTask<void> 特化正确地处理了无返回值的场景,result() 方法仅进行断言检查而不返回值,避免了非 void 版本中的移动语义问题。

host/include/librmcs/agent/alliance_board.hpp (3)

63-63: start_transmit() 按值返回 PacketBuilder。

该方法按值返回 PacketBuilder,依赖于 host::protocol::Handler::PacketBuilder 的移动语义。只要底层构建器支持移动,此设计就是正确的。


66-96: CAN 和 UART 回调路由设计良好。

路由方法(can_receive_callbackuart_receive_callback)使用 final 防止被覆盖,而按 ID 划分的虚拟回调允许派生类选择性地处理特定通道。这种模式清晰且可扩展。


15-62: 无需显式发送方法 - 数据包已通过析构函数自动发送。

Handler::PacketBuilder 不需要显式的 send()commit() 方法。底层 StreamBuffer 在析构函数中会自动调用 finalize_buffer(),该方法通过 transport_.transmit() 自动传输任何待发送的数据。当 PacketBuilder 超出作用域时,整个析构链(~PacketBuilder~PacketBuilderImpl~StreamBufferfinalize_buffer())确保数据被自动传输,遵循 RAII 设计模式,不会导致数据丢失。

Likely an incorrect or invalid review comment.

core/src/utility/stack_allocator.hpp (3)

1-14: 头文件保护和包含已修复,设计合理。

之前的审查意见已得到妥善处理:

  • #pragma once 已添加
  • 未使用的 <print> 头文件已移除
  • 包含的头文件都是必需的,设计简洁

15-47: 非调试版本实现正确,符合 LIFO 栈分配器的设计。

实现要点:

  • 对齐到 std::max_align_t 的计算正确(使用位运算,假设对齐值为 2 的幂)
  • deallocate() 仅使用指针参数 p 并重置 top_,这是 LIFO 栈分配器的核心设计假设
  • 删除拷贝和移动操作符合该分配器的语义

从相关代码片段可以看出,此分配器用于协程帧分配,LIFO 特性与协程的嵌套调用模式匹配。


90-94: 条件别名设计合理,条件LIFO检查机制正确。

NDEBUG 条件切换设计是恰当的:

  • 发布版本(BasicStackAllocator):简洁的栈分配器,deallocate 仅设置 top_ = p,无运行时检查
  • 调试版本(BasicStackAllocator):继承并添加 LIFO 检查,通过 lifo_check_stack_ 追踪所有分配,deallocate 时验证 p == lifo_check_stack_[lifo_check_depth_ - 1](第74行)

关键观察:

  • 调试构建确实能捕获 LIFO 违规(第65-66行的 push,第74行的 assert)
  • 发布构建无任何检查,违规会导致 top_ 指针错误,造成静默内存损坏
  • LifoContext 中的使用遵循 LIFO 模式,C++ 协程语义天然保证帧的嵌套生命周期

验证建议:所有通过 LifoStackedPromise 创建的协程帧确实在作用域结束时被销毁,无跨越边界的非 LIFO 使用模式。当前代码实现正确。

host/src/utility/ring_buffer.hpp (5)

26-33: 构造函数逻辑正确

构造函数正确地将容量向上取整到2的幂次(最小为2),并分配存储空间。之前关于 constexpr 的问题已经解决。


61-74: 内存顺序语义正确

readable()writable() 方法使用了正确的内存顺序语义来支持 SPSC 模式:消费者在 in_ 上使用 acquire 以确保看到生产者的写入,生产者在 out_ 上使用 acquire 以确保看到消费者的更新。


82-104: Peek 操作实现正确

peek_front()peek_back() 正确处理了空缓冲区的情况,并使用 std::launder 确保指针有效性。返回裸指针的生命周期已在文档中说明,符合 SPSC 的使用约定。


127-133: 批量操作正确处理环形缓冲区回绕

emplace_back_npop_front_n 正确地将操作分为两个切片以处理环形缓冲区的回绕情况,逻辑准确。

Also applies to: 196-207


236-247: 辅助函数实现正确

round_up_to_next_power_of_2 使用标准的位操作算法,正确处理了32位和64位 size_t

Comment threadhost/CMakeLists.txt
Comment threadhost/src/transport/usb.cpp
Comment threadhost/src/transport/usb.cpp
Comment threadhost/src/utility/ring_buffer.hpp
@qzhhhi

Copy link
Copy Markdown
MemberAuthor

@coderabbitai review

@coderabbitai

Copy link
Copy Markdown
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
host/src/logging/logging.hpp (1)

15-23: Level::OFF 仍可能导致未定义行为。

此问题与之前的评审意见相同:若将 Level::OFF 传递给日志方法(例如 log(Level::OFF, "msg")),当 should_log(Level::OFF) 返回 true 时,会在 print_prefix 的 155 行调用 core::utility::assert_failed()

建议在 log_internal 开始处显式处理 Level::OFF 并提前返回,或在 print_prefix 中为 Level::OFF 添加分支。

Also applies to: 140-158

🧹 Nitpick comments (6)
host/src/logging/logging.hpp (1)

131-138: 可考虑统一使用 std::println 以保持风格一致。

原始日志路径使用 std::cerr << msg << '\n',而格式化日志路径使用 std::println(std::cerr, ...)。为保持代码风格一致性,可将原始路径也改为 std::println(std::cerr, "{}", msg)

🔎 可选的风格统一方案
 template <typename T>
void log_internal(Level level, const T& msg) {
if (!should_log(level))
return;
print_prefix(level);
- std::cerr << msg << '\n';+ std::println(std::cerr, "{}", msg);
}
core/src/coroutine/lifo.hpp (1)

66-66: 考虑移除冗余的 context() 方法。

由于 InlineLifoContext 公开继承自 LifoContextcontext() 方法返回的 LifoContext& 可以通过隐式向上转型获得。除非有特定的 API 设计考虑,否则此方法可能是多余的。

🔎 可选:移除该方法或添加文档说明其用途
- constexpr LifoContext& context() { return static_cast<LifoContext&>(*this); }

或者在注释中说明为什么需要显式的 context() 方法。

host/src/utility/ring_buffer.hpp (2)

146-191: 建议在文档中说明 noexcept 要求

emplace_backpush_back 等用户接口函数的实现使用了条件 noexcept lambda,这些 lambda 必须满足 emplace_back_nnoexcept 约束。如果类型 T 的构造函数不是 noexcept,这些函数将无法编译。

建议在这些公共 API 的文档注释中明确说明:要求类型 T 的相应构造函数必须为 noexcept(或在失败时使用 static_assert 提供更清晰的错误消息),以避免用户遇到难以理解的编译错误。

🔎 建议的文档改进示例
 /*!
* @brief Construct one element in-place at the tail (producer)
+ * @note Requires that T's constructor from Args is noexcept
* @return true if pushed, false if buffer is full
*/
template <typename... Args>
bool emplace_back(Args&&... args) {

122-122: 建议统一拼写为 "writable"

局部变量命名为 writeable,但第 71 行的成员函数使用 writable()。为保持一致性,建议统一使用 writable

🔎 建议的修复
- auto writeable = max_size() - (in - out);+ auto writable = max_size() - (in - out);- if (count > writeable)- count = writeable;+ if (count > writable)+ count = writable;
host/src/transport/usb.cpp (2)

370-392: 建议改进接收传输初始化时的错误处理。

如果在循环中某次 libusb_submit_transfer() 失败,之前已成功提交的接收传输将保持挂起状态,导致对象处于不一致状态。虽然析构函数最终会通过 libusb_close() 清理这些传输,但在 receive() 抛出异常后,对象的行为是未定义的。

建议跟踪已提交的接收传输,并在后续提交失败时显式取消它们,或者至少在文档中说明 receive() 失败后对象应被销毁。

🔎 可选的改进方案
 void init_receive_transfers() {
+ std::vector<libusb_transfer*> submitted_transfers;+ submitted_transfers.reserve(receive_transfer_count_);+
for (size_t i = 0; i < receive_transfer_count_; i++) {
auto transfer = create_libusb_transfer();
libusb_fill_bulk_transfer(
transfer, libusb_device_handle_, in_endpoint_,
new unsigned char[core::protocol::kProtocolBufferSize],
static_cast<int>(core::protocol::kProtocolBufferSize),
[](libusb_transfer* transfer) {
static_cast<Usb*>(transfer->user_data)->usb_receive_complete_callback(transfer);
},
this, 0);
transfer->flags = libusb_transfer_flags::LIBUSB_TRANSFER_FREE_BUFFER;
int ret = libusb_submit_transfer(transfer);
if (ret != 0) [[unlikely]] {
destroy_libusb_transfer(transfer);
+ // Cancel previously submitted transfers+ for (auto* submitted : submitted_transfers) {+ libusb_cancel_transfer(submitted);+ }
throw std::runtime_error(
std::format(
"Failed to submit receive transfer: {} ({})", ret, libusb_errname(ret)));
}
+ submitted_transfers.push_back(transfer);
}
}

449-466: 可选:考虑使用 libusb 自带的错误名称函数。

libusb 库本身提供了 libusb_error_name() 函数来获取错误码的字符串表示(自 1.0.9 版本起可用)。使用库函数可以减少代码重复并自动支持新的错误码。

🔎 可选的简化方案
- static constexpr const char* libusb_errname(int number) {- switch (number) {- case LIBUSB_ERROR_IO: return "ERROR_IO";- case LIBUSB_ERROR_INVALID_PARAM: return "ERROR_INVALID_PARAM";- case LIBUSB_ERROR_ACCESS: return "ERROR_ACCESS";- case LIBUSB_ERROR_NO_DEVICE: return "ERROR_NO_DEVICE";- case LIBUSB_ERROR_NOT_FOUND: return "ERROR_NOT_FOUND";- case LIBUSB_ERROR_BUSY: return "ERROR_BUSY";- case LIBUSB_ERROR_TIMEOUT: return "ERROR_TIMEOUT";- case LIBUSB_ERROR_OVERFLOW: return "ERROR_OVERFLOW";- case LIBUSB_ERROR_PIPE: return "ERROR_PIPE";- case LIBUSB_ERROR_INTERRUPTED: return "ERROR_INTERRUPTED";- case LIBUSB_ERROR_NO_MEM: return "ERROR_NO_MEM";- case LIBUSB_ERROR_NOT_SUPPORTED: return "ERROR_NOT_SUPPORTED";- case LIBUSB_ERROR_OTHER: return "ERROR_OTHER";- default: return "UNKNOWN";- }- }+ static const char* libusb_errname(int number) {+ return libusb_error_name(number);+ }

注意:如果需要保留 constexpr 特性用于编译期求值,当前实现也是合理的。

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ab8dfbf and 18998e6.

📒 Files selected for processing (4)
  • core/src/coroutine/lifo.hpp (1 hunks)
  • host/src/logging/logging.hpp (1 hunks)
  • host/src/transport/usb.cpp (1 hunks)
  • host/src/utility/ring_buffer.hpp (1 hunks)
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-12-20T05:52:38.083Z
Learnt from: qzhhhi
Repo: Alliance-Algorithm/librmcs PR: 5
File: host/src/logging/logging.hpp:27-157
Timestamp: 2025-12-20T05:52:38.083Z
Learning: In the librmcs project, the Logger class in host/src/logging/logging.hpp intentionally does not include thread synchronization (std::mutex). The team has decided that interleaved output is acceptable to avoid blocking overhead. A full async logging solution with message queues would be needed for proper thread-safe logging, but this complexity is not desired at the current stage.

Applied to files:

  • host/src/logging/logging.hpp
📚 Learning: 2025-12-20T05:28:48.614Z
Learnt from: qzhhhi
Repo: Alliance-Algorithm/librmcs PR: 5
File: core/src/utility/assert.hpp:13-15
Timestamp: 2025-12-20T05:28:48.614Z
Learning: Require include directives in all header files to place system/library headers (e.g., #include <...>) before project headers (e.g., #include "..."). This ordering reduces cross-library macro interference (such as #undef assert in core/src/utility/assert.hpp) and improves portability. Apply this convention to every .hpp in the repository (e.g., core/src/utility/assert.hpp and other headers).

Applied to files:

  • host/src/logging/logging.hpp
  • host/src/utility/ring_buffer.hpp
  • core/src/coroutine/lifo.hpp
🧬 Code graph analysis (1)
host/src/logging/logging.hpp (1)
core/src/utility/assert.hpp (1)
  • assert_failed (20-20)
🔇 Additional comments (14)
host/src/logging/logging.hpp (1)

1-12: 整体结构合理,设计清晰。

  • Include 顺序正确(系统头文件在项目头文件之前),符合项目约定。
  • 单例模式实现得当,Logger 类的 API 设计清晰,分别提供了格式化和原始日志接口。
  • should_log 过滤逻辑简洁有效。

Also applies to: 29-42, 44-117, 161-163

core/src/coroutine/lifo.hpp (6)

1-12: 头文件顺序符合约定。

系统库头文件位于项目头文件之前,遵循了既定的编码规范,有助于减少宏干扰并提高可移植性。

基于 learnings,头文件顺序约定已正确应用。


27-36: 确认单协程恢复设计是否符合预期。

resume() 方法在每次调用时仅恢复一个等待的协程。这与 LIFO 语义一致(最后挂起的协程首先恢复),但如果存在多个协程等待同一上下文的场景,需要多次调用 resume() 来依次处理。

请确认这是预期的设计行为。


72-109: 自定义分配器实现正确。

LifoStackedPromise 通过尾部指针模式(trailer pattern)巧妙地实现了协程帧的自定义分配:

  • 对齐计算使用标准位运算
  • 使用 std::launder 正确处理类型双关
  • 静态/动态上下文的双重支持提供了灵活性

实现逻辑清晰且符合 C++ 标准。


116-155: promise_type 设计合理。

协程 promise 的实现要点:

  • 分配失败直接调用 assert_failed,符合嵌入式/实时系统的零容忍策略
  • return_value 按值接收参数支持移动优化
  • FinalAwaiter 正确实现了延续链

T 需要默认可构造是因为 result_return_value 调用前已初始化,这是合理的设计折衷。


184-192: result() 方法返回引用,设计改进已完成。

根据之前的讨论,result() 现在返回 T&const T&,由调用方显式决定是否通过 std::move 转移所有权。这比之前的按值返回更清晰地表达了所有权语义,符合现代 C++ API 设计原则。

此实现解决了之前审查中讨论的问题。


201-271: void 特化实现正确。

LifoTask<void> 特化正确地处理了无返回值场景:

  • 使用 return_void() 替代 return_value()
  • 移除了 result_ 存储
  • result() 方法仅执行断言检查,保持了 API 一致性

特化结构与主模板保持了良好的对称性。

host/src/utility/ring_buffer.hpp (4)

1-11: LGTM:头文件顺序符合规范

系统/库头文件的顺序正确,符合代码规范要求。

基于 learnings,要求所有头文件将系统/库头文件(#include <...>)放在项目头文件(#include "...")之前。


26-33: LGTM:构造函数实现正确

构造函数的 constexpr 已移除(如之前审查中建议),避免了对动态分配类型使用 constexpr 的误导性。容量计算和存储分配的逻辑正确。


114-140: LGTM:已通过 noexcept 约束解决异常安全问题

模板约束(第 116 行)要求传入的仿函数必须为 noexcept,这有效解决了之前审查中提出的异常安全隐患。对于无锁 SPSC 结构,强制执行 noexcept 可以避免部分构造导致的状态不一致和资源泄漏。


193-266: LGTM:pop 操作和辅助函数实现正确

  • pop_front_npop_front 正确使用 noexcept 约束保证回调函数的异常安全
  • 元素在销毁前被移出(第 218-219 行),顺序正确
  • round_up_to_next_power_of_2 辅助函数使用位操作实现,并正确处理 32/64 位 size_t
  • clear() 使用空 noexcept lambda 消费所有元素,实现简洁
host/src/transport/usb.cpp (3)

24-112: 整体实现质量良好。

USB 传输层的核心实现逻辑清晰且健壮:

  • 构造函数初始化顺序正确,先创建传输池再启动事件线程
  • 传输缓冲区管理使用了合适的互斥锁保护
  • transmit() 方法正确验证了大小并转移了所有权
  • receive() 方法的单次调用限制和回调验证是合理的
  • TransferWrapper 使用 RAII 模式并在析构函数中提供了防御性错误检查

156-274: 设备选择和初始化逻辑实现良好。

usb_init()select_device() 的实现展现了良好的错误处理:

  • 使用 FinalAction 守卫确保失败时的资源清理
  • 设备选择逻辑全面考虑了 vendor ID、product ID 和序列号过滤
  • print_matched_unmatched_devices() 提供了有用的诊断信息
  • 错误日志详细且有助于调试

338-447: 传输管理和回调处理实现正确。

传输的初始化和回调处理展现了对 libusb API 的深入理解:

  • init_transmit_transfers() 具有异常安全性,失败时清理已分配资源
  • 回调函数正确检查 stop_handling_events_ 标志以配合析构流程
  • active_transfers_ 计数器正确跟踪传输对象的生命周期
  • 使用 LIBUSB_TRANSFER_FREE_BUFFER 标志委托 libusb 管理缓冲区内存

@qzhhhi

Copy link
Copy Markdown
MemberAuthor

@coderabbitai review

@coderabbitai

Copy link
Copy Markdown
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
host/src/transport/transport.hpp (1)

131-132: usb_pid 参数类型不一致,但具有特定用途。

libusb 标准使用 uint16_t 作为 VID 和 PID 的参数类型,但此处 usb_viduint16_tusb_pidint32_t。经检查发现,代码在 select_device() 函数中执行 if (product_id >= 0 && ...) 检查,表明 int32_t 被有意用作哨兵值——当传入负值(如 -1)时,产品 ID 匹配被跳过,实现"任意 PID"的功能。

建议添加文档说明此参数接受负值表示匹配任意 PID,或根据实际需求考虑使用更明确的设计(如单独的布尔参数或可选类型)。

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 18998e6 and e9e915d.

📒 Files selected for processing (3)
  • host/src/protocol/stream_buffer.hpp (1 hunks)
  • host/src/transport/transport.hpp (1 hunks)
  • host/src/utility/ring_buffer.hpp (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • host/src/utility/ring_buffer.hpp
  • host/src/protocol/stream_buffer.hpp
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-12-20T05:28:48.614Z
Learnt from: qzhhhi
Repo: Alliance-Algorithm/librmcs PR: 5
File: core/src/utility/assert.hpp:13-15
Timestamp: 2025-12-20T05:28:48.614Z
Learning: Require include directives in all header files to place system/library headers (e.g., #include <...>) before project headers (e.g., #include "..."). This ordering reduces cross-library macro interference (such as #undef assert in core/src/utility/assert.hpp) and improves portability. Apply this convention to every .hpp in the repository (e.g., core/src/utility/assert.hpp and other headers).

Applied to files:

  • host/src/transport/transport.hpp
🧬 Code graph analysis (1)
host/src/transport/transport.hpp (2)
core/src/protocol/deserializer.hpp (2)
  • buffer (43-70)
  • buffer (43-43)
host/src/transport/usb.cpp (6)
  • buffer (77-94)
  • buffer (77-77)
  • buffer (96-102)
  • buffer (96-96)
  • callback (104-112)
  • callback (104-104)
🔇 Additional comments (3)
host/src/transport/transport.hpp (3)

1-11: 头文件包含顺序正确!

系统/库头文件(<cstddef><cstdint> 等)在项目头文件("core/src/protocol/constant.hpp")之前,符合约定的包含顺序规范。基于 learnings,这种顺序可减少跨库宏干扰并提高可移植性。


14-43: ITransportBuffer 接口设计良好。

文档清晰地说明了所有权规则和内存保证。BufferSpanType 别名使用 std::span 的固定大小特化,确保了编译期大小检查。

data() const 返回可变 span 的设计对于缓冲区包装器来说是合理的——const 表示不修改 ITransportBuffer 对象本身,而返回的 span 允许写入底层内存。


63-129: ITransport 接口定义完善。

  • 缓冲区生命周期管理(acquire → transmit/release)设计清晰
  • receive() 的单次调用限制和线程安全文档(lines 121-127)描述准确
  • 虚析构函数标记 noexcept 符合最佳实践

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

1 participant

@qzhhhi