Well, here is a rough sketch of what I will 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 use the SDL here exclusively for getTicks
, because this is what I used before. Feel free to replace it with any other function to get the current time.
The first argument to runFrames
- as the name implies - the frame rate in hertz, i.e. frames per second. The runFrames
function first converts each frame into an action that draws it, and then gives each addDelay
function, which checks the time before and after the action starts, then sleeps until the frame time has passed.
My own code will be slightly different from this because I usually had a more complex loop that would do other things, like polling SDL for events, processing background images, passing data to the next iteration, and & c. But the basic idea is the same.
Obviously, the nice thing about this approach is that, although you are still fairly simple, you get a consistent frame rate when possible, with an explicit means of indicating the target speed.
source share