diff --git a/classes/deck/deck.gd b/classes/deck/deck.gd index d6fd777..e802616 100644 --- a/classes/deck/deck.gd +++ b/classes/deck/deck.gd @@ -16,6 +16,9 @@ var variable_stack: Dictionary = {} ## The path to save this deck on the file system. var save_path: String = "" +## The connections graph. +var connections := NodeConnections.new() + var is_group: bool = false ## List of groups belonging to this deck, in the format of[br] ## [code]Dictionary[String -> Deck.id, Deck][/code] @@ -25,6 +28,7 @@ var id: String = "" ## If this is a group, this is the local ID of this instance of the group. var instance_id: String = "" ## The parent deck of this deck, if this is a group. +@warning_ignore("unused_private_class_variable") var _belonging_to: String = "" # 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 @@ -52,8 +56,6 @@ signal variables_updated() #region group signals signal node_added_to_group(node: DeckNode, assign_id: String, assign_to_self: bool, deck: Deck) signal node_removed_from_group(node_id: String, remove_connections: bool, deck: Deck) -signal nodes_connected_in_group(from_node_id: String, to_node_id: String, from_output_port: int, to_input_port: int, deck: Deck) -signal nodes_disconnected_in_group(from_node_id: String, to_node_id: String, from_output_port: int, to_input_port: int, deck: Deck) signal node_port_value_updated(node_id: String, port_idx: int, new_value: Variant, deck: Deck) signal node_renamed(node_id: String, new_name: String, deck: Deck) signal node_moved(node_id: String, new_position: Dictionary, deck: Deck) @@ -129,6 +131,10 @@ func get_node(uuid: String) -> DeckNode: return nodes.get(uuid) +func get_connections_dict() -> Dictionary: + return connections.data + + ## Returns [code]true[/code] if the connection between two nodes is legal. func is_valid_connection(from_node_id: String, to_node_id: String, from_output_port: int, to_input_port: int) -> bool: # do not connect to self @@ -152,7 +158,7 @@ func is_valid_connection(from_node_id: String, to_node_id: String, from_output_p return false # duplicate connection - if from_node.has_outgoing_connection_exact(from_output_port, to_node_id, to_input_port): + if connections.has_outgoing_connection_exact(from_node_id, from_output_port, to_node_id, to_input_port): return false return true @@ -162,34 +168,30 @@ func is_valid_connection(from_node_id: String, to_node_id: String, from_output_p func connect_nodes(from_node_id: String, to_node_id: String, from_output_port: int, to_input_port: int) -> bool: if not is_valid_connection(from_node_id, to_node_id, from_output_port, to_input_port): return false - + var from_node := get_node(from_node_id) var to_node := get_node(to_node_id) - if to_node.has_incoming_connection(to_input_port): - var connection: Dictionary = to_node.incoming_connections[to_input_port] - var node_id: String = connection.keys()[0] - var node_out_port: int = connection.values()[0] - disconnect_nodes(node_id, to_node_id, node_out_port, to_input_port) + if connections.has_incoming_connection(to_node_id, to_input_port): + var connection := connections.get_incoming_connection(to_node_id, to_input_port) + disconnect_nodes(connection.from_node, to_node_id, connection.from_port, to_input_port) - if is_group and emit_group_signals: - nodes_connected_in_group.emit(from_node_id, to_node_id, from_output_port, to_input_port, self) - - from_node.add_outgoing_connection(from_output_port, to_node._id, to_input_port) + connections.add_connection(from_node_id, to_node_id, from_output_port, to_input_port) nodes_connected.emit(from_node_id, to_node_id, from_output_port, to_input_port) + from_node.outgoing_connection_added.emit(from_output_port) + to_node.incoming_connection_added.emit(to_input_port) return true ## Remove a connection from two nodes. func disconnect_nodes(from_node_id: String, to_node_id: String, from_output_port: int, to_input_port: int) -> void: + connections.remove_connection(from_node_id, to_node_id, from_output_port, to_input_port) + nodes_disconnected.emit(from_node_id, to_node_id, from_output_port, to_input_port) + var from_node := get_node(from_node_id) var to_node := get_node(to_node_id) - from_node.remove_outgoing_connection(from_output_port, to_node_id, to_input_port) - to_node.remove_incoming_connection(to_input_port) - if is_group and emit_group_signals: - nodes_disconnected_in_group.emit(from_node_id, to_node_id, from_output_port, to_input_port, self) - - nodes_disconnected.emit(from_node_id, to_node_id, from_output_port, to_input_port) + from_node.outgoing_connection_removed.emit(from_output_port) + to_node.incoming_connection_removed.emit(to_input_port) ## Returns true if this deck has no nodes and no variables. @@ -210,19 +212,17 @@ func remove_node(uuid: String, remove_connections: bool = false, force: bool = f DeckHolder.close_group_instance(node.group_id, node.group_instance_id) if remove_connections: - var outgoing_connections := node.outgoing_connections.duplicate(true) - - for output_port: int in outgoing_connections: - for to_node: String in outgoing_connections[output_port]: - for to_port: int in outgoing_connections[output_port][to_node]: - disconnect_nodes(uuid, to_node, output_port, to_port) - - var incoming_connections := node.incoming_connections.duplicate(true) - - for input_port: int in incoming_connections: - for from_node: String in incoming_connections[input_port]: - disconnect_nodes(from_node, uuid, incoming_connections[input_port][from_node], input_port) - + var outgoing_connections := connections.get_all_outgoing_connections(uuid) + for from_port: int in outgoing_connections: + for outgoing: OutgoingConnection in outgoing_connections[from_port]: + var incoming := outgoing.counterpart.get_ref() as IncomingConnection + disconnect_nodes(uuid, outgoing.to_node, incoming.from_port, outgoing.to_port) + + var incoming_connections := connections.get_all_incoming_connections(uuid) + for to_port: int in incoming_connections: + var incoming := connections.get_incoming_connection(uuid, to_port) + disconnect_nodes(incoming.from_node, uuid, incoming.from_port, to_port) + nodes.erase(uuid) node_removed.emit(node) @@ -265,6 +265,10 @@ func group_nodes(nodes_to_group: Array) -> Deck: ) var group := DeckHolder.add_empty_group(id) + var connection_pairs := connections.filter_pairs(node_ids_to_keep) + + for pair in connection_pairs: + group.connections.add_pair(pair) var midpoint := Vector2() @@ -283,20 +287,31 @@ func group_nodes(nodes_to_group: Array) -> Deck: if node.position.x < leftmost: leftmost = node.position.x - var outgoing_connections := node.outgoing_connections.duplicate(true) + #var outgoing_connections := node.outgoing_connections.duplicate(true) +# + #for from_port: int in outgoing_connections: + #for to_node: String in outgoing_connections[from_port]: + #for to_port: int in outgoing_connections[from_port][to_node]: + #if to_node not in node_ids_to_keep: + #disconnect_nodes(node._id, to_node, from_port, to_port) + var outgoing_connections := connections.get_all_outgoing_connections(node._id) for from_port: int in outgoing_connections: - for to_node: String in outgoing_connections[from_port]: - for to_port: int in outgoing_connections[from_port][to_node]: - if to_node not in node_ids_to_keep: - disconnect_nodes(node._id, to_node, from_port, to_port) - - var incoming_connections := node.incoming_connections.duplicate(true) + for outgoing: OutgoingConnection in outgoing_connections[from_port]: + var incoming := outgoing.counterpart.get_ref() as IncomingConnection + disconnect_nodes(node._id, outgoing.to_node, incoming.from_port, outgoing.to_port) + #var incoming_connections := node.incoming_connections.duplicate(true) +# + #for to_port: int in incoming_connections: + #for from_node: String in incoming_connections[to_port]: + #if from_node not in node_ids_to_keep: + #disconnect_nodes(from_node, node._id, incoming_connections[to_port][from_node], to_port) + + var incoming_connections := connections.get_all_incoming_connections(node._id) for to_port: int in incoming_connections: - for from_node: String in incoming_connections[to_port]: - if from_node not in node_ids_to_keep: - disconnect_nodes(from_node, node._id, incoming_connections[to_port][from_node], to_port) + var incoming := connections.get_incoming_connection(node._id, to_port) + disconnect_nodes(incoming.from_node, node._id, incoming.from_port, to_port) midpoint += node.position_as_vector2() remove_node(node._id, false, true) @@ -344,7 +359,10 @@ func group_nodes(nodes_to_group: Array) -> Deck: func copy_nodes(nodes_to_copy: Array[String]) -> Dictionary: - var d := {"nodes": {}} + var d := { + "nodes": {}, + "connections": connections.filter_pairs(nodes_to_copy), + } for node_id: String in nodes_to_copy: d.nodes[node_id] = get_node(node_id).to_dict() @@ -392,7 +410,12 @@ func copy_nodes(nodes_to_copy: Array[String]) -> Dictionary: func copy_nodes_json(nodes_to_copy: Array[String]) -> String: - return JSON.stringify(copy_nodes(nodes_to_copy)) + var res := copy_nodes(nodes_to_copy) + res.connections = res.connections.map( + func(x: ConnectionPair): + return x.to_dict() + ) + return JSON.stringify(res) func allocate_ids(count: int) -> Array[String]: @@ -406,35 +429,30 @@ func paste_nodes_from_dict(nodes_to_paste: Dictionary, position: Vector2 = Vecto if not nodes_to_paste.get("nodes"): return + var pairs = (nodes_to_paste.connections as Array).map( + func(x: Dictionary): + return ConnectionPair.from_dict(x) + ) + var new_ids := allocate_ids(nodes_to_paste.nodes.size()) var ids_map := {} for i: int in nodes_to_paste.nodes.keys().size(): var node_id: String = nodes_to_paste.nodes.keys()[i] ids_map[node_id] = new_ids[i] + for old_id: String in ids_map: + for pair: ConnectionPair in pairs: + pair.remap_id(old_id, ids_map[old_id]) + + for pair: ConnectionPair in pairs: + connections.add_pair(pair) + for node_id: String in nodes_to_paste.nodes: nodes_to_paste.nodes[node_id]._id = ids_map[node_id] nodes_to_paste.nodes[node_id].position.x += position.x nodes_to_paste.nodes[node_id].position.y += position.y - var outgoing_connections: Dictionary = nodes_to_paste.nodes[node_id].outgoing_connections as Dictionary - var outgoing_connections_res := {} - for from_port in outgoing_connections: - outgoing_connections_res[from_port] = {} - for to_node_id in outgoing_connections[from_port]: - outgoing_connections_res[from_port][ids_map[to_node_id]] = outgoing_connections[from_port][to_node_id] - - var incoming_connections: Dictionary = nodes_to_paste.nodes[node_id].incoming_connections as Dictionary - var incoming_connections_res := {} - for to_port in incoming_connections: - incoming_connections_res[to_port] = {} - for from_node_id in incoming_connections[to_port]: - incoming_connections_res[to_port][ids_map[from_node_id]] = incoming_connections[to_port][from_node_id] - - nodes_to_paste.nodes[node_id].outgoing_connections = outgoing_connections_res - nodes_to_paste.nodes[node_id].incoming_connections = incoming_connections_res - var node := DeckNode.from_dict(nodes_to_paste.nodes[node_id]) var group_needs_unique := false if node.node_type == "group_node": @@ -465,6 +483,29 @@ func duplicate_nodes(nodes_to_copy: Array[String]) -> void: paste_nodes_from_dict(d, position) +## Send [param data] from [param from_node_id] to all outgoing connections on port [param from_output_port].[br] +## See [method DeckNode.send]. +func send(from_node_id: String, from_output_port: int, data: Variant, send_id: String) -> void: + var outgoing_connections := connections.get_outgoing_connections(from_node_id, from_output_port) + for connection in outgoing_connections: + var node := get_node(connection.to_node) + node.handle_receive(connection.to_port, data, send_id) + + +## Asynchronously request a value from an incoming connection on the node at [param node_id]'s input port at [param on_input_port]. +## Returns [code]null[/code] if no incoming connection exists on that port. +## The connected node may also return [code]null[/code].[br] +## See [method DeckNode.request_value_async]. +func request_value_async(node_id: String, on_input_port: int) -> Variant: + var connection := connections.get_incoming_connection(node_id, on_input_port) + if connection == null: + return null + + var other_node := get_node(connection.from_node) + + return await other_node._value_request(connection.from_port) + + func send_event(event_name: StringName, event_data: Dictionary = {}) -> void: for node: DeckNode in nodes.values(): node._event_received(event_name, event_data) @@ -512,7 +553,8 @@ func to_dict(with_meta: bool = true, group_ids: Array = []) -> Dictionary: "nodes": {}, "variable_stack": variable_stack, "id": id, - "groups": {} + "groups": {}, + "connections": connections.to_dict(), } for node_id: String in nodes.keys(): @@ -545,6 +587,7 @@ static func from_dict(data: Dictionary, path: String = "") -> Deck: deck.save_path = path deck.variable_stack = data.deck.variable_stack deck.id = data.deck.id + deck.connections = NodeConnections.from_dict(data.deck.connections) for key in data.meta: deck.set_meta(key, str_to_var(data.meta[key])) @@ -581,3 +624,269 @@ static func from_dict(data: Dictionary, path: String = "") -> Deck: return deck + + +class NodeConnections: + # Dictionary[String -> node id, Dictionary["incoming": Dictionary[int -> input port idx, IncomingConnection], "outgoing": Dictionary[int - > output port idx, Array[OutgoingConnection]]] + var data := {} + + + func add_connection(from_node_id: String, to_node_id: String, from_output_port: int, to_input_port: int) -> void: + var from_connection: Dictionary = data.get(from_node_id, _create_empty_connection()) + var to_connection: Dictionary = data.get(to_node_id, _create_empty_connection()) + var pair := ConnectionPair.new(from_node_id, to_node_id, from_output_port, to_input_port) + + var out_list: Array = (from_connection.outgoing as Dictionary).get(from_output_port, []) + out_list.append(pair.outgoing) + (from_connection.outgoing as Dictionary)[from_output_port] = out_list + + (to_connection.incoming as Dictionary)[to_input_port] = pair.incoming + + data[from_node_id] = from_connection + data[to_node_id] = to_connection + + + func remove_connection(from_node_id: String, to_node_id: String, from_output_port: int, to_input_port: int) -> void: + var from_connection: Dictionary = data.get(from_node_id, {}) + if from_connection.is_empty(): + return + + var out_list: Array = (from_connection.outgoing as Dictionary).get(from_output_port, []) + if out_list.is_empty(): + return + + var comp := ConnectionPair.new(from_node_id, to_node_id, from_output_port, to_input_port) + + for i in out_list.size(): + var out: OutgoingConnection = out_list[i] + var pair := ConnectionPair.from_outgoing(out) + if pair.is_equivalent(comp): + var to_connection: Dictionary = data.get(to_node_id).incoming + to_connection.erase(to_input_port) + out_list.erase(out) + break + + + func has_incoming_connection(node_id: String, on_port: int) -> bool: + return data.get(node_id, _create_empty_connection()).incoming.has(on_port) + + + func get_incoming_connection(node_id: String, on_port: int) -> IncomingConnection: + return data.get(node_id, _create_empty_connection()).incoming.get(on_port) + + + func has_outgoing_connection_exact(node_id: String, from_port: int, to_node: String, to_port: int) -> bool: + if not data.get(node_id, _create_empty_connection()).outgoing.has(from_port): + return false + + var has = false + for connection: OutgoingConnection in data.get(node_id, _create_empty_connection()).outgoing.get(from_port): + var inc := connection.counterpart.get_ref() as IncomingConnection + if connection.to_node == to_node \ + and connection.to_port == to_port\ + and inc.from_port == from_port: + has = true + + return has + + + func get_outgoing_connections(node_id: String, from_port: int) -> Array[OutgoingConnection]: + if data.get(node_id, _create_empty_connection()).outgoing.is_empty(): + return [] + + var res: Array[OutgoingConnection] = [] + + res.assign(data[node_id].outgoing[from_port]) + + return res + + + func get_all_outgoing_connections(node_id: String) -> Dictionary: + return data.get(node_id, _create_empty_connection()).outgoing + + + func get_all_incoming_connections(node_id: String) -> Dictionary: + return data.get(node_id, _create_empty_connection()).incoming + + + func filter_pairs(nodes: Array) -> Array[ConnectionPair]: + var res: Array[ConnectionPair] = [] + + for node_id: String in nodes: + var connections: Dictionary = data.get(node_id, _create_empty_connection()) + for to_port: int in connections.outgoing: + for outgoing: OutgoingConnection in connections.outgoing[to_port]: + if outgoing.to_node in nodes: + res.append(ConnectionPair.from_outgoing(outgoing)) + + return res + + + func add_pair(pair: ConnectionPair) -> void: + var outgoing := pair.outgoing + var incoming := pair.incoming + add_connection(incoming.from_node, outgoing.to_node, incoming.from_port, outgoing.to_port) + + + func _create_empty_connection() -> Dictionary: + return { + "incoming": {}, + "outgoing": {}, + } + + + func to_dict() -> Dictionary: + var res := {} + for node_id: String in data: + res[node_id] = _create_empty_connection() + for to_port: int in data[node_id].incoming: + res[node_id].incoming[to_port] = (data[node_id].incoming[to_port] as IncomingConnection).to_dict() + + for from_port: int in data[node_id].outgoing: + for i: OutgoingConnection in data[node_id].outgoing[from_port]: + var arr: Array = res[node_id].outgoing.get(from_port, []) + arr.append(i.to_dict()) + res[node_id].outgoing[from_port] = arr + return res + + + static func from_dict(d: Dictionary) -> NodeConnections: + var res := NodeConnections.new() + for node_id: String in d: + for from_port in d[node_id].outgoing: + for connection: Dictionary in d[node_id].outgoing[from_port]: + res.add_connection(node_id, connection.to_node, int(from_port), int(connection.to_port)) + return res + + +class ConnectionPair: + var from_node_id: String + var to_node_id: String + var from_output_port: int + var to_input_port: int + + var incoming: IncomingConnection + var outgoing: OutgoingConnection + + + func _init( + p_from_node_id: String, + p_to_node_id: String, + p_from_output_port: int, + p_to_input_port: int, + p_outgoing: OutgoingConnection = null, + p_incoming: IncomingConnection = null + ) -> void: + from_node_id = p_from_node_id + to_node_id = p_to_node_id + from_output_port = p_from_output_port + to_input_port = p_to_input_port + + if not p_outgoing: + outgoing = OutgoingConnection.new() + outgoing.to_node = to_node_id + outgoing.to_port = to_input_port + + incoming = IncomingConnection.new() + incoming.from_port = from_output_port + incoming.from_node = from_node_id + + incoming.counterpart = weakref(outgoing) + outgoing.counterpart = weakref(incoming) + else: + outgoing = p_outgoing + incoming = p_incoming + + + func remap_id(old_id: String, new_id: String) -> void: + if old_id == from_node_id: + from_node_id = new_id + incoming.from_node = new_id + elif old_id == to_node_id: + to_node_id = new_id + outgoing.to_node = new_id + + + func is_equivalent(other: ConnectionPair) -> bool: + if from_node_id == other.from_node_id and \ + to_node_id == other.to_node_id and \ + from_output_port == other.from_output_port and \ + to_input_port == other.to_input_port: + return true + return false + + + func to_dict() -> Dictionary: + return { + "incoming": incoming.to_dict(), + "outgoing": outgoing.to_dict(), + } + + + static func from_incoming(p_incoming: IncomingConnection) -> ConnectionPair: + @warning_ignore("shadowed_variable") + var outgoing := p_incoming.counterpart.get_ref() as OutgoingConnection + + return ConnectionPair.new( + p_incoming.from_node, outgoing.to_node, + p_incoming.from_port, outgoing.to_port, + outgoing, p_incoming + ) + + + static func from_outgoing(p_outgoing: OutgoingConnection) -> ConnectionPair: + @warning_ignore("shadowed_variable") + var incoming := p_outgoing.counterpart.get_ref() as IncomingConnection + + return ConnectionPair.new( + incoming.from_node, p_outgoing.to_node, + incoming.from_port, p_outgoing.to_port, + p_outgoing, incoming + ) + + + static func from_dict(d: Dictionary) -> ConnectionPair: + @warning_ignore("shadowed_variable") + var outgoing := OutgoingConnection.new() + outgoing.to_node = d.outgoing.to_node + outgoing.to_port = d.outgoing.to_port + + @warning_ignore("shadowed_variable") + var incoming := IncomingConnection.new() + incoming.from_node = d.incoming.from_node + incoming.from_port = d.incoming.from_port + + outgoing.counterpart = weakref(incoming) + incoming.counterpart = weakref(outgoing) + + return from_incoming(incoming) + + +class IncomingConnection: + var from_node: String + var from_port: int + + var counterpart: WeakRef # OutgoingConnection + + + func _to_string() -> String: + return str({"from_node": from_node, "from_port": from_port}) + + + func to_dict() -> Dictionary: + return {"from_node": from_node, "from_port": from_port} + + +class OutgoingConnection: + var to_node: String + var to_port: int + + var counterpart: WeakRef # IncomingConnection + + + func _to_string() -> String: + return str({"to_node": to_node, "to_port": to_port}) + + + func to_dict() -> Dictionary: + return {"to_node": to_node, "to_port": to_port} diff --git a/classes/deck/deck_holder.gd b/classes/deck/deck_holder.gd index eb4f2e9..e94f5fa 100644 --- a/classes/deck/deck_holder.gd +++ b/classes/deck/deck_holder.gd @@ -14,6 +14,10 @@ static var groups_emitted: Array[String] static var logger := Logger.new() static var signals := Signals.new() +enum Compat { + CONNECTIONS_IN_DECK = 1 << 0, +} + static func _static_init() -> void: signals.deck_added.connect(RPCSignalLayer._on_deck_added) @@ -31,6 +35,28 @@ static func add_empty_deck() -> Deck: return deck +static func get_deck_compat(data: Dictionary) -> int: + var res := 0 + + if not data.deck.has("connections"): + res |= Compat.CONNECTIONS_IN_DECK + + return res + + +static func apply_compat_patches(data: Dictionary, compat: int) -> void: + if compat & Compat.CONNECTIONS_IN_DECK: + # convert pre-0.0.6 connections to be stored in the deck instead + var connections := Deck.NodeConnections.new() + for node: String in data.deck.nodes: + for from_port in data.deck.nodes[node].outgoing_connections: + for to_node: String in data.deck.nodes[node].outgoing_connections[from_port]: + for to_port: int in data.deck.nodes[node].outgoing_connections[from_port][to_node]: + connections.add_connection(node, to_node, int(from_port), to_port) + + data.deck.connections = connections.to_dict() + + ## Opens a deck from the [param path]. static func open_deck_from_file(path: String) -> Deck: var f := FileAccess.open(path, FileAccess.READ) @@ -42,6 +68,8 @@ static func open_deck_from_file(path: String) -> Deck: static func open_deck_from_dict(data: Dictionary, path := "") -> Deck: + if get_deck_compat(data) != 0: + apply_compat_patches(data, get_deck_compat(data)) var deck := Deck.from_dict(data, path) decks[deck.id] = deck deck.connect_rpc_signals() @@ -72,7 +100,10 @@ static func add_group_from_dict(data: Dictionary, deck_id: String, instance_id: static func make_new_group_instance(group_id: String, parent: String = "") -> Deck: var group := get_deck(group_id) var data := group.to_dict() - return add_group_from_dict(data, group_id, UUID.v4(), parent) + var inst := add_group_from_dict(data, group_id, UUID.v4(), parent) + # copy connections + inst.connections = group.connections + return inst static func make_group_instance_unique(group_id: String, instance_id: String, parent_deck_id: String, group_node_id: String) -> Deck: @@ -119,8 +150,6 @@ static func add_empty_group(parent: String = "") -> Deck: static func connect_group_signals(group: Deck) -> void: group.node_added_to_group.connect(DeckHolder._on_node_added_to_group) group.node_removed_from_group.connect(DeckHolder._on_node_removed_from_group) - group.nodes_connected_in_group.connect(DeckHolder._on_nodes_connected_in_group) - group.nodes_disconnected_in_group.connect(DeckHolder._on_nodes_disconnected_in_group) group.node_port_value_updated.connect(DeckHolder._on_node_port_value_updated) group.node_renamed.connect(DeckHolder._on_node_renamed) group.node_moved.connect(DeckHolder._on_node_moved) @@ -216,30 +245,6 @@ static func _on_node_removed_from_group(node_id: String, remove_connections: boo instance.emit_group_signals = true -static func _on_nodes_connected_in_group(from_node_id: String, to_node_id: String, from_output_port: int, to_input_port: int, deck: Deck) -> void: - var group_id := deck.id - for instance_id: String in decks[group_id]: - if instance_id == deck.instance_id: - continue - - var instance: Deck = get_group_instance(group_id, instance_id) - instance.emit_group_signals = false - instance.connect_nodes(from_node_id, to_node_id, from_output_port, to_input_port) - instance.emit_group_signals = true - - -static func _on_nodes_disconnected_in_group(from_node_id: String, to_node_id: String, from_output_port: int, to_input_port: int, deck: Deck) -> void: - var group_id := deck.id - for instance_id: String in decks[group_id]: - if instance_id == deck.instance_id: - continue - - var instance: Deck = get_group_instance(group_id, instance_id) - instance.emit_group_signals = false - instance.disconnect_nodes(from_node_id, to_node_id, from_output_port, to_input_port) - instance.emit_group_signals = true - - static func _on_node_port_value_updated(node_id: String, port_idx: int, new_value: Variant, deck: Deck) -> void: var group_id := deck.id for instance_id: String in decks[group_id]: diff --git a/classes/deck/deck_node.gd b/classes/deck/deck_node.gd index e6e661b..b234306 100644 --- a/classes/deck/deck_node.gd +++ b/classes/deck/deck_node.gd @@ -10,12 +10,6 @@ class_name DeckNode ## 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] @@ -127,23 +121,9 @@ func add_port(type: DeckType.Types, return port -## 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) + _belonging_to.send(_id, from_output_port, data, send_id) func handle_receive(to_input_port: int, data: Variant, send_id: String) -> void: @@ -160,36 +140,11 @@ 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]) + 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 @@ -199,52 +154,6 @@ 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) @@ -324,8 +233,8 @@ 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: +## Virtual function that's called during deserialization before connections are loaded in.[br] +func _pre_port_load() -> void: pass @@ -357,8 +266,8 @@ 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), + #"outgoing_connections": outgoing_connections.duplicate(true), + #"incoming_connections": incoming_connections.duplicate(true), "props": {}, "node_type": node_type, "port_values": [], @@ -389,22 +298,22 @@ static func from_dict(data: Dictionary) -> DeckNode: for prop in data.props: node.set(prop, data.props[prop]) - node._pre_connection() + 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 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 diff --git a/classes/deck/nodes/group/group_input_node.gd b/classes/deck/nodes/group/group_input_node.gd index 4f29d5d..982631f 100644 --- a/classes/deck/nodes/group/group_input_node.gd +++ b/classes/deck/nodes/group/group_input_node.gd @@ -37,13 +37,10 @@ func _on_outgoing_connection_added(port_idx: int) -> void: func _on_outgoing_connection_removed(port_idx: int) -> void: var last_connected_port := 0 - #for port: int in outgoing_connections.keys().slice(1): - #if !(outgoing_connections[port] as Array).is_empty(): - #last_connected_port = port - for port: int in outgoing_connections.keys().slice(1): - if not (outgoing_connections.get(port, {}) as Dictionary).is_empty(): + var outgoing_connections = _belonging_to.connections.get_all_outgoing_connections(_id) + for port: int in outgoing_connections: + if not (outgoing_connections[port] as Array).is_empty(): last_connected_port = port - #prints("l:", last_connected_port, "p:", port_idx) if port_idx < last_connected_port: @@ -54,7 +51,7 @@ func _on_outgoing_connection_removed(port_idx: int) -> void: ports_updated.emit() -func _pre_connection() -> void: +func _pre_port_load() -> void: for i in output_count + 1: add_output_port( DeckType.Types.ANY, @@ -65,6 +62,7 @@ func _pre_connection() -> void: func _post_load() -> void: # ensure we have enough ports after connections var last_connected_port := 0 + var outgoing_connections = _belonging_to.connections.get_all_outgoing_connections(_id) for port: int in outgoing_connections: last_connected_port = port if outgoing_connections.has(port) else last_connected_port diff --git a/classes/deck/nodes/group/group_node.gd b/classes/deck/nodes/group/group_node.gd index facf26c..5538840 100644 --- a/classes/deck/nodes/group/group_node.gd +++ b/classes/deck/nodes/group/group_node.gd @@ -21,7 +21,7 @@ func _init() -> void: appears_in_search = false -func _pre_connection() -> void: +func _pre_port_load() -> void: for port_type: PortType in extra_ports: match port_type: PortType.OUTPUT: diff --git a/classes/deck/nodes/group/group_output_node.gd b/classes/deck/nodes/group/group_output_node.gd index e9351a7..10781a8 100644 --- a/classes/deck/nodes/group/group_output_node.gd +++ b/classes/deck/nodes/group/group_output_node.gd @@ -38,9 +38,9 @@ func _on_incoming_connection_added(port_idx: int) -> void: func _on_incoming_connection_removed(port_idx: int) -> void: var last_connected_port := 0 - for port: int in incoming_connections.keys().slice(1): - if not (incoming_connections[port] as Dictionary).is_empty(): - last_connected_port = port + var incoming_connections := _belonging_to.connections.get_all_incoming_connections(_id) + if not incoming_connections.is_empty(): + last_connected_port = incoming_connections.keys()[-1] #prints("l:", last_connected_port, "p:", port_idx) @@ -52,7 +52,7 @@ func _on_incoming_connection_removed(port_idx: int) -> void: ports_updated.emit() -func _pre_connection() -> void: +func _pre_port_load() -> void: for i in input_count + 1: add_input_port( DeckType.Types.ANY, @@ -63,6 +63,7 @@ func _pre_connection() -> void: func _post_load() -> void: # ensure we have enough ports after connections var last_connected_port := 0 + var incoming_connections := _belonging_to.connections.get_all_incoming_connections(_id) for port: int in incoming_connections: last_connected_port = port if incoming_connections.has(port) else last_connected_port diff --git a/graph_node_renderer/compat_dialog.tscn b/graph_node_renderer/compat_dialog.tscn new file mode 100644 index 0000000..93ba888 --- /dev/null +++ b/graph_node_renderer/compat_dialog.tscn @@ -0,0 +1,18 @@ +[gd_scene format=3 uid="uid://cd1t0gvi022gx"] + +[node name="CompatDialog" type="ConfirmationDialog"] +initial_position = 1 +size = Vector2i(440, 175) +ok_button_text = "Open" +dialog_autowrap = true + +[node name="Label" type="Label" parent="."] +offset_left = 8.0 +offset_top = 8.0 +offset_right = 432.0 +offset_bottom = 129.0 +text = "Warning: This deck was last saved with an earlier version of StreamGraph. + +Open anyway? The file will be modified, and opening it in the version it was saved with may lead to errors." +horizontal_alignment = 1 +autowrap_mode = 2 diff --git a/graph_node_renderer/deck_holder_renderer.gd b/graph_node_renderer/deck_holder_renderer.gd index 32190be..1754ee5 100644 --- a/graph_node_renderer/deck_holder_renderer.gd +++ b/graph_node_renderer/deck_holder_renderer.gd @@ -77,6 +77,8 @@ var _deck_to_save: WeakRef @onready var sidebar_split: HSplitContainer = %SidebarSplit @onready var sidebar: Sidebar = %Sidebar as Sidebar +@onready var compat_dialog: ConfirmationDialog = %CompatDialog + signal quit_completed() signal rpc_start_requested(port: int) signal rpc_stop_requested() @@ -330,6 +332,7 @@ func close_tab(tab: int) -> void: if tab_container.get_tab_count() == 0: bottom_dock.variable_viewer.disable_new_button() + ## Opens [member file_dialog] with the mode [member FileDialog.FILE_MODE_SAVE_FILE] ## as well as getting a weakref to the active [Deck] func open_save_dialog(path: String) -> void: @@ -340,14 +343,20 @@ func open_save_dialog(path: String) -> void: file_dialog.popup_centered() file_dialog.file_selected.connect(_on_file_dialog_save_file, CONNECT_ONE_SHOT) + ## Opens [member file_dialog] with the mode [FileDialog.FILE_MODE_OPEN_FILES] ## with the supplied [param path] func open_open_dialog(path: String) -> void: - file_dialog.file_mode = FileDialog.FILE_MODE_OPEN_FILES - file_dialog.title = "Open Deck(s)" + #file_dialog.file_mode = FileDialog.FILE_MODE_OPEN_FILES + # TODO: disabled opening multiple for now until better compat dialog method is found + file_dialog.file_mode = FileDialog.FILE_MODE_OPEN_FILE + #file_dialog.title = "Open Deck(s)" + file_dialog.title = "Open Deck" file_dialog.current_path = path + "/" file_dialog.popup_centered() - file_dialog.files_selected.connect(_on_file_dialog_open_files, CONNECT_ONE_SHOT) + #file_dialog.files_selected.connect(_on_file_dialog_open_files, CONNECT_ONE_SHOT) + file_dialog.file_selected.connect(open_deck_at_path, CONNECT_ONE_SHOT) + ## Connected to [signal FileDialog.save_file] on [member file_dialog]. ## Saves the selected [Deck] if it still exists. @@ -381,8 +390,23 @@ func open_deck_at_path(path: String) -> void: if tab_container.get_tab_metadata(tab, "path") == path: tab_container.set_current_tab(tab) return - - var deck := DeckHolder.open_deck_from_file(path) + + var f := FileAccess.open(path, FileAccess.READ) + if f.get_error() != OK: + return + + var deck_data: Dictionary = JSON.parse_string(f.get_as_text()) + if DeckHolder.get_deck_compat(deck_data) != 0: + compat_dialog.set_meta(&"path", path) + file_dialog.hide() + compat_dialog.popup_centered() + return + + open_deck_from_dict(deck_data, path) + + +func open_deck_from_dict(data: Dictionary, path: String) -> void: + var deck := DeckHolder.open_deck_from_dict(data, path) var inst: DeckRendererGraphEdit = DECK_SCENE.instantiate() inst.deck = deck var tab := tab_container.add_content(inst, path.get_file()) @@ -450,6 +474,10 @@ func disconnect_file_dialog_signals() -> void: if file_dialog.files_selected.is_connected(_on_file_dialog_open_files): file_dialog.files_selected.disconnect(_on_file_dialog_open_files) + + if file_dialog.file_selected.is_connected(open_deck_at_path): + file_dialog.file_selected.disconnect(open_deck_at_path) + ## Connected to [signal DeckRenderGraphEdit.group_entered_request] to allow entering ## groups based off the given [param group_id] and [param deck]. As well as adding @@ -652,3 +680,19 @@ func _on_help_id_pressed(id: int) -> void: HelpMenuId.DOCS: OS.shell_open("https://codeberg.org/Eroax/StreamGraph/wiki") + +func _on_compat_dialog_confirmed() -> void: + var path: String = compat_dialog.get_meta(&"path", "") + var f := FileAccess.open(path, FileAccess.READ) + if f.get_error() != OK: + return + + # save a backup + if path.split(".")[-2] != "old": + var backup_path := "%s.old.deck" % path.trim_suffix(".deck") + DirAccess.copy_absolute(path, backup_path) + + var deck_data: Dictionary = JSON.parse_string(f.get_as_text()) + open_deck_from_dict(deck_data, path) + # set it as dirty to make sure the user resaves + get_active_deck_renderer().dirty = true diff --git a/graph_node_renderer/deck_holder_renderer.tscn b/graph_node_renderer/deck_holder_renderer.tscn index dd77f21..548e50f 100644 --- a/graph_node_renderer/deck_holder_renderer.tscn +++ b/graph_node_renderer/deck_holder_renderer.tscn @@ -1,4 +1,4 @@ -[gd_scene load_steps=17 format=3 uid="uid://duaah5x0jhkn6"] +[gd_scene load_steps=18 format=3 uid="uid://duaah5x0jhkn6"] [ext_resource type="Script" path="res://graph_node_renderer/deck_holder_renderer.gd" id="1_67g2g"] [ext_resource type="PackedScene" uid="uid://b84f2ngtcm5b8" path="res://graph_node_renderer/tab_container_custom.tscn" id="1_s3ug2"] @@ -16,6 +16,7 @@ [ext_resource type="PackedScene" uid="uid://bu466w2w3q08c" path="res://graph_node_renderer/about_dialog.tscn" id="11_6ln7n"] [ext_resource type="PackedScene" uid="uid://brfrufvkjwcor" path="res://graph_node_renderer/rpc_setup_dialog.tscn" id="12_1xrfk"] [ext_resource type="PackedScene" uid="uid://dodqetbke5wji" path="res://graph_node_renderer/settings_dialog.tscn" id="16_rktri"] +[ext_resource type="PackedScene" uid="uid://cd1t0gvi022gx" path="res://graph_node_renderer/compat_dialog.tscn" id="17_2ndnq"] [node name="DeckHolderRenderer" type="Control"] layout_mode = 3 @@ -170,6 +171,9 @@ unique_name_in_owner = true [node name="SettingsDialog" parent="." instance=ExtResource("16_rktri")] unique_name_in_owner = true +[node name="CompatDialog" parent="." instance=ExtResource("17_2ndnq")] +unique_name_in_owner = true + [connection signal="id_pressed" from="MarginContainer/SidebarSplit/BottomSplit/VBoxContainer/MenuBar/File" to="." method="_on_file_id_pressed"] [connection signal="about_to_popup" from="MarginContainer/SidebarSplit/BottomSplit/VBoxContainer/MenuBar/Edit" to="." method="_on_edit_about_to_popup"] [connection signal="id_pressed" from="MarginContainer/SidebarSplit/BottomSplit/VBoxContainer/MenuBar/Edit" to="." method="_on_edit_id_pressed"] @@ -180,3 +184,4 @@ unique_name_in_owner = true [connection signal="confirmed" from="UnsavedChangesDialogSingleDeck" to="." method="_on_unsaved_changes_dialog_single_deck_confirmed"] [connection signal="custom_action" from="UnsavedChangesDialogSingleDeck" to="." method="_on_unsaved_changes_dialog_single_deck_custom_action"] [connection signal="confirmed" from="UnsavedChangesDialog" to="." method="_on_unsaved_changes_dialog_confirmed"] +[connection signal="confirmed" from="CompatDialog" to="." method="_on_compat_dialog_confirmed"] diff --git a/graph_node_renderer/deck_renderer_graph_edit.gd b/graph_node_renderer/deck_renderer_graph_edit.gd index 1e47196..927710b 100644 --- a/graph_node_renderer/deck_renderer_graph_edit.gd +++ b/graph_node_renderer/deck_renderer_graph_edit.gd @@ -119,11 +119,7 @@ func attempt_disconnect(from_node_name: StringName, from_port: int, to_node_name ## Returns the associated [DeckNodeRendererGraphNode] for the supplied [DeckNode]. ## Or [code]null[/code] if none is found. func get_node_renderer(node: DeckNode) -> DeckNodeRendererGraphNode: - for i: DeckNodeRendererGraphNode in get_children(): - if i.node == node: - return i - - return null + return get_node_or_null(NodePath(node._id)) func focus_node(node: DeckNodeRendererGraphNode) -> void: @@ -162,7 +158,8 @@ func initialize_from_deck() -> void: is_group = deck.is_group for node_id in deck.nodes: var node_renderer: DeckNodeRendererGraphNode = NODE_SCENE.instantiate() - node_renderer.node = deck.nodes[node_id] + node_renderer.node = deck.get_node(node_id) + node_renderer.name = node_id add_child(node_renderer) node_renderer.position_offset = node_renderer.node.position_as_vector2() change_dirty = true @@ -178,29 +175,16 @@ func initialize_from_deck() -> void: ## [method GraphEdit.connect_node] for all the connections that exist in each func refresh_connections() -> void: for node_id in deck.nodes: - var node: DeckNode = deck.nodes[node_id] - var from_node: DeckNodeRendererGraphNode = get_children().filter( - func(c: DeckNodeRendererGraphNode): - return c.node._id == node_id - )[0] - - for from_port in node.outgoing_connections: - for to_node_id: String in node.outgoing_connections[from_port]: - var to_node_ports = node.outgoing_connections[from_port][to_node_id] - var renderer: Array = get_children().filter( - func(c: DeckNodeRendererGraphNode): - return c.node._id == to_node_id + if not deck.get_connections_dict().has(node_id): + continue + for from_port: int in deck.get_connections_dict()[node_id].outgoing: + for outgoing: Deck.OutgoingConnection in deck.get_connections_dict()[node_id].outgoing[from_port]: + connect_node( + node_id, + from_port, + outgoing.to_node, + outgoing.to_port ) - if renderer.is_empty(): - break - var to_node: DeckNodeRendererGraphNode = renderer[0] - for to_node_port: int in to_node_ports: - connect_node( - from_node.name, - from_port, - to_node.name, - to_node_port - ) ## Connected to [signal Deck.node_added], used to instance the required @@ -208,6 +192,7 @@ func refresh_connections() -> void: func _on_deck_node_added(node: DeckNode) -> void: var inst: DeckNodeRendererGraphNode = NODE_SCENE.instantiate() inst.node = node + inst.name = node._id add_child(inst) inst.position_offset = inst.node.position_as_vector2() dirty = true @@ -236,8 +221,6 @@ func get_selected_nodes() -> Array: ) -## Executes functionality based off hotkey inputs. Specifically handles creating groups -## based off the action "group_nodes". func _gui_input(event: InputEvent) -> void: if RendererShortcuts.check_shortcut("group_nodes", event) and get_selected_nodes().size() > 0: clear_connections() @@ -293,8 +276,6 @@ func _gui_input(event: InputEvent) -> void: focus_selection() -## Handles entering groups with action "enter_group". Done here to bypass neighbor -## functionality. func _input(event: InputEvent) -> void: if not has_focus(): return @@ -343,7 +324,6 @@ func _on_add_node_menu_node_selected(type: String) -> void: func _on_deck_nodes_disconnected(from_node_id: String, to_node_id: String, from_output_port: int, to_input_port: int) -> void: - print("1") var from_node: DeckNodeRendererGraphNode = get_children().filter( func(x: DeckNodeRendererGraphNode): return x.node._id == from_node_id