Factorio, Lua and OO

Place to post guides, observations, things related to modding that are not mods themselves.
Post Reply
inferis
Burner Inserter
Burner Inserter
Posts: 13
Joined: Fri Nov 01, 2019 2:02 am
Contact:

Factorio, Lua and OO

Post by inferis »

For a mod I'm working on (mainly a shuttle mod tailored to my own gameplay style), I built a small framework that allows you to build a Lua based class hierarchy, allowing a more object oriented approach in building things. Sue me, I'm old.

I noticed some other mods have something similar, but I haven't found something truly OO and/or flexible enough to fit my needs. Would there be interest to release this as a separate "mod" for other mods to use?

It supports:
  • namespace hierarchies
  • subclassing, with support for `super` calls to a base implementation
  • constructors (through an __init method)
  • dot syntax with `self` support (so you can do `object.do_something(arg)`, and in `do_something(self, arg) … end` self will be correctly set to the object)
  • support for existing metatable mechanisms like __index, __newindex, __tostring etc
  • lazy properties through initializer methods
  • dynamic properties through getter/setter methods
  • method forwarding to other classes, unconditional and unconditional
  • serialization support, including reviving "frozen" objects from the global table after a load
I had a shitload of fun building this, and I love how for being such a tiny language, Lua can do amazing things. And if no-one's interested, I'll just keep it as part of the larger mod (that uses it), otherwise could turn it into a separate "library" mod.

I can show some examples if anyone cares. ;)

PyroFire
Filter Inserter
Filter Inserter
Posts: 356
Joined: Tue Mar 08, 2016 8:18 am
Contact:

Re: Factorio, Lua and OO

Post by PyroFire »

does it work with on_load?

inferis
Burner Inserter
Burner Inserter
Posts: 13
Joined: Fri Nov 01, 2019 2:02 am
Contact:

Re: Factorio, Lua and OO

Post by inferis »

PyroFire wrote:
Sat Dec 07, 2019 4:51 pm
does it work with on_load?
It does.
Why do you ask?

PyroFire
Filter Inserter
Filter Inserter
Posts: 356
Joined: Tue Mar 08, 2016 8:18 am
Contact:

Re: Factorio, Lua and OO

Post by PyroFire »

inferis wrote:
Sun Dec 08, 2019 6:33 pm
Why do you ask?
Because metatables (the whole point of your library) are not kept between save/loads.
If you have a simple way to fix that i'd love to see it.

inferis
Burner Inserter
Burner Inserter
Posts: 13
Joined: Fri Nov 01, 2019 2:02 am
Contact:

Re: Factorio, Lua and OO

Post by inferis »

PyroFire wrote:
Mon Dec 09, 2019 1:47 am
inferis wrote:
Sun Dec 08, 2019 6:33 pm
Why do you ask?
Because metatables (the whole point of your library) are not kept between save/loads.
If you have a simple way to fix that i'd love to see it.
I have.
This is one of the tests I wrote:

Code: Select all

test("serialization", function()
    local serialized 

    -- common class initialization
    local function define_classes(l)
        local Awesome = l.define_namespace("Is.This.Awesome")
        local Foo = Awesome.define_class("Foo")
        local Reb = Awesome.define_class("Reb")
        function Reb:__init() 
            self.rebValue = { dic = "yes" }
        end
    end

    -- serialize
    do
        local l = require('loop')()
        define_classes(l)
        
        local foo = l.Is.This.Awesome.Foo { bar = 10, baz = "bazzie", reb = l.Is.This.Awesome.Reb() }
        foo.reb.rebValue.dic = "nono"
        local registry = { foolala = foo }

        serialized = serpent.serialize(registry, { comment = 0 })
    end

    -- deserialize
    do
        local l = require('loop')()
        define_classes(l)

        local _, registry = serpent.load(serialized)

        l.reanimate(registry)
        assert(registry.foolala.reb.rebValue.dic == "nono")
    end
end)
The important part is:

Code: Select all

l.reanimate(registry)
It will take any value, visit all keys and values (if applicable), checks for class information on the deserialized object and uses it to recreate the class around that object. Non class objects are left alone.

PyroFire
Filter Inserter
Filter Inserter
Posts: 356
Joined: Tue Mar 08, 2016 8:18 am
Contact:

Re: Factorio, Lua and OO

Post by PyroFire »

inferis wrote:
Tue Dec 10, 2019 9:30 am
It will take any value, visit all keys and values (if applicable), checks for class information on the deserialized object and uses it to recreate the class around that object. Non class objects are left alone.
Sounds almost like you could put it in _ENV, but that'd be a little weird i think.

I don't really see anything wrong with the code you've got there or with the library you're building...
But i am wondering, who is your intended audience with this thing?

