Packages

Implementation of concurrent message passing system for parallell elevators.

Current section

Files

Jump to
elevator_project lib order_assigner.ex
Raw

lib/order_assigner.ex

defmodule OrderAssigner do
@moduledoc """
Assigns orders to the best suited elevator.
Uses the following modules:
- `Order`
- `OrderDistributor`
- `OrderAssigner.CostCalculation`
"""
use GenServer
@call_timeout 1_000
@doc false
def start_link(_init_arg) do
GenServer.start_link(__MODULE__, [], name: __MODULE__)
end
# API -------------------------------------------------
@doc """
Assigns an order to the best suited elevator. Unless the elevator
is running without being connected to the node cluster, the same
elevator will not get the same order twice in a row. Calls
`OrderDistributor.distribute_new/1` after the order is assigned.
## Parameters
- order: Order to be assgined :: %Order{}
## Return
- :ok :: atom()
"""
def assign_order(%Order{button_type: :cab} = order) do
order
|> Map.put(:owner, Node.self())
|> OrderDistributor.distribute_new()
end
def assign_order(%Order{button_type: _hall} = order) do
lowest_cost =
all_costs(order)
|> remove_node(order.owner)
|> List.keysort(1)
|> List.first()
case lowest_cost do
{best_elevator, _cost} ->
order
|> Map.put(:owner, best_elevator)
|> OrderDistributor.distribute_new()
_no_replies ->
order
|> Map.put(:owner, Node.self())
|> OrderDistributor.distribute_new()
end
end
# Init ------------------------------------------------
@impl true
def init(_init_arg) do
{:ok, []}
end
# Callbacks -------------------------------------------
@impl true
def handle_call({:get_cost, %Order{} = order}, _from, state) do
cost = OrderAssigner.CostCalculation.cost(order)
{:reply, cost, state}
end
# Helper functions ------------------------------------
defp all_costs(%Order{} = order) do
{costs, _bad_nodes} =
GenServer.multi_call(
[Node.self() | Node.list()],
__MODULE__,
{:get_cost, order},
@call_timeout
)
costs
end
defp remove_node(list, node) do
list -- [{node, list[node]}]
end
end
defmodule OrderAssigner.CostCalculation do
@moduledoc """
Calculates the cost for the elevator to take a given order, based on
the current state of the elevator.
Uses the following modules:
- `Order`
- `ElevatorOperator`
"""
# API -------------------------------------------------
@doc """
Calls `ElevatorOperator.get_data/0` to retrieve the current state of the
elevator, and calculates the cost of taking the order.
## Parameters
- order: Order to be calculated cost for :: %Order{}
## Return
- Cost of the elevator :: integer()
"""
def cost(%Order{} = order) do
{floor, direction, _state, orders} = ElevatorOperator.get_data()
cond do
direction == :down and order.floor > floor ->
length(orders) + (floor - min_floor(orders)) + (order.floor - min_floor(orders))
direction == :up and order.floor < floor ->
length(orders) + (max_floor(orders) - floor) + (max_floor(orders) - order.floor)
true ->
length(orders) + abs(order.floor - floor)
end
end
# Helper functions ------------------------------------
defp max_floor(orders) do
orders
|> Enum.map(fn %Order{} = order -> order.floor end)
|> Enum.sort()
|> List.last()
end
defp min_floor(orders) do
orders
|> Enum.map(fn %Order{} = order -> order.floor end)
|> Enum.sort()
|> List.first()
end
end