### Start and Stop State Machines Source: https://context7.com/ericentin/gen_state_machine/llms.txt Demonstrates starting processes with start_link/3 for supervision or start/3 for standalone execution. ```elixir defmodule Counter do use GenStateMachine def handle_event({:call, from}, :get, state, data) do {:next_state, state, data, [{:reply, from, {state, data}}]} end end # Start linked to current process (for supervision trees) {:ok, pid} = GenStateMachine.start_link(Counter, {:idle, 0}) # Start without link (standalone) {:ok, pid2} = GenStateMachine.start(Counter, {:idle, 0}) ``` -------------------------------- ### Starting a GenStateMachine in a Supervisor Source: https://context7.com/ericentin/gen_state_machine/llms.txt Demonstrates how to include a GenStateMachine as a child in an OTP supervisor. ```elixir children = [ {WorkerStateMachine, initial_data: 42} ] Supervisor.start_link(children, strategy: :one_for_one) ``` -------------------------------- ### Register and Stop GenStateMachine Processes Source: https://context7.com/ericentin/gen_state_machine/llms.txt Demonstrates starting processes with local or global names and stopping them with specific reasons and timeouts. ```elixir # Start with local name registration {:ok, _} = GenStateMachine.start_link(Counter, {:idle, 0}, name: MyCounter) GenStateMachine.call(MyCounter, :get) #=> {:idle, 0} # Start with global name registration {:ok, _} = GenStateMachine.start_link(Counter, {:idle, 0}, name: {:global, :global_counter}) GenStateMachine.call({:global, :global_counter}, :get) #=> {:idle, 0} # Stop with default reason (:normal) GenStateMachine.stop(pid) #=> :ok # Stop with custom reason and timeout GenStateMachine.stop(pid2, :shutdown, 5000) #=> :ok ``` -------------------------------- ### Handling Info Messages in GenStateMachine Source: https://context7.com/ericentin/gen_state_machine/llms.txt Shows how to handle regular process messages, like timer events, within a GenStateMachine's state handler. Ensure the state machine is started with the `:handle_event_function` callback mode. ```elixir defmodule TimedStateMachine do use GenStateMachine def start_link do GenStateMachine.start_link(__MODULE__, {:waiting, 0}) end def trigger(pid), do: GenStateMachine.cast(pid, :trigger) def get_count(pid), do: GenStateMachine.call(pid, :get_count) def handle_event(:cast, :trigger, :waiting, count) do # Schedule a timeout message Process.send_after(self(), :timeout_elapsed, 1000) {:next_state, :active, count + 1} end def handle_event(:info, :timeout_elapsed, :active, count) do IO.puts("Timeout elapsed, returning to waiting state") {:next_state, :waiting, count} end def handle_event({:call, from}, :get_count, state, count) do {:next_state, state, count, [{:reply, from, count}]} end end # Usage {:ok, pid} = TimedStateMachine.start_link() TimedStateMachine.trigger(pid) TimedStateMachine.get_count(pid) #=> 1 # After 1 second: "Timeout elapsed, returning to waiting state" ``` -------------------------------- ### Add application to mix.exs Source: https://github.com/ericentin/gen_state_machine/blob/master/README.md Ensure the library is included in the application list for proper startup. ```elixir def application do [applications: [:gen_state_machine]] end ``` -------------------------------- ### Custom init/1 Callback for GenStateMachine Source: https://context7.com/ericentin/gen_state_machine/llms.txt Illustrates overriding the `init/1` callback to perform custom initialization, validate arguments, and return actions during startup. The `init` callback can return `{:ok, state, data}` or `{:ok, state, data, actions}`. ```elixir defmodule ValidatedStateMachine do use GenStateMachine def start_link(config) do GenStateMachine.start_link(__MODULE__, config) end @impl true def init(config) do case validate_config(config) do {:ok, validated} -> {:ok, :initialized, validated} {:ok, validated, actions} -> {:ok, :initialized, validated, actions} {:error, reason} -> {:stop, reason} end end defp validate_config(%{required_field: value} = config) when is_binary(value) do {:ok, config} end defp validate_config(_), do: {:error, :invalid_config} def handle_event({:call, from}, :get_config, state, data) do {:next_state, state, data, [{:reply, from, data}]} end end # Successful initialization {:ok, pid} = ValidatedStateMachine.start_link(%{required_field: "value"}) # Failed initialization {:error, :invalid_config} = ValidatedStateMachine.start_link(%{}) ``` -------------------------------- ### Implement State Enter Callbacks Source: https://context7.com/ericentin/gen_state_machine/llms.txt Enables notifications upon entering a new state, useful for initialization or transition tracking. ```elixir defmodule EnterCallbackSwitch do use GenStateMachine, callback_mode: [:handle_event_function, :state_enter] def start_link do GenStateMachine.start_link(__MODULE__, {:off, %{enters: 0, flips: 0}}) end def flip(pid), do: GenStateMachine.cast(pid, :flip) def get_stats(pid), do: GenStateMachine.call(pid, :get_stats) # State enter callback - called on every state transition def handle_event(:enter, _old_state, _current_state, data) do {:keep_state, %{data | enters: data.enters + 1}} end def handle_event(:cast, :flip, :off, data) do {:next_state, :on, %{data | flips: data.flips + 1}} end def handle_event(:cast, :flip, :on, data) do {:next_state, :off, data} end def handle_event({:call, from}, :get_stats, _state, data) do {:keep_state_and_data, [{:reply, from, data}]} end end # Usage - enters count includes initial state entry {:ok, pid} = EnterCallbackSwitch.start_link() EnterCallbackSwitch.flip(pid) EnterCallbackSwitch.flip(pid) EnterCallbackSwitch.get_stats(pid) #=> %{enters: 3, flips: 1} ``` -------------------------------- ### Add dependency to mix.exs Source: https://github.com/ericentin/gen_state_machine/blob/master/README.md Include the library in the project's dependency list. ```elixir def deps do [{:gen_state_machine, "~> 3.0"}] end ``` -------------------------------- ### Define Custom Child Specifications Source: https://context7.com/ericentin/gen_state_machine/llms.txt Configure supervision tree behavior by passing options to the use GenStateMachine macro. ```elixir defmodule WorkerStateMachine do use GenStateMachine, id: :worker_fsm, restart: :transient, shutdown: 5000 def start_link(args) do GenStateMachine.start_link(__MODULE__, args, name: __MODULE__) end def init(args) do {:ok, :idle, args} end def handle_event({:call, from}, :status, state, data) do {:next_state, state, data, [{:reply, from, state}]} end end # Child spec is automatically generated WorkerStateMachine.child_spec(initial_data: 42) #=> %{ # id: :worker_fsm, # restart: :transient, # shutdown: 5000, # start: {WorkerStateMachine, :start_link, [[initial_data: 42]]} # } ``` -------------------------------- ### Implement Handle Event Function Mode Source: https://context7.com/ericentin/gen_state_machine/llms.txt Uses a single handle_event/4 callback to process all events, allowing for flexible state representation. ```elixir defmodule Switch do use GenStateMachine # Client API def start_link(initial_state \\ :off) do GenStateMachine.start_link(__MODULE__, {initial_state, 0}) end def flip(pid) do GenStateMachine.cast(pid, :flip) end def get_count(pid) do GenStateMachine.call(pid, :get_count) end # Server Callbacks def handle_event(:cast, :flip, :off, data) do {:next_state, :on, data + 1} end def handle_event(:cast, :flip, :on, data) do {:next_state, :off, data} end def handle_event({:call, from}, :get_count, state, data) do {:next_state, state, data, [{:reply, from, data}]} end end # Usage {:ok, pid} = Switch.start_link() #=> {:ok, #PID<0.123.0>} Switch.flip(pid) #=> :ok Switch.flip(pid) #=> :ok Switch.get_count(pid) #=> 1 ``` -------------------------------- ### Implement State Functions Callback Mode Source: https://context7.com/ericentin/gen_state_machine/llms.txt Routes events to functions named after the current state, requiring states to be atoms. ```elixir defmodule StateFunctionsSwitch do use GenStateMachine, callback_mode: :state_functions # Client API def start_link do GenStateMachine.start_link(__MODULE__, {:off, 0}) end def flip(pid), do: GenStateMachine.cast(pid, :flip) def get_count(pid), do: GenStateMachine.call(pid, :get_count) # State: off def off(:cast, :flip, data) do {:next_state, :on, data + 1} end def off({:call, from}, :get_count, data) do {:keep_state_and_data, [{:reply, from, data}]} end # State: on def on(:cast, :flip, data) do {:next_state, :off, data} end def on({:call, from}, :get_count, data) do {:keep_state_and_data, [{:reply, from, data}]} end end # Usage {:ok, pid} = StateFunctionsSwitch.start_link() StateFunctionsSwitch.flip(pid) StateFunctionsSwitch.get_count(pid) #=> 1 ``` -------------------------------- ### Implement code_change for Hot Code Updates Source: https://github.com/ericentin/gen_state_machine/blob/master/CHANGELOG.md If you wish to maintain the old behavior for hot code updates, implement a callback like this. This is recommended if the default implementation of code_change/4 is invoked, as it can be dangerous. ```elixir def code_change(_old_vsn, state, data, _extra) do {:ok, state, data} end ``` -------------------------------- ### Handle Explicit Replies Source: https://context7.com/ericentin/gen_state_machine/llms.txt Use reply/2 to send responses from a process after performing asynchronous work or coordination. ```elixir defmodule AsyncProcessor do use GenStateMachine def start_link, do: GenStateMachine.start_link(__MODULE__, {:idle, nil}) def process_async(pid, data) do GenStateMachine.call(pid, {:process, data}) end def handle_event({:call, from}, {:process, data}, :idle, _) do # Simulate async processing by sending message to self Process.send_after(self(), {:done, from, data * 2}, 100) {:next_state, :processing, from} end def handle_event(:info, {:done, from, result}, :processing, _) do # Reply explicitly after async work completes GenStateMachine.reply(from, {:ok, result}) {:next_state, :idle, nil} end end # Usage {:ok, pid} = AsyncProcessor.start_link() AsyncProcessor.process_async(pid, 21) #=> {:ok, 42} # Returns after 100ms delay ``` -------------------------------- ### Perform Synchronous Calls Source: https://context7.com/ericentin/gen_state_machine/llms.txt Use call/3 for requests requiring a response. The caller blocks until the server replies or the timeout expires. ```elixir defmodule Calculator do use GenStateMachine def start_link, do: GenStateMachine.start_link(__MODULE__, {:ready, 0}) def add(pid, value), do: GenStateMachine.call(pid, {:add, value}) def get(pid), do: GenStateMachine.call(pid, :get) def reset(pid), do: GenStateMachine.call(pid, :reset) def handle_event({:call, from}, {:add, value}, state, data) do new_data = data + value {:next_state, state, new_data, [{:reply, from, new_data}]} end def handle_event({:call, from}, :get, state, data) do {:next_state, state, data, [{:reply, from, data}]} end def handle_event({:call, from}, :reset, _state, _data) do {:next_state, :ready, 0, [{:reply, from, :ok}]} end end # Usage {:ok, pid} = Calculator.start_link() Calculator.add(pid, 5) #=> 5 Calculator.add(pid, 10) #=> 15 Calculator.get(pid) #=> 15 # With custom timeout (default is :infinity) GenStateMachine.call(pid, :get, 5000) #=> 15 ``` -------------------------------- ### Perform Asynchronous Casts Source: https://context7.com/ericentin/gen_state_machine/llms.txt Use cast/2 for fire-and-forget messages. This function always returns :ok regardless of the server's status. ```elixir defmodule EventLogger do use GenStateMachine def start_link do GenStateMachine.start_link(__MODULE__, {:logging, []}) end def log(pid, event), do: GenStateMachine.cast(pid, {:log, event}) def pause(pid), do: GenStateMachine.cast(pid, :pause) def resume(pid), do: GenStateMachine.cast(pid, :resume) def get_events(pid), do: GenStateMachine.call(pid, :get_events) def handle_event(:cast, {:log, event}, :logging, events) do {:next_state, :logging, [event | events]} end def handle_event(:cast, {:log, _event}, :paused, events) do # Ignore events while paused {:next_state, :paused, events} end def handle_event(:cast, :pause, _state, events) do {:next_state, :paused, events} end def handle_event(:cast, :resume, _state, events) do {:next_state, :logging, events} end def handle_event({:call, from}, :get_events, state, events) do {:next_state, state, events, [{:reply, from, Enum.reverse(events)}]} end end # Usage {:ok, pid} = EventLogger.start_link() EventLogger.log(pid, "user_login") EventLogger.log(pid, "page_view") EventLogger.pause(pid) EventLogger.log(pid, "ignored_event") EventLogger.resume(pid) EventLogger.log(pid, "checkout") EventLogger.get_events(pid) #=> ["user_login", "page_view", "checkout"] ``` === COMPLETE CONTENT === This response contains all available snippets from this library. No additional content exists. Do not make further requests.