What will the GameMsg type look like? 2016-08-10 18:59 GMT+02:00 OvermindDL1 <[email protected]>:
> > If there are alternatives *with currently available functions* to my > proposal, very interested to hear it in this thread. > > That is why I looked at more options and started thinking about how it > could also solve other filtering issues that I have, so a singular thing > that could solve it all would be nice. :-) > > > Given code from the dontfall game, the current top-post suggestion was to > change subscriptions to be: > ```elm > > subscriptions : GameData -> Sub GameMsgsubscriptions d = > Sub.batch > ([ Keyboard.downsSelectively (\c -> if Char.fromCode c == 'P' then > Just PauseToogle else Nothing) ] ++ > if d.state == Running then > [ AnimationFrame.diffs Tick > , Keyboard.downsSelectively (\c -> if Char.fromCode c == ' ' > then Just JumpDown else Nothing) > , Keyboard.upsSelectively (\c -> if Char.fromCode c == ' ' > then Just JumpUp else Nothing) > ] > else > []) > > ``` > > In the thing I proposed that can handle generic message filtering (not > just subscription) would turn the above back into a fairly normal and > readable style of: > ```elm > > subscriptions : GameData -> Sub GameMsgsubscriptions d = > Sub.batch > [ Keyboard.downs HandleKeyboardDown > , Keyboard.ups HandleKeyboardUps > , if d.state == Running then AnimationFrame.diffs Tick else Sub.none > ] > > ``` > And a new callback (added in the main where init/update/etc are, in my > package is called 'filters') would be something like this, this is a very > simple example and `filters` can do a lot more, but for simple keyboard > filtering: > ```elm > > filters : Msg -> Model -> ( Msg, States Model Msg )filters msg model = > if model.state == Running then > case msg of > HandleKeyboardDown c -> > case Char.fromCode c -> > 'P' -> ( PauseToggle, States.enableAll ) > ' ' -> ( JumpDown, States.enableAll ) > _ -> ( msg, States.disableAll ) > HandleKeyboardUp c -> > case Char.fromCode c -> > ' ' -> ( JumpUp, States.enableAll ) > _ -> ( msg, States.disableAll ) > _ -> ( msg, States.enableAll ) > else > ( msg, States.enableAll ) > > ``` > You could also delegate to other TEA-style modules. You could filter out > the Tick here instead of subscribing and unsubscribing (I like a simple > 'subscriptions' callback). Anything with `States.disableAll` will disable > all callbacks for that msg, so update will not be called, subscriptions > will not be called, and view will not be called (it returns the cached > values from last time so an exact match test of === in javascript shows > they are identical and thus Elm will do nothing). > > With the above `filters` you will not ever get > HandleKeyboardDown/HandleKeyboardUp > in your `update` at all, ever, it is either translated to another message > or entirely canceled. Adding more message conversions is as simple as > adding another case, so you could add, say a shooting state with like: > ```elm > > filters : Msg -> Model -> ( Msg, States Model Msg )filters msg model = > if model.state == Running then > case msg of > HandleKeyboardDown c -> > case Char.fromCode c -> > 'P' -> ( PauseToggle, States.enableAll ) > ' ' -> ( JumpDown, States.enableAll ) > 'z' -> ( StartShooting, States.enableAll ) > _ -> ( msg, States.disableAll ) > HandleKeyboardUp c -> > case Char.fromCode c -> > ' ' -> ( JumpUp, States.enableAll ) > 'z' -> ( StopShooting, States.enableAll ) > _ -> ( msg, States.disableAll ) > _ -> ( msg, States.enableAll ) > else > ( msg, States.enableAll ) > > ``` > > Just as simple as adding a readable case to each the down and up to send > the two StartShooting/StopShooting messages. You could easily pull values > from the model if the user can remap their keys and just do if's instead > too. > > But yes, the filtering of messages is done in one and only area, and if > you filter everything well then your update does not even need to become a > mess of nested case's for the various states and such but you know that you > will only get a message when it is actually valid for your state, so you > know that you will not get a, say, `JumpUp` message unless you are in the > `Running` state, so you do not even need to check that. Your operations > become simple and direct. > > > On Wednesday, August 10, 2016 at 10:27:42 AM UTC-6, Janis Voigtländer > wrote: >> >> Generally, Nick is right, I primarily want to hear feedback about the >> specific API proposal I made. >> >> However, I wrote the other day that "If there are alternatives *with >> currently available functions* to my proposal, very interested to hear >> it in this thread." >> >> So since Overmind seems to since have released some package/functions >> that are now available, it could be on-topic to see how/whether these do >> address points I try to address with the API proposal. Hence my questions >> (or "challenge" if you wish) in the previous message. >> >> >> 2016-08-10 18:13 GMT+02:00 Nick H <[email protected]>: >> >>> That is not what this discussion is about. Janis proposed a change to >>> the Keyboard API. The reason he started this thread was to get our thoughts >>> on his proposal. >>> >>> On Wed, Aug 10, 2016 at 8:37 AM, OvermindDL1 <[email protected]> wrote: >>> >>>> How is it off-topic considering its whole purpose is to handle this >>>> exact kind of issue? The discussion is about filtering messages, >>>> potentially translating them to a a better message that is for a specific >>>> purpose without needing to worry about checking for valid input in update >>>> area. It is handled generically. >>>> >>>> >>>> On Wednesday, August 10, 2016 at 8:44:12 AM UTC-6, Nick H wrote: >>>>> >>>>> Overmind, we can all see the thread that you started. It is still >>>>> off-topic for you to bring it up here. >>>>> >>>>> On Wed, Aug 10, 2016 at 7:40 AM, OvermindDL1 <[email protected]> >>>>> wrote: >>>>> >>>>>> Well for what it is worth I made a library that can filter messages >>>>>> before they are handled, so you could, for example, turn a keypress of >>>>>> 'w' >>>>>> into a message of 'GoForward' and 's' into 'GoBackward' or whatever. And >>>>>> if you cancel a message then it becomes entirely unhandled and you will >>>>>> never even see it appear in the event loop (update/subscription/view). >>>>>> If >>>>>> your convert a message then you never see the original, only your >>>>>> converted >>>>>> one. I am curious if anyone wants to test it with these issues as I >>>>>> think >>>>>> it would work very well as a, for example, key remapper. It is a >>>>>> nice-central place to put key mapping (since it has a model you could >>>>>> even >>>>>> setup a dynamic key mapper based on user settings with ease). Cleans up >>>>>> code in update as since you can expect to only receive valid messages if >>>>>> you filter them here first, vastly cleans up code there to only the >>>>>> actual >>>>>> work. The package is up at the elm package website (ProgramEx I think I >>>>>> named it). Still very in testing but it seems to be working utterly >>>>>> perfect in one of my big apps and has already cleaned up a lot of code. >>>>>> Here is how the filter code looks (this one 'delegates' to two different >>>>>> TEA-form libraries their messages and alters a Scroll event just like >>>>>> you'd >>>>>> expect to do with key remapping): >>>>>> >>>>>> ```elm >>>>>> >>>>>> filters : Msg -> Model -> ( Msg, States Model Msg )filters msg model = >>>>>> let >>>>>> log = >>>>>> debug_log (model.programFlags.debug_log |> Maybe.withDefault >>>>>> False) "filters" ( msg, model ) >>>>>> in >>>>>> case msg of >>>>>> Helpers helpersMsg -> >>>>>> ( msg >>>>>> , States.enableAll |> States.delegate (helpers_delegate >>>>>> Helpers helpersMsg) >>>>>> ) >>>>>> >>>>>> Mdl mdlMsg -> >>>>>> ( msg >>>>>> , States.enableAll >>>>>> |> States.delegate >>>>>> { key = "Mdl" >>>>>> , update = Just (\_ o -> Material.update mdlMsg >>>>>> o) >>>>>> , subscriptions = Just (\o -> >>>>>> Material.subscriptions Mdl o) >>>>>> } >>>>>> ) >>>>>> >>>>>> >>>>>> MesgList_Scroll scrolled -> >>>>>> case model.loc of >>>>>> RoomLocation rid _ -> >>>>>> let >>>>>> doLoad : Bool >>>>>> doLoad = >>>>>> (model.firstMessageReached == False) >>>>>> && (scrolled.pos < 16) >>>>>> && (model.isLoadingOlder == False) >>>>>> >>>>>> && ((Dict.size >>>>>> model.active_room_msgs) >= 10) >>>>>> >>>>>> in >>>>>> if doLoad then >>>>>> ( MesgList_LoadOlder rid, >>>>>> States.enableAll ) >>>>>> else >>>>>> ( msg, States.disableAll ) >>>>>> >>>>>> _ -> >>>>>> ( msg, States.disableAll ) >>>>>> >>>>>> >>>>>> _ -> >>>>>> ( msg, States.enableAll ) >>>>>> >>>>>> ``` >>>>>> >>>>>> On Tuesday, August 9, 2016 at 10:55:43 PM UTC-6, Nick H wrote: >>>>>>> >>>>>>> This would be a really nice improvement. I doubt there is anybody >>>>>>> using the keyboard API who wants to capture every key press. (But would >>>>>>> love to see a counter-example!) Typing is handled by the HTML library >>>>>>> >>>>>>> On Mon, Aug 8, 2016 at 11:01 PM, Janis Voigtländer < >>>>>>> [email protected]> wrote: >>>>>>> >>>>>>>> To up the proposal, here a revision. >>>>>>>> >>>>>>>> Instead of proposing to *add* new functions, I now propose to >>>>>>>> *replace* >>>>>>>> >>>>>>>> Keyboard.presses : (KeyCode -> msg) -> Sub msgKeyboard.downs : >>>>>>>> (KeyCode -> msg) -> Sub msgKeyboard.ups : (KeyCode -> msg) -> Sub msg >>>>>>>> >>>>>>>> by >>>>>>>> >>>>>>>> Keyboard.presses : (KeyCode -> Maybe msg) -> Sub msgKeyboard.downs : >>>>>>>> (KeyCode -> Maybe msg) -> Sub msgKeyboard.ups : (KeyCode -> Maybe msg) >>>>>>>> -> Sub msg >>>>>>>> >>>>>>>> It would still be easy to recover the old behavior if wanted in a >>>>>>>> specific situation, by throwing in Just. But actually I posit that >>>>>>>> one almost never really wants to capture all keys. Usually, one wants >>>>>>>> to be >>>>>>>> selective. That selectiveness has to be expressed somewhere, and doing >>>>>>>> it >>>>>>>> with the Maybe type that is designed for such purpose is generally >>>>>>>> better than going for something like extra NoOp or NothingHappened >>>>>>>> or NotBound constructors that then need to be handled in a special >>>>>>>> way in the app’s update logic, without help from the type system. >>>>>>>> Making >>>>>>>> consideration of the selectiveness part of the modelling up front would >>>>>>>> lead to better design. That’s at least the case in the >>>>>>>> projects/repositories I have pointed to. >>>>>>>> >>>>>>>> >>>>>>>> 2016-08-08 14:48 GMT+02:00 Janis Voigtländer < >>>>>>>> [email protected]>: >>>>>>>> >>>>>>>>> A while back there was a thread >>>>>>>>> <https://groups.google.com/d/msg/elm-discuss/u-6aCwaJezo/fu-HMPy6CQAJ> >>>>>>>>> about filtering subscriptions. The following is related, but can also >>>>>>>>> (and >>>>>>>>> probably better) be consumed and discussed independently. For those >>>>>>>>> that do >>>>>>>>> have that older thread as context in mind, the following differs in >>>>>>>>> two >>>>>>>>> essential ways: >>>>>>>>> >>>>>>>>> - Earlier, the discussion was about generic filtering of >>>>>>>>> arbitrary subscriptions. The following involves no genericity >>>>>>>>> whatsoever. >>>>>>>>> It is only a proposal about the Keyboard API specifically. >>>>>>>>> - The earlier thread was not rooted in practice, since very >>>>>>>>> little stuff had been built yet with subscriptions. In the >>>>>>>>> following, I >>>>>>>>> point to how things have played out in practice, based on uses >>>>>>>>> students >>>>>>>>> have made of the current API in projects. >>>>>>>>> >>>>>>>>> ------------------------------ >>>>>>>>> >>>>>>>>> So, on to the subject matter: >>>>>>>>> >>>>>>>>> The keyboard package >>>>>>>>> <http://package.elm-lang.org/packages/elm-lang/keyboard> >>>>>>>>> currently contains functions such as: >>>>>>>>> >>>>>>>>> Keyboard.downs : (KeyCode -> msg) -> Sub msg >>>>>>>>> >>>>>>>>> Common uses (I’ll point to several repositories below) are such >>>>>>>>> that only some keys are relevant for an application. My proposal is >>>>>>>>> to have >>>>>>>>> functions such as: >>>>>>>>> >>>>>>>>> Keyboard.downsSelectively : (KeyCode -> Maybe msg) -> Sub msg >>>>>>>>> >>>>>>>>> where the semantics is that if a given KeyCode is mapped to >>>>>>>>> Nothing by the tagger, then no message gets sent along the >>>>>>>>> subscription; otherwise the Just is peeled off and the message >>>>>>>>> gets sent. >>>>>>>>> ------------------------------ >>>>>>>>> >>>>>>>>> Let’s look at a practical case, https://github.com/arpad-m/don >>>>>>>>> tfall. It’s a game, where the player uses the keyboard for part >>>>>>>>> of the control. Important excerpts from the code are: >>>>>>>>> >>>>>>>>> The message type (in https://github.com/arpad-m/don >>>>>>>>> tfall/blob/master/src/BaseStuff.elm): >>>>>>>>> >>>>>>>>> type GameMsg = NothingHappened | ... several other messages ... >>>>>>>>> >>>>>>>>> The subscriptions definition (in https://github.com/arpad-m/don >>>>>>>>> tfall/blob/master/src/main.elm): >>>>>>>>> >>>>>>>>> subscriptions : GameData -> Sub GameMsgsubscriptions d = >>>>>>>>> Sub.batch >>>>>>>>> ([ Keyboard.downs (\c -> if Char.fromCode c == 'P' then >>>>>>>>> PauseToogle else NothingHappened) ] ++ >>>>>>>>> if d.state == Running then >>>>>>>>> [ AnimationFrame.diffs Tick >>>>>>>>> , Keyboard.downs (\c -> if Char.fromCode c == ' ' >>>>>>>>> then JumpDown else NothingHappened) >>>>>>>>> , Keyboard.ups (\c -> if Char.fromCode c == ' ' then >>>>>>>>> JumpUp else NothingHappened) >>>>>>>>> ] >>>>>>>>> else >>>>>>>>> []) >>>>>>>>> >>>>>>>>> The main case distinction in the main update function (in >>>>>>>>> https://github.com/arpad-m/dontfall/blob/master/src/main.elm): >>>>>>>>> >>>>>>>>> updateScene : GameMsg -> GameData -> (GameData, Cmd >>>>>>>>> GameMsg)updateScene msg d = >>>>>>>>> (case d.state of >>>>>>>>> ... >>>>>>>>> Running -> case msg of >>>>>>>>> MouseMove (x,_) -> { d | characterPosX = min x d.flWidth} >>>>>>>>> Tick t -> stepTime d t >>>>>>>>> PauseToogle -> { d | state = Paused } >>>>>>>>> JumpDown -> { d | jumpPressed = True } >>>>>>>>> JumpUp -> { d | jumpPressed = False } >>>>>>>>> _ -> d >>>>>>>>> , Cmd.none >>>>>>>>> ) >>>>>>>>> >>>>>>>>> Given availability of the functions I propose above, the code >>>>>>>>> could instead look as follows: >>>>>>>>> >>>>>>>>> type GameMsg = ... only the other messages, no NothingHappened ... >>>>>>>>> subscriptions : GameData -> Sub GameMsgsubscriptions d = >>>>>>>>> Sub.batch >>>>>>>>> ([ Keyboard.downsSelectively (\c -> if Char.fromCode c == 'P' >>>>>>>>> then Just PauseToogle else Nothing) ] ++ >>>>>>>>> if d.state == Running then >>>>>>>>> [ AnimationFrame.diffs Tick >>>>>>>>> , Keyboard.downsSelectively (\c -> if Char.fromCode c >>>>>>>>> == ' ' then Just JumpDown else Nothing) >>>>>>>>> , Keyboard.upsSelectively (\c -> if Char.fromCode c >>>>>>>>> == ' ' then Just JumpUp else Nothing) >>>>>>>>> ] >>>>>>>>> else >>>>>>>>> []) >>>>>>>>> updateScene : GameMsg -> GameData -> (GameData, Cmd >>>>>>>>> GameMsg)updateScene msg d = >>>>>>>>> (case d.state of >>>>>>>>> ... >>>>>>>>> Running -> case msg of >>>>>>>>> MouseMove (x,_) -> { d | characterPosX = min x d.flWidth} >>>>>>>>> Tick t -> stepTime d t >>>>>>>>> PauseToogle -> { d | state = Paused } >>>>>>>>> JumpDown -> { d | jumpPressed = True } >>>>>>>>> JumpUp -> { d | jumpPressed = False } >>>>>>>>> , Cmd.none >>>>>>>>> ) >>>>>>>>> >>>>>>>>> Advantages: >>>>>>>>> >>>>>>>>> 1. >>>>>>>>> >>>>>>>>> simpler message type, no special role no-op constructor needed >>>>>>>>> 2. >>>>>>>>> >>>>>>>>> no spurious update and render cycles while the game is running >>>>>>>>> 3. >>>>>>>>> >>>>>>>>> less room for bugs in the update logic >>>>>>>>> >>>>>>>>> Some additional comments on the latter two of these points: >>>>>>>>> >>>>>>>>> Re 2., given the current implementation, whenever a key is hit >>>>>>>>> that is not relevant, the update function is still called and >>>>>>>>> produces an >>>>>>>>> unchanged model, which is then rendered, which is extra/useless work. >>>>>>>>> Since >>>>>>>>> the game uses Graphics.*, no use can be made of Html.Lazy.* to >>>>>>>>> avoid the re-rendering. Even if something like Graphics.Lazy.* >>>>>>>>> were available, having to use it would not be as nice/pure as not >>>>>>>>> causing >>>>>>>>> those spurious updates in the first place. >>>>>>>>> >>>>>>>>> Re 3., given the current implementation, there is both more room >>>>>>>>> for bugs in the now and in a potential later, when extending the >>>>>>>>> game. In >>>>>>>>> the now, the programmer has to make sure that NothingHappened >>>>>>>>> does indeed not change the model. Concerning later, imagine that the >>>>>>>>> programmer extends the message type for some reason. With the current >>>>>>>>> version of updateScene, the programmer might forget to actually >>>>>>>>> add a branch for handling the new message, and the compiler would not >>>>>>>>> catch >>>>>>>>> that, because of the _ -> d branch that will silently catch not >>>>>>>>> only NothingHappened but also the new message which was actually >>>>>>>>> supposed to make something happen. With the version of updateScene >>>>>>>>> after the proposed change, the situation would be different. Since >>>>>>>>> there is >>>>>>>>> no _ -> d branch in that Running -> case msg of ... part anymore >>>>>>>>> (thanks to NothingHappened not being a thing), the compiler will >>>>>>>>> immediately complain if the message type is extended but the new >>>>>>>>> message is >>>>>>>>> not handled there. Bug prevented. >>>>>>>>> ------------------------------ >>>>>>>>> >>>>>>>>> It’s not only this single project. I have observed students >>>>>>>>> applying different strategies to deal with “Not all keys are relevant >>>>>>>>> to my >>>>>>>>> program”. In each case, using an API with functions of type (KeyCode >>>>>>>>> -> Maybe msg) -> Sub msg instead of (KeyCode -> msg) -> Sub msg >>>>>>>>> would have been conceptually nicer and would have simplified things. >>>>>>>>> >>>>>>>>> Some more example repos: >>>>>>>>> >>>>>>>>> - https://github.com/chemmi/elm-rocket, uses type Key = Left | >>>>>>>>> Right | ... | NotBound and keyBinding : KeyCode -> Key and >>>>>>>>> then needs to make sure to correctly (non)-deal with NotBound >>>>>>>>> in functions like updateKeyDown; whereas just not having >>>>>>>>> NotBound, but having keyBinding : KeyCode -> Maybe Key and >>>>>>>>> using that in a call to a (KeyCode -> Maybe msg) -> Sub msg >>>>>>>>> function would simplify things with the same benefits as in the >>>>>>>>> above more >>>>>>>>> fully elaborated example case. >>>>>>>>> - https://github.com/Dinendal92/Abschlussprojekt-DP2016, less >>>>>>>>> complete project, but with same approach and issues as in the >>>>>>>>> preceding >>>>>>>>> example, using type Key = Space | Unknown and fromCode : Int >>>>>>>>> -> Key. Here, since eliminating Unknown would turn Key into a >>>>>>>>> type with only one constructor, even more conceptual >>>>>>>>> simplifications would >>>>>>>>> be enabled after a switch to the (KeyCode -> Maybe msg) -> Sub >>>>>>>>> msg approach. >>>>>>>>> - https://github.com/Shaomada/Elm-Project, quite elaborate >>>>>>>>> project, structured according to TEA, uses no special Key >>>>>>>>> type, instead maps with Char.fromCode in the calls to the >>>>>>>>> keyboard subscriptions, then has to case dispatch on actual >>>>>>>>> Chars at several places distributed over the update functions >>>>>>>>> of the TEA subcomponents. Subscribing with (KeyCode -> Maybe >>>>>>>>> msg) -> Sub msg functions should allow to eliminate branches >>>>>>>>> at some of those places, removing redundancies and room for bugs. >>>>>>>>> - https://github.com/Sulring/elmaction, similar story (without >>>>>>>>> TEA) >>>>>>>>> >>>>>>>>> >>>>>>>>> >>>>>>>> >>>>>>>> -- >>>>>>>> You received this message because you are subscribed to the Google >>>>>>>> Groups "Elm Discuss" group. >>>>>>>> To unsubscribe from this group and stop receiving emails from it, >>>>>>>> send an email to [email protected]. >>>>>>>> For more options, visit https://groups.google.com/d/optout. >>>>>>>> >>>>>>> >>>>>>> -- >>>>>> You received this message because you are subscribed to the Google >>>>>> Groups "Elm Discuss" group. >>>>>> To unsubscribe from this group and stop receiving emails from it, >>>>>> send an email to [email protected]. >>>>>> For more options, visit https://groups.google.com/d/optout. >>>>>> >>>>> >>>>> -- >>>> You received this message because you are subscribed to the Google >>>> Groups "Elm Discuss" group. >>>> To unsubscribe from this group and stop receiving emails from it, send >>>> an email to [email protected]. >>>> For more options, visit https://groups.google.com/d/optout. >>>> >>> >>> -- >>> You received this message because you are subscribed to the Google >>> Groups "Elm Discuss" group. >>> To unsubscribe from this group and stop receiving emails from it, send >>> an email to [email protected]. >>> For more options, visit https://groups.google.com/d/optout. >>> >> >> -- > You received this message because you are subscribed to the Google Groups > "Elm Discuss" group. > To unsubscribe from this group and stop receiving emails from it, send an > email to [email protected]. > For more options, visit https://groups.google.com/d/optout. > -- You received this message because you are subscribed to the Google Groups "Elm Discuss" group. To unsubscribe from this group and stop receiving emails from it, send an email to [email protected]. For more options, visit https://groups.google.com/d/optout.
