lucywang000 / clj-statecharts

State Machine and StateCharts for Clojure(Script)
https://lucywang000.github.io/clj-statecharts/
Eclipse Public License 1.0
229 stars 15 forks source link

Allow cancelling of `:delay` #19

Open brjann opened 4 months ago

brjann commented 4 months ago

The :after keyword can be used to specify a delayed transition, and allows a function that returns a dynamic delay. Would it make sense to cancel the delay if the :delay function returns nil?

LIke this (example from the docs)


(defn calculate-backoff
  "Exponential backoff, with a upper limit of 15 seconds. Stops retrying after 15 attemtps"
  [state & _]
 (when (> 15 (:retries 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}}}}```
lucywang000 commented 4 months ago

technically we can do that, but from a higher level i think it's better to modify the statecharts instead to distinguish "disconnected but retrying" and "disconnected and retry exhausted" states, e.g. by modifying disconnected as a compound state:

:disconnected  
{:initial :retrying
 :states {:retrying {:entry (assign update-retries)
                     :after [{:delay calculate-backoff :target :connecting}]}
          :retry-exhausted {}

and when transition to disconnected state, the machine should jump to retry-exhausted state by checking the :retries key using some guard

brjann commented 3 months ago

Thank you @lucywang000 for replying and sorry for the delay in responding.

My example was a bit contrived, I just wanted to base it on the existing example in the docs.

My real use case is a general FSM definition that runs several FSM instances. Some of them regularly reload their data whereas others don't. Each FSM instance's :context has a key that tells whether it's a reloading FSM or not. So the FSM definition has a :delay function that checks this key. If no reload should happen, my idea was that it should return nil. Now I instead return 0 and a :guard follows that cancels the transition if no reload should happen. Which is fine, but the possibility to cancel the :delay already in the function would be nice.