Current section

Files

Jump to
fixpoint lib solver store registry_store.ex
Raw

lib/solver/store/registry_store.ex

defmodule CPSolver.Store.Registry do
alias CPSolver.ConstraintStore, as: Store
alias CPSolver.DefaultDomain, as: Domain
alias CPSolver.Variable
alias CPSolver.Utils
require Logger
@behaviour Store
@variable_registry CPSolver.Store.Registry
@impl true
@spec create(any(), [Variable.t()]) :: {:ok, [any()]}
def create(space, variables) do
{:ok,
Enum.map(
variables,
fn var ->
{:ok, _pid} =
Agent.start(
fn ->
Domain.new(var.domain)
|> tap(fn _ ->
Process.put(:fire_on_no_change, true)
{:ok, _} = Registry.register(@variable_registry, var.id, space)
end)
end,
name: variable_proc_id(var)
)
%{}
|> Map.put(:id, var.id)
|> Map.put(:space, space)
end
)}
end
@impl true
def dispose(_space, variable) do
Agent.stop(variable_proc_id(variable))
end
def variable_proc_id(variable) do
{:global, variable.id}
end
@impl true
def get(_store, var, operation, args \\ []) do
Agent.get(
variable_proc_id(var),
fn
:fail ->
:fail
|> tap(fn _ -> handle_op_on_failed_var(var, operation) end)
domain ->
apply(Domain, operation, [domain | args])
end
)
end
@impl true
def domain(_space, var) do
Agent.get(variable_proc_id(var), fn
:fail ->
:fail
|> tap(fn _ -> handle_op_on_failed_var(var, :domain) end)
domain ->
domain
end)
end
@impl true
def update(_store, var, operation, args \\ []) do
Agent.update(
variable_proc_id(var),
fn
:fail ->
:fail
|> tap(fn _ -> handle_op_on_failed_var(var, operation) end)
domain ->
case apply(Domain, operation, [domain | args]) do
:fail ->
:fail
|> tap(fn _ -> handle_failure(var) end)
:none ->
domain
|> tap(fn _ -> handle_domain_no_change(var) end)
{domain_change, new_domain} ->
new_domain
|> tap(fn _ -> handle_domain_change(domain_change, var, new_domain) end)
end
end
)
end
defp handle_failure(var) do
Logger.debug("Failure for variable #{inspect(var.id)}")
## TODO: notify space (and maybe don't notify propagators)
publish(var, {:fail, var.id})
end
defp handle_op_on_failed_var(var, operation) do
Logger.warning(
"Attempt to request #{inspect(operation)} on failed variable #{inspect(var.id)}"
)
end
defp handle_domain_no_change(var) do
## Publish no_change only once between domain change events
fire_on_no_change?() &&
publish(var, {:no_change, var.id})
|> tap(fn _ ->
Logger.debug("No change for variable #{inspect(var.id)}")
Process.put(:fire_on_no_change, false)
end)
end
defp handle_domain_change(domain_change, var, _domain) do
publish(var, {domain_change, var.id})
|> tap(fn _ ->
Logger.debug("Domain change (#{domain_change}) for #{inspect(var.id)}")
Process.put(:fire_on_no_change, true)
end)
end
defp publish(var, message) do
Utils.publish({:variable, var.id}, message)
end
defp fire_on_no_change?() do
Process.get(:fire_on_no_change)
end
@impl true
@spec get_variables(any()) :: [reference()]
def get_variables(space) do
Registry.select(CPSolver.Store.Registry, [
{{:"$1", :_, :"$3"}, [{:==, :"$3", space}], [:"$1"]}
])
end
end