Skip to content

Instantly share code, notes, and snippets.

@matthewjberger
Last active June 11, 2025 16:16
Show Gist options
  • Save matthewjberger/09f1a715cb251b004592a7dd33ba57a0 to your computer and use it in GitHub Desktop.
Save matthewjberger/09f1a715cb251b004592a7dd33ba57a0 to your computer and use it in GitHub Desktop.
rerun custom (remember to run cargo update chrono --precise 0.4.39)
[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 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