stateful.lua

Showcase your libraries, tools and other projects that help your fellow love users.
User avatar
kikito
Inner party member
Posts: 3153
Joined: Sat Oct 03, 2009 5:22 pm
Location: Madrid, Spain
Contact:

Re: stateful.lua

Post by kikito »

Thanks. I will not be able to check it today, but I will try to do it tomorrow.
When I write def I mean function.
User avatar
JaquesEdrillavo
Prole
Posts: 7
Joined: Tue Apr 08, 2014 2:12 am
Location: Mexico

Re: stateful.lua

Post by JaquesEdrillavo »

Then Game can be defined as a class inside Game.lua, and have a Menu state, a Play state, and so on.

Here's how you would create a Game with a Menu state:

Code: Select all

-- game.lua
Game = class("Game"):include(Stateful)

function Game:initialize()
  self.image =  love.graphics.newImage("image.jpg")
  self:gotoState("Menu") -- start on the Menu state
end

local Menu = Game:addState("Menu")

function Menu:enteredState() -- create buttons, options, etc and store them into self
  print("entering the state menu")
end

function Menu:draw() -- draw the menu
end

function Menu:update(dt) -- update anything that needs updates
end

function Menu:exitedState() -- destroy buttons, options etc here
  print("exiting the menu state")
end
That's the basic structure. You could add another state called "Play" similar to "Menu" following this schema.

That's pretty much it. Let me know if you have any questions.
Hey!
Awesome work. Really!

Im kind of new with Löve, im struggling like you have no idea.
I've been having some issue with trying to define the Game = class("Game"):include(Stateful)
I leave it Global, but still says that class is a nil value.
I even copied exactly as you did in your explanation and still got the same error.

Do you have an idea of what I could be doing wrong?

Thank you for everything!
User avatar
Positive07
Party member
Posts: 1014
Joined: Sun Aug 12, 2012 4:34 pm
Location: Argentina

Re: stateful.lua

Post by Positive07 »

class = require "middleclass"

That should do it
for i, person in ipairs(everybody) do
[tab]if not person.obey then person:setObey(true) end
end
love.system.openURL(github.com/pablomayobre)
User avatar
kikito
Inner party member
Posts: 3153
Joined: Sat Oct 03, 2009 5:22 pm
Location: Madrid, Spain
Contact:

Re: stateful.lua

Post by kikito »

enygmata wrote:
kikito wrote:Does any of you guys have a sample *.love file showing the issue?
I attached one simple love file for testing purposes. To make the problem happen, open the menu.lua file and save it again. Lurker will reload the file and an assertion error will be thrown:
State Menu already exists on class Game
Hi there! It took me a while to get some time to test this, with Ludum Dare and Real Life getting in the way.

First of all: there was an error in main.lua:15 - it referenced 'self' instead of 'game', and this raised an error. I fixed that, and will be ignoring it for the rest of this post.

Second, the solution you posted (removing the assertion in stateful.lua) is not really solving the issue, it's masking it temporarily. You are leaving the "old state" there instead of removing it. Which has lots of unintended consequences. For example, if you remove one function from a state and the state is reloaded by lurker, the function will "still be there" when lurker reloads. It will still "mask" parent functions, even if its code doesn't exist any more. It's one of those ninja-bugs that you really don't want to have in your code. So I am not going to recommend it.

Third, not everything is lost! I found a way to make things work. It's not as pretty as I'd like, but it's something. The gist is this: you can use stateful with lurker, as long as all the states affecting one class stay in the same file as the class.

In other words, If you move the content of menu.lua to the end of game.lua and removed the `require "menu"` from main.lua, it works - you can start the game, change the menu state message to "Menu State 2", for example, and it refreshes correctly.

The reasons for this are complex. Lurker is a "file-based" thing. game.lua and menu.lua, are two files acting on the same piece of memory. So lurker gets confused - it loads menu again, but not game.lua. But if you put everything in one file, Lurker can work with it.

There might be other ways around this - some of them might need help from Lurker's author. But putting everything in one file is the simplest solution. Here's the fixed stateful-lurker.love:
stateful-lurker-fixed.love
stateful-lurker, with menu.lua moved inside game.lua
(11.2 KiB) Downloaded 122 times
Finally, I noticed that you rely on global variables to do almost everything; you define classes as globals:

Code: Select all

Game = class('Game')
...
You also declare libraries as globals in main.lua:

Code: Select all

class = require "middleclass"
lurker = require "lurker"
...
That will give you trouble later on, especially if you want to use it with a dynamic loader such as lurker. I recommend that you:
  • Require the libraries you need on each file where you need them, as local variables
  • Define classes as locals and return them at the end of the files where they are defined
In other words, your game.lua could look like this:

Code: Select all

local class = require 'class'

local Game = class('Game')

... -- (Define Game and Menu)

return Game
And your main.lua could look like this:

Code: Select all

local lurker = require 'lurker'
local Game = require 'Game'

local game
function love.load()
  game = Game:new()
end

...
I realize that this is a bit more tedious to write, but global variables play badly with reloading code. Also, your code will be more clear, by making it explicit where each library is used. You get used to it very quickly.

