Reverse Factory machine will recycle (uncraft) nearly any item placed inside. Supports the recycling of most, if not all, modded items. Fully featured integration with Bobs Mods, Industrial Revolution, and Fantario (independently, not simultaneously)
Mods introducing new content into the game.
Furnaces, assembling machines, production chains.
I noticed that item pushing does not work after enabling the mod and placing a Reverse Factory.
The bug is quite easy to understand, addSurface()
is never called for surfaces that already exist when the mod is enabled. Instead of trying to fix that, I simply removed all references to global.surfaces
and changed checkinvs()
to iterate over game.surfaces
instead of global.surfaces
. This at least fixes the problem for me.
If you want, I can create a pull request for the mod, but I didn't find the source repository.
Many thanks for the explanation of the bug, I don't think I ever would have been able to track that one down, as (obviously) I would never be adding the mod to an existing save. I was able to fix it by making sure that the surface is added to the global list when a recycler is placed down. That should also help catch any remaining issues with auto-ingredient push not working, hopefully.
I wasn't entirely certain if iterating over game.surfaces would be safe, since the checkinvs code is particularly.. volatile. And yeah, I don't really use github, sorry about that.
Cool, thanks for fixing it! I tested it and it seemt to work now. :)
While going through the implementation yesterday, I noticed that you're running some code every tick in a handler for the on_tick
event, which then checks if the game is on a tick that matches the inverval given by the rf-delay
setting. I think you could achieve the same with script.on_nth_tick()
, but without running Lua code on every tick.
I think you could achieve this by replacing your current handler for script.on_nth_tick()
with this code:
local function setup_checkinvs()
-- Remove previously set up on_nth_tick() handler, if any.
script.on_nth_tick(nil)
script.on_nth_tick(settings.global["rf-delay"], checkinvs)
end
script.on_event(defines.events.on_runtime_mod_setting_changed, setup_checkinvs)
setup_checkinvs()
I think this might also fix a bug, which I have not confirmed, but I suspect exists, which is that if the rf-delay
setting is changed while the game is running, the change will only be applied the next time the map is loaded (i.e. the old inverval will continue to be used until the map is reloaded). I think the same applies to rf-timer
, but my change doesn't change how that setting is handled.
I think this might lead to multiplayer desync, if the setting is changed and than another player joins. The engine on the new player's computer will use the new setting while the server will use the old one. But I have 0 experience with multiplayer modding, so I could definitely be wrong. :)
Hm.. I'm afraid there's not information for me to be able to change anything with the partial code snippets provided. The on_nth_tick stuff sounds like it would be a much better implementation of the way I have things set up, but I'm not entirely sure how to use it. The code above doesn't look.. valid?
Or I might just not be informed enough to know how to use it, since basically everything about it is different to what I already have written in control.lua
But I do understand the issue. I honestly thought it was just a Factorio thing, since I've never been able to change Map settings during an existing save, without there being issues and complications, having to load, edit, save, reload, and hope the changes stuck. It's been the same way with every mod I've used so far
First, I uploaded your mod's source into a repo on GitHub: https://github.com/Feuermurmel/reverse-factory
Please tell me, if you'd rather not have me do that, and I'll delete it. :) I can also transfer the repo to your account, if you want.
I did that because I think it makes it easier to show you the code I am testing.
script.on_nth_tick():
This is the change I did to use script.on_nth_tick()
instead of the on_tick
event: https://github.com/Feuermurmel/reverse-factory/pull/1/files
I works without issues for me. You can download it as a ZIP archive. You need to rename it to reverse-factory_8.2.12.zip
. Branches on GitHub can be downloaded as ZIP archives by going to the page of the branch (e.g. on-nth-tick
) and clicking on the green "Code" button and "Download ZIP".
What makes you think that it shouldn't work? I'll try to explain! :)
Changing mod settings:
I think the problem is that you're copying the value of the sttings from settings.global
to your mod's global
here. If the user updates the settings, only the values in settings.global
change, but not in global
. The easiest fix ist to remove those values from global
and instead use settings.gloabl
in each case. Then, when the user updates the settings, the new values will be used immediately without reload. Does that make sense?
If you need to react to changes to the mod settings in some way, e.g. by updating other values in global
based on it, or, as I did, re-register the handler for the on_nth_tick
event, you can do that using the on_runtime_mod_setting_changed
event.
Thank you for the quick rundown on github, I am really surprised that it just.. works. I tested it, and it did seem to do exactly what it was supposed to do, and reflect the changes made in the Settings tab in real time. Of course, that being said, I don't understand at all how it works, and I'm hesitant to include a change that I don't entirely understand, since that'll make any later debugging more difficult.
Thank you for the quick rundown on github, I am really surprised that it just.. works. I tested it, and it did seem to do exactly what it was supposed to do, and reflect the changes made in the Settings tab in real time. Of course, that being said, I don't understand at all how it works, and I'm hesitant to include a change that I don't entirely understand, since that'll make any later debugging more difficult.
I guess from the start, I remember trying to make everything local, but it either caused errors, or just changed nothing, so I ended up not making any of the functions local.
I don't think it makes a difference here. I use local function
over function
without thinking about it much, because it has some benefites in some cases (you can have functions with the same name in multiple Lua files without them overwriting each other). But it prevents you from accessing the function if it isn't defined in the same file you are accessing it from. Maybe that's the problem you ran into.
It's like a "best practice" I lay upon myself. It's not important in this case and you can remove the local
. :)
I'm not sure how script.on_nth_tick even works, the runtime docs didn't provide any examples, just vaguely stating that the second argument needed to be a "handler", described as "function(NthTickEventData)", which didn't make much sense to me. Looking at your example, I can guess how it works now though, handler seems to be another term for function, and the "NthTickEventData" part isn't important
Yeah, the documentation is quite concise. Yes, a "handler" is just a function that get's called when the event happens. Most of the time a single argument will be passed the function, which represets the event, i.e. contains some information about it. In this case the information doesn't seem too useful to me, especially for the case you'd use it. In Lua, if a function get's more arguments that the function has parameters, the arguments simply get ignored, that's why I was able to pass checkinv()
as a handler directly, even though it doesn't use the NthTickEventData
argument.
To make this "ignoring" more clear, I could have written this instead:
script.on_nth_tick(settings.global["rf-delay"], function (data)
checkinvs()
end)
Now, data
gets passed into the anonymous function, which then calls checkinv()
, but doesn't do anything with data
.
I don't see how the lines ending with "checkinvs" work; like, without the parentheses after it, shouldn't that be treated as a variable, not a function?
I've never seen a script.on_event(...) be a single line before. All of mine have the first part (defines.events...), but the second part, I thought that needed to be its own thing, with the whole "function(event) ... end)" sort of thing
Okay, to understand all of this, there is a single thing you have to know: Every function is a values and can be stored in variables like any other value. These "variables" can be local or global variables, or parameters to other functions.
There's no distinction between variables that contain a function or something else.
When you write something("hello")
, Lua always looks for the variable something
, takes it's value, expecting the variable to contain a function (and giving an error otherwise), and then calls the function. When you omit the parenthesis, Lua does as always, it takes the value of the variable and expectes you to do somehting with it (e.g. pass it to a function or store it in another variable).
Writing something
without the parenthesis allows you to access that variable without calling the function in it. This is what happens when I write script.on_event(..., setup_checkinvs)
. The function setup_checkinvs
is read from it's variable, but not called, and instead passed to on_event()
.
When you write function something(arg) ... end
, Lua creates a function and stores it in the variable something
. When you instead write function(arg) ... end
, Lua creates a function all the same, but doesn't store it in a variable. These two snippets of code are equivalent:
function something(arg)
...
end
something = function(arg)
...
end
In the secont snippet, we simply split the two parts (creating the function and storing it in a variable) into separate parts.
When you write script.on_init(function () ... end)
, you create a function and pass it as an argument to on_init()
. But you could just as well write this:
function initworld()
...
end
script.on_init(initworld)
Here, we split the creating the function (and storing it in initworld
), and passing it to another function into separate parts. on_init()
doesn't care where it's argument comes from, whether from some variable you created earlier or from a function you created on the same line.
So maybe you'd write it the code I showed you like this?
local function setup_checkinvs()
-- Remove previously set up on_nth_tick() handler, if any.
script.on_nth_tick(nil)
script.on_nth_tick(settings.global["rf-delay"], function()
checkinvs()
end)
end
script.on_event(defines.events.on_runtime_mod_setting_changed, function()
setup_checkinvs()
end)
setup_checkinvs()
This is just as valid and works the same.
setup_checkinvs () is just.. sitting there? In the middle of everything? How does that work? Is it always running? Everything else in the control.lua made sense before; it was either the definition of a function to be used (thus starting with "function..."), or the execution of a timed script event (like "on_init", "on_tick", "on_built_entity", etc) that only runs when something specifically happens. But when does "setup_checkinvs()" run? And this time, it has the parentheses after it?
What I needed to do here is to register checkinvs()
with script.on_nth_tick()
, so it get's called by the engine while the game is running. If I only needed to do this once while the game loads, I could have just left it at the top leve, like all the calls to on_init()
, on_event()
, etc. you mentioned:
local function setup_checkinvs()
script.on_nth_tick(settings.global["rf-delay"], checkinvs)
end
setup_checkinvs()
script.on_nth_tick(settings.global["rf-delay"], checkinvs)
These two snippets are equivalent. In both cases on_nth_tick()
get's called once when control.lua
is loaded/executed. What's happening in the first snippet is that instead of there being a call directly to on_nth_tick()
(which just get's executed like normal code when the mod loads), there's a call to setup_checkinvs()
instead (which get's executed), which then calls on_nth_tick()
. In both snippets, on_nth_tick()
gets called just the same, at the same time.
The reason I did this is so I can call setup_checkinvs()
from the handler of event on_runtime_mod_setting_changed
, so I didn't have to duplicate code. You could write this instead:
script.on_event(defines.events.on_runtime_mod_setting_changed, function()
-- Remove previously set up on_nth_tick() handler, if any.
script.on_nth_tick(nil)
script.on_nth_tick(settings.global["rf-delay"], checkinvs)
end)
script.on_nth_tick(settings.global["rf-delay"], checkinvs)
For changing mod settings, I realize the issue now; I thought setting global.delay to the value in settings, would link the two, so that when one is changed, so too is the other. It works this way during the initial loading of the game (where changing one reference to an object changes all the other references), but I guess not during runtime. I thought it would just be better shorthand to define it in global, instead of having to read from settings every time, but I suppose I'll have to do that. Didn't realize the fix would be so simple.
I think you understand what's happening. :)
Sorry, if parts of what I wrote are dense. Wrote this in a bit of a hurry. :)
No worries, more words is more helpful; I understood it! Though you did somehow end up double-posting, which is funny, because I think you did that last time lol.
It's like a "best practice" I lay upon myself. It's not important in this case and you can remove the local. :)
Yeah, I forget exactly what was going wrong when I tried using local in control.lua before, maybe it was something entirely unrelated. I do use it a lot during the data steps though!
There's no distinction between variables that contain a function or something else.
I see.. that makes a lot more sense now. That definitely seems really.. weird. But I suppose that might just be a lua thing lol.
script.on_event(defines.events.on_runtime_mod_setting_changed, function()
setup_checkinvs()
end)
This looks a lot like something I would write, just like all the other script.on_event blocks I have. Being able to shorten it to just one line does look a lot cleaner though.
For the rest of stuff about setup_checkinvs() by itself, that completely changes my perspective on the control.lua; I always thought it was something that ran continuously, always reading through the script.on_event stuff on a loop. But it seems to be more like, assigning functions to these events that are being called in game; so the file itself only runs once, and the stuff in the script.on_event is what gets run on loop. Similar, but very different
So anything that is sitting out by itself, only runs once, which makes sense. But it still feels weird to me, because this isn't happening at any particular "step", like.. if it needs to run once before the game is running, then wouldn't it make more sense to put it under script.on_init, or script.on_configuration_changed, since those happening at the beginning of map loading?
For the rest of stuff about setup_checkinvs() by itself, that completely changes my perspective on the control.lua; I always thought it was something that ran continuously, always reading through the script.on_event stuff on a loop. But it seems to be more like, assigning functions to these events that are being called in game; so the file itself only runs once, and the stuff in the script.on_event is what gets run on loop.
That's exactly on point. control.lua
wires everything up at load time and from then on, code only runs when called by the Engine via event handles.
This is similar to how JavaScript in a web browser works btw, if you're familiar with that. You can't have an infinite loop there, because the web page/tab/window would freeze. Instead, after the initial load, all JavaScript code is run in timers or event handlers called by the engine.
So anything that is sitting out by itself, only runs once, which makes sense. But it still feels weird to me, because this isn't happening at any particular "step", like.. if it needs to run once before the game is running, then wouldn't it make more sense to put it under script.on_init, or script.on_configuration_changed, since those happening at the beginning of map loading?
The handler of on_init()
only gets called when the mod is first enabled, not on later loads of a save. It's mainly there, so you can change game state (e.g. change stuff in the game world) or put initial data into global
. It's not there to register event handlers because those are only kept until the game is closed.
The handler of on_configuration_changed()
gets only called when the configuration actually changes, so you still need a way to register the handlers before that.
What would work, is to register handlers in on_load()
, which gets run each time a save is loaded, or a new game is started (the documentation of on_load()
even mentions registering event handlers as a use case).
There are two reasons why I try to put everything that registers event handlers into the top-level of control.lua
:
control.lua
is like the body of a function, executing from top to bottom.on_init()
gets called before on_load()
(I don't know if that's true), but I register the event handler to on_init()
from within on_load()
, my handler to on_init()
would never get called, because that event has already passed.I'm looking at the top-level code of control.lua
like the glue that connects the engine, and my mod's code. While the game is running, the engine has references to my handlers and calls them when needed. But before that, something needs to make those connections, and that's the procedural code at the top-level of control.lua
.
Have you tried putting a few print()
calls in the top level and in some of the functions? Like this:
print("control.lua")
local function setup_checkinvs()
print("setup_checkinvs()")
-- Remove previously set up on_nth_tick() handler, if any.
script.on_nth_tick(nil)
script.on_nth_tick(settings.global["rf-delay"], checkinvs)
end
script.on_event(defines.events.on_runtime_mod_setting_changed, function()
print("on_runtime_mod_setting_changed handler")
setup_checkinvs()
end)
This helped me a lot to understand when exactly what code is run, and in what order.
Huh, I'm not sure if I remembered incorrectly or just never knew about when on_configuration_changed() and on_init() ran. I guess on_load() must have been the event I was thinking about (and that might explain a lot of the troubles I'd been having, with certain crashes involving multiple surfaces). But hm, I suppose that makes sense.
I ended up using the new code, just rearranged a little bit, and with some additional comments to remember how they work, but functionally remained identical. And I fixed up all the attempts at putting settings data into global values, and read them directly instead. Released as v8.2.13
I've honestly tried to avoid messing with control.lua too much, though I really wanted to make this auto ingredient push thing work, because it seemed easily doable at the time. I have a lot of print functions, but not any that are literally just "print(...)", I thought they had to be printed to a player, otherwise it wouldn't show up (or only showed up in the log, which is annoying to search through). And of course, players don't exist during certain events (like on_init(), I think?) so I couldn't even rely on it too much.
I have a lot of print functions, but not any that are literally just "print(...)", I thought they had to be printed to a player, otherwise it wouldn't show up (or only showed up in the log, which is annoying to search through). And of course, players don't exist during certain events (like on_init(), I think?) so I couldn't even rely on it too much.
Yeah, printing somewhere visible in the game's UI doesn't work well for debugging, in my experience. You can't access game
during initialization and, as you said, without access to a player or surface, you can't print anything.
I chose to debug solely with print()
or log()
. I usually run Factorio from a shell prompt. There I can see all output of the game and my mods immediately as it's written. Another way would be to run tail -F factorio-current.log
. It depends a bit on your OS what'll work best. If you're on windows, running Factorio from cmd.exe
or running tail
from WSL should also work.
An thanks for releasing the fix! :)
Hmm, interesting, I'll try that out sometime, if I ever have to work with control.lua again (which I would still really like to avoid lol)
Many more thanks to you for not only providing the code snippet, but also for teaching me about how it works, and especially how Factorio and control.lua interact during runtime :D