mirror of
https://codeberg.org/StreamGraph/StreamGraph.git
synced 2024-11-13 19:49:55 +01:00
c4e35043df
no longer using classes for every type. the type system has been greatly simplified, with the added bonus that it hooks directly into GraphEdit's slot type system. connections will still fail if the type conversion fails, which may be used by other renderers. the type conversion map is straightforward to understand, and easy to extend should the need arise (hopefully it shouldn't). Reviewed-on: https://codeberg.org/Eroax/Re-DotDeck/pulls/8 Co-authored-by: Lera Elvoé <yagich@poto.cafe> Co-committed-by: Lera Elvoé <yagich@poto.cafe>
268 lines
8.7 KiB
GDScript
268 lines
8.7 KiB
GDScript
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].
|
|
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:
|
|
# 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.
|
|
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])
|
|
|
|
for to_port: int in node.incoming_connections:
|
|
for from_node: String in node.incoming_connections[to_port]:
|
|
if !(from_node in node_ids_to_keep):
|
|
disconnect_nodes(get_node(from_node), node, node.incoming_connections[to_port].values()[0], 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
|
|
|
|
|
|
## 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
|