Epsilon

Dynamic Elements With Functional Reactive Programming

Posted on November 23, 2013

One thing that functional reactive programming (FRP) is very nice for is describing objects in terms of the changes of other objects. For example, things that depend on time, keyboard presses, or something more concrete like the steering wheel of a car can change their values the instant any of these change. This kind of reactive behavior is very nicely expressed with FRP.

FRP isn’t a magic bullet though, at least not for me. One of the things I’ve had a hard time describing are collections of reactive things in the syntax of FRP. To bring some context into this, imagine you have a space shooter game. Most space shooters allow you to shoot bullets. In other words, the game creates and destroys bullet entities to simulate bullets. These bullets could be different too, such as normal bullets and then also have a big bomb as well. So we need a way to handle similar but possibly different behaving objects, that can be created at runtime, as well as destroyed at runtime. How does one handle this kind of thing in a reactive environment?

Boilerplate

To help describe this problem, I want to have some code to see the problem. This requires a little bit of necessary boilerplate, to see how the things I build work. I’ll be writing this in Haskell, using the Sodium FRP library.

import FRP.Sodium

type Time = Event Int
type Key = Event Char
type Game a = Time -> Key -> Reactive (Behavior a)

run :: Show a => Game a -> IO ()
run game = do
    (dtEv, dtSink)   <- sync newEvent
    (keyEv, keySink) <- sync newEvent
    g <- sync $ do

        game' <- game dtEv keyEv
        return game'

    go g dtSink keySink
    return ()
  where
    go gameB dtSink keySink = do
        sync $ dtSink 1
        
        ks <- getLine
        mapM_ (sync . keySink) ks

        v <- sync $ sample gameB
        print v

        go gameB dtSink keySink

This is a basic event loop. The loop passes in a ‘time’ value and waits for the user to pass a line of text in and processes it. Now we move into possible implementations for our ship’s bullets.

Bullet Collection

Let’s go over the obvious solution first. Let’s look at creation of bullets using collections of bullets.

data GEvent = Alter ([Int] -> [Int])

game :: Time -> Key -> Reactive (Behavior [Int])
game dt key = do
    let spawn = const (Alter (\xs -> (0:xs))) <$> filterE (==' ') key
        update = (\t -> Alter (\xs -> map (+t) xs)) <$> dt
        applyAlter (Alter f) xs = f xs
    
    rec
        bs <- hold [] $ snapshotWith applyAlter (merge spawn update) bs 
          

    return bs

So this solution revolves around keeping a Behavior that contains a list of bullet data. Events based on time and key presses get converted into functions that act on the list, and then merged together so they can alter the list. This is pretty nice, but what if we want certain bullets to react to different events? Things like spawn a bomb when you hit space, or delete a bullet when they go past the viewable portion of the screen? Well, we could alter special bullets with a function similar to the other Alter functions. However this is a little bit unwieldy. We have to go through the whole list just to update a couple bullets. Here’s where the other more interesting approach can help us.

Bullet Behavior Collection

This is where it might be nice to work with a collection of bullet behaviors instead. There’s some fun advantages to designing it this way, but first let’s see an implementation. Here’s something that create’s bombs with a different key press.

normalBullet :: Time -> Reactive (Behavior Int)
normalBullet dt = collectE update 0 dt >>= hold 0
  where
    update t x = (x + t, x + t)

staticBullet :: Reactive (Behavior Int)
staticBullet = return $ pure 0


game :: Time -> Key -> Reactive (Behavior [Int]) 
game dt key = do
    let spawnNormal = filterE (==' ') key
        createNormal = execute $ (\_ -> normalBullet dt) <$> spawnNormal

        spawnStatic = filterE (=='b') key
        createStatic = execute $ (\_ -> staticBullet) <$> spawnStatic

        bEvs = merge createNormal createStatic

        keepList x xs = (x:xs, x:xs)  


    bBehs <- (collectE keepList [] bEvs) >>= hold [] 
        
        
    bs <- switch $ foldr (\b bs -> (:) <$> b <*> bs) (pure []) <$> bBehs      
    return $ bs

So this is pretty neat, we have a way to operate on bullets at an individual level. They can react to completely different events if they have to. This implementation also makes it so updates on a bullet doesn’t require that we go through a huge list of bullets and update ones that need it. We also don’t necessarily have to give up on using FRP to deal with each bullet, which can be nice sometimes when normal functional programming syntaxes get in the way.

The only issue with this is that to ‘destroy’ a bullet we still have to go through the whole list and filter out the destroyed bullets. I’d like to say that we can avoid this, but unfortunately this is a cost of grouping the bullets like this together. The best thing we could do is to have the concept of destruction built into the bullet type. We could do this by wrapping bullets in the Maybe monad, essentially making it possible to nullify bullets out of the list. At which point we could have an event that gets called for something resembling garbage collection. Implementing destruction will need to be more suited to what you need with your program. I’ve put the code to this up on github. These example are a bit simplistic, but I’m hoping to put them to a more complex use in a later blog post.

comments powered by Disqus