set middleclass global + avoid global classes with stateful

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.
User avatar
SuperZazu
Citizen
Posts: 56
Joined: Sun Jun 10, 2012 2:06 pm
Location: France
Contact:

set middleclass global + avoid global classes with stateful

Post by SuperZazu » Tue Nov 25, 2014 9:52 am

Hey there :-)

I have a couple of questions concerning middleclass and stateful. I've been working for a couple of months with these two libraries in order to make a simple game template (here) (just for me, a simple base to bootstrap the development of OOP games).

1/ middleclass: In the github page, it's said it's best to write

Code: Select all

local class = require 'middleclass'
in every file in which you need the library. In the case where almost every one of my files use middleclass, wouldn't it be cleaner/better/faster to just write once the require (globally) for the whole program ?

2/ stateful: in the case where my states are not defined in the same file as the "stateful" class, do you have a solution to avoid making the "stateful" class global, which may end in a lot of global variables in a big program ?
Here is a little example of my problem :

main.lua

Code: Select all

Hero = require 'Hero' -- here, I MUST make the Hero class global to add states in an external file
require 'HeroStateWalking'

my_hero = Hero:new()
-- some stuff
Hero.lua

Code: Select all

local class = require 'lib.middleclass'
local Stateful = require 'lib.stateful'

local Hero = class('Hero'):include(Stateful)

function Hero:initialize()
  self.x = 0
  self.y = 0
end

function Hero:move(x,y)
  self.x = self.x + x
  self.y = self.y + y
end

function Hero:draw()

end
HeroStateWalking.lua

Code: Select all

local HeroStateWalking = Hero:addState('Walking')

function HeroStateWalking:draw()
  print('walking drawing')
end
Have a good day,
superzazu

User avatar
kikito
Inner party member
Posts: 3152
Joined: Sat Oct 03, 2009 5:22 pm
Location: Madrid, Spain
Contact:

Re: set middleclass global + avoid global classes with state

Post by kikito » Tue Nov 25, 2014 10:32 am

Hi there!

I am a bit ill, so I will be brief.
wouldn't it be cleaner/better/faster to just write

Code: Select all

class = require 'middleclass'
once for the whole program ? It takes some time to write it every time, and wouldn't it be a bit slower if it is called in every file ?
Think about files as you think about functions.

Functions work better when they only depend on their parameters, with no globals (these are called pure functions). From the point of view of maintainability, a function with two params is always better than a function with 1 parameter which uses a global variable inside of it. This is for several reasons; one of them is that it is easy to see the dependencies of the first by looking at its params, but not on the second; you have to read the whole function body and play "find the globals".

With files it is a bit the same thing.

By putting the requires at the top of the file, you are declaring: "this file depends on these libraries". You are making the code easier to read, since there's no need to "hunt for the globals used in the file" - they are declared at the top.

The reason it is better to use locals instead of globals is that globals can mask variables used in other files. It is also marginally faster, but that's not the point really (global vs local piles up in loop-extensive code, but most of the time the difference it makes is the same as using lighter paint to make a car go faster).
stateful: in the case where my states are not defined in the same file as the "stateful" class, do you have a solution to avoid making the "stateful" class global, which may end in a lot of global variables in a big program ?
The method :addState has a second parameter called "superState". This is a table which the state will use to "look over" methods. So, return a table, and use that table on the :addState calls:

hero.lua

Code: Select all

local class = require 'lib.middleclass'
local Stateful = require 'lib.stateful'

local Walking = require 'states.walking'

local Hero = class('Hero'):include(Stateful)

Hero:addState('Walking', Walking)

function Hero:initialize()
  self.x = 0
  self.y = 0
end

function Hero:move(x,y)
  self.x = self.x + x
  self.y = self.y + y
end

function Hero:draw()

end
states/walking.lua:

Code: Select all

local Walking = {} -- regular table

function Walking:draw()
  print('walking drawing')
end

return Walking
I have not tried this recently, but it should work.

I hope this answers your questions.

Regards,

Kikito
When I write def I mean function.

User avatar
SuperZazu
Citizen
Posts: 56
Joined: Sun Jun 10, 2012 2:06 pm
Location: France
Contact:

Re: set middleclass global + avoid global classes with state

Post by SuperZazu » Tue Nov 25, 2014 2:15 pm

Thank you for the fast answer ! I have another question though.
For my game states, I inherit the GameStateStack from another class (to have a bunch of default methods). So here's what I did:

bootstrap.lua

Code: Select all

local class = require 'lib.middleclass'
local Stateful = require 'lib.stateful'


-- we create our own Game State Stack with the States we want
local EmptyGameStateStack = require 'lib.tinyLOVEtemplate.EmptyGameStateStack' -- a simple class with a bunch of empty functions
local OwnGameStateStack = class('OwnGameStateStack', EmptyGameStateStack):include(Stateful) -- I make the gamestate

local Test = require 'game.states.test' -- load the states
local TestTwo = require 'game.states.testTwo'

OwnGameStateStack:addState('Test', Test) -- add the states
OwnGameStateStack:addState('TestTwo', TestTwo)

-- launch of the game
GameStateStack = OwnGameStateStack:new()
GameStateStack:pushState('Test') -- HERE, BUG. IF I WRITE "gotoState", EVERYTHING IS OK.

log: (bootstrap.lua:19 = last line of the file)

Code: Select all

