Page 1 of 2

Game state management with hierarchy, looking for comments

Posted: Thu Dec 31, 2009 5:01 pm
by giniu
Hello,

lately I made small lib for management of game states, it's inspired by Luis Valente, Aura Conci, and Bruno Feij article "An Architecture for Game State Management based on State Hierarchies", but somewhat modified. I decided to share the code under CC, so it can get some more users, bug reports & comments - I would be very very happy to hear what you think about it. Especially that I'm not very experienced with Lua, this lib is actually port of what I did in C++ for one of my other projects - so if something can be made more Lua way, or I missed something about GC etc, I'll be really happy to fix all that :)

If someone haven't used such states before, I'll describe it, though I think it's not needed to describe it like that. Anyway - the main idea is to have states, each state have default callbacks just like current Love callbacks - that is update, draw, keypressed, keyreleased, mousepressed, mousereleased, joystickpressed and joystickreleased. One of states is marked as main state and it's first state that is run. Active states are kept on stack, all standard callbacks are passed to top-most active state. There are additional state transitions callbacks, i.e. four: init, enter, leave and exit. init callback is called when we first enter state. When we leave state for some time but will be back - leave is called. enter is called when we come back to state we left, and exit is called when we will no longer return to state. State transitions are called by pushState (calls leave+init), popState (calls exit+enter) and changeState (calls exit+init) - first two are pretty obvious - they push and pop to/from state stack - change state does pop and push at once - the difference is that we don't enter and leave state that's second on stack. One state can be only once on stack in given amount of time. This is the basics without hierarchy - now hierarchy comes into action - each state can have parent group, and each parent group can have parent group. Default parent is root, called "" - so each state is connected with it - it can be viewed as tree where states can be leaves. During state transition, we also call init/enter/leave/exit transitions for groups. The main idea is that state gets reference to data of all groups it belongs to, so we can have "Menu" groups that have common resources loaded for all states under "Menu". We don't loose performance, because state transitions are usually rare, and inside loop when processing event callbacks, we loose only one check for nil per event, i.e.

Code: Select all

e=="q"
changes into

Code: Select all

e=="q" or not _currentstate
so it should be pretty fast in most cases.

Now some details.

The lib requires love 0.6. All loading is made like love 0.6 config file, more exactly file "name.lua" should have one function "name" taking one table and inputting functions/data into it. For speed reasons love.run is slightly rewritten, and extended back by restart feature - that makes sense in state world, as far as user does all the stuff he is supposed to in init/exit callbacks - and it makes it easier to test prototypes I think.

1) hierarchy building - should be done inside love.load
a) globalData(name) - loads "states/name.lua", callbacks for root group - don't need enter and leave callbacks
b) loadGroup(name, parent) - loads "parent-path/name/name.lua", it's transition callbacks for given group. parent-path for "" parent is "states", "parent-path" is "parent-path/name" i.e. each group have it's own directory in states directory
c) loadState(name, parent) - loads "parent-path/name.lua" or if not found "parent-path/name/name.lua", it's transitions and default callbacks
d) loadMainState(name, parent) - same as loadState, but marks state as parent
2) state transitions
a) pushState(name, ...) - pushes new state to stack, additional parameters are passed to transition callbacks
b) popState(...) - pops state from stack, additional parameters are passed to transition callbacks
c) changeState(name, ...) - pops and pushes state to stack, additional parameters are passed to transition callbacks
3) app level functions
a) requestQuit() - pops all states and ends application
b) requestRestart() - pops all states and pushes main state again
4) group transition callbacks
a) init(data) - called when first entering group, receives empty table where it can put it's data
b) leave(data) - called when temporary leaving group, receives it's current data so it can compress it to save some space
c) enter(data) - called when re-entering group, receives it's current (for example compressed) data so it can for example unpack it
d) exit(data) - called when exiting group, don't have to modify the data as it's automatically emptied - but allows to unload images, close files or devices
5) state transition callbacks
a) init(data, ...) - called when first entering state - data is table build like: {[""]=globaldata, ["parent1"]={...}, ["parent2"]={...}, ..., ["nearest parent"]={...}, ["state name itself"]={...}}. Additional arguments are passed from state transition functions as is
b,c,d) leave(data, ...), enter(data, ...), exit(data, ...) - just like group transitions, just argument format is like described for state init function
6) state event callbacks - update, draw, keypressed, keyreleased, mousepressed, mousereleased, joystickpressed and joystickreleased - just like standard love callbacks, no need to describe them I think

