как справиться с состоянием гонки между вызовами функций

Я построил многопользовательскую игру (точнее, 4 игрока), используя конструкцию передачи сообщений erlang. Я следил за игрой tictactoe по следующей ссылке в качестве примера, но что действительно похоже, так это конструкция передачи сообщений, как показано в игре: ссылка

Затем я решил запустить эту игру в многопользовательском чате ejabberd, я написал для этого хук ejabberd. Но если вы посмотрите на NewGameState в файле tictactoe.erl по приведенной выше ссылке, вы обнаружите, что нет никакого способа получить его в переменной.

Поэтому я использовал mnesia и записывал каждое новое игровое состояние, сгенерированное в эту mnesia-таблицу. Теперь внутри моего хука ejabberd я вызываю свою игровую функцию (т.е. при каждом вызове выполняется ряд модулей -> «gen_server, game_modules,mnesia_modules»), а внутри хука чуть ниже вызова игровой функции я читаю из таблицы mnesia для состояние игры выглядит следующим образом (здесь функция myMessage является функцией внутри хука ejabberd):

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

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

Теперь моя проблема в том, что операция чтения дает мне пустую таблицу, когда порядок выполнения

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

и когда я вставляю задержку между этими двумя строками как timer:sleep/1, как показано ниже (значение 200 выбирается случайным образом после некоторого испытания с разными значениями):

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

Я получаю правильное значение GameState, что говорит мне о том, что операция чтения в строке

GameState=mnesia_module:read(key),

выполняется/выполняется до того, как строка some_other_module:game_func(Args) (которая представляет собой серию модулей -> "gen_server, game_modules,mnesia_modules") сможет выполнить модули mnesia и записать GameState в таблицу mnesia.

Как я могу решить эту проблему, поскольку я не хочу использовать timer:sleep/1, так как это ненадежное решение.

Может ли кто-нибудь предложить мне работу здесь. Я имею в виду, может ли кто-нибудь предложить мне способ получить GameState внутри хука любым другим способом, кроме mnesia, чтобы у меня вообще не было состояния гонки.

Или есть какой-то способ, которым ejabberd предоставляет некоторые функции, которые я могу здесь использовать?

Заранее спасибо.


person abhishek ranjan    schedule 11.08.2017    source источник
comment
Используете ли вы функции mnesia:dirty_*?   -  person Pouriya    schedule 11.08.2017
comment
нет @Pouriya Я использую mnesia:write/1 для записи данных, пока работает some_other_module:game_func(Args), и функцию mnesia:read/3 для чтения данных, когда вызывается GameState=mnesia_module:read(key).   -  person abhishek ranjan    schedule 11.08.2017
comment
Можете показать код?   -  person Pouriya    schedule 11.08.2017
comment
Большая часть кода находится в той же строке, что и в ссылке. Отличие заключается в том, что в ссылке внутри файла tictactoe.erl вы найдете NewgameState, после чего я вызываю mnesia:write/1 и сохраняю его с текущим Идентификатор игрока в качестве ключа.   -  person abhishek ranjan    schedule 11.08.2017


Ответы (2)


Согласованность данных между распределенными узлами в реальном времени — сложная проблема, и вам придется адаптировать свое решение к вашим потребностям. Вы не говорите, какую транзакцию вы используете с мнезией, так что, возможно, это решит ваши проблемы.

Однако вот простое решение, которое может помочь вам задуматься о проблеме:

Во-первых, давайте назовем один из ваших узлов master. На главном узле запустите gen_server, который обрабатывает игровое состояние. Теперь любой, кто хочет прочитать или записать игровое состояние, должен преобразовать rpc:call/4 в главный узел (если он еще не там) в gen_server:call/2. Теперь все взаимодействие с игровым состоянием синхронно.

Если вы обновляете состояние игры не чаще нескольких раз в секунду, это решение должно вам подойти. Если игры независимы, то каждая игра — это отдельный gen_server.

person Nathaniel Waisbrot    schedule 12.08.2017

Я пытаюсь дать решение, которое сработало для меня. Надеюсь, это поможет кому-то.

Вот что я сделал:

Сначала я убрал мнезию с картинки.

Сначала я зарегистрировал Pid базового модуля, как только он будет создан внутри функции start/2 (вы можете подумать о tictactoe.erl, присутствующем в ссылке, указанной в вопросе), а затем я создал функцию get_gs/0 внутри этого модуля только для получения GameState следующим образом (server — это псевдоним, который я использовал для регистрации Pid):

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

А затем внутри функции loop() у меня есть:

{ get_gs, From } ->
           From ! GameState,

           loop(FirstPlayer, SecondPlayer, CurrentPlayer, GameState)

Затем создал модуль, реализующий архитектуру gen_server, и вызвал функцию в следующем порядке (где ->представляет вызовы функций, такие как A->B, означает 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

И я получил текущий GameState.

Спасибо @Nathaniel Waisbrot за ваше ценное предложение.

person abhishek ranjan    schedule 12.08.2017
comment
Выглядит неплохо! Когда вы почувствуете, что готовы к большей сложности, я настоятельно рекомендую использовать конструкции OTP (supervisor, gen_server, gen_statem). Требуется некоторое время, чтобы разобраться с ними, но это удивительно мощные инструменты, которые вы будете использовать постоянно. (Напротив, Mnesia также сложна для изучения, но полезна только в определенных обстоятельствах.) - person Nathaniel Waisbrot; 13.08.2017
comment
Да, конечно, я рассмотрю конструкции OTP. Спасибо за помощь. - person abhishek ranjan; 17.08.2017