mirror of
https://codeberg.org/StreamGraph/StreamGraph.git
synced 2024-11-13 19:49:55 +01:00
406 lines
13 KiB
GDScript
406 lines
13 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])]
|
|
|
|
## List of library groups open this session.
|
|
static var lib_groups: Dictionary # Dictionary[String -> path, Dictionary[String -> instance_id, Deck]]
|
|
|
|
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:
|
|
NodeDB.init()
|
|
|
|
|
|
## 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
|
|
signals.deck_added.emit(uuid)
|
|
print_verbose("DeckHolder: added empty deck %s, id %s" % [deck.id, deck.get_instance_id()])
|
|
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)
|
|
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:
|
|
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
|
|
signals.deck_added.emit(deck.id)
|
|
print_verbose("DeckHolder: opened deck %s, id %s" % [deck.id, deck.get_instance_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:
|
|
signals.deck_added.emit(deck_id)
|
|
groups_emitted.append(deck_id)
|
|
#print(decks)
|
|
print_verbose("DeckHolder: added group instance %s::%s, id %s" % [group.id, group.instance_id, group.get_instance_id()])
|
|
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()
|
|
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:
|
|
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:
|
|
signals.deck_added.emit(group.id)
|
|
groups_emitted.append(group.id)
|
|
print_verbose("DeckHolder: added empty group %s::%s, id %s" % [group.id, group.instance_id, group.get_instance_id()])
|
|
return group
|
|
|
|
|
|
static func add_lib_instance(type: String, to_deck: String, instance_id: String = UUID.v4()) -> Deck:
|
|
var nd: NodeDB.NodeDescriptor = NodeDB.libraries[type]
|
|
var path := nd.script_path
|
|
var deck_data: Dictionary
|
|
if lib_groups.has(path):
|
|
deck_data = (lib_groups[path].values()[0] as Deck).to_dict()
|
|
else:
|
|
if not NodeDB.libraries.has(type):
|
|
return null
|
|
|
|
var f := FileAccess.open(path, FileAccess.READ)
|
|
if not f:
|
|
return null
|
|
|
|
deck_data = JSON.parse_string(f.get_as_text())
|
|
|
|
var instances := lib_groups.get(path, {}) as Dictionary
|
|
|
|
var deck := Deck.from_dict(deck_data)
|
|
deck.id = path
|
|
deck.instance_id = instance_id
|
|
deck.is_group = true
|
|
deck.is_library = true
|
|
deck._belonging_to = to_deck
|
|
|
|
instances[instance_id] = deck
|
|
lib_groups[path] = instances
|
|
|
|
print_verbose("DeckHolder: added lib group %s::%s, id %s" % [deck.id, deck.instance_id, deck.get_instance_id()])
|
|
return 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.node_port_value_updated.connect(DeckHolder._on_node_port_value_updated)
|
|
group.node_ports_updated.connect(DeckHolder._on_node_ports_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 get_lib(id)
|
|
|
|
if not decks[id] is Dictionary:
|
|
return decks[id]
|
|
else:
|
|
return (decks[id] as Dictionary).values()[0]
|
|
|
|
|
|
static func get_lib(path: String) -> Deck:
|
|
if not lib_groups.has(path):
|
|
return null
|
|
|
|
return (lib_groups[path] as Dictionary).values()[0]
|
|
|
|
|
|
static func get_group_instance(group_id: String, instance_id: String) -> Deck:
|
|
if not decks.has(group_id):
|
|
return get_lib_instance(group_id, instance_id)
|
|
|
|
if decks[group_id] is Dictionary:
|
|
return (decks[group_id] as Dictionary).get(instance_id)
|
|
else:
|
|
return null
|
|
|
|
|
|
static func get_lib_instance(path: String, instance_id: String) -> Deck:
|
|
if not lib_groups.has(path):
|
|
return null
|
|
|
|
return (lib_groups[path] as Dictionary).get(instance_id, 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 from := lib_groups if group_id.is_absolute_path() else decks
|
|
var group_instances: Dictionary = from.get(group_id, {}) as Dictionary
|
|
var group_inst: Deck = group_instances[instance_id]
|
|
print_verbose("DeckHolder: freeing group instance %s::%s, id %s" % [group_inst.id, group_inst.instance_id, group_inst.get_instance_id()])
|
|
group_inst.pre_exit_cleanup()
|
|
group_inst.free()
|
|
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)
|
|
from.erase(group_id)
|
|
|
|
|
|
static func close_all_group_instances(group_id: String) -> void:
|
|
var from := lib_groups if group_id.is_absolute_path() else decks
|
|
if from.get(group_id) is Dictionary:
|
|
for instance_id: String in from[group_id]:
|
|
print_verbose("DeckHolder: freeing group instance %s::%s, id %s" % [group_id, instance_id, from[group_id][instance_id].get_instance_id()])
|
|
from[group_id][instance_id].pre_exit_cleanup()
|
|
from[group_id][instance_id].free()
|
|
from.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)
|
|
print_verbose("DeckHolder: freeing deck %s, id %s" % [deck.id, deck.get_instance_id()])
|
|
deck.pre_exit_cleanup()
|
|
deck.free()
|
|
return groups
|
|
return []
|
|
|
|
|
|
static func pre_exit_cleanup() -> void:
|
|
for deck_id: String in decks:
|
|
if decks[deck_id] is Deck:
|
|
var deck: Deck = decks[deck_id]
|
|
print_verbose("DeckHolder: freeing deck %s, id %s" % [deck.id, deck.get_instance_id()])
|
|
deck.pre_exit_cleanup()
|
|
deck.free()
|
|
else:
|
|
for instance_id: String in decks[deck_id]:
|
|
var deck: Deck = decks[deck_id][instance_id]
|
|
print_verbose("DeckHolder: freeing group %s::%s, id %s" % [deck_id, instance_id, deck.get_instance_id()])
|
|
deck.pre_exit_cleanup()
|
|
deck.free()
|
|
|
|
for lib_id: String in lib_groups:
|
|
for instance_id: String in lib_groups[lib_id]:
|
|
var deck: Deck = lib_groups[lib_id][instance_id]
|
|
print_verbose("DeckHolder: freeing lib group %s::%s, id %s" % [lib_id, instance_id, deck.get_instance_id()])
|
|
deck.pre_exit_cleanup()
|
|
deck.free()
|
|
|
|
|
|
static func send_event(event_name: StringName, event_data: Dictionary = {}) -> void:
|
|
for deck_id: String in decks:
|
|
if decks[deck_id] is Deck:
|
|
var deck: Deck = (decks[deck_id] as Deck)
|
|
if not is_instance_valid(deck):
|
|
continue
|
|
deck.send_event(event_name, event_data)
|
|
else:
|
|
for deck_instance_id: String in decks[deck_id]:
|
|
var deck: Deck = (decks[deck_id][deck_instance_id] as Deck)
|
|
if not is_instance_valid(deck):
|
|
continue
|
|
deck.send_event(event_name, event_data)
|
|
|
|
for lib_group_id: String in lib_groups:
|
|
for lib_instance_id: String in lib_groups[lib_group_id]:
|
|
var deck: Deck = (lib_groups[lib_group_id][lib_instance_id] as Deck)
|
|
if not is_instance_valid(deck):
|
|
continue
|
|
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_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_ports_updated(node_id: String, deck: Deck) -> void:
|
|
var group_id := deck.id
|
|
var original_node := deck.get_node(node_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 := instance.get_node(node_id)
|
|
node.ports.clear()
|
|
for port in original_node.ports:
|
|
node.add_port(
|
|
port.type,
|
|
port.label,
|
|
port.port_type,
|
|
port.index_of_type,
|
|
port.descriptor,
|
|
port.usage_type,
|
|
)
|
|
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)
|