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".
Make current lifecycle stage accessible
- Cooldude2606
- Fast Inserter
- Posts: 103
- Joined: Sat Sep 16, 2017 9:04 pm
- Contact:
Re: Make current lifecycle stage accessible
Not sure I understand the problem correctly, but I think we already can do this: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".
- 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)"
A good mod deserves a good changelog. Here's a tutorial (WIP) about Factorio's way too strict changelog syntax!
Re: Make current lifecycle stage accessible
Some mod could make those in data stage.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)"
Re: Make current lifecycle stage accessible
Of course, you're correct: In data stage, global vars really are globally global, not just mod-global as in the control stage …curiosity wrote: ↑Sun Sep 29, 2024 6:09 pmSome mod could make those in data stage.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)"
A good mod deserves a good changelog. Here's a tutorial (WIP) about Factorio's way too strict changelog syntax!
- Cooldude2606
- Fast Inserter
- Posts: 103
- Joined: Sat Sep 16, 2017 9:04 pm
- Contact:
Re: Make current lifecycle stage accessible
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.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)"
Re: Make current lifecycle stage accessible
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.
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.
My mods: Multiple Unit Train Control, Smart Artillery Wagons
Maintainer of Vehicle Wagon 2, Cargo Ships, Honk
Maintainer of Vehicle Wagon 2, Cargo Ships, Honk
- Cooldude2606
- Fast Inserter
- Posts: 103
- Joined: Sat Sep 16, 2017 9:04 pm
- Contact:
Re: Make current lifecycle stage accessible
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.
Re: Make current lifecycle stage accessible
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.
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.
My mods: Multiple Unit Train Control, Smart Artillery Wagons
Maintainer of Vehicle Wagon 2, Cargo Ships, Honk
Maintainer of Vehicle Wagon 2, Cargo Ships, Honk
Re: Make current lifecycle stage accessible
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: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.
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
A good mod deserves a good changelog. Here's a tutorial (WIP) about Factorio's way too strict changelog syntax!