6 用 GenServer 做 server?

GenServer 跟我们游戏有什麽关西?

我来试试看直接套用我们的场景来解释 GenServer 怎麽使用好了,
如果想要读的话可以看 elixir 文件里的 GenServer 或是 elixir school 的 OTP 章节

在我们服务器上面,每一局新游戏会是一个独立的 process ,游戏的状态暂存与更新都在这里进行,
另外我们也需要写一系列的方法来操作每一个游戏 process,
在这边暂时称 process 内的行为为 Server 端,
用来操作 process 的外层方法为 Client 。

https://ithelp.ithome.com.tw/upload/images/20210920/20141054OVYD9TIlnK.png

这里的 pid 是指 process identifier ,每次建立一个新的游戏 process 都会回传该 process 的 pid,
我们便可以把它存起来,有了 pid 就可以对他下命令,
像是图里面的出牌,便是使用新增 process 回传的 pid 来对他下出牌要求

另外这边要提的是,上图的出牌是用 cast ,Client 发出要求之後就不会管了,继续做别的事,
後面会尽量使用这个非同步的方式。
那查看游戏状态则是用 call, Client 在发出要求後会等待 Server 端回覆。

实作建立新游戏

我们先建立 Server 端的好了,写在上次建立的 game.ex 里面

defmodule Game do
  defstruct rounds: [], host_hand: [], guest_hand: [], round: 1, winner: nil

  use GenServer

  def init(status) do
    {:ok, status}
  end

  def handle_call(:get_status, _from, status) do
    {:reply, status, status}
  end
end

要在 module 使用 GenServer 就要 use GenServer (好像废话
用了之後他会预期我们在 module 里面定义一系列的方法,在收到来自 Client 端的请求时,
会依据请求执行相对应的方法,例如建立新的 process 就会用到 init 方法,
process 暂存初始状态後回传 pid 回去,
我们赶紧来 iex 试试

$ iex game.ex

iex(1)> GenServer.start_link(Game, %Game{})
{:ok, #PID<0.115.0>}

成功了,我们使用 start_link/2 方法开启了在 Game module 的 game process,
并给他我们昨天设定好的 %Game{} struct

那我们再来来看看刚刚写来查看游戏状态的 handle_call

  def handle_call(:get_status, _from, status) do
    {:reply, status, status}
  end

handle_call/3 收三个变数 分别是事件名称、要求来源 还有目前在 process 上的 游戏状态
而在方法里面的要回传的格式是 {:reply, 要回传的东西, 更新後的状态}
因为这次只需要回传状态,不需要更新状态,所以第二三个是一样的。

我们来 iex 使用看看
刚刚 start_link 的时候忘记存 pid 我们在建立一次

iex(2)> {:ok, game_pid} = GenServer.start_link(Game, %Game{})
{:ok, #PID<0.117.0>}
iex(3)> GenServer.call(game_pid, :get_status)
%Game{guest_hand: [], host_hand: [], round: 1, rounds: [], winner: nil}

成功了,我们得到了目前的游戏状态
当然每次都要呼叫 GenServer.call(game_pid, :get_status) 有点冗长,
我们明天来帮他包装一下成为 Client 端的方法

是我的错,应该先讲的 elixir 语法们

补充 pattern matching

在 elixir 里面 = 是 pattern matching
他会尽量把右边的东西比对到左边
例如 [name, age] = ["John", 18]
这样 name 的值就会是 "John"

如果双方的架构不合 如 [name, age, gender] = ["John", 18]
或是左边有固定的冲突值 如 [name, 19] = ["John", 18]
都会错误

这次的用法是 {:ok, game_pid} = {:ok, #PID<0.117.0>}
所以 game_pid 就会是 #PID<0.117.0>

定义方法

elixir 定义方法是使用 def 接着方法名称(小括号变数) do
end 中间则是方法内容

方法必须要在 module 里面
例如

defmodule MyMath do
  def add(a, b) do
    # #符号是行注解
    # elixir 会回传方法内的最後一行结果
    # 在这个方法就是回传 a + b
    a + b
  end
end

# 这样子使用:
MyMath.add(13, 5)

一不小心就冒出超多新东西,对没有看过 elixir 的朋友觉得满不好意思的,
有任何问题就算是基本语法的都可以在下面问,我会尽量回答。


<<:  DAY06随机森林演算法(续3)

>>:  当你真心渴望某件事,整个宇宙都会联合起来帮助你完成。

Day 2:414. Third Maximum Number

今日题目 题目:414. Third Maximum Numbe 题目主题:Array, Sorti...

企划实现(22)

使用firebase简易资料库 在使用前要将专案连结至firebase 第一步:在firebase创...

第二十八天:文字排版

金鱼都能懂的网页切版:22、23、24、25 文字排版 在文字排版里,html版面基本是一模一样,只...

Day 10 (Bootstrap)

1.疑问? 一定要用Bootstrap吗? => 自己决定 我朋友说Bootstrap业界没人...

[Day 13] 常用数据显示 Table 表格-1(制式版)

传统网页曾经有用过Table 排版并显示 当各式排版方式横行的时代 Table 已经归类於资料呈现的...