Need some help understanding superloading

All development conversation and discussion takes place here

Moderator: Moderator

Message
Author
nate
Wyrmic
Posts: 261
Joined: Fri Jan 18, 2013 8:35 am

Need some help understanding superloading

#1 Post by nate »

I'm trying to figure out superloading so that I can write mods the right way. Running into problems.

Here's (what I believe to be) my troublesome code, in /mod/class:

Code: Select all

local _M = loadPrevious(...)

local base_onEnterLevel = _M:onEnterLevel

function _M:onEnterLevel(zone, level)
	game.logSeen(self, "This is a test of superloading.")
    return base_act(self, zone, level)
end

return _M
Which I anticipated to just give me a log message every time I entered a level, but which instead throws:

Code: Select all

FROM 	/mod/addons/example/superload/mod/class/Actor.lua	loading previous!
Lua Error: /loader/init.lua:148: bad argument #1 to 'setfenv' (number expected, got nil)
	At [C]:-1 
	At [C]:-1 setfenv
	At /loader/init.lua:148 
	At [C]:-1 require
	At /mod/class/Game.lua:45 
	At [C]:-1 require
	At /mod/load.lua:326 
	At [C]:-1 require
	At /engine/Module.lua:159 load
	At /engine/Module.lua:699 instanciate
	At /engine/utils.lua:1906 showMainMenu
	At /engine/init.lua:136 
	At [C]:-1 dofile
	At /loader/init.lua:190 
Tried to trace this through, but it doesn't make any sense to me. The above code (player.lua superload) was just added to the superload example ( http://te4.org/dl/tmp/tome-example.teaa ) from the wiki, which was modified for version 1,0,0 but otherwise untouched, and which stops throwing errors the instant I remove my overload/mod/player.lua.

Happy to provide any more details, have just provided what I believe to be necessary. Looking especially for abstract information about how superloading works, less so for how to get it to work in this particular instance (which as you can see from the code is just a learning exercise for me).

Thanks in advance for anybody who feels like helping me out!
Proud father of Fx4fx and Chronometer add-ons; proud mother of Fated add-on

Hirumakai
Thalore
Posts: 192
Joined: Wed Aug 11, 2010 2:39 pm

Re: Need some help understanding superloading

#2 Post by Hirumakai »

nate wrote:I'm trying to figure out superloading so that I can write mods the right way. Running into problems.

Here's (what I believe to be) my troublesome code, in /mod/class:

Code: Select all

local _M = loadPrevious(...)

local base_onEnterLevel = _M:onEnterLevel

function _M:onEnterLevel(zone, level)
	game.logSeen(self, "This is a test of superloading.")
    return base_act(self, zone, level)
end

return _M
It seems to me that you meant to return base_onEnterLevel(self,zone,level) instead of base_act(self,zone,level)? Also I don't ever remember including a "return _M" line in any of my superload files. What is it returning from? Its not in a function as far as I can tell.

Try

Code: Select all

local _M = loadPrevious(...)

local base_onEnterLevel = _M:onEnterLevel

function _M:onEnterLevel(zone, level)
	game.logSeen(self, "This is a test of superloading.")
    return base_onEnterLevel(self, zone, level)
end
and see how it does.

nate
Wyrmic
Posts: 261
Joined: Fri Jan 18, 2013 8:35 am

Re: Need some help understanding superloading

#3 Post by nate »

Nope, same errors with the code you provided.

(I was using the example add-on linked above as a template, which is where the return _M line came from, as well as the wrong function call on the superload's return :) But I've been through about a bazillion different ways of trying to get around this error anyways-- that wrong call only entered the picture relatively recently)

As long as I'm asking questions, what does _M mean anyways?
Proud father of Fx4fx and Chronometer add-ons; proud mother of Fated add-on

aardvark
Wyrmic
Posts: 200
Joined: Wed Aug 22, 2012 12:16 am

Re: Need some help understanding superloading

#4 Post by aardvark »

The error is because it's failing to parse your file. Probably because

Code: Select all

local base_onEnterLevel = _M:onEnterLevel
should read

Code: Select all

local base_onEnterLevel = _M.onEnterLevel
The colon doesn't work for general table member referencing, only function calls and definitions.

P.S. _M is a variable name that just happens to start with an underscore. Here, it references the table of the Actor class. There's nothing special about calling it _M, it's just sort of a T-Engine 4 custom.

nate
Wyrmic
Posts: 261
Joined: Fri Jan 18, 2013 8:35 am

Re: Need some help understanding superloading

#5 Post by nate »

Thank you, that got rid of my error! (My superloaded function isn't yet printing anything to the log, but that's something I can probably figure out on my own.)