I'm attaching the file, it's not tested well enough - actually group code almost wasn't tested :( - but still it looks like it should work, and the case when not using groups is quite well tested ;) So after this lengthy message - if anyone managed to read trough it - what do you think? As I said, I'm open for any comments!

edits:

v 0.02 - made global data handling same as other groups handling (only init and exit have meaning for root group)
v 0.03 - fixed small issue with using popState instead of requestQuit in main state, also changed loaded function execution

Re: Game state management with hierarchy, looking for comments

Posted: Wed Jan 06, 2010 10:34 pm
by kikito
Hi giniu,

I think the implementation follows the article. It is somewhat big. Some navigational help would be useful (more comments, separating the functions in groups, that sort of thing). Have you considered using Lua's metamethods for implementing the "classes", like in C++? There are several ways to do this, my own lib being one of the possible ways.

Concerning the reference article, I don't understand why they choose to split the objects into "States" and "StateGroups" (I confess, I just skip through some parts - just back from holidays and a lot too read :)) I think it would be simpler just to have composite states (and simple ways to add or copy state references).

I don't like that every state resides on an specifically-named lua file. I'd rather have all the Game States on a single lua file (game.lua or something similar).

I've implemented a lib (MindState) that combined with my OO lib (MiddleClass) can be used to implement more or less what you want (see this post answer)

..But it also does more stuff. Consider this: A mine can be set up, defused or exploding. A powerup is iddle or being collected... See the pattern? Your "entities" will benefit from being "state-oriented". No need to limit yourself to the Game object only!

Couple this with inheritance & classes. An enemy could have the states idle, alerted & dieing. An enemy soldier (subclass of enemy) could have all the states of enemy, and also shooting and looking for cover...

And then there's the crazy stuff: states that inherit from other states, states that contain other states (any level of containment), and mixins that define states, but already written.

You may want to give a look at what I'm doing with PÄSSION. Specifically, the latest code example already shows a stateful actor (only two states though: air and ground)

I'm afraid I don't understand what you do on the loadstring part. It certainly looks convoluted. Care to explain it a bit more?

Re: Game state management with hierarchy, looking for comments

Posted: Thu Jan 07, 2010 10:38 am
by giniu
Hi Kikito, thanks for comments! :)
I think the implementation follows the article. It is somewhat big. Some navigational help would be useful (more comments, separating the functions in groups, that sort of thing). Have you considered using Lua's metamethods for implementing the "classes", like in C++? There are several ways to do this, my own lib being one of the possible ways.
There is one difference, in article groups are performing only grouping (there is no init/enter/leave/exit called for groups there), also they limit transitions from state to states that are direct children of some parrent state. In my version there is init/enter/leave/exit called for groups, all the way up and down to common parent, also this version don't limit state transitions - one can freely travel trough hierarchy. About classes, I looked at it, but - inheritance here isn't needed I think so simple table with callbacks seemed enough to me, as described just below. About comments and notes - sure it would be needed, but I didn't had time to write it yet :(
Concerning the reference article, I don't understand why they choose to split the objects into "States" and "StateGroups" (I confess, I just skip through some parts - just back from holidays and a lot too read :)) I think it would be simpler just to have composite states (and simple ways to add or copy state references).

I don't like that every state resides on an specifically-named lua file. I'd rather have all the Game States on a single lua file (game.lua or something similar).

I've implemented a lib (MindState) that combined with my OO lib (MiddleClass) can be used to implement more or less what you want (see this post answer)

..But it also does more stuff. Consider this: A mine can be set up, defused or exploding. A powerup is iddle or being collected... See the pattern? Your "entities" will benefit from being "state-oriented". No need to limit yourself to the Game object only!

