| 
									
										
										
										
											2000-02-04 15:39:30 +00:00
										 |  |  | """A generally useful event scheduler class.
 | 
					
						
							| 
									
										
										
										
											1999-06-25 18:53:23 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | Each instance of this class manages its own queue. | 
					
						
							|  |  |  | No multi-threading is implied; you are supposed to hack that | 
					
						
							|  |  |  | yourself, or use a single instance per application. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Each instance is parametrized with two functions, one that is | 
					
						
							|  |  |  | supposed to return the current time, one that is supposed to | 
					
						
							|  |  |  | implement a delay.  You can implement real-time scheduling by | 
					
						
							|  |  |  | substituting time and sleep from built-in module time, or you can | 
					
						
							|  |  |  | implement simulated time by writing your own functions.  This can | 
					
						
							|  |  |  | also be used to integrate scheduling with STDWIN events; the delay | 
					
						
							|  |  |  | function is allowed to modify the queue.  Time can be expressed as | 
					
						
							|  |  |  | integers or floating point numbers, as long as it is consistent. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Events are specified by tuples (time, priority, action, argument). | 
					
						
							|  |  |  | As in UNIX, lower priority numbers mean higher priority; in this | 
					
						
							|  |  |  | way the queue can be maintained fully sorted.  Execution of the | 
					
						
							|  |  |  | event means calling the action function, passing it the argument. | 
					
						
							|  |  |  | Remember that in Python, multiple function arguments can be packed | 
					
						
							|  |  |  | in a tuple.   The action function may be an instance method so it | 
					
						
							|  |  |  | has another way to reference private data (besides global variables). | 
					
						
							|  |  |  | Parameterless functions or methods cannot be used, however. | 
					
						
							|  |  |  | """
 | 
					
						
							| 
									
										
										
										
											1991-04-07 13:41:50 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											1991-11-12 15:37:53 +00:00
										 |  |  | # XXX The timefunc and delayfunc should have been defined as methods | 
					
						
							|  |  |  | # XXX so you can define new kinds of schedulers using subclassing | 
					
						
							|  |  |  | # XXX instead of having to define a module or class just to hold | 
					
						
							| 
									
										
										
										
											1999-06-25 18:53:23 +00:00
										 |  |  | # XXX the global state of your particular time and delay functions. | 
					
						
							| 
									
										
										
										
											1991-11-12 15:37:53 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											1992-09-02 20:43:20 +00:00
										 |  |  | import bisect | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2001-02-15 22:15:14 +00:00
										 |  |  | __all__ = ["scheduler"] | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											1991-12-26 13:06:29 +00:00
										 |  |  | class scheduler: | 
					
						
							| 
									
										
										
										
											1999-06-25 18:53:23 +00:00
										 |  |  |     def __init__(self, timefunc, delayfunc): | 
					
						
							|  |  |  |         """Initialize a new instance, passing the time and delay
 | 
					
						
							|  |  |  |         functions"""
 | 
					
						
							|  |  |  |         self.queue = [] | 
					
						
							|  |  |  |         self.timefunc = timefunc | 
					
						
							|  |  |  |         self.delayfunc = delayfunc | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def enterabs(self, time, priority, action, argument): | 
					
						
							|  |  |  |         """Enter a new event in the queue at an absolute time.
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2001-01-15 01:36:40 +00:00
										 |  |  |         Returns an ID for the event which can be used to remove it, | 
					
						
							|  |  |  |         if necessary. | 
					
						
							| 
									
										
										
										
											1999-06-25 18:53:23 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2001-01-15 01:36:40 +00:00
										 |  |  |         """
 | 
					
						
							| 
									
										
										
										
											1999-06-25 18:53:23 +00:00
										 |  |  |         event = time, priority, action, argument | 
					
						
							|  |  |  |         bisect.insort(self.queue, event) | 
					
						
							|  |  |  |         return event # The ID | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def enter(self, delay, priority, action, argument): | 
					
						
							|  |  |  |         """A variant that specifies the time as a relative time.
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2001-01-15 01:36:40 +00:00
										 |  |  |         This is actually the more commonly used interface. | 
					
						
							| 
									
										
										
										
											1999-06-25 18:53:23 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2001-01-15 01:36:40 +00:00
										 |  |  |         """
 | 
					
						
							| 
									
										
										
										
											1999-06-25 18:53:23 +00:00
										 |  |  |         time = self.timefunc() + delay | 
					
						
							|  |  |  |         return self.enterabs(time, priority, action, argument) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def cancel(self, event): | 
					
						
							|  |  |  |         """Remove an event from the queue.
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2001-01-15 01:36:40 +00:00
										 |  |  |         This must be presented the ID as returned by enter(). | 
					
						
							|  |  |  |         If the event is not in the queue, this raises RuntimeError. | 
					
						
							| 
									
										
										
										
											1999-06-25 18:53:23 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2001-01-15 01:36:40 +00:00
										 |  |  |         """
 | 
					
						
							| 
									
										
										
										
											1999-06-25 18:53:23 +00:00
										 |  |  |         self.queue.remove(event) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def empty(self): | 
					
						
							|  |  |  |         """Check whether the queue is empty.""" | 
					
						
							|  |  |  |         return len(self.queue) == 0 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def run(self): | 
					
						
							|  |  |  |         """Execute events until the queue is empty.
 | 
					
						
							| 
									
										
										
										
											2001-01-15 01:36:40 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |         When there is a positive delay until the first event, the | 
					
						
							|  |  |  |         delay function is called and the event is left in the queue; | 
					
						
							|  |  |  |         otherwise, the event is removed from the queue and executed | 
					
						
							|  |  |  |         (its action function is called, passing it the argument).  If | 
					
						
							|  |  |  |         the delay function returns prematurely, it is simply | 
					
						
							|  |  |  |         restarted. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         It is legal for both the delay function and the action | 
					
						
							|  |  |  |         function to to modify the queue or to raise an exception; | 
					
						
							|  |  |  |         exceptions are not caught but the scheduler's state remains | 
					
						
							|  |  |  |         well-defined so run() may be called again. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         A questionably hack is added to allow other threads to run: | 
					
						
							|  |  |  |         just after an event is executed, a delay of 0 is executed, to | 
					
						
							|  |  |  |         avoid monopolizing the CPU when other threads are also | 
					
						
							|  |  |  |         runnable. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         """
 | 
					
						
							| 
									
										
										
										
											1999-06-25 18:53:23 +00:00
										 |  |  |         q = self.queue | 
					
						
							|  |  |  |         while q: | 
					
						
							|  |  |  |             time, priority, action, argument = q[0] | 
					
						
							|  |  |  |             now = self.timefunc() | 
					
						
							|  |  |  |             if now < time: | 
					
						
							|  |  |  |                 self.delayfunc(time - now) | 
					
						
							|  |  |  |             else: | 
					
						
							|  |  |  |                 del q[0] | 
					
						
							| 
									
										
										
										
											2003-02-27 20:14:51 +00:00
										 |  |  |                 void = action(*argument) | 
					
						
							| 
									
										
										
										
											1999-06-25 18:53:23 +00:00
										 |  |  |                 self.delayfunc(0)   # Let other threads run |