Regarding _M: So, if I understand correctly, when you declare a _M:myFunction, you're creating a function that gets added to the the table of everything of class Actor? So then you can call myActor:myFunction(), or myOtherActor:myFunction()? I'm still trying to figure a lot of this stuff out. Knowing why it's _M (and not _N) would probably help me wrap my head around things.

I would love to hear more about usage of : and . if you have any information. It seems like you use function myTable:myFunction to define/create a function, myTable:myFunction() to call that function, but duplicateHandleToFunction = myTable.myFunction to assign that function someplace else? After which you could call duplicateHandleToFunction() with the same effect as myTable:myFunction()?

Am I correct in that understanding?
Proud father of Fx4fx and Chronometer add-ons; proud mother of Fated add-on

aardvark
Wyrmic
Posts: 200
Joined: Wed Aug 22, 2012 12:16 am

Re: Need some help understanding superloading

#6 Post by aardvark »

re: _M:
nate wrote:So, if I understand correctly, when you declare a _M:myFunction, you're creating a function that gets added to the the table of everything of class Actor?
In this case, yes. The first line,

Code: Select all

local _M = loadPrevious(...)
is the key here. loadPrevious() is a T-Engine 4 function that returns the table created by the file of the same relative pathname as its argument. Your file, in this case, is superload/mod/class/Actor.lua. As such, you're superloading Tome 4's mod/class/Actor.lua. mod/class/Actor.lua is the file that defines the Actor class (the mod.class.Actor table), with all of its functions, etc.. A reference to that table is the return value of loadPrevious(), which you assign to the local variable _M.

Which is a long-winded, technical way of saying, "yes, your function definition goes into the Actor class but only because you're superloading Actor and not some other class."
nate wrote: So then you can call myActor:myFunction(), or myOtherActor:myFunction()? I'm still trying to figure a lot of this stuff out. Knowing why it's _M (and not _N) would probably help me wrap my head around things.
The fact that it's _M (and not _N) is just because that's what "you" chose to call it in your file. You could call it bob, Horatio, or that_one_class_i_am_superloading for all the difference it makes. It's a local variable that refers to the table holding the class definition contained in the file of the same(-ish) path name. Take a second to let your eyes uncross after that last sentence.

In other words, the name doesn't matter. The leading underscore doesn't matter. It looks important and confusing, but it's not special in any way.
nate wrote:I would love to hear more about usage of : and . if you have any information. It seems like you use function myTable:myFunction to define/create a function, myTable:myFunction() to call that function, but duplicateHandleToFunction = myTable.myFunction to assign that function someplace else? After which you could call duplicateHandleToFunction() with the same effect as myTable:myFunction()?

Am I correct in that understanding?
Yes, you've got it! The : operator is "syntactic sugar." A function defined

Code: Select all

function _M:thingy()
is identical to

Code: Select all

function _M.thingy(self)
where self is a reference to the same table _M references when you also call the function using a colon. That is, calling

Code: Select all

_M:thingy()
is the same as calling

Code: Select all

_M.thingy(_M)
It's just a shorthand way of passing a table as an argument to its own function so you can do object oriented-style programming.

The lua-users wiki has a far more detailed and eloquent explanation than mine.

nate
Wyrmic
Posts: 261
Joined: Fri Jan 18, 2013 8:35 am

Re: Need some help understanding superloading

#7 Post by nate »

Thank you very much for the in-depth response. I'm really impressed with the class of folks that populate this forum. I appreciate the details greatly. (I've previously checked out the page you linked, and I think you're explaining it much better, actually)

If you have energy, I would like to continue to pick your brain, although I understand perfectly if you don't :)

1) So these two statements are functionally identical:

function myTable:thingy(arg 1, arg2)

vs

self = myTable
function myTable.thingy(self, arg1, arg2)

(just to clarify how the arguments work)

And during execution, thingy can refer to self, even if declared in the first way and not sent an argument of self?

What about in calling the function, as opposed to declaration? Will

myTable.thingy(self, arg1, arg2)

work as well as

myTable:thingy(arg1, arg2)

?

2) What about code like that in mod/class/interface/combat.lua, where _M is not explicitly defined, and yet is used as the table that contains all of the functions defined therein? Is it using a definition from a different file? Is it using _M in the same way that I am in my Player.lua superload?

(I'm not totally sure that I'm using the correct terminology, and I realize that can impact understanding, so please forgive me that, and feel free to correct me.)
Proud father of Fx4fx and Chronometer add-ons; proud mother of Fated add-on

aardvark
Wyrmic
Posts: 200
Joined: Wed Aug 22, 2012 12:16 am

Re: Need some help understanding superloading

#8 Post by aardvark »

nate wrote:1) So these two statements are functionally identical:

function myTable:thingy(arg 1, arg2)

vs

self = myTable
function myTable.thingy(self, arg1, arg2)

(just to clarify how the arguments work)
That's close. The

Code: Select all

self = myTable
is superfluous, though. In the myTable.thingy() definition (after the vs), self, like arg1 and arg2, is locally scoped to the function body. The self inside the function declaration masks the preceding definition. That definition isn't needed at all for things to work.
nate wrote:And during execution, thingy can refer to self, even if declared in the first way and not sent an argument of self?
Yes. The declaration of self as the first argument (always the first argument!) is implicit because you used the colon.
nate wrote:What about in calling the function, as opposed to declaration? Will

myTable.thingy(self, arg1, arg2)

work as well as

myTable:thingy(arg1, arg2)
Yes, as long as self references myTable somehow. It could be directly or through a metatable. Note especially the section labeled __index. That's one way to do object oriented-style programming in Lua.

Code: Select all

local myTable = {}
function myTable:complicate()
	return self.elsewhere
end

local myTableInstance = { elsewhere = "Confused yet?" }
setmetatable(myTableInstance, { __index = myTable })

print(myTableInstance:complicate())
-- Prints "Confused yet?"
The T-Engine 4 also copies function references from parent class tables to their child class tables for its inheritance system. That has the same effect on function calling options.

You can also mix and match the declaration and calling styles:

Code: Select all

-- Definitions
function myTable:odds(arg)
function myTable.ends(self, arg)

-- Calls
myTable.odds(myTable, ": declared, . called")
myTable:ends(". declared, : called")
That's what you're doing with onEnterLevel() in your first post without even realizing it. Since you assigned the old function to a local variable that doesn't reference _M, you have to pass it self explicitly (which in the replacement onEnterLevel() definition is passed implicitly) even though the old function definition in mod/class/Actor.lua uses the implicit colon syntax, much like in the ": declared, . called" example above.
nate wrote:2) What about code like that in mod/class/interface/combat.lua, where _M is not explicitly defined, and yet is used as the table that contains all of the functions defined therein? Is it using a definition from a different file? Is it using _M in the same way that I am in my Player.lua superload?
Yes. The Lua function module() (called in Combat.lua, line 28 in v1.0) defines _M for you as part of its workings. That's the reason _M is used in the T-Engine 4 and ToME 4 code. I think it mostly carried over into addons for consistency.

module() used to be the way to define modules, but has been heavily criticized and deprecated with extreme prejudice in Lua 5.2. I don't use it but I don't see darkgod changing the entire existing T-Engine 4 code base anytime soon.

Those are the best answers I've got, for whatever they're worth.

Edit: Correction
Last edited by aardvark on Thu Feb 14, 2013 12:41 am, edited 1 time in total.

daftigod
Archmage
Posts: 300
Joined: Fri Feb 18, 2011 6:15 am

Re: Need some help understanding superloading

#9 Post by daftigod »

I've been hoping for a long time that someone would explain the superloading process, and here it is in full gory detail. Thanks again aardvark, you have a way of explaining all things Lua in a fashion that laymen can understand. This thread is extremely valuable for all addon authors, and I think it should be stickied here and in the addon forum.

As soon as I can sit down and actually comprehend this info, I'll be converting the actor/combat code in my barbarian addon to superloaded code. Thanks again aardvark, and good luck nate!

darkgod
Master of Eyal
Posts: 10750
Joined: Wed Jul 24, 2002 9:26 pm
Location: Angolwen
Contact:

Re: Need some help understanding superloading

#10 Post by darkgod »

I'd say you should even make some wiki pages ;)
But yeah, stickied!
[tome] joylove: You can't just release an expansion like one would release a Kraken XD
--
[tome] phantomfrettchen: your ability not to tease anyone is simply stunning ;)

nate
Wyrmic
Posts: 261
Joined: Fri Jan 18, 2013 8:35 am

Re: Need some help understanding superloading

#11 Post by nate »

Thanks aardvark, that was a really awesome explanation. I feel suitably prepared for wading a little deeper into the Lua ocean now!
Proud father of Fx4fx and Chronometer add-ons; proud mother of Fated add-on

aardvark
Wyrmic
Posts: 200
Joined: Wed Aug 22, 2012 12:16 am

Re: Need some help understanding superloading

#12 Post by aardvark »

I'm glad I could help, but now that I look closer, I'm a bit embarrassed. I saw the stack trace from the log and figured you were crashing while trying to superload Actor when you were superloading Player. Let's just pretend all the times I mentioned "Actor" I actually wrote "Player," shall we? I sincerely apologize for causing any extra confusion.

nate
Wyrmic
Posts: 261
Joined: Fri Jan 18, 2013 8:35 am

Re: Need some help understanding superloading

#13 Post by nate »

No, I knew what you meant :)
Proud father of Fx4fx and Chronometer add-ons; proud mother of Fated add-on

nate
Wyrmic
Posts: 261
Joined: Fri Jan 18, 2013 8:35 am

Re: Need some help understanding superloading

#14 Post by nate »

Okay, more questions :)

Now I'm taking a look at talents and trying to figure out how to superload them. Talent files are collections of what look like tables labelled newTalent, but that can't be quite right, because wouldn't each of these tables overwrite the last? So what exactly are these?

And, from a superloading perspective, how do I go about accessing their individual functions and changing them? Do I have to figure out the talent id in some table of talents to access their individual .actions and .infos? It seems like this is perfectly possible, but wouldn't work along the same lines of the previous superloads. It also seems like there is probably more than one way to do it. (I've never used a powerful interpreted language like Lua before, it kind of scares me :) )

EDIT: Oh, and let me clarify something: even though I am modding player.lua, onEnterLevel is still a function belonging to class Actor, because the beginning of player.lua loads module (x, class.inherit(mod.class.Actor, etc)) ? Is that correct?
Proud father of Fx4fx and Chronometer add-ons; proud mother of Fated add-on

aardvark
Wyrmic
Posts: 200
Joined: Wed Aug 22, 2012 12:16 am

Re: Need some help understanding superloading

#15 Post by aardvark »

nate wrote:Now I'm taking a look at talents and trying to figure out how to superload them. Talent files are collections of what look like tables labelled newTalent, but that can't be quite right, because wouldn't each of these tables overwrite the last? So what exactly are these?
Not exactly. newTalent() is a T-Engine 4 function that takes a table describing a talent as its argument and makes it accessible to Actor objects. It uses a little more of Lua's syntactic sugar.

Code: Select all

-- Two ways to pass a single table as a function argument
myfunc{}
myfunc({})
The same thing exists for quotes, too.

Code: Select all

-- All identical
myfunc2""
myfunc2 ""
myfunc2("")
Just remember that it only works for single arguments. If you tried

Code: Select all

myfunc3 "First", "second"
you'd get an error. When in doubt, use parentheses.
nate wrote:And, from a superloading perspective, how do I go about accessing their individual functions and changing them? Do I have to figure out the talent id in some table of talents to access their individual .actions and .infos? It seems like this is perfectly possible, but wouldn't work along the same lines of the previous superloads. It also seems like there is probably more than one way to do it. (I've never used a powerful interpreted language like Lua before, it kind of scares me :) )
Superloading the data/ subdirectory generally doesn't work. You can only superload T-Engine 4 classes, which are pretty much all in mod/class/ and mod/dialogs/. Modifying talents is best done using hooks.
nate wrote:EDIT: Oh, and let me clarify something: even though I am modding player.lua, onEnterLevel is still a function belonging to class Actor, because the beginning of player.lua loads module (x, class.inherit(mod.class.Actor, etc)) ? Is that correct?
I'm afraid not. See? My earlier mistake was confusing! onEnterLevel() is in Player.lua (line 115 in v1), not Actor.lua. Your superloaded function replaces the old Player one.

If you were to replace one of Actor's functions in your superloaded Player, it still wouldn't change the one in Actor. Your replacement would only be used when called from a table referencing mod.class.Player. Your version in Player would override the version from Actor only for Player objects.

class.inherit() makes the class you're defining a child class of each of its arguments. So Player is an Actor, Player implements the PlayerRest interface, Player implements the PlayerRun interface, and so on. It populates _M (notice it's part of the module() call) with all the functions, etc. of all its parent classes so that you can use it as if it was any of them. That is, you can use a Player object for any function that expects an Actor object (or a PlayerRest object, or...).

It creates what's called an isa relationship. So Player isa Actor, but Actor is not a Player; they're not interchangeable. You can only use a Player as an Actor, not an Actor as a Player.

It's as close as Lua gets to object-oriented multiple inheritance. Whole books have been written about OOP, but it'll rarely be something that addon authors need to worry about unless adding pretty major functionality to a module.


To recap: newTalent() is a function that uses a shorthand syntax; you should use hooks for talents; and ignore class.inherit() as much as possible until you have a decent grasp of object-oriented inheritance because it's not likely to be immediately important.

I hope that all comes across as less confusing than I think it does.
Last edited by aardvark on Sat Feb 16, 2013 3:22 am, edited 1 time in total.

Post Reply