diff --git a/addons/SignalVisualizer/Clear.svg b/addons/SignalVisualizer/Clear.svg
new file mode 100644
index 0000000..289c24b
--- /dev/null
+++ b/addons/SignalVisualizer/Clear.svg
@@ -0,0 +1 @@
+
diff --git a/addons/SignalVisualizer/Clear.svg.import b/addons/SignalVisualizer/Clear.svg.import
new file mode 100644
index 0000000..080f675
--- /dev/null
+++ b/addons/SignalVisualizer/Clear.svg.import
@@ -0,0 +1,37 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://bmnff63evbdhv"
+path="res://.godot/imported/Clear.svg-d661617e27b91e3580171e3447fde514.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/SignalVisualizer/Clear.svg"
+dest_files=["res://.godot/imported/Clear.svg-d661617e27b91e3580171e3447fde514.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=false
+editor/convert_colors_with_editor_theme=false
diff --git a/addons/SignalVisualizer/Common/signal_connection.gd b/addons/SignalVisualizer/Common/signal_connection.gd
new file mode 100644
index 0000000..9104e71
--- /dev/null
+++ b/addons/SignalVisualizer/Common/signal_connection.gd
@@ -0,0 +1,58 @@
+class_name SignalConnection extends Object
+
+# Properties
+# |===================================|
+# |===================================|
+# |===================================|
+
+var signal_id: int
+var source_node_name: String
+var destination_node_name: String
+var method_signature: String
+
+var description: String :
+ get:
+ return "ID: {signal_id} Source: {source_node_name} Destination: {destination_node_name} Method: {method_signature}".format({
+ "signal_id": signal_id,
+ "source_node_name": source_node_name,
+ "destination_node_name": destination_node_name,
+ "method_signature": method_signature,
+ })
+
+var dictionary_key: String :
+ get:
+ return "{signal_id}__{source_node_name}__{destination_node_name}__{method_signature}".format({ "signal_id": signal_id, "source_node_name": source_node_name, "destination_node_name": destination_node_name, "method_signature": method_signature.replace("::", "_") })
+
+var dictionary_representation: Dictionary :
+ get:
+ return {
+ "signal_id": signal_id,
+ "source_node_name": source_node_name,
+ "destination_node_name": destination_node_name,
+ "method_signature": method_signature,
+ }
+
+# Lifecycle
+# |===================================|
+# |===================================|
+# |===================================|
+
+func _init(signal_id: int, source_node_name: String, destination_node_name: String, method_signature: String):
+ self.signal_id = signal_id
+ self.source_node_name = source_node_name
+ self.destination_node_name = destination_node_name
+ self.method_signature = method_signature
+
+# Signals
+# |===================================|
+# |===================================|
+# |===================================|
+
+
+
+# Methods
+# |===================================|
+# |===================================|
+# |===================================|
+
+
diff --git a/addons/SignalVisualizer/Common/signal_connection.gd.uid b/addons/SignalVisualizer/Common/signal_connection.gd.uid
new file mode 100644
index 0000000..bd2142d
--- /dev/null
+++ b/addons/SignalVisualizer/Common/signal_connection.gd.uid
@@ -0,0 +1 @@
+uid://dm613ct57qfwa
diff --git a/addons/SignalVisualizer/Common/signal_description.gd b/addons/SignalVisualizer/Common/signal_description.gd
new file mode 100644
index 0000000..66dddb0
--- /dev/null
+++ b/addons/SignalVisualizer/Common/signal_description.gd
@@ -0,0 +1,56 @@
+class_name SignalDescription extends Object
+
+# Properties
+# |===================================|
+# |===================================|
+# |===================================|
+
+var id: int:
+ get:
+ if _source_id != null:
+ return _source_id
+ return get_instance_id()
+
+var node_name: String
+var signal_name: String
+
+var description: String :
+ get:
+ return "ID: {id} Node: {node_name} Signal: {signal_name}".format({
+ "id": id,
+ "node_name": node_name,
+ "signal_name": signal_name,
+ })
+
+var dictionary_representation: Dictionary :
+ get:
+ return {
+ "id": id,
+ "node_name": node_name,
+ "signal_name": signal_name,
+ }
+
+var _source_id = null
+
+# Lifecycle
+# |===================================|
+# |===================================|
+# |===================================|
+
+func _init(node_name: String, signal_name: String):
+ self.node_name = node_name
+ self.signal_name = signal_name
+
+# Signals
+# |===================================|
+# |===================================|
+# |===================================|
+
+
+
+# Methods
+# |===================================|
+# |===================================|
+# |===================================|
+
+
diff --git a/addons/SignalVisualizer/Common/signal_description.gd.uid b/addons/SignalVisualizer/Common/signal_description.gd.uid
new file mode 100644
index 0000000..1c41304
--- /dev/null
+++ b/addons/SignalVisualizer/Common/signal_description.gd.uid
@@ -0,0 +1 @@
+uid://dvgsocxisw3ae
diff --git a/addons/SignalVisualizer/Common/signal_graph.gd b/addons/SignalVisualizer/Common/signal_graph.gd
new file mode 100644
index 0000000..731d8a5
--- /dev/null
+++ b/addons/SignalVisualizer/Common/signal_graph.gd
@@ -0,0 +1,53 @@
+class_name SignalGraph extends Object
+
+# Properties
+# |===================================|
+# |===================================|
+# |===================================|
+
+var name: String
+var signals: Array[SignalDescription]
+var edges: Array[SignalConnection]
+
+var description: String :
+ get:
+ return "Signals: {signals}\nEdges: {edges}".format({
+ "signals": signals.map(func (item): return item.description),
+ "edges": edges.map(func (item): return item.description),
+ })
+
+var dictionary_representation: Dictionary :
+ get:
+ return {
+ "name": name,
+ "signals": signals.map(func (element): return element.dictionary_representation),
+ "edges": edges.map(func (element): return element.dictionary_representation),
+ }
+
+# Lifecycle
+# |===================================|
+# |===================================|
+# |===================================|
+
+func _init(name: String, signals: Array[SignalDescription] = [], edges: Array[SignalConnection] = []):
+ self.name = name
+ self.signals = signals
+ self.edges = edges
+
+# Signals
+# |===================================|
+# |===================================|
+# |===================================|
+
+
+
+# Methods
+# |===================================|
+# |===================================|
+# |===================================|
+
+func get_source_signal_for_edge(edge: SignalConnection) -> SignalDescription:
+ var result = signals.filter(func (item): return item.id == edge.signal_id)
+ if result.size() > 0:
+ return result[0]
+ return null
diff --git a/addons/SignalVisualizer/Common/signal_graph.gd.uid b/addons/SignalVisualizer/Common/signal_graph.gd.uid
new file mode 100644
index 0000000..8e72ce2
--- /dev/null
+++ b/addons/SignalVisualizer/Common/signal_graph.gd.uid
@@ -0,0 +1 @@
+uid://2qj81iy1le0a
diff --git a/addons/SignalVisualizer/Common/signal_graph_utility.gd b/addons/SignalVisualizer/Common/signal_graph_utility.gd
new file mode 100644
index 0000000..0a2cd22
--- /dev/null
+++ b/addons/SignalVisualizer/Common/signal_graph_utility.gd
@@ -0,0 +1,170 @@
+@tool
+class_name SignalGraphUtility
+
+static var SignalGraphNode = preload("res://addons/SignalVisualizer/Visualizer/signal_graph_node.tscn")
+static var GraphNodeItem = preload("res://addons/SignalVisualizer/Visualizer/signal_graph_node_item.tscn")
+
+const SOURCE_COLOR: Color = Color.SKY_BLUE
+const DESTINATION_COLOR: Color = Color.CORAL
+const CONNECTION_TYPE: int = 0
+
+#region Methods
+
+static func create_signal_graph(name: String, signals: Array, edges: Array) -> SignalGraph:
+ var signal_graph = SignalGraph.new(name)
+
+ for signal_item in signals:
+ var new_signal_description = SignalDescription.new(signal_item.node_name, signal_item.signal_name)
+ new_signal_description._source_id = signal_item.id
+ signal_graph.signals.append(new_signal_description)
+
+ for connection in edges:
+ var new_edge = SignalConnection.new(connection.signal_id, connection.source_node_name, connection.destination_node_name, connection.method_signature)
+ signal_graph.edges.append(new_edge)
+
+ return signal_graph
+
+static func create_signal_graph_from_node(root_node: Node, is_persistent_only: bool = false):
+ var signal_graph = SignalGraph.new(root_node.scene_file_path)
+ var all_nodes: Array[Node] = _gather_nodes_from_node(root_node)
+ var signals: Array[SignalDescription] = []
+ var edges: Array[SignalConnection] = []
+
+ for node in all_nodes:
+ for signal_item in node.get_signal_list():
+ var existing_signals = []
+ var connection_list = node.get_signal_connection_list(signal_item["name"] as String)
+ if connection_list.size() > 0:
+ for connection in connection_list:
+ var enabled_flags = connection["flags"] == CONNECT_PERSIST if is_persistent_only else true
+ var should_display_connection = "name" in connection["callable"].get_object() and not connection["callable"].get_object().name.begins_with("@") and enabled_flags
+ if should_display_connection:
+ var signal_description: SignalDescription
+ var filtered_signals = existing_signals.filter(func (element): return element.signal_name == signal_item.name and element.node_name == node.name)
+ if filtered_signals.size() == 1:
+ signal_description = filtered_signals[0]
+ else:
+ signal_description = SignalDescription.new(node.name, signal_item.name)
+ existing_signals.append(signal_description)
+ signals.append(signal_description)
+
+ var signal_edge = SignalConnection.new(signal_description.id, signal_description.node_name, connection["callable"].get_object().name, connection["callable"].get_method())
+ if not signal_graph.edges.any(func (element): return element.signal_id == signal_description.id):
+ edges.append(signal_edge)
+
+ var temp_signals = {}
+ for item in signals:
+ temp_signals[item.id] = item
+
+ var temp_edges = {}
+ for item in edges:
+ temp_edges[item.dictionary_key] = item
+
+ signal_graph.signals.assign(temp_signals.keys().map(func (key): return temp_signals[key]))
+ signal_graph.edges.assign(temp_edges.keys().map(func (key): return temp_edges[key]))
+
+ return signal_graph
+
+static func generate_signal_graph_nodes(signal_graph: SignalGraph, graph_node: GraphEdit, open_script_callable: Callable):
+ var graph_nodes: Dictionary = {}
+
+ for signal_item in signal_graph.signals:
+ var current_graph_node: SignalGraphNode
+ if graph_nodes.has(signal_item.node_name):
+ current_graph_node = graph_nodes[signal_item.node_name]
+ if not current_graph_node:
+ current_graph_node = SignalGraphNode.instantiate()
+ current_graph_node.title = signal_item.node_name
+ current_graph_node.name = _get_graph_node_name(signal_item.node_name)
+ graph_node.add_child(current_graph_node)
+ graph_nodes[signal_item.node_name] = current_graph_node
+
+ for edge in signal_graph.edges:
+ var destination_graph_node: SignalGraphNode
+ if graph_nodes.has(edge.destination_node_name):
+ destination_graph_node = graph_nodes[edge.destination_node_name]
+ else:
+ destination_graph_node = SignalGraphNode.instantiate()
+ destination_graph_node.title = edge.destination_node_name
+ destination_graph_node.name = _get_graph_node_name(edge.destination_node_name)
+ graph_node.add_child(destination_graph_node)
+ graph_nodes[edge.destination_node_name] = destination_graph_node
+
+ var source_signal = signal_graph.get_source_signal_for_edge(edge)
+ if source_signal != null:
+ var source_graph_node: SignalGraphNode = graph_nodes[edge.source_node_name] as SignalGraphNode
+
+ if not source_graph_node.has_source_signal_description(source_signal.signal_name, edge.destination_node_name):
+ var source_signal_label = Label.new()
+ source_signal_label.text = source_signal.signal_name
+ source_signal_label.name = "source_" + source_signal.signal_name + "_" + edge.destination_node_name
+ source_graph_node.add_child(source_signal_label)
+
+ var destination_signal_name = "destination_" + source_signal.signal_name + "_" + edge.method_signature.replace("::", "__")
+ var has_destination = destination_graph_node.has_destination_signal_description(source_signal.signal_name, edge.method_signature)
+ if not has_destination:
+ var destination_signal_item = GraphNodeItem.instantiate()
+ destination_signal_item.signal_data = SignalGraphNodeItem.Metadata.new(source_signal.signal_name, edge.method_signature, edge.destination_node_name)
+ destination_signal_item.text = edge.method_signature
+ destination_signal_item.name = destination_signal_name
+ destination_signal_item.open_script.connect(open_script_callable)
+ destination_graph_node.add_child(destination_signal_item)
+
+ for edge in signal_graph.edges:
+ var source_signal = signal_graph.get_source_signal_for_edge(edge)
+ if source_signal != null:
+ var source_graph_node: SignalGraphNode = graph_nodes[edge.source_node_name] as SignalGraphNode
+ var destination_graph_node: SignalGraphNode = graph_nodes[edge.destination_node_name] as SignalGraphNode
+
+ var from_port = source_graph_node.get_source_slot(source_signal.signal_name, edge.destination_node_name)
+ var to_port = destination_graph_node.get_destination_slot(source_signal.signal_name, edge.method_signature)
+
+ source_graph_node.set_slot(from_port, false, CONNECTION_TYPE, Color.BLACK, true, CONNECTION_TYPE, SOURCE_COLOR)
+ destination_graph_node.set_slot(to_port, true, CONNECTION_TYPE, DESTINATION_COLOR, false, CONNECTION_TYPE, Color.BLACK)
+
+ var from_slot_index = source_graph_node.get_next_source_slot(source_signal.signal_name, edge.destination_node_name)
+ var to_slot_index = destination_graph_node.get_next_destination_slot(source_signal.signal_name, edge.method_signature)
+
+ if from_port >= 0 and to_port >= 0:
+ graph_node.connect_node(source_graph_node.name, from_slot_index, destination_graph_node.name, to_slot_index)
+ else:
+ print(">>> Invalid Connection Request")
+
+static func generate_signal_graph_tree(signal_graph: SignalGraph, tree_node: Tree):
+ var root = tree_node.create_item()
+ root.set_text(0, signal_graph.name)
+
+ var tree_items: Dictionary = {}
+
+ for signal_item in signal_graph.signals:
+ var node_tree_item: TreeItem
+ if tree_items.has(signal_item.node_name):
+ node_tree_item = tree_items[signal_item.node_name] as TreeItem
+ else:
+ node_tree_item = tree_node.create_item(root)
+ node_tree_item.set_text(0, signal_item.node_name)
+ tree_items[signal_item.node_name] = node_tree_item
+
+ var signal_tree_item = tree_node.create_item(node_tree_item)
+ signal_tree_item.set_text(0, signal_item.signal_name)
+
+ for edge in signal_graph.edges.filter(func (item): return item.signal_id == signal_item.id):
+ var signal_connection_tree_item = tree_node.create_item(signal_tree_item)
+ signal_connection_tree_item.set_text(0, edge.destination_node_name + "::" + edge.method_signature)
+
+static func _get_graph_node_name(name: String) -> String:
+ return "{node_name}_graph_node".format({ "node_name": name })
+
+static func _gather_nodes_from_node(root_node: Node) -> Array[Node]:
+ var node_list: Array[Node] = [root_node]
+ return node_list + __gather_nodes_from_node(root_node)
+
+static func __gather_nodes_from_node(node: Node) -> Array[Node]:
+ var nodes: Array[Node] = []
+ for child in node.get_children(false):
+ nodes.append(child)
+ nodes += __gather_nodes_from_node(child)
+
+ return nodes
+
+#endregion
diff --git a/addons/SignalVisualizer/Common/signal_graph_utility.gd.uid b/addons/SignalVisualizer/Common/signal_graph_utility.gd.uid
new file mode 100644
index 0000000..3055a04
--- /dev/null
+++ b/addons/SignalVisualizer/Common/signal_graph_utility.gd.uid
@@ -0,0 +1 @@
+uid://csw8uccbs0vuk
diff --git a/addons/SignalVisualizer/Debugger/SignalDebugger.gd b/addons/SignalVisualizer/Debugger/SignalDebugger.gd
new file mode 100644
index 0000000..2f8a712
--- /dev/null
+++ b/addons/SignalVisualizer/Debugger/SignalDebugger.gd
@@ -0,0 +1,146 @@
+extends Node
+
+# Properties
+# |===================================|
+# |===================================|
+# |===================================|
+
+var _signal_graph: SignalGraph
+var _lambda_map: Dictionary = {}
+
+# Lifecycle
+# |===================================|
+# |===================================|
+# |===================================|
+
+func _ready():
+ if OS.is_debug_build():
+ EngineDebugger.register_message_capture("signal_debugger", _on_signal_debugger_message_capture)
+
+# Signals
+# |===================================|
+# |===================================|
+# |===================================|
+
+func _on_signal_debugger_message_capture(message: String, data: Array) -> bool:
+ if message == "start":
+ _signal_graph = generate_signal_graph()
+ for signal_item in _signal_graph.signals:
+ _connect_to_signal(signal_item)
+ EngineDebugger.send_message(
+ "signal_debugger:generated_graph",
+ [[_signal_graph.signals.map(func (item): return item.dictionary_representation), _signal_graph.edges.map(func (item): return item.dictionary_representation)]]
+ )
+ if message == "stop" and _signal_graph:
+ for signal_item in _signal_graph.signals:
+ _disconnect_from_signal(signal_item)
+
+ if message == "invoke_signal" and data.size() == 2:
+ var node_name = data[0]
+ var signal_name = data[1]
+
+ var root_node = get_tree().current_scene
+ var node = root_node if root_node.name == node_name else root_node.find_child(node_name)
+ if node:
+ var connection_list = node.get_signal_connection_list(signal_name)
+ for connection in connection_list:
+ var callable = connection["callable"]
+ var bound_args = callable.get_bound_arguments()
+ var bound_args_count = callable.get_bound_arguments_count()
+ var method = callable.get_method()
+ callable.callv([node])
+
+ return true
+
+func _on_signal_execution(signal_name: String, node_name: String, args):
+ EngineDebugger.send_message(
+ "signal_debugger:signal_executed",
+ [Time.get_datetime_string_from_system(), node_name, signal_name]
+ )
+
+# Methods
+# |===================================|
+# |===================================|
+# |===================================|
+
+func generate_signal_graph() -> SignalGraph:
+ var graph = SignalGraphUtility.create_signal_graph_from_node(get_tree().current_scene)
+ return graph
+ #var signal_graph = SignalGraph.new(get_tree().current_scene.name)
+ #var all_nodes: Array[Node] = _gather_nodes_in_scene()
+ #var signals: Array[SignalDescription] = []
+ #var edges: Array[SignalConnection] = []
+ #
+ #for node in all_nodes:
+ #for signal_item in node.get_signal_list():
+ #var existing_signals = []
+ #var connection_list = node.get_signal_connection_list(signal_item["name"] as String)
+ #if connection_list.size() > 0:
+ #for connection in connection_list:
+ #var should_display_connection = "name" in connection["callable"].get_object() and not connection["callable"].get_object().name.begins_with("@")
+ #if should_display_connection:
+ #var signal_description: SignalDescription
+ #var filtered_signals = existing_signals.filter(func (element): return element.signal_name == signal_item.name and element.node_name == node.name)
+ #if filtered_signals.size() == 1:
+ #signal_description = filtered_signals[0]
+ #else:
+ #signal_description = SignalDescription.new(node.name, signal_item.name)
+ #existing_signals.append(signal_description)
+ #signals.append(signal_description)
+ #
+ #var signal_edge = SignalConnection.new(signal_description.id, signal_description.node_name, connection["callable"].get_object().name, connection["callable"].get_method())
+ #if not signal_graph.edges.any(func (element): return element.signal_id == signal_description.id):
+ #edges.append(signal_edge)
+ #
+ #var temp_signals = {}
+ #for item in signals:
+ #temp_signals[item.id] = item
+ #
+ #var temp_edges = {}
+ #for item in edges:
+ #temp_edges[item.dictionary_key] = item
+ #
+ #signal_graph.signals.assign(temp_signals.keys().map(func (key): return temp_signals[key]))
+ #signal_graph.edges.assign(temp_edges.keys().map(func (key): return temp_edges[key]))
+ #
+ #return signal_graph
+
+#func _gather_nodes_in_scene() -> Array[Node]:
+ #var scene_root = get_tree().current_scene
+ #var node_list: Array[Node] = [scene_root]
+ #return node_list + _gather_nodes_from_node(scene_root)
+#
+#func _gather_nodes_from_node(node: Node) -> Array[Node]:
+ #var nodes: Array[Node] = []
+ #for child in node.get_children(false):
+ #nodes.append(child)
+ #nodes += _gather_nodes_from_node(child)
+ #
+ #return nodes
+
+func _connect_to_signal(signal_item: SignalDescription):
+ var root_node = get_tree().current_scene
+ var _execute: Callable = func (args = []): _on_signal_execution(signal_item.signal_name, signal_item.node_name, args)
+ if root_node.name == signal_item.node_name:
+ root_node.connect(signal_item.signal_name, _execute)
+ _lambda_map[signal_item] = _execute
+ else:
+ var child = root_node.find_child(signal_item.node_name)
+ if child:
+ child.connect(signal_item.signal_name, _execute)
+ _lambda_map[signal_item] = _execute
+
+func _disconnect_from_signal(signal_item: SignalDescription):
+ var root_node = get_tree().current_scene
+ if root_node.name == signal_item.node_name:
+ var callable = _lambda_map[signal_item]
+ if callable:
+ root_node.disconnect(signal_item.signal_name, callable)
+ _lambda_map.erase(signal_item)
+ else:
+ var child = root_node.find_child(signal_item.node_name)
+ if child:
+ var callable = _lambda_map[signal_item]
+ if callable:
+ child.disconnect(signal_item.signal_name, callable)
+ _lambda_map.erase(signal_item)
diff --git a/addons/SignalVisualizer/Debugger/SignalDebugger.gd.uid b/addons/SignalVisualizer/Debugger/SignalDebugger.gd.uid
new file mode 100644
index 0000000..cafedea
--- /dev/null
+++ b/addons/SignalVisualizer/Debugger/SignalDebugger.gd.uid
@@ -0,0 +1 @@
+uid://bmsqdh2cnmgw8
diff --git a/addons/SignalVisualizer/Debugger/SignalDebugger.tscn b/addons/SignalVisualizer/Debugger/SignalDebugger.tscn
new file mode 100644
index 0000000..12760a0
--- /dev/null
+++ b/addons/SignalVisualizer/Debugger/SignalDebugger.tscn
@@ -0,0 +1,97 @@
+[gd_scene load_steps=5 format=3 uid="uid://cbsmvov8u78q"]
+
+[ext_resource type="Script" path="res://addons/SignalVisualizer/Debugger/signal_debugger_panel.gd" id="1_66cpc"]
+[ext_resource type="Texture2D" uid="uid://be3nwoioa311t" path="res://addons/SignalVisualizer/Play.svg" id="2_2wkuv"]
+[ext_resource type="Texture2D" uid="uid://oo1oq2colx5b" path="res://addons/SignalVisualizer/Stop.svg" id="3_bg5eu"]
+[ext_resource type="Texture2D" uid="uid://bmnff63evbdhv" path="res://addons/SignalVisualizer/Clear.svg" id="4_vg63r"]
+
+[node name="SignalDebugger" type="Control"]
+clip_contents = true
+layout_mode = 3
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+script = ExtResource("1_66cpc")
+start_icon = ExtResource("2_2wkuv")
+stop_icon = ExtResource("3_bg5eu")
+
+[node name="VBoxContainer" type="VBoxContainer" parent="."]
+clip_contents = true
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+
+[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer"]
+custom_minimum_size = Vector2(2.08165e-12, 50)
+layout_mode = 2
+theme_override_constants/separation = 8
+
+[node name="ActionButton" type="Button" parent="VBoxContainer/HBoxContainer"]
+unique_name_in_owner = true
+layout_mode = 2
+disabled = true
+text = "Start"
+icon = ExtResource("2_2wkuv")
+
+[node name="ClearAllButton" type="Button" parent="VBoxContainer/HBoxContainer"]
+unique_name_in_owner = true
+layout_mode = 2
+text = "Clear All"
+icon = ExtResource("4_vg63r")
+
+[node name="Spacer" type="Control" parent="VBoxContainer/HBoxContainer"]
+layout_mode = 2
+size_flags_horizontal = 3
+
+[node name="ClearLogsButton" type="Button" parent="VBoxContainer/HBoxContainer"]
+layout_mode = 2
+text = "Clear Logs"
+icon = ExtResource("4_vg63r")
+
+[node name="HSplitContainer" type="HSplitContainer" parent="VBoxContainer"]
+layout_mode = 2
+size_flags_vertical = 3
+
+[node name="SignalTree" type="Tree" parent="VBoxContainer/HSplitContainer"]
+unique_name_in_owner = true
+custom_minimum_size = Vector2(250, 2.08165e-12)
+layout_mode = 2
+columns = 2
+allow_reselect = true
+allow_rmb_select = true
+hide_root = true
+
+[node name="VBoxContainer" type="VBoxContainer" parent="VBoxContainer/HSplitContainer"]
+layout_mode = 2
+
+[node name="TabBar" type="TabBar" parent="VBoxContainer/HSplitContainer/VBoxContainer"]
+layout_mode = 2
+tab_count = 2
+tab_0/title = "Signal Log"
+tab_1/title = "Signal Graph"
+
+[node name="LogLabel" type="RichTextLabel" parent="VBoxContainer/HSplitContainer/VBoxContainer"]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_vertical = 3
+theme_override_colors/default_color = Color(0.690196, 0.690196, 0.690196, 1)
+bbcode_enabled = true
+scroll_following = true
+
+[node name="Graph" type="GraphEdit" parent="VBoxContainer/HSplitContainer/VBoxContainer"]
+unique_name_in_owner = true
+visible = false
+layout_mode = 2
+size_flags_vertical = 3
+
+[connection signal="pressed" from="VBoxContainer/HBoxContainer/ActionButton" to="." method="_on_action_button_pressed"]
+[connection signal="pressed" from="VBoxContainer/HBoxContainer/ClearAllButton" to="." method="_on_clear_all_button_pressed"]
+[connection signal="pressed" from="VBoxContainer/HBoxContainer/ClearLogsButton" to="." method="_on_clear_logs_button_pressed"]
+[connection signal="item_selected" from="VBoxContainer/HSplitContainer/SignalTree" to="." method="_on_signal_tree_item_selected"]
+[connection signal="tab_changed" from="VBoxContainer/HSplitContainer/VBoxContainer/TabBar" to="." method="_on_tab_bar_tab_changed"]
diff --git a/addons/SignalVisualizer/Debugger/signal_debugger_panel.gd b/addons/SignalVisualizer/Debugger/signal_debugger_panel.gd
new file mode 100644
index 0000000..9cf51de
--- /dev/null
+++ b/addons/SignalVisualizer/Debugger/signal_debugger_panel.gd
@@ -0,0 +1,192 @@
+@tool
+class_name SignalDebuggerPanel extends Control
+
+signal open_script(node_name: String, method_signature: String)
+
+signal start_signal_debugging
+signal stop_signal_debugging
+
+var SignalGraphNode = preload("res://addons/SignalVisualizer/Visualizer/signal_graph_node.tscn")
+var GraphNodeItem = preload("res://addons/SignalVisualizer/Visualizer/signal_graph_node_item.tscn")
+
+const SOURCE_COLOR: Color = Color.SKY_BLUE
+const DESTINATION_COLOR: Color = Color.CORAL
+const CONNECTION_TYPE: int = 0
+
+enum Tabs {
+ LOG,
+ GRAPH
+}
+
+# Properties
+# |===================================|
+# |===================================|
+# |===================================|
+
+@export var start_icon: Texture2D
+@export var stop_icon: Texture2D
+
+@onready var action_button: Button = %ActionButton
+@onready var clear_all_button: Button = %ClearAllButton
+@onready var signal_tree: Tree = %SignalTree
+@onready var log_label: RichTextLabel = %LogLabel
+@onready var graph_node: GraphEdit = %Graph
+
+var is_started: bool = false :
+ get: return is_started
+ set(new_value):
+ is_started = new_value
+ _update_action_button()
+
+var _signals: Array = []
+var _signal_filter: Array = []
+var _is_stack_trace_enabled: bool = false
+var _debugger_tab_state: Tabs = Tabs.LOG
+var _graph: SignalGraph
+
+# Lifecycle
+# |===================================|
+# |===================================|
+# |===================================|
+
+func _ready():
+ disable()
+ _handle_tab_update(0)
+
+# Signals
+# |===================================|
+# |===================================|
+# |===================================|
+
+func _on_action_button_pressed():
+ if is_started:
+ stop()
+ else:
+ start()
+
+func _on_clear_all_button_pressed():
+ log_label.clear()
+ signal_tree.clear()
+ graph_node.clear_connections()
+ for child in graph_node.get_children():
+ if child is SignalGraphNode:
+ child.queue_free()
+
+func _on_clear_logs_button_pressed():
+ log_label.clear()
+
+func _on_signal_tree_item_selected():
+ # Updates the checkmark button
+ var selected_item = signal_tree.get_selected()
+ var is_checked = selected_item.is_checked(1)
+ selected_item.set_checked(1, (not is_checked))
+
+ # Add / Remove signal from filters
+ var selected_signal = _signals.filter(func (element): return element.signal_name == selected_item.get_text(0))[0]
+ if _signal_filter.has(selected_signal.signal_name):
+ var selected_index = _signal_filter.find(selected_signal.signal_name)
+ _signal_filter.remove_at(selected_index)
+ else:
+ _signal_filter.append(selected_signal.signal_name)
+
+func _on_tab_bar_tab_changed(tab: int):
+ _handle_tab_update(tab)
+
+func _on_stack_trace_button_pressed():
+ _is_stack_trace_enabled = not _is_stack_trace_enabled
+
+func _on_open_signal_in_script(data: SignalGraphNodeItem.Metadata):
+ open_script.emit(data.node_name, data.method_signature)
+
+# Methods
+# |===================================|
+# |===================================|
+# |===================================|
+
+func enable():
+ action_button.disabled = false
+
+func disable():
+ action_button.disabled = true
+
+func start():
+ if not is_started:
+ is_started = true
+ action_button.icon = stop_icon
+ start_signal_debugging.emit()
+ log_label.append_text("[color=#B0B0B0]Signal Debugging Started...[/color]")
+ log_label.newline()
+ log_label.newline()
+
+func stop():
+ if is_started:
+ is_started = false
+ action_button.icon = start_icon
+ stop_signal_debugging.emit()
+ log_label.newline()
+ log_label.append_text("[color=#B0B0B0]Signal Debugging Stopped[/color]")
+ log_label.newline()
+ log_label.newline()
+
+func create_tree_from_signals(signals: Array):
+ _signals = signals
+ var root = signal_tree.create_item()
+ root.set_text(0, "Signals")
+
+ var tree_items: Dictionary = {}
+
+ for signal_item in signals:
+ var node_tree_item: TreeItem
+ if tree_items.has(signal_item.node_name):
+ node_tree_item = tree_items[signal_item.node_name] as TreeItem
+ else:
+ node_tree_item = signal_tree.create_item(root)
+ node_tree_item.set_text(0, signal_item.node_name)
+ node_tree_item.set_selectable(0, false)
+ node_tree_item.set_selectable(1, false)
+ tree_items[signal_item.node_name] = node_tree_item
+
+ var signal_tree_item = signal_tree.create_item(node_tree_item)
+ signal_tree_item.set_text(0, signal_item.signal_name)
+ signal_tree_item.set_cell_mode(1, TreeItem.CELL_MODE_CHECK)
+ signal_tree_item.set_checked(1, true)
+ signal_tree_item.set_selectable(0, false)
+ signal_tree_item.set_selectable(1, true)
+
+func create_signal_graph(signals: Array, edges: Array):
+ _graph = SignalGraphUtility.create_signal_graph(get_tree().edited_scene_root.scene_file_path, signals, edges)
+ SignalGraphUtility.generate_signal_graph_nodes(_graph, graph_node, _on_open_signal_in_script)
+
+func log_signal_execution(time: String, node_name: String, signal_name: String):
+ if _signal_filter != null and _signal_filter.has(signal_name):
+ return
+
+ if not log_label.text.is_empty():
+ log_label.newline()
+ log_label.append_text(
+ "[color=#FFCC00]{time}[/color]\t\t{node_name}\t\t{signal_name}".format({ "time": time, "node_name": node_name, "signal_name": signal_name })
+ )
+ log_label.newline()
+
+func _handle_tab_update(selected_tab_index: int):
+ match selected_tab_index:
+ 1:
+ _debugger_tab_state = Tabs.GRAPH
+ _:
+ _debugger_tab_state = Tabs.LOG
+
+ match _debugger_tab_state:
+ Tabs.LOG:
+ log_label.show()
+ graph_node.hide()
+ Tabs.GRAPH:
+ log_label.hide()
+ graph_node.show()
+
+func _update_action_button():
+ if is_started:
+ action_button.text = "Stop"
+ action_button.modulate = Color("#ff3b30")
+ else:
+ action_button.text = "Start"
+ action_button.modulate = Color.WHITE
diff --git a/addons/SignalVisualizer/Debugger/signal_debugger_panel.gd.uid b/addons/SignalVisualizer/Debugger/signal_debugger_panel.gd.uid
new file mode 100644
index 0000000..45d1550
--- /dev/null
+++ b/addons/SignalVisualizer/Debugger/signal_debugger_panel.gd.uid
@@ -0,0 +1 @@
+uid://yg8cqm6f1prd
diff --git a/addons/SignalVisualizer/GraphEdit.svg b/addons/SignalVisualizer/GraphEdit.svg
new file mode 100644
index 0000000..b53e264
--- /dev/null
+++ b/addons/SignalVisualizer/GraphEdit.svg
@@ -0,0 +1 @@
+
diff --git a/addons/SignalVisualizer/GraphEdit.svg.import b/addons/SignalVisualizer/GraphEdit.svg.import
new file mode 100644
index 0000000..30d83d6
--- /dev/null
+++ b/addons/SignalVisualizer/GraphEdit.svg.import
@@ -0,0 +1,37 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://bxj8ep08wbnm6"
+path="res://.godot/imported/GraphEdit.svg-90dae61e8e0b157ab8eff95fe4b91e53.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/SignalVisualizer/GraphEdit.svg"
+dest_files=["res://.godot/imported/GraphEdit.svg-90dae61e8e0b157ab8eff95fe4b91e53.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=false
+editor/convert_colors_with_editor_theme=false
diff --git a/addons/SignalVisualizer/Play.svg b/addons/SignalVisualizer/Play.svg
new file mode 100644
index 0000000..7da41c1
--- /dev/null
+++ b/addons/SignalVisualizer/Play.svg
@@ -0,0 +1 @@
+
diff --git a/addons/SignalVisualizer/Play.svg.import b/addons/SignalVisualizer/Play.svg.import
new file mode 100644
index 0000000..e580485
--- /dev/null
+++ b/addons/SignalVisualizer/Play.svg.import
@@ -0,0 +1,37 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://be3nwoioa311t"
+path="res://.godot/imported/Play.svg-a446691ffcef211028bb160b5a2d6ff1.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/SignalVisualizer/Play.svg"
+dest_files=["res://.godot/imported/Play.svg-a446691ffcef211028bb160b5a2d6ff1.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=false
+editor/convert_colors_with_editor_theme=false
diff --git a/addons/SignalVisualizer/SignalVisualizer.gd b/addons/SignalVisualizer/SignalVisualizer.gd
new file mode 100644
index 0000000..7701613
--- /dev/null
+++ b/addons/SignalVisualizer/SignalVisualizer.gd
@@ -0,0 +1,162 @@
+@tool
+extends EditorPlugin
+
+class SignalDebuggerPlugin extends EditorDebuggerPlugin:
+ var SignalDebuggerPanelScene = preload("res://addons/SignalVisualizer/Debugger/SignalDebugger.tscn")
+
+ signal open_script
+ signal start_signal_debugging
+ signal stop_signal_debugging
+
+ var debugger_panel
+
+ func _has_capture(prefix) -> bool:
+ return prefix == "signal_debugger"
+
+ func _capture(message, data, session_id) -> bool:
+ if message == "signal_debugger:signal_executed":
+ if data.size() == 3:
+ var time = data[0]
+ var node_name = data[1]
+ var signal_name = data[2]
+ debugger_panel.log_signal_execution(time, node_name, signal_name)
+ return true
+
+ if message == "signal_debugger:generated_graph":
+ if data.size() == 1:
+ var signals = data[0][0] as Array
+ var edges = data[0][1] as Array
+ debugger_panel.create_tree_from_signals(signals)
+ debugger_panel.create_signal_graph(signals, edges)
+ return true
+
+ return false
+
+ func _setup_session(session_id):
+ debugger_panel = SignalDebuggerPanelScene.instantiate()
+ var session = get_session(session_id)
+
+ debugger_panel.name = "Signal Debugger"
+ debugger_panel.open_script.connect(func (arg1, arg2): open_script.emit(arg1, arg2))
+ debugger_panel.start_signal_debugging.connect(func (): start_signal_debugging.emit())
+ debugger_panel.stop_signal_debugging.connect(func (): stop_signal_debugging.emit())
+
+ session.started.connect(
+ func ():
+ debugger_panel.enable()
+ )
+ session.stopped.connect(
+ func ():
+ debugger_panel.stop()
+ debugger_panel.disable()
+ stop_signal_debugging.emit()
+ )
+
+ session.add_session_tab(debugger_panel)
+
+var SignalVisualizerDockScene = preload("res://addons/SignalVisualizer/Visualizer/signal_visualizer_dock.tscn")
+
+class ScriptMethodReference:
+ var script_reference: Script
+ var line_number: int
+
+# Properties
+# |===================================|
+# |===================================|
+# |===================================|
+
+var dock: Control
+var debugger: SignalDebuggerPlugin
+
+# Lifecycle
+# |===================================|
+# |===================================|
+# |===================================|
+
+func _enter_tree():
+ dock = SignalVisualizerDockScene.instantiate()
+ debugger = SignalDebuggerPlugin.new()
+
+ dock.open_script.connect(_on_open_signal_in_script)
+ add_control_to_bottom_panel(dock, "Signal Visualizer")
+
+ debugger.start_signal_debugging.connect(_on_debugger_start_signal_debugging)
+ debugger.stop_signal_debugging.connect(_on_debugger_stop_signal_debugging)
+ debugger.open_script.connect(_on_open_signal_in_script)
+ add_debugger_plugin(debugger)
+
+ if not ProjectSettings.has_setting("autoload/Signal_Debugger"):
+ add_autoload_singleton("Signal_Debugger", "res://addons/SignalVisualizer/Debugger/SignalDebugger.gd")
+
+func _exit_tree():
+ remove_control_from_bottom_panel(dock)
+ dock.free()
+
+ remove_debugger_plugin(debugger)
+ remove_autoload_singleton("Signal_Debugger")
+
+# Signals
+# |===================================|
+# |===================================|
+# |===================================|
+
+func _on_open_signal_in_script(node_name: String, method_signature: String):
+ var node: Node
+ if get_tree().edited_scene_root.name == node_name:
+ node = get_tree().edited_scene_root
+ else:
+ node = get_tree().edited_scene_root.find_child(node_name)
+
+ if node != null:
+ var script: Script = node.get_script()
+ if script != null:
+ var editor = get_editor_interface()
+ var method_reference = _find_method_reference_in_script(script, method_signature)
+
+ if method_reference != null:
+ editor.edit_script(method_reference.script_reference, method_reference.line_number, 0)
+ editor.set_main_screen_editor("Script")
+ else:
+ push_warning("Requested method in script ({script}) for node ({name}) is not available.".format({ "name": node_name, "script": script.name }))
+ else:
+ push_warning("Requested script for node ({name}) is not available.".format({ "name": node_name }))
+ else:
+ push_warning("Requested script for node ({name}) is not available.".format({ "name": node_name }))
+
+func _on_debugger_start_signal_debugging():
+ for session in debugger.get_sessions():
+ session.send_message("signal_debugger:start", [])
+
+func _on_debugger_stop_signal_debugging():
+ for session in debugger.get_sessions():
+ session.send_message("signal_debugger:stop", [])
+
+# Methods
+# |===================================|
+# |===================================|
+# |===================================|
+
+func _find_method_reference_in_script(script: Script, method_signature: String) -> ScriptMethodReference:
+ var line_number = __find_method_line_number_in_script(script, method_signature)
+
+ if line_number == -1:
+ var base_script = script.get_base_script()
+ if base_script:
+ return _find_method_reference_in_script(base_script, method_signature)
+
+ var reference = ScriptMethodReference.new()
+ reference.script_reference = script
+ reference.line_number = line_number
+
+ return reference
+
+func __find_method_line_number_in_script(script: Script, method_signature: String) -> int:
+ var line_number = 0
+ var found = false
+ for line in script.source_code.split("\n", true):
+ line_number += 1
+ if line.contains(method_signature):
+ found = true
+ return line_number
+
+ return -1
diff --git a/addons/SignalVisualizer/SignalVisualizer.gd.uid b/addons/SignalVisualizer/SignalVisualizer.gd.uid
new file mode 100644
index 0000000..3cb5af2
--- /dev/null
+++ b/addons/SignalVisualizer/SignalVisualizer.gd.uid
@@ -0,0 +1 @@
+uid://43lcsn3nt3ri
diff --git a/addons/SignalVisualizer/Stop.svg b/addons/SignalVisualizer/Stop.svg
new file mode 100644
index 0000000..4c79ea1
--- /dev/null
+++ b/addons/SignalVisualizer/Stop.svg
@@ -0,0 +1 @@
+
diff --git a/addons/SignalVisualizer/Stop.svg.import b/addons/SignalVisualizer/Stop.svg.import
new file mode 100644
index 0000000..5e92938
--- /dev/null
+++ b/addons/SignalVisualizer/Stop.svg.import
@@ -0,0 +1,37 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://oo1oq2colx5b"
+path="res://.godot/imported/Stop.svg-e085086fb31c334bc2f02ca2bffba522.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/SignalVisualizer/Stop.svg"
+dest_files=["res://.godot/imported/Stop.svg-e085086fb31c334bc2f02ca2bffba522.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=false
+editor/convert_colors_with_editor_theme=false
diff --git a/addons/SignalVisualizer/Visualizer/resizable_label.gd b/addons/SignalVisualizer/Visualizer/resizable_label.gd
new file mode 100644
index 0000000..1c09297
--- /dev/null
+++ b/addons/SignalVisualizer/Visualizer/resizable_label.gd
@@ -0,0 +1,31 @@
+@tool
+extends Label
+
+# Properties
+# |===================================|
+# |===================================|
+# |===================================|
+
+
+
+# Lifecycle
+# |===================================|
+# |===================================|
+# |===================================|
+
+
+
+# Signals
+# |===================================|
+# |===================================|
+# |===================================|
+
+
+
+# Methods
+# |===================================|
+# |===================================|
+# |===================================|
+
+func get_text_size() -> Vector2:
+ return get_theme_default_font().get_string_size(text)
diff --git a/addons/SignalVisualizer/Visualizer/resizable_label.gd.uid b/addons/SignalVisualizer/Visualizer/resizable_label.gd.uid
new file mode 100644
index 0000000..b87c32a
--- /dev/null
+++ b/addons/SignalVisualizer/Visualizer/resizable_label.gd.uid
@@ -0,0 +1 @@
+uid://d3lyqancfvwup
diff --git a/addons/SignalVisualizer/Visualizer/signal_graph_node.gd b/addons/SignalVisualizer/Visualizer/signal_graph_node.gd
new file mode 100644
index 0000000..1b3fa5e
--- /dev/null
+++ b/addons/SignalVisualizer/Visualizer/signal_graph_node.gd
@@ -0,0 +1,94 @@
+@tool
+class_name SignalGraphNode extends GraphNode
+
+# Properties
+# |===================================|
+# |===================================|
+# |===================================|
+
+var connections: Array = [] :
+ get: return connections
+ set(new_value):
+ connections = new_value
+
+# Lifecycle
+# |===================================|
+# |===================================|
+# |===================================|
+
+func _ready():
+ selectable = true
+ resizable = true
+ draggable = true
+
+# Signals
+# |===================================|
+# |===================================|
+# |===================================|
+
+func _on_resize_request(new_minsize):
+ size = new_minsize
+
+# Methods
+# |===================================|
+# |===================================|
+# |===================================|
+
+func has_source_signal_description(signal_name: String, destination_node_name: String) -> bool:
+ for child in get_children():
+ if child.name == "source_" + signal_name + "_" + destination_node_name:
+ return true
+
+ return false
+
+func get_source_slot(signal_name: String, destination_node_name: String) -> int:
+ var index = 0
+ for child in get_children():
+ if child.name == "source_" + signal_name + "_" + destination_node_name:
+ return index
+
+ index += 1
+
+ return -1
+
+func get_next_source_slot(signal_name: String, destination_node_name: String) -> int:
+ var index = 0
+ for child in get_children():
+ if child.name.begins_with("source_"):
+ if child.name == "source_" + signal_name + "_" + destination_node_name:
+ return index
+
+ index += 1
+
+ return -1
+
+func has_destination_signal_description(signal_name: String, method_signature: String) -> bool:
+ for child in get_children():
+ if child.name == "destination_" + signal_name + "_" + _sanitize_method_signature(method_signature):
+ return true
+
+ return false
+
+func get_destination_slot(signal_name: String, method_signature: String) -> int:
+ var index = 0
+ for child in get_children():
+ if child.name == "destination_" + signal_name + "_" + _sanitize_method_signature(method_signature):
+ return index
+
+ index += 1
+
+ return -1
+
+func get_next_destination_slot(signal_name: String, method_signature: String) -> int:
+ var index = 0
+ for child in get_children():
+ if child.name.begins_with("destination_"):
+ if child.name == "destination_" + signal_name + "_" + _sanitize_method_signature(method_signature):
+ return index
+
+ index += 1
+
+ return -1
+
+func _sanitize_method_signature(signature: String) -> String:
+ return signature.replace("::", "__")
diff --git a/addons/SignalVisualizer/Visualizer/signal_graph_node.gd.uid b/addons/SignalVisualizer/Visualizer/signal_graph_node.gd.uid
new file mode 100644
index 0000000..fd4bbdb
--- /dev/null
+++ b/addons/SignalVisualizer/Visualizer/signal_graph_node.gd.uid
@@ -0,0 +1 @@
+uid://bdwkkgkhgfrtk
diff --git a/addons/SignalVisualizer/Visualizer/signal_graph_node.tscn b/addons/SignalVisualizer/Visualizer/signal_graph_node.tscn
new file mode 100644
index 0000000..b28fdda
--- /dev/null
+++ b/addons/SignalVisualizer/Visualizer/signal_graph_node.tscn
@@ -0,0 +1,12 @@
+[gd_scene load_steps=2 format=3 uid="uid://cq10iaub18e54"]
+
+[ext_resource type="Script" path="res://addons/SignalVisualizer/Visualizer/signal_graph_node.gd" id="1_ovklj"]
+
+[node name="SignalGraphNode" type="GraphNode"]
+custom_minimum_size = Vector2(100, 50)
+offset_right = 232.0
+offset_bottom = 54.0
+resizable = true
+script = ExtResource("1_ovklj")
+
+[connection signal="resize_request" from="." to="." method="_on_resize_request"]
diff --git a/addons/SignalVisualizer/Visualizer/signal_graph_node_item.gd b/addons/SignalVisualizer/Visualizer/signal_graph_node_item.gd
new file mode 100644
index 0000000..60f7df5
--- /dev/null
+++ b/addons/SignalVisualizer/Visualizer/signal_graph_node_item.gd
@@ -0,0 +1,57 @@
+@tool
+class_name SignalGraphNodeItem extends Control
+
+signal open_script(metadata: SignalGraphNodeItem.Metadata)
+
+class Metadata:
+ var signal_name: String
+ var method_signature: String
+ var node_name: String
+
+ func _init(signal_name: String, method_signature: String, node_name: String):
+ self.signal_name = signal_name
+ self.method_signature = method_signature
+ self.node_name = node_name
+
+# Properties
+# |===================================|
+# |===================================|
+# |===================================|
+
+@onready var label: Label = %DescriptionLabel
+
+var signal_data: Metadata = null
+
+var text: String = "" :
+ get: return text
+ set(new_value):
+ text = new_value
+ if label:
+ label.text = text
+
+# Lifecycle
+# |===================================|
+# |===================================|
+# |===================================|
+
+func _ready():
+ _update()
+
+# Signals
+# |===================================|
+# |===================================|
+# |===================================|
+
+func _on_open_signal_in_script_button_pressed():
+ open_script.emit(signal_data)
+
+# Methods
+# |===================================|
+# |===================================|
+# |===================================|
+
+func _update():
+ label.text = text
+
+ var text_size = label.get_text_size()
+ custom_minimum_size = Vector2((text_size.x * 2) + 50, text_size.y * 3)
diff --git a/addons/SignalVisualizer/Visualizer/signal_graph_node_item.gd.uid b/addons/SignalVisualizer/Visualizer/signal_graph_node_item.gd.uid
new file mode 100644
index 0000000..f506749
--- /dev/null
+++ b/addons/SignalVisualizer/Visualizer/signal_graph_node_item.gd.uid
@@ -0,0 +1 @@
+uid://c0n3sifmbiih0
diff --git a/addons/SignalVisualizer/Visualizer/signal_graph_node_item.tscn b/addons/SignalVisualizer/Visualizer/signal_graph_node_item.tscn
new file mode 100644
index 0000000..788ba36
--- /dev/null
+++ b/addons/SignalVisualizer/Visualizer/signal_graph_node_item.tscn
@@ -0,0 +1,43 @@
+[gd_scene load_steps=3 format=3 uid="uid://b2lwtwp6kpwtb"]
+
+[ext_resource type="Script" path="res://addons/SignalVisualizer/Visualizer/signal_graph_node_item.gd" id="1_jrd34"]
+[ext_resource type="Script" path="res://addons/SignalVisualizer/Visualizer/resizable_label.gd" id="2_4wwd5"]
+
+[node name="SignalItem" type="Control"]
+clip_contents = true
+custom_minimum_size = Vector2(51, 23)
+layout_mode = 3
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+size_flags_horizontal = 3
+size_flags_vertical = 3
+script = ExtResource("1_jrd34")
+
+[node name="HBoxContainer" type="HBoxContainer" parent="."]
+custom_minimum_size = Vector2(100, 50)
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+
+[node name="DescriptionLabel" type="Label" parent="HBoxContainer"]
+unique_name_in_owner = true
+custom_minimum_size = Vector2(100, 50)
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_vertical = 1
+vertical_alignment = 1
+clip_text = true
+script = ExtResource("2_4wwd5")
+
+[node name="OpenSignalInScriptButton" type="Button" parent="HBoxContainer"]
+layout_mode = 2
+text = "Open"
+flat = true
+
+[connection signal="pressed" from="HBoxContainer/OpenSignalInScriptButton" to="." method="_on_open_signal_in_script_button_pressed"]
diff --git a/addons/SignalVisualizer/Visualizer/signal_visualizer_dock.gd b/addons/SignalVisualizer/Visualizer/signal_visualizer_dock.gd
new file mode 100644
index 0000000..b3f6539
--- /dev/null
+++ b/addons/SignalVisualizer/Visualizer/signal_visualizer_dock.gd
@@ -0,0 +1,67 @@
+@tool
+extends Control
+
+signal open_script(node_name: String, method_signature: String)
+
+var SignalGraphNode = preload("res://addons/SignalVisualizer/Visualizer/signal_graph_node.tscn")
+var GraphNodeItem = preload("res://addons/SignalVisualizer/Visualizer/signal_graph_node_item.tscn")
+
+# Properties
+# |===================================|
+# |===================================|
+# |===================================|
+
+const SOURCE_COLOR: Color = Color.SKY_BLUE
+const DESTINATION_COLOR: Color = Color.CORAL
+const CONNECTION_TYPE: int = 0
+
+@onready var arrange_nodes_checkbox: CheckBox = %ArrangeNodesCheckBox
+@onready var signal_details_checkbox: CheckBox = %SignalDetailsCheckBox
+@onready var signal_tree: Tree = %SignalTree
+@onready var graph: GraphEdit = %Graph
+
+# Lifecycle
+# |===================================|
+# |===================================|
+# |===================================|
+
+
+
+# Signals
+# |===================================|
+# |===================================|
+# |===================================|
+
+func _on_clear_graph_button_pressed():
+ clear()
+
+func _on_generate_graph_button_pressed():
+ clear()
+
+ var scene_signal_graph = SignalGraphUtility.create_signal_graph_from_node(get_tree().edited_scene_root, true)
+ SignalGraphUtility.generate_signal_graph_nodes(scene_signal_graph, graph, _on_open_signal_in_script)
+ SignalGraphUtility.generate_signal_graph_tree(scene_signal_graph, signal_tree)
+
+ if arrange_nodes_checkbox.button_pressed:
+ graph.arrange_nodes()
+
+func _on_open_signal_in_script(data: SignalGraphNodeItem.Metadata):
+ open_script.emit(data.node_name, data.method_signature)
+
+# Methods
+# |===================================|
+# |===================================|
+# |===================================|
+
+func clear():
+ _clear_graph_nodes()
+ _clear_tree()
+
+func _clear_graph_nodes():
+ graph.clear_connections()
+ for child in graph.get_children():
+ if child is SignalGraphNode:
+ child.queue_free()
+
+func _clear_tree():
+ signal_tree.clear()
diff --git a/addons/SignalVisualizer/Visualizer/signal_visualizer_dock.gd.uid b/addons/SignalVisualizer/Visualizer/signal_visualizer_dock.gd.uid
new file mode 100644
index 0000000..ccd3b7e
--- /dev/null
+++ b/addons/SignalVisualizer/Visualizer/signal_visualizer_dock.gd.uid
@@ -0,0 +1 @@
+uid://bbd48wbihmuos
diff --git a/addons/SignalVisualizer/Visualizer/signal_visualizer_dock.tscn b/addons/SignalVisualizer/Visualizer/signal_visualizer_dock.tscn
new file mode 100644
index 0000000..c94817d
--- /dev/null
+++ b/addons/SignalVisualizer/Visualizer/signal_visualizer_dock.tscn
@@ -0,0 +1,78 @@
+[gd_scene load_steps=5 format=3 uid="uid://dppfamjc0ji40"]
+
+[ext_resource type="Script" path="res://addons/SignalVisualizer/Visualizer/signal_visualizer_dock.gd" id="1_akar5"]
+[ext_resource type="Texture2D" uid="uid://bmnff63evbdhv" path="res://addons/SignalVisualizer/Clear.svg" id="2_m8bsv"]
+[ext_resource type="Texture2D" uid="uid://bxj8ep08wbnm6" path="res://addons/SignalVisualizer/GraphEdit.svg" id="3_dtmqs"]
+
+[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_ae0jg"]
+
+[node name="SignalVisualizerDock" type="Control"]
+clip_contents = true
+custom_minimum_size = Vector2(2.08165e-12, 200)
+layout_mode = 3
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+script = ExtResource("1_akar5")
+
+[node name="VBoxContainer" type="VBoxContainer" parent="."]
+clip_contents = true
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+size_flags_horizontal = 3
+
+[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer"]
+clip_contents = true
+custom_minimum_size = Vector2(2.08165e-12, 50)
+layout_mode = 2
+theme_override_constants/separation = 8
+alignment = 2
+
+[node name="ArrangeNodesCheckBox" type="CheckBox" parent="VBoxContainer/HBoxContainer"]
+unique_name_in_owner = true
+layout_mode = 2
+text = "Arrange Nodes"
+
+[node name="SignalDetailsCheckBox" type="CheckBox" parent="VBoxContainer/HBoxContainer"]
+unique_name_in_owner = true
+layout_mode = 2
+text = "Signal Details"
+
+[node name="Panel" type="Panel" parent="VBoxContainer/HBoxContainer"]
+layout_mode = 2
+size_flags_horizontal = 3
+theme_override_styles/panel = SubResource("StyleBoxEmpty_ae0jg")
+
+[node name="ClearGraphButton" type="Button" parent="VBoxContainer/HBoxContainer"]
+layout_mode = 2
+text = "Clear Graph"
+icon = ExtResource("2_m8bsv")
+
+[node name="GenerateGraphButton" type="Button" parent="VBoxContainer/HBoxContainer"]
+layout_mode = 2
+text = "Generate Graph"
+icon = ExtResource("3_dtmqs")
+
+[node name="HSplitContainer" type="HSplitContainer" parent="VBoxContainer"]
+layout_mode = 2
+size_flags_vertical = 3
+
+[node name="SignalTree" type="Tree" parent="VBoxContainer/HSplitContainer"]
+unique_name_in_owner = true
+custom_minimum_size = Vector2(200, 2.08165e-12)
+layout_mode = 2
+column_titles_visible = true
+
+[node name="Graph" type="GraphEdit" parent="VBoxContainer/HSplitContainer"]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_vertical = 3
+
+[connection signal="pressed" from="VBoxContainer/HBoxContainer/ClearGraphButton" to="." method="_on_clear_graph_button_pressed"]
+[connection signal="pressed" from="VBoxContainer/HBoxContainer/GenerateGraphButton" to="." method="_on_generate_graph_button_pressed"]
diff --git a/addons/SignalVisualizer/plugin.cfg b/addons/SignalVisualizer/plugin.cfg
new file mode 100644
index 0000000..1b02071
--- /dev/null
+++ b/addons/SignalVisualizer/plugin.cfg
@@ -0,0 +1,7 @@
+[plugin]
+
+name="SignalVisualizer"
+description="Visual the current scene's signal connections as a graph. Debug the current running scene's signals with automatic logging in a new debugger panel."
+author="MiniGameDev"
+version="1.7.0"
+script="SignalVisualizer.gd"
diff --git a/project.godot b/project.godot
index 13b948e..3b014da 100644
--- a/project.godot
+++ b/project.godot
@@ -22,6 +22,7 @@ config/icon="uid://b2smanpdo1y5e"
Dialogic="*res://addons/dialogic/Core/DialogicGameHandler.gd"
InventoryManager="*res://scripts/CSharp/Common/Inventory/InventoryManager.cs"
+Signal_Debugger="*res://addons/SignalVisualizer/Debugger/SignalDebugger.gd"
[dialogic]
@@ -94,7 +95,7 @@ movie_writer/movie_file="/home/kaddi/Documents/Repos/Godot/Babushka/_clips/clip.
[editor_plugins]
-enabled=PackedStringArray("res://addons/anthonyec.camera_preview/plugin.cfg", "res://addons/dialogic/plugin.cfg")
+enabled=PackedStringArray("res://addons/SignalVisualizer/plugin.cfg", "res://addons/anthonyec.camera_preview/plugin.cfg", "res://addons/dialogic/plugin.cfg")
[file_customization]