Delayed Transitions #
Basic Delayed Transitions #
To specify a transition that shall happen automatically after some time, use the
:after
keyword:
{:states
{:s1 {:after [{:delay 1000
:target :s2
:guard some-condition-fn
:actions some-action}
{:delay 2000
:target :s3
:actions some-action}]}}}
All transition features like guards and actions could be used here, as you can see in the code snippet above.
Dynamic Delay #
The amount of delay could be expressed as a context function.
For example, in a state machine that manages a websocket connection, the reconnection delay could be calculated as an exponential backoff.
(defn calculate-backoff
"Exponential backoff, with a upper limit of 15 seconds."
[state & _]
(-> (js/Math.pow 2 (:retries state))
(* 1000)
(min 15000)))
(defn update-retries [state & _]
(update state :retries inc))
;; Part of the machine definition
{:states
{:connecting {:entry try-connect
:on {:success-connect :connected}}
:disconnected {:entry (assign update-retries)
:after [{:delay calculate-backoff :target :connecting}]}
:connected {:on {:connection-closed :disconnected}}}}
Unit Testing a StateCharts That Uses Delayed Transitions #
When unit-testing a statecharts that uses delayed transitions, we don’t want to really wait for the exact delay to timeout.
To facilitate this, clj-statecharts provides a “simulated clock” to be used in
unit tests. This clock could be manipulated by calling its advance
method.
If you are interested, see this test case for inspiration.
Note
: if in your actions code you need to get the current time value, you
shall not use OS API (i.e. (js/Date.now)
or (System/currentTimeMillis)
), but
use (statecharts.clock/now)
. The latter returns the current time in
milliseconds, and when the clock is a simulated one, it would return the value
of the simulated time. This is the only way to make your statecharts
unit-testing-friendly.
Notes #
-
Delayed transitions currently only works in the CLJS. CLJ support is going to be added soon.
-
When using the Immutable API, the machine spec must have a
:scheduler
key that satisfies thestatecharts.delayed.Scheduler
protocol.