Powered by AppSignal & Oban Pro
Would you like to see your link here? Contact us

Agent

guides/06-in-memory-state/agent.livemd

Agent

An Agent is a specialized GenServer that provides a set of helper functions which make it easy to store and update state in-memory using a process. Agents come in handy if you want a simple GenServer to hold onto state.

defmodule RunElixir.GameState do
  use Agent

  # Public

  @initial_state %{
    rounds_played: 0,
    current_points: 0,
    total_points: 0
  }

  # Starts a new Agent process with an initial state.
  def start_link(_args) do
    Agent.start_link(fn -> @initial_state end)
  end

  # Creates a fire-and-forget message to the Agent.
  #
  # Unlike in a GenServer, we don't have to write callbacks
  # for our events like `handle_cast/2` or `handle_call/3`
  # but can define the callback as an anonymous function.
  #
  # The anonymous function receives the current state
  # and has to return the new state.
  def add_points(pid, points) do
    Agent.cast(pid, fn state ->
      Map.update!(state, :current_points, &(&1 + points))
    end)
  end

  # Returns the current state.
  def get_state(pid), do: Agent.get(pid, fn state -> state end)

  # Ends the round by updating and returning the state.
  #
  # `Agent.get_and_update/2` receives the current state in the
  # anonymous function and has to return a tuple with the state
  # that should be returned to the calling process and
  # the new process state.
  def end_round(pid) do
    Agent.get_and_update(pid, fn state ->
      %{
        rounds_played: rounds_played,
        total_points: total_points,
        current_points: current_points
      } = state

      new_state = %{
        rounds_played: rounds_played + 1,
        total_points: total_points + current_points,
        current_points: 0
      }

      {new_state, new_state}
    end)
  end
end

{:ok, pid} = RunElixir.GameState.start_link([])
RunElixir.GameState.get_state(pid) |> IO.inspect(label: 1)
RunElixir.GameState.add_points(pid, 100) |> IO.inspect(label: 2)
RunElixir.GameState.get_state(pid) |> IO.inspect(label: 3)
RunElixir.GameState.end_round(pid) |> IO.inspect(label: 4)
1: %{current_points: 0, total_points: 0, rounds_played: 0}
2: :ok
3: %{current_points: 100, total_points: 0, rounds_played: 0}
4: %{current_points: 0, total_points: 100, rounds_played: 1}