How to persist classes

Place to get help with not working mods / modding interface.
Post Reply
User avatar
Therenas
Factorio Staff
Factorio Staff
Posts: 232
Joined: Tue Dec 11, 2018 2:10 pm
Contact:

How to persist classes

Post by Therenas »

I've been trying out classes in lua, including inheritance. My code looks like this:

Code: Select all

BaseClass = {}
BaseClass.__index = BaseClass

setmetatable(BaseClass, {
    __call = function (cls, ...)
        local self = setmetatable({}, cls)
        self:_init(...)
        return self
    end,
})

function BaseClass:_init(name)
    self.name = name
end


function BaseClass:set_name(name)
    self.name = name
end

function BaseClass:get_name()
    return self.name
end

Code: Select all

Product = {}
Product.__index = Product


setmetatable(Product, {
    __index = BaseClass,
    __call = function (cls, ...)
        local self = setmetatable({}, cls)
        self:_init(...)
        return self
    end,
})

function Product:_init(name, amount)
    BaseClass._init(self, name)
    self.amount = amount_produced
end

Now this works great when you start your game, as I require those classes in my control.lua file. When saving and loading though, they are not there anymore. Now this makes sense, as global lua variables are not persisted when saving, only values in the factorio global table are. But I have trouble getting them to 'reload' when you load the save. Now I know that they should probably be reloaded in the on_load() listener, but, after some research and testing, I haven't gotten it to work. I'm sure it is not hard, but I can't work it out. Anyone got a solution?

eduran
Filter Inserter
Filter Inserter
Posts: 344
Joined: Fri May 09, 2014 2:52 pm
Contact:

Re: How to persist classes

Post by eduran »

Therenas wrote:
Thu Feb 07, 2019 12:02 pm
Now this works great when you start your game, as I require those classes in my control.lua file.
Code in control.lua (and by extension any code required in control.lua) runs on every load. So your class definitions always exist.
If you want to persist objects, you can store them in global. Metatables will not survive a save/load cycle, those have to be restored in on_load.

My approach is to only store dynamic properties of those objects, instead of the entire object, in global. That means my objects are recreated on every load, and any non-static properties are restored from the global table in on_load.

Code: Select all

