+++ /dev/null
-"""A generally useful event scheduler class.\r
-\r
-Each instance of this class manages its own queue.\r
-No multi-threading is implied; you are supposed to hack that\r
-yourself, or use a single instance per application.\r
-\r
-Each instance is parametrized with two functions, one that is\r
-supposed to return the current time, one that is supposed to\r
-implement a delay. You can implement real-time scheduling by\r
-substituting time and sleep from built-in module time, or you can\r
-implement simulated time by writing your own functions. This can\r
-also be used to integrate scheduling with STDWIN events; the delay\r
-function is allowed to modify the queue. Time can be expressed as\r
-integers or floating point numbers, as long as it is consistent.\r
-\r
-Events are specified by tuples (time, priority, action, argument).\r
-As in UNIX, lower priority numbers mean higher priority; in this\r
-way the queue can be maintained as a priority queue. Execution of the\r
-event means calling the action function, passing it the argument\r
-sequence in "argument" (remember that in Python, multiple function\r
-arguments are be packed in a sequence).\r
-The action function may be an instance method so it\r
-has another way to reference private data (besides global variables).\r
-"""\r
-\r
-# XXX The timefunc and delayfunc should have been defined as methods\r
-# XXX so you can define new kinds of schedulers using subclassing\r
-# XXX instead of having to define a module or class just to hold\r
-# XXX the global state of your particular time and delay functions.\r
-\r
-import heapq\r
-from collections import namedtuple\r
-\r
-__all__ = ["scheduler"]\r
-\r
-Event = namedtuple('Event', 'time, priority, action, argument')\r
-\r
-class scheduler:\r
- def __init__(self, timefunc, delayfunc):\r
- """Initialize a new instance, passing the time and delay\r
- functions"""\r
- self._queue = []\r
- self.timefunc = timefunc\r
- self.delayfunc = delayfunc\r
-\r
- def enterabs(self, time, priority, action, argument):\r
- """Enter a new event in the queue at an absolute time.\r
-\r
- Returns an ID for the event which can be used to remove it,\r
- if necessary.\r
-\r
- """\r
- event = Event(time, priority, action, argument)\r
- heapq.heappush(self._queue, event)\r
- return event # The ID\r
-\r
- def enter(self, delay, priority, action, argument):\r
- """A variant that specifies the time as a relative time.\r
-\r
- This is actually the more commonly used interface.\r
-\r
- """\r
- time = self.timefunc() + delay\r
- return self.enterabs(time, priority, action, argument)\r
-\r
- def cancel(self, event):\r
- """Remove an event from the queue.\r
-\r
- This must be presented the ID as returned by enter().\r
- If the event is not in the queue, this raises ValueError.\r
-\r
- """\r
- self._queue.remove(event)\r
- heapq.heapify(self._queue)\r
-\r
- def empty(self):\r
- """Check whether the queue is empty."""\r
- return not self._queue\r
-\r
- def run(self):\r
- """Execute events until the queue is empty.\r
-\r
- When there is a positive delay until the first event, the\r
- delay function is called and the event is left in the queue;\r
- otherwise, the event is removed from the queue and executed\r
- (its action function is called, passing it the argument). If\r
- the delay function returns prematurely, it is simply\r
- restarted.\r
-\r
- It is legal for both the delay function and the action\r
- function to to modify the queue or to raise an exception;\r
- exceptions are not caught but the scheduler's state remains\r
- well-defined so run() may be called again.\r
-\r
- A questionable hack is added to allow other threads to run:\r
- just after an event is executed, a delay of 0 is executed, to\r
- avoid monopolizing the CPU when other threads are also\r
- runnable.\r
-\r
- """\r
- # localize variable access to minimize overhead\r
- # and to improve thread safety\r
- q = self._queue\r
- delayfunc = self.delayfunc\r
- timefunc = self.timefunc\r
- pop = heapq.heappop\r
- while q:\r
- time, priority, action, argument = checked_event = q[0]\r
- now = timefunc()\r
- if now < time:\r
- delayfunc(time - now)\r
- else:\r
- event = pop(q)\r
- # Verify that the event was not removed or altered\r
- # by another thread after we last looked at q[0].\r
- if event is checked_event:\r
- action(*argument)\r
- delayfunc(0) # Let other threads run\r
- else:\r
- heapq.heappush(q, event)\r
-\r
- @property\r
- def queue(self):\r
- """An ordered list of upcoming events.\r
-\r
- Events are named tuples with fields for:\r
- time, priority, action, arguments\r
-\r
- """\r
- # Use heapq to sort the queue rather than using 'sorted(self._queue)'.\r
- # With heapq, two events scheduled at the same time will show in\r
- # the actual order they would be retrieved.\r
- events = self._queue[:]\r
- return map(heapq.heappop, [events]*len(events))\r