轮到处理桌子进程了。桌子进程抛开消息发送,基本上就是table的转调用。
无谓测试驱动先还是写代码先,反正怎么顺就怎么搞。
defmodule TableServer do use GenServer, restart: :temporary, start: {__MODULE__, :start_link, []} def start_link(table) do GenServer.start_link(__MODULE__, table, name: register_name(table)) end def init(table) do {:ok, table} end def register_name(%{} = table), do: register_name(table |> SimpleTable.get_id) def register_name(id), do: {:via, Registry, {LocalRegistry, {Table, id}}} def exist?(table) do key = {Table, table |> SimpleTable.get_id} case Registry.lookup(LocalRegistry, key) do [{_pid, _}] -> true [] -> false end end def create(player) do table = SimpleTable.init |> SimpleTable.set_id(player |> Player.get_id) |> SimpleTable.set_creator(player) |> SimpleTable.add_seat(player) TableSupervisor.start_table(table) end def join(table, player), do: GenServer.cast(table, {:join, player: player}) def quit(table, player), do: GenServer.cast(table, {:quit, player: player}) def dismiss(table, player), do: GenServer.cast(table, {:dismiss, player: player}) def start(table, player), do: GenServer.cast(table, {:start, player: player}) def open(table, player), do: GenServer.cast(table, {:open, player: player}) def makeup(table, player), do: GenServer.cast(table, {:makeup, player: player}) def handle_cast(request, table) do {:ok, table} = inner_handle_cast(request, table) {:noreply, table} end def send_error(_player, _error) do end def inner_handle_cast({:join, player: player}, table) do with {:ok, table} <- table |> SimpleTable.join(player) do seat = SimpleTable.find_seat(table, player) broadcast_join(table, seat) else {:error, error} -> send_error(player, error) end {:ok, table} end def inner_handle_cast({:quit, player: player}, table) do with {:ok, table} <- table |> SimpleTable.quit(player) do broadcast_quit(table, player) else {:error, error} -> send_error(player, error) end {:ok, table} end def inner_handle_cast({:dismiss, player: player}, table) do with {:ok, table} <- table |> SimpleTable.dismiss(player) do broadcast_dismiss(table) else {:error, error} -> send_error(player, error) end {:ok, table} end def inner_handle_cast({:start, player: player}, table) do with {:ok, table} <- table |> SimpleTable.start(player) do broadcast_start(table) else {:error, error} -> send_error(player, error) end {:ok, table} end def inner_handle_cast({:open, player: player}, table) do with {:ok, table} <- table |> SimpleTable.open(player) do send_open(table, player) else {:error, error} -> send_error(player, error) end {:ok, table} end def inner_handle_cast({:makeup, player: player}, table) do with {:ok, table} <- table |> SimpleTable.make_up(player) do send_makeup(table, player) else {:error, error} -> send_error(player, error) end {:ok, table} end def broadcast_join(_table, _seat) do end def broadcast_quit(_table, _player) do end def broadcast_dismiss(_table) do end def broadcast_start(_table) do end def send_open(_table, _player) do end def send_makeup(_table, _player) do end end
虽然table_server 很简单,但我还是花了点时间在上面。
主要在考虑下面的问题:
1. 要不要用exactor 库简化api接口
后来没有用, exactor 还是适合于速错模式用, 而游戏我们通常要try catch,如果要用,需要包装exactor的宏,麻烦。
当然如果把table存到ets里, 就可以比较方便的崩溃恢复, 也许这比较适合用exactor。
2. inner_handle_cast 应该是怎么样的接口才方便修改
想来想去,用 {cmd, keyword_list} 比较方便, 直观且容易修改
3. 消息发送怎么样才方便以及直观
一开始是尝试 broadcast_table, 诱惑是凡是发消息就调用该接口。
但明显感觉有2个缺陷
一个是粒度太大(结果是该函数里比如要有不同分支)
一个无法直观每个操作的具体影响
所以最后改成,需要发送什么,就搞个api发送什么, 这就有broadcast_join broadcast_quit 等等,
感觉就清晰很多,自然很多,并且粒度小了发送的信息也少了。
下回增加相关的测试和代码吧