Skip to content

Commit

Permalink
fix: state chart debugger performance
Browse files Browse the repository at this point in the history
fixes #24
  • Loading branch information
derkork committed Aug 21, 2023
1 parent dc877b1 commit 5ed2824
Show file tree
Hide file tree
Showing 5 changed files with 173 additions and 24 deletions.
12 changes: 12 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,18 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).


## [0.4.1] - 2023-08-21
### Fixed
- The state chart debugger's performance has been vastly improved, so it should no longer affect the framerate. The history field is now only updated twice a second rather than every frame and only when it is actually visible. Also history is now held in a ring buffer which helps to speedily add and overwrite history entries as well as keeping memory usage in check ([#24](https://github.com/derkork/godot-statecharts/issues/24)).

### Added
- You can now filter out information from the state chart debugger. For now you can ignore events, state changes and transitions. These settings can also be changed at runtime, so you can filter out information that is not relevant for the current situation.

### Removed
- The _Auto Track State Changes_ setting has been removed from the state charts debugger, as its functionality was made obsolete by the new filter settings.


## [0.4.0] - 2023-08-17
### Breaking changes

Expand Down
2 changes: 1 addition & 1 deletion addons/godot_state_charts/plugin.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@
name="Godot State Charts"
description="A simple, yet powerful state charts library for Godot"
author="Jan Thomä & Contributors"
version="0.3.1"
version="0.4.1"
script="godot_state_charts.gd"
48 changes: 48 additions & 0 deletions addons/godot_state_charts/utilities/ring_buffer.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@


## The content of the ring buffer
var _content:Array[String] = []

## The current index in the ring buffer
var _index = 0

## The size of the ring buffer
var _size = 0

## Whether the buffer is fully populated
var _filled = false


func _init(size:int = 300):
_size = size
_content.resize(size)


## Adds an item to the ring buffer
func append(value:String):
_content[_index] = value
if _index + 1 < _size:
_index += 1
else:
_index = 0
_filled = true


## Joins the items of the ring buffer into a big string
func join():
var result = ""
if _filled:
# start by _index + 1, run to the end and then continue from the start
for i in range(_index, _size):
result += _content[i]

# when not filled, just start at the beginning
for i in _index:
result += _content[i]

return result


func clear():
_index = 0
_filled = false
96 changes: 73 additions & 23 deletions addons/godot_state_charts/utilities/state_chart_debugger.gd
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
@icon("state_chart_debugger.svg")
extends Control

var RingBuffer = preload("ring_buffer.gd")

## Whether or not the debugger is enabled.
@export var enabled:bool = true:
set(value):
enabled = value
if not Engine.is_editor_hint():
_setup_processing(enabled)

## Whether or not the debugger should automatically track state changes.
@export var auto_track_state_changes:bool = true


## The initial node that should be watched. Optional, if not set
## then no node will be watched. You can set the node that should
Expand All @@ -21,13 +20,29 @@ extends Control
## for best performance.
@export var maximum_lines:int = 300

## If set to true, events will not be printed in the history panel.
## If you send a large amount of events then this may clutter the
## output so you can disable it here.
@export var ignore_events:bool = false

## If set to true, state changes will not be printed in the history
## panel. If you have a large amount of state changes, this may clutter
## the output so you can disable it here.
@export var ignore_state_changes:bool = false

## If set to true, transitions will not be printed in the history.
@export var ignore_transitions:bool = false

## The tree that shows the state chart.
@onready var _tree:Tree = %Tree
## The text field with the history.
@onready var _historyEdit:TextEdit = %HistoryEdit
@onready var _history_edit:TextEdit = %HistoryEdit

# the number of lines in the edit
var _lines = 0
# We store history in a ring buffer
var _buffer = null
# Flag indicating if we have stuff in the ring buffer that needs to
# go into the text field
var _dirty = false

# the state chart we track
var _state_chart:StateChart
Expand All @@ -39,25 +54,28 @@ var _connected_states:Array[State] = []
func _ready():
# always run, even if the game is paused
process_mode = Node.PROCESS_MODE_ALWAYS

# initialize the buffer
_buffer = RingBuffer.new()

%CopyToClipboardButton.pressed.connect(func (): DisplayServer.clipboard_set(_historyEdit.text))
%CopyToClipboardButton.pressed.connect(func (): DisplayServer.clipboard_set(_history_edit.text))
%ClearButton.pressed.connect(_clear_history)

var to_watch = get_node_or_null(initial_node_to_watch)
if is_instance_valid(to_watch):
debug_node(to_watch)

# mirror the editor settings
%IgnoreEventsCheckbox.set_pressed_no_signal(ignore_events)
%IgnoreStateChangesCheckbox.set_pressed_no_signal(ignore_state_changes)
%IgnoreTransitionsCheckbox.set_pressed_no_signal(ignore_transitions)



