{-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE FlexibleContexts #-} module Reflex.Time.UTCTime ( TimeBits, utc2TimeBits, afterTime, atTime, time ) where import Control.Concurrent import Control.Concurrent.EQueue import Control.Monad import Control.Monad.Fix import Control.Monad.Trans import qualified Control.Time as T import Data.Bits import Data.Dependent.Sum (DSum ((:=>))) import Data.Functor.Identity import Data.Semigroup import Data.Time import Data.Time.Clock.POSIX import qualified GHC.Event as GHC import Reflex import Reflex.Host.Class -- | TimeBits is a list, each step containing the value of the time since the POSIX epoc, -- shifted right one more then the last. -- By using this, one can wait on the time changing more then a desired picosecond*2^Nat amount. -- To get a precise wait, one may then have to wait again on a finer time counter. -- The head of the list stores the full time as picoseconds since the unix epoch. type TimeBits t = (Dynamic t UTCTime, [Dynamic t Integer]) -- | Create a (lazy) list of Dynamics, each holding a the value of the UTCTime right-shifted as many bits -- as there are elements before it in the list. utc2TimeBits :: forall t. (Functor (Dynamic t)) => Dynamic t UTCTime -> TimeBits t utc2TimeBits dt = (dt, [timeSlice b <$> dt | b <- [0..]]) -- | Get the value of the UTCTime right-shifted the specified number of bits. -- The conversion to Integer could be shared but would prevent code reuse. timeSlice :: Int -> UTCTime -> Integer timeSlice b = (`shiftR` b) . floor . (* 10^(12::Int)) . utcTimeToPOSIXSeconds -- | Efficiently compare a target time with a timebits, becoming True at the chosen time. -- O(log2 (then - now)) tests against the time while waiting to become true. -- -- We find a list of the postfixs of the time that show the target time isn't already past. -- The list should be the length of the log of the difference of the times, making this efficient. -- -- This should be leap second resilient. afterTime :: forall t m. (Reflex t, MonadHold t m, MonadFix m) => TimeBits t -> UTCTime -> m (Dynamic t Bool) afterTime (curTime, tbs) tgt = do n <- sample . current $ curTime case tgt <= n of True -> return . constDyn $ True False -> do prefix <- dissimilarPrefix 0 [] tbs -- onceE to avoid reprocessing once True. holdDyn False =<< headE =<< (allGreater $ prefix) where -- Check that each step of the time is now greater then or equal to the time, -- switching promptly to the next step in the chain each time, -- and firing when the time is >=. -- All finer events fire when a corser event fires, so if we not are >= a more precise event, we'll -- pass thought it on this step, and go directly to the next we're not >= to. allGreater :: [Event t Integer] -> m (Event t Bool) allGreater [] = -- Since TimeBits changed, the curTime component had to fire. -- Thus we can generatea firing event off it. -- Since we got here, we know nothing in the list differs so we're definately True. return . fmap (const True) . updated $ curTime -- Process each potential difference one step at a time, moving to the next step when it has become True. allGreater (cstep:t) = do -- We need what the event for the next step would be. nstep <- allGreater t -- whenever this step fires, we want to become that next step. switchHoldPromptly never . fmap (const nstep) $ cstep -- Find the prefix of time bits to a time >= dissimilarPrefix :: (Reflex t, MonadSample t m1) => Int -> [Event t Integer] -> [Dynamic t Integer] -> m1 [Event t Integer] dissimilarPrefix _ _ [] = error "Unpossible, its an infinite list!" dissimilarPrefix step pre (cur:rest) = do thisStep <- sample . current $ cur let whatThisStepShouldBe = timeSlice step tgt if thisStep >= whatThisStepShouldBe then return pre else let fireWhenTargetOrGreater = ffilter (\tStep -> tStep >= whatThisStepShouldBe) . updated $ cur in dissimilarPrefix (step+1) (fireWhenTargetOrGreater:pre) rest -- | Efficiently fires an event at the target time, or immediately after. -- Fires instantly if the time is already past. -- -- Does not take into account leap seconds. atTime :: (Reflex t, EQueue eq, MonadReflexCreateTrigger t m) => eq (DSum (EventTrigger t) Identity) -> UTCTime -> m (Event t ()) atTime eq theTime = do e <- newEventWithTrigger $ \et -> do tm <- GHC.getSystemTimerManager now <- getCurrentTime (enqueue, dereg) <- registerQueued eq tk <- GHC.registerTimeout tm (max 0 . ceiling $ (theTime `diffUTCTime` now)*(10^(6::Int))) (enqueue (et :=> Identity ())) return $ dereg >> GHC.unregisterTimeout tm tk return e -- | Provides a Dynamic UTCTime signal at the desired resolution. -- -- Slight drift in the form of more time then the request difference between timesteps is expected. time :: (Reflex t, EQueue eq, MonadFix m, MonadIO m, MonadHold t m, MonadReflexCreateTrigger t m) => eq (DSum (EventTrigger t) Identity) -> DiffTime -> m (Dynamic t UTCTime) time eq dt = do e <- newEventWithTrigger $ \et -> do (enqueue, dereg) <- registerSemi eq ((:=>) et . Identity . getMax) t <- forkIO . forever $ do getCurrentTime >>= enqueue . Max -- This drifts. Not considered a problem for its intended use. T.delay dt return $ dereg >> killThread t now <- liftIO getCurrentTime holdDyn now e