miggor-StreamGraph/classes/deck/deck_node.gd
Lera Elvoé c4e35043df types system simplification (#8)
no longer using classes for every type. the type system has been greatly simplified, with the added bonus that it hooks directly into GraphEdit's slot type system. connections will still fail if the type conversion fails, which may be used by other renderers.

the type conversion map is straightforward to understand, and easy to extend should the need arise (hopefully it shouldn't).

Reviewed-on: https://codeberg.org/Eroax/Re-DotDeck/pulls/8
Co-authored-by: Lera Elvoé <yagich@poto.cafe>
Co-committed-by: Lera Elvoé <yagich@poto.cafe>
2023-11-26 22:07:15 +00:00

287 lines
9.3 KiB
GDScript

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, Array[Dictionary[String -> DeckNode#_id, 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]
## 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]
## The category of this node. Must be snake_case. This is additional data which
## a renderer can optionally show to the user.
var category: 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}
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)
## 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)
## 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)
## 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)
## 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)
ports.append(port)
port_added.emit(ports.size() - 1)
ports_updated.emit()
## Remove a port from this node.
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, extra_data: Array = []) -> void:
if outgoing_connections.get(from_output_port) == null:
return
for connection in outgoing_connections[from_output_port]:
connection = connection as Dictionary
# key is node uuid
# value is input port on destination node
for node in connection:
get_node(node)._receive(connection[node], data, extra_data)
## Virtual function that's called when this node receives data from another node's [method send] call.
func _receive(to_input_port: int, data: Variant, extra_data: Array = []) -> 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 port_connections: Array = outgoing_connections.get(from_port, [])
port_connections.append({to_node: to_port})
outgoing_connections[from_port] = port_connections
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)
## 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(on_port: int) -> Variant:
if !incoming_connections.has(on_port):
return null
var connection: Dictionary = incoming_connections[on_port]
var node := get_node(connection.keys()[0])
return 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].
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, connection_hash: int) -> void:
var port_connections: Array = (outgoing_connections.get(from_port, []) as Array).duplicate(true)
if port_connections.is_empty():
return
var incoming_connection := {}
var to_remove: int = -1
for i in port_connections.size():
if port_connections[i].hash() == connection_hash:
to_remove = i
if to_remove == -1:
print('nothing to remove')
return
port_connections.remove_at(to_remove)
outgoing_connections[from_port] = port_connections
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 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
## 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,
"incoming_connections": incoming_connections,
"props": {},
"node_type": node_type,
"port_values": [],
"position": position,
}
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
## Returns the node's [member position] as a [Vector2].
func position_as_vector2() -> Vector2:
return Vector2(position.x, position.y)