Error: lib/stateful.lua:93: attempt to call a nil value
stack traceback:
	lib/stateful.lua:93: in function '_invokeCallback'
	lib/stateful.lua:159: in function 'pushState'
	game/bootstrap.lua:19: in main chunk

User avatar
kikito
Inner party member
Posts: 3152
Joined: Sat Oct 03, 2009 5:22 pm
Location: Madrid, Spain
Contact:

Re: set middleclass global + avoid global classes with state

Post by kikito » Tue Nov 25, 2014 7:37 pm

Thanks for reporting this. It is indeed a bug in stateful. It seems to affect states whose superstate is not the "default base one" when a callback is invoked (the callback is not found, so an error is raised). If I'm right, you will encounter that error every time you do something with the states (push, pop, exit, continue...).

I can't write a fix with tests and everything ATM. Please try replacing _invokeCallback with this in stateful.lua, that should do it:

Code: Select all

local function _invokeCallback(self, state, callbackName, ...)
  if state and state[callbackName] then state[callbackName](self, ...) end
end
When I write def I mean function.

User avatar
SuperZazu
Citizen
Posts: 56
Joined: Sun Jun 10, 2012 2:06 pm
Location: France
Contact:

Re: set middleclass global + avoid global classes with state

Post by SuperZazu » Tue Nov 25, 2014 7:45 pm

Seems to work like a charm ! Thank you :awesome:

User avatar
kikito
Inner party member
Posts: 3152
Joined: Sat Oct 03, 2009 5:22 pm
Location: Madrid, Spain
Contact:

Re: set middleclass global + avoid global classes with state

Post by kikito » Tue Nov 25, 2014 10:58 pm

Awesome.

I have added the change to stateful, as well as a small fix for a test that was suddently failing for me. I've released stateful 1.0.3 with these changes.
When I write def I mean function.

User avatar
SuperZazu
Citizen
Posts: 56
Joined: Sun Jun 10, 2012 2:06 pm
Location: France
Contact:

Re: set middleclass global + avoid global classes with state

Post by SuperZazu » Thu Nov 27, 2014 9:51 pm

Thank you for this, it is working perfectly !

But, what is the difference between the callbacks 'pushedState' and 'enteredState' ? (I understand the difference, but why two callbacks?)
And more importantly, did you omit the callback 'addedState' on purpose ? It would be great to have a state load the resources needed when the 'addState' method is called (?)

User avatar
kikito
Inner party member
Posts: 3152
Joined: Sat Oct 03, 2009 5:22 pm
Location: Madrid, Spain
Contact:

Re: set middleclass global + avoid global classes with state

Post by kikito » Fri Nov 28, 2014 12:40 am

SuperZazu wrote:what is the difference between the callbacks 'pushedState' and 'enteredState' ?
enteredState gets called when you "go to a state" (with gotoState) as well as when you "push it" (with pushState). pushedState is only called on the later case. The same happens with exitedState & poppedState.
SuperZazu wrote:And more importantly, did you omit the callback 'addedState' on purpose ? It would be great to have a state load the resources needed when the 'addState' method is called (?)
It honestly didn't even occur to me that it would be necessary. Do you need it?
When I write def I mean function.

User avatar
SuperZazu
Citizen
Posts: 56
Joined: Sun Jun 10, 2012 2:06 pm
Location: France
Contact:

Re: set middleclass global + avoid global classes with state

Post by SuperZazu » Fri Nov 28, 2014 3:23 pm

kikito wrote:
SuperZazu wrote:what is the difference between the callbacks 'pushedState' and 'enteredState' ?
enteredState gets called when you "go to a state" (with gotoState) as well as when you "push it" (with pushState). pushedState is only called on the later case. The same happens with exitedState & poppedState.
Oh thanks, I never thought of that (it didn't occur to me that I would someday need a callback when entered (and not pushed) but now that I think of it...). :-)
kikito wrote:
SuperZazu wrote:And more importantly, did you omit the callback 'addedState' on purpose ? It would be great to have a state load the resources needed when the 'addState' method is called (?)
It honestly didn't even occur to me that it would be necessary. Do you need it?
Yes.
I may be doing things the wrong way, but the states often use their own resources (quads, images, etc) and that would be great to not re-reload them everytime a push/pop is called. :-)

Have a good day :-)

User avatar
kikito
Inner party member
Posts: 3152
Joined: Sat Oct 03, 2009 5:22 pm
Location: Madrid, Spain
Contact:

Re: set middleclass global + avoid global classes with state

Post by kikito » Fri Nov 28, 2014 4:24 pm

The problem I see is that when the state is included in the class the instance still doesn't exist. What would "self" be in such "callback"? The class adding the state? That would make it different from the rest of the callbacks.
the states often use their own resources (quads, images, etc) and that would be great to not re-reload them everytime a push/pop is called. :-)
If you need some "resource mamagement logic" to "not reload resources when they are already loaded", I would move that to its own entity; your state is probably already doing too much stuff without having to do that too. First of all, consider just loading everything in advance, when the program starts, instead of "when the state is added to a class". That's probably the simplest solution.

If you want to get really fancy, you could use a "Media" class or module which loads the image the first time, but the second time it returns a cached version. But try loading everything at the start first - it is often enough.
When I write def I mean function.

Post Reply

Who is online

Users browsing this forum: Google [Bot] and 7 guests