好吧,这是我要做什么的粗略草图:
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
显然我在这里使用 SDL 纯粹是为了getTicks
,因为这是我以前使用过的。随意将其替换为任何其他功能以获取当前时间。
runFrames
顾名思义,第一个参数是以赫兹为单位的帧速率,即每秒帧数。该runFrames
函数首先将每个帧转换为一个绘制它的动作,然后将每个帧传递给该addDelay
函数,该函数检查运行该动作之前和之后的时间,然后休眠直到帧时间过去。
我自己的代码看起来会与此有所不同,因为我通常会有一个更复杂的循环来执行其他操作,例如轮询 SDL 以获取事件、执行后台处理、将数据传递给下一次迭代等。但基本思想是一样的。
显然,这种方法的好处在于,虽然仍然相当简单,但您可以在可能的情况下获得一致的帧速率,并通过明确的方式指定目标速度。