Chapter 17 Rendering Reactive Animations
Motivation • In the previous chapter we learned just enough about advanced IO and concurrency to develop a “renderer” for FAL. • That renderer is called “reactimate”, and has type:reactimate :: String -> Behavior a -> (a -> IO Graphic) -> IO () • But first, we need a way to extract an infinite stream of time-stamped user actions from the operating system. We want the values in the stream to be: • “Just ua” if there is a user action “ua” present at the time we ask for it, and • “Nothing” if there is not.
windowUser • The stream of user actions will be supplied by: windowUser :: Window -> IO (([Maybe UserAction],[Time]), IO ()) • Executing the command:((us,ts), addEvents) <- windowUser w • yields a user action stream “us” and a time stream “ts”. • also yields an IO action “addEvents” that causes the OS to update “us” with whatever user actions happen to be pending at that time, followed by a “Nothing”. • Note: “addEvents” is an IO action – it does nothing until executed. “reactimate” will call windowUser, process all user actions in “us” until it sees “Nothing”, then execute “addEvents”, and repeat.
Using First-Class Channels • “windowUser” will use first-class channels. • As a helper function, we define:makeStream :: IO ([a], a -> IO ())makeStream = do ch <- newChan contents <- getChanContents ch return (contents, writeChan ch) • “makeStream” creates a new channel, converts its contents into a stream using “getChannelContents”, and returns that stream along with a function to write into the channel.
Definition of windowUser windowUser :: Window -> IO (([Maybe UserAction],[Time]), IO ()) windowUser w =do (evs, addEv) <- makeStream t0 <- timeGetTime let loop rt = do mev <- maybeGetWindowEvent w case mev of Nothing -> return () Just e -> do addEv (Just e, rt) loop rt let addEvents = do t <- timeGetTime let rt = w32ToTime (t-t0) loop rt addEv (Nothing, rt) return ((map fst evs, map snd evs), addEvents) w32ToTime t = intToFloat (word32ToInt t) / 1000
Turning Nothinginto Something • Recall that the non-occurrence of an event corresponds to a time when we wish to sample the picture. So we need to convert non-occurrences into occurrences: sample :: Event () sample = Event (\(us,_) -> map aux us) where aux Nothing = Just () aux (Just _) = Nothing • Then, for a given FAL program “franProg”, we can describe the individual picture values that we want as an event stream: sample `snapshot_` franProg :: Event Picture • This leads finally to the definition of “reactimate”.
Reactimate reactimate :: String -> Behavior a -> (a -> IO Graphic) -> IO () reactimate title franProg toGraphic =runGraphics $do w <- openWindowEx title (Just (0,0)) (Just (xWin,yWin)) drawBufferedGraphic (Just 30) (user, addEvents) <- windowUser w addEvents let drawPic (Just p) = do g <- toGraphic p setGraphic w g addEvents getWindowTick w drawPic Nothing = return () mapM_ drawPic (runEvent (sample `snapshot_` franProg) user) runEvent (Event fe) u = fe u