1{-# LANGUAGE DeriveDataTypeable #-}
2{-# LANGUAGE FlexibleContexts #-}
3{-# LANGUAGE MultiParamTypeClasses #-}
4{-# LANGUAGE RecursiveDo #-}
5{-# LANGUAGE ScopedTypeVariables #-}
6{-# LANGUAGE TypeFamilies #-}
7module Reflex.Dom.Time where
11import Reflex.PerformEvent.Class
12import Reflex.PostBuild.Class
13import Reflex.TriggerEvent.Class
15import Control.Concurrent
16import qualified Control.Concurrent.Thread.Delay as Concurrent
18import Control.Monad.Fix
19import Control.Monad.IO.Class
22import Data.Sequence (Seq, (|>))
23import qualified Data.Sequence as Seq
30 = TickInfo { _tickInfo_lastUTC :: UTCTime
31 -- ^ UTC time immediately after the last tick.
32 , _tickInfo_n :: Integer
33 -- ^ Number of time periods since t0
34 , _tickInfo_alreadyElapsed :: NominalDiffTime
35 -- ^ Amount of time already elapsed in the current tick period.
37 deriving (Eq, Ord, Show, Typeable)
39-- | Special case of tickLossyFrom that uses the post-build event to start the
41tickLossy :: (PostBuild t m, PerformEvent t m, TriggerEvent t m, MonadIO (Performable m), MonadFix m) => NominalDiffTime -> UTCTime -> m (Event t TickInfo)
42tickLossy dt t0 = tickLossyFrom dt t0 =<< getPostBuild
44-- | Send events over time with the given basis time and interval
45-- If the system starts running behind, occurrences will be dropped rather than buffered
46-- Each occurrence of the resulting event will contain the index of the current interval, with 0 representing the basis time
48 :: (PerformEvent t m, TriggerEvent t m, MonadIO (Performable m), MonadFix m)
52 -- ^ Event that starts a tick generation thread. Usually you want this to
53 -- be something like the result of getPostBuild that only fires once. But
54 -- there could be uses for starting multiple timer threads.
55 -> m (Event t TickInfo)
56tickLossyFrom dt t0 e = tickLossyFrom' $ (dt, t0) <$ e
58-- | Generalization of tickLossyFrom that takes dt and t0 in the event.
60 :: (PerformEvent t m, TriggerEvent t m, MonadIO (Performable m), MonadFix m)
61 => Event t (NominalDiffTime, UTCTime)
62 -- ^ Event that starts a tick generation thread. Usually you want this to
63 -- be something like the result of getPostBuild that only fires once. But
64 -- there could be uses for starting multiple timer threads.
65 -> m (Event t TickInfo)
67 rec result <- performEventAsync $ callAtNextInterval <$> leftmost [e, snd <$> result]
68 return $ fst <$> result
69 where callAtNextInterval pair cb = void $ liftIO $ forkIO $ do
70 tick <- uncurry getCurrentTick pair
71 Concurrent.delay $ ceiling $ (fst pair - _tickInfo_alreadyElapsed tick) * 1000000
74clockLossy :: (MonadIO m, PerformEvent t m, TriggerEvent t m, MonadIO (Performable m), PostBuild t m, MonadHold t m, MonadFix m) => NominalDiffTime -> UTCTime -> m (Dynamic t TickInfo)
76 initial <- liftIO $ getCurrentTick dt t0
80getCurrentTick :: NominalDiffTime -> UTCTime -> IO TickInfo
81getCurrentTick dt t0 = do
83 let offset = t `diffUTCTime` t0
84 (n, alreadyElapsed) = offset `divMod'` dt
85 return $ TickInfo t n alreadyElapsed
87-- | Delay an Event's occurrences by a given amount in seconds.
88delay :: (PerformEvent t m, TriggerEvent t m, MonadIO (Performable m)) => NominalDiffTime -> Event t a -> m (Event t a)
89delay dt e = performEventAsync $ ffor e $ \a cb -> liftIO $ void $ forkIO $ do
90 Concurrent.delay $ ceiling $ dt * 1000000
93-- | Send events with Poisson timing with the given basis and rate
94-- Each occurence of the resulting event will contain the index of
95-- the current interval, with 0 representing the basis time
97 :: (RandomGen g, MonadIO (Performable m), PerformEvent t m, TriggerEvent t m)
100 -- ^ Poisson event rate (Hz)
102 -- ^ Baseline time for events
104 -- ^ Event that starts a tick generation thread. Usually you want this to
105 -- be something like the result of getPostBuild that only fires once. But
106 -- there could be uses for starting multiple timer threads.
107 -- Start sending events in response to the event parameter.
108 -> m (Event t TickInfo)
109poissonLossyFrom rnd rate = inhomogeneousPoissonFrom rnd (constant rate) rate
112-- | Send events with Poisson timing with the given basis and rate
113-- Each occurence of the resulting event will contain the index of
114-- the current interval, with 0 representing the basis time.
115-- Automatically begin sending events when the DOM is built
117 :: (RandomGen g, MonadIO (Performable m), PerformEvent t m, TriggerEvent t m, PostBuild t m)
120 -- ^ Poisson event rate (Hz)
122 -- ^ Baseline time for events
123 -> m (Event t TickInfo)
124poissonLossy rnd rate t0 = poissonLossyFrom rnd rate t0 =<< getPostBuild
126-- | Send events with inhomogeneous Poisson timing with the given basis
127-- and variable rate. Provide a maxRate that you expect to support.
128inhomogeneousPoissonFrom
129 :: (RandomGen g, MonadIO (Performable m), PerformEvent t m, TriggerEvent t m)
135 -> m (Event t TickInfo)
136inhomogeneousPoissonFrom rnd rate maxRate t0 e = do
138 -- Create a thread for producing homogeneous poisson events
139 -- along with random Doubles (usage of Double's explained below)
140 ticksWithRateRand <- performEventAsync $
141 fmap callAtNextInterval e
143 -- Filter homogeneous events according to associated
144 -- random values and the current rate parameter
145 return $ attachWithMaybe filterFun rate ticksWithRateRand
149 -- Inhomogeneous poisson processes are built from faster
150 -- homogeneous ones by randomly dropping events from the
151 -- fast process. For each fast homogeneous event, choose
152 -- a uniform random sample from (0, rMax). If the
153 -- inhomogeneous rate at this moment is greater than the
154 -- random sample, then keep this event, otherwise drop it
155 filterFun :: Double -> (TickInfo, Double) -> Maybe TickInfo
156 filterFun r (tInfo, p)
157 | r >= p = Just tInfo
158 | otherwise = Nothing
160 callAtNextInterval _ cb = void $ liftIO $ forkIO $ go t0 rnd cb 0
162 go tTargetLast lastGen cb lastN = do
165 -- Generate random numbers for this poisson interval (u)
166 -- and sample-retention likelihood (p)
167 let (u, nextGen) = randomR (0,1) lastGen
168 (p :: Double, nextGen') = randomR (0,maxRate) nextGen
170 -- Inter-event interval is drawn from exponential
171 -- distribution accourding to u
172 let dt = realToFrac $ (-1) * log u / maxRate :: NominalDiffTime
174 alreadyElapsed = diffUTCTime t tTargetLast
175 tTarget = addUTCTime dt tTargetLast
176 thisDelay = realToFrac $ diffUTCTime tTarget t :: Double
177 Concurrent.delay $ ceiling $ thisDelay * 1000000
178 _ <- cb (TickInfo t nEvents alreadyElapsed, p)
179 go tTarget nextGen' cb nEvents
181-- | Send events with inhomogeneous Poisson timing with the given basis
182-- and variable rate. Provide a maxRate that you expect to support
184 :: (RandomGen g, MonadIO (Performable m), PerformEvent t m, TriggerEvent t m, PostBuild t m)
189 -> m (Event t TickInfo)
190inhomogeneousPoisson rnd rate maxRate t0 =
191 inhomogeneousPoissonFrom rnd rate maxRate t0 =<< getPostBuild
193-- | Block occurrences of an Event until the given number of seconds elapses without
194-- the Event firing, at which point the last occurrence of the Event will fire.
195debounce :: (MonadFix m, MonadHold t m, PerformEvent t m, TriggerEvent t m, MonadIO (Performable m)) => NominalDiffTime -> Event t a -> m (Event t a)
197 n :: Dynamic t Integer <- count e
198 let tagged = attachPromptlyDynWith (,) n e
199 delayed <- delay dt tagged
200 return $ attachWithMaybe (\n' (t, v) -> if n' == t then Just v else Nothing) (current n) delayed
202-- | When the given 'Event' occurs, wait the given amount of time and collect
203-- all occurrences during that time. Then, fire the output 'Event' with the
205batchOccurrences :: (MonadFix m, MonadHold t m, PerformEvent t m, TriggerEvent t m, MonadIO (Performable m)) => NominalDiffTime -> Event t a -> m (Event t (Seq a))
206batchOccurrences t newValues = do
207 let f s x = (Just newState, out)
208 where newState = case x of
211 These a _ -> Seq.singleton a
213 This _ -> if Seq.null s then Just () else Nothing
216 rec (buffer, toDelay) <- mapAccumMaybe f mempty $ align newValues delayed
217 delayed <- delay t toDelay
218 return $ tag buffer delayed
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list