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.DefaultDomain, as: Domain
use ConstraintStore
use GenServer
## Store callbacks
@impl true
def create(variables, opts \\ []) do
variable_map =
Map.new(
variables,
fn var ->
{var.id, %{domain: Domain.new(var.domain), 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
:ok
end
@impl true
def on_fail(_store, _variable) do
:ok
end
@impl true
def on_no_change(_store, _variable) do
:ok
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, &ConstraintStore.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, get_domain(var, data), data}
end
def handle_call({request_kind, var, operation, args}, _from, data)
when request_kind in [:update, :get] do
var
|> get_domain(data)
|> handle_request(request_kind, var, operation, args, 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({:subscribe, subscriptions}, %{variables: variables} = _data) do
subscriptions_by_var = Enum.group_by(subscriptions, fn s -> s.variable end)
updated_variables =
Enum.reduce(subscriptions_by_var, variables, fn {var, subscrs}, acc ->
Map.update(acc, var, nil, fn rec ->
%{
rec
| subscriptions: (rec.subscriptions ++ subscrs) |> Enum.uniq_by(fn rec -> rec.pid end)
}
end)
end)
{:noreply, Map.put(variables, :variables, updated_variables)}
end
def handle_cast(:stop, data) do
{:stop, :normal, data}
end
defp handle_request(nil, _, _, _, _, data) do
{:reply, :not_found, data}
end
defp handle_request(:fail, _, _, _, _, data) do
{:reply, :fail, data}
end
defp handle_request(domain, :get, _variable, operation, args, data) do
{:reply, apply(Domain, operation, [domain | args]), data}
end
defp handle_request(domain, :update, variable, operation, args, data) do
{result, new_data} =
case apply(Domain, operation, [domain | args]) do
:fail ->
{:fail, update_variable_domain(variable, :fail, :fail, data)}
:no_change ->
{:no_change, data}
{domain_change, new_domain} ->
{domain_change, update_variable_domain(variable, new_domain, domain_change, data)}
end
{:reply, result, new_data}
end
defp get_domain(var_id, %{variables: variables} = _data) do
case Map.get(variables, var_id) do
nil -> nil
%{domain: domain} -> domain
end
end
defp update_variable_domain(var_id, domain, event, %{variables: variables} = data) do
variable_rec = Map.get(variables, var_id)
variable_rec
|> Map.put(:domain, domain)
|> then(fn updated_var ->
updated_vars = Map.put(variables, var_id, updated_var)
Map.put(data, :variables, updated_vars)
end)
|> tap(fn _ ->
ConstraintStore.notify_subscribers(var_id, event, variable_rec.subscriptions)
end)
end
end