# (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 Object 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 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 ## The instance ID of the group this node belongs to, if it belongs to a group. #var _belonging_to_instance: String ## 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) ## Emitted when the node is about to be freed. signal about_to_free() signal port_value_updated(port_idx: int, new_value: Variant) signal renamed(new_name: String) func connect_rpc_signals() -> void: Util.safe_connect(port_added, RPCSignalLayer._on_node_port_added.bind(self)) Util.safe_connect(ports_updated, RPCSignalLayer._on_node_ports_updated.bind(self)) Util.safe_connect(port_value_updated, RPCSignalLayer._on_node_port_value_updated.bind(self)) Util.safe_connect(renamed, RPCSignalLayer._on_node_renamed.bind(self)) ## 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) -> Port: return 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) -> Port: return 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) -> Port: return 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) -> Port: 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() return port ## 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: _belonging_to.send(_id, from_output_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 ## 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: return await _belonging_to.request_value_async(_id, on_port) ## 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 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 func press_button(port_idx: int) -> void: var port := ports[port_idx] if port.descriptor.split(":")[0] == "button": port.button_pressed.emit() ## Get a sibling node by its' ID. func get_node(uuid: String) -> DeckNode: return _belonging_to.get_node(uuid) ## Get the deck this node belongs to. #func get_deck() -> Deck: #if not DeckHolder.get_deck(_belonging_to).is_group: #return DeckHolder.get_deck(_belonging_to) #else: #return DeckHolder.get_group_instance(_belonging_to, _belonging_to_instance) ## Virtual function that's called during deserialization before connections are loaded in.[br] func _pre_port_load() -> void: pass ## Virtual function that's called after the node has been deserialized. @warning_ignore("unused_parameter") func _post_load(connections: Deck.NodeConnections) -> 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, connections: Deck.NodeConnections = null) -> 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_port_load() #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(connections) return node ## Returns the node's [member position] as a [Vector2]. func position_as_vector2() -> Vector2: return Vector2(position.x, position.y) func _notification(what: int) -> void: if what == NOTIFICATION_PREDELETE: about_to_free.emit()