events raised in on_init get skipped by mods already present on the save
Posted: Tue May 27, 2025 11:06 am
I'll post it in documentation improvement requests since it seems just ever so slightly more suitable than calling it a bug due to the lifecycle diagram.
So lets start with probably the most common usecase, tracking data for any/all surfaces within your own mod, this is typically done by going over all surfaces (and possibly iterating entities on them), and then listening to the on_surface_created in order to detect new surfaces, but surfaces can slip through these cracks.
(note that it doesn't seem unique for just the surface creation events, raising build events from on_init also get missed by mod A)
"mod A"
"mod B"
When you add these both to a world at the same time you'll see this in the log:
- Script @__mod-a__/control.lua:10: surface 'on_init' created.
- Script @__mod-a__/control.lua:15: {x = 0.5, y = 0.5}
- Script @__mod-a__/control.lua:10: surface 'on_tick' created.
- Script @__mod-a__/control.lua:15: {x = 2.5, y = 0.5}
But when you create the world with A installed, save, enable B, load you'll see this:
- Script @__mod-a__/control.lua:10: surface 'on_configuration_changed' created.
- Script @__mod-a__/control.lua:15: {x = 1.5, y = 0.5}
- Script @__mod-a__/control.lua:10: surface 'on_tick' created.
- Script @__mod-a__/control.lua:15: {x = 2.5, y = 0.5}
Or in other words, even though A is already initialized (and thus "ready" to receive events) the surface creation event triggered by the on_init of mod B doesn't get picked up by mod A at all. (surface "on_init" did get created, and surfaces created in on_configuration_changed do seem to get picked up)
So yea is this a bug, or something worth clearly documenting that events fired from on_init do not trigger in existing mods, so everyone in their "A" should recheck for new entities just like they would in their on_init?
I often get around it by writing code like this, the first time i ran into this issue long ago it felt really weird, it still does, but at least this does work:
So lets start with probably the most common usecase, tracking data for any/all surfaces within your own mod, this is typically done by going over all surfaces (and possibly iterating entities on them), and then listening to the on_surface_created in order to detect new surfaces, but surfaces can slip through these cracks.
(note that it doesn't seem unique for just the surface creation events, raising build events from on_init also get missed by mod A)
"mod A"
Code: Select all
script.on_init(function()
storage.surfacedata = {}
for _, surface in pairs(game.surfaces) do
storage.surfacedata[surface.index] = {some = "thing"}
end
end)
script.on_event(defines.events.on_surface_created, function(event)
log(string.format("surface '%s' created.", game.get_surface(event.surface_index).name))
storage.surfacedata[event.surface_index] = {foo = "bar"}
end)
script.on_event(defines.events.script_raised_built, function(event)
log(serpent.line(event.entity.position))
end)
commands.add_command("mod-a", nil, function(command)
log(serpent.block(storage.surfacedata))
end)
Code: Select all
script.on_init(function()
game.create_surface("on_init")
game.surfaces["nauvis"].create_entity{
name = "wooden-chest",
force = "neutral",
position = {0, 0},
raise_built = true,
}
end)
script.on_configuration_changed(function()
game.create_surface("on_configuration_changed")
game.surfaces["nauvis"].create_entity{
name = "wooden-chest",
force = "neutral",
position = {1, 0},
raise_built = true,
}
end)
-- non save-load stable example
script.on_event(defines.events.on_tick, function(event)
game.create_surface("on_tick")
game.surfaces["nauvis"].create_entity{
name = "wooden-chest",
force = "neutral",
position = {2, 0},
raise_built = true,
}
script.on_event(defines.events.on_tick, nil)
end)
- Script @__mod-a__/control.lua:10: surface 'on_init' created.
- Script @__mod-a__/control.lua:15: {x = 0.5, y = 0.5}
- Script @__mod-a__/control.lua:10: surface 'on_tick' created.
- Script @__mod-a__/control.lua:15: {x = 2.5, y = 0.5}
But when you create the world with A installed, save, enable B, load you'll see this:
- Script @__mod-a__/control.lua:10: surface 'on_configuration_changed' created.
- Script @__mod-a__/control.lua:15: {x = 1.5, y = 0.5}
- Script @__mod-a__/control.lua:10: surface 'on_tick' created.
- Script @__mod-a__/control.lua:15: {x = 2.5, y = 0.5}
Or in other words, even though A is already initialized (and thus "ready" to receive events) the surface creation event triggered by the on_init of mod B doesn't get picked up by mod A at all. (surface "on_init" did get created, and surfaces created in on_configuration_changed do seem to get picked up)
So yea is this a bug, or something worth clearly documenting that events fired from on_init do not trigger in existing mods, so everyone in their "A" should recheck for new entities just like they would in their on_init?
I often get around it by writing code like this, the first time i ran into this issue long ago it felt really weird, it still does, but at least this does work:
Code: Select all
local function refresh_surfacedata()
-- deleted old
for surface_index, surfacedata in pairs(storage.surfacedata) do
if surfacedata.surface.valid == false then
storage.surfacedata[surface_index] = nil
end
end
-- created new
for _, surface in pairs(game.surfaces) do
storage.surfacedata[surface.index] = storage.surfacedata[surface.index] or {
surface = surface,
}
end
end
script.on_init(function()
storage.surfacedata = {}
refresh_surfacedata()
end)
script.on_configuration_changed(function()
refresh_surfacedata()
end)
script.on_event(defines.events.on_surface_created, refresh_surfacedata)
script.on_event(defines.events.on_surface_deleted, refresh_surfacedata)