miggor-StreamGraph/classes/deck/deck.gd

292 lines
9.6 KiB
GDScript3
Raw Normal View History

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
## 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].
2023-07-21 10:10:24 +02:00
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.
2023-11-29 09:47:47 +01:00
func connect_nodes(from_node_id: String, to_node_id: String, from_output_port: int, to_input_port: int) -> bool:
var from_node := get_node(from_node_id)
var to_node := get_node(to_node_id)
# check that we can do the type conversion
var type_a: DeckType.Types = from_node.get_output_ports()[from_output_port].type
var type_b: DeckType.Types = to_node.get_input_ports()[to_input_port].type
if !DeckType.can_convert(type_a, type_b):
print("Can not convert from %s to %s." % [DeckType.type_str(type_a), DeckType.type_str(type_b)])
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.
2023-11-29 09:47:47 +01:00
func disconnect_nodes(from_node_id: String, to_node_id: String, from_output_port: int, to_input_port: int) -> void:
var hash := {to_node_id: to_input_port}.hash()
var from_node := get_node(from_node_id)
var to_node := get_node(to_node_id)
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.
2023-11-29 09:47:47 +01:00
func remove_node(uuid: String, remove_connections: bool = false) -> void:
var node := get_node(uuid)
if node == null:
return
if remove_connections:
var outgoing_connections := node.outgoing_connections.duplicate(true)
for output_port: int in outgoing_connections:
for connection: Dictionary in outgoing_connections[output_port]:
disconnect_nodes(uuid, connection.keys()[0], output_port, connection.values()[0])
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)
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
2023-11-29 09:47:47 +01:00
var outgoing_connections := node.outgoing_connections.duplicate(true)
for from_port: int in outgoing_connections:
for connection: Dictionary in outgoing_connections[from_port]:
if !(connection.keys()[0] in node_ids_to_keep):
2023-11-29 09:47:47 +01:00
disconnect_nodes(node._id, connection.keys()[0], from_port, connection.values()[0])
var incoming_connections := node.incoming_connections.duplicate(true)
2023-11-29 09:47:47 +01:00
for to_port: int in incoming_connections:
for from_node: String in incoming_connections[to_port]:
if !(from_node in node_ids_to_keep):
2023-11-29 09:47:47 +01:00
disconnect_nodes(from_node, node._id, incoming_connections[to_port][from_node], to_port)
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
2023-07-21 10:10:24 +02:00
## 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:
2023-07-21 10:10:24 +02:00
var deck := Deck.new()
deck.save_path = path
2023-07-21 10:10:24 +02:00
deck.variable_stack = data.deck.variable_stack
deck.id = data.deck.id
2023-07-21 10:10:24 +02:00
for key in data.meta:
deck.set_meta(key, str_to_var(data.meta[key]))
2023-07-21 10:10:24 +02:00
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)
2023-07-21 10:10:24 +02:00
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()
2023-07-21 10:10:24 +02:00
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])
2023-07-21 10:10:24 +02:00
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])
2023-07-21 10:10:24 +02:00
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]
2023-07-21 10:26:43 +02:00
node.ports[i].value = port_value
2023-07-21 10:10:24 +02:00
for key in nodes_data[node_id].meta:
node.set_meta(key, str_to_var(nodes_data[node_id].meta[key]))
2023-07-21 10:10:24 +02:00
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()
2023-07-21 10:10:24 +02:00
return deck