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

Simplify graph viewer rendering logic #8227

Merged
merged 32 commits into from
Nov 29, 2024
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
e62c933
Prototype of scene
abey79 Nov 26, 2024
7cc8a42
Restruture `nodes` visualizer
grtlr Nov 26, 2024
cec7395
Implement text and cirlce rendering
grtlr Nov 26, 2024
48957e6
Properly register pan and zoom
grtlr Nov 26, 2024
c189ef5
Fix panning and re-add debug info
grtlr Nov 27, 2024
145a1a4
Restructure into `canvas`
grtlr Nov 27, 2024
45a2dc7
Convert to `DrawableNode`
grtlr Nov 27, 2024
22b5489
[skip ci] Get layout to work again
grtlr Nov 27, 2024
2a1535c
[skip ci] Introduce new `LayoutRequest` to get rid of `Discriminator`
grtlr Nov 27, 2024
226804c
[skip ci] Redo graph viewer
grtlr Nov 27, 2024
f8429ae
Add entiy rects
grtlr Nov 28, 2024
f50d698
[skip ci] Fix interactions and highlights
grtlr Nov 28, 2024
19902a3
Code cleanup
grtlr Nov 28, 2024
96927da
Rename `NodeIndex` to `NodeId`
grtlr Nov 28, 2024
0cc4fec
Fix double-click to center
grtlr Nov 28, 2024
2de4d1c
Re-add tracing
grtlr Nov 28, 2024
1874a67
Remove un-needed `size_hash`
grtlr Nov 28, 2024
dedd199
Fix combined hashing
grtlr Nov 28, 2024
8ff48a2
[skip ci] Try to fix ray intersection
grtlr Nov 28, 2024
86bb623
[skip ci] Add doccomment
grtlr Nov 28, 2024
7fcbb1a
Add `Option` to `LayoutResult` getters
grtlr Nov 28, 2024
db3a546
[ski ci] doc comments
grtlr Nov 28, 2024
98c9fc8
Provide many comments and try to clarify
grtlr Nov 28, 2024
96f7a53
[skip ci] unused for now
grtlr Nov 28, 2024
f8fd175
[skip ci] rename world -> scene
grtlr Nov 28, 2024
9df6694
[skip ci] display works, interactions don't
grtlr Nov 28, 2024
834fb2f
[skip ci] Fix tooltips and remaining interactions
grtlr Nov 28, 2024
9a5f261
Clean up
grtlr Nov 28, 2024
8d8056a
Remaining cleanup tasks
grtlr Nov 28, 2024
7a4ad6d
Merge branch 'main' into grtlr/simplify-graph-viewer
grtlr Nov 29, 2024
5c00d50
Fx lints
grtlr Nov 29, 2024
95e12e3
Fix typos
grtlr Nov 29, 2024
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
12 changes: 6 additions & 6 deletions crates/viewer/re_space_view_graph/src/graph/index.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,25 @@ use re_types::components;

use super::GraphNodeHash;

