Make current lifecycle stage accessible

Place to ask discuss and request the modding support of Factorio. Don't request mods here.
User avatar
Cooldude2606
Fast Inserter
Fast Inserter
Posts: 103
Joined: Sat Sep 16, 2017 9:04 pm
Contact:

Make current lifecycle stage accessible

Post by Cooldude2606 »

Custom libraries can contain methods that only work during certain stages of the data lifecycle. Most of these will raise some kind of error such as modifying global during on_load. However, there are edge cases when a method is only safe to use during the control stage, and if used afterwards would result in desyncs without obvious error messages. This is particularly common if the library acts as a singleton that has data registered to it and mutation of this data during runtime would lead to desyncs.

A solution to this would be to allow these custom library methods to assert that the current lifecycle stage is not runtime. A single method could be used to cover this edge case, but a more general solution would be more appropriate which allows the pattern "assert(lifecycle_stage == defines.lifecycle_stage.control)" which can also be used for the settings, data, and runtime stages. This would either require a new global or an extension of an existing one, possibly "package.lifecycle_stage".
--- Developer for Explosive Gaming and Clusterio. Please contact me via our Discord. ---
Pi-C
Smart Inserter
Smart Inserter
Posts: 1726
Joined: Sun Oct 14, 2018 8:13 am
Contact:

Re: Make current lifecycle stage accessible

Post by Pi-C »

Cooldude2606 wrote: Sun Sep 29, 2024 3:51 pm A solution to this would be to allow these custom library methods to assert that the current lifecycle stage is not runtime. A single method could be used to cover this edge case, but a more general solution would be more appropriate which allows the pattern "assert(lifecycle_stage == defines.lifecycle_stage.control)" which can also be used for the settings, data, and runtime stages. This would either require a new global or an extension of an existing one, possibly "package.lifecycle_stage".
Not sure I understand the problem correctly, but I think we already can do this:
  • Global table 'data' exists only during data stage: "assert(data and true or false)"
  • Global table 'script' exists only after the data stage has finished, as soon as control.lua is loaded: "assert(script and not game and true or false)"
  • Global table 'game' exists only during migrations and after script.on_load has finished: "assert(game and true or false)"
I'm not arguing against your suggestion, though: having something like 'defines.lifecycle_stage' is more obvious and easier to read. :-)
A good mod deserves a good changelog. Here's a tutorial (WIP) about Factorio's way too strict changelog syntax!
curiosity
Filter Inserter
Filter Inserter
Posts: 470
Joined: Wed Sep 11, 2019 4:13 pm
Contact:

Re: Make current lifecycle stage accessible

Post by curiosity »

Pi-C wrote: Sun Sep 29, 2024 5:47 pm
  • Global table 'script' exists only after the data stage has finished, as soon as control.lua is loaded: "assert(script and not game and true or false)"
  • Global table 'game' exists only during migrations and after script.on_load has finished: "assert(game and true or false)"
Some mod could make those in data stage.
Pi-C
Smart Inserter
Smart Inserter
Posts: 1726
Joined: Sun Oct 14, 2018 8:13 am
Contact:

Re: Make current lifecycle stage accessible

Post by Pi-C »

curiosity wrote: Sun Sep 29, 2024 6:09 pm
Pi-C wrote: Sun Sep 29, 2024 5:47 pm
  • Global table 'script' exists only after the data stage has finished, as soon as control.lua is loaded: "assert(script and not game and true or false)"
  • Global table 'game' exists only during migrations and after script.on_load has finished: "assert(game and true or false)"
Some mod could make those in data stage.
Of course, you're correct: In data stage, global vars really are globally global, not just mod-global as in the control stage …
A good mod deserves a good changelog. Here's a tutorial (WIP) about Factorio's way too strict changelog syntax!
User avatar
Cooldude2606
Fast Inserter
Fast Inserter
Posts: 103
Joined: Sat Sep 16, 2017 9:04 pm
Contact:

Re: Make current lifecycle stage accessible

Post by Cooldude2606 »

Pi-C wrote: Sun Sep 29, 2024 5:47 pm Not sure I understand the problem correctly, but I think we already can do this:
  • Global table 'data' exists only during data stage: "assert(data and true or false)"
  • Global table 'script' exists only after the data stage has finished, as soon as control.lua is loaded: "assert(script and not game and true or false)"
  • Global table 'game' exists only during migrations and after script.on_load has finished: "assert(game and true or false)"
