Page 1 of 1

events raised in on_init get skipped by mods already present on the save

Posted: Tue May 27, 2025 11:06 am
by Quezler
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"

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)
"mod B"

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)
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:

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)

Re: events raised in on_init get skipped by mods already present on the save

Posted: Tue May 27, 2025 11:12 am
by boskid
on_load:
Note that no other events will be raised for a mod until it has finished this step.

Re: events raised in on_init get skipped by mods already present on the save

Posted: Tue May 27, 2025 11:17 am
by Quezler
Wow that's quite out of sight near the bottom and in a normal font, not used to read the on_load documentation since i know what the event is for, it might be worth moving it to a more visible spot, like right below the lifecycle diagram in a noticable color 👀

Re: events raised in on_init get skipped by mods already present on the save

Posted: Tue May 27, 2025 12:41 pm
by curiosity
Same for on_init, but you don't seem to have a problem with that.

Re: events raised in on_init get skipped by mods already present on the save

Posted: Tue May 27, 2025 1:53 pm
by robot256
This is an interesting topic. Reading the diagram closely I can see how this occurs. It does feel unintuitive that "new" mods are potentially initialized before "old" mods are allowed to load. It's possible that this or similar behavior could explain some of the weirder bug reports I've received. And it will require more spam in in_configuration_changed to prevent.