Skip to content

Commit

Permalink
Example of how to embed the Rerun Viewer inside your own GUI (+ ergon…
Browse files Browse the repository at this point in the history
…omic improvements) (#2250)

Closes #2243

### What
Add an example showing how to wrap the Rerun Viewer in your own GUI,
using
[`eframe`](https://github.com/emilk/egui/tree/master/crates/eframe) and
[`egui`](https://github.com/emilk/egui).

This example can be useful for anyone who wants to extend the viewer
with their own controls and widgets. The example shows how to read from
the data store.

Analytics are supported but is an opt-in feature in the example
`Cargo.toml`.

I created a `re_crash_handler` crate so that the example can get useful
info on panics and signals, and have them sent to rerun if they enable
analytics.

Only works on `main` (not on 0.6.0).


![image](https://github.com/rerun-io/rerun/assets/1148717/cbbad63e-9b18-4e54-bafe-b6ffd723f63e)

## TODO
* [ ] Top-level docs at https://www.rerun.io/docs

### Checklist
* [x] I have read and agree to [Contributor
Guide](https://github.com/rerun-io/rerun/blob/main/CONTRIBUTING.md) and
the [Code of
Conduct](https://github.com/rerun-io/rerun/blob/main/CODE_OF_CONDUCT.md)
* [x] I've included a screenshot or gif (if applicable)

<!-- This line will get updated when the PR build summary job finishes.
-->
PR Build Summary: https://build.rerun.io/pr/2250

---------

Co-authored-by: Andreas Reich <[email protected]>
  • Loading branch information
emilk and Wumpf authored May 29, 2023
1 parent 00a7030 commit c138a8c
Show file tree
Hide file tree
Showing 40 changed files with 580 additions and 192 deletions.
30 changes: 25 additions & 5 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ re_arrow_store = { path = "crates/re_arrow_store", version = "0.7.0-alpha.0", de
re_build_build_info = { path = "crates/re_build_build_info", version = "0.7.0-alpha.0", default-features = false }
re_build_info = { path = "crates/re_build_info", version = "0.7.0-alpha.0", default-features = false }
re_build_web_viewer = { path = "crates/re_build_web_viewer", version = "0.7.0-alpha.0", default-features = false }
re_crash_handler = { path = "crates/re_crash_handler", version = "0.7.0-alpha.0", default-features = false }
re_data_store = { path = "crates/re_data_store", version = "0.7.0-alpha.0", default-features = false }
re_data_ui = { path = "crates/re_data_ui", version = "0.7.0-alpha.0", default-features = false }
re_error = { path = "crates/re_error", version = "0.7.0-alpha.0", default-features = false }
Expand Down
4 changes: 4 additions & 0 deletions crates/re_arrow_store/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ pub use arrow2::io::ipc::read::{StreamReader, StreamState};
#[doc(no_inline)]
pub use re_log_types::{TimeInt, TimeRange, TimeType, Timeline}; // for politeness sake

pub mod external {
pub use arrow2;
}

// ---

/// Native-only profiling macro for puffin.
Expand Down
4 changes: 2 additions & 2 deletions crates/re_arrow_store/src/store_helpers.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use re_log_types::{
ComponentName, DataCell, DataRow, DeserializableComponent, EntityPath, RowId,
SerializableComponent, TimeInt, TimePoint, Timeline,
SerializableComponent, TimePoint, Timeline,
};

use crate::{DataStore, LatestAtQuery};
Expand Down Expand Up @@ -86,7 +86,7 @@ impl DataStore {
{
crate::profile_function!();

let query = LatestAtQuery::new(Timeline::default(), TimeInt::MAX);
let query = LatestAtQuery::latest(Timeline::default());
self.query_latest_component(entity_path, &query)
}
}
Expand Down
7 changes: 7 additions & 0 deletions crates/re_arrow_store/src/store_read.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,13 @@ impl LatestAtQuery {
pub const fn new(timeline: Timeline, at: TimeInt) -> Self {
Self { timeline, at }
}

pub const fn latest(timeline: Timeline) -> Self {
Self {
timeline,
at: TimeInt::MAX,
}
}
}

/// A query over a time range, for a given timeline.
Expand Down
40 changes: 40 additions & 0 deletions crates/re_crash_handler/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
[package]
name = "re_crash_handler"
authors.workspace = true
description = "Detect panics and signals, logging them and optionally sending them to analytics."
edition.workspace = true
homepage.workspace = true
include.workspace = true
license.workspace = true
publish = true
readme = "README.md"
repository.workspace = true
rust-version.workspace = true
version.workspace = true

[package.metadata.docs.rs]
all-features = true


[features]
default = ["analytics"]

## Send analytics to Rerun on crashes
analytics = ["dep:re_analytics"]

[dependencies]
re_build_info.workspace = true

itertools.workspace = true
parking_lot.workspace = true

# Optional dependencies:
re_analytics = { workspace = true, optional = true }

# Native dependencies:
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
backtrace = "0.3"

# Native unix dependencies:
[target.'cfg(not(any(target_arch = "wasm32", target_os = "windows")))'.dependencies]
libc = "0.2"
10 changes: 10 additions & 0 deletions crates/re_crash_handler/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# re_crash_handler

Part of the [`rerun`](https://github.com/rerun-io/rerun) family of crates.

[![Latest version](https://img.shields.io/crates/v/re_crash_handler.svg)](https://crates.io/crates/re_crash_handler)
[![Documentation](https://docs.rs/re_crash_handler/badge.svg)](https://docs.rs/re_crash_handler)
![MIT](https://img.shields.io/badge/license-MIT-blue.svg)
![Apache](https://img.shields.io/badge/license-Apache-blue.svg)

Detect and handle signals, panics, and other crashes, making sure to log them and optionally send them off to analytics.
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
//! Detect and handle signals, panics, and other crashes, making sure to log them and optionally send them off to analytics.
use re_build_info::BuildInfo;

use parking_lot::Mutex;
Expand Down Expand Up @@ -180,10 +182,7 @@ fn install_signal_handler(build_info: BuildInfo) {

// Send analytics - this also sleeps a while to give the analytics time to send the event.
#[cfg(feature = "analytics")]
{
let build_info = BUILD_INFO
.lock()
.unwrap_or_else(|| re_build_info::build_info!());
if let Some(build_info) = *BUILD_INFO.lock() {
send_signal_analytics(build_info, signal_name, callstack);
}

Expand Down
6 changes: 6 additions & 0 deletions crates/re_data_store/src/log_db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,12 @@ impl Default for EntityDb {
}

impl EntityDb {
/// A sorted list of all the entity paths in this database.
pub fn entity_paths(&self) -> Vec<&EntityPath> {
use itertools::Itertools as _;
self.entity_path_from_hash.values().sorted().collect()
}

#[inline]
pub fn entity_path_from_hash(&self, entity_path_hash: &EntityPathHash) -> Option<&EntityPath> {
self.entity_path_from_hash.get(entity_path_hash)
Expand Down
5 changes: 2 additions & 3 deletions crates/re_data_store/src/util.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
use re_arrow_store::LatestAtQuery;
use re_log_types::{
DataRow, DeserializableComponent, EntityPath, RowId, SerializableComponent, TimeInt, TimePoint,
Timeline,
DataRow, DeserializableComponent, EntityPath, RowId, SerializableComponent, TimePoint, Timeline,
};

use crate::LogDb;
Expand Down Expand Up @@ -53,7 +52,7 @@ pub fn query_timeless_single<C: DeserializableComponent>(
where
for<'b> &'b C::ArrayType: IntoIterator,
{
let query = re_arrow_store::LatestAtQuery::new(Timeline::default(), TimeInt::MAX);
let query = re_arrow_store::LatestAtQuery::latest(Timeline::default());
query_latest_single(data_store, entity_path, &query)
}

Expand Down
23 changes: 6 additions & 17 deletions crates/re_data_ui/src/component.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,7 @@ impl DataUi for EntityComponentWithInstances {
) {
crate::profile_function!(self.component_name().full_name());

let mut instance_keys = match self.component_data.iter_instance_keys() {
Ok(instance_keys) => instance_keys,
Err(err) => {
ui.label(ctx.re_ui.error_text(format!("Error: {err}")));
return;
}
};
let instance_keys: Vec<_> = self.component_data.iter_instance_keys().collect();

let num_instances = self.num_instances();

Expand All @@ -52,15 +46,15 @@ impl DataUi for EntityComponentWithInstances {
if num_instances == 0 {
ui.weak("(empty)");
} else if num_instances == 1 {
if let Some(instance_key) = instance_keys.next() {
if let Some(instance_key) = instance_keys.first() {
ctx.component_ui_registry.ui(
ctx,
ui,
verbosity,
query,
&self.entity_path,
&self.component_data,
&instance_key,
instance_key,
);
} else {
ui.label(ctx.re_ui.error_text("Error: missing instance key"));
Expand Down Expand Up @@ -88,15 +82,10 @@ impl DataUi for EntityComponentWithInstances {
re_ui::ReUi::setup_table_body(&mut body);
let row_height = re_ui::ReUi::table_line_height();
body.rows(row_height, num_instances, |index, mut row| {
if let Some(instance_key) = self
.component_data
.iter_instance_keys()
.ok()
.and_then(|mut keys| keys.nth(index))
{
if let Some(instance_key) = instance_keys.get(index) {
row.col(|ui| {
let instance_path =
InstancePath::instance(self.entity_path.clone(), instance_key);
InstancePath::instance(self.entity_path.clone(), *instance_key);
item_ui::instance_path_button_to(
ctx,
ui,
Expand All @@ -113,7 +102,7 @@ impl DataUi for EntityComponentWithInstances {
query,
&self.entity_path,
&self.component_data,
&instance_key,
instance_key,
);
});
}
Expand Down
21 changes: 7 additions & 14 deletions crates/re_data_ui/src/component_path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,26 +13,19 @@ impl DataUi for ComponentPath {
) {
let store = &ctx.log_db.entity_db.data_store;

match re_query::get_component_with_instances(
if let Some((_, component_data)) = re_query::get_component_with_instances(
store,
query,
self.entity_path(),
self.component_name,
) {
Err(re_query::QueryError::PrimaryNotFound) => {
ui.label("<unset>");
}
Err(err) => {
// Any other failure to get a component is unexpected
ui.label(ctx.re_ui.error_text(format!("Error: {err}")));
}
Ok((_, component_data)) => {
super::component::EntityComponentWithInstances {
entity_path: self.entity_path.clone(),
component_data,
}
.data_ui(ctx, ui, verbosity, query);
super::component::EntityComponentWithInstances {
entity_path: self.entity_path.clone(),
component_data,
}
.data_ui(ctx, ui, verbosity, query);
} else {
ui.label("<unset>");
}
}
}
30 changes: 19 additions & 11 deletions crates/re_data_ui/src/component_ui_registry.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use re_arrow_store::LatestAtQuery;
use re_log_types::{
component_types::InstanceKey, external::arrow2, DeserializableComponent, EntityPath, SizeBytes,
component_types::InstanceKey, external::arrow2, DeserializableComponent, EntityPath,
};
use re_query::ComponentWithInstances;
use re_viewer_context::{ComponentUiRegistry, UiVerbosity, ViewerContext};
Expand Down Expand Up @@ -79,21 +79,29 @@ fn fallback_component_ui(
) {
// No special ui implementation - use a generic one:
if let Some(value) = component.lookup_arrow(instance_key) {
let bytes = value.total_size_bytes();
if bytes < 256 {
// For small items, print them
let mut repr = String::new();
let display = arrow2::array::get_display(value.as_ref(), "null");
display(&mut repr, 0).unwrap();
ui.label(repr);
} else {
ui.label(format!("{bytes} bytes"));
}
ui.label(format_arrow(&*value));
} else {
ui.weak("(null)");
}
}

fn format_arrow(value: &dyn arrow2::array::Array) -> String {
use re_log_types::SizeBytes as _;

let bytes = value.total_size_bytes();
if bytes < 256 {
// Print small items:
let mut string = String::new();
let display = arrow2::array::get_display(value, "null");
if display(&mut string, 0).is_ok() {
return string;
}
}

// Fallback:
format!("{bytes} bytes")
}

// ----------------------------------------------------------------------------

impl DataUi for re_log_types::component_types::TextEntry {
Expand Down
Loading

0 comments on commit c138a8c

Please sign in to comment.