mirror of
				https://github.com/JoelBender/bacpypes
				synced 2025-10-27 00:57:47 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			233 lines
		
	
	
		
			6.1 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			233 lines
		
	
	
		
			6.1 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| #!/usr/bin/python
 | |
| 
 | |
| """
 | |
| To Do List
 | |
| 
 | |
| A ToDoItem is a thing that builds an IOCB to be given to a contoller.  Items
 | |
| are appended to a ToDoList which acts like a supervisor to make sure the
 | |
| items get done.  An item becomes "active" when the ToDoList asks the item
 | |
| for its IOCB.
 | |
| 
 | |
| Items may be "threaded" which means that an item must wait until some previously
 | |
| created item is complete before it becomes active.
 | |
| 
 | |
| The ToDoList can activate more than one item at a time.  Its idle() function
 | |
| is called when there are no more active or pending items.
 | |
| """
 | |
| 
 | |
| import random
 | |
| 
 | |
| from bacpypes.debugging import bacpypes_debugging, ModuleLogger
 | |
| from bacpypes.consolelogging import ArgumentParser
 | |
| 
 | |
| from bacpypes.core import run, deferred
 | |
| from bacpypes.iocb import IOCB, IOController
 | |
| from bacpypes.task import FunctionTask
 | |
| 
 | |
| # some debugging
 | |
| _debug = 0
 | |
| _log = ModuleLogger(globals())
 | |
| 
 | |
| # globals
 | |
| args = None
 | |
| 
 | |
| 
 | |
| #
 | |
| #   ToDoItem
 | |
| #
 | |
| 
 | |
| @bacpypes_debugging
 | |
| class ToDoItem:
 | |
| 
 | |
|     def __init__(self, _thread=None):
 | |
|         if _debug: ToDoItem._debug("__init__")
 | |
| 
 | |
|         # basic status information
 | |
|         self._completed = False
 | |
| 
 | |
|         # may depend on another item to complete
 | |
|         self._thread = _thread
 | |
| 
 | |
|     def prepare(self):
 | |
|         if _debug: ToDoItem._debug("prepare")
 | |
|         raise NotImplementedError
 | |
| 
 | |
| #
 | |
| #   ToDoList
 | |
| #
 | |
| 
 | |
| @bacpypes_debugging
 | |
| class ToDoList:
 | |
| 
 | |
|     def __init__(self, controller, active_limit=1):
 | |
|         if _debug: ToDoList._debug("__init__")
 | |
| 
 | |
|         # save a reference to the controller for workers
 | |
|         self.controller = controller
 | |
| 
 | |
|         # limit to the number of active workers
 | |
|         self.active_limit = active_limit
 | |
| 
 | |
|         # no workers, nothing active
 | |
|         self.pending = []
 | |
|         self.active = set()
 | |
| 
 | |
|         # launch already deferred
 | |
|         self.launch_deferred = False
 | |
| 
 | |
|     def append(self, item):
 | |
|         if _debug: ToDoList._debug("append %r", item)
 | |
| 
 | |
|         # add the item to the list of pending items
 | |
|         self.pending.append(item)
 | |
| 
 | |
|         # if an item can be started, schedule to launch it
 | |
|         if len(self.active) < self.active_limit and not self.launch_deferred:
 | |
|             if _debug: ToDoList._debug("    - will launch")
 | |
| 
 | |
|             self.launch_deferred = True
 | |
|             deferred(self.launch)
 | |
| 
 | |
|     def launch(self):
 | |
|         if _debug: ToDoList._debug("launch")
 | |
| 
 | |
|         # find some workers and launch them
 | |
|         while self.pending and (len(self.active) < self.active_limit):
 | |
|             # look for the next to_do_item that can be started
 | |
|             for i, item in enumerate(self.pending):
 | |
|                 if not item._thread:
 | |
|                     break
 | |
|                 if item._thread._completed:
 | |
|                     break
 | |
|             else:
 | |
|                 if _debug: ToDoList._debug("    - waiting")
 | |
|                 break
 | |
|             if _debug: ToDoList._debug("    - item: %r", item)
 | |
| 
 | |
|             # remove it from the pending list, add it to active
 | |
|             del self.pending[i]
 | |
|             self.active.add(item)
 | |
| 
 | |
|             # prepare it and capture the IOCB
 | |
|             iocb = item.prepare()
 | |
|             if _debug: ToDoList._debug("    - iocb: %r", iocb)
 | |
| 
 | |
|             # break the reference to the completed to_do_item
 | |
|             item._thread = None
 | |