-- control.lua
myObj = require('MyClass')(some, input, arguments) -- runs on every load and re-creates myObj
script.on_load(function() myObj:on_load(global.storage_for_myObj end)  -- hands a table with properties to myObj, restoring its former state

User avatar
Therenas
Factorio Staff
Factorio Staff
Posts: 232
Joined: Tue Dec 11, 2018 2:10 pm
Contact:

Re: How to persist classes

Post by Therenas »

Thanks for the reply. I already store my objects in a global table. The issue I have, it seems, is with restoring the metatables correctly. How can I achieve that?

Edit: The error I get, only when it is a loaded save, is that the functions of the class can not be found(= is nil). So I assume the metatables are not set correctly, and it can't find the parent class. I can't work out how to correct that, though.
Last edited by Therenas on Thu Feb 07, 2019 8:27 pm, edited 1 time in total.

quyxkh
Smart Inserter
Smart Inserter
Posts: 1028
Joined: Sun May 08, 2016 9:01 am
Contact:

Re: How to persist classes

Post by quyxkh »

The easiest thing to do might be to have a persisted `metatables` table, with every global table that has a metatable listed.

Code: Select all

    script.on_init( function()
        global.metatables={}
        function global.set_persistent_metatable(table,meta) global.metatables[table] = meta; setmetatable(table,meta)
        -- set all persisted metatables here with that global.set_persistent_metatable function
        end)
    script.on_load( function()
        for k,v in next,global do _G[k]=v end
        for k,v in next,global.metatables do setmetatable(k,v) end
        end)
I haven't tested this, but I believe it'll work.

edit: fixed the global function name to persist a metatable, duh, on_load would stomp on a lua function with the old name.
Last edited by quyxkh on Thu Feb 07, 2019 10:55 pm, edited 1 time in total.

User avatar
Therenas
Factorio Staff
Factorio Staff
Posts: 232
Joined: Tue Dec 11, 2018 2:10 pm
Contact:

Re: How to persist classes

Post by Therenas »

quyxkh wrote:
Thu Feb 07, 2019 8:25 pm
I haven't tested this, but I believe it'll work.
Alright so I integrated this, and on world generation, it works, but when loading a save, the two lines in on_load() lead to a stack overflow for some reason. In the error report it says the overflow happens on the setmetatable(table,meta) call, inside the global.setmetatable. Any idea what the reason could be? It doesn't happen if only one of them runs, but then the intended effect is ruined of course.

quyxkh
Smart Inserter
Smart Inserter
Posts: 1028
Joined: Sun May 08, 2016 9:01 am
Contact:

Re: How to persist classes

Post by quyxkh »

Therenas wrote:
Thu Feb 07, 2019 10:37 pm
quyxkh wrote:
Thu Feb 07, 2019 8:25 pm
I haven't tested this, but I believe it'll work.
Alright so I integrated this, and on world generation, it works, but when loading a save, the two lines in on_load() lead to a stack overflow for some reason. In the error report it says the overflow happens on the setmetatable(table,meta) call, inside the global.setmetatable. Any idea what the reason could be? It doesn't happen if only one of them runs, but then the intended effect is ruined of course.
I don't see how the on_load code winds up invoking global.setmetatable, it's calling the native lua setmetatable not the persisted frontender. Perhaps a transcription error?

edit: ... hmmm, someone once suggested I try `_G=global` to get all my mod globals automatically persisted, if you do that you'll stomp on the lua setmetatable. Try renaming the frontender function "set_persisted_metatable" or something.

edit 2: see my edit of the original code, fixed my bad, sorry.

Rseding91
Factorio Staff
Factorio Staff
Posts: 13209
Joined: Wed Jun 11, 2014 5:23 am
Contact:

Re: How to persist classes

Post by Rseding91 »

There's always the option of: don't use classes or meta tables. Lua has no such thing as a "class" anyway. They literally aren't needed in any part of Factorio modding and serve no purpose except to make your mod harder for others to understand/debug.

You've spent all of this time trying to persist them instead of just using basic functions and passing the values around as parameters directly.

Additionally it will make every bit of code that uses them slower to execute.
If you want to get ahold of me I'm almost always on Discord.

User avatar
Therenas
Factorio Staff
Factorio Staff
Posts: 232
Joined: Tue Dec 11, 2018 2:10 pm
Contact:

Re: How to persist classes

Post by Therenas »

quyxkh wrote:
Thu Feb 07, 2019 10:51 pm
edit 2: see my edit of the original code, fixed
Yeah I thought of namespace conflicts as well, should have tested that I guess. Fixing it prevents the crash, but sadly still doesn't have the intended effect. Thanks for your suggestions though, they made me understand the problem at hand much better! I will try to come up with a solution, if I do, I will of course post it here.


Rseding91 wrote:
Fri Feb 08, 2019 12:32 am
There's always the option of: don't use classes or meta tables. Lua has no such thing as a "class" anyway. They literally aren't needed in any part of Factorio modding and serve no purpose except to make your mod harder for others to understand/debug.

You've spent all of this time trying to persist them instead of just using basic functions and passing the values around as parameters directly.

Additionally it will make every bit of code that uses them slower to execute.
That is a valid point, but I think in my case, there are enough advantages to offset the disadvantages. I only use it for my data, not any UI functions. It helps me in various ways; Inheritance spares me of a lot of code duplication, plus the data structure is quite deep, with custom objects as attributes for other objects and so on, so making everything into a function makes me not have to pass along 2 or more id's with every call. Having these as objects in variables makes the actual code more readable actually. Also, a debugger for Factorio would really help me out here :P

But I have a question for you, if you don't mind: When loading a save, am I correct in assuming that all control-stage files are loaded (and executed) before on_load is called?

quyxkh
Smart Inserter
Smart Inserter
Posts: 1028
Joined: Sun May 08, 2016 9:01 am
Contact:

Re: How to persist classes

Post by quyxkh »

Yes, see doc-html/Data-Lifecycle.html in your install or at lua-api.factorio.com/latest.

User avatar
Therenas
Factorio Staff
Factorio Staff
Posts: 232
Joined: Tue Dec 11, 2018 2:10 pm
Contact:

Re: How to persist classes

Post by Therenas »

quyxkh wrote:
Fri Feb 08, 2019 4:17 pm
Yes, see doc-html/Data-Lifecycle.html in your install or at lua-api.factorio.com/latest.
I read that, just wasn't 100% sure if I understood it correctly, thanks for confirming though.

Another thing I just noticed in my testing, is that when I save the class (BaseClass for example) into the factorio global table, it is a of the type 'table', which makes sense. However, when loading the exact same variable in on_load(), it's type is 'function'. Not sure what to make of that.

Edit: err or not? type() tells me it's a table both times, log() errors tell me it's a function in on_load(). Guess I should trust the type() function.

eduran
Filter Inserter
Filter Inserter
Posts: 344
Joined: Fri May 09, 2014 2:52 pm
Contact:

Re: How to persist classes

Post by eduran »

Do you even need to persist your metatables? Are they changing at runtime?

User avatar
Therenas
Factorio Staff
Factorio Staff
Posts: 232
Joined: Tue Dec 11, 2018 2:10 pm
Contact:

Re: How to persist classes

Post by Therenas »

No, they are not. I just need them to get reapplied on load to my classes. Just loading the file definition normally doesn't work when loading a save, I assume because then you recreate the classes, and the data you already saved has technically been saved with a different 'class' (different unique id or something), so they are not compatible. Could be totally wrong though, this is all not very intuitive to me. I have been experimenting a bit more, and it seems that the setmetatables() doesn't do anything in on_load(). Currently trying to find out why.

User avatar
Therenas
Factorio Staff
Factorio Staff
Posts: 232
Joined: Tue Dec 11, 2018 2:10 pm
Contact:

Re: How to persist classes

Post by Therenas »

Alright, after several hours of trying to get this to work, I'm ready to move on. It has to be possible somehow, but it seems I'm not going to find out how for the moment. Thanks everyone for their help, though! Now, onwards to trying closure-based objects.

Edit: Well, seems like that approach has kind of the same issue. Looks like Rseding91 was (mostly) right, as he almost always is. Going back to a non-OOP approach for now, even though that makes me sad.

Post Reply

Return to “Modding help”