I’m trying to get a very quick and dirty animated display of some data produced using Haskell. The simplest thing to try seems to be ASCII art — in other words, something along the lines of:
type Frame = [[Char]] -- each frame is given as an array of characters
type Animation = [Frame]
display :: Animation -> IO ()
display = ??
How can I best do this?
The part I can’t figure out at all is how to ensure a minimal pause between frames; the rest is straightforward using putStrLn
together with clearScreen
from the ansi-terminal package, found via this answer.
Well, here's a rough sketch of what I'd do:
import Graphics.UI.SDL.Time (getTicks)
import Control.Concurrent (threadDelay)
type Frame = [[Char]]
type Animation = [Frame]
displayFrame :: Frame -> IO ()
displayFrame = mapM_ putStrLn
timeAction :: IO () -> IO Integer
timeAction act = do t <- getTicks
act
t' <- getTicks
return (fromIntegral $ t' - t)
addDelay :: Integer -> IO () -> IO ()
addDelay hz act = do dt <- timeAction act
let delay = calcDelay dt hz
threadDelay $ fromInteger delay
calcDelay dt hz = max (frame_usec - dt_usec) 0
where frame_usec = 1000000 `div` hz
dt_usec = dt * 1000
runFrames :: Integer -> Animation -> IO ()
runFrames hz frs = mapM_ (addDelay hz . displayFrame) frs
Obviously I'm using SDL here purely for getTicks
, because it's what I've used before. Feel free to replace it with any other function to get the current time.
The first argument to runFrames
is--as the name suggests--the frame rate in hertz, i.e., frames per second. The runFrames
function first converts each frame into an action that draws it, then gives each to the addDelay
function, which checks the time before and after running the action, then sleeps until the frame time has passed.
My own code would look a bit different than this, because I'd generally have a more complicated loop that would do other stuff, e.g., polling SDL for events, doing background processing, passing data to the next iteration, &c. But the basic idea is the same.
Obviously the nice thing about this approach is that, while still being fairly simple, you get a consistent frame rate when possible, with a clear means of specifying the target speed.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With