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"
Code: Select all
e=="q" or not _currentstate
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