## Adds an item to the history list.
func add_history_entry(text:String):
_historyEdit.text += "[%s]: %s \n" % [Engine.get_process_frames(), text]
if _lines + 1 < maximum_lines:
_lines += 1
else:
# cut the first line from the text
_historyEdit.remove_text(0,0,1,0)

_historyEdit.scroll_vertical = _historyEdit.get_line_count() - 1

_buffer.append("[%s]: %s \n" % [Engine.get_process_frames(), text])
_dirty = true

## Sets up the debugger to track the given state chart. If the given node is not
## a state chart, it will search the children for a state chart. If no state chart
Expand Down Expand Up @@ -117,7 +135,8 @@ func _setup_processing(enabled:bool):
func _disconnect_all_signals():
if is_instance_valid(_state_chart):
_state_chart._before_transition.disconnect(_on_before_transition)
_state_chart.event_received.disconnect(_on_event_received)
if not ignore_events:
_state_chart.event_received.disconnect(_on_event_received)

for state in _connected_states:
# in case the state has been destroyed meanwhile
Expand All @@ -130,9 +149,6 @@ func _disconnect_all_signals():
func _connect_all_signals():
_connected_states.clear()

if not auto_track_state_changes:
return

if not is_instance_valid(_state_chart):
return

Expand Down Expand Up @@ -203,21 +219,55 @@ func _collect_active_states(root:Node, parent:TreeItem):


func _clear_history():
_historyEdit.text = ""
_lines = 0

_history_edit.text = ""
_buffer.clear()
_dirty = false

func _on_before_transition(transition:Transition, source:State):
if ignore_transitions:
return

add_history_entry("Transition: %s from %s to %s" % [transition.name, _state_chart.get_path_to(source), _state_chart.get_path_to(transition.resolve_target())])


func _on_event_received(event:StringName):
if ignore_events:
return

add_history_entry("Event received: %s" % event)


func _on_state_entered(state:State):
if ignore_state_changes:
return

add_history_entry("Enter: %s" % state.name)


func _on_state_exited(state:State):
if ignore_state_changes:
return

add_history_entry("exiT : %s" % state.name)


func _on_timer_timeout():
# ignore the timer if the history edit isn't visible
if not _history_edit.visible or not _dirty:
return

# fill the history field
_history_edit.text = _buffer.join()
_history_edit.scroll_vertical = _history_edit.get_line_count() - 1
_dirty = false


func _on_ignore_events_checkbox_toggled(button_pressed):
ignore_events = button_pressed


func _on_ignore_state_changes_checkbox_toggled(button_pressed):
ignore_state_changes = button_pressed

func _on_ignore_transitions_checkbox_toggled(button_pressed):
ignore_transitions = button_pressed
39 changes: 39 additions & 0 deletions addons/godot_state_charts/utilities/state_chart_debugger.tscn
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ script = ExtResource("1_i74os")
layout_mode = 2

[node name="StateChart" type="MarginContainer" parent="TabContainer"]
visible = false
layout_mode = 2
theme_override_constants/margin_left = 5
theme_override_constants/margin_top = 5
Expand Down Expand Up @@ -55,3 +56,41 @@ text = "Clear"
unique_name_in_owner = true
layout_mode = 2
text = "Copy to Clipboard"

[node name="Settings" type="MarginContainer" parent="TabContainer"]
layout_mode = 2
theme_override_constants/margin_left = 5
theme_override_constants/margin_top = 5
theme_override_constants/margin_right = 5
theme_override_constants/margin_bottom = 5

[node name="VBoxContainer" type="VBoxContainer" parent="TabContainer/Settings"]
layout_mode = 2
theme_override_constants/separation = 4

[node name="IgnoreEventsCheckbox" type="CheckBox" parent="TabContainer/Settings/VBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
tooltip_text = "Do not show events in the history."
text = "Ignore events"

[node name="IgnoreStateChangesCheckbox" type="CheckBox" parent="TabContainer/Settings/VBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
tooltip_text = "Do not show state changes in the history."
text = "Ignore state changes"

[node name="IgnoreTransitionsCheckbox" type="CheckBox" parent="TabContainer/Settings/VBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
tooltip_text = "Do not show transitions in the history."
text = "Ignore transitions"

[node name="Timer" type="Timer" parent="."]
wait_time = 0.5
autostart = true

[connection signal="toggled" from="TabContainer/Settings/VBoxContainer/IgnoreEventsCheckbox" to="." method="_on_ignore_events_checkbox_toggled"]
[connection signal="toggled" from="TabContainer/Settings/VBoxContainer/IgnoreStateChangesCheckbox" to="." method="_on_ignore_state_changes_checkbox_toggled"]
[connection signal="toggled" from="TabContainer/Settings/VBoxContainer/IgnoreTransitionsCheckbox" to="." method="_on_ignore_transitions_checkbox_toggled"]
[connection signal="timeout" from="Timer" to="." method="_on_timer_timeout"]

0 comments on commit 5ed2824

Please sign in to comment.