use std::collections::BTreeMap;
use rerun::{
external::{anyhow, re_build_info, re_chunk_store, re_log, re_log_types::ResolvedTimeRange},
time::TimeInt,
ChunkStoreEvent, ChunkStoreSubscriber, ComponentName, EntityPath, StoreId, Timeline,
};
fn main() -> anyhow::Result<std::process::ExitCode> {
let main_thread_token = rerun::MainThreadToken::i_promise_i_am_on_the_main_thread();
re_log::setup_logging();
let _handle = re_chunk_store::ChunkStore::register_subscriber(Box::<Orchestrator>::default());
let build_info = re_build_info::build_info!();
rerun::run(
main_thread_token,
build_info,
rerun::CallSource::Cli,
std::env::args(),
)
.map(std::process::ExitCode::from)
}
#[derive(Default)]
struct Orchestrator {
components_per_recording: ComponentsPerRecording,
time_ranges_per_entity: TimeRangesPerEntity,
}
impl ChunkStoreSubscriber for Orchestrator {
fn name(&self) -> String {
"rerun.store_subscriber.ScreenClearer".into()
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
self
}
fn on_events(&mut self, events: &[ChunkStoreEvent]) {
print!("\x1B[2J\x1B[1;1H"); self.components_per_recording.on_events(events);
self.time_ranges_per_entity.on_events(events);
}
}
#[derive(Default, Debug, PartialEq, Eq)]
struct ComponentsPerRecording {
counters: BTreeMap<StoreId, BTreeMap<ComponentName, u64>>,
}
impl ChunkStoreSubscriber for ComponentsPerRecording {
fn name(&self) -> String {
"rerun.store_subscriber.ComponentsPerRecording".into()
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
self
}
fn on_events(&mut self, events: &[ChunkStoreEvent]) {
for event in events {
let per_component = self.counters.entry(event.store_id.clone()).or_default();
for component_name in event.chunk.component_names() {
let count = per_component.entry(component_name).or_default();
if event.delta() > 0 && *count == 0 {
println!(
"New component introduced in recording {}: {}!",
event.store_id, component_name,
);
}
else if event.delta() < 0 && *count <= event.delta().unsigned_abs() {
println!(
"Component retired from recording {}: {}!",
event.store_id, component_name,
);
}
*count = count.saturating_add_signed(event.delta());
}
}
if self.counters.is_empty() {
return;
}
println!("Component stats");
println!("---------------");
for (recording, per_component) in &self.counters {
println!(" Recording '{recording}':"); for (component, counter) in per_component {
println!(" {component}: {counter} occurrences");
}
}
}
}
#[derive(Default, Debug, PartialEq, Eq)]
struct TimeRangesPerEntity {
times: BTreeMap<EntityPath, BTreeMap<Timeline, BTreeMap<TimeInt, u64>>>,
}
impl ChunkStoreSubscriber for TimeRangesPerEntity {
fn name(&self) -> String {
"rerun.store_subscriber.TimeRangesPerEntity".into()
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
self
}
fn on_events(&mut self, events: &[ChunkStoreEvent]) {
for event in events {
for (timeline, time_column) in event.chunk.timelines() {
for time in time_column.times() {
let per_timeline = self
.times
.entry(event.chunk.entity_path().clone())
.or_default();
let per_time = per_timeline.entry(*timeline).or_default();
let count = per_time.entry(time).or_default();
*count = count.saturating_add_signed(event.delta());
if *count == 0 {
per_time.remove(&time);
}
}
}
}
if self.times.is_empty() {
return;
}
println!("Entity time ranges");
println!("------------------");
for (entity_path, per_timeline) in &self.times {
println!(" {entity_path}:");
for (timeline, times) in per_timeline {
let time_range = ResolvedTimeRange::new(
times
.first_key_value()
.map_or(TimeInt::MIN, |(time, _)| *time),
times
.last_key_value()
.map_or(TimeInt::MAX, |(time, _)| *time),
);
let time_range = timeline.format_time_range_utc(&time_range);
println!(" {time_range}");
}
}
}
}