added some docs
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
Andre Henriques 2024-01-19 18:23:08 +00:00
parent a22005c868
commit 8c674626f8
2 changed files with 291 additions and 129 deletions

View File

@ -1,11 +1,30 @@
#
# This files contains some modules nessesary for paxos to work
#
# Utils: General Utilities
# Paxos: Paxos logic
# Ballot: Contain the logic to handle the ballot numbers
# EventualLeaderElector: Leader elector code
# EagerReliableBroadcast: Handles eager reliable broadcast
#
defmodule Utils do defmodule Utils do
@moduledoc """ @moduledoc """
This module contains some helpful functions are used throughout This module contains some helpful functions are used throughout all modules
"""
@doc """
Sets the min log level
0 - show all
1 - ignore paxos
2 - ignore server
""" """
@min_print_level 3 @min_print_level 3
@doc """
This function works similiary with unicast but it allows both for a pid or an atom
to be given as the first parameter
"""
def safecast(p, m) when p == nil, do: IO.puts('Trying to safecast #{m} with p as nil') def safecast(p, m) when p == nil, do: IO.puts('Trying to safecast #{m} with p as nil')
def safecast(p, m) when is_pid(p), do: send(p, m) def safecast(p, m) when is_pid(p), do: send(p, m)
def safecast(p, m) do def safecast(p, m) do
@ -15,9 +34,19 @@ defmodule Utils do
end end
end end
@doc """
This function creates a new name for processes
"""
def alter_name(name, part), do: :"#{name}#{part}" def alter_name(name, part), do: :"#{name}#{part}"
@doc """
This function sends a message to a list of porcesses
"""
def beb_broadcast(m, dest), do: for(p <- dest, do: safecast(p, m)) def beb_broadcast(m, dest), do: for(p <- dest, do: safecast(p, m))
@doc """
This function register a pid in the global name space
"""
def register_name(name, pid, link \\ true) do def register_name(name, pid, link \\ true) do
case :global.re_register_name(name, pid) do case :global.re_register_name(name, pid) do
:yes -> :yes ->
@ -33,7 +62,10 @@ defmodule Utils do
:error :error
end end
end end
@doc """
This macro defines a new log function for a specific log level
"""
defmacro create_log(level) do defmacro create_log(level) do
quote do quote do
def log(msg) do def log(msg) do
@ -41,13 +73,26 @@ defmodule Utils do
end end
end end
end end
@doc """
This function is used bu the create_log macro.
This function only prints logs if the level definied in the create_log macro
"""
def _log(msg, level) do def _log(msg, level) do
if (@min_print_level <= level) do if (@min_print_level <= level) do
IO.puts(msg) IO.puts(msg)
end end
end end
@doc """
This macro defines a an or_state function that acts like this:
if val do
expr
else
state
end
"""
defmacro or_state(val, do: expr) do defmacro or_state(val, do: expr) do
quote do quote do
case unquote(val) do case unquote(val) do
@ -56,7 +101,10 @@ defmodule Utils do
end end
end end
end end
@doc """
This macro defines a macro to simplify the process of creating run functions
"""
defmacro runfn(do: expr) do defmacro runfn(do: expr) do
quote do quote do
def run(s) do def run(s) do
@ -69,37 +117,45 @@ defmodule Utils do
end end
end end
#
#
# Possible actions
# :kill_before_decision
# :increase_ballot_number - this makes it so that it does not propose but jump simply increases the number of the current ballot
# this is usefull when forcing a nack
#
defmodule Paxos do defmodule Paxos do
@moduledoc """
This module contains all the logic for Paxos
To start a paxos instance run Paxos.start
To propose a a value to paxos run Paxos.propose
To get a previous decision please run Paxos.get_decision
"""
require Utils require Utils
import Utils import Utils
create_log 0 create_log 0
defmacro set_instmap(do: expr) do @doc """
This macro allows the state.instmap to be updated very easily
"""
defmacrop set_instmap(do: expr) do
quote do quote do
var!(map) = var!(state).instmap[var!(inst)] var!(map) = var!(state).instmap[var!(inst)]
new_instmap = Map.put(var!(state).instmap, var!(inst), unquote(expr)) new_instmap = Map.put(var!(state).instmap, var!(inst), unquote(expr))
var!(state) = %{var!(state) | instmap: new_instmap } var!(state) = %{var!(state) | instmap: new_instmap }
end end
end end
# Starts the Paxos replica with a specific name and some processes @doc """
Starts the Paxos replica with a specific name and some processes
"""
def start(name, processes, link \\ false) do def start(name, processes, link \\ false) do
log("Starting paxos for #{name}") log("Starting paxos for #{name}")
pid = spawn(Paxos, :init, [name, processes]) pid = spawn(Paxos, :init, [name, processes])
register_name(name, pid, link) register_name(name, pid, link)
end end
# Init event must be the first @doc """
# one after the component is created Inicializes the state starts the eager reliable broadcast, and starts the eventual leader eletector
"""
def init(name, processes) do def init(name, processes) do
EventualLeaderElector.start(name, processes) EventualLeaderElector.start(name, processes)
EagerReliableBroadcast.start(name, processes) EagerReliableBroadcast.start(name, processes)
@ -114,9 +170,11 @@ defmodule Paxos do
run(state) run(state)
end end
# Guarantees that a specific state exists @doc """
def has_or_create(state, inst, value \\ nil, pid_to_inform \\ nil, action \\ nil) do Guarantees that a specific state exists for a specific instance
"""
defp has_or_create(state, inst, value \\ nil, pid_to_inform \\ nil, action \\ nil) do
or_state state.instmap[inst] == nil do or_state state.instmap[inst] == nil do
instmap = instmap =
Map.put(state.instmap, inst, %{ Map.put(state.instmap, inst, %{
@ -138,8 +196,13 @@ defmodule Paxos do
%{state | instmap: instmap} %{state | instmap: instmap}
end end
end end
def has_finished(state, inst, ignore_aborted \\ false) do @doc """
Checks if an instance has finished or if it was aborted.
If the optional parameter ignore_aborted was set to true makes this function only check
if the the instance has finished
"""
defp has_finished(state, inst, ignore_aborted \\ false) do
cond do cond do
Map.has_key?(state.decided, inst) -> true Map.has_key?(state.decided, inst) -> true
ignore_aborted -> false ignore_aborted -> false
@ -147,15 +210,32 @@ defmodule Paxos do
true -> false true -> false
end end
end end
@doc """
This is the run/recieve function
All the messages that are handled by this function are:
{:ele_trust, proc} ->
{:propose, inst, value, pid_to_inform, action} ->
{:rb_deliver, proc, {:other_propose, inst, value}} ->
{:rb_deliver, proc, {:prepare, proc, inst, ballot}} ->
{:nack, inst, ballot} ->
{:rb_deliver, _proc, {:abort, inst, ballot}} ->
{:prepared, inst, ballot, accepted_ballot, accepted_value} ->
{:rb_deliver, proc, {:accept, inst, ballot, value}} ->
{:accepted, inst, ballot} ->
{:get_value, inst, pid_to_inform} ->
{:rb_deliver, _, {:decide, inst, value}} ->
"""
runfn do runfn do
# Handles leader elector
{:ele_trust, proc} -> {:ele_trust, proc} ->
log("#{state.name} - #{proc} is leader") log("#{state.name} - #{proc} is leader")
Enum.reduce(Map.keys(state.instmap), %{state | leader: proc}, fn inst, st -> Enum.reduce(Map.keys(state.instmap), %{state | leader: proc}, fn inst, st ->
prepare(st, inst) prepare(st, inst)
end) end)
# Handles a proposal from the parent process
{:propose, inst, value, pid_to_inform, action} -> {:propose, inst, value, pid_to_inform, action} ->
log("#{state.name} - Propose #{inspect(inst)} with action #{inspect(action)}") log("#{state.name} - Propose #{inspect(inst)} with action #{inspect(action)}")
@ -195,7 +275,8 @@ defmodule Paxos do
EagerReliableBroadcast.broadcast(state.name, {:other_propose, inst, value}) EagerReliableBroadcast.broadcast(state.name, {:other_propose, inst, value})
prepare(state, inst) prepare(state, inst)
end end
# Handles the sharing of a proposal value to other processes
{:rb_deliver, proc, {:other_propose, inst, value}} -> {:rb_deliver, proc, {:other_propose, inst, value}} ->
cond do cond do
has_finished(state, inst, true) -> has_finished(state, inst, true) ->
@ -212,7 +293,8 @@ defmodule Paxos do
end end
prepare(state, inst) prepare(state, inst)
end end
# Handles a prepare request from the leader
{:rb_deliver, proc, {:prepare, proc, inst, ballot}} -> {:rb_deliver, proc, {:prepare, proc, inst, ballot}} ->
log("#{state.name} - prepare from #{proc}") log("#{state.name} - prepare from #{proc}")
@ -243,7 +325,8 @@ defmodule Paxos do
safecast(proc, {:nack, inst, ballot}) safecast(proc, {:nack, inst, ballot})
state state
end end
# Handles a nack
{:nack, inst, ballot} -> {:nack, inst, ballot} ->
log("#{state.name} - nack #{inspect(inst)} #{inspect(ballot)}") log("#{state.name} - nack #{inspect(inst)} #{inspect(ballot)}")
@ -270,6 +353,7 @@ defmodule Paxos do
state state
end end
# Handles an abort
{:rb_deliver, _proc, {:abort, inst, ballot}} -> {:rb_deliver, _proc, {:abort, inst, ballot}} ->
cond do cond do
has_finished(state, inst) -> has_finished(state, inst) ->
@ -285,6 +369,8 @@ defmodule Paxos do
state state
end end
# Handles a prepared
# Sends out accept when a quorum is met
{:prepared, inst, ballot, accepted_ballot, accepted_value} -> {:prepared, inst, ballot, accepted_ballot, accepted_value} ->
log( log(
"#{state.name} - prepared #{inspect(inst)} #{inspect(ballot)} #{inspect(accepted_ballot)} #{inspect(accepted_value)}" "#{state.name} - prepared #{inspect(inst)} #{inspect(ballot)} #{inspect(accepted_ballot)} #{inspect(accepted_value)}"
@ -311,6 +397,7 @@ defmodule Paxos do
state state
end end
# Handles a accept
{:rb_deliver, proc, {:accept, inst, ballot, value}} -> {:rb_deliver, proc, {:accept, inst, ballot, value}} ->
cond do cond do
has_finished(state, inst) -> has_finished(state, inst) ->
@ -338,6 +425,8 @@ defmodule Paxos do
end end
end end
# Handles a accept
# Sends out accept when a decide when a quoeom is met
{:accepted, inst, ballot} -> {:accepted, inst, ballot} ->
log("#{state.name} - accepted #{inspect(inst)} #{inspect(ballot)}") log("#{state.name} - accepted #{inspect(inst)} #{inspect(ballot)}")
@ -355,14 +444,17 @@ defmodule Paxos do
true -> true ->
state state
end end
# handles a parent request to get a value
{:get_value, inst, pid_to_inform} -> {:get_value, inst, pid_to_inform} ->
# log("#{state.name} get_value") # log("#{state.name} get_value")
if has_finished(state, inst, true) do if has_finished(state, inst, true) do
safecast(pid_to_inform, {:get_value_res, inst, state.decided[inst]}) safecast(pid_to_inform, {:get_value_res, inst, state.decided[inst]})
end end
state state
# Adds a decision made by a leader to the list of decisions
# and informs the parent of the decision
{:rb_deliver, _, {:decide, inst, value}} -> {:rb_deliver, _, {:decide, inst, value}} ->
log("#{state.name} - decided #{inspect(inst)} #{inspect(value)}") log("#{state.name} - decided #{inspect(inst)} #{inspect(value)}")
@ -379,13 +471,12 @@ defmodule Paxos do
end end
end end
# @doc """
# Puts process in the preapre state Does the logic to decide when to send the prepare messages
# Also sets the state nessesary to run the proposal
"""
def prepare(state, _) when state.leader != state.name, do: state defp prepare(state, _) when state.leader != state.name, do: state
defp prepare(state, inst) do
def prepare(state, inst) do
cond do cond do
state.instmap[inst] == nil -> state.instmap[inst] == nil ->
state state
@ -417,12 +508,11 @@ defmodule Paxos do
end end
end end
# @doc """
# Process the prepared responses Processes the prepared messages and when a quorum is met the accept messages are send
# """
def prepared(state, _) when state.leader != state.name, do: state defp prepared(state, _) when state.leader != state.name, do: state
defp prepared(state, inst) do
def prepared(state, inst) do
if length(state.instmap[inst].prepared_values) >= floor(length(state.processes) / 2) + 1 and if length(state.instmap[inst].prepared_values) >= floor(length(state.processes) / 2) + 1 and
not state.instmap[inst].has_sent_accept do not state.instmap[inst].has_sent_accept do
{_, a_val} = {_, a_val} =
@ -465,13 +555,12 @@ defmodule Paxos do
end end
end end
# @doc """
# Process the accepted responses Processes the accepted messages and when a qurum is met decide on the value
# """
def accepted(state, _) when state.leader != state.name, do: state defp accepted(state, _) when state.leader != state.name, do: state
defp accepted(state, inst) do
def accepted(state, inst) do or_state state.instmap[inst].accepted >= floor(length(state.processes) / 2) + 1 do
if state.instmap[inst].accepted >= floor(length(state.processes) / 2) + 1 do
value = state.instmap[inst].ballot_value value = state.instmap[inst].ballot_value
if state.instmap[inst].action == :kill_before_decision do if state.instmap[inst].action == :kill_before_decision do
@ -493,69 +582,76 @@ defmodule Paxos do
| decided: Map.put(state.decided, inst, value), | decided: Map.put(state.decided, inst, value),
instmap: Map.delete(state.instmap, inst) instmap: Map.delete(state.instmap, inst)
} }
else
state
end end
end end
#######################
# Interface #
#######################
@doc """
Send the propose message to the paxos replica and waits for a response from the correct instance
"""
def propose(pid, inst, value, t, action \\ nil) do def propose(pid, inst, value, t, action \\ nil) do
send(pid, {:propose, inst, value, self(), action}) send(pid, {:propose, inst, value, self(), action})
propose_loop({inst, t}) propose_loop(inst, t)
end end
def propose_loop(input) do @doc """
{_, t} = input Waits the right message from the paxos replica
"""
defp propose_loop(inst, t) do
receive do receive do
{:timeout, inst} -> {:timeout, ^inst} -> {:timeout}
check_and_apply({:timeout}, inst, input, &propose_loop/1) {:abort, ^inst} -> {:abort}
{:decision, ^inst, d} -> {:decision, d}
{:abort, inst} ->
check_and_apply({:abort}, inst, input, &propose_loop/1)
{:decision, inst, d} ->
check_and_apply({:decision, d}, inst, input, &propose_loop/1)
x -> x ->
Process.send_after(self(), x, 500) Process.send_after(self(), x, 500)
propose_loop(input) propose_loop(inst, t)
after after
t -> {:timeout} t -> {:timeout}
end end
end end
@doc """
Sends the :get_value message to the paxos replica
"""
def get_decision(pid, inst, t) do def get_decision(pid, inst, t) do
send(pid, {:get_value, inst, self()}) send(pid, {:get_value, inst, self()})
get_decision_loop({inst, t}) get_decision_loop(inst, t)
end end
def get_decision_loop(input) do @doc """
{_, t} = input Sends waits for the right message from the paxos replica
"""
defp get_decision_loop(inst, t) do
receive do receive do
{:get_value_res, inst, v} -> {:get_value_res, ^inst, v} ->
check_and_apply(v, inst, input, &get_decision_loop/1) v
x -> x ->
Process.send_after(self(), x, 500) Process.send_after(self(), x, 500)
get_decision_loop(input) get_decision_loop(inst, t)
after after
t -> nil t -> nil
end end
end end
def check_and_apply(v, inst, input, fun) do
{inInst, _} = input
if inst == inInst do
v
else
fun.(input)
end
end
end end
defmodule Ballot do defmodule Ballot do
def init(name, number \\ 0), do: {name, number} @moduledoc """
A set of a helper functions to manage ballots
"""
@doc """
Create a new ballot
"""
def init(name, number \\ 0), do: {name, number}
@doc """
Increase the ballot number
"""
def inc(b, name \\ nil) do def inc(b, name \\ nil) do
{old_name, number} = b {old_name, number} = b
@ -564,7 +660,10 @@ defmodule Ballot do
number + 1 number + 1
} }
end end
@doc """
Compare the name of 2 processes and select the lowest one
"""
defp lexicographical_compare(a, b) do defp lexicographical_compare(a, b) do
cond do cond do
a == b -> 0 a == b -> 0
@ -573,7 +672,9 @@ defmodule Ballot do
end end
end end
@doc """
Callculate the difference between w ballots
"""
defp diff({name1, number1}, {name2, number2}) do defp diff({name1, number1}, {name2, number2}) do
diff = number1 - number2 diff = number1 - number2
if diff == 0 do if diff == 0 do
@ -582,25 +683,42 @@ defmodule Ballot do
diff diff
end end
end end
@doc """
Compare 2 ballots
"""
def compare(b1, operator, b2), do: operator.(diff(b1, b2), 0) def compare(b1, operator, b2), do: operator.(diff(b1, b2), 0)
end end
defmodule EagerReliableBroadcast do defmodule EagerReliableBroadcast do
@moduledoc """
This modules implents eager reiable broadcast with the optimization of removing the from the deliver list after hearing
the same number of echos as there is processes
emits {:rb_deliver, proc, message}
"""
require Utils require Utils
import Utils import Utils
def get_non_rb_name(name) do @doc """
Removes _br from the name of a process
"""
defp get_non_rb_name(name) do
String.to_atom(String.replace(Atom.to_string(name), "_rb", "")) String.to_atom(String.replace(Atom.to_string(name), "_rb", ""))
end end
@doc """
Starts the Eager reliable process and registers it with a name and links the process with the parent process
"""
def start(name, processes) do def start(name, processes) do
pid = spawn(EagerReliableBroadcast, :init, [name, processes]) pid = spawn(EagerReliableBroadcast, :init, [name, processes])
register_name(alter_name(name, "_rb"), pid) register_name(alter_name(name, "_rb"), pid)
end end
# Init event must be the first @doc """
# one after the component is created Sets state and calls the run function
"""
def init(parent, processes) do def init(parent, processes) do
state = %{ state = %{
name: alter_name(parent, "_rb"), name: alter_name(parent, "_rb"),
@ -643,17 +761,24 @@ defmodule EagerReliableBroadcast do
############# #############
# Interface # # Interface #
############# #############
@doc """
Sends a a broadcast request to the eager reliable broadcast replica
"""
def broadcast(name, m), do: safecast(alter_name(name, "_rb"), {:broadcast, m}) def broadcast(name, m), do: safecast(alter_name(name, "_rb"), {:broadcast, m})
end end
#
# Emits {:ele_trust, proc }
#
defmodule EventualLeaderElector do defmodule EventualLeaderElector do
@moduledoc """
This modules implents eventual leader elector
emits {:ele_leader, proc}
"""
require Utils require Utils
import Utils import Utils
@doc """
Initializes the leader elector process and registers is under name_ele and links it with the parent process
"""
def start(name, processes) do def start(name, processes) do
new_name = alter_name(name, "_ele") new_name = alter_name(name, "_ele")
pid = spawn(EventualLeaderElector, :init, [new_name, name, processes]) pid = spawn(EventualLeaderElector, :init, [new_name, name, processes])
@ -661,8 +786,9 @@ defmodule EventualLeaderElector do
register_name(new_name, pid) register_name(new_name, pid)
end end
# Init event must be the first @doc """
# one after the component is created Initializes state and starts the run function
"""
def init(name, parent, processes) do def init(name, parent, processes) do
processes = Enum.map(processes, fn name -> alter_name(name, "_ele") end) processes = Enum.map(processes, fn name -> alter_name(name, "_ele") end)
@ -678,7 +804,10 @@ defmodule EventualLeaderElector do
run(request_heartbeats(state)) run(request_heartbeats(state))
end end
@doc """
Clears heard_back and updates the sequence number and then sends a request for heartbeats to all proccesses
"""
def request_heartbeats(state) do def request_heartbeats(state) do
state = %{state | heard_back: MapSet.new(), seq: state.seq + 1} state = %{state | heard_back: MapSet.new(), seq: state.seq + 1}
beb_broadcast({:heartbeat_request, state.name, state.seq}, state.processes) beb_broadcast({:heartbeat_request, state.name, state.seq}, state.processes)
@ -688,15 +817,18 @@ defmodule EventualLeaderElector do
end end
runfn do runfn do
# Answer to a heartbeat_request
{:heartbeat_request, name, seq} -> {:heartbeat_request, name, seq} ->
safecast(name, {:heartbeat, state.parent, seq}) safecast(name, {:heartbeat, state.parent, seq})
state state
# Update heard_back with this name
{:heartbeat, name, seq} -> {:heartbeat, name, seq} ->
or_state seq == state.seq do or_state seq == state.seq do
%{state | heard_back: MapSet.put(state.heard_back, name)} %{state | heard_back: MapSet.put(state.heard_back, name)}
end end
# One time out check the heard_back and send if a new leader is elected inform the parent
{:timeout} -> {:timeout} ->
state = or_state MapSet.size(state.heard_back) >= floor(length(state.processes)/2) + 1 do state = or_state MapSet.size(state.heard_back) >= floor(length(state.processes)/2) + 1 do
to_trust = Enum.at(Enum.sort(MapSet.to_list(state.heard_back)), 0) to_trust = Enum.at(Enum.sort(MapSet.to_list(state.heard_back)), 0)

View File

@ -1,6 +1,13 @@
defmodule ServerMacros do defmodule ServerMacros do
@moduledoc """
def create_create_loop(name, do: match_exp, else: process_exp) do This module defines some helper macros that are used within the server macro
"""
@doc """
This macro creates a wait loop to wait for the messages that are receive match what is inside the
loop
"""
defmacro create_loop(name, do: match_exp) do
function_name = :"#{name}_loop" function_name = :"#{name}_loop"
ast1 = quote do ast1 = quote do
@ -19,7 +26,6 @@ defmodule ServerMacros do
quote do quote do
defp unquote(function_name)(v, t) do defp unquote(function_name)(v, t) do
var!(v) = v var!(v) = v
unquote(process_exp)
receive do receive do
unquote(ast3) unquote(ast3)
after after
@ -28,19 +34,11 @@ defmodule ServerMacros do
end end
end end
end end
def create_create_loop(name, do: exp, else: else_exp) do @doc """
create_create_loop(name, do: exp, else: else_exp) this function tries to propose and on failure it calls the else block and
end on success tries to match with the expressions on the do block
"""
def create_create_loop(name, do: exp) do
create_create_loop(name, do: exp, else: nil)
end
defmacro create_loop(name, clauses) do
create_create_loop(name, clauses)
end
defmacro try_propose(val, do: ready, else: recal_do) do defmacro try_propose(val, do: ready, else: recal_do) do
ast1 = quote do ast1 = quote do
{:timeout} -> unquote(recal_do) {:timeout} -> unquote(recal_do)
@ -68,20 +66,29 @@ defmodule ServerMacros do
end end
defmodule Server do defmodule Server do
@moduledoc """
Contains the to run the server Code
"""
require ServerMacros require ServerMacros
import ServerMacros import ServerMacros
require Utils require Utils
import Utils import Utils
create_log 2 create_log 2
@doc """
Contains the start code for the server
"""
def start(name, participants) do def start(name, participants) do
log("starting server") log("#{name} Starting server")
pid = spawn(Server, :init, [name, participants]) pid = spawn(Server, :init, [name, participants])
register_name(name, pid, false) register_name(name, pid, false)
end end
@doc """
Initializes the state and starts the paxos inspect and then it calls the run fn
"""
def init(name, participants) do def init(name, participants) do
paxos = Paxos.start(alter_name(name, "_paxos"), Enum.map(participants, fn name -> alter_name(name, "_paxos") end), true) paxos = Paxos.start(alter_name(name, "_paxos"), Enum.map(participants, fn name -> alter_name(name, "_paxos") end), true)
state = %{ state = %{
@ -113,7 +120,10 @@ defmodule Server do
try_to_play_checks(state, game_id, move, pid_to_inform) try_to_play_checks(state, game_id, move, pid_to_inform)
end end
@doc """
Checks if the user can play the move before starting the requesting the user to play
"""
defp try_to_play_checks(state, game_id, move, pid_to_inform, repeat \\ false) do defp try_to_play_checks(state, game_id, move, pid_to_inform, repeat \\ false) do
cond do cond do
state.games[game_id] == :not_playing_in_game -> state.games[game_id] == :not_playing_in_game ->
@ -154,7 +164,10 @@ defmodule Server do
end end
end end
end end
@doc """
Tries to propose to paxos the game action
"""
defp try_to_play(state, game_id, move, pid_to_inform) do defp try_to_play(state, game_id, move, pid_to_inform) do
name = state.name name = state.name
@ -195,7 +208,10 @@ defmodule Server do
try_to_play(state, game_id, move, pid_to_inform) try_to_play(state, game_id, move, pid_to_inform)
end end
end end
@doc """
Get the most recent game_state and return it to the player
"""
defp get_game_state(state, game_id, pid_to_inform, repeat \\ false) do defp get_game_state(state, game_id, pid_to_inform, repeat \\ false) do
cond do cond do
state.games[game_id] == :not_playing_in_game -> state.games[game_id] == :not_playing_in_game ->
@ -223,7 +239,10 @@ defmodule Server do
state state
end end
end end
@doc """
This generates a new hand based on the current game state
"""
defp get_hand_for_game_state(game_state) do defp get_hand_for_game_state(game_state) do
r1 = Enum.random(0..100) r1 = Enum.random(0..100)
cond do cond do
@ -237,7 +256,10 @@ defmodule Server do
Enum.random(mn..mx) Enum.random(mn..mx)
end end
end end
@doc """
This tries to create a game by sending the create message to paxos
"""
defp try_to_create_game(state, participants) do defp try_to_create_game(state, participants) do
game_ids = Map.keys(state.games) game_ids = Map.keys(state.games)
latest = Enum.at(Enum.sort(game_ids), length(game_ids) - 1) latest = Enum.at(Enum.sort(game_ids), length(game_ids) - 1)
@ -259,14 +281,19 @@ defmodule Server do
# #
# Utils # Utils
# #
@doc """
Checks if a game has been finished
"""
defp is_finished(state, game) do defp is_finished(state, game) do
case state.games[game] do case state.games[game] do
{:finished, _} -> true {:finished, _} -> true
_ -> false _ -> false
end end
end end
@doc """
Gets up to the most recent instance
"""
defp qurey_status(state) do defp qurey_status(state) do
v = Paxos.get_decision(state.paxos, state.instance, 100) v = Paxos.get_decision(state.paxos, state.instance, 100)
or_state v != nil do or_state v != nil do
@ -274,7 +301,10 @@ defmodule Server do
qurey_status(state) qurey_status(state)
end end
end end
@doc """
Sets the modified flag
"""
defp set_modifed(state, game, val \\ false) do defp set_modifed(state, game, val \\ false) do
or_state not is_finished(state, game) and state.games[game] != :not_playing_in_game and state.games[game] != nil do or_state not is_finished(state, game) and state.games[game] != :not_playing_in_game and state.games[game] != nil do
%{state | games: Map.put(state.games, game, %{state.games[game] | modified: val})} %{state | games: Map.put(state.games, game, %{state.games[game] | modified: val})}