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
Before you make a thread asking for help, read this.
Post Reply
DGM
Prole
Posts: 16
Joined: Tue Mar 20, 2018 6:42 am

Love2d selectively refusing to recognize my functions

Post by DGM »

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
User avatar
Tjakka5
Party member
Posts: 243
Joined: Thu Dec 26, 2013 12:17 pm

Re: Love2d selectively refusing to recognize my functions

Post by Tjakka5 »

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.
DGM
Prole
Posts: 16
Joined: Tue Mar 20, 2018 6:42 am

Re: Love2d selectively refusing to recognize my functions

Post by DGM »

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?
User avatar
zorg
Party member
Posts: 3465
Joined: Thu Dec 13, 2012 2:55 pm
Location: Absurdistan, Hungary
Contact:

Re: Love2d selectively refusing to recognize my functions

Post by zorg »

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 :3True 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.
User avatar
raidho36
Party member
Posts: 2063
Joined: Mon Jun 17, 2013 12:00 pm

Re: Love2d selectively refusing to recognize my functions

Post by raidho36 »

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: 16
Joined: Tue Mar 20, 2018 6:42 am

Re: Love2d selectively refusing to recognize my functions

Post by DGM »

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.
User avatar
zorg
Party member
Posts: 3465
Joined: Thu Dec 13, 2012 2:55 pm
Location: Absurdistan, Hungary
Contact:

Re: Love2d selectively refusing to recognize my functions

Post by zorg »

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 :3True 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.
User avatar
pgimeno
Party member
Posts: 3655
Joined: Sun Oct 18, 2015 2:58 pm

Re: Love2d selectively refusing to recognize my functions

Post by pgimeno »

DGM wrote: Tue Mar 20, 2018 1:35 pmFair 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.
DGM
Prole
Posts: 16
Joined: Tue Mar 20, 2018 6:42 am

Re: Love2d selectively refusing to recognize my functions

Post by DGM »

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

Who is online

Users browsing this forum: Google [Bot], slime and 4 guests