/var/log/messages

Jul 21, 2019 - 2 minute read - Comments - programming

Game of Life の実装 (2)

気になっていた以下な記述、Cell なプロセス単位で位置情報も保持、ということなのか。

  # Start a generic server process for the Cell. The process 
  # is named according to the position with a Registry as index
  def start_link(position) do
    GenServer.start_link(__MODULE__, position, name: {
      :via, Registry, {Cell.Registry, position}
    })
  end

これを使ってこんなことしています。

  # Translate the Cell position {x, y} into the process
  # ID if the Cell is alive (avoid ghost neighbours)
  def lookup(position) do
    Cell.Registry
    |> Registry.lookup(position)
    |> Enum.map(fn
      {pid, _valid} -> pid
      nil -> nil
    end)
    |> Enum.filter(&Process.alive?/1)
    |> List.first
  end

ある位置の Cell は生存状態かどうか、な判定ができるのかどうか。参考にしたのは以下です。

とりあえず

最上位なナニから掘削を試みてみます。Universe.Supervisor から。キモは以下の部分かと。

  # Supervise the Universe object, the Cell supervisor and
  # the Cell registry with one_for_one strategy: if a
  # child process terminates, only that process is restarted
  def init(_) do
    children = [
      worker(Universe, []),
      supervisor(Cell.Supervisor, []),
      supervisor(Registry, [:unique, Cell.Registry])
    ]
    supervise(children, strategy: :one_for_one)
  end

これってこーゆー意味になるのかな。

Universe.Supervisor --+-- Universe
                      |
                      +-- Cell.Supervisor --+-- Cell
                      |
                      +-- Registry --+-- Cell.Registry

なるほど。one_for_one な strategy というのは失敗したやつのみを再生成、なのかどうか。あるいはスルーしている以下の部分ですが

defmodule Universe.Supervisor do
  use Supervisor

  # Entry point for the application
  def start(_type, _args) do
    start_link()
  end

  # Start a supervisor for the Universe
  def start_link do
    Supervisor.start_link(__MODULE__, [], name: __MODULE__)
  end

  # Supervise the Universe object, the Cell supervisor and
  # the Cell registry with one_for_one strategy: if a
  # child process terminates, only that process is restarted
  def init(_) do
  • start がエントリポイント
  • start_link は start から呼び出されている
  • init は初期化終了時 (?) の callback

という理解で良いのかしら

Universe 確認してみます。基本的にゲームの進行を tick という i/f にて管理していることがわかります。以下、記述について確認してみる方向にて。

  def handle_call({:tick}, _from, []) do
    get_cells()
    |> tick_each_process
    |> wait_for_ticks
    |> reduce_ticks
    |> reap_and_sow
    {:reply, :ok, []}
  end

  # Return all the Cell processes
  defp get_cells do
    Cell.Supervisor.children
  end

  # Asynchronously advance each Cell to next state
  defp tick_each_process(processes) do
    map(processes, &(Task.async(fn -> Cell.tick(&1) end)))
  end

  # Wait for the asynchronous ticks of the Cells
  defp wait_for_ticks(processes) do
    map(processes, &Task.await/1)
  end

  # Build lists for Cells to be reaped and sowed
  defp reduce_ticks(ticks) do
    reduce(ticks, {[], []}, &accumulate_ticks/2)
  end

  # Accumulator helper fn
  defp accumulate_ticks({reap, sow}, {acc_reap, acc_sow}) do
    {acc_reap ++ reap, acc_sow ++ sow}
  end

  # Reap and sow Cells according to the provided lists  
  defp reap_and_sow({to_reap, to_sow}) do
    map(to_reap, &Cell.reap/1)
    map(to_sow,  &Cell.sow/1)
  end

ざっくり、以下なカンジ?

  • Cell.Supervisor が持ってる Cell を全て取得
  • 非同期に Cell.tick 実行
  • 非同期処理の終了待ち
  • 生まれる、消える、な Cell の列挙
  • 列挙されたものについて実際に生まれる、消えるを適用

reduce て何だろ、と思ったのですがおそらくは

  • 第一引数なリストを順に
  • 第三引数な手続きを適用
  • 第二引数な初期値のソレを accumulator として第一引数なリストの car を渡すのか

書いてて自分でも分かりにくいw

Game of Life の実装 Game of Life の実装 (3)

comments powered by Disqus