summaryrefslogtreecommitdiff
path: root/RealTimeQueue.hs
blob: 78314b1626c81440057333ca3d5469aee612ec5a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
module RealTimeQueue (Queue, createQueue, RealTimeQueue.null, scheduleEvent, extractScheduledEvents, scheduleEventIO, runScheduledIO) where
import BasePrelude hiding ((<>))
import System.Clock
import Data.IntPSQ as Q
import Control.Monad.IO.Class
import Data.Semigroup

-- The intended interface of this library is mainly the three functions
-- ''createQueue'', ''scheduleEventIO'' and ''runScheduledIO''.

createQueue :: Queue event
createQueue = Queue Q.empty 0

-- The IO commands wrap pure code alternatives (which are also exported), using
-- MonadIO to add management of the clock and the ability to run a user-supplied
-- handler. The ''Monotonic'' clock type from ''System.Clock'' is used to define
-- scheduling priorities internally, but these functions expect time to be
-- specified relative to the present.

scheduleEventIO :: MonadIO m => (TimeSpec, event) -> Queue event -> m (Queue event)
scheduleEventIO (ts, ev) queue = do
    now <- liftIO $ getTime Monotonic
    return $ scheduleEvent (ts + now, ev) queue

runScheduledIO :: MonadIO m => TimeSpec -> QueueRunner m event -> Queue event -> m (Queue event)
runScheduledIO timeSpan runner queue = do
    now <- liftIO $ getTime Monotonic
    let (events, queue') = extractScheduledEvents now timeSpan queue
    forM_ events $ \(ts, ev) -> runner (ts - now, ev)
    return queue'

null :: Queue event -> Bool
null (Queue q _) = Q.null q




-- The queue runner receives a time, relative to the present, at which the event
-- is scheduled to occur. Thus, it can wait for this amount of time if desired
-- (or, in the case of ALSA, push the event onto the kernel queue with this
-- amount of delay).

-- It is possible for this time to be negative, in which case the event was
-- scheduled to happen in the past.

type QueueRunner m event = (TimeSpec, event) -> m ()




-- It is important that the keys are never re-used. Therefore the constructor is
-- not exported.

-- (Yes, the keys can wrap around, but if your usage is anything remotely like a
-- FIFO, the earlier keys will be removed before they get reused. In order to
-- ensure that the keys _never_ wrap, IntPSQ could be replaced with HashPSQ and
-- the key type made Integer, but then the size of the keys would increase over
-- time.)

data Queue event = Queue {
    _intQueue :: IntPSQ TimeSpec event,
    _nextKey :: Int
}
instance Semigroup (Queue event) where
    _ <> q = q





-- These three functions provide a pure interface to the schedule queue. They
-- are agnostic as to the definition of time, but the IO versions below expect
-- that time should always be defined by the ''Monotonic'' clock in
-- System.Clock.

-- Schedule an event using absolute time.
scheduleEvent :: (TimeSpec, event) -> Queue event -> Queue event
scheduleEvent (ts, ev) (Queue q i) = Queue (Q.insert i ts ev q) (i+1)

-- Given an absolute current time and a timespan (relative to current) into the
-- future, remove and return all events from the queue that are scheduled
-- earlier than the end of the timespan.

-- The returned events specify their time as relative to the supplied absolute
-- current time.
extractScheduledEvents :: TimeSpec -> TimeSpec -> Queue event -> ([(TimeSpec, event)], Queue event)
extractScheduledEvents currentTime timeSpan = getAllScheduled
    where
        getAllScheduled :: Queue event -> ([(TimeSpec, event)], Queue event)
        getAllScheduled queue@(Queue q i)
            | isNothing $ getFirstScheduled queue = ([], queue)
            | otherwise = let (Just (_, p, v, q')) = minView q
                              queue' = Queue q' i
                          in ([(p, v)], queue') <> getAllScheduled queue'


        getFirstScheduled :: Queue event -> Maybe (TimeSpec, event)
        getFirstScheduled (Queue x _) = case findMin x of
            Nothing -> Nothing
            Just (_, ts, _) | ts > currentTime + timeSpan -> Nothing
            Just (_, ts, ev) -> Just (ts - currentTime, ev)