README.md

# GenLoop

This library is an adaptation of awesome Ulf Wiger's erlang library `:plain_fsm` for
Elixir. It reuses as `:plain_fsm` code as possible, but adds some features :

- Elixir-like OTP system behaviours for starting processes, stopping processes and name
  registering. That means that you can use the classic naming conventions as in
  `GenServer`:
  ```elixir
  name = atom_key
  name = {:global, key}
  name = {:via, Registry, {AppRegistry, key}}
  GenLoop.start_link(module, args, name: name)
  GenLoop.send(name, message)
  GenLoop.stop(name)
  ```
- `receive/2` macro inspired from
  [ashneyderman/plain_fsm_ex](https://github.com/ashneyderman/plain_fsm_ex) that
  automatically handles system messages. It handles parent `:EXIT` messages if
  your process traps exits too.

This is still a work in progress, notably the documentation must be completed.

## Installation

The package can be installed by adding `gen_loop` to your list
of dependencies in `mix.exs`:

```elixir
def deps do
  [
    {:gen_loop, "~> 1.0"},
  ]
end
```

As of version 1.0.0, [plain_fsm](https://hex.pm/packages/plain_fsm) is
a normal dependency pulled from hex.pm.

## Why ?

This library is a direct concurrent to `GenServer` or `:gen_statem` : it provides
selective receive and more freedom but makes it easier to shoot yourself in the
foot.

More info in [`plain_fsm` rationale](https://github.com/uwiger/plain_fsm/blob/master/doc/plain_fsm.md).

## How To ?

This section is to be polished, but basically :

- First, `use GenLoop, enter: :my_loop` in your module, where `:my_loop` is the
  name of a function in your module.
- Call `GenLoop.start_link(__MODULE__, init_arg)`, you can also give options like
  `name`, just like a `GenServer`.
- Maybe `def init(init_arg)` . It should return `{:ok, state}, {:stop, reason} or :ignore`.
- Define your `my_loop(init_arg)` function where your code now runs in a
  supervised process.
- In your state functions, you can use `receive/1` blocks just as normal
  but you can also use the `receive/2` macro in your main state function.
  It's best to use the latter on a base state where the most time is spent,
  in order to handle system messages automatically and keep the classic
  `receive/1` blocks for transient states.

  ```elixir
  def my_loop(state)
    my_state = change_stuf(state)
    receive my_state do   # Pass the state if you want to handle system messages
      rcall(from, msg) -> # Match a message from GenLoop.call/2
        reply(from, :ok)  # Reply with GenLoop.reply (automatically imported)
        my_loop(state)    # Don't forget to re-enter the loop
      rcast(msg) ->       # Match a message from GenLoop.cast
        state = do_stuff(msg)
        my_loop(state)
      msg ->              # Match a mere message from Kernel.send/2 or GenLoop.send/2
        state = do_stuff(msg)
        other_loop(state) # You can go to another loop to change state
    after
      1000 -> my_loop(state)
    end
    # You must not have any code after receive.
  end
  ```
- `rcall` and `rcast` work also with normal `receive/1`.
- `receive/1` or `receive/2` must be the last expression in the function. 
- If you add the `get_state` option when using GenLoop, your module
  will automatically define a `get_state(pid_or_name)` function
  and any `receive/2` block will answer to this call with the current
  process state. 
  Currently only the `:all` option is supported.
  It's better to keep this functionality for debug
  purposes.

 ```
 use GenLoop, get_state: :all
 ```


Have a look at [loop_example.ex](https://github.com/niahoo/gen_loop/blob/master/lib/loop_example.ex).

## Alternative

GenLoop is designed for communicating processes : servers, FSMs, etc. Have a
look at the [Task](https://hexdocs.pm/elixir/Task.html) module if you just want
to supervise autonomous processes.

GenLoop is not a replacement for GenServer : if your have only one loop in your
module with a "catch all messages" clause, you woud better use GenServer
instead of GenLoop.

You may also use :gen_statem as a good replacement to selective receives.