class_name Deck ## A deck/graph with nodes. ## ## A container for [DeckNode]s, managing connections between them. ## The [DeckNode]s that belong to this deck. The key is the node's id, the value ## is the [DeckNode] instance. var nodes: Dictionary ## The list of [DeckType] for easy port creation. enum Types{ ERROR = -1, BOOL, NUMERIC, STRING, ARRAY, DICTIONARY, } ## A dictionary mapping [enum Types] to [DeckType] subclasses. static var type_assoc: Dictionary = { Types.ERROR: DeckType.DeckTypeError, Types.BOOL: DeckType.DeckTypeBool, Types.NUMERIC: DeckType.DeckTypeNumeric, Types.STRING: DeckType.DeckTypeString, Types.ARRAY: DeckType.DeckTypeArray, Types.DICTIONARY: DeckType.DeckTypeDictionary, } ## A map of variables set on this deck. var variable_stack: Dictionary = {} ## The path to save this deck on the file system. var save_path: String = "" var is_group: bool = false ## List of groups belonging to this deck, in the format of[br] ## [code]Dictionary[String -> Deck.id, Deck][/code] var groups: Dictionary = {} ## A unique identifier for this deck. var id: String = "" ## The parent deck of this deck, if this is a group. var _belonging_to: Deck # for groups ## The ID of this group's input node. Used only if [member is_group] is [code]true[/code]. var group_input_node: String ## The ID of this group's input node. Used only if [member is_group] is [code]true[/code]. var group_output_node: String ## The ID of the group node this group is represented by, contained in this deck's parent deck. ## Used only if [member is_group] is [code]true[/code]. ## @experimental var group_node: String ## Emitted when a node has been added to this deck. signal node_added(node: DeckNode) ## Emitted when a node has been removed from this deck. signal node_removed(node: DeckNode) ## Instantiate a node by its' [member DeckNode.node_type] and add it to this deck.[br] ## See [method add_node_inst] for parameter descriptions. func add_node_type(type: String, assign_id: String = "", assign_to_self: bool = true) -> DeckNode: var node_inst: DeckNode = NodeDB.instance_node(type) return add_node_inst(node_inst, assign_id, assign_to_self) ## Add a [DeckNode] instance to this deck.[br] ## If [param assign_id] is empty, the node will get its' ID (re-)assigned. ## Otherwise, it will be assigned to be that value.[br] ## If [param assign_to_self] is [code]true[/code], the node's ## [member DeckNode._belonging_to] property will be set to [code]self[/code]. func add_node_inst(node: DeckNode, assign_id: String = "", assign_to_self: bool = true) -> DeckNode: if assign_to_self: node._belonging_to = self if assign_id == "": var uuid := UUID.v4() nodes[uuid] = node node._id = uuid else: nodes[assign_id] = node node_added.emit(node) return node ## Get a node belonging to this deck by its' ID. 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: DeckNode, to_node: DeckNode, from_output_port: int, to_input_port: int) -> bool: # first, check that we can do the type conversion. var type_a: Types = from_node.get_output_ports()[from_output_port].type var type_b: Types = to_node.get_input_ports()[to_input_port].type var err: DeckType = (type_assoc[type_b]).from(type_assoc[type_a].new()) if err is DeckType.DeckTypeError: print(err.error_message) return false # TODO: prevent duplicate connections from_node.add_outgoing_connection(from_output_port, to_node._id, to_input_port) return true ## Remove a connection from two nodes. func disconnect_nodes(from_node: DeckNode, to_node: DeckNode, from_output_port: int, to_input_port: int) -> void: var hash = {to_node._id: to_input_port}.hash() from_node.remove_outgoing_connection(from_output_port, hash) to_node.remove_incoming_connection(to_input_port) ## Returns true if this deck has no nodes and no variables. func is_empty() -> bool: return nodes.is_empty() && variable_stack.is_empty() ## Remove a node from this deck. func remove_node(uuid: String) -> void: var node = nodes.get(uuid) nodes.erase(uuid) node_removed.emit(node) ## Group the [param nodes_to_group] into a new deck and return it. ## Returns [code]null[/code] on failure.[br] ## Adds a group node to this deck, and adds group input and output nodes in the group. func group_nodes(nodes_to_group: Array) -> Deck: if nodes_to_group.is_empty(): return null var node_ids_to_keep := nodes_to_group.map( func(x: DeckNode): return x._id ) var group := Deck.new() group.is_group = true group._belonging_to = self var group_id := UUID.v4() group.id = group_id var midpoint := Vector2() for node: DeckNode in nodes_to_group: if node.node_type == "group_node": # for recursive grouping var _group_id: String = node.group_id var _group: Deck = groups[_group_id] groups.erase(_group) group.groups[_group_id] = _group _group._belonging_to = group for from_port: int in node.outgoing_connections: for connection: Dictionary in node.outgoing_connections[from_port]: if !(connection.keys()[0] in node_ids_to_keep): disconnect_nodes(node, get_node(connection.keys()[0]), from_port, connection.values()[0]) midpoint += node.position_as_vector2() remove_node(node._id) group.add_node_inst(node, node._id) midpoint /= nodes_to_group.size() var _group_node := add_node_type("group_node") _group_node.group_id = group_id _group_node.position.x = midpoint.x _group_node.position.y = midpoint.y _group_node.position_updated.emit(_group_node.position) group.group_node = _group_node._id var input_node := group.add_node_type("group_input") var output_node := group.add_node_type("group_output") group.group_input_node = input_node._id group.group_output_node = output_node._id _group_node.input_node = input_node _group_node.output_node = output_node _group_node.setup_connections() groups[group_id] = group return group ## Get a group belonging to this deck by its ID. func get_group(uuid: String) -> Deck: return groups.get(uuid) ## Returns a [Dictionary] representation of this deck. func to_dict(with_meta: bool = true) -> Dictionary: var inner := { "nodes": {}, "variable_stack": variable_stack, "id": id, "groups": {} } for node_id in nodes.keys(): inner["nodes"][node_id] = nodes[node_id].to_dict(with_meta) for group_id in groups.keys(): inner["groups"][group_id] = groups[group_id].to_dict(with_meta) if is_group: inner["group_node"] = group_node inner["group_input_node"] = group_input_node inner["group_output_node"] = group_output_node var d := {"deck": inner} if with_meta: d["meta"] = {} for meta in get_meta_list(): d["meta"][meta] = var_to_str(get_meta(meta)) return d ## Create a new deck from a [Dictionary] representation, such as one created by [method to_dict]. static func from_dict(data: Dictionary, path: String = "") -> Deck: var deck := Deck.new() deck.save_path = path deck.variable_stack = data.deck.variable_stack deck.id = data.deck.id for key in data.meta: deck.set_meta(key, str_to_var(data.meta[key])) var nodes_data: Dictionary = data.deck.nodes as Dictionary for node_id in nodes_data: var node := deck.add_node_type(nodes_data[node_id].node_type, node_id, false) node._id = node_id node.name = nodes_data[node_id].name node._belonging_to = deck node.position = nodes_data[node_id].position for prop in nodes_data[node_id].props: node.set(prop, nodes_data[node_id].props[prop]) node._pre_connection() for connection_id in nodes_data[node_id].outgoing_connections: var connection_data = nodes_data[node_id].outgoing_connections[connection_id] for connection in connection_data: connection[connection.keys()[0]] = int(connection.values()[0]) node.outgoing_connections[int(connection_id)] = connection_data for connection_id in nodes_data[node_id].incoming_connections: var connection_data = nodes_data[node_id].incoming_connections[connection_id] for connection in connection_data: connection_data[connection] = int(connection_data[connection]) node.incoming_connections[int(connection_id)] = connection_data for i in node.ports.size(): var port_value: Variant if (nodes_data[node_id].port_values as Array).size() <= i: port_value = null else: port_value = (nodes_data[node_id].port_values as Array)[i] node.ports[i].value = port_value for key in nodes_data[node_id].meta: node.set_meta(key, str_to_var(nodes_data[node_id].meta[key])) node._post_load() var groups_data: Dictionary = data.deck.groups as Dictionary for group_id: String in groups_data: var group := Deck.from_dict(groups_data[group_id]) group._belonging_to = deck group.is_group = true deck.groups[group_id] = group group.group_node = groups_data[group_id]["deck"]["group_node"] group.group_input_node = groups_data[group_id]["deck"]["group_input_node"] group.group_output_node = groups_data[group_id]["deck"]["group_output_node"] deck.get_node(group.group_node).init_io() return deck