This repository has been archived by the owner on Apr 9, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 138
Modify ConnectionEvents to specify both endpoints, and implement many to many connections #30
Open
gmorenz
wants to merge
3
commits into
setzer22:main
Choose a base branch
from
gmorenz:many-many
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How do you disconnect a node when the connection is not unique? I think dragging from an input should start a disconnect event even if the input port has multiple incoming connections.
The logic to select which connection is a bit more nuanced... Here's how blender does it for reference:
In Blender's case, they have some sort of "wide" port and depending on where exactly in the port you drag from you get different connections. Since we don't have this logic here, we could devise a simpler mechanism where you simply disconnect the last connection in the list. This will be a bit iconvenient for users, but I can't think of a better way. I think in the long run we need something like blender's wide ports.
EDIT: Also important to note that for some nodes taking multiple inputs users will care about the order in which those inputs are processed. This again brings us back to blender's solution of the problem.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh no, forgot about this issue for many-many nodes.
So, for many->one types (which is what I'm using this for in my project), the behavior is just the reverse of one->many nodes. Dragging from the input creates a new edge, dragging from the output lets you delete the edge. This feels right to me. I'm not using one-one nodes, but dragging from either output removes the current edge (which also sounds right).
For many-many data-types, I'm inclined to think that the right solution is to make it possible to select edges. Right now it's not possible to delete many-many edges short of deleting nodes. Alternatively it might just be better to not have many-many data types, and use an alternative solution for them (one of blenders... see below).
I didn't realize this, but blender actually supports (at least) two different solutions for many-many edges.
One is a sort of wide port, the other keeps adding more inputs as you fill up the existing inputs.
The latter is something the events changes make pretty easy to implement, I've done it (except for outputs)... and it's also one of the reasons I want #32.
Both of these solutions strike me as a decision that should be made by the port, not data that should attach to the data type. I.e. the same data type should be able to be used with wide and "thin" ports.
Circling back to what to do in this PR.
I think selecting edges and/or wide ports really belongs in a follow up PR (it seems unlikely to be a completely trivial change) - but I should probably make sure that wide ports are at least reasonably simple to implement.
If we don't want to go the selecting edges route, it might make sense to change the API for defining data-types so that many-many datatypes are unrepresentable (which shouldn't forbid the use of many-one data-types with wide-ports to emulate them).
We could also conceivably abandon this approach and just go with wide ports. I think this is a worse solution for control flow like data which really is naturally one->many, but it might have some simplicity benefits for programs that don't need that, and it might be possible to make it configurable enough to get good behavior with a bit of extra work.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You are quite right here, the "cardinality" of a port is a property of a port, so my
VectorList
datatype example was a bad one. Instead, we should add a port that accepts multiple inputs of typeVector
.I think that wouldn't be a very flexible solution. In a regular "data flow" dependency graph, everywhere many-to-one is useful, many-to-many is also useful. Consider this example, here a node like "Join Geometry" would be far less useful if it affected their inputs preventing them from being shared to other outputs. That would be quite a weird thing for a node graph (although Rust has it, feels pretty much like move semantics, and I guess that's why it works well in your case, since you're not modelling data but control flow 🤔)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After thinking about this a bit more, I think I have come up with a good understanding of the issue and a solution that fits all our use cases 🤔
The common logic in your PR is that dragging away from a port that is "full" (i.e. doesn't accept any more connections) initiates a disconnection event. This works for many-to-one, one-to-many and one-to-one ports, but not for many-to-many ports, because they are never "full". The problem here is that there are two orthogonal concepts that are being mixed together. Let me ellaborate:
We can classify ports in the following ways:
Your control flow "join" port is a one-to-many unordered port, and blender's "Join Geometry" input is a many-to-many ordered port. Other examples exist, and after giving it some thought I think all combinations can have valid use cases 🤔
In the long term (not for this PR) ordered ports can be represented as wide ports, just like in Blender. This allows users to see the order of the different inputs visually, while unordered ports are just a single dot, just like now.
But then, when it comes to this PR, I think the key to solving our issue is separate the cardinality of a port from its disconnect behavior. When a user drags away from a port, instead of using
splittable
andmergeable
to determine whether we should initiate a connect or disconnect event, we instead add a different set of methodsdisconnect_when_input
anddisconnect_when_output
, so that users can implement their own behavior.Your control flow ports could return
disconnect_when_input = false
anddisconnect_when_output = true
and this would get you the desired behavior. Meanwhile, in an application closer to blender, where the data flow is being modelled, users can set the usualdisconnect_when_input = true
anddisconnect_when_input = false
that would be consistent with one-to-many ports. For now, since there are no wide ports, disconnecting from a "many" port would simply drag the last (or whatever) connection, we can improve this UX later on with wide ports.There is still room for
splittable
andmergeable
. These fields indicate the cardinality of the port, and that is still checked when finishing the connection event. If a port is not mergeable, you won't add an additional connection to it when the user releases an ongoing connection on top of it.I think this does not take a lot of extra work and solves the issue for everyone. What do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like the idea of having orthogonal traits of order vs cardinality, but I think I would strongly recommend using wide ports regardless of whether the port is ordered or unordered.
I think it's very unlikely that there would be a use case where someone always wants all connections to be pulled off of a port when clicking and dragging on a port. Instead I think "ordered" vs "unordered" should just decide whether or not the library is allowed to reorder the connections in the wide port to eliminate tangling. That would probably be a future feature outside the scope of an initial many-to-many port PR.
To allow users to choose between removing a connection vs creating a connection for wide ports, I recommend a tweak to the UI, drafted in this image:
Multi Port
will always begin to create a connection.Having this additional square element will also help to visually communicate that multiple connections to this port are allowed. We can have the unused dot disappear when the port reaches its limit for number of connections to communicate that the port does not accept any more connections.
I naively assume that this should be a relatively simple interface to implement. Probably the hardest part will be making the row height for this port dynamically grow.
If this suggestion sounds reasonable then I can try taking a stab at implementing for this PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mxgrey Right, you have a point there. My original comment was a bit biased by the fact that we didn't have a wide port, but if the plan is to implement it, I agree it makes sense to have it on both cases.
I like your description for the UX of the wide port. I'd be happy to have something like that. Feels more explicit than blender's approach 👍