I think you have a good understanding of the problem from the suggestions you provided. But there are still gaps which we can not determine: control vs on_load (both have script but no game) and runtime vs on_init (both have script and game). The first one is where I have my problem, I want methods to be useable during control only and not during on_load.
--- Developer for Explosive Gaming and Clusterio. Please contact me via our Discord. ---
robot256
Filter Inserter
Filter Inserter
Posts: 894
Joined: Sun Mar 17, 2019 1:52 am
Contact:

Re: Make current lifecycle stage accessible

Post by robot256 »

What methods do you have that you need to call in both on_load and on_init that would change their behavior based on what game stage they detect? Why do they need to be in the same method? Generally on_load should contain the bare minimum of code required to restore the Lua instance to the pre-save state.

I get the impulse to combine things, but it seems like questionable coding practice to me. I've never had trouble splitting the behavior into separate "on_load safe" and "on_load unsafe" methods, and then calling only the appropriate ones in the respective event handlers.
User avatar
Cooldude2606
Fast Inserter
Fast Inserter
Posts: 103
Joined: Sat Sep 16, 2017 9:04 pm
Contact:

Re: Make current lifecycle stage accessible

Post by Cooldude2606 »

robot256 wrote: Tue Oct 01, 2024 3:19 pm What methods do you have that you need to call in both on_load and on_init that would change their behavior based on what game stage they detect? Why do they need to be in the same method?
You have the wrong idea. My methods are not changing their behaviour, I am wanting to raise an error because they were called outside the stage they are valid to be used in. As you say, it is not hard to split the code into their respective handlers and methods, the issue is I am not the only one using my functions and it is possible for the incorrect function to be used and go unnoticed. Being able to assert the current lifecycle stage would make this error obvious to the other developer.

For example, a general rule is that a function called at runtime should never modify values outside their local scope or are not inside the global table. There are a only a few exceptions to this rule that would not cause a desync. The issue is these function might not cause a desync right away but maybe a few minutes or even hours later. This is much harder to debug and find the root cause of, but say you were able to use "assert(package.lifecycle_stage ~= defines.lifecycle_stage.control)" at the start of the function, then as soon as you attempt to call it at runtime you get a very clear immediate error rather than a desync at some point in the future.
--- Developer for Explosive Gaming and Clusterio. Please contact me via our Discord. ---
robot256
Filter Inserter
Filter Inserter
Posts: 894
Joined: Sun Mar 17, 2019 1:52 am
Contact:

Re: Make current lifecycle stage accessible

Post by robot256 »

Thank you for explaining! That does make a lot of sense for code that is shared/in a library.

If all you want is to cause an error, you could intentionally make a normally benign call that will fail (like local x=game.surfaces[1]) before doing something that could have side effects.
Pi-C
Smart Inserter
Smart Inserter
Posts: 1726
Joined: Sun Oct 14, 2018 8:13 am
Contact:

Re: Make current lifecycle stage accessible

Post by Pi-C »

Cooldude2606 wrote: Mon Sep 30, 2024 9:39 am I think you have a good understanding of the problem from the suggestions you provided. But there are still gaps which we can not determine: control vs on_load (both have script but no game) and runtime vs on_init (both have script and game). The first one is where I have my problem, I want methods to be useable during control only and not during on_load.
Meanwhile, I've found that your suggestion would be very helpful indeed! I'm working on a library mod combining functions that I use in most of my other mods. There are some files that should be read only In the data and in the control stage. Unfortunately, what I posted above was not completely correct:
Pi-C wrote: Sun Sep 29, 2024 5:47 pm
  • Global table 'data' exists only during data stage: "assert(data and true or false)"
This is wrong: 'data' already exists in the settings stage, and if some other mod has run setting.lua before mine, it is even possible that data.raw is not empty. This makes sense as we have to use 'data:extend()' to define our settings -- and they will be copied to data.raw just like the items, recipes etc. we define during the data stage. I've found a safe way to determine whether my lib mod is loaded during the settings stage:

Code: Select all

local is_settings_stage
if data and data.raw then
  is_settings_stage = true

  -- We may be in settings stage even if data.raw is not empty!
  if next(data.raw) then
    local setting_types = {
      ["int-setting"] = true,
      ["bool-setting"] = true,
      ["color-setting"] = true,
      ["string-setting"] = true,
      ["double-setting"] = true,
    }
    for k, v in pairs(data.raw) do
      if not setting_types[k] then
        is_settings_stage = false
        break
      end
    end
  end
end
Obviously, the thing you've suggested is much simpler and more elegant. :-)
A good mod deserves a good changelog. Here's a tutorial (WIP) about Factorio's way too strict changelog syntax!
Post Reply

Return to “Modding interface requests”