mirror of
https://codeberg.org/StreamGraph/StreamGraph.git
synced 2024-11-13 19:49:55 +01:00
c7c2e0ca6b
For some stuff an async version of this function was needed. This is *probably* going to be removed when we move most of the stuff to being async. But for this branch at least this allows some nodes to work.
404 lines
13 KiB
GDScript
404 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]
|
|
|
|
## 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 = "") -> 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)
|
|
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, extra_data: Array = []) -> 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)._receive(input_port, data, extra_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, 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 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)
|
|
|
|
|
|
## 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])
|
|
|
|
|
|
## Asynchronous version of [method request_value].
|
|
func request_value_async(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 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 !outgoing_connections.has(from_port):
|
|
return false
|
|
|
|
var connection: Dictionary = outgoing_connections[from_port]
|
|
if !connection.has(to_node):
|
|
return false
|
|
|
|
var connections_to_node: Array = connection[to_node]
|
|
if !(to_port 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]. [br]
|
|
## 2. The result of [method Port.value_callback]. [br]
|
|
## 3. The input [Port] at index [param input_port]'s stored [member Port.value]. [br]
|
|
## 4. [code]null[/code].
|
|
func resolve_input_port_value(input_port: int) -> Variant:
|
|
if request_value(input_port) != null:
|
|
return request_value(input_port)
|
|
elif get_input_ports()[input_port].value_callback.get_object() && get_input_ports()[input_port].value_callback.call() != null:
|
|
return get_input_ports()[input_port].value_callback.call()
|
|
elif get_input_ports()[input_port].value != null:
|
|
return get_input_ports()[input_port].value
|
|
else:
|
|
return null
|
|
|
|
func resolve_input_port_value_async(input_port: int) -> Variant:
|
|
if await request_value_async(input_port) != null:
|
|
return await request_value_async(input_port)
|
|
elif get_input_ports()[input_port].value_callback.get_object() && get_input_ports()[input_port].value_callback.call() != null:
|
|
return get_input_ports()[input_port].value_callback.call()
|
|
elif get_input_ports()[input_port].value != null:
|
|
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)
|