Here's a version of stateful-lurker with all the globals removed:
stateful-lurker-no-globals.love
stateful-lurker-fixed with globals removed
(11.22 KiB) Downloaded 127 times
I hope this helps.
Last edited by kikito on Thu May 01, 2014 10:30 am, edited 1 time in total.
When I write def I mean function.
User avatar
kikito
Inner party member
Posts: 3153
Joined: Sat Oct 03, 2009 5:22 pm
Location: Madrid, Spain
Contact:

Re: stateful.lua

Post by kikito »

(sorry about double posting, I'm answering to two different posts here)
Positive07 wrote:class = require "middleclass"

That should do it
I recommend doing this instead:

Code: Select all

local class = require 'middleclass'
local Stateful = require 'stateful'
at the beginning of game.lua.

In general, use local xxx = require 'xxx' on all the files that need to use xxx. It's a bit longer to write, but it pays off. The end of my previous post explains a bit more about this.
When I write def I mean function.
User avatar
Positive07
Party member
Posts: 1014
Joined: Sun Aug 12, 2012 4:34 pm
Location: Argentina

Re: stateful.lua

Post by Positive07 »

kikito wrote: I recommend doing this instead:
Yeah sure! but I think that this is not done here or here, making things confusing. It would be great if you changed:
require "middleclass" to local class = require "middleclass"
for i, person in ipairs(everybody) do
[tab]if not person.obey then person:setObey(true) end
end
love.system.openURL(github.com/pablomayobre)
User avatar
kikito
Inner party member
Posts: 3153
Joined: Sat Oct 03, 2009 5:22 pm
Location: Madrid, Spain
Contact:

Re: stateful.lua

Post by kikito »

Positive07 wrote: Yeah sure! but I think that this is not done here or here, making things confusing. It would be great if you changed:
require "middleclass" to local class = require "middleclass"
You are right, of course. I've updated those, thanks!
When I write def I mean function.
User avatar
CaptainMaelstrom
Party member
Posts: 161
Joined: Sat Jan 05, 2013 10:38 pm

Re: stateful.lua

Post by CaptainMaelstrom »

Hey kikito. I've been playing around with stateful lately and I really like it.

I do have a couple suggestions though. I ran some tests and noticed that you can enter states without leaving them, at least, the "enteredState" function executes for a specific state if you try to "gotoState" into it without having left it first. Example:

Code: Select all

function Troop:initialize() end
function Visible:enteredState() str = str .. '3' end
function love.load()
	troop = Troop()
	str = '1'
end
function love.draw() lg.print(str,20,20) end
function love.keypressed(key)
	if key=='f' then troop:gotoState('Visible') end
	if key=='a' then troop:gotoState(nil) end
end
Running that code and pressing 'f' twice yields a string '133'. Is this intended?

A bigger problem for me is that

Code: Select all

function Troop:enteredState() str = str .. '2' end
adding that code and switching to the nil state doesn't add '2' to the string. Is there anyway to get a callback function to trigger when a troop instance goes to the nil state?
rxi
Prole
Posts: 47
Joined: Sun Mar 02, 2014 10:22 pm

Re: stateful.lua

Post by rxi »

kikito wrote:That will give you trouble later on, especially if you want to use it with a dynamic loader such as lurker
A bit of a slow response, but just wanted to clarify on this point: Unless you meant this was due to the combination of Lurker and Stateful, Lurker should work quite happily with modules which are set to global variables. I wrote it in a way such that it rebuilds all the corresponding module tables in place when it reloads a module, rather than recreating the module. This was so that things like metatables are also immediately effected without objects needing to be reconstructed, and is also the reason for another point you mentioned:
kikito wrote: if you remove one function from a state and the state is reloaded by lurker, the function will "still be there" when lurker reloads.
I won't go into too much detail, but many of the decisions were made to try and make lurker work really well for quick, small, iterative changes -- for example, if you want to perfectly tweak the gravity in a game and see the effects of this immediately. Due to how it works it does mean you either need to explicitly set a table's deleted function to nil or simply restart the game for changes where you delete a function.
User avatar
kikito
Inner party member
Posts: 3153
Joined: Sat Oct 03, 2009 5:22 pm
Location: Madrid, Spain
Contact:

Re: stateful.lua

Post by kikito »

CaptainMaelstrom wrote: Running that code and pressing 'f' twice yields a string '133'. Is this intended?
Yes - reason below.
CaptainMaelstrom wrote: A bigger problem for me is that

Code: Select all

function Troop:enteredState() str = str .. '2' end
adding that code and switching to the nil state doesn't add '2' to the string. Is there anyway to get a callback function to trigger when a troop instance goes to the nil state?
'nil' is not a state. In consequence, it does not have entered/exited callbacks, nor it does occupy a place in the internal state stack.

If you want your objects to always be in a state, you have to provide one for them - call it Default or Init or Idle or something. You can probably make your instances go to that state by default in their constructor:

Code: Select all

function Troop:initialize()
  self:gotoState('Default')
end
function Default:enteredState()
  str = str .. '1'
end
rxi wrote:...
Thank you for your clarifications.
When I write def I mean function.
Post Reply

Who is online

Users browsing this forum: Bing [Bot], Google [Bot] and 3 guests