Ticking clock using Phoenix Channels

208 Views Asked by At

I'm trying to create a Phoenix Channel that just contains a clock that users can subscribe to.

defmodule MyWeb.TimeChannel do
  use Phoenix.Channel
  def join("room:time", _message, socket) do
    schedule_heartbeat()
    {:ok, socket}
  end

  def handle_info(_, socket) do
    broadcast!(socket, "update", %{time: DateTime.utc_now()})
    schedule_heartbeat()
    {:noreply, socket} 
  end

  @heartbeat_ms 1000
  defp schedule_heartbeat, do: Process.send_after(self(), :heartbeat, @heartbeat_ms)

end

The issue I have is when more than one client joins, I end up scheduling multiple ticks per second.

How can I just schedule one ticking clock?

1

There are 1 best solutions below

3
cjm2671 On

I fixed this by creating a separate GenServer that posts to the channel:

defmodule MarsWeb.TimeServer do
  use GenServer

  def start_link(_) do
    GenServer.start_link(__MODULE__, %{}, name: :time_server )
  end

  def child_spec(arg) do
    %{
      id: __MODULE__,
      start: {__MODULE__, :start_link, [arg]},
      restart: :permanent,
    }
  end

  @impl true
  def init(_) do
    schedule_tick()
    {:ok, %{}}
  end

  @impl true
  def handle_info(:tick, state) do
    MarsWeb.Endpoint.broadcast!("room:time", "update", %{time: DateTime.utc_now()})
    schedule_tick()
    {:noreply, state} 
  end

  @tick_ms 1000
  defp schedule_tick, do: Process.send_after(self(), :tick, @tick_ms)

end