Skip to content

Commit

Permalink
feat: add warning when sending non-existing events.
Browse files Browse the repository at this point in the history
  • Loading branch information
derkork committed Nov 4, 2024
1 parent 7781a79 commit 9d6d07e
Show file tree
Hide file tree
Showing 8 changed files with 115 additions and 3 deletions.
6 changes: 5 additions & 1 deletion CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,14 @@ 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.17.1] - 2024-11-04
### Added
- The state chart will now issue a warning in debug builds when trying to send an event that is not defined in any transition of the state chart. This can help to catch typos in event names early on ([#150](https://github.com/derkork/godot-statecharts/issues/150)). This warning is now enabled by default but can be disabled per state chart in state chart settings.

## [0.17.0] - 2024-08-16
### Added
- The C# wrappers now provide type-safe events for all signals that the underlying nodes emit. This way you can simply subscribe to a signal using the familiar `+=` notation, e.g. `stateChart.StateEntered += OnStateEntered`. This makes it easier to work with the state chart from C# code. A big thanks goes out to [Marques Lévy](https://github.com/Prakkkmak) for suggesting this feature and providing a POC PR for it ([#126](https://github.com/derkork/godot-statecharts/pull/126)). Note that the usual rules for signals in C# apply, e.g. signal connections will not automatically be disconnected when the receiver is freed.
-

### Fixed
- The library now handles cases better where code tries to access a state chart that has been removed from the tree. This may happen when using Godot's `change_scene_to_file` or `change_scene_to_packed` functions. Debug output in these cases will no longer try to get full path names of nodes that have been removed from the tree. This should prevent errors and crashes in these cases ([#129](https://github.com/derkork/godot-statecharts/issues/129)).
- The error messages for evaluating expressions have been improved. They now show the expression that was evaluated and the result of the evaluation ([#138](https://github.com/derkork/godot-statecharts/issues/138))
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.17.0"
version="0.17.1"
script="godot_state_charts.gd"
22 changes: 22 additions & 0 deletions addons/godot_state_charts/state_chart.gd
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ extends Node
## The the remote debugger
const DebuggerRemote = preload("utilities/editor_debugger/editor_debugger_remote.gd")

## The state chart utility class.
const StateChartUtil = preload("utilities/state_chart_util.gd")

## Emitted when the state chart receives an event. This will be
## emitted no matter which state is currently active and can be
## useful to trigger additional logic elsewhere in the game
Expand All @@ -20,10 +23,19 @@ const DebuggerRemote = preload("utilities/editor_debugger/editor_debugger_remote
## while another is still processing, it will be enqueued.
signal event_received(event:StringName)

@export_group("Debugging")
## Flag indicating if this state chart should be tracked by the
## state chart debugger in the editor.
@export var track_in_editor:bool = false

## If set, the state chart will issue a warning when trying to
## send an event that is not configured for any transition of
## the state chart. It is usually a good idea to leave this
## enabled, but in certain cases this may get in the way so
## you can disable it here.
@export var warn_on_sending_unknown_events:bool = true

@export_group("")
## Initial values for the expression properties. These properties can be used in expressions, e.g
## for guards or transition delays. It is recommended to set an initial value for each property
## you use in an expression to ensure that this expression is always valid. If you don't set
Expand Down Expand Up @@ -54,6 +66,7 @@ var _queued_transitions:Array[Dictionary] = []
var _transitions_processing_active:bool = false

var _debugger_remote:DebuggerRemote = null
var _valid_event_names:Array[StringName] = []


func _ready() -> void:
Expand All @@ -70,6 +83,12 @@ func _ready() -> void:
if not child is StateChartState:
push_error("StateMachine's child must be a State")
return

# in debug builds, collect a list of valid event names
# to warn the developer when using an event that doesn't
# exist.
if OS.is_debug_build():
_valid_event_names = StateChartUtil.events_of(self)

# set the initial expression properties
if initial_expression_properties != null:
Expand Down Expand Up @@ -105,6 +124,9 @@ func send_event(event:StringName) -> void:
if not is_instance_valid(_state):
push_error("State chart has no root state. Ignoring call to `send_event`.")
return

if warn_on_sending_unknown_events and event != "" and OS.is_debug_build() and not _valid_event_names.has(event):
push_warning("State chart does not have an event '", event , "' defined. Sending this event will do nothing.")

_queued_events.append(event)
_run_changes()
Expand Down
1 change: 1 addition & 0 deletions godot-state-charts.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,7 @@
<Content Include="tests\test_any_of_guard.gd" />
<Content Include="tests\test_automatic_transition_leaving_compound_state.gd" />
<Content Include="tests\test_automatic_transition_on_property_change.gd" />
<Content Include="tests\test_multiple_delayed_transition.gd" />
<Content Include="tests\test_parallel_state_event_handling.gd" />
<Content Include="tests\test_automatic_transition_on_state_enter.gd" />
<Content Include="tests\test_immediate_condition_state.gd" />
Expand Down
4 changes: 3 additions & 1 deletion tests/framework/state_chart_test_base.gd
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@ func before_each() -> void:


func after_each() -> void:
remove_child(_chart)
var parent = _chart.get_parent()
if is_instance_valid(parent):
parent.remove_child(_chart)
_chart.free()

func assert_active(state: StateChartState) -> void:
Expand Down
35 changes: 35 additions & 0 deletions tests/test_multiple_delayed_transition.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
extends StateChartTestBase

# Checks that multiple delayed transitions work.
# https://github.com/derkork/godot-statecharts/issues/148
func test_multiple_delayed_transitions_work():
var root := compound_state("root")

var think := atomic_state("think", root)
var explore := atomic_state("explore", root)
var inspect := atomic_state("inspect", root)

transition(think, explore, "pick_destination")
transition( explore, inspect, "target_reached", "1.0")
transition( explore, think, "", "2.0")

await finish_setup()

assert_active(think)

# when I pick a destination
send_event("pick_destination")

# then I should be exploring
assert_active(explore)

# when I now reach the target
await wait_seconds(1.1, "wait for target reached")
send_event("target_reached")

# then after 1 second I should be inspecting
await wait_seconds(1.1, "wait for inspect")
assert_active(inspect)



26 changes: 26 additions & 0 deletions tests/test_parallel_state_initialization.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
extends StateChartTestBase

# https://github.com/derkork/godot-statecharts/issues/143
# currently disabled because it doesn't work yet.
func __test_parallel_state_initialization():
var root := parallel_state("root")
var a := atomic_state("a", root)
var b := compound_state("b", root)
var b1 := atomic_state("b1", b)
var b2 := atomic_state("b2", b)

transition(b1, b2, "some_event")
a.state_entered.connect(func():
send_event("some_event")
)


await finish_setup()

# a should be active right now..
assert_active(a)

# and b2 should be active
assert_active(b2)


22 changes: 22 additions & 0 deletions tests/test_warning_on_nonexisting_event.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
extends StateChartTestBase

func test_warning_on_nonexisting_event():
var root = compound_state("root")
var a = atomic_state("a", root)
var b = atomic_state("b", root)
transition(a, b, "some_event")
transition(b, a, "some_event")
await finish_setup()

assert_active(a)

# when i send the correct event, i move to state b
send_event("some_event")
assert_active(b)

# when i send a wrong event, nothing happens
send_event("narf")

assert_active(b)


0 comments on commit 9d6d07e

Please sign in to comment.