#[derive(Clone, Copy, PartialEq, Eq)]
pub struct NodeIndex {
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct NodeId {
pub entity_hash: EntityPathHash,
pub node_hash: GraphNodeHash,
}

impl nohash_hasher::IsEnabled for NodeIndex {}
impl nohash_hasher::IsEnabled for NodeId {}

// We implement `Hash` manually, because `nohash_hasher` requires a single call to `state.write_*`.
// More info: https://crates.io/crates/nohash-hasher
impl std::hash::Hash for NodeIndex {
impl std::hash::Hash for NodeId {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
// TODO(grtlr): Consider using `write_usize` here, to further decrease the risk of collision.
let combined = self.entity_hash.hash64() << 32 | self.node_hash.hash64();
grtlr marked this conversation as resolved.
Show resolved Hide resolved
state.write_u64(combined);
}
}

impl NodeIndex {
impl NodeId {
pub fn from_entity_node(entity_path: &EntityPath, node: &components::GraphNode) -> Self {
Self {
entity_hash: entity_path.hash(),
Expand All @@ -30,7 +30,7 @@ impl NodeIndex {
}
}

impl std::fmt::Debug for NodeIndex {
impl std::fmt::Debug for NodeId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "NodeIndex({:?}@{:?})", self.node_hash, self.entity_hash)
}
Expand Down
178 changes: 138 additions & 40 deletions crates/viewer/re_space_view_graph/src/graph/mod.rs
Original file line number Diff line number Diff line change
@@ -1,87 +1,185 @@
mod hash;
use std::hash::{Hash as _, Hasher as _};

use egui::{Pos2, Vec2};
pub(crate) use hash::GraphNodeHash;
mod index;
pub(crate) use index::NodeIndex;
pub(crate) use index::NodeId;

use re_types::components::{GraphNode, GraphType};
use re_chunk::EntityPath;
use re_log_types::Instance;
use re_types::{components::GraphType, ArrowString};

use crate::visualizers::{EdgeData, EdgeInstance, NodeData, NodeInstance};
use crate::{
ui::DrawableLabel,
visualizers::{EdgeData, NodeData},
};

pub struct NodeInstanceImplicit {
pub node: GraphNode,
pub index: NodeIndex,
pub enum Node {
Explicit {
id: NodeId,
instance: Instance,
node: ArrowString,
grtlr marked this conversation as resolved.
Show resolved Hide resolved
position: Option<Pos2>,
label: DrawableLabel,
},
Implicit {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Docstring these! I would not expect a reader to understand what an implicit node is (I don't)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added doc comments, maybe this makes it clearer. Because Node::Implicit only has a very limited subset of information, I chose to introduce the enum at the top level to avoid any confusion. This is also the reason wy I opted against an enum NodeCategory { Explicit, Implicit }.

We could also introduce a mixture of both approaches, but that would introduce another level of indirection, which I think can be avoided by a little redundancy.

id: NodeId,
node: ArrowString,
label: DrawableLabel,
},
}

impl std::hash::Hash for NodeInstanceImplicit {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.index.hash(state);
impl Node {
pub fn id(&self) -> NodeId {
match self {
Self::Explicit { id, .. } | Self::Implicit { id, .. } => *id,
}
}

pub fn label(&self) -> &DrawableLabel {
match self {
Self::Explicit { label, .. } | Self::Implicit { label, .. } => label,
}
}

pub fn size(&self) -> Vec2 {
match self {
Self::Explicit { label, .. } | Self::Implicit { label, .. } => label.size(),
}
grtlr marked this conversation as resolved.
Show resolved Hide resolved
}

pub fn position(&self) -> Option<Pos2> {
match self {
Self::Explicit { position, .. } => *position,
Self::Implicit { .. } => None,
}
}
}

pub struct Edge {
pub from: NodeId,
pub to: NodeId,
pub arrow: bool,
}

#[derive(Hash)]
pub struct Graph<'a> {
explicit: &'a [NodeInstance],
implicit: Vec<NodeInstanceImplicit>,
edges: &'a [EdgeInstance],
pub struct Graph {
entity: EntityPath,
nodes: Vec<Node>,
edges: Vec<Edge>,
kind: GraphType,
}

impl<'a> Graph<'a> {
pub fn new(node_data: Option<&'a NodeData>, edge_data: Option<&'a EdgeData>) -> Self {
impl Graph {
pub fn new<'a>(
ui: &egui::Ui,
entity: EntityPath,
node_data: Option<&'a NodeData>,
edge_data: Option<&'a EdgeData>,
) -> Self {
// We keep track of the nodes to find implicit nodes.
let mut seen = ahash::HashSet::default();

let explicit = if let Some(data) = node_data {
let mut nodes: Vec<Node> = if let Some(data) = node_data {
seen.extend(data.nodes.iter().map(|n| n.index));
data.nodes.as_slice()
data.nodes
.iter()
.map(|n| Node::Explicit {
id: n.index,
instance: n.instance,
node: n.node.0 .0.clone(),
position: n.position,
label: DrawableLabel::from_label(ui, &n.label),
})
.collect()
} else {
&[][..]
Vec::new()
};

let (edges, implicit, kind) = if let Some(data) = edge_data {
let mut implicit = Vec::new();
let (edges, kind) = if let Some(data) = edge_data {
for edge in &data.edges {
if !seen.contains(&edge.source_index) {
implicit.push(NodeInstanceImplicit {
node: edge.source.clone(),
index: edge.source_index,
nodes.push(Node::Implicit {
id: edge.source_index,
node: edge.source.0 .0.clone(),
label: DrawableLabel::implicit_circle(),
});
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe it would make more sense with a enum NodeCategory { Implicit, Explicit } and struct Node { category: NodeCategory, id: … } ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See above.

seen.insert(edge.source_index);
}
if !seen.contains(&edge.target_index) {
implicit.push(NodeInstanceImplicit {
node: edge.target.clone(),
index: edge.target_index,
nodes.push(Node::Implicit {
id: edge.target_index,
node: edge.target.0 .0.clone(),
label: DrawableLabel::implicit_circle(),
});
seen.insert(edge.target_index);
}
}
(data.edges.as_slice(), implicit, Some(data.graph_type))

let es = data.edges.iter().map(|e| Edge {
from: e.source_index,
to: e.target_index,
arrow: data.graph_type == GraphType::Directed,
});

(es.collect(), data.graph_type)
} else {
(&[][..], Vec::new(), None)
(Vec::new(), GraphType::default())
};

Self {
explicit,
implicit,
entity,
nodes,
edges,
kind: kind.unwrap_or_default(),
kind,
}
}

pub fn nodes_explicit(&self) -> impl Iterator<Item = &NodeInstance> {
self.explicit.iter()
}

pub fn nodes_implicit(&self) -> impl Iterator<Item = &NodeInstanceImplicit> + '_ {
self.implicit.iter()
pub fn nodes(&self) -> &[Node] {
&self.nodes
}

pub fn edges(&self) -> impl Iterator<Item = &EdgeInstance> {
self.edges.iter()
pub fn edges(&self) -> &[Edge] {
&self.edges
}

pub fn kind(&self) -> GraphType {
self.kind
}

pub fn entity(&self) -> &EntityPath {
&self.entity
}

#[deprecated]
pub fn size_hash(&self) -> u64 {
grtlr marked this conversation as resolved.
Show resolved Hide resolved
let mut hasher = ahash::AHasher::default();
for node in &self.nodes {
match node {
Node::Explicit {
id,
position,
label,
// The following fields can be ignored:
instance: _instance,
node: _node,
} => {
id.hash(&mut hasher);

position.as_ref().map(bytemuck::bytes_of).hash(&mut hasher);
grtlr marked this conversation as resolved.
Show resolved Hide resolved
bytemuck::bytes_of(&label.size()).hash(&mut hasher);
}
Node::Implicit {
id,
label,
// The following fields can be ignored:
node: _node,
} => {
id.hash(&mut hasher);
bytemuck::bytes_of(&label.size()).hash(&mut hasher);
}
}
}
hasher.finish()
}
}
Loading
Loading