defmodule ServerMacros do def create_create_loop(name, do: match_exp, else: process_exp) do function_name = :"#{name}_loop" ast1 = quote do {:timeout} -> :timeout end ast2 = quote do value -> Process.send_after(self(), value, 100) unquote(function_name)(v) end ast3 = ast1 ++ match_exp ++ ast2 quote do def unquote(function_name)(v) do var!(v) = v unquote(process_exp) receive do unquote(ast3) end end end end 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 ast1 = quote do {:timeout} -> unquote(recal_do) {:abort} -> unquote(recal_do) end ast2 = quote do {:decision, v} -> var!(state) = apply_game(var!(state), v) unquote(recal_do) v -> raise "Unknown message on try_propose #{inspect(v)}" end ast3 = ast1 ++ ready ++ ast2 quote do v = Paxos.propose(var!(state).paxos, var!(state).instance, unquote(val), 1000) case v do unquote(ast3) end end end end defmodule Server do require ServerMacros import ServerMacros require Utils import Utils create_log 2 def start(name, participants) do log("starting server") pid = spawn(Server, :init, [name, participants]) register_name(name, pid, false) end def init(name, participants) do paxos = Paxos.start(alter_name(name, "_paxos"), Enum.map(participants, fn name -> alter_name(name, "_paxos") end)) state = %{ name: name, procs: participants, games: %{}, paxos: paxos, instance: 0, } run(state) end runfn do {:start_game, participants, pid_to_inform} -> {state, game_id} = try_to_create_game(state, participants) safecast(pid_to_inform, {:start_game_ans, game_id}) state end def try_to_create_game(state, participants) do game_ids = Map.keys(state.games) new_game_id = Enum.at(Enum.sort(game_ids), length(game_ids) - 1) + 1 # TODO: randomize game state new_game_state = [1, 1] # TODO: randomize Initial hand value hand = Enum.reduce(participants, %{}, fn p, acc -> Map.put(acc, p, 1) end) try_propose {:start_game, new_game_id, participants, new_game_state, hand} do {:decision, {:start_game, ^new_game_id, ^participants, ^new_game_state, ^hand}} -> state = apply_game(state, {:start_game, new_game_id, participants, new_game_state, hand}) {state, new_game_id} else try_to_create_game(state, participants) end end # # Apply Game States # def apply_game(state, {:start_game, game_id, participants, new_game_state, hand}) do if state.name in participants do %{state | games: Map.put(state.games, game_id, %{ game_state: new_game_state, participants: participants, hand: hand[state.name] })} else %{state | games: Map.put(state.games, game_id, :not_playing_in_it)} end end def apply_game(state, _), do: raise :do_not_know_how_to_apply_game_state ############ # Interface ############ create_loop :start_game do {:start_game, game_id} -> log("Started a game #{game_id}") end def start_game(name, participants) do safecast(name, {:start_game, participants, self()}) start_game_loop(nil) end ############ # Debug ############ def spinup(number_of_participants) do procs = Enum.to_list(0..number_of_participants) |> Enum.map(fn n -> :"p#{n}" end) Enum.map(procs, fn proc -> Server.start(proc, procs) end) end def kill (pids) do pids |> Enum.map(fn m -> Process.exit(m, :kill) end) end end