Couple this with inheritance & classes. An enemy could have the states idle, alerted & dieing. An enemy soldier (subclass of enemy) could have all the states of enemy, and also shooting and looking for cover...

And then there's the crazy stuff: states that inherit from other states, states that contain other states (any level of containment), and mixins that define states, but already written.
hmm... I don't know I communicated the idea well - it is mostly for resources grouping and gameplay types separation, entities will be there also, but it's I think totally different thing. Files are separated because usually there would be few large states. For example in prototype I work in free time now, for now there is group "menu" and "navigation map". There is state "main menu" under "menu", "galaxy view" under navigation map, and "system view" under same parent. That's all - because each state can look like totally different game, and is actually quite big because of different kind of events to process, different files give nice separation. Inside given state, there will be one file with all needed entities, processing information, etc - but this state level is only top most hierarchy. I.e. on click on sun X I'm pushing state "system view" using pushState("systemview", X) and don't care more about how it works, I'm not tempted to access other data that is owned by "galaxy state", other than those from "navigation map" group. Also when I leave subtree permanently (by pop or change state), I can unload the resources allocated in groups, close files access, etc, but I keep them loaded as long as the subtree is active.

The idea about state containing, inheriting, or mixing-in other states is also good one, but I think it makes the thing much more complicated , especially that state transitions happen rarely, between levels, when going to menu, etc. I totally agree that it's good solution for entities, but my primary idea was that this would be inside those states.
You may want to give a look at what I'm doing with PÄSSION. Specifically, the latest code example already shows a stateful actor (only two states though: air and ground)
Oh yes, I looked at it already, even before starting this, but as I understood it's about actor/entity mostly? In my case, states groups different gameplay/functional parts of game, each would come with it's own world of entities and way the world is processed, or just with the way it's processed and world shared trough group (for example the type of situation it can be useful I think, lots of jRPG use one view for navigation and other, totally different for fights. Sometimes it's even 2D vs 3D. Thanks to those states one can separate those two different views in clean way, and just communicate with simple message - one side what and where to fight, and with result in the other side. Thanks to groups some data can be shared, like main character, equipment data, etc. It's true it can be done with checks, but then the additional cost is with every event and conditional branch quickly, while in stated system the cost is almost only during transitions. Other solution would be world substituting, but that's exactly same thing as those states here just in one file, so you have to be careful about it when it gets big)
I'm afraid I don't understand what you do on the loadstring part. It certainly looks convoluted. Care to explain it a bit more?
Sure, I keep the callbacks in files that look like love.conf, so I have for example:

Code: Select all

function statename(t)
function t.init(...)
...
end
...
end
I want to load the file statename.lua and execute statename(states['statename'].callbacks), after setting states['statename'].callbacks={}, so the callbacks get inserted into table states['statename'].callbacks. The problem is, that states is defined local to do..end block in file, to limit access from possible addons, player made extension wouldn't be able to access any states other than itself and from all groups that its child for. I use require to load the file, then I loadstring to execute given function name that was just loaded. The problem is that loadstring works always (as I read) in global scope, so if I give it element of states directly, I get that there is no such animal. That's why I create local copy of global variable (just in case it was used by something else), use global to read it, set the states to that global variable, and then set the global variable to what it was (just in case). That's probably not best way, but best one I could think of. Keep in mind it's like my first project in lua and I'm learning the language as I go, so I'm open for all comments :)

Re: Game state management with hierarchy, looking for comments

Posted: Thu Jan 07, 2010 2:13 pm
by kikito
Oh yes, I looked at it already, even before starting this, but as I understood it's about actor/entity mostly?
You understood correctly. This doesn't mean that the game state is left out, though - throgh MindState. I encourage you again to check this other post answer, which illustrates how to model a Game controller.
Files are separated because usually there would be few large states.
What I didn't like is being forced to have a concrete file/directory structure. I'd rather have a just a global variable "states", and then being able to add states to it on 1, 2 or 10 .lua files (in your example, menu.lua, combat.lua & map.lua would be fine, but in other cases with smaller states a game.lua with two states).
I want to load the file statename.lua and execute statename(states['statename'].callbacks), after setting states['statename'].callbacks={}, so the callbacks get inserted into table states['statename'].callbacks.
Can't you just load the proper callbacks on the do... end block, along with the local variables?

