Skip to content

Commit

Permalink
POC: Many to many edges
Browse files Browse the repository at this point in the history
  • Loading branch information
gmorenz committed Apr 8, 2022
1 parent 5cba218 commit 394eb21
Show file tree
Hide file tree
Showing 5 changed files with 113 additions and 30 deletions.
47 changes: 35 additions & 12 deletions egui_node_graph/src/editor_ui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ where
}
NodeResponse::DisconnectEvent{ input, output } => {
let other_node = self.graph.get_input(input).node();
self.graph.remove_connection(input);
self.graph.remove_connection(input, output);
self.connection_in_progress =
Some((other_node, AnyParameterId::Output(output)));
}
Expand Down Expand Up @@ -316,7 +316,7 @@ where
for (param_name, param_id) in inputs {
if self.graph[param_id].shown_inline {
let height_before = ui.min_rect().bottom();
if self.graph.connection(param_id).is_some() {
if self.graph.incoming(param_id).len() != 0 {
ui.label(param_name);
} else {
self.graph[param_id].value.value_widget(&param_name, ui);
Expand Down Expand Up @@ -359,7 +359,11 @@ where
param_id: AnyParameterId,
port_locations: &mut PortLocations,
ongoing_drag: Option<(NodeId, AnyParameterId)>,
connected_to_output: Option<OutputId>,
// If the datatype of this node restricts it to connecting to
// at most one other node, and there is a connection, then this
// parameter should be Some(PortItIsConnectedTo), otherwise it
// should be None
unique_connection: Option<AnyParameterId>,
) where
DataType: DataTypeTrait,
UserResponse: UserResponseTrait,
Expand All @@ -384,14 +388,18 @@ where
.circle(port_rect.center(), 5.0, port_color, Stroke::none());

if resp.drag_started() {
if let Some(output) = connected_to_output {
responses.push(NodeResponse::DisconnectEvent {
output,
let response = match unique_connection {
Some(AnyParameterId::Input(input)) => NodeResponse::DisconnectEvent {
input,
output: param_id.assume_output(),
},
Some(AnyParameterId::Output(output)) => NodeResponse::DisconnectEvent {
input: param_id.assume_input(),
});
} else {
responses.push(NodeResponse::ConnectEventStarted(node_id, param_id));
}
output,
},
None => NodeResponse::ConnectEventStarted(node_id, param_id),
};
responses.push(response);
}

if let Some((origin_node, origin_param)) = ongoing_drag {
Expand Down Expand Up @@ -424,6 +432,13 @@ where
InputParamKind::ConnectionOrConstant => true,
};

let unique_connection =
if !self.graph.get_input(*param).typ.mergeable() {
self.graph.incoming(*param).first().copied().map(AnyParameterId::Output)
} else {
None
};

if should_draw {
let pos_left = pos2(port_left, port_height);
draw_port(
Expand All @@ -435,7 +450,7 @@ where
AnyParameterId::Input(*param),
self.port_locations,
self.ongoing_drag,
self.graph.connection(*param),
unique_connection,
);
}
}
Expand All @@ -446,6 +461,14 @@ where
.iter()
.zip(output_port_heights.into_iter())
{

let unique_connection =
if !self.graph.get_output(*param).typ.splittable() {
self.graph.outgoing(*param).first().copied().map(AnyParameterId::Input)
} else {
None
};

let pos_right = pos2(port_right, port_height);
draw_port(
ui,
Expand All @@ -456,7 +479,7 @@ where
AnyParameterId::Output(*param),
self.port_locations,
self.ongoing_drag,
None,
unique_connection,
);
}

Expand Down
7 changes: 5 additions & 2 deletions egui_node_graph/src/graph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,10 @@ pub struct Graph<NodeData, DataType, ValueType> {
pub inputs: SlotMap<InputId, InputParam<DataType, ValueType>>,
/// The [`OutputParam`]s of the graph
pub outputs: SlotMap<OutputId, OutputParam<DataType>>,
// Connects the input of a node, to the output of its predecessor that
// Connects the input of a node, to the output(s) of its predecessor(s) that
// produces it
pub connections: SecondaryMap<InputId, OutputId>,
pub incoming: SecondaryMap<InputId, SVec<OutputId>>,
// Connects the outputs of a node, to the input(s) of its predecessor(s) that
// consumes it
pub outgoing: SecondaryMap<OutputId, SVec<InputId>>,
}
81 changes: 66 additions & 15 deletions egui_node_graph/src/graph_impls.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
use super::*;

impl<NodeData, DataType, ValueType> Graph<NodeData, DataType, ValueType> {
impl<NodeData, DataType, ValueType> Graph<NodeData, DataType, ValueType>
where DataType: DataTypeTrait {
pub fn new() -> Self {
Self {
nodes: SlotMap::default(),
inputs: SlotMap::default(),
outputs: SlotMap::default(),
connections: SecondaryMap::default(),
incoming: SecondaryMap::default(),
outgoing: SecondaryMap::default(),
}
}

Expand Down Expand Up @@ -64,37 +66,86 @@ impl<NodeData, DataType, ValueType> Graph<NodeData, DataType, ValueType> {
}

pub fn remove_node(&mut self, node_id: NodeId) {
self.connections
.retain(|i, o| !(self.outputs[*o].node == node_id || self.inputs[i].node == node_id));
let inputs: SVec<_> = self[node_id].input_ids().collect();
for input in inputs {
self.inputs.remove(input);
self.remove_incoming_connections(input);
}
let outputs: SVec<_> = self[node_id].output_ids().collect();
for output in outputs {
self.outputs.remove(output);
self.remove_outgoing_connections(output);
}
self.nodes.remove(node_id);
}

pub fn remove_connection(&mut self, input_id: InputId) -> Option<OutputId> {
self.connections.remove(input_id)
pub fn remove_connection(&mut self, input_id: InputId, output_id: OutputId) {

This comment has been minimized.

Copy link
@philpax

philpax May 21, 2022

Minor API design thing I noticed while using this:

pub fn add_connection(&mut self, output: OutputId, input: InputId);
pub fn remove_connection(&mut self, input_id: InputId, output_id: OutputId);

Would suggest swapping the arguments on remove_connection to be consistent.

This comment has been minimized.

Copy link
@gmorenz

gmorenz May 21, 2022

Author Owner

Good call. Will do.

Maybe also a good idea to swap the order of the return values in iter_connections to be the same (Output -> Input).

self.outgoing[output_id].retain(|&mut x| x != input_id);
self.incoming[input_id].retain(|&mut x| x != output_id);
}

pub fn remove_incoming_connections(&mut self, input_id: InputId) {
if let Some(outputs) = self.incoming.get(input_id) {
for &output in outputs {
self.outgoing[output].retain(|&mut x| x != input_id);
}
}
self.incoming.remove(input_id);
}

pub fn remove_outgoing_connections(&mut self, output_id: OutputId) {
if let Some(inputs) = self.outgoing.get(output_id) {
for &input in inputs {
self.incoming[input].retain(|&mut x| x != output_id);
}
}
self.outgoing.remove(output_id);
}

pub fn iter_nodes(&self) -> impl Iterator<Item = NodeId> + '_ {
self.nodes.iter().map(|(id, _)| id)
}

pub fn add_connection(&mut self, output: OutputId, input: InputId) {
self.connections.insert(input, output);
if self.get_input(input).typ.mergeable() {
self.incoming.entry(input)
.expect("Old InputId")
.or_default()
.push(output);
} else {
self.remove_incoming_connections(input);
let mut v = SVec::new();
v.push(output);
self.incoming.insert(input, v);
}

if self.get_output(output).typ.splittable() {
self.outgoing.entry(output)
.expect("Old OutputId")
.or_default()
.push(input);
} else {
self.remove_outgoing_connections(output);
let mut v = SVec::new();
v.push(input);
self.outgoing.insert(output, v);
}
}

pub fn iter_connections(&self) -> impl Iterator<Item = (InputId, OutputId)> + '_ {
self.connections.iter().map(|(o, i)| (o, *i))
self.incoming.iter().flat_map(|(o, inputs)|
inputs.iter().map(move |&i| (o, i))
)
}

pub fn incoming(&self, input: InputId) -> &[OutputId] {
self.incoming.get(input)
.map(|x| x.as_slice())
.unwrap_or(&[])
}

pub fn connection(&self, input: InputId) -> Option<OutputId> {
self.connections.get(input).copied()
pub fn outgoing(&self, output: OutputId) -> &[InputId] {
self.outgoing.get(output)
.map(|x| x.as_slice())
.unwrap_or(&[])
}

pub fn any_param_type(&self, param: AnyParameterId) -> Result<&DataType, EguiGraphError> {
Expand All @@ -114,21 +165,21 @@ impl<NodeData, DataType, ValueType> Graph<NodeData, DataType, ValueType> {
}
}

impl<NodeData, DataType, ValueType> Default for Graph<NodeData, DataType, ValueType> {
impl<NodeData, DataType: DataTypeTrait, ValueType> Default for Graph<NodeData, DataType, ValueType> {
fn default() -> Self {
Self::new()
}
}

impl<NodeData> Node<NodeData> {
pub fn inputs<'a, DataType, DataValue>(
pub fn inputs<'a, DataType: DataTypeTrait, DataValue>(
&'a self,
graph: &'a Graph<NodeData, DataType, DataValue>,
) -> impl Iterator<Item = &InputParam<DataType, DataValue>> + 'a {
self.input_ids().map(|id| graph.get_input(id))
}

pub fn outputs<'a, DataType, DataValue>(
pub fn outputs<'a, DataType: DataTypeTrait, DataValue>(
&'a self,
graph: &'a Graph<NodeData, DataType, DataValue>,
) -> impl Iterator<Item = &OutputParam<DataType>> + 'a {
Expand Down
6 changes: 6 additions & 0 deletions egui_node_graph/src/traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@ pub trait DataTypeTrait: PartialEq + Eq {

// The name of this datatype
fn name(&self) -> &str;

/// Whether an output of this datatype can be sent to multiple nodes
fn splittable(&self) -> bool { true }

/// Whether an input of this datatype can be recieved from multiple nodes
fn mergeable(&self) -> bool { false }
}

/// This trait must be implemented for the `NodeData` generic parameter of the
Expand Down
2 changes: 1 addition & 1 deletion egui_node_graph/src/ui_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ pub struct GraphEditorState<NodeData, DataType, ValueType, NodeTemplate, UserSta
pub user_state: UserState,
}

impl<NodeData, DataType, ValueType, NodeKind, UserState>
impl<NodeData, DataType: DataTypeTrait, ValueType, NodeKind, UserState>
GraphEditorState<NodeData, DataType, ValueType, NodeKind, UserState>
{
pub fn new(default_zoom: f32, user_state: UserState) -> Self {
Expand Down

0 comments on commit 394eb21

Please sign in to comment.