[Solved] Multiplayer Events Sequence
Posted: Fri Oct 16, 2020 5:01 pm
Sorry - this one's a doozy
Ask: Can anyone point me to the minimally required sequence of events I should account for to make multiplayer work for my mod? Between players joining/leaving (and then rejoining), new/save games I'm starting to get brain mush. I haven't even gotten to configuration changes yet.
Problem: When Player2 joins, leaves, and then joins my game a SECOND time, I get an error saying that a gui element already exists - and its referencing the very first child object of my custom player gui.
Error:
Mod Structure:
Civ definition:
Gui definition:
Ask: Can anyone point me to the minimally required sequence of events I should account for to make multiplayer work for my mod? Between players joining/leaving (and then rejoining), new/save games I'm starting to get brain mush. I haven't even gotten to configuration changes yet.
Problem: When Player2 joins, leaves, and then joins my game a SECOND time, I get an error saying that a gui element already exists - and its referencing the very first child object of my custom player gui.
Error:
Code: Select all
Error while running event CityPeeps::on_player_joined_game (ID 43)
Gui element with name k2cp_main_table already present in the parent element.
- My mod has a Civ class created at control stage - created once and commonly referenced for all players.
- Among many other things, in Civ self.state, I have a guis table that manages player GUIs for multiplayer.
- The guis table inserts in on_player_joined_game and removes in on_player_left with instantiations of a Gui class that is each player's gui.
- After resolving all my self-made 'ID 10 T' bugs and assuming I've tested everything I should, I haven't been able to break singleplayer for my mod. This includes game save/loads, player respawn, creating, mining, and destroying custom entities with player/robots etc.
- Multiplayer has never encountered any desyncs yet. To be honest, I'm not even sure what a desync look like. I'm testing with two of my own accounts.
- Multiplayer GUI is updating as I want it to - common elements are displayed correctly and each player's clicks are unique to them. The common Civ class is being updated as it should and those changes are displayed in all player guis. Even the console messages and inventory updates for all players is working correctly.
- As a player joins, my guis table seems to be adding a single instance correctly - with the player_index verified.
- As a player leaves, my guis table seems to be removing the appropriate instance correctly - with the table count and player_index verified.
- Then I had the same account that was player 2 join a second time and boom. The only way I could get a 'this gui element already exists' is if I'm mixing my signals between tracked instances (in which chase, how in the heck is everything else working fine) or there's some saved/cached version of player two still left in my game?
Civ definition:
Code: Select all
Civ = {}
function Civ:init()
self.state = {
...
guis = {}, -- Manage each player's GUI
}
global.civ_state = self.state
end
function Civ:load()
if global.civ_state ~= nil then
self.state = global.civ_state
for _, academy in pairs(self.state.academies) do
setmetatable(academy, {__index = Academy})
end
for _, city in pairs(self.state.cities) do
setmetatable(city, {__index = City})
end
for _, hub in pairs(self.state.hubs) do
setmetatable(hub, {__index = Hub})
end
for _, resort in pairs(self.state.resorts) do
setmetatable(resort, {__index = Resort})
end
for _, gui in pairs(self.state.guis) do
setmetatable(gui, {__index = Gui})
end
if global.actions then
script.on_event(defines.events.on_tick, on_tick)
end
end
end
function Civ:player_joined(event)
local player = game.players[event.player_index]
if not (player and player.valid) then return end
local new_gui = Gui:new()
new_gui:init_gui(event)
table.insert(self.state.guis, new_gui)
Civ:debug("Player Gui Size: "..#self.state.guis)
end
function Civ:player_left(event)
for index, gui in ipairs(self.state.guis) do
if (gui.state.player_index == event.player_index) then
table.remove(self.state.guis, index)
end
end
for index, gui in ipairs(self.state.guis) do
Civ:debug("Player Index Remaining Post On Left: "..gui.state.player_index)
end
Civ:debug("Player Gui Size: "..#self.state.guis)
end
Code: Select all
Gui = {}
function Gui:new(o)
o = o or {}
setmetatable(o, self)
self.__index = self
return o
end
function Gui:init_gui(event)
local player=game.players[event.player_index]
if not (player and player.valid) then return end
self.state = {
player_index = event.player_index,
player = player,
-- Cached frames/flows
main_frame = nil,
dtl_frame = nil,
...