miggor-StreamGraph/classes/deck/deck_node.gd
Lera Elvoé f57374f3e2 add send id system (#75)
addresses https://codeberg.org/StreamGraph/StreamGraph/issues/59#issuecomment-1572604

Reviewed-on: https://codeberg.org/StreamGraph/StreamGraph/pulls/75
Co-authored-by: Lera Elvoé <yagich@poto.cafe>
Co-committed-by: Lera Elvoé <yagich@poto.cafe>
2024-02-22 07:23:25 +00:00

413 lines
13 KiB
GDScript

# (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)
class_name DeckNode
## A node in a [Deck].
##
## Nodes are the essential building block of a [Deck] graph. They can have connections between ports
## and send data through them.
## The name initially shown to a renderer.
var name: String
## A map of outgoing connections from this node, in the format[br]
## [code]Dictionary[int -> output port, Dictionary[String -> DeckNode#_id, Array[int -> input port]]][/code]
var outgoing_connections: Dictionary
## A map of incoming connections to this node, in the format[br]
## [code]Dictionary[int -> input port, [Dictionary[String -> DeckNode#_id, int -> output port]][/code]
var incoming_connections: Dictionary
## A list of [Port]s on this node.
var ports: Array[Port]
var _last_send_id: String
## The deck this node belongs to.
var _belonging_to: Deck
## A unique identifier for this node.
var _id: String
## The type of this node, used for instantiation.
var node_type: String
## The description of this node, shown to the user by a renderer.
var description: String
## A list of aliases for this node, used by search.
var aliases: Array[String]
## Controls whether this node should appear in [SearchProvider].
var appears_in_search: bool = true
## A list of additional properties to save when this node is saved.
var props_to_serialize: Array[StringName]
## The position of this node relative to the parent graph.
## Only used by renderers.
var position: Dictionary = {"x": 0.0, "y": 0.0}
## If [code]true[/code], the user can delete this node by normal means.
## The parent [Deck] can still delete the node by other means.
var user_can_delete: bool = true
enum PortType{
INPUT, ## Input port type (slot on the left).
OUTPUT, ## Output port type (slot on the right).
VIRTUAL, ## Virtual port type (no slot on left [i]or[/i] right).
}
## Emitted when this node has been moved.
signal position_updated(new_position: Dictionary)
## Emitted when a port has been added.
signal port_added(port: int)
## Emitted when a port has been removed.
signal port_removed(port: int)
## Emitted when a port or multiple ports have been updated (added or removed).
signal ports_updated()
## Emitted when a connection from this node has been added.
signal outgoing_connection_added(from_port: int)
## Emitted when a connection from this node has been removed.
signal outgoing_connection_removed(from_port: int)
## Emitted when a connection to this node has been added.
signal incoming_connection_added(from_port: int)
## Emitted when a connection to this node has been removed.
signal incoming_connection_removed(from_port: int)
signal port_value_updated(port_idx: int, new_value: Variant)
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 = "",
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 = "",
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 = "",
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 = "",
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(
func(new_value: Variant) -> void:
port_value_updated.emit(port.index, new_value)
)
ports_updated.emit()
## Remove a port from this node.
## @deprecated
func remove_port(port_idx: int) -> void:
outgoing_connections.erase(port_idx)
incoming_connections.erase(port_idx)
ports.remove_at(port_idx)
port_removed.emit(port_idx)
## Send data to all outgoing connections on port [param from_output_port].
func send(from_output_port: int, data: Variant, send_id: String = UUID.v4()) -> void:
if outgoing_connections.get(from_output_port) == null:
return
for node: String in outgoing_connections[from_output_port]:
for input_port: int in outgoing_connections[from_output_port][node]:
get_node(node).handle_receive(input_port, data, send_id)
func handle_receive(to_input_port: int, data: Variant, send_id: String) -> void:
if send_id == _last_send_id:
return
_last_send_id = send_id
_receive(to_input_port, data)
## Virtual function that's called when this node receives data from another node's [method send] call.
@warning_ignore("unused_parameter")
func _receive(to_input_port: int, data: Variant) -> void:
pass
## Add a connection from the output port at [param from_port] to [param to_node]'s input port
## at [param to_port].
func add_outgoing_connection(from_port: int, to_node: String, to_port: int) -> void:
var inner: Dictionary = outgoing_connections.get(from_port, {}) as Dictionary
var inner_ports: Array = inner.get(to_node, []) as Array
inner_ports.append(to_port)
inner[to_node] = inner_ports
outgoing_connections[from_port] = inner
get_node(to_node).add_incoming_connection(to_port, _id, from_port)
outgoing_connection_added.emit(from_port)
## Add an incoming connection from [param from_node]'s output port at [param from_port] to this node's
## input port at [param to_port].
func add_incoming_connection(to_port: int, from_node: String, from_port: int) -> void:
var connection := {from_node: from_port}
incoming_connections[to_port] = connection
incoming_connection_added.emit(to_port)
## Asynchronously request a value from an incoming connection on this node's input port at [param on_port].
## Returns [code]null[/code] if no incoming connection exists on that port.
## The connected node may also return [code]null[/code].
func request_value_async(on_port: int) -> Variant:
if not incoming_connections.has(on_port):
return null
var connection: Dictionary = incoming_connections[on_port]
var node := get_node(connection.keys()[0])
return await node._value_request(connection.values()[0])
## Virtual function that's called when this node has been requested a value from the output port
## at [param from_port].
@warning_ignore("unused_parameter")
func _value_request(from_port: int) -> Variant:
return null
## Remove an outgoing connection from this node.
## Does [b]not[/b] remove the other node's incoming connection equivalent.
func remove_outgoing_connection(from_port: int, to_node: String, to_port: int) -> void:
var connections: Dictionary = outgoing_connections.get(from_port, {}) as Dictionary
if connections.is_empty():
return
var inner_ports: Array = connections.get(to_node, []) as Array
inner_ports.erase(to_port)
if inner_ports.is_empty():
(outgoing_connections[from_port] as Dictionary).erase(to_node)
if (outgoing_connections[from_port] as Dictionary).is_empty():
outgoing_connections.erase(from_port)
outgoing_connection_removed.emit(from_port)
## Remove an incoming connection to this node on the input port at [param to_port].
## Does [b]not[/b] remove the other node's outgoing connection equivalent.
func remove_incoming_connection(to_port: int) -> void:
incoming_connections.erase(to_port)
incoming_connection_removed.emit(to_port)
## Returns [code]true[/code] if the exact connection from this node exists.
func has_outgoing_connection_exact(from_port: int, to_node: String, to_port: int) -> bool:
if not outgoing_connections.has(from_port):
return false
var connection: Dictionary = outgoing_connections[from_port]
if not connection.has(to_node):
return false
var connections_to_node: Array = connection[to_node]
if to_port not in connections_to_node:
return false
return true
## Returns [code]true[/code] if the node has an incoming connection at input port [param on_port].
func has_incoming_connection(on_port: int) -> bool:
return incoming_connections.has(on_port)
func rename(new_name: String) -> void:
name = new_name
renamed.emit(new_name)
@warning_ignore("unused_parameter")
func _event_received(event_name: StringName, event_data: Dictionary = {}) -> void:
pass
## Returns a list of all input ports.
func get_input_ports() -> Array[Port]:
return ports.filter(
func(port: Port) -> bool:
return port.port_type == PortType.INPUT
)
## Returns a list of all output ports.
func get_output_ports() -> Array[Port]:
return ports.filter(
func(port: Port) -> bool:
return port.port_type == PortType.OUTPUT
)
## Returns a list of all virtual ports.
func get_virtual_ports() -> Array[Port]:
return ports.filter(
func(port: Port) -> bool:
return port.port_type == PortType.VIRTUAL
)
## Returns the global port index from the input port index at [param idx].
func get_global_port_idx_from_input(idx: int) -> int:
if get_input_ports().size() > idx:
return get_input_ports()[idx].index
else:
return -1
## Returns the global port index from the output port index at [param idx].
func get_global_port_idx_from_output(idx: int) -> int:
if get_output_ports().size() > idx:
return get_output_ports()[idx].index
else:
return -1
## Returns the global port index from the virtual port index at [param idx].
func get_global_port_idx_from_virtual(idx: int) -> int:
if get_virtual_ports().size() > idx:
return get_virtual_ports()[idx].index
else:
return -1
## Get a port's local index from the global port index at [param idx].
func get_port_type_idx_from_global(idx: int) -> int:
return ports[idx].index_of_type
## Returns the list of all ports.
func get_all_ports() -> Array[Port]:
return ports
## Get a sibling node by its' ID.
func get_node(uuid: String) -> DeckNode:
return _belonging_to.get_node(uuid)
## Virtual function that's called during deserialization before connections are loaded in.
func _pre_connection() -> void:
pass
## Virtual function that's called after the node has been deserialized.
func _post_load() -> void:
pass
## A helper function to get a value on an input port. Returns the best match in the following
## order of priority:[br]
## 1. The direct result of [method request value], called asynchronously. [br]
## 2. The result of [method Port.value_callback], if it's not equal to [param empty_value]. [br]
## 3. The input [Port] at index [param input_port]'s stored [member Port.value], if it's not equal to [param empty_value]. [br]
## 4. [code]null[/code].[br]
func resolve_input_port_value_async(input_port: int, empty_value: Variant = null) -> Variant:
var request = await request_value_async(input_port)
if request != null:
return request
elif get_input_ports()[input_port].value_callback.get_object() and get_input_ports()[input_port].value_callback.call() != empty_value:
return get_input_ports()[input_port].value_callback.call()
elif get_input_ports()[input_port].value != empty_value:
return get_input_ports()[input_port].value
else:
return null
## Returns a [Dictionary] representation of this node.
func to_dict(with_meta: bool = true) -> Dictionary:
var d := {
"_id": _id,
"name": name,
"outgoing_connections": outgoing_connections.duplicate(true),
"incoming_connections": incoming_connections.duplicate(true),
"props": {},
"node_type": node_type,
"port_values": [],
"position": position.duplicate(),
}
for prop in props_to_serialize:
d.props[prop] = get(prop)
ports.map(
func(port: Port) -> void:
d.port_values.append(port.value)
)
if with_meta:
d["meta"] = {}
for meta in get_meta_list():
d["meta"][meta] = var_to_str(get_meta(meta))
return d
static func from_dict(data: Dictionary) -> DeckNode:
var node := NodeDB.instance_node(data.node_type)
#node._id = data._id
node.name = data.name
node.position = data.position
for prop in data.props:
node.set(prop, data.props[prop])
node._pre_connection()
for from_port in data.outgoing_connections:
var connection_data: Dictionary = data.outgoing_connections[from_port]
node.outgoing_connections[int(from_port)] = {}
for to_node in connection_data:
var input_ports: Array = connection_data[to_node]
node.outgoing_connections[int(from_port)][to_node] = []
for to_input_port in input_ports:
node.outgoing_connections[int(from_port)][to_node].append(int(to_input_port))
for to_port in data.incoming_connections:
var connection_data = data.incoming_connections[to_port]
for connection in connection_data:
connection_data[connection] = int(connection_data[connection])
node.incoming_connections[int(to_port)] = connection_data
for i in node.ports.size():
var port_value: Variant
if (data.port_values as Array).size() <= i:
port_value = null
else:
port_value = (data.port_values as Array)[i]
node.ports[i].value = port_value
for key in data.meta:
node.set_meta(key, str_to_var(data.meta[key]))
node._post_load()
return node
## Returns the node's [member position] as a [Vector2].
func position_as_vector2() -> Vector2:
return Vector2(position.x, position.y)