derkork / godot-statecharts

A state charts extension for Godot 4
MIT License
821 stars 44 forks source link

Delayed Automatic Transition Priorities #148

Open laurentsenta opened 1 week ago

laurentsenta commented 1 week ago

Hi there, thanks for maintaining the library,

I'm confused how to deal with delayed automatic transitions,

A behavior I'm trying to implement: CleanShot 2024-10-08 at 16 01 48@2x

My main issue is with the Explore state and transition:

on_target_reached is a transition with event: target_reached and delay: 4.0s on_timeout_think_again is a transition with no event and delay: 8.0s

My expectation:

What happens (0.17.0):

It seems like a bug, doc says:

If a transition has a time delay, it will be marked as pending and executed after the time delay has elapsed but only if the state to which the transition belongs is still active at this time and was not left temporarily. Only one transition can ever be active or pending for any given state. So if another transition is executed for a state while one is pending, the pending transition will be discarded.

Would it be possible to configure/fix this behavior?

derkork commented 1 week ago

Your expectations are correct here, whenever a new transition is taken while another one is pending this new transition should replace the pending one.

However I can't seem to reproduce the behaviour you have observed. I have written a test to check for this:

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)

and the test works with 0.17.0. So I also re-built the setup manually with some buttons to trigger the events and there it also works as expected.

https://github.com/user-attachments/assets/5e3e0e9d-cd17-4468-ae7b-186ab6f59539

Could you check the history tab in the state chart debugger? This tab shows all incoming events and reactions to them, so maybe you can see there what causes your problem. My guess would be a typo in the event name for on_target_reached.