Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Click recording://entity/path links in markdown #3442

Merged
merged 26 commits into from
Sep 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
463da76
WIP: Description markdown for SfM example
nikolausWest Sep 20, 2023
13e9d0d
Implement `EntityPath::from_str`
emilk Sep 20, 2023
69263fd
impl std::str::FromStr for Item
emilk Sep 20, 2023
27f1d99
You can now click `recording://entity/path` links in markdown
emilk Sep 20, 2023
dc719c0
Large heading in markdown
emilk Sep 20, 2023
7887e0c
Better UI message when linking to non-existing entity
emilk Sep 21, 2023
ff2f48c
Better formatting of strings in the UI
emilk Sep 21, 2023
8819c33
Code cleanup
emilk Sep 21, 2023
2de0f17
Clean up entity path parsing slightly
emilk Sep 21, 2023
8bcb93d
Remove unused Index::Pixel
emilk Sep 21, 2023
7d6a449
Limit the set of characters allowed in the "Name" part of an entity path
emilk Sep 21, 2023
4896dad
Rewrite the parsing of EntityPath
emilk Sep 21, 2023
8e4af3e
Implement from_str for ComponentPath and InstancePath
emilk Sep 21, 2023
c461a73
Support links to components
emilk Sep 21, 2023
b9bccc1
Add markdown as code-example
emilk Sep 21, 2023
7353c15
Improve lint.py
emilk Sep 21, 2023
cc2776f
Github -> GitHub
emilk Sep 21, 2023
b775d49
Remove TODO
emilk Sep 21, 2023
c74d572
Add link to issue in TODO
emilk Sep 21, 2023
1a3bbbe
Fix test
emilk Sep 21, 2023
6857e0b
Use proper entity paths
emilk Sep 21, 2023
3c55dff
Update egui_commonmark to support syntax highlighting of code blocks
emilk Sep 21, 2023
2eb6c5b
Use monospace when showing user text
emilk Sep 21, 2023
034d2a7
Add two more test cases
emilk Sep 22, 2023
d3f4a2f
Test parsing DataPath
emilk Sep 22, 2023
e14be61
Be forgiving when parsing entity paths
emilk Sep 25, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.lock

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

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ emath = { git = "https://github.com/emilk/egui", rev = "d949eaf" }
epaint = { git = "https://github.com/emilk/egui", rev = "d949eaf" }

# Temporary patch until next egui_commonmark release
egui_commonmark = { git = "https://github.com/lampsitter/egui_commonmark.git", rev = "a133564f26a95672e756079ac5583817e0cdaa1f" }
egui_commonmark = { git = "https://github.com/lampsitter/egui_commonmark.git", rev = "a4470c253b06a4a350e17418a7c6cbc413ade644" }

# Temporary patch until next egui_tiles release
egui_tiles = { git = "https://github.com/rerun-io/egui_tiles", rev = "c66d6cba7ddb5b236be614d1816be4561260274e" }
45 changes: 43 additions & 2 deletions crates/re_data_store/src/instance_path.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::hash::Hash;
use std::{hash::Hash, str::FromStr};

use re_log_types::{EntityPath, EntityPathHash, RowId};
use re_log_types::{DataPath, EntityPath, EntityPathHash, PathParseError, RowId};
use re_types::components::InstanceKey;

use crate::{store_db::EntityDb, VersionedInstancePath, VersionedInstancePathHash};
Expand All @@ -19,6 +19,13 @@ pub struct InstancePath {
pub instance_key: InstanceKey,
}

impl From<EntityPath> for InstancePath {
#[inline]
fn from(entity_path: EntityPath) -> Self {
Self::entity_splat(entity_path)
}
}

impl InstancePath {
/// Indicate the whole entity (all instances of it) - i.e. a splat.
///
Expand Down Expand Up @@ -77,6 +84,40 @@ impl std::fmt::Display for InstancePath {
}
}

impl FromStr for InstancePath {
type Err = PathParseError;

fn from_str(s: &str) -> Result<Self, Self::Err> {
let DataPath {
entity_path,
instance_key,
component_name,
} = DataPath::from_str(s)?;

if let Some(component_name) = component_name {
return Err(PathParseError::UnexpectedComponentName(component_name));
}

let instance_key = instance_key.unwrap_or(InstanceKey::SPLAT);

Ok(InstancePath {
entity_path,
instance_key,
})
}
}

#[test]
fn test_parse_instance_path() {
assert_eq!(
InstancePath::from_str("world/points[#123]"),
Ok(InstancePath {
entity_path: EntityPath::from_str("world/points").unwrap(),
instance_key: InstanceKey(123)
})
);
}

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

