From 51652ef277f8bc6235a80b564e90ee8af2361cae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lera=20Elvo=C3=A9?= Date: Wed, 21 Feb 2024 04:08:36 +0000 Subject: [PATCH] add port usage type to Port (#69) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit first part of addressing #59 every `Port` now has a `usage_type` field that indicates whether it can be used for triggers (eg. sending and receiving events), value requests, or both. `Deck` has an additional method to validate if a potential connection is legal, which checks for the following in order: 1. the source and target nodes are not the same node; 2. the port usage is valid (trigger to trigger, value to value, both to any); 3. the port types are compatible 4. the connection doesn't already exist all node ports by default use the "both" usage, since that will be the most common use case (especially in cases where an input port can accept either a trigger and a value request but the output can only send one type), but it can be specified as an optional argument in `add_[input|output]_port()` usage types are represented in the renderer by different port icons: ![image](/attachments/28d3cfe9-c62c-4dd4-937d-64dbe87cb205) there is a reference implementation in the Compare Values and Twitch Chat Received nodes, since those were used as examples in #59. other nodes will be added as a separate PR later if this is merged, since behavior will vary greatly per node. Reviewed-on: https://codeberg.org/StreamGraph/StreamGraph/pulls/69 Co-authored-by: Lera ElvoƩ Co-committed-by: Lera ElvoƩ --- classes/deck/deck.gd | 45 +++++++++++++------ classes/deck/deck_node.gd | 33 ++++++++++---- classes/deck/nodes/general/is_equal.gd | 11 +++++ .../deck/nodes/test/test_connection_types.gd | 33 ++++++++++++++ .../deck/nodes/twitch/twitch_chat_received.gd | 13 +++--- classes/deck/port.gd | 10 +++++ .../deck_node_renderer_graph_node.gd | 7 +++ .../deck_renderer_graph_edit.gd | 7 +++ graph_node_renderer/textures/port_any_12.svg | 1 + .../textures/port_any_12.svg.import | 37 +++++++++++++++ .../textures/port_data_request_12.svg | 1 + .../textures/port_data_request_12.svg.import | 37 +++++++++++++++ .../textures/port_trigger_12.svg | 1 + .../textures/port_trigger_12.svg.import | 37 +++++++++++++++ 14 files changed, 246 insertions(+), 27 deletions(-) create mode 100644 classes/deck/nodes/test/test_connection_types.gd create mode 100644 graph_node_renderer/textures/port_any_12.svg create mode 100644 graph_node_renderer/textures/port_any_12.svg.import create mode 100644 graph_node_renderer/textures/port_data_request_12.svg create mode 100644 graph_node_renderer/textures/port_data_request_12.svg.import create mode 100644 graph_node_renderer/textures/port_trigger_12.svg create mode 100644 graph_node_renderer/textures/port_trigger_12.svg.import diff --git a/classes/deck/deck.gd b/classes/deck/deck.gd index 963b9ba..af3a043 100644 --- a/classes/deck/deck.gd +++ b/classes/deck/deck.gd @@ -114,24 +114,42 @@ func get_node(uuid: String) -> DeckNode: return nodes.get(uuid) -## Attempt to connect two nodes. Returns [code]true[/code] if the connection succeeded. -func connect_nodes(from_node_id: String, to_node_id: String, from_output_port: int, to_input_port: int) -> bool: +## Returns [code]true[/code] if the connection between two nodes is legal. +func is_valid_connection(from_node_id: String, to_node_id: String, from_output_port: int, to_input_port: int) -> bool: + # do not connect to self + if from_node_id == to_node_id: + return false + var from_node := get_node(from_node_id) var to_node := get_node(to_node_id) - # check that we can do the type conversion - var type_a: DeckType.Types = from_node.get_output_ports()[from_output_port].type - var type_b: DeckType.Types = to_node.get_input_ports()[to_input_port].type - if !DeckType.can_convert(type_a, type_b): - DeckHolder.logger.log_deck( - "Can not convert from %s to %s." % [DeckType.type_str(type_a), DeckType.type_str(type_b)], - Logger.LogType.ERROR - ) + + var usage_from: Port.UsageType = from_node.get_output_ports()[from_output_port].usage_type + var usage_to: Port.UsageType = to_node.get_input_ports()[to_input_port].usage_type + # incompatible usages + if (usage_from != Port.UsageType.BOTH) && (usage_to != Port.UsageType.BOTH): + if usage_from != usage_to: + return false + + var type_from: DeckType.Types = from_node.get_output_ports()[from_output_port].type + var type_to: DeckType.Types = to_node.get_input_ports()[to_input_port].type + # incompatible types + if !DeckType.can_convert(type_from, type_to): return false - - # refuse duplicate connections + + # duplicate connection if from_node.has_outgoing_connection_exact(from_output_port, to_node_id, to_input_port): - print("no duplicates") return false + + return true + + +## Attempt to connect two nodes. Returns [code]true[/code] if the connection succeeded. +func connect_nodes(from_node_id: String, to_node_id: String, from_output_port: int, to_input_port: int) -> bool: + if !is_valid_connection(from_node_id, to_node_id, from_output_port, to_input_port): + return false + + var from_node := get_node(from_node_id) + var to_node := get_node(to_node_id) if to_node.has_incoming_connection(to_input_port): var connection: Dictionary = to_node.incoming_connections[to_input_port] @@ -139,7 +157,6 @@ func connect_nodes(from_node_id: String, to_node_id: String, from_output_port: i var node_out_port: int = connection.values()[0] disconnect_nodes(node_id, to_node_id, node_out_port, to_input_port) - if is_group && emit_group_signals: nodes_connected_in_group.emit(from_node_id, to_node_id, from_output_port, to_input_port, self) diff --git a/classes/deck/deck_node.gd b/classes/deck/deck_node.gd index 459b12a..0036733 100644 --- a/classes/deck/deck_node.gd +++ b/classes/deck/deck_node.gd @@ -74,23 +74,40 @@ signal renamed(new_name: String) ## Add an input port to this node. Usually only used at initialization. -func add_input_port(type: DeckType.Types, label: String, descriptor: String = "") -> void: - add_port(type, label, PortType.INPUT, get_input_ports().size(), descriptor) +func add_input_port( + type: DeckType.Types, + label: String, + descriptor: String = "", + usage: Port.UsageType = Port.UsageType.BOTH) -> void: + add_port(type, label, PortType.INPUT, get_input_ports().size(), descriptor, usage) ## Add an output port to this node. Usually only used at initialization. -func add_output_port(type: DeckType.Types, label: String, descriptor: String = "") -> void: - add_port(type, label, PortType.OUTPUT, get_output_ports().size(), descriptor) +func add_output_port( + type: DeckType.Types, + label: String, + descriptor: String = "", + usage: Port.UsageType = Port.UsageType.BOTH) -> void: + add_port(type, label, PortType.OUTPUT, get_output_ports().size(), descriptor, usage) ## Add a virtual port to this node. Usually only used at initialization. -func add_virtual_port(type: DeckType.Types, label: String, descriptor: String = "") -> void: - add_port(type, label, PortType.VIRTUAL, get_virtual_ports().size(), descriptor) +func add_virtual_port( + type: DeckType.Types, + label: String, + descriptor: String = "", + usage: Port.UsageType = Port.UsageType.BOTH) -> void: + add_port(type, label, PortType.VIRTUAL, get_virtual_ports().size(), descriptor, usage) ## Add a port to this node. Usually only used at initialization. -func add_port(type: DeckType.Types, label: String, port_type: PortType, index_of_type: int, descriptor: String = "") -> void: - var port := Port.new(type, label, ports.size(), port_type, index_of_type, descriptor) +func add_port(type: DeckType.Types, + label: String, + port_type: PortType, + index_of_type: int, + descriptor: String = "", + usage: Port.UsageType = Port.UsageType.BOTH) -> void: + var port := Port.new(type, label, ports.size(), port_type, index_of_type, descriptor, usage) ports.append(port) port_added.emit(ports.size() - 1) port.value_updated.connect( diff --git a/classes/deck/nodes/general/is_equal.gd b/classes/deck/nodes/general/is_equal.gd index d1169fd..674eb40 100644 --- a/classes/deck/nodes/general/is_equal.gd +++ b/classes/deck/nodes/general/is_equal.gd @@ -32,3 +32,14 @@ func _value_request(on_port: int) -> Variant: var b = await resolve_input_port_value_async(1) return a == b + + +func _receive(on_port: int, data: Variant, extra_data: Array = []) -> void: + if on_port == 0: + var b = await resolve_input_port_value_async(1) + if data == b: + send(0, true) + else: + var b = await resolve_input_port_value_async(0) + if data == b: + send(0, true) diff --git a/classes/deck/nodes/test/test_connection_types.gd b/classes/deck/nodes/test/test_connection_types.gd new file mode 100644 index 0000000..1f075b8 --- /dev/null +++ b/classes/deck/nodes/test/test_connection_types.gd @@ -0,0 +1,33 @@ +# (c) 2023-present Eroax +# (c) 2023-present Yagich +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +extends DeckNode + + +func _init() -> void: + name = "Test Usage" + node_type = name.to_snake_case() + + add_input_port( + DeckType.Types.ANY, "Both", "", Port.UsageType.BOTH + ) + + add_input_port( + DeckType.Types.ANY, "Trigger", "", Port.UsageType.TRIGGER + ) + + add_input_port( + DeckType.Types.ANY, "Value", "", Port.UsageType.VALUE_REQUEST + ) + + add_output_port( + DeckType.Types.ANY, "Both", "", Port.UsageType.BOTH + ) + + add_output_port( + DeckType.Types.ANY, "Trigger", "", Port.UsageType.TRIGGER + ) + + add_output_port( + DeckType.Types.ANY, "Value", "", Port.UsageType.VALUE_REQUEST + ) diff --git a/classes/deck/nodes/twitch/twitch_chat_received.gd b/classes/deck/nodes/twitch/twitch_chat_received.gd index cc38e6b..1a693ac 100644 --- a/classes/deck/nodes/twitch/twitch_chat_received.gd +++ b/classes/deck/nodes/twitch/twitch_chat_received.gd @@ -14,10 +14,10 @@ func _init(): node_type = "twitch_chat_received" description = "Receives Twitch chat events from a Twitch connection." - add_output_port(DeckType.Types.STRING, "Username") - add_output_port(DeckType.Types.STRING, "Message") - add_output_port(DeckType.Types.STRING, "Channel") - add_output_port(DeckType.Types.DICTIONARY, "Tags") + add_output_port(DeckType.Types.STRING, "Username", "", Port.UsageType.TRIGGER) + add_output_port(DeckType.Types.STRING, "Message", "", Port.UsageType.TRIGGER) + add_output_port(DeckType.Types.STRING, "Channel", "", Port.UsageType.TRIGGER) + add_output_port(DeckType.Types.DICTIONARY, "Tags", "", Port.UsageType.TRIGGER) add_output_port( DeckType.Types.BOOL, @@ -37,7 +37,10 @@ func _event_received(event_name : StringName, event_data : Dictionary = {}): message = event_data.message channel = event_data.channel tags = event_data - + send(0, username) + send(1, message) + send(2, channel) + send(3, tags) send(4, true) diff --git a/classes/deck/port.gd b/classes/deck/port.gd index 110fcbc..3b4ea53 100644 --- a/classes/deck/port.gd +++ b/classes/deck/port.gd @@ -7,6 +7,12 @@ class_name Port ## Ports are used for connections between [DeckNode]s and can contain data that is passed between ## them on a node. +enum UsageType { + TRIGGER, ## Port can send or receive events, not request values. + VALUE_REQUEST, ## Port can request values and respond to value requests, not send or receive events. + BOTH, ## Port can send or receive events [b]and[/b] request and respond to value requests. +} + ## The type index of this port. var type: DeckType.Types ## The label of this port. Used by the renderer to display. How it's displayed depends on the renderer @@ -22,6 +28,8 @@ var value_callback: Callable ## The type of this port (input, output or virtual) var port_type: DeckNode.PortType +## The usage type of this port (see [enum UsageType]). +var usage_type: UsageType ## The local index of this port. var index_of_type: int ## The global index of this port. @@ -41,6 +49,7 @@ func _init( p_index_of_type: int, p_descriptor: String = "", # p_value_callback: Callable = Callable(), + p_usage_type: UsageType = UsageType.BOTH, ) -> void: type = p_type label = p_label @@ -50,6 +59,7 @@ func _init( port_type = p_port_type index_of_type = p_index_of_type index = p_index + usage_type = p_usage_type func set_value(v: Variant) -> void: diff --git a/graph_node_renderer/deck_node_renderer_graph_node.gd b/graph_node_renderer/deck_node_renderer_graph_node.gd index 377c594..d5131f6 100644 --- a/graph_node_renderer/deck_node_renderer_graph_node.gd +++ b/graph_node_renderer/deck_node_renderer_graph_node.gd @@ -19,6 +19,11 @@ const TYPE_COLORS := { DeckType.Types.ANY: Color.WHITE, } +const PORT_USAGE_ICONS := { + Port.UsageType.TRIGGER: preload("res://graph_node_renderer/textures/port_trigger_12.svg"), + Port.UsageType.VALUE_REQUEST: preload("res://graph_node_renderer/textures/port_data_request_12.svg"), + Port.UsageType.BOTH: preload("res://graph_node_renderer/textures/port_any_12.svg"), +} ## Setups up all the properties based off [member node]. Including looping through ## [method DeckNode.get_all_ports()] and setting up all the descriptors. @@ -204,4 +209,6 @@ func update_port(port: Port) -> void: port.port_type == DeckNode.PortType.OUTPUT, port.type, TYPE_COLORS[port.type], + PORT_USAGE_ICONS[port.usage_type], + PORT_USAGE_ICONS[port.usage_type], ) diff --git a/graph_node_renderer/deck_renderer_graph_edit.gd b/graph_node_renderer/deck_renderer_graph_edit.gd index c781cc2..02095d3 100644 --- a/graph_node_renderer/deck_renderer_graph_edit.gd +++ b/graph_node_renderer/deck_renderer_graph_edit.gd @@ -87,6 +87,13 @@ func attempt_connection(from_node_name: StringName, from_port: int, to_node_name ) dirty = true + +func _is_node_hover_valid(from_node: StringName, from_port: int, to_node: StringName, to_port: int) -> bool: + var from_node_renderer: DeckNodeRendererGraphNode = get_node(NodePath(from_node)) + var to_node_renderer: DeckNodeRendererGraphNode = get_node(NodePath(to_node)) + + return deck.is_valid_connection(from_node_renderer.node._id, to_node_renderer.node._id, from_port, to_port) + ## Receives [signal GraphEdit.disconnection_request] and attempts to disconnect the two [DeckNode]s ## involved, utilizes [NodeDB] for accessing them. func attempt_disconnect(from_node_name: StringName, from_port: int, to_node_name: StringName, to_port: int) -> void: diff --git a/graph_node_renderer/textures/port_any_12.svg b/graph_node_renderer/textures/port_any_12.svg new file mode 100644 index 0000000..cce736c --- /dev/null +++ b/graph_node_renderer/textures/port_any_12.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/graph_node_renderer/textures/port_any_12.svg.import b/graph_node_renderer/textures/port_any_12.svg.import new file mode 100644 index 0000000..74bdff6 --- /dev/null +++ b/graph_node_renderer/textures/port_any_12.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c0qh34njvu4xt" +path="res://.godot/imported/port_any_12.svg-64aca00b3bf28084e8a38d718780e0ff.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://graph_node_renderer/textures/port_any_12.svg" +dest_files=["res://.godot/imported/port_any_12.svg-64aca00b3bf28084e8a38d718780e0ff.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/graph_node_renderer/textures/port_data_request_12.svg b/graph_node_renderer/textures/port_data_request_12.svg new file mode 100644 index 0000000..e73eaab --- /dev/null +++ b/graph_node_renderer/textures/port_data_request_12.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/graph_node_renderer/textures/port_data_request_12.svg.import b/graph_node_renderer/textures/port_data_request_12.svg.import new file mode 100644 index 0000000..ebefa7d --- /dev/null +++ b/graph_node_renderer/textures/port_data_request_12.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://b5houge8j0lij" +path="res://.godot/imported/port_data_request_12.svg-da181e9908b91f72dfc70f3dd7196720.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://graph_node_renderer/textures/port_data_request_12.svg" +dest_files=["res://.godot/imported/port_data_request_12.svg-da181e9908b91f72dfc70f3dd7196720.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/graph_node_renderer/textures/port_trigger_12.svg b/graph_node_renderer/textures/port_trigger_12.svg new file mode 100644 index 0000000..02cc890 --- /dev/null +++ b/graph_node_renderer/textures/port_trigger_12.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/graph_node_renderer/textures/port_trigger_12.svg.import b/graph_node_renderer/textures/port_trigger_12.svg.import new file mode 100644 index 0000000..be616c0 --- /dev/null +++ b/graph_node_renderer/textures/port_trigger_12.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://tbxgx46ch210" +path="res://.godot/imported/port_trigger_12.svg-ddfb2ada8bfb56f098470a2797045ba0.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://graph_node_renderer/textures/port_trigger_12.svg" +dest_files=["res://.godot/imported/port_trigger_12.svg-ddfb2ada8bfb56f098470a2797045ba0.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false