GenLoop
This library is an adaptation of awesome Ulf Wiger's library :plain_fsm for
Elixir. It reuses as :plain_fsm code as possible, but adds some features :
- 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_keyname = {: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 and plain_fsm to your list
of dependencies in mix.exs: We need to add plain_fsm because it is old on
Hex repository, we use a more up-to-date version of the library (mainly to
handle terminate/2 callback).
def deps do
[
{:gen_loop, "~> 0.1.0"},
]
end
As 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 messagesrcall(from, msg) -> # Match a message from GenLoop.call/2reply(from, :ok) # Reply with GenLoop.reply (automatically imported)my_loop(state) # Don't forget to re-enter the looprcast(msg) -> # Match a message from GenLoop.caststate = do_stuff(msg)my_loop(state)msg -> # Match a mere message from Kernel.send/2 or GenLoop.send/2state = do_stuff(msg)other_loop(state) # You can go to another loop to change stateafter1000 -> 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: :all
Have 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.