So I’m not sure this is an issue with genserver itself, the channel communication, or the way I’ve got the JS hooked up. The architecture is as:
- User loads the page, JS imports happen, a VueJS component is instantiated, on the mount trigger for the component the user connects to a general channel for this “duel”. The response from joining includes the state of the game, which is used to populate the VueJS component and a another call to join a user particular channel is made.
The problem I’m running into is that, if I have the game going and state has been altered, and then I refresh the page, the state I’m being served is stale on the first connection. If I then take an action the update broadcast actually shows the correct state (and updates the vuejs component accordingly). My question is why is the gen state serving a stale state on the beginning and then serving the correct one afterwards?
The code is as follows:
#js side
imports {.... }
export default class View extends Shared {
...
Vue.component('Game', Game)
new Vue({
el: 'main',
data() {
return {
...
}
},
mounted() {
this.channel = socket.channel("duel:"+window.gameId, {token: window.userToken});
this.channel.join()
.receive("ok", response => {
this.user_id = response.user_id;
...
joinUserChannel(this, response.user_id);
}
}
}
#channel side
def join("duel:" <> id, payload, socket) do
case authorized?(payload, socket, id) do
{:ok, socket} ->
stats = Monitor.game_stats(id) |> Processor.clean_stats(socket.assigns.current_user)
{:ok, %{user_id: socket.assigns.current_user, game: stats}, socket}
{:error, _} ->
{:error}
end
end
def handle_in("pass", %{"game_id" => game_id}, socket) do
IO.puts("handle in pass")
stats = Monitor.game_stats(game_id) |> Processor.pass(socket.assigns.current_user)
|> broadcast_this
{:noreply, socket}
end
#monitor
defmodule AetherWars.Duels.Monitor do
@moduledoc """
Keeps and serves Game State
"""
use GenServer
alias AetherWars.Duels.Processor
alias AetherWars.Games
...
def start_link(game_id) do
IO.puts("Monitor start_link -> game #{game_id}")
GenServer.start_link(__MODULE__, game_id, name: ref(game_id))
end
def init(game_id) do
IO.puts("Initializing Game #{game_id}")
game = Games |> Games.players |> AetherWars.Repo.get(game_id)
case game.game do
nil ->
IO.puts("Initializing Game #{game_id} - doesn't exist")
state = Processor.create(game)
changeset = Ecto.Changeset.change game, game: state
AetherWars.Repo.update changeset
{:ok, state}
game ->
IO.puts("Initializing Game #{game_id} - exists")
state = game
{:ok, state}
end
end
def game_stats(game_id) do
IO.puts("game_stats game: #{game_id}")
try_call game_id, {:game_stats}
end
...
def handle_call({:game_stats}, _from, state) do
IO.puts("handle_call game_stats")
{:reply, state, state}
end
defp try_call(game_id, call_function) do
case GenServer.whereis(ref(game_id)) do
nil ->
IO.puts("try_call whereis nil")
case create(game_id) do
{:ok, pid} ->
GenServer.call(pid, {:game_stats})
_ ->
{:error}
end
pid ->
IO.puts("try_call whereis not nil match")
IO.inspect(pid)
GenServer.call(pid, call_function)
end
end
So what I’m not understanding is why the first call to Monitor.game_stats(game_id) on the join seems to return a stale state but as soon as I make any other action that triggers the same Monitor.game_stats the returned state is actually the updated version?
Am I missing something obvious? I could make it ask explicitly for the game state after joining once again, but shouldn’t it be serving the correct state right away? The only difference when making the call is that it then broadcasts them to the particular channel of the user, but the data is loaded into the vuecomponent the same way (and indeed on the join it’s loading data, but just as if the state was the init state).
(do channels cache their response? because then a refresh of the page could mean the cached response was being served over the channel? it doesn’t seem to make sense though…)
Thanks