)
Rerun 深度定制用 eframe/egui 封装自己的 Rerun Viewerextend_viewer_ui 实战解析【免费下载链接】rerunVisualize, query, and stream to train on multimodal robotics data.项目地址: https://gitcode.com/GitHub_Trending/re/rerun导读Rerun Viewer 本身是一个独立的 GUI 程序但在真实业务中你往往需要把它嵌进自己的桌面应用旁边摆上你自己的控制面板、状态栏或数据浏览组件再让 Rerun 负责 3D/时序数据的渲染与查询。本文以仓库中的 extend_viewer_ui 示例为骨架讲解如何用eframeegui编写一个外壳应用将re_viewer::App作为核心组件嵌入其中并打通 SDK 数据通道。读完本文你将掌握自建eframe应用并初始化 Rerun 渲染环境、内嵌 gRPC 数据服务、在侧边面板中实时查询并展示EntityDb中的实体与组件数据以及数据持久化与生命周期管理的完整套路。示例定位一个可裁剪的 Viewer 外壳extend_viewer_ui的定位非常明确——它不重写 Rerun 的可视化能力而是演示「如何把 Rerun Viewer 包进你自己的 GUI」。你的 eframe Appegui 界面 ├── 你自己的面板右侧 Side PanelMy custom panel │ └── 实时展示 EntityDb 中的实体 / 组件 / 数值 └── re_viewer::App占用剩余空间渲染 3D 场景、时序数据等 └── add_log_receiver(rx) ← 来自内嵌 gRPC SDK 服务器正如 README 所述The example is really basic, but should be something you can build upon.——它刻意保持最小化是一个可以在此基础上自由扩展的起点模板。环境与运行Cargo 依赖与特性示例的 Cargo.toml 揭示了嵌入 Viewer 需要的最小特性组合[dependencies] rerun { path ../../../crates/top/rerun, default-features false, features [ native_viewer, # 原生桌面 Viewer含 eframe/egui/wgpu 渲染管线 sdk, # 日志 SDK用于接收/处理 log 消息 server, # 内嵌 gRPC 服务器能力 log_setup, # re_log 日志初始化RUST_LOG 控制 ] } # mimalloc 是更快的分配器 mimalloc.workspace true注意使用default-features false精确控制特性避免引入无关能力可选特性analytics [rerun/analytics]开启后崩溃等事件会与 Rerun 共享分析数据包版本为0.38.0-alpha.1devedition 2024rust-version 1.96请确保工具链满足要求。运行方式按 README 指引分两步启动# 第一步启动自定义 Viewer含内嵌 SDK 服务器监听 0.0.0.0:9876 cargo run -p extend_viewer_ui # 第二步向它灌数据minimal_options 示例使用 --connect 连接远程 viewer cargo run -p minimal_options -- --connect第二步中的--connect参数由 minimal_options 通过rerun::clap::RerunArgs解析其init()会建立到默认地址即本机 9876 端口的连接并持续发送点云数据。也可以改用 Python SDK 连接同一端口。关于--connect的通用语义可参考 examples/rust/README.md 中的说明connects and sends the logged data to a remote Rerun viewer。代码骨架逐段剖析核心实现全部位于 src/main.rs全文件约 200 行下面按初始化顺序拆解。1. 全局分配器内存记账 快速分配#[global_allocator] static GLOBAL: re_memory::AccountingAllocatormimalloc::MiMalloc re_memory::AccountingAllocator::new(mimalloc::MiMalloc);源码注释点明了它的两个作用re_memory::AccountingAllocator让 Rerun 能精确追踪自身内存占用并在超过阈值时修剪数据存储prune the data storemimalloc提供更快的分配速度。这是所有 Rerun 原生应用的标准配置也是长会话数据流场景下避免内存失控的根基。2. 主线程令牌与 tokio 运行时#[tokio::main] async fn main() - Result(), Boxdyn std::error::Error { let main_thread_token re_viewer::MainThreadToken::i_promise_i_am_on_the_main_thread();#[tokio::main]是必须的内嵌的 gRPC 服务器是作为 tokio 任务运行的见后文MainThreadToken是零大小类型zero-sized type本质是一个编译期/运行时约定eframe::run_native与文件对话框等 API 只能在主线程调用。通过显式持有该令牌类型系统把「我在主线程」这一前提变成可传递的证据。re_viewer在 lib.rs 中将它从re_capabilities重新导出。3. 日志与崩溃处理re_log::setup_logging(); // 输出到 stderr可用 RUST_LOGdebug 等控制级别 re_crash_handler::install_crash_handlers(re_viewer::build_info());setup_logging()把logcrate 的日志接到 stderrinstall_crash_handlers安装 panic/crash 处理器打印到 stderr并若开启analytics特性上报给 Rerun 分析服务。4. 内嵌 SDK 服务器喂数据的入口let (rx, _grpc_server_handle) re_grpc_server::spawn_with_recv( 0.0.0.0:9876.parse()?, Default::default(), re_grpc_server::shutdown::never(), );这是「Python 或 Rust 日志 SDK 可以连上来」这一能力的关键。底层实现位于 re_grpc_server/src/lib.rs它在指定地址这里是0.0.0.0:9876启动 Rerun server额外创建一条re_log_channel::LogChannel把收到的每条消息送入返回的LogReceiverrx其文档明确说明这等价于「创建客户端再调用ReadMessages」但省去了 localhost 连接的开销——数据直接经由内存通道进入 Viewer不走网络栈服务器作为 tokio 任务运行若无 tokio 运行时则 panic这正是示例使用#[tokio::main]的原因。注释还提示spawn_with_recv只是喂数据的一种方式——all you need is are_log_channel::LogReceiver你完全可以用其他来源构造接收端。5. eframe 原生选项let mut native_options re_viewer::native::eframe_options(None); native_options.viewport native_options .viewport .with_app_id(rerun_extend_viewer_ui_example);eframe_options 返回预配置好的eframe::NativeOptions其内部使用re_ui::viewport_with_window_chrome设置窗口 chrome指定应用图标默认窗口内尺寸1600x1200。示例在这里改写了App ID。这个 ID 很重要它决定 eframe 把应用状态持久化到磁盘的哪个位置Rerun 自带的 ID 是rerun。换成自己的 ID可避免与官方 Viewer 的状态文件相互污染。参数None对应force_wgpu_backend可传webgl/webgpu等强制后端。6. 启动选项与应用环境let startup_options re_viewer::StartupOptions::default(); let app_env re_viewer::AppEnvironment::Custom(My Wrapper.to_owned());StartupOptions 是「启动时设置一次、不参与序列化」的配置集合字段包括persist_state是否持久化状态、is_in_notebook、hide_welcome_screen、expect_data_soon、force_wgpu_backend、视频解码硬件加速选项等。桌面端还有screenshot_to_path_then_quit、resolution_in_points等调试用字段AppEnvironment::Custom(...)是供遥测/分析使用的身份标记查看 AppEnvironment 的name()Custom(_)会归为custom从而与官方python_sdk、rust_sdk、rerun_cli、web_viewer等区分开。不开启analytics特性时它基本无副作用但保留它能让你的包装器在 Rerun 的统计口径中具备独立身份。7. 创建并嵌入 re_viewer::Appeframe::run_native( window_title, // My Customized Viewer native_options, Box::new(move |cc| { re_viewer::customize_eframe_and_setup_renderer(cc)?; let mut rerun_app re_viewer::App::new( main_thread_token, re_viewer::build_info(), app_env, startup_options, cc, None, // connection_registry re_viewer::AsyncRuntimeHandle::from_current_tokio_runtime_or_wasmbindgen()?, ); rerun_app.add_log_receiver(rx); Ok(Box::new(MyApp { rerun_app })) }), )?;这段是整篇示例的枢纽逐点说明customize_eframe_and_setup_renderer(cc)实现在 lib.rs完成两件关键事情从cc.wgpu_render_state取出适配器/设备/队列创建re_renderer::RenderContext并放入 egui 的 paint callback resources——这样 Rerun 的 3D 绘制回调就能访问渲染器调用re_ui::apply_style_and_install_loaders(cc.egui_ctx)应用 Rerun 官方主题样式并安装字体/纹理加载器。不调用它Viewer 的渲染与观感都不会正常。它必须在App::new之前调用。App::new签名app/mod.rs接收主线程令牌、构建信息、应用环境、启动选项、eframe 创建上下文、连接注册表传None时内部会创建默认ConnectionRegistry以及异步运行时句柄。此外还有进阶入口App::with_commands同文件 L224可额外注入text_log_rx与命令通道实现更深度的控制。add_log_receiver(rx)add_data_source.rs把上文的 gRPC 接收端挂进 Viewer它会给接收端设置一个 waker每当有消息到达时request_repaint_after(10ms)——即“花几毫秒解码新消息然后触发重绘”保证数据持续流动时 UI 能及时刷新。AsyncRuntimeHandle::from_current_tokio_runtime_or_wasmbindgen()复用当前 tokio 运行时#[tokio::main]提供的那个WebAssembly 目标则回退到 wasm-bindgen 机制。自定义 eframe::App你的界面与 Rerun 的分工MyApp实现了eframe::Apptrait三个方法各司其职save状态持久化fn save(mut self, storage: mut dyn eframe::Storage) { self.rerun_app.save(storage); }直接透传给rerun_app。底层 App::save 会写入版本号、序列化应用状态与凭据、保存 blueprint若blueprint_gc开启还会先做 GC但仅当startup_options.persist_state为真时才生效。ui绘制每一帧fn ui(mut self, ui: mut egui::Ui, frame: mut eframe::Frame) { // 先放自己的面板 egui::Panel::right(my_side_panel) .default_size(200.0) .show(ui, |ui| { self.ui(ui); }); // 剩余空间全部交给 Rerun Viewer self.rerun_app.ui(ui, frame); }这是整个嵌入模式的核心规律先绘制你自己的面板再把剩余空间交给rerun_app.ui。egui 的面板系统Side/Top/Bottom 等天然支持这种布局——Panel::right占据右侧 200 逻辑像素其余全部渲染 Rerun 的 3D 视图与时间轴等。logic低频逻辑更新fn logic(mut self, ctx: egui::Context, frame: mut eframe::Frame) { self.rerun_app.logic(ctx, frame); }logic在需要重绘时被调用可能高达 60Hz用于处理与渲染无关的应用逻辑同样透传给rerun_app其实现见 app/mod.rs。在侧边面板中实时读取数据EntityDb 查询实战自定义面板MyApp::ui演示了如何不借助 Rerun 自带 UI直接从数据存储中取数展示——这是把 Rerun 变成“可编程可视化内核”的关键能力。层级 1应用 ID 与实体列表fn entity_db_ui(ui: mut egui::Ui, entity_db: re_entity_db::EntityDb) { if let Some(store_info) entity_db.store_info() { ui.label(format!(Application ID: {}, store_info.application_id())); } let timeline re_log_types::TimelineName::log_time(); // ... for entity_path in entity_db.sorted_entity_paths() { ui.collapsing(entity_path.to_string(), |ui| { entity_ui(ui, entity_db, timeline, entity_path); }); } }通过self.rerun_app.recording_db()app/mod.rs拿到当前录音对应的EntityDb引用尚无数据时为None界面会显示 No log database loaded yet.时间线取log_timere_log_types::TimelineName::log_time()——它永远存在是示例的稳妥选择sorted_entity_paths()提供按路径排序的实体列表用 egui 的collapsing折叠 UI 逐层展开。层级 2实体 → 组件if let Some(components) entity_db .storage_engine() .store() .all_components_on_timeline_sorted(timeline, entity_path) { for component in components { /* 折叠展示每个组件 */ } }每个实体可挂载多个组件位置、颜色、半径……all_components_on_timeline_sorted返回该时间线上按名排序的组件集合。层级 3组件 → 数值LatestAt 查询let query re_chunk_store::LatestAtQuery::latest(timeline); let results entity_db.storage_engine().cache().latest_at( re_chunk_store::ChunkTrackingMode::Report, query, entity_path, [component], ); if let Some(data) results.component_batch_raw(component) { // 遍历所有实例例如点云中的每个点…… }LatestAtQuery::latest表示「任意时刻都取最近一次记录的值」是演示最简的选择源码注释也指出可以查询任意时间点查询走storage_engine().cache()即缓存加速的读取路径适合 UI 每帧刷新component_batch_raw返回原始 Arrow 批量数据data.len()是实例数data.slice(i, 1)取出第 i 个实例。层级 4把 Arrow 值格式化为可读文本fn format_arrow(array: dyn arrow::array::Array) - String { use arrow::util::display::{ArrayFormatter, FormatOptions}; let num_bytes array.get_buffer_memory_size(); if array.len() 1 num_bytes 256 { // 小数据直接格式化输出 // ... return formatter.value(0).to_string(); } // 兜底显示字节数避免打印海量数据 format!({num_bytes} bytes) }这是非常实用的防爆 UI 策略单实例且小于 256 字节的标量直接转成字符串点云、网格这类大批量数据只显示占用的字节数防止把界面刷爆。至此你的自定义面板可以做到数据一进来右侧列表实时列出实体 → 组件 → 数值而左侧是完整的 Rerun 交互式视图。局限与注意事项Web 支持受限README 引用了 Rerun issue #2337 的说明——要基于这些自定义能力生成 Web Viewer你必须自行构建对应版本的 Web Viewer 构建产物在 Rerun 仓库之外目前不提供开箱即用的支持。也就是说extend_viewer_ui面向原生桌面场景。主线程约束eframe::run_native、文件对话框等只能出现在主线程MainThreadToken就是为此存在的编译期证明务必在进入任何异步上下文前先创建它。端口冲突默认监听0.0.0.0:9876这是 Rerun 官方 SDK 连接的默认端口若本机已有 Rerun Viewer 占用该端口需要修改地址。feature 面default-features false时需显式带上native_viewer、sdk、server、log_setup缺一不可——它们分别对应渲染、日志消息处理、gRPC 服务和日志初始化。可继续深入的方向示例刻意保持最小仓库中还有同类的进阶参考viewer_callbacks在保留官方 Viewer 的同时挂接回调监听App生命周期事件custom_view 与 custom_visualizer进一步自定义视图与可视化器custom_store_subscriber自定义数据存储订阅者可拦截/变换流入的数据。如果你需要把数据源从 gRPC 换成别的通道记住关键原则re_viewer::App只需要一个re_log_channel::LogReceiver——文件回放、自定义 importer、直接构造LogMsg流都可以殊途同归地接入add_log_receiver。小结extend_viewer_ui完整展示了把 Rerun Viewer 从“独立程序”变成“可嵌入组件”的四步套路用AccountingAllocator mimalloc管理内存在#[tokio::main]中创建MainThreadToken用spawn_with_recv内嵌 SDK 服务器获得内存级LogReceiver调customize_eframe_and_setup_renderer初始化渲染环境用App::newadd_log_receiver构建 Rerun 应用在自定义eframe::App的ui里先画自己的面板再让rerun_app.ui占满剩余空间并用EntityDb查询 API 实时展示数据。掌握了这条链路你就可以基于本仓库的 main.rs 模板把 Rerun 的实时可视化能力无缝织入你自己的桌面工具链。【免费下载链接】rerunVisualize, query, and stream to train on multimodal robotics data.项目地址: https://gitcode.com/GitHub_Trending/re/rerun创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考