Page 1 of 1

Yet another state system

Posted: Sat Jul 02, 2016 4:10 am
by HDPLocust
Here simple state-system, working like average state automaton.
States can be required from files, returns a table with the methods.
Each state has its own local field, which does not affect the global variables and other states.
Example:

Code: Select all

state = require 'gamestate'

menu = {}
function menu:load(x, y)	--method calls every gamestate:set('statename', ...), can be called with any arguments
	--'self' is local namespase for that state
	self.x = x or 10
	self.y = y or 20
end

function menu:unload()	--method is called when this state is replaced by another, using gamestate:set('statename', ...).
	self.x, self.y = nil, nil
end

function menu:keypressed(key)
	if key == 'return' then state:set('game') end
end

function menu:draw()
	love.graphics.print('state: menu', self.x, self.y)
end

game = {}
function game:load(...)
	self.x = 10
	self.y = 20
end

function game:keypressed(key)
	if key == 'return' then state:set('menu', math.random(300), math.random(200)) end
end

function game:draw()
	love.graphics.print('state: game', self.x, self.y)
end

state:new('menu', menu)
state:new('game', game)
state:set('menu')

--if love[callbackname] is defined, is required to use state:[callbackname] method.
function love.keypressed(key)
	if key == 'escape' then love.event.quit() end
	state:keypressed(key)
end
Full list of callbacks here: https://love2d.org/wiki/love.

Easy example attached.
Upd: autotracker handlers.

Re: Yet another state system

Posted: Sat Jul 02, 2016 6:51 am
by HDPLocust
I use it for easy separate states to list of files and control every state without using global strings.

Next features in development - stack of states and multiple states in one time.

Re: Yet another state system

Posted: Sat Jul 02, 2016 2:09 pm
by cval
The thing that kinda worries me with libraries and helpers that are appearing lately and that rely on, process or affect engine callbacks in any way, is that when people implement "keypressed" callback (for example), they sometimes forget about the fact that this function actually accepts and passes three arguments, according to wiki:

Code: Select all

love.keypressed( key, scancode, isrepeat )
"Scancode" appeared in 0.10 and whereas it's not really mandatory to write your library functions exactly like that (and if you aim for compatibility with older versions - not necessary at all), people who use it later in their project may experience bugs that sometimes aren't easy to track. You know, because their code may expect those omitted arguments and process it somehow to change state of their program flow.

