defmodule Trigger do
@moduledoc """
A simple way to sync between processes.
"""
@enforce_keys [:ref, :receiver]
defstruct [:ref, :receiver]
@opaque t() :: %Trigger{
ref: reference(),
receiver: pid(),
}
@spec new(options) :: t
when options: [
receiver: pid(),
]
def new(opts \\ []) do
receiver = Keyword.get(opts, :receiver, self())
ref = make_ref()
%Trigger{ref: ref, receiver: receiver}
end
@spec fire(t, term) :: :ok
def fire(%Trigger{} = trigger, data \\ nil) do
send(trigger.receiver, {trigger.ref, self(), data})
:ok
end
@spec fire_wait(t, term, timeout) :: term | no_return
def fire_wait(%Trigger{ref: ref} = trigger, data \\ nil, timeout \\ :infinity) do
fire(trigger, data)
receive do
{^ref, data} -> data
after
timeout -> exit({:timeout, {__MODULE__, :fire_wait, [trigger, data, timeout]}})
end
end
@spec wait(t, timeout) :: {pid, term} | no_return
def wait(%Trigger{ref: ref} = trigger, timeout \\ :infinity) do
receive do
{^ref, from, data} -> {from, data}
after
timeout -> exit({:timeout, {__MODULE__, :wait, [trigger, timeout]}})
end
end
@spec reply(t, pid, term) :: :ok
def reply(%Trigger{ref: ref}, to, reply \\ nil) do
send(to, {ref, reply})
:ok
end
@spec wait_reply(t, term, timeout) :: term | no_return
def wait_reply(%Trigger{} = trigger, data \\ nil, timeout \\ :infinity) do
{from, data_in} = wait(trigger, timeout)
reply(trigger, from, data)
data_in
end
end