Anyone who's good enough with lua would likely know enough to deal with their own metatables (saves learning your code, and less work debugging if things dont work properly), and anyone else who doesn't know metatables who might use your library either doesn't have a practical use for them to begin with, or they would be using your library to perform a function that they don't actually understand (nor would they know how to debug it if they can't get something working -- leading back to point one about people already knowing metatables).

I think you're more likely to just get reviews of your code and have people more experienced than you giving feedback as to where you can improve, maybe point out some bad habits etc.
Personally your library is of little to no use to me, i know enough about metatables to make my own and from experience most developers prefer to write their own libraries for things (i'm definitely one of those people).

Still though, post what you got.
Always good to share code around and others who are still learning could gain a lot from seeing a class manager thing, or on the flipside someone experienced might see some interesting methods to go about a problem which they haven't considered before.

inferis
Burner Inserter
Burner Inserter
Posts: 13
Joined: Fri Nov 01, 2019 2:02 am
Contact:

Re: Factorio, Lua and OO

Post by inferis »

PyroFire wrote:
Tue Dec 10, 2019 10:22 am
inferis wrote:
Tue Dec 10, 2019 9:30 am
It will take any value, visit all keys and values (if applicable), checks for class information on the deserialized object and uses it to recreate the class around that object. Non class objects are left alone.
Sounds almost like you could put it in _ENV, but that'd be a little weird i think.
Not sure what you mean exactly, but there's a global instance too. Just doing referencing the require() result (instead of calling it like a function) gives you that:

Code: Select all

local main_loop = require('loop')
gives you that global instance instead of a private instance.
PyroFire wrote:
Tue Dec 10, 2019 10:22 am
I don't really see anything wrong with the code you've got there or with the library you're building...
But i am wondering, who is your intended audience with this thing?

Anyone who's good enough with lua would likely know enough to deal with their own metatables (saves learning your code, and less work debugging if things dont work properly), and anyone else who doesn't know metatables who might use your library either doesn't have a practical use for them to begin with, or they would be using your library to perform a function that they don't actually understand (nor would they know how to debug it if they can't get something working -- leading back to point one about people already knowing metatables).
Fair. I don't have an intended audience, to be honest. I wrote this mostly for myself because it allows me to code to more abstract entities instead of manual table and metatable juggling. If there's any interest I don't mind sharing it, but it's not a priority (given that sharing explicitly kind of means supporting it too). And if there was any goal, it was to provide a mostly straightforward but flexible and powerful library to make more complex stuff a bit easier.

I agree with you on the debugging experience, but that's an issue with any "support" mod. But I can see that this is perhaps more fundamental.
PyroFire wrote:
Tue Dec 10, 2019 10:22 am
I think you're more likely to just get reviews of your code and have people more experienced than you giving feedback as to where you can improve, maybe point out some bad habits etc.
Good! :)
PyroFire wrote:
Tue Dec 10, 2019 10:22 am
Personally your library is of little to no use to me, i know enough about metatables to make my own and from experience most developers prefer to write their own libraries for things (i'm definitely one of those people).
*coughs*
PyroFire wrote:
Tue Dec 10, 2019 10:22 am
Still though, post what you got.
Always good to share code around and others who are still learning could gain a lot from seeing a class manager thing, or on the flipside someone experienced might see some interesting methods to go about a problem which they haven't considered before.
Well, there's the repo: https://bitbucket.org/inferis/loop
If I need to make turn into a mod so I can use it in other mods, I'll do so unless someone specifically requests it. :)

Honktown
Smart Inserter
Smart Inserter
Posts: 1025
Joined: Thu Oct 03, 2019 7:10 am
Contact:

Re: Factorio, Lua and OO

Post by Honktown »

Doesn't lua already allow __self? One can already "inherit" metatables by doing a get set metatable()... Constructors aren't necessarily needed in lua, if you handle nils.

Not a criticism of your intent mind you, but I don't exactly see a need for extra object handling over what Lua provides already if you dig a bit. p.s. "I'm old" doesn't mean anything these days, I was programming C89 and Perl for a fax and SIP stack not three years ago, in a codebase older than I. Wasn't even allowed to dynamically allocate memory when doing the C ;(

I'll say I'm definitely not a proper audience for your library.
I have mods! I guess!
Link

mrvn
Smart Inserter
Smart Inserter
Posts: 5682
Joined: Mon Sep 05, 2016 9:10 am
Contact:

Re: Factorio, Lua and OO

Post by mrvn »

Honktown wrote:
Fri Dec 13, 2019 4:18 pm
Doesn't lua already allow __self? One can already "inherit" metatables by doing a get set metatable()... Constructors aren't necessarily needed in lua, if you handle nils.

Not a criticism of your intent mind you, but I don't exactly see a need for extra object handling over what Lua provides already if you dig a bit. p.s. "I'm old" doesn't mean anything these days, I was programming C89 and Perl for a fax and SIP stack not three years ago, in a codebase older than I. Wasn't even allowed to dynamically allocate memory when doing the C ;(

I'll say I'm definitely not a proper audience for your library.
metatables are not saved and you have to restore them on load yourself. Everything else I thing lua already has kind of.

Honktown
Smart Inserter
Smart Inserter
Posts: 1025
Joined: Thu Oct 03, 2019 7:10 am
Contact:

Re: Factorio, Lua and OO

Post by Honktown »

mrvn wrote:
Fri Dec 13, 2019 4:35 pm
Honktown wrote:
Fri Dec 13, 2019 4:18 pm
Doesn't lua already allow __self? One can already "inherit" metatables by doing a get set metatable()... Constructors aren't necessarily needed in lua, if you handle nils.

Not a criticism of your intent mind you, but I don't exactly see a need for extra object handling over what Lua provides already if you dig a bit. p.s. "I'm old" doesn't mean anything these days, I was programming C89 and Perl for a fax and SIP stack not three years ago, in a codebase older than I. Wasn't even allowed to dynamically allocate memory when doing the C ;(

I'll say I'm definitely not a proper audience for your library.
metatables are not saved and you have to restore them on load yourself. Everything else I thing lua already has kind of.
Going by the lua description, they seem to be just tables returned from get_metatable(), so you could store them in global
I have mods! I guess!
Link

inferis
Burner Inserter
Burner Inserter
Posts: 13
Joined: Fri Nov 01, 2019 2:02 am
Contact:

Re: Factorio, Lua and OO

Post by inferis »

Honktown wrote:
Fri Dec 13, 2019 4:18 pm
Doesn't lua already allow __self? One can already "inherit" metatables by doing a get set metatable()... Constructors aren't necessarily needed in lua, if you handle nils.
Lua doesn't have a real concept of self. The only thing close to it is using ":" syntax to call a "method" instead of ".".

Code: Select all

local foo = {}

function foo:bar()
   self:baz('zip')
end

print(foo:bar())

which is equivalent to:

Code: Select all

local foo = {}

function bar(self)
   baz(self, 'zip')
end

print(bar(foo))
The : syntax works but it's not that straightforward, when you're coming from another language. Snooping through other mods code bases, I didn't see much use of it, but that might be me. One of the reasons probably is that Factorio already keeps state around (in global) and most "objects" are just proxies to that.

But at least for me, it doesn't fit my mental model very well and it feels very fragile, especially for larger mods. I'm probably too damaged by my line of work. :D
Honktown wrote:
Fri Dec 13, 2019 4:18 pm
Not a criticism of your intent mind you, but I don't exactly see a need for extra object handling over what Lua provides already if you dig a bit. p.s. "I'm old" doesn't mean anything these days, I was programming C89 and Perl for a fax and SIP stack not three years ago, in a codebase older than I. Wasn't even allowed to dynamically allocate memory when doing the C ;(

I'll say I'm definitely not a proper audience for your library.
Of course.

Like I said, I mostly wrote this for my own use. I had a shit ton of fun doing it (and learning the ins and outs of Lua).If no one is interested in using it, that's just fine for me. ;)

justarandomgeek
Filter Inserter
Filter Inserter
Posts: 300
Joined: Fri Mar 18, 2016 4:34 pm
Contact:

Re: Factorio, Lua and OO

Post by justarandomgeek »

inferis wrote:
Fri Dec 13, 2019 9:28 pm
Lua doesn't have a real concept of self. The only thing close to it is using ":" syntax to call a "method" instead of ".".
Well, it kinda does. The ':' call syntax compiles to a different lua vm opcode, SELF which does the lookup operation and prepares the resulting function and the parent object in registers for calling, while doing the same thing with '.' (`t.foo(t,...)`) just looks up the function (GETTABLE, or GETTABUP if it's an upvalue) and then copies the parent object with a MOVE instruction to be ready for calling. Either case is then followed by preparing the rest of the arguments and then a CALL or TAILCALL.

A simple case with no further arguments:

Code: Select all

local function do_stuff(foo)
    foo:bar()
--      1       SELF            1 0 -1  ; "bar"
--      2       CALL            1 2 1
    foo.bar(foo)
--      3       GETTABLE        1 0 -1  ; "bar"
--      4       MOVE            2 0
--      5       CALL            1 2 1
end
--      6       RETURN          0 1

inferis
Burner Inserter
Burner Inserter
Posts: 13
Joined: Fri Nov 01, 2019 2:02 am
Contact:

Re: Factorio, Lua and OO

Post by inferis »

justarandomgeek wrote:
Thu Jan 02, 2020 12:53 am
inferis wrote:
Fri Dec 13, 2019 9:28 pm
Lua doesn't have a real concept of self. The only thing close to it is using ":" syntax to call a "method" instead of ".".
Well, it kinda does. The ':' call syntax compiles to a different lua vm opcode, SELF which does the lookup operation and prepares the resulting function and the parent object in registers for calling, while doing the same thing with '.' (`t.foo(t,...)`) just looks up the function (GETTABLE, or GETTABUP if it's an upvalue) and then copies the parent object with a MOVE instruction to be ready for calling. Either case is then followed by preparing the rest of the arguments and then a CALL or TAILCALL.

A simple case with no further arguments:

Code: Select all

local function do_stuff(foo)
    foo:bar()
--      1       SELF            1 0 -1  ; "bar"
--      2       CALL            1 2 1
    foo.bar(foo)
--      3       GETTABLE        1 0 -1  ; "bar"
--      4       MOVE            2 0
--      5       CALL            1 2 1
end
--      6       RETURN          0 1
Cool, I didn't know that. The more you know. :)
I'm assuming the first one is more performant. I wonder I I can trick to the compiler into generating these SELF instructions, because I'm fairly sure that my approach is going the GETTABLE/MOVE route.

Anyway, from a code standpoint, it doesn't really matter much, right? The runtime has a self concept, and the language "kind of" does. It's there but not as strongly or explicitly defined as in other languages.

The more I learn about Lua, the more I love it. ;)

justarandomgeek
Filter Inserter
Filter Inserter
Posts: 300
Joined: Fri Mar 18, 2016 4:34 pm
Contact:

Re: Factorio, Lua and OO

Post by justarandomgeek »

inferis wrote:
Wed Jan 08, 2020 9:13 pm
Cool, I didn't know that. The more you know. :)
I'm assuming the first one is more performant. I wonder I I can trick to the compiler into generating these SELF instructions, because I'm fairly sure that my approach is going the GETTABLE/MOVE route.

Anyway, from a code standpoint, it doesn't really matter much, right? The runtime has a self concept, and the language "kind of" does. It's there but not as strongly or explicitly defined as in other languages.

The more I learn about Lua, the more I love it. ;)
The interesting difference is that SELF only evaluates the "object" expression once. The other way is only a MOVE if it was already in a register (that is, if it was a local or param), since Lua isn't smart enough to do common subexpression elimination. If it's a global lookup (GETTABUP _ENV "something" + zero or more GETTABLE) or the result of some function call, it would GETTABLE in it that result, discard it and then execute it again to get the copy to place into the first param.

I doubt you can make it generate SELF without using the : call syntax.

User avatar
MEOWMI
Filter Inserter
Filter Inserter
Posts: 307
Joined: Wed May 22, 2019 12:21 pm
Contact:

Re: Factorio, Lua and OO

Post by MEOWMI »

I would definitely be interested in checking this out if I get back into Factorio modding.
PyroFire wrote:
Tue Dec 10, 2019 10:22 am
if they can't get something working
Isn't the point that it should be a (mostly) working library? :P You can still make use of it as you learn about things, if that's the position you're in.
PyroFire wrote:
Tue Dec 10, 2019 10:22 am
most developers prefer to write their own libraries for things (i'm definitely one of those people).
I definitely prefer using existing libraries, provided they do what I need, so it has to be a reasonably good library.

I'm a strong proponent of not reinventing the wheel, but of course that's not always possible so there is some truth to what you say.

inferis
Burner Inserter
Burner Inserter
Posts: 13
Joined: Fri Nov 01, 2019 2:02 am
Contact:

Re: Factorio, Lua and OO

Post by inferis »

MEOWMI wrote:
Thu Jan 09, 2020 11:31 am
I would definitely be interested in checking this out if I get back into Factorio modding.
PyroFire wrote:
Tue Dec 10, 2019 10:22 am
if they can't get something working
Isn't the point that it should be a (mostly) working library? :P You can still make use of it as you learn about things, if that's the position you're in.
Also, the goal is to make it as straightforward to use (which is a tall order, of course).
I am very much aware of the "dangers" of relying on a 3rd party library, but when the benefits outweigh the upsides, it could be worth it. (not saying that's the case here… yet).
MEOWMI wrote:
Thu Jan 09, 2020 11:31 am
PyroFire wrote:
Tue Dec 10, 2019 10:22 am
most developers prefer to write their own libraries for things (i'm definitely one of those people).
I definitely prefer using existing libraries, provided they do what I need, so it has to be a reasonably good library.

I'm a strong proponent of not reinventing the wheel, but of course that's not always possible so there is some truth to what you say.
But reinventing the wheel is fun. ;)

Post Reply

Return to “Modding discussion”