105 lines
		
	
	
		
			2.8 KiB
		
	
	
	
		
			Elixir
		
	
	
	
	
	
			
		
		
	
	
			105 lines
		
	
	
		
			2.8 KiB
		
	
	
	
		
			Elixir
		
	
	
	
	
	
defmodule Phoenix.Tracker.Clock do
 | 
						|
  @moduledoc false
 | 
						|
  alias Phoenix.Tracker.State
 | 
						|
 | 
						|
  @type context :: State.context
 | 
						|
  @type clock :: {State.name, context}
 | 
						|
 | 
						|
  @doc """
 | 
						|
  Returns a list of replicas from a list of contexts.
 | 
						|
  """
 | 
						|
  @spec clockset_replicas([clock]) :: [State.name]
 | 
						|
  def clockset_replicas(clockset) do
 | 
						|
    for {replica, _} <- clockset, do: replica
 | 
						|
  end
 | 
						|
 | 
						|
  @doc """
 | 
						|
  Adds a replicas context to a clockset, keeping only dominate contexts.
 | 
						|
  """
 | 
						|
  @spec append_clock([clock], clock) :: [clock]
 | 
						|
  def append_clock(clockset, {_, clock}) when map_size(clock) == 0, do: clockset
 | 
						|
  def append_clock(clockset, {node, clock}) do
 | 
						|
    big_clock = combine_clocks(clockset)
 | 
						|
    cond do
 | 
						|
      dominates?(clock, big_clock) -> [{node, clock}]
 | 
						|
      dominates?(big_clock, clock) -> clockset
 | 
						|
      true -> filter_clocks(clockset, {node, clock})
 | 
						|
    end
 | 
						|
  end
 | 
						|
 | 
						|
  @doc """
 | 
						|
  Checks if one clock causally dominates the other for all replicas.
 | 
						|
  """
 | 
						|
  @spec dominates?(context, context) :: boolean
 | 
						|
  def dominates?(c1, c2) when map_size(c1) < map_size(c2), do: false
 | 
						|
  def dominates?(c1, c2) do
 | 
						|
    Enum.reduce_while(c2, true, fn {replica, clock}, true ->
 | 
						|
      if Map.get(c1, replica, 0) >= clock do
 | 
						|
        {:cont, true}
 | 
						|
      else
 | 
						|
        {:halt, false}
 | 
						|
      end
 | 
						|
    end)
 | 
						|
  end
 | 
						|
 | 
						|
  @doc """
 | 
						|
  Checks if one clock causally dominates the other for their shared replicas.
 | 
						|
  """
 | 
						|
  def dominates_or_equal?(c1, c2) when c1 == %{} and c2 == %{}, do: true
 | 
						|
  def dominates_or_equal?(c1, _c2) when c1 == %{}, do: false
 | 
						|
  def dominates_or_equal?(c1, c2) do
 | 
						|
    Enum.reduce_while(c1, true, fn {replica, clock}, true ->
 | 
						|
      if clock >= Map.get(c2, replica, 0) do
 | 
						|
        {:cont, true}
 | 
						|
      else
 | 
						|
        {:halt, false}
 | 
						|
      end
 | 
						|
    end)
 | 
						|
  end
 | 
						|
 | 
						|
  @doc """
 | 
						|
  Returns the upper bound causal context of two clocks.
 | 
						|
  """
 | 
						|
  def upperbound(c1, c2) do
 | 
						|
    Map.merge(c1, c2, fn _, v1, v2 -> max(v1, v2) end)
 | 
						|
  end
 | 
						|
 | 
						|
  @doc """
 | 
						|
  Returns the lower bound causal context of two clocks.
 | 
						|
  """
 | 
						|
  def lowerbound(c1, c2) do
 | 
						|
    Map.merge(c1, c2, fn _, v1, v2 -> min(v1, v2) end)
 | 
						|
  end
 | 
						|
 | 
						|
  @doc """
 | 
						|
  Returns the clock with just provided replicas.
 | 
						|
  """
 | 
						|
  def filter_replicas(c, replicas), do: Map.take(c, replicas)
 | 
						|
 | 
						|
  @doc """
 | 
						|
  Returns replicas from the given clock.
 | 
						|
  """
 | 
						|
  def replicas(c), do: Map.keys(c)
 | 
						|
 | 
						|
  defp filter_clocks(clockset, {node, clock}) do
 | 
						|
    clockset
 | 
						|
    |> Enum.reduce({[], false}, fn {node2, clock2}, {set, insert} ->
 | 
						|
      if dominates?(clock, clock2) do
 | 
						|
        {set, true}
 | 
						|
      else
 | 
						|
        {[{node2, clock2}| set], insert || !dominates?(clock2, clock)}
 | 
						|
      end
 | 
						|
    end)
 | 
						|
    |> case do
 | 
						|
      {new_clockset, true} -> [{node, clock} | new_clockset]
 | 
						|
      {new_clockset, false} -> new_clockset
 | 
						|
    end
 | 
						|
  end
 | 
						|
 | 
						|
  defp combine_clocks(clockset) do
 | 
						|
    clockset
 | 
						|
    |> Enum.map(fn {_, clocks} -> clocks end)
 | 
						|
    |> Enum.reduce(%{}, &upperbound(&1, &2))
 | 
						|
  end
 | 
						|
end
 |