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

Circular dependency between machine and scheduler #9

Closed mainej closed 2 years ago

mainej commented 2 years ago

There's a circular dependency between a machine and a scheduler.

transition is passed a machine, so in case the transition leads to delayed events, the machine has to carry the scheduler. But the scheduler needs to refer to the machine in its dispatch function. And in turn, that machine has to have a scheduler, so that the delayed events can schedule further delayed events.

Because of this dependency, it's tricky to construct machines that need schedulers. The work around is to create some sort of mutable reference to a machine. See for example, here, here, and here. This works, but isn't idiomatic Clojure.

One fix would be for the machine to pass itself to the delayed/schedule function (and on to the dispatch function):

;; delay.cljc
(defprotocol IScheduler
  (schedule [this machine state event delay])
  ;;              ^-- new
  (unschedule [this machine state event]))
  ;;                ^-- new

;; impl.cljc (edited for brevity)
(defn- execute-internal-action
  [{:as fsm :keys [scheduler]} state transition-event internal-action]
  ,,,
  (cond
    (= action :fsm/schedule-event)
    (fsm.d/schedule scheduler fsm state event event-delay)
    ;;                        ^-- new

    (= action :fsm/unschedule-event)
    (fsm.d/unschedule scheduler fsm state event)))
    ;;                          ^-- new

With this relationship, you could assoc a scheduler onto a machine, and not have to go through any further steps to ensure that the scheduler had access to the machine.