more efficient doLater processing

This commit is contained in:
Joe Shochet 2003-09-19 03:30:27 +00:00
parent e7fb534f6b
commit a0ccebc539
2 changed files with 109 additions and 38 deletions

View File

@ -64,7 +64,6 @@ class Task:
Task.count += 1 Task.count += 1
self.__call__ = callback self.__call__ = callback
self.__priority = priority self.__priority = priority
self.uponDeath = None
self.dt = 0.0 self.dt = 0.0
self.maxDt = 0.0 self.maxDt = 0.0
self.avgDt = 0.0 self.avgDt = 0.0
@ -81,6 +80,9 @@ class Task:
def remove(self): def remove(self):
self.__removed = 1 self.__removed = 1
# Remove any refs to real objects
# In case we hang around the doLaterList for a while
del self.__call__
def isRemoved(self): def isRemoved(self):
return self.__removed return self.__removed
@ -106,11 +108,12 @@ class Task:
self.pstats = PStatCollector.PStatCollector("App:Show code:" + name) self.pstats = PStatCollector.PStatCollector("App:Show code:" + name)
def finishTask(self, verbose): def finishTask(self, verbose):
if self.uponDeath: if hasattr(self, "uponDeath"):
self.uponDeath(self) self.uponDeath(self)
if verbose: if verbose:
# We regret to announce... # We regret to announce...
messenger.send('TaskManager-removeTask', sentArgs = [self, self.name]) messenger.send('TaskManager-removeTask', sentArgs = [self, self.name])
del self.uponDeath
def __repr__(self): def __repr__(self):
if hasattr(self, 'name'): if hasattr(self, 'name'):
@ -167,7 +170,8 @@ def make_sequence(taskList):
# If we got to the end of the list, this sequence is done # If we got to the end of the list, this sequence is done
if (self.index >= len(self.taskList)): if (self.index >= len(self.taskList)):
assert(TaskManager.notify.debug('sequence done: ' + self.name)) if TaskManager.notify.getDebug():
TaskManager.notify.debug('sequence done: ' + self.name)
frameFinished = 1 frameFinished = 1
taskDoneStatus = done taskDoneStatus = done
@ -273,9 +277,12 @@ class DoLaterList(list):
def __init__(self): def __init__(self):
list.__init__(self) list.__init__(self)
def peek(self):
return self[-1]
def add(self, task): def add(self, task):
""" """
Add task, keeping the list sorted. Add task, keeping the list reverse sorted so we can pop off the end efficiently.
This does a binary search for the index to insert into. This does a binary search for the index to insert into.
Returns the index at which task was inserted. Returns the index at which task was inserted.
""" """
@ -283,13 +290,32 @@ class DoLaterList(list):
hi = len(self) hi = len(self)
while lo < hi: while lo < hi:
mid = (lo+hi)//2 mid = (lo+hi)//2
if task.wakeTime < self[mid].wakeTime: if task.wakeTime > self[mid].wakeTime:
hi = mid hi = mid
else: else:
lo = mid+1 lo = mid+1
list.insert(self, lo, task) list.insert(self, lo, task)
return lo return lo
def remove(self, task):
"""
This does a binary search for the index of the task
Then deletes the entry from the list
"""
lo = 0
hi = len(self)
while lo < hi:
mid = (lo+hi)//2
if task is self[mid]:
del self[mid]
return 1
elif task.wakeTime > self[mid].wakeTime:
hi = mid
else:
lo = mid+1
return 0
class TaskManager: class TaskManager:
notify = None notify = None
@ -311,9 +337,9 @@ class TaskManager:
self.pStatsTasks = 0 self.pStatsTasks = 0
self.resumeFunc = None self.resumeFunc = None
self.fVerbose = 0 self.fVerbose = 0
# Dictionary of task name to list of tasks with that name
self.nameDict = {} self.nameDict = {}
self.add(self.__doLaterProcessor, "doLaterProcessor") self.add(self.__doLaterProcessor, "doLaterProcessor")
# Dictionary of task name to list of tasks with that name
def stepping(self, value): def stepping(self, value):
self.stepping = value self.stepping = value
@ -352,18 +378,16 @@ class TaskManager:
return tasks return tasks
def __doLaterProcessor(self, task): def __doLaterProcessor(self, task):
# Make a temp list of all the dolaters that expired this time # Removing the tasks during the for loop is a bad idea
# through so we can remove them after we are done with the # Instead we just flag them as removed
# for loop. Removing them during the for loop is a bad idea # Later, somebody else cleans them out
while self.doLaterList: while self.doLaterList:
# TODO: because this processor breaks out early, some tasks # Check the first one on the list (actually the last one on
# which have been flagged for removal may stay on the end of # the list since it is reverse sorted)
# the doLaterList longer than expected. One brute force fix dl = self.doLaterList.peek()
# would be to cycle through all tasks removing the ones that
# are flagged each frame.
dl = self.doLaterList[0]
if dl.isRemoved(): if dl.isRemoved():
del self.doLaterList[0] # Get rid of this task forever
self.doLaterList.pop()
dl.setOnDoLaterList(0) dl.setOnDoLaterList(0)
continue continue
# If the time now is less than the start of the doLater + delay # If the time now is less than the start of the doLater + delay
@ -373,8 +397,9 @@ class TaskManager:
# is not ready to go, we can return # is not ready to go, we can return
break break
else: else:
assert(TaskManager.notify.debug('__doLaterProcessor: spawning %s' % (dl))) if TaskManager.notify.getDebug():
del self.doLaterList[0] TaskManager.notify.debug('__doLaterProcessor: spawning %s' % (dl))
self.doLaterList.pop()
dl.setStartTimeFrame(self.currentTime, self.currentFrame) dl.setStartTimeFrame(self.currentTime, self.currentFrame)
# No longer on the doLaterList # No longer on the doLaterList
dl.setOnDoLaterList(0) dl.setOnDoLaterList(0)
@ -383,7 +408,8 @@ class TaskManager:
return cont return cont
def __spawnDoLater(self, task): def __spawnDoLater(self, task):
assert(TaskManager.notify.debug('spawning doLater: %s' % (task))) if TaskManager.notify.getDebug():
TaskManager.notify.debug('spawning doLater: %s' % (task))
# Add this task to the nameDict # Add this task to the nameDict
nameList = self.nameDict.setdefault(task.name, []) nameList = self.nameDict.setdefault(task.name, [])
nameList.append(task) nameList.append(task)
@ -404,7 +430,8 @@ class TaskManager:
return task return task
def doLater(self, delayTime, task, taskName): def doLater(self, delayTime, task, taskName):
assert(TaskManager.notify.debug('doLater: %s' % (taskName))) if TaskManager.notify.getDebug():
TaskManager.notify.debug('doLater: %s' % (taskName))
task.delayTime = delayTime task.delayTime = delayTime
task.name = taskName task.name = taskName
return self.__spawnDoLater(task) return self.__spawnDoLater(task)
@ -418,7 +445,8 @@ class TaskManager:
Add a new task to the taskMgr. Add a new task to the taskMgr.
You can add a Task object or a method that takes one argument. You can add a Task object or a method that takes one argument.
""" """
assert(TaskManager.notify.debug('add: %s' % (name))) if TaskManager.notify.getDebug():
TaskManager.notify.debug('add: %s' % (name))
if isinstance(funcOrTask, Task): if isinstance(funcOrTask, Task):
funcOrTask.setPriority(priority) funcOrTask.setPriority(priority)
return self.__spawnTaskNamed(funcOrTask, name) return self.__spawnTaskNamed(funcOrTask, name)
@ -432,7 +460,8 @@ class TaskManager:
return self.__spawnTaskNamed(task, name) return self.__spawnTaskNamed(task, name)
def __spawnTaskNamed(self, task, name): def __spawnTaskNamed(self, task, name):
assert(TaskManager.notify.debug('__spawnTaskNamed: %s' % (name))) if TaskManager.notify.getDebug():
TaskManager.notify.debug('__spawnTaskNamed: %s' % (name))
# Init params # Init params
task.name = name task.name = name
# be sure to ask the globalClock for the current frame time # be sure to ask the globalClock for the current frame time
@ -447,7 +476,8 @@ class TaskManager:
return task return task
def __addPendingTask(self, task): def __addPendingTask(self, task):
assert(TaskManager.notify.debug('__addPendingTask: %s' % (task.name))) if TaskManager.notify.getDebug():
TaskManager.notify.debug('__addPendingTask: %s' % (task.name))
pri = task.getPriority() pri = task.getPriority()
if self.pendingTaskDict.has_key(pri): if self.pendingTaskDict.has_key(pri):
taskPriList = self.pendingTaskDict[pri] taskPriList = self.pendingTaskDict[pri]
@ -520,7 +550,8 @@ class TaskManager:
standard shell globbing characters like *, ?, and []. standard shell globbing characters like *, ?, and [].
""" """
assert(TaskManager.notify.debug('removing tasks matching: ' + taskPattern)) if TaskManager.notify.getDebug():
TaskManager.notify.debug('removing tasks matching: ' + taskPattern)
num = 0 num = 0
keyList = filter(lambda key: fnmatch.fnmatchcase(key, taskPattern), self.nameDict.keys()) keyList = filter(lambda key: fnmatch.fnmatchcase(key, taskPattern), self.nameDict.keys())
for key in keyList: for key in keyList:
@ -530,7 +561,8 @@ class TaskManager:
def __removeTasksEqual(self, task): def __removeTasksEqual(self, task):
# Remove this task from the nameDict (should be a short list) # Remove this task from the nameDict (should be a short list)
if self.__removeTaskFromNameDict(task): if self.__removeTaskFromNameDict(task):
assert(TaskManager.notify.debug('__removeTasksEqual: removing task: %s' % (task))) if TaskManager.notify.getDebug():
TaskManager.notify.debug('__removeTasksEqual: removing task: %s' % (task))
# Flag the task for removal from the real list # Flag the task for removal from the real list
task.remove() task.remove()
if task.isOnDoLaterList(): if task.isOnDoLaterList():
@ -544,7 +576,8 @@ class TaskManager:
def __removeTasksNamed(self, taskName): def __removeTasksNamed(self, taskName):
if not self.nameDict.has_key(taskName): if not self.nameDict.has_key(taskName):
return 0 return 0
assert(TaskManager.notify.debug('__removeTasksNamed: removing tasks named: %s' % (taskName))) if TaskManager.notify.getDebug():
TaskManager.notify.debug('__removeTasksNamed: removing tasks named: %s' % (taskName))
for task in self.nameDict[taskName]: for task in self.nameDict[taskName]:
# Flag for removal # Flag for removal
task.remove() task.remove()
@ -611,7 +644,11 @@ class TaskManager:
break break
# See if this task has been removed in show code # See if this task has been removed in show code
if task.isRemoved(): if task.isRemoved():
assert(TaskManager.notify.debug('__stepThroughList: task is flagged for removal %s' % (task))) if TaskManager.notify.getDebug():
assert(TaskManager.notify.debug('__stepThroughList: task is flagged for removal %s' % (task)))
# If it was removed in show code, it will need finishTask run
# If it was removed by the taskMgr, it will not, but that is ok
# because finishTask is safe to call twice
task.finishTask(self.fVerbose) task.finishTask(self.fVerbose)
taskPriList.remove(i) taskPriList.remove(i)
# Do not increment the iterator # Do not increment the iterator
@ -623,17 +660,20 @@ class TaskManager:
# Leave it for next frame, its not done yet # Leave it for next frame, its not done yet
pass pass
elif ((ret == done) or (ret == exit) or (ret == None)): elif ((ret == done) or (ret == exit) or (ret == None)):
assert(TaskManager.notify.debug('__stepThroughList: task is finished %s' % (task))) if TaskManager.notify.getDebug():
assert(TaskManager.notify.debug('__stepThroughList: task is finished %s' % (task)))
# Remove the task # Remove the task
if not task.isRemoved(): if not task.isRemoved():
assert(TaskManager.notify.debug('__stepThroughList: task not removed %s' % (task))) if TaskManager.notify.getDebug():
assert(TaskManager.notify.debug('__stepThroughList: task not removed %s' % (task)))
task.remove() task.remove()
# Note: Should not need to remove from doLaterList here because # Note: Should not need to remove from doLaterList here because
# this task is not in the doLaterList # this task is not in the doLaterList
task.finishTask(self.fVerbose) task.finishTask(self.fVerbose)
self.__removeTaskFromNameDict(task) self.__removeTaskFromNameDict(task)
else: else:
assert(TaskManager.notify.debug('__stepThroughList: task already removed %s' % (task))) if TaskManager.notify.getDebug():
assert(TaskManager.notify.debug('__stepThroughList: task already removed %s' % (task)))
self.__removeTaskFromNameDict(task) self.__removeTaskFromNameDict(task)
taskPriList.remove(i) taskPriList.remove(i)
# Do not increment the iterator # Do not increment the iterator
@ -649,12 +689,14 @@ class TaskManager:
for taskList in self.pendingTaskDict.values(): for taskList in self.pendingTaskDict.values():
for task in taskList: for task in taskList:
if (task and not task.isRemoved()): if (task and not task.isRemoved()):
assert(TaskManager.notify.debug('step: moving %s from pending to taskList' % (task.name))) if TaskManager.notify.getDebug():
assert(TaskManager.notify.debug('step: moving %s from pending to taskList' % (task.name)))
self.__addNewTask(task) self.__addNewTask(task)
self.pendingTaskDict.clear() self.pendingTaskDict.clear()
def step(self): def step(self):
assert(TaskManager.notify.debug('step: begin')) if TaskManager.notify.getDebug():
assert(TaskManager.notify.debug('step: begin'))
self.currentTime, self.currentFrame = self.__getTimeFrame() self.currentTime, self.currentFrame = self.__getTimeFrame()
# Replace keyboard interrupt handler during task list processing # Replace keyboard interrupt handler during task list processing
# so we catch the keyboard interrupt but don't handle it until # so we catch the keyboard interrupt but don't handle it until
@ -670,13 +712,15 @@ class TaskManager:
while priIndex < len(self.taskList): while priIndex < len(self.taskList):
taskPriList = self.taskList[priIndex] taskPriList = self.taskList[priIndex]
pri = taskPriList.getPriority() pri = taskPriList.getPriority()
assert(TaskManager.notify.debug('step: running through taskList at pri: %s, priIndex: %s' % (pri, priIndex))) if TaskManager.notify.getDebug():
assert(TaskManager.notify.debug('step: running through taskList at pri: %s, priIndex: %s' % (pri, priIndex)))
self.__stepThroughList(taskPriList) self.__stepThroughList(taskPriList)
# Now see if that generated any pending tasks for this taskPriList # Now see if that generated any pending tasks for this taskPriList
pendingTasks = self.pendingTaskDict.get(pri, []) pendingTasks = self.pendingTaskDict.get(pri, [])
while pendingTasks: while pendingTasks:
assert(TaskManager.notify.debug('step: running through pending tasks at pri: %s' % (pri))) if TaskManager.notify.getDebug():
assert(TaskManager.notify.debug('step: running through pending tasks at pri: %s' % (pri)))
# Remove them from the pendingTaskDict # Remove them from the pendingTaskDict
del self.pendingTaskDict[pri] del self.pendingTaskDict[pri]
# Execute them # Execute them
@ -684,7 +728,8 @@ class TaskManager:
# Add these to the real taskList # Add these to the real taskList
for task in pendingTasks: for task in pendingTasks:
if (task and not task.isRemoved()): if (task and not task.isRemoved()):
assert(TaskManager.notify.debug('step: moving %s from pending to taskList' % (task.name))) if TaskManager.notify.getDebug():
assert(TaskManager.notify.debug('step: moving %s from pending to taskList' % (task.name)))
self.__addNewTask(task) self.__addNewTask(task)
# See if we generated any more for this pri level # See if we generated any more for this pri level
pendingTasks = self.pendingTaskDict.get(pri, []) pendingTasks = self.pendingTaskDict.get(pri, [])

26
direct/src/task/TaskTester.py Executable file
View File

@ -0,0 +1,26 @@
import Task
import random
numTasks = 10000
maxDelay = 20
counter = 0
def spawnNewTask():
global counter
counter = (counter + 1) % 1000
delay = random.random() * maxDelay
taskMgr.doMethodLater(delay, taskCallback, ("taskTester-%s" % counter))
def taskCallback(task):
randNum = int(round(random.random() * 1000))
n = ("taskTester-%s" % randNum)
taskMgr.remove(n)
spawnNewTask()
spawnNewTask()
return Task.done
taskMgr.removeTasksMatching("taskTester*")
for i in range(numTasks):
spawnNewTask()