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

many-to-many connections support #91

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

kamirr
Copy link
Contributor

@kamirr kamirr commented May 10, 2023

This patch is an attempt at adding many-to-many connection support without overhauling the library and minimal breaking changes to the user.

The core of the idea is to replace Graph::connections from SecondaryMap<InputId, OutputId> to SecondaryMap<InputId, HashSet<OutputId>> and roll from that.

Ports are parametrized by an argument specifying the maximum non-zero number of connections (not yet enforced), and if that number is greater than 1 a so called wide-port is drawn -- it's characterized by being longer than a typical port and connections made to it are spread out and can be disconnected individually.

Known limitations:

  • Again, max connections per node is not enforced.
  • HashSet<_> isn't an adequate data structure for storing the connected outputs -- we have no control over ordering, let alone the end user.
  • API needs fleshing out.
  • Dragging from a non-empty wide port always triggers a disconnect.
  • ...?

@kamirr
Copy link
Contributor Author

kamirr commented May 10, 2023

I felt like #30 and #81 offer an overly complicated solution to the problem of many-to-many connections, so I'm messing around with a different approach. I'd appreciate comments regarding the general design of the feature.

image

Do note that this is purely an experiment to see if this approach can even lead to a workable solution that offers good UX and simple, flexible API.

Copy link
Owner

@setzer22 setzer22 left a comment

Choose a reason for hiding this comment

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

This is really nice! 🎉 Thanks a lot for this PR :)

Indeed, I've been hesitant to merge previous attempts to this in the past because of how much they deviated from the original design. This looks like a very clean incremental update over what we had, and indeed, users should not have a hard time upgrading. I'd be very much OK with merging something like this.

I feel the ordering of parameters is worth discussing. In this version, you are using a HashSet to store the multiple inputs in a wide port, and this leads to a random ordering of the connections. This leads to the question: Do we care about the ordering of parameters? In your example, an addition operation would not care, since in those cases, as it is commutative. But what happens with operations that are not commutative?

I'm not sure how hard it would be to extend the current design into something that allows users to reorder the connections in a wide port. Essentially, replace that HashSet with a Vec and all the necessary UI interaction bits that make this work. Calculating the insertion point into the list of inputs based on the mouse position when the mouse is released into a connection into a pin doesn't sound that complicated. 🤔 Oh, and now I just realized you're already doing a ver similar logic to handle pin disconnections, so this calculation is already in place!

In case we decide ordered connections in a port are broadening the scope too much for now, input connections should at least be sorted in a deterministic way before iterating them.

egui_node_graph/src/editor_ui.rs Outdated Show resolved Hide resolved
@kamirr
Copy link
Contributor Author

kamirr commented May 13, 2023

This leads to the question: Do we care about the ordering of parameters? In your example, an addition operation would not care, since in those cases, as it is commutative. But what happens with operations that are not commutative?

Having sat on the issue for a while, these are my thoughts:

  • Ordering of inputs is important not only because the consumer might care (honestly, I'd rather discourage using wide ports for non-commutative operations), but it's crucial from the UX-perspective. If the user is not able to order connections at will, unwanted intersections of connections are bound to come up.
  • HashSet<_> seemed like an elegant structure for this and I hoped that ordering could come from someplace else, but this would make it more convoluted to access for the end user. I'll switch to just using a Vec<_>.

@kamirr
Copy link
Contributor Author

kamirr commented May 13, 2023

Implemented ordered inputs, here's the current status: nodes_ordering.webm. Sorry for the invisible cursor

Copy link
Owner

@setzer22 setzer22 left a comment

Choose a reason for hiding this comment

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

Looking good so far 👍 The new port ordering looks pretty nice!

Once we get the input size and y offset for the wide ports figured out, this will be ready for merging. I left a comment above with a potential solution.

@kamirr
Copy link
Contributor Author

kamirr commented May 16, 2023

Screenshots

image
image
image
image
(The args label missing in the first image is fixed by the last commit, ignore that.)

Looks like this is feature-complete for now. I'll clean up the code, rebase and mark it as ready to review.

@setzer22
Copy link
Owner

setzer22 commented May 16, 2023

Amazing! 🎉 Thanks a lot for the hard work @kamirr.

I'd say with the nice round of recent changes, this being merged would be a good time for a release. But I'm not sure if you're planning on tackling any other tasks, there's really no rush so we can wait.

@kamirr
Copy link
Contributor Author

kamirr commented May 16, 2023

I'd like to work on documentation before the next release, I'll get to that right after we get this PR merged.

Ps. I was a bit too eager to declare feature-completeness, max_connections still isn't enforced, but that's easy enough to address.

This change enables many-to-manny graphs by extending input ports with a
capability to optionally accept multiple connections, via a
max_connections parameter. If this parameter is set to anything other
than Some(NonZeroU32(1)) the port will be referred to as `wide`.

* Adjust Graph::connections to map from InputId to Vec<OutputId> instead
  of just OutputId.
* Adjust UI to render wide ports as segments extending in length
  proportionally to the number of connections.
* Allow creating a new connection by dragging from an extra slot in a
  wide port.
* If a wide port is not full, an in-progress connection will move
  existing ones out of the way to allow intuitive ordering. Otherwise,
  the new connection will override the one it's hovering over within the
  port.
* Extend NodeResponse::ConnectEventEnded with an additional field
  containing the index under which the new connection resides within
  the input port.
* Add Graph::add_wide_input_param for creating wide ports.
@kamirr kamirr marked this pull request as ready for review May 17, 2023 14:57
@kamirr
Copy link
Contributor Author

kamirr commented May 17, 2023

Should be ready for review:

  • Rebased onto main.
  • Some cleanups in UI.
  • Deleted changes in the example app.
  • Enforce max_connections.

The history of my changes is lost due to the rebase with squashes I did, but it can be browsed on the kek/many-to-many2 branch on my fork.

@kamirr kamirr requested a review from setzer22 May 17, 2023 15:03
@kamirr kamirr changed the title [WIP] many-to-many connections support many-to-many connections support May 18, 2023
@setzer22
Copy link
Owner

Thanks for all the amazing work :) I'll review this soon, but I already made a quick look and things are looking good.

@fenollp
Copy link
Contributor

fenollp commented Jan 26, 2024

I'm not sure how hard it would be to extend the current design into something that allows users to reorder the connections in a wide port. Essentially, replace that HashSet with a Vec

My 2 cents: https://doc.rust-lang.org/std/collections/struct.BTreeSet.html is insertion-sorted :)

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants