packages feed

hans-2.1.0.0: src/Hans/Layer/Timer.hs

{-# LANGUAGE BangPatterns          #-}
{-# LANGUAGE FlexibleContexts      #-}
{-# LANGUAGE TypeSynonymInstances  #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# OPTIONS -fno-warn-orphans      #-}

module Hans.Layer.Timer (
    TimerHandle
  , runTimerLayer

  , Milliseconds
  , delay
  , Microseconds
  , udelay
  ) where

import Hans.Channel

import Control.Concurrent (forkIO,threadDelay,MVar,newMVar,takeMVar,putMVar)
import Data.FingerTree as FT
import Data.Monoid (Monoid(..))
import Data.Time.Clock.POSIX (POSIXTime,getPOSIXTime)
import MonadLib


-- The Timers Structure --------------------------------------------------------

data Timer a = Timer
  { timerAt    :: POSIXTime
  , timerValue :: a
  }

instance Monoid POSIXTime where
  mempty = 0
  mappend a b | a > b     = a
              | otherwise = b

instance Measured POSIXTime (Timer a) where
  measure t = timerAt t

type Timers a = FingerTree POSIXTime (Timer a)


-- | Add an action to happen sometime in the future.
at :: POSIXTime -> a -> Timers a -> Timers a
at fut a ts = as' >< bs
  where
  (as,bs) = runnable fut ts
  as' = as |> Timer fut a


-- | Partition the actions into runnable and deferred.
runnable :: POSIXTime -> Timers a -> (Timers a, Timers a)
runnable now ts = FT.split (> now) ts


-- | Step through the timers in a group.
stepTimers :: Timers a -> Maybe (a, Timers a)
stepTimers ts = case viewl ts of
  EmptyL  -> Nothing
  r :< rs -> Just (timerValue r,rs)


-- | Run all timers, when the action is an IO action.
runTimers :: Timers (IO a) -> IO (Timers (IO a))
runTimers ts = do
  now <- getPOSIXTime
  let loop (Just (a,as)) = a >> loop (stepTimers as)
      loop Nothing       = return ()
      (rs,rest) = runnable now ts
  loop (stepTimers rs)
  return rest


-- The External Message Interface ----------------------------------------------

type ActionTimers = Timers (IO ())

-- | Mesages handled by the timer thread.
data TimerMessage = AddTimer POSIXTime (IO ())

-- | A channel to the timer thread.
type TimerHandle = Channel TimerMessage


-- | Start the timer thread.
runTimerLayer :: TimerHandle -> IO ()
runTimerLayer h = do
  timers <- newMVar FT.empty
  _      <- forkIO (actionHandler timers)
  _      <- forkIO (messageHandler h timers)
  return ()


type Milliseconds = Int

-- | Add a message to happen after some number of milliseconds.
delay :: TimerHandle -> Milliseconds -> IO () -> IO ()
delay h !off = udelay h (off * 1000)

type Microseconds = Int

-- | Add a message to happen after some number of microseconds.
udelay :: TimerHandle -> Microseconds -> IO () -> IO ()
udelay h !micros k = do
  now <- getPOSIXTime
  -- the granularity for a NominalTimeDiff is 10^-12
  let off = fromIntegral micros / 1000000
  send h (AddTimer (now + off) k)


-- Internal Loops --------------------------------------------------------------

-- | The delay granularity of the timer layer.
timerStep :: Microseconds
timerStep  = 100


-- | Loop, running available timer actions.
actionHandler :: MVar ActionTimers -> IO ()
actionHandler timers = forever $ do
  threadDelay timerStep
  putMVar timers =<< runTimers =<< takeMVar timers


-- | Loop, processing timer add requests.
messageHandler :: TimerHandle -> MVar ActionTimers -> IO ()
messageHandler h timers = forever $ do
  AddTimer t k <- receive h
  ts           <- takeMVar timers
  putMVar timers $! at t k ts