4

I have built a multi player game(to be exact, 4 players) using the Message passing construct of erlang. I have followed the tictactoe game on the following link as a example but what really is similar is the Message passing construct as shown in the game:link

I then chose to run this game on ejabberd Multi user Chatroom, I did write a ejabberd hook for this. But if you look at the NewGameState in the file tictactoe.erl on the above link, you will find that there is no way of retrieving it out in a variable.

So I used mnesia and wrote each new gamestate generated to this mnesia table. Now Inside my ejabberd hook I call my game function (i.e on each call a series of modules -> "gen_server, game_modules,mnesia_modules" are executed) and inside the hook just below the call of game function I am reading from mnesia table for the gamestate as follows(here the function myMessage is the function inside ejabberd hook):

myMessage({#message = Msg, C2SState})->
    some_other_module:game_func(Args),
    State=mnesia_module:read(key),

    {Msg, C2SState};
myMessage(Acc) ->
    Acc.

Now my problem is that the read operation is giving me a empty table when the order of execution is

some_other_module:game_func(Args),
 GameState=mnesia_module:read(key),

and when I insert a delay between these two lines as timer:sleep/1 as below(the value 200 is chosen randomly after some trial with different values):

some_other_module:game_func(Args),
timer:sleep(200)
 GameState=mnesia_module:read(key),

I am getting the correct value of GameState thus suggesting me that the reading operation in line

GameState=mnesia_module:read(key),

is getting performed/executed before the line some_other_module:game_func(Args) (which is a series of modules -> "gen_server, game_modules,mnesia_modules") is able to execute the mnesia modules and write the GameState into mnesia table.

How can I resolve this issue as I don't want to use timer:sleep/1 as it is not dependable solution.

Can anyone suggest me a work around here.What I mean is can anyone suggest me a way to retrieve the GameState inside the hook by any other means other than mnesia so I do not have a race condition at all.

Or either there is some way that ejabberd provides some functionality that I can use here?

Thanks in advance.

abhishek ranjan
  • 612
  • 7
  • 23
  • Are you using mnesia:dirty_* functions ? – Pouriya Aug 11 '17 at 12:11
  • no @Pouriya I am using mnesia:write/1 to write the data while some_other_module:game_func(Args) is running and mnesia:read/3 function to read the data when GameState=mnesia_module:read(key) is called. – abhishek ranjan Aug 11 '17 at 12:23
  • Can you show the code ? – Pouriya Aug 11 '17 at 12:31
  • Most of the code is on the same line as given on the link.What is different is that in the link,inside tictactoe.erl file you will find a `NewgameState` after which I call the mnesia:write/1 and store it with the current Player id as key. – abhishek ranjan Aug 11 '17 at 12:40

2 Answers2

5

Realtime consistency of data between distributed nodes is a hard problem and you're going to need to tailor your solution to your needs. You don't say what kind of transaction you're using with mnesia, so maybe that would solve your problems.

However, here's a simple solution that may help you think about the problem:

First, let's call one of your nodes master. On the master node, start a gen_server which handles the game-state. Now, anyone who wants to read or write game-state needs to make an rpc:call/4 to the master node (unless they're already there) into a gen_server:call/2. Now all interaction with the game state is synchronous.

If you update the game state no more than a few times a second, this solution should work quite well for you. If games are independent, then each game is a different gen_server.

Nathaniel Waisbrot
  • 23,261
  • 7
  • 71
  • 99
1

I am trying to give the solution that worked for me. Hope it helps someone.

Here is what I did:

First I removed mnesia from the picture.

I first registered the Pid of the base Module as soon as it is created inside the start/2 function(you can think of tictactoe.erl present on the link provided in the question) and then I created a get_gs/0 function inside that module only to retrieve the GameState as follows(server is the alias I have used to register the Pid):

get_gs()->
    server ! {get_gs, self()},
     receive
        GameState ->
            GameState
    end.

And then Inside the loop() function I have:

{ get_gs, From } ->
           From ! GameState,

           loop(FirstPlayer, SecondPlayer, CurrentPlayer, GameState)

Then created a module implementing gen_server architecture and called a function in the following order(where ->represents function calls like A->B means From A i call B):

My custom hook on ejabberd->gen_server based module->gameclient:get_gs/0->gameserver:get_gs/0->tictactoe:get_gs/0

And I got the current GameState.

Thank you @Nathaniel Waisbrot for your valuable suggestion.

abhishek ranjan
  • 612
  • 7
  • 23
  • Looks good! When you feel ready for a little more complexity, I strongly recommend using OTP constructs (supervisor, gen_server, gen_statem). It takes a while to get your head around them but they are amazingly powerful tools that you will use _constantly_. (In contrast, Mnesia is also complex to learn but is useful only in specific circumstances.) – Nathaniel Waisbrot Aug 13 '17 at 12:17
  • Yes Surely I will look into OTP constructs.Thank you for helping.Cheers – abhishek ranjan Aug 17 '17 at 12:46