defmodule Multiverses.Registry do
@moduledoc """
This module is intended to be a drop-in replacement for `Registry`, but
not all functionality is implemented.
If universes are active, keys in the Registry will be `{universe, key}`
instead of the normal `key`. A convenience `via/2` macro has been
provided, which will perform this substitution correctly.
Unimplemented functionality:
- `count_match/3,4`
- `match/3,4`
- `unregister_match/3,4`
"""
@doc false
def register_name(_, _)
@doc false
def send(_, _)
@doc false
def unregister_name(_)
use Multiverses.Clone,
module: Registry,
except: [
count: 1,
dispatch: 3,
dispatch: 4,
keys: 2,
lookup: 2,
register: 3,
unregister: 2,
update_value: 3,
select: 2,
whereis_name: 1,
# these two functions are deprecated.
start_link: 3,
# these two functions are deprecated.
start_link: 2
]
require Multiverses
defp id, do: Multiverses.id(Registry)
def count(registry) do
selection = [
{
{:"$1", :_, :_},
[{:==, {:element, 1, :"$1"}, {:const, id()}}],
[:"$1"]
}
]
registry
|> Registry.select(selection)
|> Enum.count()
end
def dispatch(registry, key, fun, opts \\ []) do
Registry.dispatch(registry, {id(), key}, fun, opts)
end
def keys(registry, pid) do
id = id()
registry
|> Registry.keys(pid)
|> Enum.map(fn {^id, key} -> key end)
# NB: there shouldn't be any pids that don't match this universe.
end
def lookup(registry, key) do
Registry.lookup(registry, {id(), key})
end
@doc """
Registers the calling process with the Registry. Works as `Registry.register/3` does.
"""
def register(registry, key, value) do
Registry.register(registry, {id(), key}, value)
end
def select(registry, spec) do
universe = id()
new_spec =
Enum.map(spec, fn {match, filters, result} ->
{new_match, match_var} =
case match do
{:_, a, b} -> {{:"$4", a, b}, :"$4"}
{a, b, c} -> {{a, b, c}, a}
end
# this adjustment function has to takes existing filters and results
# and intrusively changes them to select on the second part of the
# element when the match var matches the first position. This needs
# to be a arity-2 function that is passed itself, to allow using
# recursivity in a lambda with a y-combinator technique.
# NB: this needs to be a lambda so that Multiverses can be compile-time
# only.
new_filters =
adjust(filters, match_var) ++
[{:==, {:element, 1, match_var}, {:const, universe}}]
new_result = adjust(result, match_var)
{new_match, new_filters, new_result}
end)
Registry.select(registry, new_spec)
end
defp adjust(match_var, match_var) do
{:element, 2, match_var}
end
defp adjust(list, match_var) when is_list(list) do
Enum.map(list, &adjust(&1, match_var))
end
defp adjust(tuple, match_var) when is_tuple(tuple) do
tuple
|> Tuple.to_list()
|> adjust(match_var)
|> List.to_tuple()
end
defp adjust(map, match_var) when is_map(map) do
Map.new(map, fn {k, v} -> {adjust(k, match_var), adjust(v, match_var)} end)
end
defp adjust(any, _), do: any
def unregister(registry, key) do
Registry.unregister(registry, {id(), key})
end
def update_value(registry, key, callback) do
Registry.update_value(registry, {id(), key}, callback)
end
def whereis_name({registry, key}), do: Registry.whereis_name({registry, {id(), key}})
def whereis_name({registry, key, _value}), do: Registry.whereis_name({registry, {id(), key}})
end