printing different messages for each player causes desync

Place to get help with not working mods / modding interface.
Post Reply
User avatar
Optera
Smart Inserter
Smart Inserter
Posts: 2918
Joined: Sat Jun 11, 2016 6:41 am
Contact:

printing different messages for each player causes desync

Post by Optera »

I've tried making a function able to show different granularity for each player.

Code: Select all

if log_level >= 2 then printmsg("test", false) end

local lastMessages = {}

function printmsg(msg, useFilter)
  if useFilter == nil then
    useFilter = true
  end
  --suppress message if among the last n messages
  if useFilter and message_filter_size > 0 then
    for i=#lastMessages, 1, -1 do
      if lastMessages[i] == msg then --don't spam the same message
        return
      end
    end
    lastMessages[#lastMessages+1] = msg
    if #lastMessages > message_filter_size then --remove oldest message
      table.remove(lastMessages, 1)
    end
  end

  if log_output == "console" or log_output == "both" then
    game.print("[LTN] " .. msg)
  end
  if log_output == "log" or log_output == "both" then
    log("[LTN] " .. msg)
  end
end
log_level, log_output and lastMessages can be different for each player.
Is there a way to make this work per player or do i have to turn log_level, log_output and lastMessages into globals so they are synced?

User avatar
Adil
Filter Inserter
Filter Inserter
Posts: 945
Joined: Fri Aug 15, 2014 8:36 pm
Contact:

Re: printing different messages for each player causes desync

Post by Adil »

Everything that should be retained across tick should be in global.
Make it global.lastMessages[player_index]={}.
I do mods. Modding wiki is friend, it teaches how to mod. Api docs is friend too...
I also update mods, some of them even work.
Recently I did a mod tutorial.

User avatar
Optera
Smart Inserter
Smart Inserter
Posts: 2918
Joined: Sat Jun 11, 2016 6:41 am
Contact:

Re: printing different messages for each player causes desync

Post by Optera »

What about log_level?
It seems even calling the function for only some players causes desync.

User avatar
Klonan
Factorio Staff
Factorio Staff
Posts: 5150
Joined: Sun Jan 11, 2015 2:09 pm
Contact:

Re: printing different messages for each player causes desync

Post by Klonan »

Optera wrote:What about log_level?
It seems even calling the function for only some players causes desync.
All values that you want to reuse again should be put in the global table, unless you are strictly using them as constant variables

I would recommend putting lastmessages as a player name varible in global:

Code: Select all

global.last_messages[player.name] = { }
And then only access that for players in your function for each player

useFilter should be fine because it is just a local argument to that function

If you are changing log_output at all, you should keep it in global too

User avatar
Optera
Smart Inserter
Smart Inserter
Posts: 2918
Joined: Sat Jun 11, 2016 6:41 am
Contact:

Re: printing different messages for each player causes desync

Post by Optera »

Great.... that means I I have to use global.log_level or global.log_level[playerName] and also find a way to reset it to log_level from config.lua when loading a save.
Would be really simple IF we where allowed to change globals in on_load since neither on_init, on_configuration_changed nor on_player_created always trigger when loading a savegame.
Why can't we have an event that fires every time a player starts/loads/joins a game in single player, sandbox and multiplayer which also allows us to properly initialize? :cry:

Probably the best course is to forget about having different log levels for players and force everyone to use the same config file setting, which still leaves a potentially bloating of global.lastMessage[playerName].

User avatar
prg
Filter Inserter
Filter Inserter
Posts: 947
Joined: Mon Jan 19, 2015 12:39 am
Contact:

Re: printing different messages for each player causes desync

Post by prg »

Well, having different settings for different people is a great way to cause desyncs, yes. Everyone needs to agree what everyone else is printing to the console. Maybe use an in game GUI that people can click to change their settings in a way that stays synchronized across clients. Then instead of using game.print(), iterate over all players and use player.print() according to a player's settings.
Automatic Belt (and pipe) Planner—Automate yet another aspect of constructing your factory!

User avatar
Adil
Filter Inserter
Filter Inserter
Posts: 945
Joined: Fri Aug 15, 2014 8:36 pm
Contact:

Re: printing different messages for each player causes desync

Post by Adil »

Optera wrote:Great.... that means I I have to use global.log_level or global.log_level[playerName] and also find a way to reset it to log_level from config.lua when loading a save.
Players can't have different config.lua. You will have to wait for 0.15 or to implement ingame means for setting up settings.
I do mods. Modding wiki is friend, it teaches how to mod. Api docs is friend too...
I also update mods, some of them even work.
Recently I did a mod tutorial.

User avatar
Optera
Smart Inserter
Smart Inserter
Posts: 2918
Joined: Sat Jun 11, 2016 6:41 am
Contact:

Re: printing different messages for each player causes desync

Post by Optera »

prg wrote:Well, having different settings for different people is a great way to cause desyncs, yes. Everyone needs to agree what everyone else is printing to the console. Maybe use an in game GUI that people can click to change their settings in a way that stays synchronized across clients. Then instead of using game.print(), iterate over all players and use player.print() according to a player's settings.
I'd rather not add to the button spam. there's way too much of that already.
The reasoning behind desync for any print function eludes me. That'd mean a whisper message should also cause desync.
Adil wrote:Players can't have different config.lua. You will have to wait for 0.15 or to implement ingame means for setting up settings.
I can however use a remote call to overwrite the variable. Being able to change log level without editing the config has been quite useful in finding some bugs already.

The only way I see to make user defined log levels work with the cumbersome need to make it global while having no way to (re)initialize globals in on_load pretty much leaves me with only two options of either dropping the feature or write my own on_load called in on_tick.

User avatar
prg
Filter Inserter
Filter Inserter
Posts: 947
Joined: Mon Jan 19, 2015 12:39 am
Contact:

Re: printing different messages for each player causes desync

Post by prg »

Optera wrote:I'd rather not add to the button spam. there's way too much of that already.
Then find a different way of letting players change things around, like a key binding or whatever.
Optera wrote:The reasoning behind desync for any print function eludes me. That'd mean a whisper message should also cause desync.
The message history of every player is part of the game state, so every other player needs to know about it even if it's not shown directly on the screen for them.
Automatic Belt (and pipe) Planner—Automate yet another aspect of constructing your factory!

User avatar
Klonan
Factorio Staff
Factorio Staff
Posts: 5150
Joined: Sun Jan 11, 2015 2:09 pm
Contact:

Re: printing different messages for each player causes desync

Post by Klonan »

Optera wrote:
prg wrote:Well, having different settings for different people is a great way to cause desyncs, yes. Everyone needs to agree what everyone else is printing to the console. Maybe use an in game GUI that people can click to change their settings in a way that stays synchronized across clients. Then instead of using game.print(), iterate over all players and use player.print() according to a player's settings.
I'd rather not add to the button spam. there's way too much of that already.
The reasoning behind desync for any print function eludes me. That'd mean a whisper message should also cause desync.
Adil wrote:Players can't have different config.lua. You will have to wait for 0.15 or to implement ingame means for setting up settings.
I can however use a remote call to overwrite the variable. Being able to change log level without editing the config has been quite useful in finding some bugs already.

The only way I see to make user defined log levels work with the cumbersome need to make it global while having no way to (re)initialize globals in on_load pretty much leaves me with only two options of either dropping the feature or write my own on_load called in on_tick.
Why do you need to reinitialize your global variable? the point of the global variable is specifically so its preserved during save and load, and thus doesn't need to re initialized.

You can also just have a function you can call on_player_join or something, to set the global to whatever you want.

Nexela
Smart Inserter
Smart Inserter
Posts: 1828
Joined: Wed May 25, 2016 11:09 am
Contact:

Re: printing different messages for each player causes desync

Post by Nexela »

Optera wrote: The reasoning behind desync for any print function eludes me. That'd mean a whisper message should also cause desync.
It is not that you are printing to player x and not y. It is that client A is printing msg to player 1, and client b is printing a different msg to player 1 (or not printing a message at all). Now the game is out of sync.

Desyncs are caused by not doing the exact same thing on both clients. (i.e. print the same message to the same player). The biggest cause of this is use of variables that are not serialised in the save being recalled at a different times. Take buttons for example. You can have player 1 only have a button but both client A and client B need to create that button for player 1.
The only way I see to make user defined log levels work with the cumbersome need to make it global while having no way to (re)initialise globals in on_load pretty much leaves me with only two options of either dropping the feature or write my own on_load called in on_tick.
Here is how I do it in Nanobots


When the control file is loaded I read my config table into a mod global variable called CONFIG my config table contains things like loglevel, debug, etc. During on_init I copy my CONFIG table to global.config. (this is a general overview my configs are actually broken up in the data and control configs)
Then I have remote calls that change the log level and save that to a persistent player (or global) global.players[index] table. Then a remote.call is used to change the loglevel. There is no need to "reset" the log level when loading a saved game. If the player has to manually change the loglevel then the player can manauly change it back when they no longer need the logging.

User avatar
Optera
Smart Inserter
Smart Inserter
Posts: 2918
Joined: Sat Jun 11, 2016 6:41 am
Contact:

Re: printing different messages for each player causes desync

Post by Optera »

Nexela wrote: Here is how I do it in Nanobots


When the control file is loaded I read my config table into a mod global variable called CONFIG my config table contains things like loglevel, debug, etc. During on_init I copy my CONFIG table to global.config. (this is a general overview my configs are actually broken up in the data and control configs)
Then I have remote calls that change the log level and save that to a persistent player (or global) global.players[index] table. Then a remote.call is used to change the loglevel. There is no need to "reset" the log level when loading a saved game. If the player has to manually change the loglevel then the player can manauly change it back when they no longer need the logging.
That does sound very complicated and on_init does only get called on new games or first time a mod is loaded so it's no option to reset global variables to config.lua values.
Nexela wrote:It is not that you are printing to player x and not y. It is that client A is printing msg to player 1, and client b is printing a different msg to player 1 (or not printing a message at all). Now the game is out of sync.

Desyncs are caused by not doing the exact same thing on both clients. (i.e. print the same message to the same player). The biggest cause of this is use of variables that are not serialised in the save being recalled at a different times. Take buttons for example. You can have player 1 only have a button but both client A and client B need to create that button for player 1.
When called in scripts from each player shouldn't game.print write the same message #players times?

Replacing game.print with game.player.print is by far the most straight forward way.
Do I have to store the player index/name during on_player_joined or can I get the player running a script directly?

Nexela
Smart Inserter
Smart Inserter
Posts: 1828
Joined: Wed May 25, 2016 11:09 am
Contact:

Re: printing different messages for each player causes desync

Post by Nexela »

Disclaimer: This was a lot more though out until Chrome decided to crash and erase everything
Optera wrote: That does sound very complicated and on_init does only get called on new games or first time a mod is loaded so it's no option to reset global variables to config.lua values.
This is where I go back to why reset it? If they manually change the loglevel, they can manually change it back. If you really want to reset it doing it in on_player_joined would be the best option as long as you remember that in SP joined is only called one time.
When called in scripts from each player shouldn't game.print write the same message #players times?
This is exactly what happens. Desyncs are caused when the game DOES different things. Lets walk through this simplified example code:

Your mod has a variable called MESSAGE.
Client A connects
Upon loading the mod Client A sets MESSAGE to "This is ok" -- This is the default declaration in your code
After some time you change MESSAGE to "Why does this desync?!?"
Client B connects
Upon loading the mod MESSAGE for client B gets set to "This is ok". (Client B can't see variables from Client A so it uses its own)
After some time you do game.print(MESSAGE)
Client A does game.print("Why does this desync") to both Client A and Client B
Client B does game.print("This is ok") to both Client A and Client B
At this point game differs between both players and desync happens.
Do I have to store the player index/name during on_player_joined or can I get the player running a script directly?
It depends on what you are doing and how you are comparing the data. Some events pass a player index. If you are trying to do something based on a variable set for the player then yes you need to store the players extra variables in the global. table. Typically this is done in on_player_created. Then in on_joined you could set the loglevel to whatever is in config.
Optera wrote:Replacing game.print with game.player.print is by far the most straight forward way.
If you are printing different message for players this is the way to do it. It still follows the same conventions from above that ALL clients need to send the SAME message to the player.

User avatar
Optera
Smart Inserter
Smart Inserter
Posts: 2918
Joined: Sat Jun 11, 2016 6:41 am
Contact:

Re: printing different messages for each player causes desync

Post by Optera »

Nexela wrote:Disclaimer: This was a lot more though out until Chrome decided to crash and erase everything
That's a bummer, but your explanations are really helpful as they are.
Optera wrote: That does sound very complicated and on_init does only get called on new games or first time a mod is loaded so it's no option to reset global variables to config.lua values.
This is where I go back to why reset it? If they manually change the loglevel, they can manually change it back. If you really want to reset it doing it in on_player_joined would be the best option as long as you remember that in SP joined is only called one time.
Loading a save should show all messages once and all settings should revert to config.lua settings. When loading a save I can't remember if I had iron available nor do I want the higher log level from a previous debug session. Maybe it's only me, but I really like having things reset to a predefined state instead of having to remember to reset them manually.
When called in scripts from each player shouldn't game.print write the same message #players times?
This is exactly what happens. Desyncs are caused when the game DOES different things. Lets walk through this simplified example code:

Your mod has a variable called MESSAGE.
Client A connects
Upon loading the mod Client A sets MESSAGE to "This is ok" -- This is the default declaration in your code
After some time you change MESSAGE to "Why does this desync?!?"
Client B connects
Upon loading the mod MESSAGE for client B gets set to "This is ok". (Client B can't see variables from Client A so it uses its own)
After some time you do game.print(MESSAGE)
Client A does game.print("Why does this desync") to both Client A and Client B
Client B does game.print("This is ok") to both Client A and Client B
At this point game differs between both players and desync happens.
Optera wrote:Replacing game.print with game.player.print is by far the most straight forward way.
If you are printing different message for players this is the way to do it. It still follows the same conventions from above that ALL clients need to send the SAME message to the player.
If I where to store the player object for every client in currentPlayer, would calling currentPlayer.print(MESSAGE) result in
Client A does game.player[A].print("Why does this desync") only to ClientA
Client B does game.player.print("This is ok") only to ClientB

Otherwise I'd have to make MESSAGE into global.MESSAGE[player] and use for player in game.players do player.print(MESSAGE[player]?

Nexela
Smart Inserter
Smart Inserter
Posts: 1828
Joined: Wed May 25, 2016 11:09 am
Contact:

Re: printing different messages for each player causes desync

Post by Nexela »

log_level, log_output and lastMessages can be different for each player.
Is there a way to make this work per player or do i have to turn log_level, log_output and lastMessages into globals so they are synced?
Mostly pseudo/unfinished code:

on_configuration_changed and on_init

Code: Select all

--loop through all existing players and create a per player global table, I typically index on the players index as this makes it easier :)
global.players = {}
for index in pairs(game.players) do
  global.players[index] = {
    log_level = "Default Log Level",
    log_output = "Default log output",
    messages = {},
  }
end
on_player_created

Code: Select all

global.players[event.player_index] = {
  log_level = "Default Log Level"
  log_output = "Default Log Output"
  messages = {},
}
Whatever event you use to write the message

Code: Select all

local playerData = global.players[event.player_index]
playerData.messages[#playerData.messages+1] = "blah"
in whatever event you want to use to display/log the messages

Code: Select all

for index, playerData in pairs(global.players) do
  player = game.players[index]
  for i=#playerData.messages, 1, -1 do
  --Not sure why you would do it this way as it would print last in first.
    player.print(playerData.messages[i])  --each client will do this so the game stays in sync
    table.remove(playerData.messages, i)
  end
end

User avatar
Optera
Smart Inserter
Smart Inserter
Posts: 2918
Joined: Sat Jun 11, 2016 6:41 am
Contact:

Re: printing different messages for each player causes desync

Post by Optera »

Thanks for the help.
It was very educational trying to get the (unintended) SP functionality into MP. In the end I did settle for just making message buffer for filtering spam-y messages global for all players and warn players not to change log status during MP sessions.
The additional overhead in runtime simply was not worth such a feature.

Post Reply

Return to “Modding help”