Как решить проблему гонки между вызовами функций
Я построил многопользовательскую игру (а точнее, 4 игрока), используя конструкцию передачи сообщений erlang. В качестве примера я следовал игре tictactoe по следующей ссылке, но на самом деле похожа конструкция передачи сообщений, как показано в игре: ссылка
Затем я решил запустить эту игру в многопользовательском чате ejabberd, для этого я написал хук ejabberd. Но если вы посмотрите на NewGameState в файле tictactoe.erl по приведенной выше ссылке, вы обнаружите, что нет способа извлечь его из переменной.
Поэтому я использовал mnesia и записывал каждое новое сгенерированное состояние игры в эту таблицу mnesia. Теперь внутри моего хука ejabberd я вызываю свою игровую функцию (т.е. при каждом вызове выполняется серия модулей -> "gen_server, game_modules,mnesia_modules"), а внутри хука чуть ниже вызова игровой функции, которую я читаю из таблицы мнезий для Gamestate выглядит следующим образом (здесь функция 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 предоставляет некоторые функции, которые я могу использовать здесь?
Заранее спасибо.
2 ответа
Я пытаюсь дать решение, которое сработало для меня. Надеюсь, это поможет кому-то.
Вот что я сделал:
Сначала я убрал мнезию с картинки.
Я впервые зарегистрировал 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 за ваше ценное предложение.
Согласованность данных в реальном времени между распределенными узлами является сложной проблемой, и вам нужно будет адаптировать свое решение к вашим потребностям. Вы не говорите, какую транзакцию вы используете с mnesia, так что, возможно, это решит ваши проблемы.
Однако вот простое решение, которое может помочь вам подумать о проблеме:
Во-первых, давайте назовем один из ваших узлов master
, На главном узле запустите gen_server, который обрабатывает игровое состояние. Теперь любой, кто хочет читать или писать игровое состояние, должен сделать rpc:call/4
на главный узел (если они уже там) в gen_server:call/2
, Теперь все взаимодействие с состоянием игры происходит синхронно.
Если вы обновляете игровое состояние не чаще, чем несколько раз в секунду, это решение должно работать довольно хорошо для вас. Если игры независимы, то каждая игра является отдельным gen_server.