Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 11 additions & 9 deletions lib/elevator/hall_orders.ex
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
defmodule Elevator.HallOrders do
@moduledoc """
Module responsible for all changes occuring to the hall_order part of the state.
The events that can change hall orders are:
- Button is pressed.
- Arrived at floor.
- Received hall orders from another node.
"""
alias Elevator.HallOrders.Order
alias Elevator.HallOrders.Cost
Expand Down Expand Up @@ -43,21 +47,21 @@ defmodule Elevator.HallOrders do
end

@doc """
Callback for receiving the hall order state from another node.
Merges the states by updating the individual
Receiving the hall order state from another node.
Merges the states by updating the individual order status.
"""
@spec receive_state(hall_order_map()) :: :ok
def receive_state(other_state), do: GenServer.cast(__MODULE__, {:receive_state, other_state})

@doc """
Callback for a button press.
Places the corresponding order in pending state if it is in idle.
"""
@spec button_press(floor(), hall_btn()) :: :ok
def button_press(floor, button_type),
do: GenServer.cast(__MODULE__, {:button_press, floor, button_type})

@doc """
Callback for clearing a floor.
Goes back to idle if the order is confirmed.
"""
@spec arrived_at_floor(floor(), :up | :down) :: :ok
def arrived_at_floor(floor, direction) do
Expand All @@ -81,7 +85,7 @@ defmodule Elevator.HallOrders do
end

@doc """
Get all orders in same format as get_my_orders.
Get all confirmed orders in same format as get_my_orders.
These are the orders we turn the light on for.
"""
@spec get_confirmed_orders() :: %{Elevator.Types.floor() => MapSet.t(Elevator.Types.hall_btn())}
Expand Down Expand Up @@ -129,7 +133,6 @@ defmodule Elevator.HallOrders do
new_order_map =
Map.keys(order_map)
|> Enum.map(fn key ->
# TODO
new_value = Order.merge_hall_orders(key, order_map[key], other_order_map[key], my_orders)
{key, new_value}
end)
Expand Down Expand Up @@ -184,7 +187,6 @@ defmodule Elevator.HallOrders do
key = {floor, button_type}
order_value = order_map[key]

