clocked-0.4.1: src/qt-heuristic-seq/Clocked.hs
{-# 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"