Current section

Files

Jump to
fixpoint lib solver store local_store.ex
Raw

lib/solver/store/local_store.ex

defmodule CPSolver.Store.Local do
alias CPSolver.ConstraintStore
alias CPSolver.Variable
alias CPSolver.Variable.Agent, as: VariableAgent
use ConstraintStore
use GenServer
## Store callbacks
@impl true
def create(variables, opts \\ []) do
variable_map =
Map.new(
variables,
fn var ->
{:ok, pid} = VariableAgent.create(var, registry: false)
{var.id, %{agent: pid, subscriptions: []}}
end
)
{:ok, _store} = GenServer.start_link(__MODULE__, [variable_map, opts])
end
@impl true
def dispose(store, _variables) do
GenServer.cast(store, :stop)
end
@impl true
def domain(store, var) do
GenServer.call(store, {:domain, var.id})
end
@impl true
def get(store, var, operation, args \\ []) do
GenServer.call(store, {:get, var.id, operation, args})
end
@impl true
def update_domain(store, var, operation, args \\ []) do
GenServer.call(store, {:update, var.id, operation, args})
end
@impl true
def on_change(store, variable, change) do
GenServer.cast(store, {:on_change, variable, change})
end
@impl true
def on_fail(store, variable) do
GenServer.cast(store, {:on_fail, variable})
end
@impl true
def on_no_change(store, variable) do
GenServer.cast(store, {:on_no_change, variable})
end
@impl true
def get_variables(store) do
GenServer.call(store, :get_variables)
end
@impl true
def subscribe(store, subscriptions) do
GenServer.cast(store, {:subscribe, Enum.map(subscriptions, &normalize_subscription/1)})
end
## GenServer callbacks
@impl true
def init([variables, _opts]) do
{:ok, %{variables: variables}}
end
@impl true
def handle_call({:domain, var}, _from, data) do
{:reply,
var
|> get_agent_pid(data)
|> VariableAgent.operation(:domain), data}
end
def handle_call({request_kind, var, operation, args}, _from, data)
when request_kind in [:update, :get] do
## Locate pid
reply =
var
|> get_agent_pid(data)
|> then(fn
nil ->
{:not_found, var}
agent_pid ->
VariableAgent.operation(agent_pid, operation, args)
|> tap(fn result ->
request_kind == :update && notify_subscribers(var, result, data)
end)
end)
{:reply, reply, data}
end
def handle_call(:get_variables, _from, %{variables: variables} = data) do
var_ids = Map.keys(variables)
{:reply, var_ids, data}
end
@impl true
def handle_cast({:on_change, _var, _change}, data) do
{:noreply, data}
end
def handle_cast({:on_fail, _var}, data) do
{:noreply, data}
end
def handle_cast({:on_no_change, _var}, data) do
{:noreply, data}
end
def handle_cast({:subscribe, subscriptions}, %{variables: variables} = data) do
new_data =
subscriptions
|> Enum.group_by(fn s -> s.variable end, fn s -> Map.delete(s, :variable) end)
|> Map.merge(variables, fn _var, new_subscr, agent ->
(new_subscr ++ agent.subscriptions)
|> Enum.uniq_by(fn s -> s.pid end)
|> then(fn updated_subscriptions ->
Map.put(agent, :subscriptions, updated_subscriptions)
end)
end)
|> then(fn updated_variables -> Map.put(data, :variables, Map.new(updated_variables)) end)
{:noreply, new_data}
end
def handle_cast(:stop, %{variables: variables} = data) do
Enum.each(variables, fn {_var_id, %{agent: pid}} = _agent ->
GenServer.stop(pid)
end)
{:stop, :normal, data}
end
defp get_agent_pid(var_id, %{variables: variables} = _data) do
case Map.get(variables, var_id) do
nil -> nil
%{agent: pid} -> pid
end
end
defp normalize_subscription(%{variable: variable, events: events} = subscription) do
%{subscription | variable: variable_id(variable), events: normalize_events(events)}
end
defp variable_id(%Variable{id: id}) do
id
end
defp variable_id(id) do
id
end
defp normalize_events(events) do
## :fixed is mandatory
events
|> Enum.uniq()
|> then(fn deduped -> (Enum.member?(deduped, :fixed) && deduped) || [:fixed | deduped] end)
end
defp notify_subscribers(_var, :no_change, _) do
:ignore
end
defp notify_subscribers(var, event, %{variables: variables} = _data) do
subscriptions = Map.get(variables, var) |> Map.get(:subscriptions)
Enum.each(subscriptions, fn s -> notify(s, var, event) end)
end
defp notify(%{pid: subscriber, events: _events} = _subscription, var, event) do
## TODO: notify based on the list of events
send(subscriber, {event, var})
end
end