Just wanted to point that out (=
Kinda personal preference actually, but it cost me a few hours of headache once just because i had to change similar piece of code in my project for it to work as intended.

Re: Yet another state system

Posted: Sat Jul 02, 2016 2:16 pm
by zorg
Calling functions the following way will ensure that a lib designer has no responsibility for any changes in passed arguments:

Code: Select all

a_function(...)
-- example:
function love.keypressed(...)
    currentState.keypressed(...)
end
-- where currentState has a function like this, though param names don't really matter all that much:
state.keypressed(key, scancode, isrepeat)
Granted this won't work if the function is not defined in the state, but that's another bag of tricks to deal with :3
(currentState.keypressed and currentState.keypressed(...) is one solution, but one could or it with an empty function too)
i do prefer hump gamestates myself though.

Re: Yet another state system

Posted: Sat Jul 02, 2016 2:48 pm
by HDPLocust
zorg wrote:Granted this won't work if the function is not defined in the state, but that's another bag of tricks to deal with :3
(currentState.keypressed and currentState.keypressed(...) is one solution, but one could or it with an empty function too)
cval wrote: "Scancode" appeared in 0.10 and whereas it's not really mandatory to write your library functions exactly like that (and if you aim for compatibility with older versions - not necessary at all), people who use it later in their project may experience bugs that sometimes aren't easy to track. You know, because their code may expect those omitted arguments and process it somehow to change state of their program flow.
I use function generation from list of callbacknames, and this tricks too.
Any callback called with multiple arguments, passes through "...", like original callbacks.

Code: Select all

--This simplifies the addition of new callbacks with a totally any arguments.
local callbacks = {
'keypressed', 'keyreleased', 'textinput', 'textedited',
'mousepressed', 'mousereleased', 'mousefocus', 'mousemoved', 'wheelmoved', 
'touchpressed', 'touchreleased', 'touchmoved', 
'directorydropped', 'filedropped', 
'quit', 'lowmemory', 
'focus', 'visible', 
'threaderror', 'errhand',
'update', 'draw',
'gamepadaxis', 'gamepadpressed', 'gamepadreleased', 
'joystickadded', 'joystickaxis', 'joystickhat',
'joystickpressed', 'joystickreleased', 'joystickremoved'
}
--Self and additional love callback initialisation
--(load/update/draw/keypressed/keyreleased etc)
function states:init()
	for _, k in ipairs(callbacks) do
		--initialisation self methods like self:update(dt)
		self[k] = function(self, ...)
			if self.current[k] and type(self.current[k]) == 'function' then
				--passed "self" and any number of arguments, with any names and etc.
				self.current[k](self.current, ...)
			end
		end
		--optional addition methods for love, if not defined
		if not love[k] then
			love[k] = function(...)
				self[k](self, ...)
			end
		end
	end
	return self
end
function anystate:keypressed(key, scancode, isRepeat) {code} end - works fine too.
In love 0.10. Indeed, in another version, they will not be passed arguments "scancode" and "isrepeat".
Heh.

I can't say that "the designer no responsibility".
Rather, it is the possibility of using the library on all versions of love.


Yea, callbacknames forbidden for use:

Code: Select all

--Function returns new state like object
local function state(t)
	return setmetatable({}, {
		--table with methods
		__index = t,
		--forbidding reserved names 
		__newindex = function(self, key, value)
				--checking if key is not named like any callback in list
				if not isReserved(key) then
					rawset(self, key, value)
				else
					error('Trying to modify reserved callback "'..key..'"', 2)
				end 
			end 
		}
	)
end
I'm sorry, I do not use github, and therefore the source code on pastebin.
Little comments included.
http://pastebin.com/9rgH4BAj

Re: Yet another state system

Posted: Sat Jul 02, 2016 3:13 pm
by cval
If you really want to cover all callbacks (like, all of them that are defined in love.handlers) you can fill your callback names table with following code on table init. (i.e. you don't need to fill table manually)

Code: Select all

local i = 1
	for k,v in pairs(love.handlers) do
		print(k,v) -- this one just prints them
		callbacks[i] = tostring(k)
		i = i+1
	end
This way your lib will automatically track if there is a new callback/handler in upcoming updates.

Re: Yet another state system

Posted: Sat Jul 02, 2016 3:22 pm
by HDPLocust
cval wrote:If you really want to cover all callbacks (like, all of them that are defined in love.handlers) you can fill your callback names table with following code on table init.

Code: Select all

for k,v in pairs(love.handlers) do
	print(k,v) -- this one just prints them
	table.insert(callbacks, tostring(k))
end
This way your lib will automatically track if there is a new callback/handler in upcoming updates.
Wow, nice!
I use it, thanks :3

Love handler no update/draw callbacks,
but they just can be added manually, and the rest of the track automatically.

Re: Yet another state system

Posted: Sat Jul 02, 2016 4:01 pm
by cval
HDPLocust wrote:
Love handler no update/draw callbacks,
It is because of how love.run works by default, you can see that if update/draw it is defined by user, it will be called, otherwise it is skipped in run loop.

Re: Yet another state system

Posted: Sat Jul 02, 2016 4:06 pm
by HDPLocust
cval wrote:
HDPLocust wrote:
Love handler no update/draw callbacks,
It is because of how love.run works by default, you can see that if update/draw it is defined by user, it will be called, otherwise it is skipped in run loop.
Every user i know, uses update and draw :3
Is no require to uses love.graphics.present and structuring code.
Hm, restrictions.

Re: Yet another state system

Posted: Thu May 09, 2019 11:37 am
by HDPLocust
So. Now it have state-stack with overriding. I need to make the doc.