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]