mirror of
https://github.com/panda3d/panda3d.git
synced 2025-10-04 10:54:24 -04:00
more efficient doLater processing
This commit is contained in:
parent
e7fb534f6b
commit
a0ccebc539
@ -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
26
direct/src/task/TaskTester.py
Executable 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()
|
Loading…
x
Reference in New Issue
Block a user