Skip to content

Commit

Permalink
Show currently loading recordings in Recordings menu (#3307)
Browse files Browse the repository at this point in the history
### What
* Closes #3192

Shows currently loading receivers in the recordings panel until they
actually receive some data.

This is a blink-and-you'll-miss-it sort of thing (especially with a good
internet connection), but take a look at the top-left of the screen:


![loading-msg](https://github.com/rerun-io/rerun/assets/1148717/20e133ed-8164-4c73-9057-82ba7bfd5d8e)


### 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)
* [x] I have tested [demo.rerun.io](https://demo.rerun.io/pr/3307) (if
applicable)

- [PR Build Summary](https://build.rerun.io/pr/3307)
- [Docs
preview](https://rerun.io/preview/44f7f8d87b7f656725e3eb7698eee8cd3947ecfb/docs)
<!--DOCS-PREVIEW-->
- [Examples
preview](https://rerun.io/preview/44f7f8d87b7f656725e3eb7698eee8cd3947ecfb/examples)
<!--EXAMPLES-PREVIEW-->
- [Recent benchmark results](https://ref.rerun.io/dev/bench/)
- [Wasm size tracking](https://ref.rerun.io/dev/sizes/)

---------

Co-authored-by: Andreas Reich <[email protected]>
  • Loading branch information
emilk and Wumpf authored Sep 13, 2023
1 parent d38a342 commit 43124fd
Show file tree
Hide file tree
Showing 9 changed files with 167 additions and 20 deletions.
2 changes: 1 addition & 1 deletion crates/re_renderer/examples/framework.rs
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,7 @@ impl<E: Example + 'static> Application<E> {
return;
}
Err(err) => {
re_log::warn!(%err, "dropped frame");
re_log::warn!("Dropped frame: {err}");
return;
}
};
Expand Down
5 changes: 5 additions & 0 deletions crates/re_smart_channel/src/receive_set.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ impl<T: Send> ReceiveSet<T> {
rx.push(r);
}

/// Disconnect from any channel with the given source.
pub fn remove(&self, source: &SmartChannelSource) {
self.receivers.lock().retain(|r| r.source() != source);
}

/// List of connected receiver sources.
///
/// This gets culled after calling one of the `recv` methods.
Expand Down
9 changes: 5 additions & 4 deletions crates/re_ui/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -702,12 +702,13 @@ impl ReUi {
}

/// Two-column grid to be used in selection view.
///
/// Use this when you expect the right column to have multi-line entries.
#[allow(clippy::unused_self)]
pub fn selection_grid(&self, ui: &mut egui::Ui, id: &str) -> egui::Grid {
pub fn selection_grid(&self, _ui: &mut egui::Ui, id: &str) -> egui::Grid {
// Spread rows a bit to make it easier to see the groupings
egui::Grid::new(id)
.num_columns(2)
.spacing(ui.style().spacing.item_spacing + egui::vec2(0.0, 8.0))
let spacing = egui::vec2(8.0, 16.0);
egui::Grid::new(id).num_columns(2).spacing(spacing)
}

/// Draws a shadow into the given rect with the shadow direction given from dark to light
Expand Down
4 changes: 2 additions & 2 deletions crates/re_viewer/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -712,9 +712,9 @@ impl App {
re_smart_channel::SmartMessagePayload::Msg(msg) => msg,
re_smart_channel::SmartMessagePayload::Quit(err) => {
if let Some(err) = err {
re_log::warn!(%msg.source, err, "data source has left unexpectedly");
re_log::warn!("Data source {} has left unexpectedly: {err}", msg.source);
} else {
re_log::debug!(%msg.source, "data source has left");
re_log::debug!("Data source {} has left", msg.source);
}
continue;
}
Expand Down
2 changes: 1 addition & 1 deletion crates/re_viewer/src/app_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ impl AppState {
// before drawing the blueprint panel.
ui.spacing_mut().item_spacing.y = 0.0;

let recording_shown = recordings_panel_ui(&mut ctx, ui);
let recording_shown = recordings_panel_ui(&mut ctx, rx, ui);

if recording_shown {
ui.add_space(4.0);
Expand Down
2 changes: 1 addition & 1 deletion crates/re_viewer/src/store_hub.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ impl StoreHub {
StoreContext {
blueprint,
recording,
alternate_recordings: self.store_dbs.recordings().collect_vec(),
all_recordings: self.store_dbs.recordings().collect_vec(),
}
})
}
Expand Down
157 changes: 149 additions & 8 deletions crates/re_viewer/src/ui/recordings_panel.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,24 @@
use re_viewer_context::{CommandSender, SystemCommand, SystemCommandSender, ViewerContext};
use std::collections::BTreeMap;

use time::macros::format_description;

use re_log_types::LogMsg;
use re_smart_channel::{ReceiveSet, SmartChannelSource};
use re_viewer_context::{CommandSender, SystemCommand, SystemCommandSender, ViewerContext};

static TIME_FORMAT_DESCRIPTION: once_cell::sync::Lazy<
&'static [time::format_description::FormatItem<'static>],
> = once_cell::sync::Lazy::new(|| format_description!(version = 2, "[hour]:[minute]:[second]Z"));

/// Show the currently open Recordings in a selectable list.
/// Also shows the currently loading receivers.
///
/// Returns `true` if any recordings were shown.
pub fn recordings_panel_ui(ctx: &mut ViewerContext<'_>, ui: &mut egui::Ui) -> bool {
pub fn recordings_panel_ui(
ctx: &mut ViewerContext<'_>,
rx: &ReceiveSet<LogMsg>,
ui: &mut egui::Ui,
) -> bool {
ctx.re_ui.panel_content(ui, |re_ui, ui| {
re_ui.panel_title_bar_with_buttons(
ui,
Expand All @@ -26,16 +35,87 @@ pub fn recordings_panel_ui(ctx: &mut ViewerContext<'_>, ui: &mut egui::Ui) -> bo
.auto_shrink([false, true])
.max_height(300.)
.show(ui, |ui| {
ctx.re_ui
.panel_content(ui, |_re_ui, ui| recording_list_ui(ctx, ui))
ctx.re_ui.panel_content(ui, |_re_ui, ui| {
let mut any_shown = false;
any_shown |= recording_list_ui(ctx, ui);

// Show currently loading things after.
// They will likely end up here as recordings soon.
any_shown |= loading_receivers_ui(ctx, rx, ui);

any_shown
})
})
.inner
}

fn loading_receivers_ui(
ctx: &mut ViewerContext<'_>,
rx: &ReceiveSet<LogMsg>,
ui: &mut egui::Ui,
) -> bool {
let sources_with_stores: ahash::HashSet<SmartChannelSource> = ctx
.store_context
.all_recordings
.iter()
.filter_map(|store| store.data_source.clone())
.collect();

let mut any_shown = false;

for source in rx.sources() {
let (always_show, string) = match source.as_ref() {
SmartChannelSource::File(path) => (false, format!("Loading {}…", path.display())),

SmartChannelSource::RrdHttpStream { url } => (false, format!("Loading {url}…")),

SmartChannelSource::RrdWebEventListener => {
(false, "Waiting on Web Event Listener…".to_owned())
}

SmartChannelSource::Sdk => (false, "Waiting on SDK…".to_owned()),

SmartChannelSource::WsClient { ws_server_url } => {
(false, format!("Loading from {ws_server_url}…"))
}

SmartChannelSource::TcpServer { port } => {
// We have a TcpServer when running just `cargo rerun`
(true, format!("Hosting a TCP Server on port {port}"))
}
};

// Only show if we don't have a recording for this source,
// i.e. if this source hasn't sent anything yet.
// Note that usually there is a one-to-one mapping between a source and a recording,
// but it is possible to send multiple recordings over the same channel.
if always_show || !sources_with_stores.contains(&source) {
any_shown = true;
let response = ctx
.re_ui
.list_item(string)
.with_buttons(|re_ui, ui| {
let resp = re_ui
.small_icon_button(ui, &re_ui::icons::REMOVE)
.on_hover_text("Disconnect from this source");
if resp.clicked() {
rx.remove(&source);
}
resp
})
.show(ui);
if let SmartChannelSource::TcpServer { .. } = source.as_ref() {
response.on_hover_text("You can connect to this viewer from a Rerun SDK");
}
}
}

any_shown
}

/// Draw the recording list.
///
/// Returns `true` if any recordings were shown.
#[allow(clippy::blocks_in_if_conditions)]
fn recording_list_ui(ctx: &mut ViewerContext<'_>, ui: &mut egui::Ui) -> bool {
let ViewerContext {
store_context,
Expand All @@ -44,7 +124,7 @@ fn recording_list_ui(ctx: &mut ViewerContext<'_>, ui: &mut egui::Ui) -> bool {
} = ctx;

let mut store_dbs_map: BTreeMap<_, Vec<_>> = BTreeMap::new();
for store_db in &store_context.alternate_recordings {
for store_db in &store_context.all_recordings {
let key = store_db
.store_info()
.map_or("<unknown>", |info| info.application_id.as_str());
Expand Down Expand Up @@ -133,7 +213,7 @@ fn recording_ui(
})
.unwrap_or("<unknown time>".to_owned());

re_ui
let response = re_ui
.list_item(format!("{prefix}{name}"))
.with_buttons(|re_ui, ui| {
let resp = re_ui
Expand All @@ -155,7 +235,68 @@ fn recording_ui(
ui.painter()
.circle(rect.center(), 4.0, color, egui::Stroke::NONE);
})
.show(ui)
.show(ui);

response.on_hover_ui(|ui| {
recording_hover_ui(re_ui, ui, store_db);
})
}

fn recording_hover_ui(re_ui: &re_ui::ReUi, ui: &mut egui::Ui, store_db: &re_data_store::StoreDb) {
egui::Grid::new("recording_hover_ui")
.num_columns(2)
.show(ui, |ui| {
re_ui.grid_left_hand_label(ui, "Store ID");
ui.label(store_db.store_id().to_string());
ui.end_row();

if let Some(data_source) = &store_db.data_source {
re_ui.grid_left_hand_label(ui, "Data source");
ui.label(data_source_string(data_source));
ui.end_row();
}

if let Some(set_store_info) = store_db.recording_msg() {
let re_log_types::StoreInfo {
application_id,
store_id: _,
is_official_example: _,
started,
store_source,
store_kind,
} = &set_store_info.info;

re_ui.grid_left_hand_label(ui, "Application ID");
ui.label(application_id.to_string());
ui.end_row();

re_ui.grid_left_hand_label(ui, "Recording started");
ui.label(started.format());
ui.end_row();

re_ui.grid_left_hand_label(ui, "Source");
ui.label(store_source.to_string());
ui.end_row();

// We are in the recordings menu, we know the kind
if false {
re_ui.grid_left_hand_label(ui, "Kind");
ui.label(store_kind.to_string());
ui.end_row();
}
}
});
}

fn data_source_string(data_source: &re_smart_channel::SmartChannelSource) -> String {
match data_source {
SmartChannelSource::File(path) => path.display().to_string(),
SmartChannelSource::RrdHttpStream { url } => url.clone(),
SmartChannelSource::RrdWebEventListener => "Web Event Listener".to_owned(),
SmartChannelSource::Sdk => "SDK".to_owned(),
SmartChannelSource::WsClient { ws_server_url } => ws_server_url.clone(),
SmartChannelSource::TcpServer { port } => format!("TCP Server, port {port}"),
}
}

fn add_button_ui(ctx: &mut ViewerContext<'_>, ui: &mut egui::Ui) {
Expand Down
2 changes: 1 addition & 1 deletion crates/re_viewer_context/src/store_context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ use re_data_store::StoreDb;
pub struct StoreContext<'a> {
pub blueprint: &'a StoreDb,
pub recording: Option<&'a StoreDb>,
pub alternate_recordings: Vec<&'a StoreDb>,
pub all_recordings: Vec<&'a StoreDb>,
}
4 changes: 2 additions & 2 deletions crates/re_ws_comms/src/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -165,9 +165,9 @@ fn to_broadcast_stream(
}
re_smart_channel::SmartMessagePayload::Quit(err) => {
if let Some(err) = err {
re_log::warn!(%msg.source, err, "sender has left unexpectedly");
re_log::warn!("Sender {} has left unexpectedly: {err}", msg.source);
} else {
re_log::debug!(%msg.source, "sender has left");
re_log::debug!("Sender {} has left", msg.source);
}
}
}
Expand Down

0 comments on commit 43124fd

Please sign in to comment.