{-# language ForeignFunctionInterface, ScopedTypeVariables #-} -- Implementation using qt -- and a heuristic to shorten the time span to be waited -- to get closer to the wanted time span. module Clocked ( Timer, withTimer, waitTimer, getTimeDouble, ) where import Data.IORef import Data.Sequence import Control.Monad import Control.Monad.CatchIO import Control.Monad.IO.Class import Control.Applicative ((<$>)) import Clocked.Common import Clocked.WinTimePeriod -- | The heuristic correction is calculated using a moving average -- of the time deviation. This is the size of the window for -- calculating the moving average. windowSize :: Double windowSize = 10 -- | Estimate of the time deviation. -- Empiric data on linux (2.6.35-28) initialDeviation = 0.00018 -- | the heuristic correction gets limited by a fraction of the wanted time span heuristicCorrectionFraction = 0.1 foreign import ccall qtUSleep :: Int -> IO () newtype Timer = Timer (IORef State) -- | Run a monadic action with a 'Timer'. withTimer :: MonadCatchIO m => (Timer -> m a) -> m a withTimer m = withTimePeriod 1 $ (m =<< liftIO (Timer <$> (newIORef =<< mkState))) data State = State { lastTime :: Double, deviations :: Seq Double, deviationMovingAverage :: Double } mkState :: IO State mkState = do now <- getTimeDouble return $ State now (mkFullBuffer (round windowSize) initialDeviation) initialDeviation -- | Waits the given amount of seconds minus the elapsed time -- since the last call to 'waitTimer' (or the initialisation of the timer). waitTimer :: Timer -> Double -> IO () waitTimer (Timer ref) waitSeconds = do state <- readIORef ref now <- getTimeDouble let heuristicCorrection = deviationMovingAverage state waitTime :: Double = waitSeconds - (now - lastTime state) - heuristicCorrection waitMicroSeconds = round (waitTime * 10 ^ 6) when (waitMicroSeconds > 0) $ qtUSleep waitMicroSeconds after <- getTimeDouble let actuallyWaited = after - lastTime state deviation = min (actuallyWaited - waitSeconds + heuristicCorrection) (waitSeconds * heuristicCorrectionFraction) (droppedDeviation, newBuffer) = crank deviation (deviations state) newAverage = deviationMovingAverage state + ((deviation - droppedDeviation) / windowSize) state' = State after newBuffer newAverage writeIORef ref state' -- * buffer functions for Seq mkFullBuffer :: Int -> a -> Seq a mkFullBuffer = Data.Sequence.replicate crank :: a -> Seq a -> (a, Seq a) crank new buffer = case viewl buffer of head :< tail -> (head, tail |> new) EmptyL -> error "crank on empty buffer"