defmodule CPSolver.ConstraintStore do @moduledoc """ Constraint store is a key-value store, where `key` is a variable id, and `value` is a implementation-dependent structure that allows to update and keep track of variables' domains. """ ################# alias CPSolver.Common alias CPSolver.Variable @type get_operation :: Common.domain_get_operation() | nil @type update_operation :: Common.domain_update_operation() def default_store() do CPSolver.Store.Registry end ### Callbacks ## Tell basic constraints (a.k.a, domains) to a constraint store @callback create(variables :: Enum.t(), opts :: Keyword.t()) :: {:ok, any()} | {:error, any()} ## Get variable details @callback get(store :: any(), variable :: Variable.t(), get_operation(), [any()]) :: {:ok, any()} | {:error, any()} @callback update(store :: any(), variable :: Variable.t(), update_operation(), [any()]) :: any() @callback update_domain(store :: any(), variable :: Variable.t(), update_operation(), [any()]) :: any() @callback dispose(store :: any(), variables :: [Variable.t()]) :: :ok | :not_found @callback domain(store :: any(), variable :: Variable.t()) :: {:ok, any()} | {:error, any()} @callback on_fail(variable :: Variable.t()) :: any() @callback on_no_change(variable :: Variable.t()) :: any() @callback on_change( variable :: Variable.t(), change :: :fixed | :min_change | :max_change | :domain_change ) :: any() @callback get_variables(store :: any()) :: [any()] ### API defmacro __using__(_) do quote do @behaviour CPSolver.ConstraintStore @domain_changes CPSolver.Common.domain_changes() require Logger def update(store, variable, operation, args) do update_domain(store, variable, operation, args) |> tap(fn :fail -> on_fail(variable) :no_change -> on_no_change(variable) change when change in @domain_changes -> on_change(variable, change) end) end def on_change(var, domain_change) do publish(var, domain_change) |> tap(fn _ -> Logger.debug("Domain change (#{domain_change}) for #{inspect(var.id)}") maybe_unsubscribe_all(domain_change, var) end) end def on_fail(var) do Logger.debug("Failure for variable #{inspect(var.id)}") ## TODO: notify space (and maybe don't notify propagators) publish(var, :fail) end def on_no_change(_var) do :ok end defp publish(variable, event) do Variable.publish(variable, {event, variable.id}) end defp maybe_unsubscribe_all(:fixed, var) do Enum.each(Variable.subscribers(var), fn pid -> Variable.unsubscribe(pid, var) end) end defp maybe_unsubscribe_all(_, _var) do :ok end defoverridable update: 4 defoverridable on_change: 2 defoverridable on_fail: 1 defoverridable on_no_change: 1 end end def create_store(store_impl, variables) do {:ok, store_instance} = store_impl.create(variables) {:ok, Enum.map(variables, fn var -> var |> Map.put(:id, var.id) |> Map.put(:name, var.name) |> Map.put(:store, store_instance) end), store_instance} end end