Skip to content
This repository was archived by the owner on Apr 9, 2024. It is now read-only.

Commit ef3e7d0

Browse files
committed
Many to many connections
Currently graph connections are always one output to many inputs. This is appropriate for most "data type values", but not always correct. The motivating example for a place where it is incorrect is "control flow", where many nodes might "output" control flow to the same node's control flow input. This changes that so that it is configurable via data-type whether nodes are one-many, many-one, many-many, or even one-one with the concept of splittable data-types (data types that can be copied from one output to many inputs) and mergeable data-types (data types that can accept many outputs into a single input).
1 parent 1e76f94 commit ef3e7d0

File tree

6 files changed

+131
-31
lines changed

6 files changed

+131
-31
lines changed

egui_node_graph/src/editor_ui.rs

+40-12
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,7 @@ where
201201
}
202202
NodeResponse::DisconnectEvent { input, output } => {
203203
let other_node = self.graph.get_input(input).node();
204-
self.graph.remove_connection(input);
204+
self.graph.remove_connection(output, input);
205205
self.connection_in_progress =
206206
Some((other_node, AnyParameterId::Output(output)));
207207
}
@@ -327,7 +327,7 @@ where
327327
for (param_name, param_id) in inputs {
328328
if self.graph[param_id].shown_inline {
329329
let height_before = ui.min_rect().bottom();
330-
if self.graph.connection(param_id).is_some() {
330+
if self.graph.incoming(param_id).len() != 0 {
331331
ui.label(param_name);
332332
} else {
333333
self.graph[param_id].value.value_widget(&param_name, ui);
@@ -370,7 +370,11 @@ where
370370
param_id: AnyParameterId,
371371
port_locations: &mut PortLocations,
372372
ongoing_drag: Option<(NodeId, AnyParameterId)>,
373-
connected_to_output: Option<OutputId>,
373+
// If the datatype of this node restricts it to connecting to
374+
// at most one other node, and there is a connection, then this
375+
// parameter should be Some(PortItIsConnectedTo), otherwise it
376+
// should be None
377+
unique_connection: Option<AnyParameterId>,
374378
) where
375379
DataType: DataTypeTrait,
376380
UserResponse: UserResponseTrait,
@@ -395,14 +399,18 @@ where
395399
.circle(port_rect.center(), 5.0, port_color, Stroke::none());
396400

397401
if resp.drag_started() {
398-
if let Some(output) = connected_to_output {
399-
responses.push(NodeResponse::DisconnectEvent {
400-
output,
402+
let response = match unique_connection {
403+
Some(AnyParameterId::Input(input)) => NodeResponse::DisconnectEvent {
404+
input,
405+
output: param_id.assume_output(),
406+
},
407+
Some(AnyParameterId::Output(output)) => NodeResponse::DisconnectEvent {
401408
input: param_id.assume_input(),
402-
});
403-
} else {
404-
responses.push(NodeResponse::ConnectEventStarted(node_id, param_id));
405-
}
409+
output,
410+
},
411+
None => NodeResponse::ConnectEventStarted(node_id, param_id),
412+
};
413+
responses.push(response);
406414
}
407415

408416
if let Some((origin_node, origin_param)) = ongoing_drag {
@@ -436,6 +444,16 @@ where
436444
InputParamKind::ConnectionOrConstant => true,
437445
};
438446

447+
let unique_connection = if !self.graph.get_input(*param).typ.mergeable() {
448+
self.graph
449+
.incoming(*param)
450+
.first()
451+
.copied()
452+
.map(AnyParameterId::Output)
453+
} else {
454+
None
455+
};
456+
439457
if should_draw {
440458
let pos_left = pos2(port_left, port_height);
441459
draw_port(
@@ -447,7 +465,7 @@ where
447465
AnyParameterId::Input(*param),
448466
self.port_locations,
449467
self.ongoing_drag,
450-
self.graph.connection(*param),
468+
unique_connection,
451469
);
452470
}
453471
}
@@ -458,6 +476,16 @@ where
458476
.iter()
459477
.zip(output_port_heights.into_iter())
460478
{
479+
let unique_connection = if !self.graph.get_output(*param).typ.splittable() {
480+
self.graph
481+
.outgoing(*param)
482+
.first()
483+
.copied()
484+
.map(AnyParameterId::Input)
485+
} else {
486+
None
487+
};
488+
461489
let pos_right = pos2(port_right, port_height);
462490
draw_port(
463491
ui,
@@ -468,7 +496,7 @@ where
468496
AnyParameterId::Output(*param),
469497
self.port_locations,
470498
self.ongoing_drag,
471-
None,
499+
unique_connection,
472500
);
473501
}
474502

egui_node_graph/src/graph.rs

+5-2
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,10 @@ pub struct Graph<NodeData, DataType, ValueType> {
8585
pub inputs: SlotMap<InputId, InputParam<DataType, ValueType>>,
8686
/// The [`OutputParam`]s of the graph
8787
pub outputs: SlotMap<OutputId, OutputParam<DataType>>,
88-
// Connects the input of a node, to the output of its predecessor that
88+
// Connects the input of a node, to the output(s) of its predecessor(s) that
8989
// produces it
90-
pub connections: SecondaryMap<InputId, OutputId>,
90+
pub incoming: SecondaryMap<InputId, SVec<OutputId>>,
91+
// Connects the outputs of a node, to the input(s) of its predecessor(s) that
92+
// consumes it
93+
pub outgoing: SecondaryMap<OutputId, SVec<InputId>>,
9194
}

egui_node_graph/src/graph_impls.rs

+74-15
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
11
use super::*;
22

3-
impl<NodeData, DataType, ValueType> Graph<NodeData, DataType, ValueType> {
3+
impl<NodeData, DataType, ValueType> Graph<NodeData, DataType, ValueType>
4+
where
5+
DataType: DataTypeTrait,
6+
{
47
pub fn new() -> Self {
58
Self {
69
nodes: SlotMap::default(),
710
inputs: SlotMap::default(),
811
outputs: SlotMap::default(),
9-
connections: SecondaryMap::default(),
12+
incoming: SecondaryMap::default(),
13+
outgoing: SecondaryMap::default(),
1014
}
1115
}
1216

@@ -64,37 +68,90 @@ impl<NodeData, DataType, ValueType> Graph<NodeData, DataType, ValueType> {
6468
}
6569

6670
pub fn remove_node(&mut self, node_id: NodeId) {
67-
self.connections
68-
.retain(|i, o| !(self.outputs[*o].node == node_id || self.inputs[i].node == node_id));
6971
let inputs: SVec<_> = self[node_id].input_ids().collect();
7072
for input in inputs {
71-
self.inputs.remove(input);
73+
self.remove_incoming_connections(input);
7274
}
7375
let outputs: SVec<_> = self[node_id].output_ids().collect();
7476
for output in outputs {
75-
self.outputs.remove(output);
77+
self.remove_outgoing_connections(output);
7678
}
7779
self.nodes.remove(node_id);
7880
}
7981

80-
pub fn remove_connection(&mut self, input_id: InputId) -> Option<OutputId> {
81-
self.connections.remove(input_id)
82+
pub fn remove_connection(&mut self, output_id: OutputId, input_id: InputId) {
83+
self.outgoing[output_id].retain(|&mut x| x != input_id);
84+
self.incoming[input_id].retain(|&mut x| x != output_id);
85+
}
86+
87+
pub fn remove_incoming_connections(&mut self, input_id: InputId) {
88+
if let Some(outputs) = self.incoming.get(input_id) {
89+
for &output in outputs {
90+
self.outgoing[output].retain(|&mut x| x != input_id);
91+
}
92+
}
93+
self.incoming.remove(input_id);
94+
}
95+
96+
pub fn remove_outgoing_connections(&mut self, output_id: OutputId) {
97+
if let Some(inputs) = self.outgoing.get(output_id) {
98+
for &input in inputs {
99+
self.incoming[input].retain(|&mut x| x != output_id);
100+
}
101+
}
102+
self.outgoing.remove(output_id);
82103
}
83104

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

88109
pub fn add_connection(&mut self, output: OutputId, input: InputId) {
89-
self.connections.insert(input, output);
110+
if self.get_input(input).typ.mergeable() {
111+
self.incoming
112+
.entry(input)
113+
.expect("Old InputId")
114+
.or_default()
115+
.push(output);
116+
} else {
117+
self.remove_incoming_connections(input);
118+
let mut v = SVec::new();
119+
v.push(output);
120+
self.incoming.insert(input, v);
121+
}
122+
123+
if self.get_output(output).typ.splittable() {
124+
self.outgoing
125+
.entry(output)
126+
.expect("Old OutputId")
127+
.or_default()
128+
.push(input);
129+
} else {
130+
self.remove_outgoing_connections(output);
131+
let mut v = SVec::new();
132+
v.push(input);
133+
self.outgoing.insert(output, v);
134+
}
90135
}
91136

92137
pub fn iter_connections(&self) -> impl Iterator<Item = (InputId, OutputId)> + '_ {
93-
self.connections.iter().map(|(o, i)| (o, *i))
138+
self.incoming
139+
.iter()
140+
.flat_map(|(o, inputs)| inputs.iter().map(move |&i| (o, i)))
141+
}
142+
143+
pub fn incoming(&self, input: InputId) -> &[OutputId] {
144+
self.incoming
145+
.get(input)
146+
.map(|x| x.as_slice())
147+
.unwrap_or(&[])
94148
}
95149

96-
pub fn connection(&self, input: InputId) -> Option<OutputId> {
97-
self.connections.get(input).copied()
150+
pub fn outgoing(&self, output: OutputId) -> &[InputId] {
151+
self.outgoing
152+
.get(output)
153+
.map(|x| x.as_slice())
154+
.unwrap_or(&[])
98155
}
99156

100157
pub fn any_param_type(&self, param: AnyParameterId) -> Result<&DataType, EguiGraphError> {
@@ -114,21 +171,23 @@ impl<NodeData, DataType, ValueType> Graph<NodeData, DataType, ValueType> {
114171
}
115172
}
116173

117-
impl<NodeData, DataType, ValueType> Default for Graph<NodeData, DataType, ValueType> {
174+
impl<NodeData, DataType: DataTypeTrait, ValueType> Default
175+
for Graph<NodeData, DataType, ValueType>
176+
{
118177
fn default() -> Self {
119178
Self::new()
120179
}
121180
}
122181

123182
impl<NodeData> Node<NodeData> {
124-
pub fn inputs<'a, DataType, DataValue>(
183+
pub fn inputs<'a, DataType: DataTypeTrait, DataValue>(
125184
&'a self,
126185
graph: &'a Graph<NodeData, DataType, DataValue>,
127186
) -> impl Iterator<Item = &InputParam<DataType, DataValue>> + 'a {
128187
self.input_ids().map(|id| graph.get_input(id))
129188
}
130189

131-
pub fn outputs<'a, DataType, DataValue>(
190+
pub fn outputs<'a, DataType: DataTypeTrait, DataValue>(
132191
&'a self,
133192
graph: &'a Graph<NodeData, DataType, DataValue>,
134193
) -> impl Iterator<Item = &OutputParam<DataType>> + 'a {

egui_node_graph/src/traits.rs

+10
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,16 @@ pub trait DataTypeTrait: PartialEq + Eq {
1616

1717
// The name of this datatype
1818
fn name(&self) -> &str;
19+
20+
/// Whether an output of this datatype can be sent to multiple nodes
21+
fn splittable(&self) -> bool {
22+
true
23+
}
24+
25+
/// Whether an input of this datatype can be recieved from multiple nodes
26+
fn mergeable(&self) -> bool {
27+
false
28+
}
1929
}
2030

2131
/// This trait must be implemented for the `NodeData` generic parameter of the

egui_node_graph/src/ui_state.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ pub struct GraphEditorState<NodeData, DataType, ValueType, NodeTemplate, UserSta
3131
pub user_state: UserState,
3232
}
3333

34-
impl<NodeData, DataType, ValueType, NodeKind, UserState>
34+
impl<NodeData, DataType: DataTypeTrait, ValueType, NodeKind, UserState>
3535
GraphEditorState<NodeData, DataType, ValueType, NodeKind, UserState>
3636
{
3737
pub fn new(default_zoom: f32, user_state: UserState) -> Self {

egui_node_graph_example/src/app.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -515,7 +515,7 @@ fn evaluate_input(
515515
let input_id = graph[node_id].get_input(param_name)?;
516516

517517
// The output of another node is connected.
518-
if let Some(other_output_id) = graph.connection(input_id) {
518+
if let Some(&other_output_id) = graph.incoming(input_id).first() {
519519
// The value was already computed due to the evaluation of some other
520520
// node. We simply return value from the cache.
521521
if let Some(other_value) = outputs_cache.get(&other_output_id) {

0 commit comments

Comments
 (0)