defmodule ServerMacros do def create_create_loop(name, do: match_exp, else: process_exp, after: after_exp) do function_name = :"#{name}_loop" ast1 = quote do {:timeout} -> :timeout end ast2 = quote do value -> log("Got unexpected value: #{inspect(value)}") Process.send_after(self(), value, t + 2000) unquote(function_name)(v, t) end ast3 = ast1 ++ match_exp ++ ast2 if after_exp != nil do quote do def unquote(function_name)(v, t) do var!(v) = v unquote(process_exp) receive do unquote(ast3) after unquote(after_exp) end end end else quote do def unquote(function_name)(v, t) do var!(v) = v unquote(process_exp) receive do unquote(ast3) after t -> :timeout end end end end end def create_create_loop(name, do: exp, else: else_exp) do create_create_loop(name, do: exp, else: else_exp, after: nil) end def create_create_loop(name, do: exp, after: after_exp) do create_create_loop(name, do: exp, else: nil, after: after_exp) end def create_create_loop(name, do: exp) do create_create_loop(name, do: exp, else: nil, after: 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 3 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), true) 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 {:get_game_state, game_id, pid_to_inform} -> get_game_state(state, game_id, pid_to_inform) end def get_game_state(state, game_id, pid_to_inform, repeat \\ false) do cond do state.games[game_id] == :not_playing_in_game -> safecast(pid_to_inform, {:game_state, game_id, :not_playing}) state state.games[game_id] == nil -> if repeat do safecast(pid_to_inform, {:game_state, game_id, :game_does_not_exist}) state else state = qurey_status(state) get_game_state(state, game_id, pid_to_inform, true) end true -> state = qurey_status(state) safecast(pid_to_inform, {:game_state, game_id, state.games[game_id].game_state, state.games[game_id].hand}) state end end def try_to_create_game(state, participants) do game_ids = Map.keys(state.games) latest = Enum.at(Enum.sort(game_ids), length(game_ids) - 1) new_game_id = if latest do latest else 0 end + 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 # # Utils # def qurey_status(state) do v = Paxos.get_decision(state.paxos, state.instance, 100) or_state v != nil do state = apply_game(state, v) qurey_status(state) end end # # Apply Game States # def apply_game(state, {:start_game, game_id, participants, new_game_state, hand}) do cond do state.games[game_id] -> raise :game_already_exists state.name in participants -> %{state | games: Map.put(state.games, game_id, %{ game_state: new_game_state, participants: participants, hand: hand[state.name], }), instance: state.instance + 1 } true -> %{state | games: Map.put(state.games, game_id, :not_playing_in_game), instance: state.instance + 1, } end end def apply_game(_, _), do: raise :do_not_know_how_to_apply_game_state ############ # Interface ############ create_loop :start_game do {:start_game_ans, game_id} -> log("Started a game #{game_id}") {:start_game, game_id} end def start_game(name, participants) do safecast(name, {:start_game, participants, self()}) start_game_loop(nil, 1000) end create_loop :get_game_state do {:game_state, ^v, :not_playing} -> IO.puts("Not Playing in that game") :not_playing {:game_state, ^v, game_state, hand} -> IO.puts("Got game state, #{inspect(game_state)}, hand: #{inspect(hand)}") {game_state, hand} {:game_state, ^v, :game_does_not_exist} -> IO.puts("Got game does not exist") nil end def get_game_state(name, game_id) do safecast(name, {:get_game_state, game_id, self()}) get_game_state_loop(game_id, 1000) 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