/// Hashes of the components of an [`InstancePath`].
Expand Down
5 changes: 5 additions & 0 deletions crates/re_data_store/src/store_db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,11 @@ impl EntityDb {
self.entity_path_from_hash.get(entity_path_hash)
}

#[inline]
pub fn knows_of_entity(&self, entity_path: &EntityPath) -> bool {
self.entity_path_from_hash.contains_key(&entity_path.hash())
}

fn register_entity_path(&mut self, entity_path: &EntityPath) {
self.entity_path_from_hash
.entry(entity_path.hash())
Expand Down
27 changes: 20 additions & 7 deletions crates/re_data_ui/src/component_path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,34 @@ impl DataUi for ComponentPath {
verbosity: UiVerbosity,
query: &re_arrow_store::LatestAtQuery,
) {
let Self {
entity_path,
component_name,
} = self;

let store = &ctx.store_db.entity_db.data_store;

if let Some((_, component_data)) = re_query::get_component_with_instances(
store,
query,
self.entity_path(),
self.component_name,
) {
if let Some((_, component_data)) =
re_query::get_component_with_instances(store, query, entity_path, *component_name)
{
super::component::EntityComponentWithInstances {
entity_path: self.entity_path.clone(),
component_data,
}
.data_ui(ctx, ui, verbosity, query);
} else if let Some(entity_tree) = ctx.store_db.entity_db.tree.subtree(entity_path) {
if entity_tree.components.contains_key(component_name) {
ui.label("<unset>");
} else {
ui.label(format!(
"Entity {entity_path:?} has no component {component_name:?}"
));
}
} else {
ui.label("<unset>");
ui.label(
ctx.re_ui
.error_text(format!("Unknown entity: {entity_path:?}")),
);
}
}
}
72 changes: 64 additions & 8 deletions crates/re_data_ui/src/component_ui_registry.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use re_arrow_store::LatestAtQuery;
use re_log_types::{external::arrow2, EntityPath};
use re_query::ComponentWithInstances;
use re_types::external::arrow2::array::Utf8Array;
use re_viewer_context::{ComponentUiRegistry, UiVerbosity, ViewerContext};

use super::EntityDataUi;
Expand Down Expand Up @@ -54,33 +55,88 @@ pub fn create_component_ui_registry() -> ComponentUiRegistry {
fn fallback_component_ui(
_ctx: &mut ViewerContext<'_>,
ui: &mut egui::Ui,
_verbosity: UiVerbosity,
verbosity: UiVerbosity,
_query: &LatestAtQuery,
_entity_path: &EntityPath,
component: &ComponentWithInstances,
instance_key: &re_types::components::InstanceKey,
) {
// No special ui implementation - use a generic one:
if let Some(value) = component.lookup_arrow(instance_key) {
ui.label(format_arrow(&*value));
arrow_ui(ui, verbosity, &*value);
} else {
ui.weak("(null)");
}
}

fn format_arrow(value: &dyn arrow2::array::Array) -> String {
fn arrow_ui(ui: &mut egui::Ui, verbosity: UiVerbosity, array: &dyn arrow2::array::Array) {
use re_log_types::SizeBytes as _;

let bytes = value.total_size_bytes();
if bytes < 256 {
// Special-treat text.
// Note: we match on the raw data here, so this works for any component containing text.
if let Some(utf8) = array.as_any().downcast_ref::<Utf8Array<i32>>() {
if utf8.len() == 1 {
let string = utf8.value(0);
text_ui(string, ui, verbosity);
return;
}
}
if let Some(utf8) = array.as_any().downcast_ref::<Utf8Array<i64>>() {
if utf8.len() == 1 {
let string = utf8.value(0);
text_ui(string, ui, verbosity);
return;
}
}

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

// Fallback:
format!("{bytes} bytes")
ui.label(format!(
"{} of {:?}",
re_format::format_bytes(num_bytes as _),
array.data_type()
));
}

fn text_ui(string: &str, ui: &mut egui::Ui, verbosity: UiVerbosity) {
let font_id = egui::TextStyle::Monospace.resolve(ui.style());
let color = ui.visuals().text_color();
let wrap_width = ui.available_width();
let mut layout_job =
egui::text::LayoutJob::simple(string.to_owned(), font_id, color, wrap_width);

let mut needs_scroll_area = false;

match verbosity {
UiVerbosity::Small => {
// Elide
layout_job.wrap.max_rows = 1;
layout_job.wrap.break_anywhere = true;
}
UiVerbosity::Reduced => {
layout_job.wrap.max_rows = 3;
}
UiVerbosity::All => {
let num_newlines = string.chars().filter(|&c| c == '\n').count();
needs_scroll_area = 10 < num_newlines || 300 < string.len();
}
}

if needs_scroll_area {
egui::ScrollArea::vertical().show(ui, |ui| {
ui.label(layout_job);
});
} else {
ui.label(layout_job);
}
}
39 changes: 26 additions & 13 deletions crates/re_data_ui/src/instance_path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,26 @@ impl DataUi for InstancePath {
verbosity: UiVerbosity,
query: &re_arrow_store::LatestAtQuery,
) {
let Self {
entity_path,
instance_key,
} = self;

let store = &ctx.store_db.entity_db.data_store;

let Some(mut components) = store.all_components(&query.timeline, &self.entity_path) else {
ui.label(format!("No components in entity {}", self.entity_path));
let Some(mut components) = store.all_components(&query.timeline, entity_path) else {
if ctx.store_db.entity_db.knows_of_entity(entity_path) {
ui.label(format!(
"No components in entity {:?} on timeline {:?}",
entity_path,
query.timeline.name()
));
} else {
ui.label(
ctx.re_ui
.error_text(format!("Unknown entity: {entity_path:?}")),
);
}
return;
};
components.sort();
Expand All @@ -29,12 +45,9 @@ impl DataUi for InstancePath {
.num_columns(2)
.show(ui, |ui| {
for component_name in components {
let Some((_, component_data)) = get_component_with_instances(
store,
query,
&self.entity_path,
component_name,
) else {
let Some((_, component_data)) =
get_component_with_instances(store, query, entity_path, component_name)
else {
continue; // no need to show components that are unset at this point in time
};

Expand All @@ -56,12 +69,12 @@ impl DataUi for InstancePath {
item_ui::component_path_button(
ctx,
ui,
&ComponentPath::new(self.entity_path.clone(), component_name),
&ComponentPath::new(entity_path.clone(), component_name),
);

if self.instance_key.is_splat() {
if instance_key.is_splat() {
super::component::EntityComponentWithInstances {
entity_path: self.entity_path.clone(),
entity_path: entity_path.clone(),
component_data,
}
.data_ui(ctx, ui, UiVerbosity::Small, query);
Expand All @@ -71,9 +84,9 @@ impl DataUi for InstancePath {
ui,
UiVerbosity::Small,
query,
&self.entity_path,
entity_path,
&component_data,
&self.instance_key,
instance_key,
);
}

Expand Down
4 changes: 0 additions & 4 deletions crates/re_log_types/src/index.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,6 @@ pub enum Index {
/// For arrays, assumed to be dense (0, 1, 2, …).
Sequence(u64),

/// X,Y pixel coordinates, from top left.
Pixel([u64; 2]),

/// Any integer, e.g. a hash or an arbitrary identifier.
Integer(i128),

Expand Down Expand Up @@ -56,7 +53,6 @@ impl std::fmt::Display for Index {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Sequence(seq) => format!("#{seq}").fmt(f),
Self::Pixel([x, y]) => format!("[{x}, {y}]").fmt(f),
Self::Integer(value) => value.fmt(f),
Self::Uuid(value) => value.fmt(f),
Self::String(value) => format!("{value:?}").fmt(f), // put it in quotes
Expand Down
42 changes: 42 additions & 0 deletions crates/re_log_types/src/path/data_path.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
use re_types::{components::InstanceKey, ComponentName};

use crate::EntityPath;

/// A general path to some data.
///
/// This always starts with an [`EntityPath`], followed
/// by an optional [`InstanceKey`], followed by an optional [`ComponentName`].
///
/// For instance:
///
/// * `points`
/// * `points.Color`
/// * `points[#42]`
/// * `points[#42].Color`
#[derive(Clone, Eq, PartialEq, Hash)]
pub struct DataPath {
pub entity_path: EntityPath,

pub instance_key: Option<InstanceKey>,

pub component_name: Option<ComponentName>,
}

impl std::fmt::Display for DataPath {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.entity_path.fmt(f)?;
if let Some(instance_key) = &self.instance_key {
write!(f, "[#{}]", instance_key.0)?;
}
if let Some(component_name) = &self.component_name {
write!(f, ".{component_name:?}")?;
}
Ok(())
}
}

impl std::fmt::Debug for DataPath {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.to_string().fmt(f)
}
}
Loading