Code: Select all

do
... (local variables)
states['statename'].callbacks = { ... } -- <-- here
end

Re: Game state management with hierarchy, looking for comments

Posted: Thu Jan 07, 2010 2:40 pm
by giniu
kikito wrote:Can't you just load the proper callbacks on the do... end block, along with the local variables?

Code: Select all

do
... (local variables)
states['statename'].callbacks = { ... } -- <-- here
end
hmm, but if that would be put in separate file (where I want the callbacks to be defined), it would be other do..end block, so different environment, right? while states is local for block inside states.lua, it would require to include all the callbacks for all states and groups inside same do..end block in states.lua, so no separation at all, or making states global, not local - but I wouldn't like to - unless states, that is local to do..end block in states.lua, would be visible in do..end block in other do..end block in different file? Sorry if that should be obvious

Re: Game state management with hierarchy, looking for comments

Posted: Thu Jan 07, 2010 2:50 pm
by bartbes
I'd love to help with the loadstring, there should be a way out of it, I understand it's all scope, but that's about all, it's one big puzzle, care to explain some more?

Re: Game state management with hierarchy, looking for comments

Posted: Thu Jan 07, 2010 3:11 pm
by giniu
bartbes wrote:I'd love to help with the loadstring, there should be a way out of it, I understand it's all scope, but that's about all, it's one big puzzle, care to explain some more?
Hi Bartbes, thanks for interest - I would try to describe the problem I have a little better, on example. So, here's some shorter version of code, let's say I have function somefun in global scope, loaded from file, let's say:

Code: Select all

function somefun(t)
  t.status="ok"
end
the function name is passed as argument, so I have to execute it trough loadstring afaik. If I'd like to input it to global var, I would do:

Code: Select all

result = {}
assert(loadstring("somefun(result)"))()
print(result.status)
but table I want to input the result in is local to some do..end block. I'd like to do something like

Code: Select all

do
  local localvar={}
  assert(loadstring("somefun(localvar)"))()
  print(localvar.status)
end
but as loadstring works on global scope, it says "attempt to index local 't' (a nil value)" in function somefun, what is obvious because it cannot see localvar from global scope. Only thing I came up with is:

Code: Select all

do
  local localvar 
  local temp = _globalvar
  _globalvar = {}
  assert(loadstring("somefun(_globalvar)"))()
  localvar, _globalvar, temp = _globalvar, temp, nil
  print(localvar.status)
end
(doing the shuffle with temp in case name of _globalvar is used by something else, don't know if I'm being too careful here, but better safe than sorry I think, it's used only once in love.load so shouldn't be big overhead).

Re: Game state management with hierarchy, looking for comments

Posted: Thu Jan 07, 2010 3:16 pm
by giniu
kikito wrote:You understood correctly. This doesn't mean that the game state is left out, though - throgh MindState. I encourage you again to check this other post answer, which illustrates how to model a Game controller.
I understand now what you was suggesting. I will check out that post. Thanks

Re: Game state management with hierarchy, looking for comments

Posted: Thu Jan 07, 2010 3:22 pm
by kalle2990
Since loadstring returns a function, you can pass arguments to it in (). Arguments are passed as ... (unknown number of arguments) which can be put in a table with table = {...}. If you pass localvar as a lonely argument you can access it with table[1]. Here's an example on how to do it:

Code: Select all

do
  local localvar={}
  loadstring("a = {...} somefun(a[1])")(localvar)
  print(localvar.status)
end

Re: Game state management with hierarchy, looking for comments

Posted: Thu Jan 07, 2010 3:24 pm
by bartbes
I think you need _G:

Code: Select all

local localvar = {}
_G["somefun"](localval)
print(localvar.status)
should work.

@kalle2990 (posted while I was posting)
This might work too, seems to make more sense:

Code: Select all

local localvar = {}
loadstring("return somefun")(localvar)
print(localvar.status)
EDIT: Everywhere I take it that you use a variable containing a string, instead of the constant "somefun" (because else this would all be pretty useless)