|             iocb._to_do_item = item
 | |
| 
 | |
|             # add our completion routine
 | |
|             iocb.add_callback(self.complete)
 | |
| 
 | |
|             # submit it to our controller
 | |
|             self.controller.request_io(iocb)
 | |
| 
 | |
|         # clear the deferred flag
 | |
|         self.launch_deferred = False
 | |
|         if _debug: ToDoList._debug("    - done launching")
 | |
| 
 | |
|         # check for idle
 | |
|         if (not self.active) and (not self.pending):
 | |
|             self.idle()
 | |
| 
 | |
|     def complete(self, iocb):
 | |
|         if _debug: ToDoList._debug("complete %r", iocb)
 | |
| 
 | |
|         # extract the to_do_item
 | |
|         item = iocb._to_do_item
 | |
|         if _debug: ToDoList._debug("    - item: %r", item)
 | |
| 
 | |
|         # mark it completed, remove it from active
 | |
|         item._completed = True
 | |
|         self.active.remove(item)
 | |
| 
 | |
|         # find another to_do_item
 | |
|         if not self.launch_deferred:
 | |
|             if _debug: ToDoList._debug("    - will launch")
 | |
| 
 | |
|             self.launch_deferred = True
 | |
|             deferred(self.launch)
 | |
| 
 | |
|     def idle(self):
 | |
|         if _debug: ToDoList._debug("idle")
 | |
| 
 | |
| #
 | |
| #   SomethingController
 | |
| #
 | |
| 
 | |
| @bacpypes_debugging
 | |
| class SomethingController(IOController):
 | |
| 
 | |
|     def process_io(self, iocb):
 | |
|         if _debug: SomethingController._debug("process_io %r", iocb)
 | |
| 
 | |
|         # simulate taking some time to complete this request
 | |
|         task_delta = random.random() * 3.0
 | |
|         print("{}, {}, {:4.2f}s".format(iocb.args, iocb.kwargs, task_delta))
 | |
| 
 | |
|         task = FunctionTask(self.complete_io, iocb, True)
 | |
|         task.install_task(delta=task_delta)
 | |
|         if _debug: SomethingController._debug("    - task: %r", task)
 | |
| 
 | |
| #
 | |
| #   SomethingToDo
 | |
| #
 | |
| 
 | |
| @bacpypes_debugging
 | |
| class SomethingToDo(ToDoItem):
 | |
| 
 | |
|     def __init__(self, *args, **kwargs):
 | |
|         if _debug: SomethingToDo._debug("__init__")
 | |
|         ToDoItem.__init__(self, _thread=kwargs.get("_thread", None))
 | |
| 
 | |
|         self.args = args
 | |
|         self.kwargs = kwargs
 | |
| 
 | |
|     def prepare(self):
 | |
|         if _debug: SomethingToDo._debug("prepare(%d)", self.args[0])
 | |
| 
 | |
|         # build an IOCB and add the completion callback
 | |
|         iocb = IOCB(*self.args, **self.kwargs)
 | |
|         iocb.add_callback(self.complete)
 | |
|         if _debug: SomethingToDo._debug("    - iocb: %r", iocb)
 | |
| 
 | |
|         return iocb
 | |
| 
 | |
|     def complete(self, iocb):
 | |
|         if _debug: SomethingToDo._debug("complete(%d)", self.args[0])
 | |
| 
 | |
| #
 | |
| #   main
 | |
| #
 | |
| 
 | |
| def main():
 | |
|     global args
 | |
| 
 | |
|     # parse the command line arguments
 | |
|     args = ArgumentParser(description=__doc__).parse_args()
 | |
| 
 | |
|     if _debug: _log.debug("initialization")
 | |
|     if _debug: _log.debug("    - args: %r", args)
 | |
| 
 | |
|     # make a controller for to_do_item requests
 | |
|     controller = SomethingController()
 | |
|     if _debug: _log.debug("    - controller: %r", controller)
 | |
| 
 | |
|     for i in range(3):
 | |
|         # make a list bound to the contoller
 | |
|         to_do_list = ToDoList(controller, active_limit=2)
 | |
|         if _debug: _log.debug("    - to_do_list: %r", to_do_list)
 | |
| 
 | |
|         for j in range(5):
 | |
|             to_do_list.append(SomethingToDo(i, j))
 | |
| 
 | |
|     _log.debug("running")
 | |
| 
 | |
|     run()
 | |
| 
 | |
|     _log.debug("fini")
 | |
| 
 | |
| if __name__ == "__main__":
 | |
|     main()
 | |
| 
 | 
