Performing a single switch in reactive-banana
Asked Answered
M

1

6

I'm building a multi-modal editor using reactive-banana - and for the most part it's going perfect. To expand on my scenario, the editor is some mapping software, or you could think of it as a very simple vector graphics editor. It currently has two states - selection mode and polygon creation mode. In selection mode, the user is able to select previously created polygons with the right mouse button (which would in theory take you to a new selected mode) or they can begin creating a new polygon with the left mouse button.

The intention is, when the left mouse button is pressed, we switch from selection mode into polygon creation mode. In this mode, a left mouse button means "add a new vertex", until the user returns to the original vertex. At this point, they have closed the polygon, so we return to selection mode.

I've implemented this a few different ways, and recently noticed that event switch almost makes this very elegant. I can have:

defaultMode :: Frameworks t => HadoomGUI -> Moment t (Behavior t Diagram)
defaultMode gui@HadoomGUI{..} =
  do mouseMoved <- registerMotionNotify guiMap
     mouseClicked <- registerMouseClicked guiMap
     let lmbClicked = ...
         gridCoords = ...
         diagram = ... 
     switchToCreateSector <- execute ((\m ->
                                         FrameworksMoment
                                           (=<< trimB =<< createSectorMode gui emptySectorBuilder m)) <$>
                                      (gridCoords <@ lmbClicked))
     return (switchB diagram switchToCreateSector)

Along with

createSectorMode :: Frameworks t
                 => HadoomGUI
                 -> SectorBuilder
                 -> Point V2 Double
                 -> Moment t (Behavior t Diagram)
createSectorMode HadoomGUI{..} initialSectorBuilder firstVertex =
  do mouseClicked <- registerMouseClicked guiMap
     ...

This certainly works - for a single mouse click. If I click on the map once, I switch into sector creation mode from the state I was just in. However, if I click again, defaultMode receives the click event and switches into a new polygon creation mode, throwing away my previous state.

What I'd like to do is switch in defaultMode once and never have the possibility of coming back. Essentially I want to "swap" the Behavior t Diagram produced by defaultMode with the result of createSectorMode.

I understand reactive-banana has problems with garbage collection of dynamic events, but I'm willing to live with that for now. The above formulation is significantly more precise than anything else I've written so far - such as having a single CurrentState variable and filtering various events based on the contents of that. The problem I have with this is that it's too big, and leaves way too much scope for me to mess things up. With switching, I only have in scope the events I can about.

Mcalpin answered 7/1, 2015 at 9:19 Comment(0)
M
4

The problem is somewhat open ended, so I can't give a definite answer. But I can certainly give my opinion. ;-)

However, what I would probably do is to separate the switching between modes from the behavior within a mode. If we forget about FRP for a moment, your program looks a bit like pair of functions that recursively call themselves:

defaultMode = ... `andthen` sectorMode
sectorMode  = ... `andthen` defaultMode

It's written a bit like a "sequential" program, "first do this mode, then do that mode". I think there is nothing wrong with that, though the default API reactive-banana, in particular switchB, does not support that style very well. You mentioned (privately) that you can write a

once :: Event t a -> Event t a

combinator that lets through the first occurrence of an event, but discards the rest. This is indeed what you would need for the sequential style.

Since you always return to the default mode, though, I would probably try a different approach, where each mode has an event that indicates that it wants to be switched away. The switching itself is taken care of by an "outside" entity. The idea is to avoid the explicit recursion in the program above by some higher-order combinator. In pseudo-code, this would look something like this:

modeManager = switchB initialMode changeMode
changeMode  = defaultModeSwitch `union` sectorModeSwitch

though I am a bit unsure about the details. In fact, I'm not entirely sure if it works at all, you probably still need the once combinator.

Anyway, that's just an idea on how to go about the switching. I fully agree that switching is the right way to deal with different modes.

Mcelhaney answered 10/1, 2015 at 17:18 Comment(6)
Thanks, this is an interesting approach that I hadn't considered. Do still need once as you still have the problem that old "modes" are still receiving events, even when they ask to be switched away? I'll be sure to have a play with this and see what I find.Mcalpin
In both cases, the old modes are still receiving events -- it's just that you discard them / ignore their results and create new modes instead. This probably means that you still need once in order to make sure that each mode may only signal once that it is to be replaced by a new one.Mcelhaney
I don't suppose this is anything garbage collection would help address in the future?Mcalpin
Maybe. I think I would have to bake once into the API in order to enable garbage collection for events that use this combinator. The idea is that events can be garbage collected whenever: (a) they don't have an observable effect or (b) they cannot have occurrences anymore. The once combinator certainly satisfies (b), but an implementation in terms of filter cannot prove this to the run-time. Actually, could you make an issue about once (and your whole scenario) on github? This way, I won't forget to think about it when I get around to work on reactive-banana-0.9.Mcelhaney
Ok, I thought it might need some primitive support to work due to being unable to do intensional analysis on filter. I'll go report that now - thanks!Mcalpin
Thanks for making the issue!Mcelhaney

© 2022 - 2024 — McMap. All rights reserved.