Commit | Line | Data |
---|---|---|
920dae64 AT |
1 | """A generally useful event scheduler class. |
2 | ||
3 | Each instance of this class manages its own queue. | |
4 | No multi-threading is implied; you are supposed to hack that | |
5 | yourself, or use a single instance per application. | |
6 | ||
7 | Each instance is parametrized with two functions, one that is | |
8 | supposed to return the current time, one that is supposed to | |
9 | implement a delay. You can implement real-time scheduling by | |
10 | substituting time and sleep from built-in module time, or you can | |
11 | implement simulated time by writing your own functions. This can | |
12 | also be used to integrate scheduling with STDWIN events; the delay | |
13 | function is allowed to modify the queue. Time can be expressed as | |
14 | integers or floating point numbers, as long as it is consistent. | |
15 | ||
16 | Events are specified by tuples (time, priority, action, argument). | |
17 | As in UNIX, lower priority numbers mean higher priority; in this | |
18 | way the queue can be maintained fully sorted. Execution of the | |
19 | event means calling the action function, passing it the argument. | |
20 | Remember that in Python, multiple function arguments can be packed | |
21 | in a tuple. The action function may be an instance method so it | |
22 | has another way to reference private data (besides global variables). | |
23 | Parameterless functions or methods cannot be used, however. | |
24 | """ | |
25 | ||
26 | # XXX The timefunc and delayfunc should have been defined as methods | |
27 | # XXX so you can define new kinds of schedulers using subclassing | |
28 | # XXX instead of having to define a module or class just to hold | |
29 | # XXX the global state of your particular time and delay functions. | |
30 | ||
31 | import bisect | |
32 | ||
33 | __all__ = ["scheduler"] | |
34 | ||
35 | class scheduler: | |
36 | def __init__(self, timefunc, delayfunc): | |
37 | """Initialize a new instance, passing the time and delay | |
38 | functions""" | |
39 | self.queue = [] | |
40 | self.timefunc = timefunc | |
41 | self.delayfunc = delayfunc | |
42 | ||
43 | def enterabs(self, time, priority, action, argument): | |
44 | """Enter a new event in the queue at an absolute time. | |
45 | ||
46 | Returns an ID for the event which can be used to remove it, | |
47 | if necessary. | |
48 | ||
49 | """ | |
50 | event = time, priority, action, argument | |
51 | bisect.insort(self.queue, event) | |
52 | return event # The ID | |
53 | ||
54 | def enter(self, delay, priority, action, argument): | |
55 | """A variant that specifies the time as a relative time. | |
56 | ||
57 | This is actually the more commonly used interface. | |
58 | ||
59 | """ | |
60 | time = self.timefunc() + delay | |
61 | return self.enterabs(time, priority, action, argument) | |
62 | ||
63 | def cancel(self, event): | |
64 | """Remove an event from the queue. | |
65 | ||
66 | This must be presented the ID as returned by enter(). | |
67 | If the event is not in the queue, this raises RuntimeError. | |
68 | ||
69 | """ | |
70 | self.queue.remove(event) | |
71 | ||
72 | def empty(self): | |
73 | """Check whether the queue is empty.""" | |
74 | return len(self.queue) == 0 | |
75 | ||
76 | def run(self): | |
77 | """Execute events until the queue is empty. | |
78 | ||
79 | When there is a positive delay until the first event, the | |
80 | delay function is called and the event is left in the queue; | |
81 | otherwise, the event is removed from the queue and executed | |
82 | (its action function is called, passing it the argument). If | |
83 | the delay function returns prematurely, it is simply | |
84 | restarted. | |
85 | ||
86 | It is legal for both the delay function and the action | |
87 | function to to modify the queue or to raise an exception; | |
88 | exceptions are not caught but the scheduler's state remains | |
89 | well-defined so run() may be called again. | |
90 | ||
91 | A questionably hack is added to allow other threads to run: | |
92 | just after an event is executed, a delay of 0 is executed, to | |
93 | avoid monopolizing the CPU when other threads are also | |
94 | runnable. | |
95 | ||
96 | """ | |
97 | q = self.queue | |
98 | while q: | |
99 | time, priority, action, argument = q[0] | |
100 | now = self.timefunc() | |
101 | if now < time: | |
102 | self.delayfunc(time - now) | |
103 | else: | |
104 | del q[0] | |
105 | void = action(*argument) | |
106 | self.delayfunc(0) # Let other threads run |