{-# LANGUAGE DataKinds #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE NoImplicitPrelude #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE ScopedTypeVariables #-} {-# OPTIONS_GHC -fno-warn-name-shadowing #-} {-# OPTIONS_GHC -fno-warn-orphans #-} {-# OPTIONS_GHC -fno-warn-type-defaults #-} -- | timing effects module Box.Time ( sleep , keepOpen , delayTimed , Stamped(..) , stampNow , emitStamp ) where import Control.Applicative import Control.Monad import Data.Maybe import Data.Time import Box.Cont import Box.Emitter import Box.Stream import Protolude hiding (threadDelay, STM) import qualified Streaming.Prelude as S import qualified Streaming as S import Control.Monad.Conc.Class as C -- | sleep for x seconds sleep :: (MonadConc m) => Double -> m () sleep x = threadDelay (floor $ x * 1e6) -- | keeping a box open sometimes needs a long running emitter keepOpen :: (MonadConc m) => Cont m (Emitter (STM m) a) keepOpen = toEmit $ lift $ threadDelay (365 * 24 * 60 * 60 * 10 ^ 6) -- | a stream with suggested delays. DiffTime is the length of time to wait since the start of the stream -- > delayTimed (S.each (zip (fromIntegral <$> [1..10]) [1..10])) |> S.print delayTimed :: (MonadConc m, MonadIO m) => S.Stream (S.Of (NominalDiffTime, a)) m () -> S.Stream (S.Of a) m () delayTimed s = do t0 <- liftIO getCurrentTime go (S.hoistUnexposed lift s) t0 where go s t0 = do n <- S.uncons s case n of Nothing -> pure () Just ((t1, a'), s') -> do lift $ delayTo (addUTCTime t1 t0) S.yield a' go s' t0 delayTo t = do now <- liftIO getCurrentTime let gap = max 0 $ diffUTCTime t now -- sleep gap threadDelay (truncate (gap * 1000000)) data Stamped a = Stamped { timestamp :: UTCTime , value :: a } deriving (Eq, Show, Read) stampNow :: (MonadConc m, MonadIO m) => a -> m (Stamped a) stampNow a = do t <- liftIO getCurrentTime pure $ Stamped t a -- | adding a time stamp -- todo: how to do this properly? emitStamp :: (MonadConc m, MonadIO m) => Cont m (Emitter m a) -> Cont m (Emitter m (Stamped a)) emitStamp e = emap (fmap Just . stampNow) <$> e