Last active
June 11, 2025 16:16
-
-
Save matthewjberger/09f1a715cb251b004592a7dd33ba57a0 to your computer and use it in GitHub Desktop.
rerun custom (remember to run cargo update chrono --precise 0.4.39)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[package] | |
name = "scout" | |
version = "0.1.0" | |
edition = "2024" | |
[dependencies] | |
mimalloc = "0.1.43" | |
re_crash_handler = { version = "0.22.1", features = ["analytics"] } | |
re_grpc_server = "0.22.1" | |
re_sdk_comms = { version = "0.22.1", features = ["server"] } | |
re_viewer = { version = "0.22.1", features = ["analytics"] } | |
tokio = { version = "1.14.0", features = [ | |
"macros", | |
"rt-multi-thread", | |
"time", | |
"net", | |
"io-util", | |
"sync", | |
"signal", | |
] } | |
rerun = "0.22.1" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//! This example shows how to wrap the Rerun Viewer in your own GUI. | |
use re_viewer::external::{ | |
arrow, eframe, egui, re_chunk_store, re_entity_db, re_log, re_log_types, re_memory, | |
re_renderer, | |
re_types::{self, ComponentDescriptor}, | |
re_ui::Help, | |
re_viewer_context::{ | |
self, IdentifiedViewSystem, ViewClass, ViewContext, ViewContextCollection, ViewQuery, | |
ViewSpawnHeuristics, ViewState, ViewStateExt, ViewSystemExecutionError, | |
ViewSystemIdentifier, VisualizerQueryInfo, VisualizerSystem, | |
}, | |
}; | |
// By using `re_memory::AccountingAllocator` Rerun can keep track of exactly how much memory it is using, | |
// and prune the data store when it goes above a certain limit. | |
// By using `mimalloc` we get faster allocations. | |
#[global_allocator] | |
static GLOBAL: re_memory::AccountingAllocator<mimalloc::MiMalloc> = | |
re_memory::AccountingAllocator::new(mimalloc::MiMalloc); | |
fn main() -> Result<(), Box<dyn std::error::Error>> { | |
let main_thread_token = re_viewer::MainThreadToken::i_promise_i_am_on_the_main_thread(); | |
// Direct calls using the `log` crate to stderr. Control with `RUST_LOG=debug` etc. | |
re_log::setup_logging(); | |
// Install handlers for panics and crashes that prints to stderr and send | |
// them to Rerun analytics (if the `analytics` feature is on in `Cargo.toml`). | |
re_crash_handler::install_crash_handlers(re_viewer::build_info()); | |
// Listen for TCP connections from Rerun's logging SDKs. | |
// There are other ways of "feeding" the viewer though - all you need is a `re_smart_channel::Receiver`. | |
let rx = re_sdk_comms::serve( | |
"0.0.0.0", | |
re_sdk_comms::DEFAULT_SERVER_PORT, | |
Default::default(), | |
)?; | |
let mut native_options = re_viewer::native::eframe_options(None); | |
native_options.viewport = native_options | |
.viewport | |
.with_app_id("rerun_extend_viewer_ui_example"); | |
let startup_options = re_viewer::StartupOptions { | |
hide_welcome_screen: true, | |
persist_state: false, | |
..Default::default() | |
}; | |
// This is used for analytics, if the `analytics` feature is on in `Cargo.toml` | |
let app_env = re_viewer::AppEnvironment::Custom("My Wrapper".to_owned()); | |
let window_title = "My Customized Viewer"; | |
eframe::run_native( | |
window_title, | |
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, | |
); | |
rerun_app.add_receiver(rx); | |
rerun_app.add_view_class::<SystemIpc>().unwrap(); | |
let stream = rerun::RecordingStreamBuilder::new("streamer").spawn()?; | |
Ok(Box::new(MyApp { rerun_app, stream })) | |
}), | |
)?; | |
Ok(()) | |
} | |
struct MyApp { | |
rerun_app: re_viewer::App, | |
stream: rerun::RecordingStream, | |
} | |
impl eframe::App for MyApp { | |
fn save(&mut self, storage: &mut dyn eframe::Storage) { | |
// Store viewer state on disk | |
self.rerun_app.save(storage); | |
} | |
/// Called whenever we need repainting, which could be 60 Hz. | |
fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) { | |
egui::TopBottomPanel::top("top_panel").show(ctx, |ui| { | |
ui.label("Top panel"); | |
}); | |
egui::SidePanel::right("my_side_panel") | |
.default_width(200.0) | |
.show(ctx, |ui| { | |
self.ui(ui); | |
}); | |
self.rerun_app.update(ctx, frame); | |
} | |
} | |
impl MyApp { | |
fn ui(&mut self, ui: &mut egui::Ui) { | |
ui.add_space(4.0); | |
ui.vertical_centered(|ui| { | |
ui.strong("Custom Right Panel"); | |
if ui.button("Connect").clicked() {} | |
}); | |
ui.separator(); | |
if let Some(entity_db) = self.rerun_app.recording_db() { | |
entity_db_ui(ui, entity_db); | |
} else { | |
ui.label("No log database loaded yet."); | |
} | |
} | |
} | |
/// Show the content of the log database. | |
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)); | |
} | |
// There can be many timelines, but the `log_time` timeline is always there: | |
let timeline = re_log_types::Timeline::log_time(); | |
ui.separator(); | |
ui.strong("Entities:"); | |
egui::ScrollArea::vertical() | |
.auto_shrink([false, true]) | |
.show(ui, |ui| { | |
for entity_path in entity_db.entity_paths() { | |
ui.collapsing(entity_path.to_string(), |ui| { | |
entity_ui(ui, entity_db, timeline, entity_path); | |
}); | |
} | |
}); | |
} | |
fn entity_ui( | |
ui: &mut egui::Ui, | |
entity_db: &re_entity_db::EntityDb, | |
timeline: re_log_types::Timeline, | |
entity_path: &re_log_types::EntityPath, | |
) { | |
// Each entity can have many components (e.g. position, color, radius, …): | |
if let Some(components) = entity_db | |
.storage_engine() | |
.store() | |
.all_components_on_timeline_sorted(&timeline, entity_path) | |
{ | |
for component in components { | |
ui.collapsing(component.to_string(), |ui| { | |
component_ui(ui, entity_db, timeline, entity_path, component); | |
}); | |
} | |
} | |
} | |
fn component_ui( | |
ui: &mut egui::Ui, | |
entity_db: &re_entity_db::EntityDb, | |
timeline: re_log_types::Timeline, | |
entity_path: &re_log_types::EntityPath, | |
component_name: re_types::ComponentName, | |
) { | |
// You can query the data for any time point, but for now | |
// just show the last value logged for each component: | |
let query = re_chunk_store::LatestAtQuery::latest(timeline); | |
let results = | |
entity_db | |
.storage_engine() | |
.cache() | |
.latest_at(&query, entity_path, [component_name]); | |
if let Some(data) = results.component_batch_raw(&component_name) { | |
egui::ScrollArea::vertical() | |
.auto_shrink([false, true]) | |
.show(ui, |ui| { | |
// Iterate over all the instances (e.g. all the points in the point cloud): | |
let num_instances = data.len(); | |
for i in 0..num_instances { | |
ui.label(format_arrow(&*data.slice(i, 1))); | |
} | |
}); | |
}; | |
} | |
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 { | |
// Print small items: | |
let options = FormatOptions::default(); | |
if let Ok(formatter) = ArrayFormatter::try_new(array, &options) { | |
return formatter.value(0).to_string(); | |
} | |
} | |
// Fallback: | |
format!("{num_bytes} bytes") | |
} | |
/// A custom view class for rerun | |
#[derive(Default)] | |
pub struct SystemIpc; | |
impl ViewClass for SystemIpc { | |
fn identifier() -> re_types::ViewClassIdentifier | |
where | |
Self: Sized, | |
{ | |
"System IPC".into() | |
} | |
fn display_name(&self) -> &'static str { | |
"System IPC" | |
} | |
fn help(&self, egui_ctx: &egui::Context) -> re_viewer::external::re_ui::Help<'_> { | |
Help::new("System IPC").markdown("This is a custom view class for System IPC.") | |
} | |
fn on_register( | |
&self, | |
system_registry: &mut re_viewer::external::re_viewer_context::ViewSystemRegistrator<'_>, | |
) -> Result<(), re_viewer::external::re_viewer_context::ViewClassRegistryError> { | |
system_registry.register_visualizer::<SystemIpcSystem>() | |
} | |
fn new_state(&self) -> Box<dyn re_viewer::external::re_viewer_context::ViewState> { | |
Box::<SystemIpcViewState>::default() | |
} | |
fn layout_priority(&self) -> re_viewer::external::re_viewer_context::ViewClassLayoutPriority { | |
Default::default() | |
} | |
fn spawn_heuristics( | |
&self, | |
ctx: &re_viewer::external::re_viewer_context::ViewerContext<'_>, | |
) -> re_viewer::external::re_viewer_context::ViewSpawnHeuristics { | |
ViewSpawnHeuristics::root() | |
} | |
fn ui( | |
&self, | |
ctx: &re_viewer::external::re_viewer_context::ViewerContext<'_>, | |
ui: &mut egui::Ui, | |
state: &mut dyn re_viewer::external::re_viewer_context::ViewState, | |
query: &re_viewer::external::re_viewer_context::ViewQuery<'_>, | |
system_output: re_viewer::external::re_viewer_context::SystemExecutionOutput, | |
) -> Result<(), re_viewer::external::re_viewer_context::ViewSystemExecutionError> { | |
// let system_outputs = system_output.view_systems.get::<SystemIpcSystem>()?; | |
let state = state.downcast_mut::<SystemIpcViewState>()?; | |
ui.label("System IPC Connection"); | |
if ui.button("Connect").clicked() { | |
let _ = state.ipc_client.connect("127.0.0.1:9000"); | |
} | |
Ok(()) | |
} | |
} | |
/// Our System IPC view's contents | |
#[derive(Default)] | |
pub struct SystemIpcSystem { | |
pub value: u8, | |
} | |
impl IdentifiedViewSystem for SystemIpcSystem { | |
fn identifier() -> ViewSystemIdentifier { | |
"InstanceSystemIpc".into() | |
} | |
} | |
impl VisualizerSystem for SystemIpcSystem { | |
fn visualizer_query_info(&self) -> VisualizerQueryInfo { | |
VisualizerQueryInfo::from_archetype::<SystemIpcArchetype>() | |
} | |
/// Populates the scene part with data from the store. | |
fn execute( | |
&mut self, | |
ctx: &ViewContext<'_>, | |
query: &ViewQuery<'_>, | |
_context_systems: &ViewContextCollection, | |
) -> Result<Vec<re_renderer::QueueableDrawData>, ViewSystemExecutionError> { | |
// We're not using `re_renderer` here, so return an empty vector. | |
Ok(Vec::new()) | |
} | |
fn as_any(&self) -> &dyn std::any::Any { | |
self | |
} | |
fn fallback_provider(&self) -> &dyn re_viewer_context::ComponentFallbackProvider { | |
self | |
} | |
} | |
// Implements a `ComponentFallbackProvider` trait for the `InstanceColorSystem`. | |
// It is left empty here but could be used to provides fallback values for optional components in case they're missing. | |
re_viewer_context::impl_component_fallback_provider!(SystemIpcSystem => []); | |
struct SystemIpcArchetype; | |
impl re_types::Archetype for SystemIpcArchetype { | |
type Indicator = re_types::GenericIndicatorComponent<Self>; | |
fn indicator() -> re_types::SerializedComponentBatch { | |
use re_types::ComponentBatch as _; | |
#[allow(clippy::unwrap_used)] | |
Self::Indicator::default().serialized().unwrap() | |
} | |
fn name() -> re_types::ArchetypeName { | |
"InstanceSystemIpc".into() | |
} | |
fn display_name() -> &'static str { | |
"System IPC" | |
} | |
fn required_components() -> ::std::borrow::Cow<'static, [ComponentDescriptor]> { | |
vec![].into() | |
} | |
} | |
/// View state for the custom view. | |
/// | |
/// This state is preserved between frames, but not across Viewer sessions. | |
pub struct SystemIpcViewState { | |
ipc_client: ipc::Client, | |
} | |
impl Default for SystemIpcViewState { | |
fn default() -> Self { | |
Self { | |
ipc_client: ipc::Client::new("explorer", ipc::Settings::default()), | |
} | |
} | |
} | |
impl ViewState for SystemIpcViewState { | |
fn as_any(&self) -> &dyn std::any::Any { | |
self | |
} | |
fn as_any_mut(&mut self) -> &mut dyn std::any::Any { | |
self | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment