## Love2d selectively refusing to recognize my functions

Questions about the LÖVE API, installing LÖVE and other support related questions go here.
Forum rules
DGM
Prole
Posts: 14
Joined: Tue Mar 20, 2018 6:42 am

### Love2d selectively refusing to recognize my functions

Greetings all,

I have some simple programming experience but am new to Lua and Love2d. I created a simple counting program to get familiar with the basics. I made 3 functions:

timepiece:create () - sets a counter, called in the load function.
timepiece:increment () - adds 1 to the counter, called in update.
timepiece:draw () - displays the current value, called in draw.

The first function is good and if I use other methods to increment and draw the program works as intended. But if I try to call either of my other two functions it complains that the methods are nil values. I've tried a dozen varients and I simply can't figure out why create works but the others don't. I feel like I'm missing something very basic, so if someone could clue me in I'd appreciate it.

Here's the working version of the code with the lines that cause the crash commented out:

Code: Select all

function love.load ()
t1 = timepiece:create ()
end

function love.update ()
--t1:increment ()
t1.time = t1.time + 1
end

function love.draw ()
love.graphics.clear ()
--t1:draw ()
love.graphics.print (t1.time)
end

timepiece = {}

function timepiece:create ()
local object = {time = 1}
return object
end

function timepiece:increment ()
self.time = self.time + 1
end

function timepiece:draw ()
love.graphics.print (self.time)
end

Tjakka5
Party member
Posts: 237
Joined: Thu Dec 26, 2013 12:17 pm

### Re: Love2d selectively refusing to recognize my functions

You seem to be wanting to create a class, but have no functionality to let your object inherit the class's functions.
One fix for it would be like this:

Code: Select all

timepiece = {}

function timepiece:create ()
local object = {time = 1}

-- Our object inherits the functions here
object.increment = timepiece.increment
object.draw = timepiece.draw

return object
end

function timepiece:increment ()
self.time = self.time + 1
end

function timepiece:draw ()
love.graphics.print (self.time)
end

There are a bunch of different ways to go about making a class, this is probably the worst of them all.
If you're looking to optimize it you can look into using 'setmetatable' and the '__index' metamethod. Or use a class library that has done all that for you.
Check out my portfolio: http://tjakka5.sorunome.de/

DGM
Prole
Posts: 14
Joined: Tue Mar 20, 2018 6:42 am

### Re: Love2d selectively refusing to recognize my functions

Tjakka5 wrote:
Tue Mar 20, 2018 10:24 am
You seem to be wanting to create a class, but have no functionality to let your object inherit the class's functions.
I was working my way towards that, yes.

There are a bunch of different ways to go about making a class, this is probably the worst of them all.
Fair enough, but right now I'm just trying to understand what I was doing wrong. I was using this tutorial and as far as I can see my code should have worked. Why couldn't I pass t1 to the increment and draw functions? Why did Love2d seem to think those functions didn't even exist?

zorg
Party member
Posts: 2509
Joined: Thu Dec 13, 2012 2:55 pm
Location: Absurdistan, Hungary
Contact:

### Re: Love2d selectively refusing to recognize my functions

DGM wrote:
Tue Mar 20, 2018 1:35 pm
Tjakka5 wrote:
Tue Mar 20, 2018 10:24 am
There are a bunch of different ways to go about making a class, this is probably the worst of them all.
Fair enough, but right now I'm just trying to understand what I was doing wrong. I was using this tutorial and as far as I can see my code should have worked. Why couldn't I pass t1 to the increment and draw functions? Why did Love2d seem to think those functions didn't even exist?
The PiL example page you were probably looking for is the one coming right after that; still, the reason it didn't work, is because of this nice little blurb:
PiL wrote: This kind of function is almost what we call a method. However, the use of the global name Account inside the function is a bad programming practice. First, this function will work only for this particular object. Second, even for this particular object the function will work only as long as the object is stored in that particular global variable; if we change the name of this object, withdraw does not work any more:
You're not storing the value as timepiece.time, but as a local object returned by timepiece:create.
One solution that would work would be to call the :increment and :draw functions of timepiece not with a colon, but with a dot, and to explicitly pass in t1; that would work.

Then again, usually one does not need a self parameter for constructors, hence, those are defined with the dot syntax (unless it's a copy constructor or something).

I prefer "classes" this way:

Code: Select all


-- Private members

local Private = {}

-- The table containing methods and (public) members of the class, if needed.

local Class = {}

-- All the methods of the class (minus constructor, strictly speaking, not a method)

Class.foo = function(obj, arg1, arg2)
-- obj is a stand-in for self, in case you don't want to use the : syntax sugar, or want more telling a name for the passed-in objects.
end

-- Define a metatable; things get a bit complex here, but this basically only means that the __index key is pointing to the Class table.

local mtClass = {__index = Class}

-- The constructor.

local new = function(arg1, arg2)

-- create new instance
local obj = {}

-- set state (either here, or by a helper function)
obj.bar = 0

-- finally, make it so that calling a method on the instance itself will try to call it from the class - that's what the __index metatable is for; less code duplication.
obj = setmetatable(obj, mtClass)

-- and return the instance
return obj

end

-- In the end, return the constructor only (Since i'm guessing you don't have class-methods that would operate on the class itself, not on the instances)
return new


All of this in a separate lua file, and it's all neat and tidy.
Me and my stuff True Neutral Aspirant. Why, yes, i do indeed enjoy sarcastically correcting others when they make the most blatant of spelling mistakes. No bullying or trolling the innocent tho.

raidho36
Party member
Posts: 1685
Joined: Mon Jun 17, 2013 12:00 pm

### Re: Love2d selectively refusing to recognize my functions

Or you can use one of bajillion class implementations. Note however that they aren't all made equal, some of them impose very significant data access & function call overhead, while others don't. Simply choosing wrong class library can drop game performance by as much as 50% in particularly bad cases. That said, don't assume your own implementation will automatically have no such problems.

DGM
Prole
Posts: 14
Joined: Tue Mar 20, 2018 6:42 am

### Re: Love2d selectively refusing to recognize my functions

zorg wrote:
Tue Mar 20, 2018 5:44 pm
You're not storing the value as timepiece.time, but as a local object returned by timepiece:create.
One solution that would work would be to call the :increment and :draw functions of timepiece not with a colon, but with a dot, and to explicitly pass in t1; that would work.
I'm definitely missing something, then, because I thought the whole point of the colon syntax was to be syntactic sugar for exactly that. Shouldn't t1:draw() be functionally identical to t1.draw(t1) or t1.draw(self)? Why would one work but not the other?

raidho36 wrote:
Wed Mar 21, 2018 5:38 am
Or you can use one of bajillion class implementations... That said, don't assume your own implementation will automatically have no such problems.
Noted, but the point is to learn exactly how Lua and Love2d work. A good implementation can come later.

zorg
Party member
Posts: 2509
Joined: Thu Dec 13, 2012 2:55 pm
Location: Absurdistan, Hungary
Contact:

### Re: Love2d selectively refusing to recognize my functions

DGM wrote:
Fri Mar 23, 2018 9:25 am
zorg wrote:
Tue Mar 20, 2018 5:44 pm
You're not storing the value as timepiece.time, but as a local object returned by timepiece:create.
One solution that would work would be to call the :increment and :draw functions of timepiece not with a colon, but with a dot, and to explicitly pass in t1; that would work.
I'm definitely missing something, then, because I thought the whole point of the colon syntax was to be syntactic sugar for exactly that. Shouldn't t1:draw() be functionally identical to t1.draw(t1) or t1.draw(self)? Why would one work but not the other?
It is syntactic sugar, but the problem is, as i said, was that you were applying it to the wrong variable... in a sense.

If the variable you returned would have a metatable, whose __index field pointed to timepiece's table (where the methods are stored), then it would have worked; that's the thing you were missing.

Otherwise, again, the objects you created only contained one thing, a variable called time, with a value of 1.

Code: Select all


timepiece = {}

mtTimepiece = {__index = timepiece}

function timepiece:create ()
local object = {time = 1}
object = setmetatable(object, mtTimepiece) -- allow the objects to use functions/variables in timepiece itself.
return object
end

Me and my stuff True Neutral Aspirant. Why, yes, i do indeed enjoy sarcastically correcting others when they make the most blatant of spelling mistakes. No bullying or trolling the innocent tho.

pgimeno
Party member
Posts: 1501
Joined: Sun Oct 18, 2015 2:58 pm

### Re: Love2d selectively refusing to recognize my functions

DGM wrote:
Tue Mar 20, 2018 1:35 pm
Fair enough, but right now I'm just trying to understand what I was doing wrong.
Let's see if this explanation helps clarifying that.

I think you are confusing class and instance. If your functions are in the class but not in the instance, but you want them to act on the instance, you need to pass the instance to them, not the class. Otherwise the class has no idea of what instance to affect.

When you do this:

Code: Select all

function timepiece:create ()
local object = {time = 1}
return object
end

you're creating a function in the class that returns a table with one value and no functions, therefore you can't do this:

Code: Select all

function timepiece:increment () -- this function is in the timepiece class
self.time = self.time + 1
end

local timeinstance = timepiece:create() -- timeinstance equals {time=1} after doing this
timeinstance:increment() -- error: increment is nil because timeinstance doesn't have it, it only has time

because that's equivalent to timeinstance.increment(timeinstance), but timeinstance is still an object with no functions, therefore you would be calling timeinstance.increment, which is nil, and get an error. You need to call the class functions, but since you want them to act on the instance, you need to pass the instance to them, like this:

Code: Select all

local timeinstance = timepiece:create()
timepiece.increment(timeinstance)

That would work.

Once you understand that, you can realize that you need something else in your instances that helps you call functions from the class without directly referencing the class, either by copying them to the instance, as Tjakka5 suggested, or by setting a metatable with __index pointing to the class, as Zorg suggested. Or by using a class library that solves it for you, as raidho suggested.
Thrust II Reloaded - GifLoad for Löve - GSpöt GUI - My NotABug.org repositories - portland (mobile orientation)
The MS-Github repositories I had have been closed after the acquisition announcement and will be removed in the near future.

DGM
Prole
Posts: 14
Joined: Tue Mar 20, 2018 6:42 am

### Re: Love2d selectively refusing to recognize my functions

Okay, I think I'm getting it now. Thanks, guys.

### Who is online

Users browsing this forum: No registered users and 9 guests