Get Started #
Installation #
Add the dep to to your project.clj/deps.edn/shadow-cljs.edn:
The below document assumes you have a require
statement like this:
(require '[statecharts.core :as fsm])
Two layers of APIs #
There are two layers of APIs in clj-statecharts:
- The Immutable API that deals with machines and states directly. This layer is purely functional.
- The Service API are the higher level one. It is built on top of the immutable API, stateful and easier to get started.
Part 1. The Immutable API #
Simply define a machine, which includes:
- the states and transitions on each state
- the initial state value
- the initial context
And use the fsm/initialize
and fsm/transition
functions.
;; import proper ns
(ns statecharts-samples.basic-immuatable
(:require [statecharts.core :as fsm]))
;; define the machine
(def machine
(fsm/machine
{:id :lights
:initial :red
:context nil
:states
{:green {:on
{:timer {:target :yellow
:actions (fn [& _]
(println "transitioned to :yellow!"))
}}}
:yellow {:on
{:timer :red}}
:red {:on
{:timer :green}}}
:on {:power-outage :red}
}))
(def s1 (fsm/initialize machine)) ; {:_state :red}
(def s2 (fsm/transition machine s1 {:type :timer})) ; {:_state :green}
(def s3 (fsm/transition machine s2 {:type :timer})) ; {:_state :yellow}
(fsm/initialize machine) #
Returns the initial state of the machine. It also executes all the entry actions of the initial states, if any.
If you do not want these actions to be executed, use (fsm/initialize machine {:exec false})
instead. The action functions would be collected in the _actions
key of the new state map. For example, the test code of clj-statecharts uses this
feature to make assertions to ensure correct actions are collected during
transitions.
(fsm/transition machine state event) #
Returns the next state based the current state & event. It also executes all the entry/exit/transition actions.
If you do not want these actions to be executed, use (fsm/transition machine state event {:exec false})
instead.
Part 2. The Service API #
The immutable API provides a clean interface so you can integrate it into your own state management system like re-frame.
However, sometimes it’s more convenient to provide a higher level API that could manage the state out of the box. Here comes the service API.
The usage pattern for the service API is very simple:
- Define a machine
- Define a service that runs the machine
- Send events to trigger transitions on this machine.
- Use functions like
fsm/state
orfsm/value
to get the state of the service.
;; import proper ns
(ns statecharts-samples.basic
(:require [statecharts.core :as fsm]))
;; define the machine
(def machine
(fsm/machine
{:id :lights
:initial :red
:context nil
:states
{:green {:on
{:timer {:target :yellow
:actions (fn [& _]
(println "transitioned to :yellow!"))
}}}
:yellow {:on
{:timer :red}}
:red {:on
{:timer :green}}}
:on {:power-outage :red}
}))
;; define the service
(def service (fsm/service machine))
;; start the service
(fsm/start service)
;; prints :red
(println (fsm/value service))
;; send events to trigger transitions
(fsm/send service :timer)
;; prints :green
(println (fsm/value service))
(fsm/send service :timer)
;; prints :yellow
(println (fsm/value service))