miggor-StreamGraph/classes/deck/deck_holder.gd
Lera Elvoé a518e46b0f add a method to make group instances unique, making them independent (#143)
closes #97

when copying group nodes across decks (including in and out of groups), they become unique and completely independent copies of the original. this is done recursively, so in the case of copying:

- group X
	- contained in Deck A
	- has another group Z

into Deck B, group X will become group Y, group Z will become group W.

there is a rare bug that will sometimes cause the deck to save with no groups at all, which i haven't been able to hunt down and don't know how to replicate at the moment.

Reviewed-on: https://codeberg.org/StreamGraph/StreamGraph/pulls/143
Co-authored-by: Lera Elvoé <yagich@poto.cafe>
Co-committed-by: Lera Elvoé <yagich@poto.cafe>
2024-04-11 14:56:33 +00:00

282 lines
9.3 KiB
GDScript

# (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)
class_name DeckHolder
## @experimental
## A static class holding references to all decks opened in the current session.
## List of decks opened this session.
#static var decks: Array[Deck]
static var decks: Dictionary # Dictionary[String -> id, (Deck|Dictionary[String -> instance_id, Deck])]
static var groups_emitted: Array[String]
static var logger := Logger.new()
static var signals := Signals.new()
static func _static_init() -> void:
signals.deck_added.connect(RPCSignalLayer._on_deck_added)
signals.deck_closed.connect(RPCSignalLayer._on_deck_closed)
## Returns a new empty deck and assigns a new random ID to it.
static func add_empty_deck() -> Deck:
var deck := Deck.new()
var uuid := UUID.v4()
decks[uuid] = deck
deck.id = uuid
deck.connect_rpc_signals()
signals.deck_added.emit(uuid)
return deck
## Opens a deck from the [param path].
static func open_deck_from_file(path: String) -> Deck:
var f := FileAccess.open(path, FileAccess.READ)
if f.get_error() != OK:
return null
var deck := open_deck_from_dict(JSON.parse_string(f.get_as_text()), path)
return deck
static func open_deck_from_dict(data: Dictionary, path := "") -> Deck:
var deck := Deck.from_dict(data, path)
decks[deck.id] = deck
deck.connect_rpc_signals()
signals.deck_added.emit(deck.id)
return deck
static func add_group_from_dict(data: Dictionary, deck_id: String, instance_id: String, parent: String = "") -> Deck:
var group := Deck.from_dict(data)
group.instance_id = instance_id
group.is_group = true
group._belonging_to = parent
group.group_input_node = data.deck.group_input_node
group.group_output_node = data.deck.group_output_node
var instances: Dictionary = decks.get(deck_id, {})
instances[instance_id] = group
decks[deck_id] = instances
connect_group_signals(group)
if deck_id not in groups_emitted:
group.connect_rpc_signals()
signals.deck_added.emit(deck_id)
groups_emitted.append(deck_id)
#print(decks)
return group
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)
static func make_group_instance_unique(group_id: String, instance_id: String, parent_deck_id: String, group_node_id: String) -> Deck:
if not decks.has(group_id):
return null
var group_node := get_deck(parent_deck_id).get_node(group_node_id)
var inst := get_group_instance(group_id, instance_id)
var new_id := UUID.v4()
var new_inst_id := UUID.v4()
inst.id = new_id
group_node.group_id = new_id
group_node.group_instance_id = new_inst_id
var data := inst.to_dict()
#data.instance_id = new_inst_id
(decks[group_id] as Dictionary).erase(instance_id)
var new_instance := add_group_from_dict(data, new_id, new_inst_id, parent_deck_id)
group_node.init_io()
#TODO: some weird close bug
var instances := inst.get_referenced_group_instances()
for instance: Dictionary in instances:
make_group_instance_unique(instance.group_id, instance.instance_id, instance.parent_id, instance.group_node)
return new_instance
static func add_empty_group(parent: String = "") -> Deck:
var group := Deck.new()
group.is_group = true
group.id = UUID.v4()
group._belonging_to = parent
group.instance_id = UUID.v4()
decks[group.id] = {group.instance_id: group}
connect_group_signals(group)
if group.id not in groups_emitted:
group.connect_rpc_signals()
signals.deck_added.emit(group.id)
groups_emitted.append(group.id)
return group
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)
static func get_deck(id: String) -> Deck:
if not decks.has(id):
return null
if not decks[id] is Dictionary:
return decks[id]
else:
return (decks[id] as Dictionary).values()[0]
static func get_group_instance(group_id: String, instance_id: String) -> Deck:
if not decks.has(group_id):
return null
if decks[group_id] is Dictionary:
return (decks[group_id] as Dictionary).get(instance_id)
else:
return null
static func close_group_instance(group_id: String, instance_id: String) -> void:
# this is kinda dumb, but to close groups that may be dangling
# when all instances are closed, we have to get that list
# *before* we close the instance
var dangling_groups := get_deck(group_id).get_referenced_groups()
var group_instances: Dictionary = decks.get(group_id, {}) as Dictionary
group_instances.erase(instance_id)
if group_instances.is_empty():
for group in dangling_groups:
close_all_group_instances(group)
signals.deck_closed.emit(group_id)
groups_emitted.erase(group_id)
decks.erase(group_id)
static func close_all_group_instances(group_id: String) -> void:
if decks.get(group_id) is Dictionary:
decks.erase(group_id)
## Unloads a deck. Returns a list of groups that are closed as a result of
## closing this deck.
static func close_deck(deck_id: String) -> Array:
if decks.get(deck_id) is Deck:
var deck: Deck = decks[deck_id] as Deck
var groups := deck.get_referenced_groups()
for group in groups:
close_all_group_instances(group)
signals.deck_closed.emit(deck_id)
decks.erase(deck_id)
return groups
return []
static func send_event(event_name: StringName, event_data: Dictionary = {}) -> void:
for deck_id: String in decks:
if decks[deck_id] is Deck:
(decks[deck_id] as Deck).send_event(event_name, event_data)
else:
for deck_instance_id: String in decks[deck_id]:
(decks[deck_id][deck_instance_id] as Deck).send_event(event_name, event_data)
#region group signal callbacks
static func _on_node_added_to_group(node: DeckNode, assign_id: String, assign_to_self: bool, 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
var node_duplicate := DeckNode.from_dict(node.to_dict())
instance.add_node_inst(node_duplicate, assign_id, assign_to_self)
instance.emit_group_signals = true
static func _on_node_removed_from_group(node_id: String, remove_connections: bool, 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.remove_node(node_id, remove_connections)
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]:
if instance_id == deck.instance_id:
continue
var instance: Deck = get_group_instance(group_id, instance_id)
instance.emit_group_signals = false
instance.get_node(node_id).get_all_ports()[port_idx].set_value_no_signal(new_value)
instance.emit_group_signals = true
static func _on_node_renamed(node_id: String, new_name: String, 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.get_node(node_id).name = new_name
instance.emit_group_signals = true
static func _on_node_moved(node_id: String, new_position: Dictionary, 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.get_node(node_id).position = new_position.duplicate()
instance.emit_group_signals = true
#endregion
class Signals:
signal deck_added(deck_id: String)
signal deck_closed(deck_id: String)