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: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/2macro inspired from ashneyderman/plain_fsm_ex that automatically handles system messages. It handles parent:EXITmessages 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:
def deps do
[
{:gen_loop, "~> 1.0"},
]
endAs of version 1.0.0, 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.
How To ?
This section is to be polished, but basically :
First,
use GenLoop, enter: :my_loopin your module, where:my_loopis the name of a function in your module.Call
GenLoop.start_link(__MODULE__, init_arg), you can also give options likename, just like aGenServer.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/1blocks just as normal but you can also use thereceive/2macro 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 classicreceive/1blocks for transient states.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. endrcallandrcastwork also with normalreceive/1.receive/1orreceive/2must be the last expression in the function.If you add the
get_stateoption when using GenLoop, your module will automatically define aget_state(pid_or_name)function and anyreceive/2block will answer to this call with the current process state. Currently only the:alloption is supported. It's better to keep this functionality for debug purposes.
use GenLoop, get_state: :allHave a look at loop_example.ex.
Alternative
GenLoop is designed for communicating processes : servers, FSMs, etc. Have a look at the Task 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.