Current section

Files

Jump to
hyperview lib hyparview view.ex
Raw

lib/hyparview/view.ex

defmodule Hyparview.View do
@moduledoc """
Hyparview VIEW struct and functions
"""
alias __MODULE__
alias Hyparview.Config
alias Hyparview.NodeMonitor
defstruct active: MapSet.new(),
passive: Config.contact_nodes(),
active_size: Config.active_view_size(),
passive_size: Config.passive_view_size(),
arwl: Config.active_random_walk_length(),
prwl: Config.passive_random_walk_length()
@type t :: %View{
active: MapSet.t(),
passive: MapSet.t(),
active_size: non_neg_integer(),
passive_size: non_neg_integer(),
arwl: non_neg_integer(),
prwl: non_neg_integer()
}
@spec has_free_slot_in_active_view?(t()) :: boolean()
def has_free_slot_in_active_view?(%View{active: %MapSet{map: active}} = view),
do: view.active_size >= map_size(active)
@spec move_passive_to_active(View.t(), Node.t()) :: View.t()
def move_passive_to_active(view, node) do
passive = MapSet.delete(view.passive, node)
active = MapSet.put(view.active, node)
:ok = NodeMonitor.add_node(node)
%{view | active: active, passive: passive}
end
@spec move_active_to_passive(Node.t(), View.t()) :: View.t()
def move_active_to_passive(node, view) do
active = MapSet.delete(view.active, node)
passive = MapSet.put(view.passive, node)
:ok = NodeMonitor.schedule_delete_node(node, 10_000)
%{view | active: active, passive: passive}
end
@spec is_node_already_added?(Node.t(), t()) :: boolean()
def is_node_already_added?(node, view) do
[view.active, view.passive]
|> Enum.map(&MapSet.member?(&1, node))
|> Enum.any?()
end
# credo:disable-for-next-line Credo.Check.Readability.MaxLineLength
@spec try_add_node_to_active(Node.t(), t()) :: {:ok, t()} | {{:error, :failed_to_connect}, t()}
def try_add_node_to_active(node, view) do
case Node.connect(node) do
false -> {{:error, :failed_to_connect}, view}
true -> {:ok, try_add_node_to_active_1(node, view)}
end
end
@spec trim_and_add_to_passive(View.t(), Node.t() | MapSet.t()) :: View.t()
def trim_and_add_to_passive(%View{} = view, %MapSet{} = nodes) do
nodes
|> MapSet.delete(Node.self())
|> Enum.reduce(view, fn node, acc -> trim_and_add_to_passive(acc, node) end)
end
def trim_and_add_to_passive(view, node) when node != node() do
if not is_node_already_added?(node, view) do
passive =
view.passive
|> Enum.split(view.passive_size)
|> Kernel.elem(0)
|> MapSet.new()
|> MapSet.put(node)
%{view | passive: passive}
else
view
end
end
def trim_and_add_to_passive(view, _node) do
view
end
# private funtions
@spec try_add_node_to_active_1(Node.t(), t()) :: t()
defp try_add_node_to_active_1(node, view) do
view
|> trim_active()
|> move_passive_to_active(node)
end
@spec trim_active(t()) :: t()
defp trim_active(view) do
dropped_nodes = select_drop_nodes_from_active(view)
:ok = Enum.each(dropped_nodes, &NodeMonitor.schedule_delete_node(&1, 25_000))
Enum.reduce(dropped_nodes, view, &move_active_to_passive/2)
end
@spec select_drop_nodes_from_active(t()) :: MapSet.t()
defp select_drop_nodes_from_active(view) do
view.active
|> Enum.shuffle()
|> Enum.split(view.active_size)
|> Kernel.elem(1)
|> MapSet.new()
end
end