# TODO: Maybe check that it is our order
order_map =
case order_value do
{order_version, {:confirmed, _}} ->
Expand Down Expand Up @@ -234,8 +236,7 @@ defmodule Elevator.HallOrders do
Enum.filter(order_map, fn {_, {_order_version, order_state}} ->
case order_state do
{:confirmed, cost_map} ->
# Hmm.
Cost.min_alive_cost(cost_map, alive) == Node.self()
Cost.min_alive_cost(cost_map, alive) == Communicator.my_id()

_ ->
false
Expand All @@ -249,6 +250,7 @@ defmodule Elevator.HallOrders do
| Enumerable.t({Elevator.Types.hall_order_key(), Elevator.Types.hall_order_value()})
@spec orders_by_floor(enum_orders()) :: %{floor() => MapSet.t(hall_btn())}
defp orders_by_floor(orders) do
# Restructure order map to the format floor => MapSet(order)
orders
|> Enum.map(fn {{floor, btn_type}, _} -> {floor, btn_type} end)
|> Enum.group_by(fn {floor, _} -> floor end)
Expand Down
85 changes: 42 additions & 43 deletions lib/elevator/hall_orders/order.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ defmodule Elevator.HallOrders.Order do
Logic concerning a single Hall Order.

A hall order is tied to a floor and direction (up/down). It is essentially
one of the hall buttons. It is in one of the following states:
- unknown: Initial, will transition to any state. Light: off
one of the hall buttons. An order has both a version number and a state.
State is one of the following:
- idle: No known order. Light: off
- pending: Someone pressed a button, but everyone does not know it. Light: off
- confirmed: All alive nodes know about the order and has indicated their preference to it. Light on.
- confirmed: All alive nodes know about the order and has indicated their cost to serve it. Light on.
"""

alias Elevator.Types
Expand All @@ -24,33 +24,36 @@ defmodule Elevator.HallOrders.Order do
Types.floor() => MapSet.t(Types.hall_btn())
}) ::
hall_order_value()
def merge_hall_orders(button_key, button_state, other_state, my_hall_orders) do
{new_button_version, new_button_state} = merge_orders(button_state, other_state)
def merge_hall_orders(order_key, order_value, other_order_value, my_hall_orders) do
{new_order_version, new_order_state} = merge_orders(order_value, other_order_value)

# Ensure self is in any barrier set.
{new_button_version, new_button_state} =
case new_button_state do
new_order_state =
case new_order_state do
{:pending, barrier_set} ->
{new_button_version, {:pending, MapSet.put(barrier_set, Node.self())}}
{:pending, MapSet.put(barrier_set, Node.self())}

_ ->
{new_button_version, new_button_state}
new_order_state
end

# Ensure self is in a score map
case new_button_state do
{:confirmed, cost_map} ->
my_id = Communicator.my_id()

if not Map.has_key?(cost_map, my_id) do
{new_button_version,
{:confirmed, Map.put(cost_map, my_id, Cost.compute_cost(button_key, my_hall_orders))}}
else
{new_button_version, new_button_state}
end
new_order_state =
case new_order_state do
{:confirmed, cost_map} ->
my_id = Communicator.my_id()

_ ->
{new_button_version, new_button_state}
end
if not Map.has_key?(cost_map, my_id) do
{:confirmed, Map.put(cost_map, my_id, Cost.compute_cost(order_key, my_hall_orders))}
else
new_order_state
end

_ ->
new_order_state
end

{new_order_version, new_order_state}
end

@doc """
Expand All @@ -61,25 +64,27 @@ defmodule Elevator.HallOrders.Order do
@spec update_hall_order(hall_order_key(), hall_order_value(), %{
Types.floor() => MapSet.t(Types.hall_btn())
}) :: {boolean(), hall_order_value()}
def update_hall_order(key, {button_version, button_state}, confirmed_hall_orders) do
def update_hall_order(order_key, {order_version, order_state}, confirmed_hall_orders) do
alive = Communicator.who_can_serve()

case button_state do
{:pending, barrier_set} ->
if MapSet.intersection(barrier_set, alive) == alive do
my_cost = Cost.compute_cost(key, confirmed_hall_orders)
{true, {button_version, {:confirmed, %{Communicator.my_id() => my_cost}}}}
else
{false, {button_version, button_state}}
end
{did_change, new_state} =
case order_state do
{:pending, barrier_set} ->
if MapSet.intersection(barrier_set, alive) == alive do
my_cost = Cost.compute_cost(order_key, confirmed_hall_orders)
{true, {:confirmed, %{Communicator.my_id() => my_cost}}}
else
{false, order_state}
end

_ ->
{false, {button_version, button_state}}
end
_ ->
{false, order_state}
end

{did_change, {order_version, new_state}}
end

defp merge_orders({my_version, my_state}, {other_version, other_state}) do
# This is the full state machine of the hall order consensus algorithm.
cond do
my_version > other_version ->
{my_version, my_state}
Expand All @@ -89,20 +94,14 @@ defmodule Elevator.HallOrders.Order do

true ->
case {my_state, other_state} do
{:idle, other_state} ->
{other_version, other_state}

{_, :idle} ->
{my_version, my_state}

{{:pending, my_barrier}, {:pending, other_barrier}} ->
{my_version, {:pending, MapSet.union(my_barrier, other_barrier)}}

{{:confirmed, my_cost_map}, {:confirmed, other_cost_map}} ->
{my_version, {:confirmed, Cost.merge_cost(my_cost_map, other_cost_map)}}

{{:confirmed, _}, _} ->
{my_version, my_state}
{:idle, _} ->
{other_version, other_state}

{_, {:confirmed, _}} ->
{other_version, other_state}
Expand Down
Loading