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
32 changes: 15 additions & 17 deletions lib/elevator/hall_orders.ex
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ defmodule Elevator.HallOrders do
_ -> [{floor, :hall_up}, {floor, :hall_down}]
end
end)
|> Enum.map(&{&1, {0, :idle}})
|> Enum.map(&{&1, :idle})
|> Enum.into(%{})

Process.send_after(self(), :refresh_hall_orders, @hall_order_refresh_period_ms)
Expand Down Expand Up @@ -104,9 +104,9 @@ defmodule Elevator.HallOrders do
@impl true
def handle_call(:get_confirmed_orders, _from, order_map) do
confirmed_orders =
Enum.filter(order_map, fn {_, {_order_version, order_state}} ->
Enum.filter(order_map, fn {_, order_state} ->
case order_state do
{:confirmed, _} -> true
{:handling, _} -> true
_ -> false
end
end)
Expand Down Expand Up @@ -146,24 +146,23 @@ defmodule Elevator.HallOrders do
def handle_cast({:button_press, floor, direction}, order_map) do
# If in idle, go to pending. Otherwise, ignore.
key = {floor, direction}
old_order_value = order_map[key]

{old_order_version, old_order_state} = old_order_value
old_order_state = order_map[key]

new_order_map =
case old_order_state do
:idle ->
Map.put(order_map, key, {old_order_version + 1, {:pending, MapSet.new([Node.self()])}})
Map.put(order_map, key, {:pending, MapSet.new([Communicator.my_id()])})

_ ->
order_map
end

new_order_value = new_order_map[key]
new_order_state = new_order_map[key]

if old_order_value != new_order_value do
if old_order_state != new_order_state do
Logger.debug(fn ->
"hall_button_press floor=#{floor} button=#{direction} from=#{inspect(old_order_value)} to=#{inspect(new_order_value)}"
"hall_button_press floor=#{floor} button=#{direction} from=#{inspect(old_order_state)} to=#{inspect(new_order_state)}"
end)
end

Expand All @@ -172,22 +171,21 @@ defmodule Elevator.HallOrders do

@impl true
def handle_cast({:arrived_at_floor, floor, direction}, order_map) do
# If in confirmed, go to idle. Otherwise, ignore.
# TODO: Find out if barrier set should be full as well?
button_type = [up: :hall_up, down: :hall_down][direction]
key = {floor, button_type}
order_value = order_map[key]
order_state = order_map[key]

new_order_map =
case order_value do
{order_version, {:confirmed, _}} ->
Map.put(order_map, key, {order_version + 1, :idle})
case order_state do
{:handling, _} ->
Map.put(order_map, key, {:arrived, MapSet.new([Communicator.my_id()])})

_ ->
order_map
end

{:noreply, new_order_map}
{:noreply, new_order_map, {:continue, :hall_update_state}}
end

@impl true
Expand Down Expand Up @@ -224,9 +222,9 @@ defmodule Elevator.HallOrders do
end

defp my_orders_from_order_map(order_map, alive) do
Enum.filter(order_map, fn {_, {_order_version, order_state}} ->
Enum.filter(order_map, fn {_, order_state} ->
case order_state do
{:confirmed, cost_map} ->
{:handling, cost_map} ->
Cost.min_alive_cost(cost_map, alive) == Communicator.my_id()

_ ->
Expand Down
90 changes: 55 additions & 35 deletions lib/elevator/hall_orders/order.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3,48 +3,52 @@ 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. An order has both a version number and a state.
State is one of the following:
one of the hall buttons.
The state of an order 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 cost to serve it. Light on.
- arrived: A node is signalling that the order has been served. Light: off
"""

alias Elevator.Types
alias Elevator.HallOrders.Cost
alias Elevator.Communicator

@type hall_order_key :: Elevator.Types.hall_order_key()
@type hall_order_value :: Elevator.Types.hall_order_value()
@type hall_order_state :: Elevator.Types.hall_order_state()

@doc """
Update a hall order based on an incoming hall order from another node.
"""
@spec merge_hall_orders(hall_order_key(), hall_order_value(), hall_order_value(), %{
@spec merge_hall_orders(hall_order_key(), hall_order_state(), hall_order_state(), %{
Types.floor() => MapSet.t(Types.hall_btn())
}) ::
hall_order_value()
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)
hall_order_state()
def merge_hall_orders(order_key, order_state, other_order_state, my_hall_orders) do
new_order_state = merge_orders(order_state, other_order_state)

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

{:arrived, barrier_set} ->
{:arrived, MapSet.put(barrier_set, Node.self())}

_ ->
new_order_state
end

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

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))}
{:handling, Map.put(cost_map, my_id, Cost.compute_cost(order_key, my_hall_orders))}
else
new_order_state
end
Expand All @@ -53,26 +57,33 @@ defmodule Elevator.HallOrders.Order do
new_order_state
end

{new_order_version, new_order_state}
new_order_state
end

@doc """
Advances a pending order to confirmed if the barrier set is full.
Computes and records this node's cost at the point of confirmation.
Returns `{true, new_value}` if the state changed, `{false, unchanged}` otherwise.
"""
@spec update_hall_order(hall_order_key(), hall_order_value(), %{
@spec update_hall_order(hall_order_key(), hall_order_state(), %{
Types.floor() => MapSet.t(Types.hall_btn())
}) :: {boolean(), hall_order_value()}
def update_hall_order(order_key, {order_version, order_state}, confirmed_hall_orders) do
}) :: {boolean(), hall_order_state()}
def update_hall_order(order_key, order_state, confirmed_hall_orders) do
alive = Communicator.who_can_serve()

{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}}}
{true, {:handling, %{Communicator.my_id() => my_cost}}}
else
{false, order_state}
end

{:arrived, barrier_set} ->
if MapSet.intersection(barrier_set, alive) == alive do
{true, :idle}
else
{false, order_state}
end
Expand All @@ -81,34 +92,43 @@ defmodule Elevator.HallOrders.Order do
{false, order_state}
end

{did_change, {order_version, new_state}}
{did_change, new_state}
end

defp merge_orders({my_version, my_state}, {other_version, other_state}) do
cond do
my_version > other_version ->
{my_version, my_state}
defp merge_orders(my_state, other_state) do
case {my_state, other_state} do
{:idle, {:arrived, _}} ->
my_state

{:idle, other_state} ->
other_state

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

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

{{:pending, _}, other_state} ->
other_state

my_version < other_version ->
{other_version, other_state}
{{:handling, my_cost_map}, {:handling, other_cost_map}} ->
{:handling, Cost.merge_cost(my_cost_map, other_cost_map)}

true ->
case {my_state, other_state} do
{{:pending, my_barrier}, {:pending, other_barrier}} ->
{my_version, {:pending, MapSet.union(my_barrier, other_barrier)}}
{{:handling, _}, {:arrived, _}} ->
other_state

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

{:idle, _} ->
{other_version, other_state}
{{:arrived, my_barrier}, {:arrived, other_barrier}} ->
{:arrived, MapSet.union(my_barrier, other_barrier)}

{_, {:confirmed, _}} ->
{other_version, other_state}
{{:arrived, _}, :idle} ->
other_state

_ ->
{my_version, my_state}
end
_ ->
my_state
end
end
end
4 changes: 3 additions & 1 deletion lib/elevator/hardware/outputs.ex
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,9 @@ defmodule Elevator.Hardware.Outputs do
end

defp set_floor_light(state) do
Driver.set_floor_indicator(state.floor)
if state.floor != :unknown do
Driver.set_floor_indicator(state.floor)
end
end

defp get_light_orders() do
Expand Down
7 changes: 3 additions & 4 deletions lib/elevator/types.ex
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,11 @@ defmodule Elevator.Types do
@type hall_order_state ::
:idle
| {:pending, MapSet.t()}
| {:confirmed, %{node_id() => integer()}}

@type hall_order_value :: {non_neg_integer(), hall_order_state()}
| {:handling, %{node_id() => integer()}}
| {:arrived, MapSet.t()}

@type hall_order_map :: %{
hall_order_key() => hall_order_value()
hall_order_key() => hall_order_state()
}

@type cab_orders_snapshot :: %{
Expand Down
20 changes: 10 additions & 10 deletions test/multi/hall_orders_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,12 @@ defmodule Test.Multi.HallOrdersTest do
# Node 1 gets a button call
:rpc.call(node1, HallOrders, :button_press, [0, :hall_up])
node1_state = :rpc.call(node1, HallOrders, :get_state, [])
assert {_, {:pending, _}} = node1_state[{0, :hall_up}]
assert {:pending, _} = node1_state[{0, :hall_up}]

# Node 1 sends their orders to node 2
assert :rpc.call(node2, HallOrders, :receive_state, [node1_state]) == :ok
node2_state = :rpc.call(node2, HallOrders, :get_state, [])
assert {_, {:pending, barrier2}} = node2_state[{0, :hall_up}]
assert {:pending, barrier2} = node2_state[{0, :hall_up}]

assert MapSet.member?(barrier2, node1)
assert MapSet.member?(barrier2, node2)
Expand All @@ -40,7 +40,7 @@ defmodule Test.Multi.HallOrdersTest do
# Node 1 sends their orders to node 3
assert :rpc.call(node3, HallOrders, :receive_state, [node1_state]) == :ok
node3_state = :rpc.call(node3, HallOrders, :get_state, [])
assert {_, {:pending, barrier3}} = node3_state[{0, :hall_up}]
assert {:pending, barrier3} = node3_state[{0, :hall_up}]

assert MapSet.member?(barrier3, node1)
assert MapSet.member?(barrier3, node3)
Expand All @@ -51,7 +51,7 @@ defmodule Test.Multi.HallOrdersTest do
assert :rpc.call(node1, HallOrders, :receive_state, [node3_state]) == :ok

node1_state = :rpc.call(node1, HallOrders, :get_state, [])
assert {_, {:confirmed, _}} = node1_state[{0, :hall_up}]
assert {:handling, _} = node1_state[{0, :hall_up}]

clique_exchange_states([node1, node2, node3])

Expand All @@ -61,7 +61,7 @@ defmodule Test.Multi.HallOrdersTest do

# All should have converged on the alive set
assert node1_state == node2_state and node2_state == node3_state
assert {_, {:confirmed, _}} = node1_state[{0, :hall_up}]
assert {:handling, _} = node1_state[{0, :hall_up}]
converged_state = node1_state

# ... so another exchange run should not affect the result
Expand All @@ -82,7 +82,7 @@ defmodule Test.Multi.HallOrdersTest do
clique_exchange_states(nodes)

node1_state = :rpc.call(node1, HallOrders, :get_state, [])
assert {_, {:confirmed, _}} = node1_state[{1, :hall_down}]
assert {:handling, _} = node1_state[{1, :hall_down}]

# Assume node3 arrives at the floor
:rpc.call(node3, HallOrders, :arrived_at_floor, [1, :down])
Expand All @@ -93,9 +93,9 @@ defmodule Test.Multi.HallOrdersTest do
node2_state = :rpc.call(node2, HallOrders, :get_state, [])
node3_state = :rpc.call(node3, HallOrders, :get_state, [])

assert {_, :idle} = node1_state[{1, :hall_down}]
assert {_, :idle} = node2_state[{1, :hall_down}]
assert {_, :idle} = node3_state[{1, :hall_down}]
assert :idle = node1_state[{1, :hall_down}]
assert :idle = node2_state[{1, :hall_down}]
assert :idle = node3_state[{1, :hall_down}]
end

@tag :manual_sending
Expand Down Expand Up @@ -143,7 +143,7 @@ defmodule Test.Multi.HallOrdersTest do
node3_state = :rpc.call(node3, HallOrders, :get_state, [])

assert node1_state == node2_state and node2_state == node3_state
assert {_, :idle} = node1_state[{2, :hall_up}]
assert :idle = node1_state[{2, :hall_up}]
end

defp clique_exchange_states([node1, node2, node3]) do
Expand Down
Loading
Loading