You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
When I first wrote this library I just passed certain callbacks to all screens on the stack, but over time I realized that maybe for convenience it should forward all callbacks to all screens on the stack.
In my current project the callback handling works fine the way it currently is, so I don't really have an idea how to improve it.
Of course we could simply break the event handling if one of the screens returned false, but I'm not sure how nicely that would work in more complicated setups.
It creates a table per event which I couldn't avoid easily, but maybe you can do something like:
localfunctionpropagator (callback, level, a, b, c, d, e, f)
localstate=stack[level]
ifnotstatethenreturnendlocalpropagate=function()
returnpropagator(callback, level+1, a, b, c, d, e, f)
endreturnstate[callback](state, propagate, a, b, c, d, e, f)
end
functionScreenManager.gamepadpressed( joystick, button )
returnpropagator("gamepadpressed", 1, joystick, button)
end
Since currently love.event.poll only returns 6 arguments per event
Note that the callbacks could also be:
functionScreenManager.gamepadpressed( ... )
returnpropagator( "gamepadpressed", 1, ... )
end
I have cleaned up the propagator function, now called delegate:
----- Delegates a callback to the stack, with the appropriate propagate function-- callback is the callback name-- a, b, c, d, e, f are the event arguments-- level is the level of the stack to delegate to (defaults to the top)--localfunctiondelegate( callback, a, b, c, d, e, f, level )
level=levelor#stacklocalstate=stack[level]
ifnotstateortype( state[callback] ) ~="function" thenreturnendlocalfunctionpropagate()
returndelegate( callback, a, b, c, d, e, f, level-1 )
endreturnstate[callback]( state, propagate, a, b, c, d, e, f )
end
localMainScreen= {}
functionMainScreen.new()
localself= {}
localactive=truefunctionself:isActive()
returnactiveendfunctionself:setActive( nactiv )
active=nactivendfunctionself:draw( propagate )
propagate() --We will draw after other screens in the stack--Here we draw our stuffendfunctionself.mousepressed( propagate, x, y, button )
ifx>100andx<700andy>100andy<500then--We consume the eventelse--Since we didn't consume it we propagate itpropagate()
endendfunctionself.keypressed( propagate, k )
ifk=="space" then--We consume the eventend--We propagate anywaypropagate()
endendreturnMainScreen
Notes:
In the example I don't use Screen.lua, since the delegate function checks if the callback exists before trying to call it.
Also note that the metatable approach lets me define my own events so for example I could call ScreenManager.serverRequest( request ) and if the active state can handle it then it will be consumed otherwise nothing happens
delegate could propagate if the current state doesn't define the callback but I'm not sure if that is desirable
I like the general approach, but I'm not a fan of the table allocation in the draw function local values = { delegate( "draw" )} because it produces a ton of garbage. I'm usually not too concerned with (over) optimisation until it becomes necessary, but I want to keep ScreenManager as lightweight as possible.
I understand why you are doing it, but I'd prefer something like this:
local a, b, c = delegate( 'draw' )
Of course the user would have to know which values his functions return, but that's something he should know anyway.
The other thing I'm concerned with (though maybe it isn't a problem ... I haven't been able to test it yet) is delegate( callback, a, b, c, d, e, f, level ). This would mean we could only pass six parameters to the functions. Why not use delegate( callback, level, ... ) instead? I'm thinking about situations where you want to pass custom values through the function.
Haha :D I think it's better than creating a ton of tables.
I'll try to sit down this weekend and give your version a try. Right now I'm basically discussing just from looking at the code and a quick test I wrote today.
So maybe this is a stupid question, but what are the benefits of your delegate / propagate approach compared to my original suggestion of simply using a return value. I just like simple solutions and by default LÖVE callbacks don't have return values IIRC. That's why I'd do this:
By default the user wouldn't have to change his current screens since nil would mean the event isn't consumed. So the user could do something like this:
functionself:update( dt )
ifcoolStuffIsHappeningthen-- Do stuff-- ...-- then consume the eventreturntrueendend
Your example has a fixed order, it traverses the stack in a for loop from the top of the stack to the bottom. You cannot change the order of the stack while in the loop or that may shield undefined behaviour.
Still your example can be implemented with delegate/propagate like this:
functionself:update( propagate, dt )
ifcoolStuffHappeningthen-- Do stuffelse--We didn't consume the event so we propagatereturnpropagate()
endend
But you could also do something like this:
functionself:update( propagate, dt )
localvalue=propagate()
ifvaluethen-- Do somethingendend
This basically means that the current state updates AFTER the state underneath it, and depending on the value returned by it, it performs an action or not.
There is no way to implement this with the for loop approach without changing ScreenManager's code.
And even then, this approach allows you to change the order per-screen so the order is up to you
Activity
pablomayobre commentedon Feb 17, 2017
I also thought about this, and a way to tell the screens down the stack if someone already consumed the event would be nice.
I also added some functions to my "fork" in Positive07/lovejam-training to call functions in the first stack or all the stacks
rm-code commentedon Mar 8, 2017
In my current project the callback handling works fine the way it currently is, so I don't really have an idea how to improve it.
Of course we could simply break the event handling if one of the screens returned false, but I'm not sure how nicely that would work in more complicated setups.
On the other hand I don't want to over-complicate the library. I'm open for ideas :)
pablomayobre commentedon Mar 8, 2017
I really like this idea by @s-ol
Basically you pass a first parameter to the callback which is a function that propagates the event down the stack
pablomayobre commentedon Mar 8, 2017
I would implement it really simply like this:
Then in each callback you would do something like:
It creates a table per event which I couldn't avoid easily, but maybe you can do something like:
Since currently love.event.poll only returns 6 arguments per event
Note that the callbacks could also be:
pablomayobre commentedon Mar 9, 2017
I have cleaned up the
propagatorfunction, now calleddelegate:You could define all the callbacks by doing this:
Or alternatively:
And a usage example would be one like this:
Notes:
In the example I don't use
Screen.lua, since thedelegatefunction checks if the callback exists before trying to call it.Also note that the metatable approach lets me define my own events so for example I could call
ScreenManager.serverRequest( request )and if the active state can handle it then it will be consumed otherwise nothing happensdelegatecould propagate if the current state doesn't define the callback but I'm not sure if that is desirableYou can check the propagate branch in my fork
rm-code commentedon Mar 10, 2017
I like the general approach, but I'm not a fan of the table allocation in the draw function
local values = { delegate( "draw" )}because it produces a ton of garbage. I'm usually not too concerned with (over) optimisation until it becomes necessary, but I want to keep ScreenManager as lightweight as possible.I understand why you are doing it, but I'd prefer something like this:
local a, b, c = delegate( 'draw' )Of course the user would have to know which values his functions return, but that's something he should know anyway.
The other thing I'm concerned with (though maybe it isn't a problem ... I haven't been able to test it yet) is
delegate( callback, a, b, c, d, e, f, level ). This would mean we could only pass six parameters to the functions. Why not usedelegate( callback, level, ... )instead? I'm thinking about situations where you want to pass custom values through the function.pablomayobre commentedon Mar 10, 2017
An ugly hack for the
drawissue could beIt could work hahaha
I'm not sure that I like the idea of
ScreenManagerassuming how many values I will return from my callback.The
a, b, c, d, e, farguments are there to avoid the issue of varargs being undefined inpropagateThe other alternatively is to allocate a table like I proposed first
But I don't think you want that since you are trying to optimize the unneeded creation of tables.
So I proposed to use 6 arguments instead, which is the biggest quantity of arguments that love.event.poll can handle
Yet as I said I don't want
ScreenManagerto assume how many arguments a callback can take and that probably all callbacks should be defined like:This way I can do stuff like:
rm-code commentedon Mar 10, 2017
Haha :D I think it's better than creating a ton of tables.
I'll try to sit down this weekend and give your version a try. Right now I'm basically discussing just from looking at the code and a quick test I wrote today.
So maybe this is a stupid question, but what are the benefits of your delegate / propagate approach compared to my original suggestion of simply using a return value. I just like simple solutions and by default LÖVE callbacks don't have return values IIRC. That's why I'd do this:
By default the user wouldn't have to change his current screens since
nilwould mean the event isn't consumed. So the user could do something like this:pablomayobre commentedon Mar 10, 2017
Your example has a fixed order, it traverses the stack in a for loop from the top of the stack to the bottom. You cannot change the order of the stack while in the loop or that may shield undefined behaviour.
Still your example can be implemented with
delegate/propagatelike this:But you could also do something like this:
This basically means that the current state updates AFTER the state underneath it, and depending on the value returned by it, it performs an action or not.
There is no way to implement this with the for loop approach without changing
ScreenManager's code.And even then, this approach allows you to change the order per-screen so the order is up to you
pablomayobre commentedon Mar 19, 2017
Any new thoughts on this?
rm-code commentedon Mar 20, 2017
I see the benefits of it, but I'm not willing to merge the current implementation into ScreenManager as it produces too much garbage for my taste.
rm-code commentedon Mar 20, 2017
I'll keep this open, but for now I'll add the simple consume feature I talked about earlier:
I know this isn't the perfect solution, but it's better than what we currently have.