### FSM Paths Example Source: https://hexdocs.pm/finitomata/Finitomata.Pool.md Illustrates the possible paths and transitions within the FSM, including start, idle, ready, and done states. ```elixir ↝‹:* ⇥ "__start__" ↦ :idle ⇥ "init" ↦ :ready ⇥ "stop" ↦ :done ⇥ "__end__" ↦ :*› ↝‹:* ⇥ "__start__" ↦ :idle ⇥ "do" ↦ :ready ⇥ "stop" ↦ :done ⇥ "__end__" ↦ :*› ``` -------------------------------- ### Start and Use Finitomata Source: https://hexdocs.pm/finitomata/cheatsheet.md Demonstrates how to start the Finitomata supervision tree, start an FSM instance with initial state payload, trigger a transition with event payload, and query the current state. ```elixir # Start the supervision tree (or embed Finitomata.child_spec() into yours) {:ok, _pid} = Finitomata.start_link() # Start an FSM instance Finitomata.start_fsm(MyFSM, "my_fsm_1", %{foo: :bar}) # Trigger a transition Finitomata.transition("my_fsm_1", {:process, %{some: :data}}) # Query the state Finitomata.state("my_fsm_1") ``` -------------------------------- ### Start and Transition FSM Source: https://hexdocs.pm/finitomata/Finitomata.md Demonstrates starting a Finitomata FSM and performing transitions. Ensure the FSM module is defined elsewhere. ```elixir {:ok, _pid} = Finitomata.start_link() Finitomata.start_fsm MyFSM, "My first FSM", %{foo: :bar} Finitomata.transition "My first FSM", {:to_s2, nil} Finitomata.state "My first FSM" #⇒ %Finitomata.State{current: :s2, history: [:s1], payload: %{foo: :bar}} Finitomata.allowed? "My first FSM", :* #⇒ true Finitomata.responds? "My first FSM", :to_s2 #⇒ false Finitomata.transition "My first FSM", {:__end__, nil} #⇒ [info] [â—‰ ⇄] [state: %Finitomata.State{current: :s2, history: [:s1], payload: %{foo: :bar}}] Finitomata.alive? "My first FSM" #⇒ false ``` -------------------------------- ### Supervisor Child Specification Example Source: https://hexdocs.pm/finitomata/Finitomata.Supervisor.md Example of a child specification for starting a module under a supervisor, referencing `Supervisor`. ```elixir Returns a specification to start this module under a supervisor. See `Supervisor`. ``` -------------------------------- ### Start and Interact with Finitomata.Accessible Source: https://hexdocs.pm/finitomata/Finitomata.Accessible.md Demonstrates starting a Finitomata.Accessible instance, looking it up, updating its state, and retrieving its current state. Requires Finitomata.Supervisor to be running. ```elixir iex|🌢|1 â–¶ Finitomata.Accessible.start_link(type: Finitomata, implementation: L, id: L1) {:ok, #PID<0.214.0>} ``` ```elixir iex|🌢|2 â–¶ sup = Finitomata.Accessible.lookup(L1) %Finitomata.Accessible{ type: Finitomata, id: L1, implementation: Finitomata.Test.Log, last_event: nil, cached_pid: #PID<0.214.0> } ``` ```elixir iex|🌢|3 â–¶ put_in(sup, ["MyFSM"], :accept) 07:16:34.736 [debug] [→ ↹] […] 07:16:34.738 [debug] [✓ ⇄] with: [current: :*, event: :__start__, event_payload: %{payload: nil, __retries__: 1}, state: %{}] 07:16:34.738 [debug] [← ↹] […] 07:16:34.738 [debug] [→ ↹] […] 07:16:34.739 [debug] [✓ ⇄] with: [current: :idle, event: :accept, event_payload: nil, state: %{}] 07:16:34.739 [debug] [← ↹] […] %Finitomata.Accessible{ type: Finitomata, id: L1, implementation: Finitomata.Test.Log, last_event: {"MyFSM", :accept}, cached_pid: #PID<0.214.0> } ``` ```elixir iex|🌢|4 â–¶ get_in(sup, ["MyFSM"]) #Finitomata<[ name: {Finitomata.L1.Registry, "MyFSM"}, state: [current: :accepted, previous: :idle, payload: %{}], internals: [errored?: false, persisted?: false, timer: false] ]> ``` -------------------------------- ### Start and Manage a Finitomata FSM Source: https://hexdocs.pm/finitomata/index.html Demonstrates how to start a Finitomata FSM, perform transitions, and query its state. Ensure the FSM module is defined elsewhere. ```elixir {:ok, _pid} = Finitomata.start_link() Finitomata.start_fsm MyFSM, "My first FSM", %{foo: :bar} Finitomata.transition "My first FSM", {:to_s2, nil} Finitomata.state "My first FSM" #⇒ %Finitomata.State{current: :s2, history: [:s1], payload: %{foo: :bar}} Finitomata.allowed? "My first FSM", :* #⇒ true Finitomata.responds? "My first FSM", :to_s2 #⇒ false Finitomata.transition "My first FSM", {:__end__, nil} #⇒ [info] [◉ ⇄] [state: %Finitomata.State{current: :s2, history: [:s1], payload: %{foo: :bar}}] Finitomata.alive? "My first FSM" #⇒ false ``` -------------------------------- ### FSM Syntax: State Diagram (PlantUML) Source: https://hexdocs.pm/finitomata/cheatsheet.md Example of FSM definition using the `:state_diagram` (PlantUML) syntax. Explicit start and end transitions are required. ```text [*] --> idle : wake idle --> ready : start ready --> done : process done --> [*] : finish ``` -------------------------------- ### Start New FSM Instance Source: https://hexdocs.pm/finitomata/Finitomata.Supervisor.md Callback to start a new FSM instance within a specified ID branch. Similar semantics to `DynamicSupervisor.start_child/2`. ```elixir @callback start_fsm( id :: Finitomata.id(), fsm_name :: Finitomata.fsm_name(), implementation :: Finitomata.implementation(), payload :: Finitomata.payload() ) :: DynamicSupervisor.on_start_child() ``` -------------------------------- ### Finitomata.Cache.start_link/1 Source: https://hexdocs.pm/finitomata/Finitomata.Cache.md Starts a Finitomata.Cache instance as a supervised process. ```APIDOC ## `start_link` *since 0.26.0* ```elixir @spec start_link( id: term(), type: term(), ttl: pos_integer(), live?: boolean(), getter: (term() -> term()) | term() ) :: Supervisor.on_start() ``` Supervision tree embedder. ## Options to `Finitomata.Cache.start_link/1` * `:id` (`t:term/0`) - Required. The unique `ID` of this _Finitomata_ “branch,” when `nil` the `Finitomata.Cache` value would be used * `:type` - The actual `Finitomata.Supervisor` implementation (typically, `Finitomata` or `Infinitomata`) The default value is `Infinitomata`. * `:ttl` (`t:pos_integer/0`) - Required. The default time-to-live value in seconds, after which the value would be either revalidated or discarded * `:live?` (`t:boolean/0`) - When `true`, the value will be automatically renewed upon expiration (and discarded otherwise) The default value is `false`. * `:getter` - The shared for all instances getter returning a value based on the name of the instance, used as a key The default value is `nil`. ``` -------------------------------- ### FSM Syntax: Flowchart (Default) Source: https://hexdocs.pm/finitomata/cheatsheet.md Example of FSM definition using the default `:flowchart` (Mermaid) syntax. Starting and ending transitions are implicit. ```text idle --> |wake| ready ready --> |process| done ready --> |fail| broken ``` -------------------------------- ### Interact with FSM Instance Source: https://hexdocs.pm/finitomata/readme.md Demonstrates starting and interacting with a Finitomata FSM instance. Includes starting the FSM, performing transitions, querying state, checking allowed events, and ending the FSM. ```elixir # or embed into supervision tree using `Finitomata.child_spec()` {:ok, _pid} = Finitomata.start_link() Finitomata.start_fsm MyFSM, "My first FSM", %{foo: :bar} Finitomata.transition "My first FSM", {:to_s2, nil} Finitomata.state "My first FSM" #⇒ %Finitomata.State{current: :s2, history: [:s1], payload: %{foo: :bar}} Finitomata.allowed? "My first FSM", :* #⇒ true Finitomata.responds? "My first FSM", :to_s2 #⇒ false Finitomata.transition "My first FSM", {:__end__, nil} # to final state #⇒ [info] [â—‰ ⇄] [state: %Finitomata.State{current: :s2, history: [:s1], payload: %{foo: :bar}}] Finitomata.alive? "My first FSM" #⇒ false ``` -------------------------------- ### Start FSM in Distributed Environment Source: https://hexdocs.pm/finitomata/Infinitomata.md Starts an FSM in a distributed environment using Infinitomata. This function is the distributed counterpart to Finitomata.start_fsm/4 and requires cluster information to be properly initialized. ```elixir @spec start_fsm(Finitomata.id(), Finitomata.fsm_name(), module(), any()) :: DynamicSupervisor.on_start_child() ``` -------------------------------- ### FSM Loops Example Source: https://hexdocs.pm/finitomata/Finitomata.Pool.md Demonstrates a loop within the FSM, specifically the 'do' transition from the 'ready' state back to 'ready'. ```elixir ↺‹:ready ⇥ "do" ↦ :ready› ``` -------------------------------- ### Get Entry State Source: https://hexdocs.pm/finitomata/Finitomata.Transition.md Returns the state that is entered after starting a transition. This is the state that follows the initial transition. ```elixir @spec entry(:state | :transition, [t()]) :: state() ``` ```elixir iex> {:ok, transitions} = ...> Finitomata.PlantUML.parse("[*] --> entry : foo\nentry --> exit : go\nexit --> [*] : terminate") ...> Finitomata.Transition.entry(transitions) :entry ``` -------------------------------- ### Testing FSM with Finitomata.ExUnit Helpers Source: https://hexdocs.pm/finitomata/Finitomata.ExUnit.md Utilizes `Finitomata.ExUnit` for a more concise testing experience, reducing boilerplate. The `setup_finitomata` macro simplifies FSM and context setup. ```elixir describe "Turnstile" do setup_finitomata do parent = self() initial_passengers = 42 [ fsm: [implementation: Turnstile, payload: %{data: %{passengers: initial_passengers}})], context: [passengers: initial_passengers] ] end test_path "respectful passenger", %{passengers: initial_passengers} do :coin_in -> assert_state :opened do assert_payload do data.passengers ~> ^initial_passengers end end :walk_in -> assert_state :closed do assert_payload do data.passengers ~> one_more when one_more == 1 + initial_passengers end end :switch_off -> assert_state :switched_off assert_state :* end ``` -------------------------------- ### start_fsm Source: https://hexdocs.pm/finitomata/Finitomata.Supervisor.md Starts a new FSM instance within a specified branch, similar to `DynamicSupervisor.start_child/2`. ```APIDOC ## start_fsm ### Description Starts the new _FSM_ instance under `id` “branch,” the semantics is similar to `DynamicSupervisor.start_child/2`. ### Callback Signature ```elixir start_fsm( id :: Finitomata.id(), fsm_name :: Finitomata.fsm_name(), implementation :: Finitomata.implementation(), payload :: Finitomata.payload() ) :: DynamicSupervisor.on_start_child() ``` ``` -------------------------------- ### Start Link for Finitomata.Accessible Source: https://hexdocs.pm/finitomata/Finitomata.Accessible.md Defines the `start_link/1` function for embedding Finitomata.Supervisor into a supervision tree. It accepts options such as `:type` and `:implementation`. ```elixir start_link/1 ``` -------------------------------- ### Start FSM Alone Source: https://hexdocs.pm/finitomata/Finitomata.Pool.md Starts a single FSM with a given name and payload. Typically used within a supervision tree or via `Finitomata.start_fsm/4`. ```elixir start_link() ``` -------------------------------- ### Finitomata.Transition.entry/1 Source: https://hexdocs.pm/finitomata/Finitomata.Transition.md Returns the state after starting a transition, known as the 'entry' state. ```APIDOC ## entry/1 ### Description Returns the state after starting one, so-called `entry` state. ### Spec ```elixir @spec entry(:state | :transition, [t()]) :: state() ``` ### Example ```elixir iex> {:ok, transitions} = Finitomata.PlantUML.parse("[*] --> entry : foo\nentry --> exit : go\nexit --> [*] : terminate") Finitomata.Transition.entry(transitions) :entry ``` ``` -------------------------------- ### Mermaid/flowchart Syntax Source: https://hexdocs.pm/finitomata/index.html Example of FSM definition using Mermaid or :flowchart syntax. This format implicitly defines start and end states and transitions. ```mermaid s1 --> |to_s2| s2 s1 --> |to_s3| s3 ``` -------------------------------- ### start_link/1 Source: https://hexdocs.pm/finitomata/Finitomata.Accessible.md Starts and links a Finitomata.Supervisor implementation into a supervision tree. It accepts options to configure the FSM type, implementation module, and a unique ID. ```APIDOC ## `start_link/1` Supervision tree embedder. ### Options to `Finitomata.Accessible.start_link/1` * `:type` - The actual `Finitomata.Supervisor` implementation (typically, `Finitomata` or `Infinitomata`) The default value is `Infinitomata`. * `:implementation` (`t:atom/0`) - Required. The actual implementation of _FSM_ (the module, having `use Finitomata` clause) * `:id` (`t:term/0`) - The unique `ID` of this _Finitomata_ “branch,” when `nil` the `implementation` value would be used The default value is `nil`. ``` -------------------------------- ### Finitomata Initialization Chain Pattern Source: https://hexdocs.pm/finitomata/cheatsheet.md Illustrates an FSM that auto-transitions through initial setup states (`idle` -> `configuring` -> `ready`) before waiting for an external event (`:process`). ```elixir idle --> |init!| configuring configuring --> |configure!| ready ready --> |process| done ``` -------------------------------- ### initialize/2 Source: https://hexdocs.pm/finitomata/Finitomata.Pool.md Initializes the pool. This is explicitly separated to allow for external service initialization before pool setup. ```APIDOC ## `initialize/2` ### Description Initializes the pool. This is explicitly separated to allow for external service initialization before pool setup. ### Function Signature `initialize(pool, opts)` ### Parameters - `pool` (Finitomata.Pool.t()): The pool to initialize. - `opts` (keyword list): Options for initialization. ``` -------------------------------- ### Finitomata.start_fsm/4 Source: https://hexdocs.pm/finitomata/cheatsheet.md Starts a new Finitomata FSM process. It requires the FSM module, a name for the FSM, and an initial payload. ```APIDOC ## Finitomata.start_fsm/4 ### Description Starts a new Finitomata FSM process. It requires the FSM module, a name for the FSM, and an initial payload. The `:parent` key in the payload can be used to override the default parent process. ### Method `Finitomata.start_fsm/4` ### Parameters #### Path Parameters None #### Query Parameters None #### Request Body None ### Request Example ```elixir # Starting with a basic payload Finitomata.start_fsm(MyFSM, "my_fsm", %{initial_data: :value}) # Starting with a specified parent PID Finitomata.start_fsm(MyFSM, "my_fsm", %{parent: some_pid, foo: :bar}) ``` ### Response #### Success Response (Implicitly handled by Finitomata) #### Response Example None ``` -------------------------------- ### Finitomata.ClusterInfo.init/1 Source: https://hexdocs.pm/finitomata/Finitomata.ClusterInfo.md Initializes the cluster info lookup implementation. This call is mandatory before starting an FSM to ensure consistency. ```APIDOC ## `init/1` ### Description Call this function to select an implementation of a cluster lookup. ### Parameters #### Path Parameters - **module** (module) - Required - The module implementing the desired cluster info behaviour. ``` -------------------------------- ### start_fsm Source: https://hexdocs.pm/finitomata/Infinitomata.md Starts the FSM in the distributed environment. This function is the distributed version of Finitomata.start_fsm/4. ```APIDOC ## `start_fsm` ### Description Starts the FSM in the distributed environment. See `Finitomata.start_fsm/4` for docs and options. ### Method `Infinitomata.start_fsm/4` ### Parameters - `id` (Finitomata.id()): The ID of the FSM. - `fsm_name` (Finitomata.fsm_name()): The name of the FSM. - `module` (module()): The module implementing the FSM. - `args` (any()): Arguments to pass to the FSM. ### Returns `DynamicSupervisor.on_start_child()` ``` -------------------------------- ### Start Finitomata.Cache with start_link Source: https://hexdocs.pm/finitomata/Finitomata.Cache.md The `start_link/1` function is used to embed Finitomata.Cache within a supervision tree. It requires essential options such as `id`, `type`, `ttl`, `live?`, and `getter` to configure the cache instance. ```elixir @spec start_link( id: term(), type: term(), ttl: pos_integer(), live?: boolean(), getter: (term() -> term()) | term() ) :: Supervisor.on_start() ``` -------------------------------- ### Finitomata.Transition.events/2 Source: https://hexdocs.pm/finitomata/Finitomata.Transition.md Returns a list of events, optionally including internal start and end transitions. ```APIDOC ## events/2 ### Description Returns the not ordered list of events, including or excluding the internal starting and ending transitions `:__start__` and `__end__` according to the second argument. ### Spec ```elixir @spec events([t()], boolean()) :: [state()] ``` ### Example ```elixir iex> {:ok, transitions} = Finitomata.Mermaid.parse("s1 --> |ok| s2\ns1 --> |ko| s3") Finitomata.Transition.events(transitions, true) [:ok, :ko] Finitomata.Transition.events(transitions) [:__start__, :ok, :ko, :__end__] ``` ``` -------------------------------- ### State Payload Management Source: https://hexdocs.pm/finitomata/cheatsheet.md Demonstrates how to pass an initial state payload when starting an FSM and how it's received and updated within the `on_transition/4` callback. ```elixir # Pass it at startup Finitomata.start_fsm(MyFSM, "my_fsm", %{counter: 0, items: []}) # It arrives as the 4th argument in on_transition/4 def on_transition(:ready, :process, _event_payload, state_payload) do new_payload = %{state_payload | counter: state_payload.counter + 1} {:ok, :done, new_payload} end ``` -------------------------------- ### initialize Source: https://hexdocs.pm/finitomata/Finitomata.Pool.md Initializes started FSMs with a payload or a payload function. This function is available since version 0.18.0. ```APIDOC ## initialize ### Description Initializes the started _FSM_s with the same payload, or with what `payload_fun/1` would return as many times as the number of workers. ### Signature ```elixir @spec initialize(Finitomata.id(), (pos_integer() -> any()) | any()) :: :ok ``` ``` -------------------------------- ### Start Pool of Workers (Flexible Arguments) Source: https://hexdocs.pm/finitomata/Finitomata.Pool.md Starts a pool of asynchronous workers wrapped by an FSM. This version supports various argument formats including keyword lists and maps. ```elixir @spec start_pool( id :: Finitomata.id(), count :: pos_integer(), [actor: actor(), on_error: handler(), on_result: handler(), payload: :term] | %{ :actor => actor(), optional(:on_error) => handler(), optional(:on_result) => handler(), optional(:payload) => :term } | [implementation: module(), payload: :term] | %{ :implementation => module(), optional(:payload) => :term } ) :: GenServer.on_start() ``` -------------------------------- ### paths Source: https://hexdocs.pm/finitomata/Finitomata.Transition.md Returns all the paths from starting to ending state. ```APIDOC ## `paths` ### Description Returns all the paths from starting to ending state. ### Function Signature ```elixir @spec paths(:states | :transitions, [t()], state(), state()) :: Enumerable.t(t()) | [Finitomata.Transition.Path.t()] ``` ### Example ```elixir iex> {:ok, transitions} = Finitomata.PlantUML.parse("[*] --> s1 : foo\ns1 --> s2 : ok\ns1 --> s3 : ok\ns2 --> [*] : ko\ns3 --> [*] : ko") iex> Finitomata.Transition.paths(transitions) [%Finitomata.Transition.Path{from: :*, to: :*, path: [foo: :s1, ok: :s2, ko: :*]}, %Finitomata.Transition.Path{from: :*, to: :*, path: [foo: :s1, ok: :s3, ko: :*]}] ``` ``` -------------------------------- ### start_link Source: https://hexdocs.pm/finitomata/Finitomata.Pool.md Starts an FSM alone with a given name and payload. Typically used within a supervision tree, not directly. Available since 0.18.0. ```APIDOC ## start_link ### Description Starts an _FSM_ alone with `name` and `payload` given. Usually one does not want to call this directly, the most common way would be to start a `Finitomata` supervision tree or even better embed it into the existing supervision tree _and_ start _FSM_ with `Finitomata.start_fsm/4` passing `Finitomata.Pool` as the first parameter. For distributed applications, use `Infinitomata.start_fsm/4` instead. ### Signature (No explicit signature provided in source) ``` -------------------------------- ### Start FSM with custom parent PID Source: https://hexdocs.pm/finitomata/cheatsheet.md When starting an FSM using `start_fsm/4`, the `:parent` key can be included in the initial payload to override the default parent PID (`self()`). This `:parent` value is stored internally and not part of the FSM's state payload. ```elixir Finitomata.start_fsm(MyFSM, "my_fsm", %{parent: some_pid, foo: :bar}) ``` -------------------------------- ### Start and Call Throttler Source: https://hexdocs.pm/finitomata/Finitomata.Throttler.md Starts a throttler process and performs a throttled synchronous call. The throttler is identified by a name, and the call involves executing a function with arguments. The output shows the result of the call and the internal state of the throttler. ```elixir {:ok, pid} = Finitomata.Throttler.start_link(name: Throttler) Finitomata.Throttler.call(Throttler, {IO, :inspect, [42]}) 42 #⇒ %Finitomata.Throttler{ # from: {#PID<0.335.0>, #Reference<0.3154300821.2643722246.59214>}, # fun: {IO, :inspect}, # args: ~c"*", # result: 42, # duration: 192402, # payload: nil # } ``` -------------------------------- ### start_pool (overload 1) Source: https://hexdocs.pm/finitomata/Finitomata.Pool.md Starts a pool of asynchronous workers wrapped by an FSM using keyword arguments. Available since 0.18.0. ```APIDOC ## start_pool ### Description Starts a pool of asynchronous workers wrapped by an _FSM_. ### Signature ```elixir @spec start_pool( id: Finitomata.id(), payload: :term, count: pos_integer(), actor: actor(), on_error: handler(), on_result: handler() ) :: GenServer.on_start() ``` ``` -------------------------------- ### child_spec/1 Source: https://hexdocs.pm/finitomata/Finitomata.Pool.md Returns a specification to start this module under a supervisor. ```APIDOC ## `child_spec/1` ### Description Returns a specification to start this module under a supervisor. ### Function Signature `child_spec(opts)` ### Parameters - `opts` (keyword list): Options for the child specification. ``` -------------------------------- ### start_pool/1 Source: https://hexdocs.pm/finitomata/Finitomata.Pool.md Starts the pool of processes. This function is intended to be called directly to initiate the pool. ```APIDOC ## `start_pool/1` ### Description Starts the pool of processes. This function is intended to be called directly to initiate the pool. ### Function Signature `start_pool(opts)` ### Parameters - `opts` (keyword list): Options for starting the pool, including the actor function and optional callbacks. ``` -------------------------------- ### Finitomata.Throttler.start_link/1 Source: https://hexdocs.pm/finitomata/Finitomata.Throttler.md Starts the throttler with the underlying producer-consumer stages. It accepts options such as `name` for identifying the throttler, `initial` load, and `max_demand` and `initial` for GenStage consumer configuration. ```APIDOC ## `start_link` Starts the throttler with the underlying producer-consumer stages. Accepted options are: - `name` the base name for the throttler to be used in calls to `call/3` - `initial` the initial load of requests (avoid using it unless really needed) - `max_demand`, `initial` the options to be passed directly to `GenStage`’s consumer ``` -------------------------------- ### steps Source: https://hexdocs.pm/finitomata/Finitomata.Transition.md Returns the minimal number of steps required to get from `from` state to `to` state, including hard transitions. ```APIDOC ## `steps` ### Description Returns the minimal number of steps required to get from `from` state to `to` state, including hard transitions. ### Function Signature ```elixir @spec steps([t()], state(), state()) :: non_neg_integer() ``` ``` -------------------------------- ### steps_handled Source: https://hexdocs.pm/finitomata/Finitomata.Transition.md Returns the minimal number of steps required to get from `from` state to `to` state, omitting hard transitions. ```APIDOC ## `steps_handled` ### Description Returns the minimal number of steps required to get from `from` state to `to` state, omitting hard transitions. ### Function Signature ```elixir @spec steps_handled([t()], state(), state()) :: non_neg_integer() ``` ``` -------------------------------- ### start_pool (overload 2) Source: https://hexdocs.pm/finitomata/Finitomata.Pool.md Starts a pool of asynchronous workers wrapped by an FSM using a list or map for configuration. Available since 0.18.0. ```APIDOC ## start_pool ### Description Starts a pool of asynchronous workers wrapped by an _FSM_. ### Signature ```elixir @spec start_pool( id :: Finitomata.id(), count :: pos_integer(), [actor: actor(), on_error: handler(), on_result: handler(), payload: :term] | %{ :actor => actor(), optional(:on_error) => handler(), optional(:on_result) => handler(), optional(:payload) => :term } | [implementation: module(), payload: :term] | %{:implementation => module(), optional(:payload) => :term} ) :: GenServer.on_start() ``` ``` -------------------------------- ### FSM Automatic Progression Example Source: https://hexdocs.pm/finitomata/cheatsheet.md Illustrates how hard events enable automatic FSM progression without manual calls, provided `on_transition/4` resolves each step. ```elixir # The FSM will automatically move idle -> ready -> done -> ended # without any manual transition call, as long as on_transition/4 # resolves each step. ``` -------------------------------- ### Add Telemetria Dependency Source: https://hexdocs.pm/finitomata/Finitomata.md Example of adding the Telemetria library and its backend to your project's dependencies in `mix.exs`. ```elixir defp deps do [ ... {:telemetry, "~> 1.0"}, {:telemetry_poller, "~> 1.0"}, {:telemetria, "~> 0.22"} ] end ``` -------------------------------- ### Runtime Flow Selection with on_fork Source: https://hexdocs.pm/finitomata/Finitomata.Flow.md Implement `on_fork/2` to dynamically select which Flow module to start at runtime when multiple flows are defined for the same event and state. ```elixir use Finitomata, fsm: @fsm, …, forks: [s1: [evt: Flow1, evt: Flow2]] […] @impl Finitomata def on_fork(:s1, %{} = state) do if state.simple_flow?, do: {:ok, Flow1}, else: {:ok, Flow2} end ``` -------------------------------- ### Standard FSM Test with Mox Source: https://hexdocs.pm/finitomata/Finitomata.ExUnit.md Demonstrates a standard approach to testing Finitomata state machines using Mox for mocking and GenServer interactions. Requires explicit setup of the supervisor and FSM. ```elixir test "standard approach" do start_supervised(Finitomata.Supervisor) fini_name = "Turnstile_1" fsm_name = {:via, Registry, {Finitomata.Registry, fini_name}} parent = self() Turnstile.Mox |> allow(parent, fn -> GenServer.whereis(fsm_name) end) |> expect(:after_transition, 4, fn id, state, payload -> parent |> send({:on_transition, id, state, payload}) |> then(fn _ -> :ok end) end) Finitomata.start_fsm(Turnstile, fini_name, %{data: %{passengers: 0}}) Finitomata.transition(fini_name, :coin_in) assert_receive {:on_transition, ^fsm_name, :opened, %{data: %{passengers: 0}}} # assert %{data: %{passengers: 0}}} = Finitomata.state(Turnstile, "Turnstile_1", :payload) Finitomata.transition(fini_name, :walk_in) assert_receive {:on_transition, ^fsm_name, :closed, %{data: %{passengers: 1}}} # assert %{data: %{passengers: 1}}} = Finitomata.state(Turnstile, "Turnstile_1", :payload) Finitomata.transition(fini_name, :switch_off) assert_receive {:on_transition, ^fsm_name, :switched_off, %{data: %{passengers: 1}}} Process.sleep(200) refute Finitomata.alive?(Turnstile, "Turnstile_1") end ``` -------------------------------- ### Add Finitomata.Cache to Supervision Tree Source: https://hexdocs.pm/finitomata/Finitomata.Cache.md This example shows how to integrate Finitomata.Cache into your application's supervision tree. Ensure the `id`, `ttl`, `live?`, and `getter` options are configured appropriately for your use case. ```elixir {Finitomata.Cache, [ [id: MyCache, ttl: 60_000, live?: true, type: Infinitomata, getter: &MyMod.getter/1]]} ``` -------------------------------- ### PlantUML/state_diagram Syntax Source: https://hexdocs.pm/finitomata/index.html Example of FSM definition using PlantUML or :state_diagram syntax. This format explicitly defines states and transitions with event names. ```plantuml [*] --> s1 : to_s1 s1 --> s2 : to_s2 s1 --> s3 : to_s3 s2 --> [*] : ok s3 --> [*] : ok ``` -------------------------------- ### Define a Finite State Machine (FSM) Source: https://hexdocs.pm/finitomata/cheatsheet.md Define an FSM using the `@fsm` attribute and `use Finitomata`. This example shows a simple FSM with 'idle', 'ready', and 'done' states and a 'process' transition. ```elixir defmodule MyFSM do @fsm """ idle --> |wake!| ready ready --> |process| done """ use Finitomata, fsm: @fsm, auto_terminate: true @impl Finitomata def on_transition(:ready, :process, _event_payload, state_payload) do {:ok, :done, state_payload} end end ``` -------------------------------- ### Implement Finitomata.ClusterInfo with Libring Source: https://hexdocs.pm/finitomata/Infinitomata.md Implement the Finitomata.ClusterInfo behavior to wrap calls to a libring consistent hash ring. This is necessary for Infinitomata to manage FSMs transparently in a cluster. Ensure the libring instance is started before initializing Infinitomata. ```elixir defmodule MyApp.ClusterInfo do @moduledoc false @behaviour Finitomata.ClusterInfo @impl Finitomata.ClusterInfo def nodes(_fini_id), do: HashRing.nodes(@ring) -- [node()] @impl Finitomata.ClusterInfo def whois(_fini_id, id), do: HashRing.key_to_node(@ring, id) end ``` -------------------------------- ### Get Events from Transitions Source: https://hexdocs.pm/finitomata/Finitomata.Transition.md Returns a list of events from transitions, optionally including internal start and end transitions. The boolean argument controls the inclusion of :__start__ and :__end__. ```elixir @spec events([t()], boolean()) :: [state()] ``` ```elixir iex> {:ok, transitions} = ...> Finitomata.Mermaid.parse("s1 --> |ok| s2\ns1 --> |ko| s3") ...> Finitomata.Transition.events(transitions, true) [:ok, :ko] ``` ```elixir ...> Finitomata.Transition.events(transitions) [:__start__, :ok, :ko, :__end__] ``` -------------------------------- ### Implement on_start callback for FSM initialization Source: https://hexdocs.pm/finitomata/cheatsheet.md The optional `on_start/1` callback is invoked once during FSM initialization. It can be used to modify the initial payload or control startup behavior by returning `{:continue, new_payload}` or `{:ok, new_payload}`. ```elixir @impl Finitomata def on_start(payload) do # Fetch some data, validate, enrich the payload, etc. {:continue, Map.put(payload, :started_at, DateTime.utc_now())} end ``` -------------------------------- ### Get All Paths Between States in Finitomata Source: https://hexdocs.pm/finitomata/Finitomata.Transition.md Returns all possible paths from a specified starting state to an ending state. This function is useful for understanding all sequences of transitions that can occur between two points in the FSM. ```elixir @spec paths(:states | :transitions, [t()], state(), state()) :: Enumerable.t(t()) | [Finitomata.Transition.Path.t()] ``` ```elixir iex> {:ok, transitions} = ...> Finitomata.PlantUML.parse("[*] --> s1 : foo\ns1 --> s2 : ok\ns1 --> s3 : ok\ns2 --> [*] : ko\ns3 --> [*] : ko") ...> Finitomata.Transition.paths(transitions) [%Finitomata.Transition.Path{from: :*, to: :*, path: [foo: :s1, ok: :s2, ko: :*]}, %Finitomata.Transition.Path{from: :*, to: :*, path: [foo: :s1, ok: :s3, ko: :*]}] ``` -------------------------------- ### Find Loops in Finitomata Transitions Source: https://hexdocs.pm/finitomata/Finitomata.Transition.md Identifies and returns all 'loops' or internal paths where the starting and ending states are identical. This is useful for detecting cycles within the state machine. ```elixir @spec loops(:states | :transitions, [t()]) :: Enumerable.t(t()) | [Finitomata.Transition.Path.t()] ``` ```elixir iex> {:ok, transitions} = ...> Finitomata.PlantUML.parse("[*] --> s1 : foo\ns1 --> s2 : ok\ns2 --> s1 : ok\ns2 --> [*] : ko") ...> Finitomata.Transition.loops(transitions) [%Finitomata.Transition.Path{from: :s1, to: :s1, path: [ok: :s2, ok: :s1]}, %Finitomata.Transition.Path{from: :s2, to: :s2, path: [ok: :s1, ok: :s2]}] ``` -------------------------------- ### Get Straight Paths Between States in Finitomata Source: https://hexdocs.pm/finitomata/Finitomata.Transition.md Retrieves all 'straight' paths from a starting state to an ending state. These are paths that do not involve self-loops or revisiting states within the path itself, useful for linear flow analysis. ```elixir @spec straight_paths(:states | :transitions, [t()], state(), state()) :: Enumerable.t(t()) | Finitomata.Transition.Path.t() ``` ```elixir iex> {:ok, transitions} = ...> Finitomata.PlantUML.parse("[*] --> s1 : foo\ns1 --> s2 : ok\ns1 --> s3 : ok\ns2 --> s2 : loop\ns2 --> [*] : ko\ns3 --> s4 : step\ns4 --> [*] : ko") ...> Finitomata.Transition.straight_paths(transitions) [%Finitomata.Transition.Path{from: :*, to: :*, path: [foo: :s1, ok: :s2, ko: :*]}, %Finitomata.Transition.Path{from: :*, to: :*, path: [foo: :s1, ok: :s3, step: :s4, ko: :*]}] ``` -------------------------------- ### start_fsm Source: https://hexdocs.pm/finitomata/Finitomata.md Initiates a new FSM instance, managed under a supervision tree. The order of arguments has changed in versions prior to v0.15.0. ```APIDOC ## start_fsm ### Description Starts the FSM instance. The FSM is started supervised. If the global name/id is given, it should be passed to all calls like `transition/4`. Before `v0.15.0` the second and third parameters were expected in different order. This is deprecated and will be removed in `v1.0.0`. ### Arguments - `global_name` (optional): The global name of `Finitomata` instance (defaults to `Finitomata`). - `fsm_name`: The name of the FSM (must be unique). - `fsm_implementation`: The module implementing the FSM (must `use Finitomata`). - `initial_payload`: The payload to be carried in the FSM state during its lifecycle. ### Function Signature ```elixir @spec start_fsm(any(), Finitomata.fsm_name(), module(), Finitomata.State.payload()) :: pid() ``` ``` -------------------------------- ### Distributed FSM with `Infinitomata` Source: https://hexdocs.pm/finitomata/cheatsheet.md Use `Infinitomata` as a drop-in replacement for cluster-wide FSMs, distributing them across nodes using `:pg` process groups. The API remains the same for starting, transitioning, and querying. ```elixir # In your supervision tree {Infinitomata, nil} # Start, transition, query -- same API Infinitomata.start_fsm(MyFSM, "my_fsm", %{foo: :bar}) Infinitomata.transition("my_fsm", {:process, nil}) Infinitomata.state("my_fsm") ``` -------------------------------- ### Start Pool of Workers Source: https://hexdocs.pm/finitomata/Finitomata.Pool.md Starts a pool of asynchronous workers wrapped by an FSM. This version accepts named arguments for configuration. ```elixir @spec start_pool( id: Finitomata.id(), payload: :term, count: pos_integer(), actor: actor(), on_error: handler(), on_result: handler() ) :: GenServer.on_start() ``` -------------------------------- ### on_start/1 callback Source: https://hexdocs.pm/finitomata/cheatsheet.md An optional callback invoked once during FSM initialization. It can modify the initial payload or control startup behavior. ```APIDOC ## on_start/1 callback ### Description An optional callback invoked once during FSM initialization. It can modify the initial payload or control startup behavior. It receives the initial payload and can return `:ok`, `:ignore`, or `{:continue, new_payload}` to proceed, or `{:ok, new_payload}` to set a new payload without auto-transitioning. ### Method `on_start(payload)` ### Parameters #### Path Parameters None #### Query Parameters None #### Request Body None ### Request Example ```elixir @impl Finitomata def on_start(payload) do {:continue, Map.put(payload, :started_at, DateTime.utc_now())} end ``` ### Response #### Success Response - `:ok` - Proceed normally, payload unchanged - `:ignore` - Proceed normally, payload unchanged - `{:continue, new_payload}` - Proceed with modified payload (auto-enter start) - `{:ok, new_payload}` - Set new payload but do NOT auto-transition to the entry state. #### Response Example None ``` -------------------------------- ### Setting up Finitomata for ExUnit tests Source: https://hexdocs.pm/finitomata/Finitomata.ExUnit.md This macro sets up the Finitomata testing environment within an ExUnit test or describe block. It initializes the FSM and configures the test context with necessary details like the test process PID. ```elixir describe "MyFSM tests" do setup_finitomata do parent = self() [ fsm: [implementation: MyFSM, payload: %{}], context: [parent: parent] ] end … end ``` -------------------------------- ### get/3 Source: https://hexdocs.pm/finitomata/Finitomata.Pool.md Gets the value for the given key from the structure. ```APIDOC ## `get/3` ### Description Gets the value for the given key from the structure. ### Function Signature `get(pool, key, default)` ### Parameters - `pool` (%Finitomata.Pool{}): The pool structure. - `key` (Estructura.Config.key()): The key to retrieve. - `default` (any()): The default value to return if the key is not found. ``` -------------------------------- ### straight_paths Source: https://hexdocs.pm/finitomata/Finitomata.Transition.md Returns the straight paths from starting to ending state. ```APIDOC ## `straight_paths` ### Description Returns the straight paths from starting to ending state. ### Function Signature ```elixir @spec straight_paths(:states | :transitions, [t()], state(), state()) :: Enumerable.t(t()) | Finitomata.Transition.Path.t() ``` ### Example ```elixir iex> {:ok, transitions} = Finitomata.PlantUML.parse("[*] --> s1 : foo\ns1 --> s2 : ok\ns1 --> s3 : ok\ns2 --> s2 : loop\ns2 --> [*] : ko\ns3 --> s4 : step\ns4 --> [*] : ko") iex> Finitomata.Transition.straight_paths(transitions) [%Finitomata.Transition.Path{from: :*, to: :*, path: [foo: :s1, ok: :s2, ko: :*]}, %Finitomata.Transition.Path{from: :*, to: :*, path: [foo: :s1, ok: :s3, step: :s4, ko: :*]}] ``` ``` -------------------------------- ### shortest_paths Source: https://hexdocs.pm/finitomata/Finitomata.Transition.md Returns the shortest path from starting to ending state. ```APIDOC ## `shortest_paths` ### Description Returns the shortest path from starting to ending state. ### Function Signature ```elixir @spec shortest_paths(:states | :transitions, [t()], state(), state(), boolean()) :: Enumerable.t(t()) | [Finitomata.Transition.Path.t()] ``` ### Example ```elixir iex> {:ok, transitions} = Finitomata.PlantUML.parse("[*] --> s1 : foo\ns1 --> s2 : ok\ns1 --> s3 : ok\ns2 --> [*] : ko\ns3 --> s4 : step\ns4 --> [*] : ko") iex> Finitomata.Transition.shortest_paths(transitions) [%Finitomata.Transition.Path{from: :*, to: :*, path: [foo: :s1, ok: :s2, ko: :*]}] ``` ```