From 6895e25221cce44c1a4f39a522bcdf17c8adf450 Mon Sep 17 00:00:00 2001 From: Darren Ranalli Date: Mon, 15 Sep 2008 21:47:37 +0000 Subject: [PATCH] added task profiler --- direct/src/showbase/PythonUtil.py | 97 ++- direct/src/task/Task.py | 1091 ++++++++++++++++------------- 2 files changed, 688 insertions(+), 500 deletions(-) diff --git a/direct/src/showbase/PythonUtil.py b/direct/src/showbase/PythonUtil.py index 824ce579f4..d67c5d8719 100644 --- a/direct/src/showbase/PythonUtil.py +++ b/direct/src/showbase/PythonUtil.py @@ -30,7 +30,8 @@ __all__ = ['enumerate', 'unique', 'indent', 'nonRepeatingRandomList', 'nullGen', 'loopGen', 'makeFlywheelGen', 'flywheel', 'choice', 'printStack', 'printReverseStack', 'listToIndex2item', 'listToItem2index', 'pandaBreak','pandaTrace','formatTimeCompact','DestructiveScratchPad', -'deeptype',] +'deeptype','getProfileResultString','StdoutCapture','StdoutPassthrough', +'Averager',] import types import string @@ -794,12 +795,47 @@ def binaryRepr(number, max_length = 32): digits = digits [digits.index (1):] return string.join (map (repr, digits), '') +class StdoutCapture: + # redirects stdout to a string + def __init__(self): + self._oldStdout = sys.stdout + sys.stdout = self + self._string = '' + def destroy(self): + sys.stdout = self._oldStdout + del self._oldStdout + + def getString(self): + return self._string + + # internal + def write(self, string): + self._string = ''.join([self._string, string]) + +class StdoutPassthrough(StdoutCapture): + # like StdoutCapture but also allows output to go through to the OS as normal + + # internal + def write(self, string): + self._string = ''.join([self._string, string]) + self._oldStdout.write(string) + # constant profile defaults PyUtilProfileDefaultFilename = 'profiledata' PyUtilProfileDefaultLines = 80 PyUtilProfileDefaultSorts = ['cumulative', 'time', 'calls'] -def profile(callback, name, terse): +_ProfileResultStr = '' + +def getProfileResultString(): + # if you called profile with 'log' not set to True, + # you can call this function to get the results as + # a string + global _ProfileResultStr + return _ProfileResultStr + +def profile(callback, name, terse, log=True): + global _ProfileResultStr import __builtin__ if 'globalProfileFunc' in __builtin__.__dict__: # rats. Python profiler is not re-entrant... @@ -811,10 +847,20 @@ def profile(callback, name, terse): )) return __builtin__.globalProfileFunc = callback - print '***** START PROFILE: %s *****' % name - startProfile(cmd='globalProfileFunc()', callInfo=(not terse)) - print '***** END PROFILE: %s *****' % name + __builtin__.globalProfileResult = [None] + prefix = '***** START PROFILE: %s *****' % name + if log: + print prefix + startProfile(cmd='globalProfileResult[0]=globalProfileFunc()', callInfo=(not terse), silent=not log) + suffix = '***** END PROFILE: %s *****' % name + if log: + print suffix + else: + _ProfileResultStr = '%s\n%s\n%s' % (prefix, _ProfileResultStr, suffix) + result = globalProfileResult[0] del __builtin__.__dict__['globalProfileFunc'] + del __builtin__.__dict__['globalProfileResult'] + return result def profiled(category=None, terse=False): """ decorator for profiling functions @@ -883,12 +929,14 @@ def startProfile(filename=PyUtilProfileDefaultFilename, filename = '%s.%s' % (filename, randUint31()) import profile profile.run(cmd, filename) - if not silent: + if silent: + extractProfile(filename, lines, sorts, callInfo) + else: printProfile(filename, lines, sorts, callInfo) import os os.remove(filename) -# call this to see the results again +# call these to see the results again, as a string or in the log def printProfile(filename=PyUtilProfileDefaultFilename, lines=PyUtilProfileDefaultLines, sorts=PyUtilProfileDefaultSorts, @@ -903,6 +951,18 @@ def printProfile(filename=PyUtilProfileDefaultFilename, s.print_callees(lines) s.print_callers(lines) +# same args as printProfile +def extractProfile(*args, **kArgs): + global _ProfileResultStr + # capture print output + sc = StdoutCapture() + # print the profile output, redirected to the result string + printProfile(*args, **kArgs) + # make a copy of the print output + _ProfileResultStr = sc.getString() + # restore stdout to what it was before + sc.destroy() + def getSetterName(valueName, prefix='set'): # getSetterName('color') -> 'setColor' # getSetterName('color', 'get') -> 'getColor' @@ -927,8 +987,14 @@ class Functor: self._function = function self._args = args self._kargs = kargs - self.__name__ = self._function.__name__ - self.__doc__ = self._function.__doc__ + if hasattr(self._function, '__name__'): + self.__name__ = self._function.__name__ + else: + self.__name__ = str(itype(self._function)) + if hasattr(self._function, '__doc__'): + self.__doc__ = self._function.__doc__ + else: + self.__doc__ = self.__name__ def destroy(self): del self._function @@ -1714,6 +1780,19 @@ def average(*args): val += arg return val / len(args) +class Averager: + def __init__(self, name): + self._name = name + self._total = 0. + self._count = 0 + def addValue(self, value): + self._total += value + self._count += 1 + def getAverage(self): + return self._total / self._count + def getCount(self): + return self._count + def addListsByValue(a, b): """ returns a new array containing the sums of the two array arguments diff --git a/direct/src/task/Task.py b/direct/src/task/Task.py index 4616d90171..c2175106ac 100644 --- a/direct/src/task/Task.py +++ b/direct/src/task/Task.py @@ -19,6 +19,7 @@ import time import fnmatch import string import signal +import random try: Dtool_PreloadDLL("libp3heapq") from libp3heapq import heappush, heappop, heapify @@ -161,6 +162,13 @@ class Task: self.time = currentTime - self.starttime self.frame = currentFrame - self.startframe + def getNamePattern(self, taskName=None): + # get a version of the task name that doesn't contain any numbers + digits = '0123456789' + if taskName is None: + taskName = self.name + return ''.join([c for c in taskName if c not in digits]) + def setupPStats(self): if __debug__ and TaskManager.taskTimerVerbose and not self.pstats: # Get the PStats name for the task. By convention, @@ -358,6 +366,8 @@ class TaskManager: # multiple of average frame duration DefTaskDurationWarningThreshold = 40. + _DidTests = False + def __init__(self): self.running = 0 self.stepping = 0 @@ -370,6 +380,12 @@ class TaskManager: self._profileFrames = False self.MaxEpockSpeed = 1.0/30.0; + # this will be set when it's safe to import StateVar + self._profileTasks = None + self._taskProfiler = None + self._profileTaskId = None + self._profileDt = None + self._lastProfileResultString = None # We copy this value in from __builtins__ when it gets set. # But since the TaskManager might have to run before it gets @@ -404,6 +420,8 @@ class TaskManager: self.add(self.__doLaterProcessor, "doLaterProcessor", -10) def destroy(self): + if self._taskProfiler: + self._taskProfiler.destroy() del self.nameDict del self.trueClock del self.globalClock @@ -722,15 +740,27 @@ class TaskManager: def __executeTask(self, task): task.setCurrentTimeFrame(self.currentTime, self.currentFrame) + + doProfile = (task.id == self._profileTaskId) + if not self.taskTimerVerbose: startTime = self.trueClock.getShortTime() # don't record timing info - ret = task(*task.extraArgs) + if doProfile: + ret = profile(Functor(task, *task.extraArgs), + 'TASK_PROFILE:%s' % task.name, True, log=False) + else: + ret = task(*task.extraArgs) endTime = self.trueClock.getShortTime() # Record the dt dt = endTime - startTime + if doProfile: + # if we profiled, record the measured duration but don't pollute the task's + # normal duration + self._profileDt = dt + dt = task.avgDt task.dt = dt else: @@ -738,13 +768,22 @@ class TaskManager: if task.pstats: task.pstats.start() startTime = self.trueClock.getShortTime() - ret = task(*task.extraArgs) + if doProfile: + ret = profile(Functor(task, *task.extraArgs), + 'profiled-task-%s' % task.name, True, log=False) + else: + ret = task(*task.extraArgs) endTime = self.trueClock.getShortTime() if task.pstats: task.pstats.stop() # Record the dt dt = endTime - startTime + if doProfile: + # if we profiled, record the measured duration but don't pollute the task's + # normal duration + self._profileDt = dt + dt = task.avgDt task.dt = dt # See if this is the new max @@ -758,6 +797,9 @@ class TaskManager: else: task.avgDt = 0 + if doProfile: + self._lastProfileResultString = self._getProfileResultString() + # warn if the task took too long if self.warnTaskDuration and self.globalClock: avgFrameRate = self.globalClock.getAverageFrameRate() @@ -889,6 +931,65 @@ class TaskManager: result = self.step(*args, **kArgs) return result + def getProfileTasks(self): + return self._profileTasks.get() + + def getProfileTasksSV(self): + return self._profileTasks + + def setProfileTasks(self, profileTasks): + self._profileTasks.set(profileTasks) + if (not self._taskProfiler) and profileTasks: + # import here due to import dependencies + from direct.task.TaskProfiler import TaskProfiler + self._taskProfiler = TaskProfiler() + + def _setProfileTask(self, task): + self._profileTaskId = task.id + self._profileDt = None + self._lastProfileResultString = None + + def _getTaskProfileDt(self): + return self._profileDt + + def _getLastProfileResultString(self): + return self._lastProfileResultString + + def _getRandomTask(self): + numTasks = 0 + for name in self.nameDict.iterkeys(): + numTasks += len(self.nameDict[name]) + numDoLaters = len(self.__doLaterList) + if random.random() < (numDoLaters / float(numTasks + numDoLaters)): + # grab a doLater that will most likely trigger in the next frame + tNow = globalClock.getFrameTime() + avgFrameRate = globalClock.getAverageFrameRate() + if avgFrameRate < .00001: + avgFrameDur = 0. + else: + avgFrameDur = (1. / globalClock.getAverageFrameRate()) + tNext = tNow + avgFrameDur + # binary search to find doLaters that are likely to trigger on the next frame + curIndex = int(numDoLaters / 2) + rangeStart = 0 + rangeEnd = numDoLaters + while True: + if tNext < self.__doLaterList[curIndex].wakeTime: + rangeEnd = curIndex + else: + rangeStart = curIndex + prevIndex = curIndex + curIndex = int((rangeStart + rangeEnd) / 2) + if curIndex == prevIndex: + break + index = curIndex + task = self.__doLaterList[random.randrange(index+1)] + else: + # grab a task + name = random.choice(self.nameDict.keys()) + task = random.choice(self.nameDict[name]) + return task + def step(self): # assert TaskManager.notify.debug('step: begin') self.currentTime, self.currentFrame = self.__getTimeFrame() @@ -952,6 +1053,16 @@ class TaskManager: def run(self): + # do things that couldn't be done earlier because of import dependencies + if (not TaskManager._DidTests) and __debug__: + TaskManager._DidTests = True + self._runTests() + + if not self._profileTasks: + from direct.fsm.StatePush import StateVar + self._profileTasks = StateVar(False) + self.setProfileTasks(getBase().config.GetBool('profile-task-spikes', 0)) + # Set the clock to have last frame's time in case we were # Paused at the prompt for a long time if self.globalClock: @@ -1279,6 +1390,492 @@ class TaskManager: dtfmt % (totalAvgDt*1000),)) return cont + def _runTests(self): + if __debug__: + tm = TaskManager() + # looks like nothing runs on the first frame...? + # step to get past the first frame + tm.step() + + # check for memory leaks after every test + tm._startTrackingMemLeaks() + tm._checkMemLeaks() + + # run-once task + l = [] + def _testDone(task, l=l): + l.append(None) + return task.done + tm.add(_testDone, 'testDone') + tm.step() + assert len(l) == 1 + tm.step() + assert len(l) == 1 + _testDone = None + tm._checkMemLeaks() + + # remove by name + def _testRemoveByName(task): + return task.done + tm.add(_testRemoveByName, 'testRemoveByName') + assert tm.remove('testRemoveByName') == 1 + assert tm.remove('testRemoveByName') == 0 + _testRemoveByName = None + tm._checkMemLeaks() + + # duplicate named tasks + def _testDupNamedTasks(task): + return task.done + tm.add(_testDupNamedTasks, 'testDupNamedTasks') + tm.add(_testDupNamedTasks, 'testDupNamedTasks') + assert tm.remove('testRemoveByName') == 0 + _testDupNamedTasks = None + tm._checkMemLeaks() + + # continued task + l = [] + def _testCont(task, l = l): + l.append(None) + return task.cont + tm.add(_testCont, 'testCont') + tm.step() + assert len(l) == 1 + tm.step() + assert len(l) == 2 + tm.remove('testCont') + _testCont = None + tm._checkMemLeaks() + + # continue until done task + l = [] + def _testContDone(task, l = l): + l.append(None) + if len(l) >= 2: + return task.done + else: + return task.cont + tm.add(_testContDone, 'testContDone') + tm.step() + assert len(l) == 1 + tm.step() + assert len(l) == 2 + tm.step() + assert len(l) == 2 + assert not tm.hasTaskNamed('testContDone') + _testContDone = None + tm._checkMemLeaks() + + # hasTaskNamed + def _testHasTaskNamed(task): + return task.done + tm.add(_testHasTaskNamed, 'testHasTaskNamed') + assert tm.hasTaskNamed('testHasTaskNamed') + tm.step() + assert not tm.hasTaskNamed('testHasTaskNamed') + _testHasTaskNamed = None + tm._checkMemLeaks() + + # task priority + l = [] + def _testPri1(task, l = l): + l.append(1) + return task.cont + def _testPri2(task, l = l): + l.append(2) + return task.cont + tm.add(_testPri1, 'testPri1', priority = 1) + tm.add(_testPri2, 'testPri2', priority = 2) + tm.step() + assert len(l) == 2 + assert l == [1, 2,] + tm.step() + assert len(l) == 4 + assert l == [1, 2, 1, 2,] + tm.remove('testPri1') + tm.remove('testPri2') + _testPri1 = None + _testPri2 = None + tm._checkMemLeaks() + + # task extraArgs + l = [] + def _testExtraArgs(arg1, arg2, l=l): + l.extend([arg1, arg2,]) + return done + tm.add(_testExtraArgs, 'testExtraArgs', extraArgs=[4,5]) + tm.step() + assert len(l) == 2 + assert l == [4, 5,] + _testExtraArgs = None + tm._checkMemLeaks() + + # task appendTask + l = [] + def _testAppendTask(arg1, arg2, task, l=l): + l.extend([arg1, arg2,]) + return task.done + tm.add(_testAppendTask, '_testAppendTask', extraArgs=[4,5], appendTask=True) + tm.step() + assert len(l) == 2 + assert l == [4, 5,] + _testAppendTask = None + tm._checkMemLeaks() + + # task uponDeath + l = [] + def _uponDeathFunc(task, l=l): + l.append(task.name) + def _testUponDeath(task): + return done + tm.add(_testUponDeath, 'testUponDeath', uponDeath=_uponDeathFunc) + tm.step() + assert len(l) == 1 + assert l == ['testUponDeath'] + _testUponDeath = None + _uponDeathFunc = None + tm._checkMemLeaks() + + # task owner + class _TaskOwner: + def _clearTask(self, task): + self.clearedTaskName = task.name + to = _TaskOwner() + l = [] + def _testOwner(task): + return done + tm.add(_testOwner, 'testOwner', owner=to) + tm.step() + assert hasattr(to, 'clearedTaskName') + assert to.clearedTaskName == 'testOwner' + _testOwner = None + del to + _TaskOwner = None + tm._checkMemLeaks() + + + doLaterTests = [0,] + + # doLater + l = [] + def _testDoLater1(task, l=l): + l.append(1) + def _testDoLater2(task, l=l): + l.append(2) + def _monitorDoLater(task, tm=tm, l=l, doLaterTests=doLaterTests): + if task.time > .03: + assert l == [1, 2,] + doLaterTests[0] -= 1 + return task.done + return task.cont + tm.doMethodLater(.01, _testDoLater1, 'testDoLater1') + tm.doMethodLater(.02, _testDoLater2, 'testDoLater2') + doLaterTests[0] += 1 + # make sure we run this task after the doLaters if they all occur on the same frame + tm.add(_monitorDoLater, 'monitorDoLater', priority=10) + _testDoLater1 = None + _testDoLater2 = None + _monitorDoLater = None + # don't check until all the doLaters are finished + #tm._checkMemLeaks() + + # doLater priority + l = [] + def _testDoLaterPri1(task, l=l): + l.append(1) + def _testDoLaterPri2(task, l=l): + l.append(2) + def _monitorDoLaterPri(task, tm=tm, l=l, doLaterTests=doLaterTests): + if task.time > .02: + assert l == [1, 2,] + doLaterTests[0] -= 1 + return task.done + return task.cont + tm.doMethodLater(.01, _testDoLaterPri1, 'testDoLaterPri1', priority=1) + tm.doMethodLater(.01, _testDoLaterPri2, 'testDoLaterPri2', priority=2) + doLaterTests[0] += 1 + # make sure we run this task after the doLaters if they all occur on the same frame + tm.add(_monitorDoLaterPri, 'monitorDoLaterPri', priority=10) + _testDoLaterPri1 = None + _testDoLaterPri2 = None + _monitorDoLaterPri = None + # don't check until all the doLaters are finished + #tm._checkMemLeaks() + + # doLater extraArgs + l = [] + def _testDoLaterExtraArgs(arg1, l=l): + l.append(arg1) + def _monitorDoLaterExtraArgs(task, tm=tm, l=l, doLaterTests=doLaterTests): + if task.time > .02: + assert l == [3,] + doLaterTests[0] -= 1 + return task.done + return task.cont + tm.doMethodLater(.01, _testDoLaterExtraArgs, 'testDoLaterExtraArgs', extraArgs=[3,]) + doLaterTests[0] += 1 + # make sure we run this task after the doLaters if they all occur on the same frame + tm.add(_monitorDoLaterExtraArgs, 'monitorDoLaterExtraArgs', priority=10) + _testDoLaterExtraArgs = None + _monitorDoLaterExtraArgs = None + # don't check until all the doLaters are finished + #tm._checkMemLeaks() + + # doLater appendTask + l = [] + def _testDoLaterAppendTask(arg1, task, l=l): + assert task.name == 'testDoLaterAppendTask' + l.append(arg1) + def _monitorDoLaterAppendTask(task, tm=tm, l=l, doLaterTests=doLaterTests): + if task.time > .02: + assert l == [4,] + doLaterTests[0] -= 1 + return task.done + return task.cont + tm.doMethodLater(.01, _testDoLaterAppendTask, 'testDoLaterAppendTask', + extraArgs=[4,], appendTask=True) + doLaterTests[0] += 1 + # make sure we run this task after the doLaters if they all occur on the same frame + tm.add(_monitorDoLaterAppendTask, 'monitorDoLaterAppendTask', priority=10) + _testDoLaterAppendTask = None + _monitorDoLaterAppendTask = None + # don't check until all the doLaters are finished + #tm._checkMemLeaks() + + # doLater uponDeath + l = [] + def _testUponDeathFunc(task, l=l): + assert task.name == 'testDoLaterUponDeath' + l.append(10) + def _testDoLaterUponDeath(arg1, l=l): + return done + def _monitorDoLaterUponDeath(task, tm=tm, l=l, doLaterTests=doLaterTests): + if task.time > .02: + assert l == [10,] + doLaterTests[0] -= 1 + return task.done + return task.cont + tm.doMethodLater(.01, _testDoLaterUponDeath, 'testDoLaterUponDeath', + uponDeath=_testUponDeathFunc) + doLaterTests[0] += 1 + # make sure we run this task after the doLaters if they all occur on the same frame + tm.add(_monitorDoLaterUponDeath, 'monitorDoLaterUponDeath', priority=10) + _testUponDeathFunc = None + _testDoLaterUponDeath = None + _monitorDoLaterUponDeath = None + # don't check until all the doLaters are finished + #tm._checkMemLeaks() + + # doLater owner + class _DoLaterOwner: + def _clearTask(self, task): + self.clearedTaskName = task.name + doLaterOwner = _DoLaterOwner() + l = [] + def _testDoLaterOwner(l=l): + pass + def _monitorDoLaterOwner(task, tm=tm, l=l, doLaterOwner=doLaterOwner, + doLaterTests=doLaterTests): + if task.time > .02: + assert hasattr(doLaterOwner, 'clearedTaskName') + assert doLaterOwner.clearedTaskName == 'testDoLaterOwner' + doLaterTests[0] -= 1 + return task.done + return task.cont + tm.doMethodLater(.01, _testDoLaterOwner, 'testDoLaterOwner', + owner=doLaterOwner) + doLaterTests[0] += 1 + # make sure we run this task after the doLaters if they all occur on the same frame + tm.add(_monitorDoLaterOwner, 'monitorDoLaterOwner', priority=10) + _testDoLaterOwner = None + _monitorDoLaterOwner = None + del doLaterOwner + _DoLaterOwner = None + # don't check until all the doLaters are finished + #tm._checkMemLeaks() + + # run the doLater tests + while doLaterTests[0] > 0: + tm.step() + del doLaterTests + tm._checkMemLeaks() + + # getTasks + def _testGetTasks(task): + return task.cont + # the doLaterProcessor is always running + assert len(tm.getTasks()) == 1 + tm.add(_testGetTasks, 'testGetTasks1') + assert len(tm.getTasks()) == 2 + assert (tm.getTasks()[0].name == 'testGetTasks1' or + tm.getTasks()[1].name == 'testGetTasks1') + tm.add(_testGetTasks, 'testGetTasks2') + tm.add(_testGetTasks, 'testGetTasks3') + assert len(tm.getTasks()) == 4 + tm.remove('testGetTasks2') + assert len(tm.getTasks()) == 3 + tm.remove('testGetTasks1') + tm.remove('testGetTasks3') + assert len(tm.getTasks()) == 1 + _testGetTasks = None + tm._checkMemLeaks() + + # getDoLaters + def _testGetDoLaters(): + pass + # the doLaterProcessor is always running + assert len(tm.getDoLaters()) == 0 + tm.doMethodLater(.1, _testGetDoLaters, 'testDoLater1') + assert len(tm.getDoLaters()) == 1 + assert tm.getDoLaters()[0].name == 'testDoLater1' + tm.doMethodLater(.1, _testGetDoLaters, 'testDoLater2') + tm.doMethodLater(.1, _testGetDoLaters, 'testDoLater3') + assert len(tm.getDoLaters()) == 3 + tm.remove('testDoLater2') + assert len(tm.getDoLaters()) == 2 + tm.remove('testDoLater1') + tm.remove('testDoLater3') + assert len(tm.getDoLaters()) == 0 + _testGetDoLaters = None + tm._checkMemLeaks() + + # duplicate named doLaters removed via taskMgr.remove + def _testDupNameDoLaters(): + pass + # the doLaterProcessor is always running + tm.doMethodLater(.1, _testDupNameDoLaters, 'testDupNameDoLater') + tm.doMethodLater(.1, _testDupNameDoLaters, 'testDupNameDoLater') + assert len(tm.getDoLaters()) == 2 + tm.remove('testDupNameDoLater') + assert len(tm.getDoLaters()) == 0 + _testDupNameDoLaters = None + tm._checkMemLeaks() + + # duplicate named doLaters removed via remove() + def _testDupNameDoLatersRemove(): + pass + # the doLaterProcessor is always running + dl1 = tm.doMethodLater(.1, _testDupNameDoLatersRemove, 'testDupNameDoLaterRemove') + dl2 = tm.doMethodLater(.1, _testDupNameDoLatersRemove, 'testDupNameDoLaterRemove') + assert len(tm.getDoLaters()) == 2 + dl2.remove() + assert len(tm.getDoLaters()) == 1 + dl1.remove() + assert len(tm.getDoLaters()) == 0 + _testDupNameDoLatersRemove = None + # nameDict etc. isn't cleared out right away with task.remove() + tm._checkMemLeaks() + + # getTasksNamed + def _testGetTasksNamed(task): + return task.cont + assert len(tm.getTasksNamed('testGetTasksNamed')) == 0 + tm.add(_testGetTasksNamed, 'testGetTasksNamed') + assert len(tm.getTasksNamed('testGetTasksNamed')) == 1 + assert tm.getTasksNamed('testGetTasksNamed')[0].name == 'testGetTasksNamed' + tm.add(_testGetTasksNamed, 'testGetTasksNamed') + tm.add(_testGetTasksNamed, 'testGetTasksNamed') + assert len(tm.getTasksNamed('testGetTasksNamed')) == 3 + tm.remove('testGetTasksNamed') + assert len(tm.getTasksNamed('testGetTasksNamed')) == 0 + _testGetTasksNamed = None + tm._checkMemLeaks() + + # removeTasksMatching + def _testRemoveTasksMatching(task): + return task.cont + tm.add(_testRemoveTasksMatching, 'testRemoveTasksMatching') + assert len(tm.getTasksNamed('testRemoveTasksMatching')) == 1 + tm.removeTasksMatching('testRemoveTasksMatching') + assert len(tm.getTasksNamed('testRemoveTasksMatching')) == 0 + tm.add(_testRemoveTasksMatching, 'testRemoveTasksMatching1') + tm.add(_testRemoveTasksMatching, 'testRemoveTasksMatching2') + assert len(tm.getTasksNamed('testRemoveTasksMatching1')) == 1 + assert len(tm.getTasksNamed('testRemoveTasksMatching2')) == 1 + tm.removeTasksMatching('testRemoveTasksMatching*') + assert len(tm.getTasksNamed('testRemoveTasksMatching1')) == 0 + assert len(tm.getTasksNamed('testRemoveTasksMatching2')) == 0 + tm.add(_testRemoveTasksMatching, 'testRemoveTasksMatching1a') + tm.add(_testRemoveTasksMatching, 'testRemoveTasksMatching2a') + assert len(tm.getTasksNamed('testRemoveTasksMatching1a')) == 1 + assert len(tm.getTasksNamed('testRemoveTasksMatching2a')) == 1 + tm.removeTasksMatching('testRemoveTasksMatching?a') + assert len(tm.getTasksNamed('testRemoveTasksMatching1a')) == 0 + assert len(tm.getTasksNamed('testRemoveTasksMatching2a')) == 0 + _testRemoveTasksMatching = None + tm._checkMemLeaks() + + # create Task object and add to mgr + l = [] + def _testTaskObj(task, l=l): + l.append(None) + return task.cont + t = Task(_testTaskObj) + tm.add(t, 'testTaskObj') + tm.step() + assert len(l) == 1 + tm.step() + assert len(l) == 2 + tm.remove('testTaskObj') + tm.step() + assert len(l) == 2 + _testTaskObj = None + tm._checkMemLeaks() + + # remove Task via task.remove() + l = [] + def _testTaskObjRemove(task, l=l): + l.append(None) + return task.cont + t = Task(_testTaskObjRemove) + tm.add(t, 'testTaskObjRemove') + tm.step() + assert len(l) == 1 + tm.step() + assert len(l) == 2 + t.remove() + tm.step() + assert len(l) == 2 + del t + _testTaskObjRemove = None + tm._checkMemLeaks() + + """ + # this test fails, and it's not clear what the correct behavior should be. + # priority passed to Task.__init__ is always overridden by taskMgr.add() + # even if no priority is specified, and calling Task.setPriority() has no + # effect on the taskMgr's behavior. + # set/get Task priority + l = [] + def _testTaskObjPriority(arg, task, l=l): + l.append(arg) + return task.cont + t1 = Task(_testTaskObjPriority, priority=1) + t2 = Task(_testTaskObjPriority, priority=2) + tm.add(t1, 'testTaskObjPriority1', extraArgs=['a',], appendTask=True) + tm.add(t2, 'testTaskObjPriority2', extraArgs=['b',], appendTask=True) + tm.step() + assert len(l) == 2 + assert l == ['a', 'b'] + assert t1.getPriority() == 1 + assert t2.getPriority() == 2 + t1.setPriority(3) + assert t1.getPriority() == 3 + tm.step() + assert len(l) == 4 + assert l == ['a', 'b', 'b', 'a',] + t1.remove() + t2.remove() + tm.step() + assert len(l) == 4 + del t1 + del t2 + _testTaskObjPriority = None + tm._checkMemLeaks() + """ + + del l + tm.destroy() + del tm # These constants are moved to the top level of the module, @@ -1292,495 +1889,7 @@ again = Task.again if __debug__: - # keep everything in a function namespace so it's easier to clean up - def runTests(): - tm = TaskManager() - # looks like nothing runs on the first frame...? - # step to get past the first frame - tm.step() - - # check for memory leaks after every test - tm._startTrackingMemLeaks() - tm._checkMemLeaks() - - # run-once task - l = [] - def _testDone(task, l=l): - l.append(None) - return task.done - tm.add(_testDone, 'testDone') - tm.step() - assert len(l) == 1 - tm.step() - assert len(l) == 1 - _testDone = None - tm._checkMemLeaks() - - # remove by name - def _testRemoveByName(task): - return task.done - tm.add(_testRemoveByName, 'testRemoveByName') - assert tm.remove('testRemoveByName') == 1 - assert tm.remove('testRemoveByName') == 0 - _testRemoveByName = None - tm._checkMemLeaks() - - # duplicate named tasks - def _testDupNamedTasks(task): - return task.done - tm.add(_testDupNamedTasks, 'testDupNamedTasks') - tm.add(_testDupNamedTasks, 'testDupNamedTasks') - assert tm.remove('testRemoveByName') == 0 - _testDupNamedTasks = None - tm._checkMemLeaks() - - # continued task - l = [] - def _testCont(task, l = l): - l.append(None) - return task.cont - tm.add(_testCont, 'testCont') - tm.step() - assert len(l) == 1 - tm.step() - assert len(l) == 2 - tm.remove('testCont') - _testCont = None - tm._checkMemLeaks() - - # continue until done task - l = [] - def _testContDone(task, l = l): - l.append(None) - if len(l) >= 2: - return task.done - else: - return task.cont - tm.add(_testContDone, 'testContDone') - tm.step() - assert len(l) == 1 - tm.step() - assert len(l) == 2 - tm.step() - assert len(l) == 2 - assert not tm.hasTaskNamed('testContDone') - _testContDone = None - tm._checkMemLeaks() - - # hasTaskNamed - def _testHasTaskNamed(task): - return task.done - tm.add(_testHasTaskNamed, 'testHasTaskNamed') - assert tm.hasTaskNamed('testHasTaskNamed') - tm.step() - assert not tm.hasTaskNamed('testHasTaskNamed') - _testHasTaskNamed = None - tm._checkMemLeaks() - - # task priority - l = [] - def _testPri1(task, l = l): - l.append(1) - return task.cont - def _testPri2(task, l = l): - l.append(2) - return task.cont - tm.add(_testPri1, 'testPri1', priority = 1) - tm.add(_testPri2, 'testPri2', priority = 2) - tm.step() - assert len(l) == 2 - assert l == [1, 2,] - tm.step() - assert len(l) == 4 - assert l == [1, 2, 1, 2,] - tm.remove('testPri1') - tm.remove('testPri2') - _testPri1 = None - _testPri2 = None - tm._checkMemLeaks() - - # task extraArgs - l = [] - def _testExtraArgs(arg1, arg2, l=l): - l.extend([arg1, arg2,]) - return done - tm.add(_testExtraArgs, 'testExtraArgs', extraArgs=[4,5]) - tm.step() - assert len(l) == 2 - assert l == [4, 5,] - _testExtraArgs = None - tm._checkMemLeaks() - - # task appendTask - l = [] - def _testAppendTask(arg1, arg2, task, l=l): - l.extend([arg1, arg2,]) - return task.done - tm.add(_testAppendTask, '_testAppendTask', extraArgs=[4,5], appendTask=True) - tm.step() - assert len(l) == 2 - assert l == [4, 5,] - _testAppendTask = None - tm._checkMemLeaks() - - # task uponDeath - l = [] - def _uponDeathFunc(task, l=l): - l.append(task.name) - def _testUponDeath(task): - return done - tm.add(_testUponDeath, 'testUponDeath', uponDeath=_uponDeathFunc) - tm.step() - assert len(l) == 1 - assert l == ['testUponDeath'] - _testUponDeath = None - _uponDeathFunc = None - tm._checkMemLeaks() - - # task owner - class _TaskOwner: - def _clearTask(self, task): - self.clearedTaskName = task.name - to = _TaskOwner() - l = [] - def _testOwner(task): - return done - tm.add(_testOwner, 'testOwner', owner=to) - tm.step() - assert hasattr(to, 'clearedTaskName') - assert to.clearedTaskName == 'testOwner' - _testOwner = None - del to - _TaskOwner = None - tm._checkMemLeaks() - - - doLaterTests = [0,] - - # doLater - l = [] - def _testDoLater1(task, l=l): - l.append(1) - def _testDoLater2(task, l=l): - l.append(2) - def _monitorDoLater(task, tm=tm, l=l, doLaterTests=doLaterTests): - if task.time > .03: - assert l == [1, 2,] - doLaterTests[0] -= 1 - return task.done - return task.cont - tm.doMethodLater(.01, _testDoLater1, 'testDoLater1') - tm.doMethodLater(.02, _testDoLater2, 'testDoLater2') - doLaterTests[0] += 1 - # make sure we run this task after the doLaters if they all occur on the same frame - tm.add(_monitorDoLater, 'monitorDoLater', priority=10) - _testDoLater1 = None - _testDoLater2 = None - _monitorDoLater = None - # don't check until all the doLaters are finished - #tm._checkMemLeaks() - - # doLater priority - l = [] - def _testDoLaterPri1(task, l=l): - l.append(1) - def _testDoLaterPri2(task, l=l): - l.append(2) - def _monitorDoLaterPri(task, tm=tm, l=l, doLaterTests=doLaterTests): - if task.time > .02: - assert l == [1, 2,] - doLaterTests[0] -= 1 - return task.done - return task.cont - tm.doMethodLater(.01, _testDoLaterPri1, 'testDoLaterPri1', priority=1) - tm.doMethodLater(.01, _testDoLaterPri2, 'testDoLaterPri2', priority=2) - doLaterTests[0] += 1 - # make sure we run this task after the doLaters if they all occur on the same frame - tm.add(_monitorDoLaterPri, 'monitorDoLaterPri', priority=10) - _testDoLaterPri1 = None - _testDoLaterPri2 = None - _monitorDoLaterPri = None - # don't check until all the doLaters are finished - #tm._checkMemLeaks() - - # doLater extraArgs - l = [] - def _testDoLaterExtraArgs(arg1, l=l): - l.append(arg1) - def _monitorDoLaterExtraArgs(task, tm=tm, l=l, doLaterTests=doLaterTests): - if task.time > .02: - assert l == [3,] - doLaterTests[0] -= 1 - return task.done - return task.cont - tm.doMethodLater(.01, _testDoLaterExtraArgs, 'testDoLaterExtraArgs', extraArgs=[3,]) - doLaterTests[0] += 1 - # make sure we run this task after the doLaters if they all occur on the same frame - tm.add(_monitorDoLaterExtraArgs, 'monitorDoLaterExtraArgs', priority=10) - _testDoLaterExtraArgs = None - _monitorDoLaterExtraArgs = None - # don't check until all the doLaters are finished - #tm._checkMemLeaks() - - # doLater appendTask - l = [] - def _testDoLaterAppendTask(arg1, task, l=l): - assert task.name == 'testDoLaterAppendTask' - l.append(arg1) - def _monitorDoLaterAppendTask(task, tm=tm, l=l, doLaterTests=doLaterTests): - if task.time > .02: - assert l == [4,] - doLaterTests[0] -= 1 - return task.done - return task.cont - tm.doMethodLater(.01, _testDoLaterAppendTask, 'testDoLaterAppendTask', - extraArgs=[4,], appendTask=True) - doLaterTests[0] += 1 - # make sure we run this task after the doLaters if they all occur on the same frame - tm.add(_monitorDoLaterAppendTask, 'monitorDoLaterAppendTask', priority=10) - _testDoLaterAppendTask = None - _monitorDoLaterAppendTask = None - # don't check until all the doLaters are finished - #tm._checkMemLeaks() - - # doLater uponDeath - l = [] - def _testUponDeathFunc(task, l=l): - assert task.name == 'testDoLaterUponDeath' - l.append(10) - def _testDoLaterUponDeath(arg1, l=l): - return done - def _monitorDoLaterUponDeath(task, tm=tm, l=l, doLaterTests=doLaterTests): - if task.time > .02: - assert l == [10,] - doLaterTests[0] -= 1 - return task.done - return task.cont - tm.doMethodLater(.01, _testDoLaterUponDeath, 'testDoLaterUponDeath', - uponDeath=_testUponDeathFunc) - doLaterTests[0] += 1 - # make sure we run this task after the doLaters if they all occur on the same frame - tm.add(_monitorDoLaterUponDeath, 'monitorDoLaterUponDeath', priority=10) - _testUponDeathFunc = None - _testDoLaterUponDeath = None - _monitorDoLaterUponDeath = None - # don't check until all the doLaters are finished - #tm._checkMemLeaks() - - # doLater owner - class _DoLaterOwner: - def _clearTask(self, task): - self.clearedTaskName = task.name - doLaterOwner = _DoLaterOwner() - l = [] - def _testDoLaterOwner(l=l): - pass - def _monitorDoLaterOwner(task, tm=tm, l=l, doLaterOwner=doLaterOwner, - doLaterTests=doLaterTests): - if task.time > .02: - assert hasattr(doLaterOwner, 'clearedTaskName') - assert doLaterOwner.clearedTaskName == 'testDoLaterOwner' - doLaterTests[0] -= 1 - return task.done - return task.cont - tm.doMethodLater(.01, _testDoLaterOwner, 'testDoLaterOwner', - owner=doLaterOwner) - doLaterTests[0] += 1 - # make sure we run this task after the doLaters if they all occur on the same frame - tm.add(_monitorDoLaterOwner, 'monitorDoLaterOwner', priority=10) - _testDoLaterOwner = None - _monitorDoLaterOwner = None - del doLaterOwner - _DoLaterOwner = None - # don't check until all the doLaters are finished - #tm._checkMemLeaks() - - # run the doLater tests - while doLaterTests[0] > 0: - tm.step() - del doLaterTests - tm._checkMemLeaks() - - # getTasks - def _testGetTasks(task): - return task.cont - # the doLaterProcessor is always running - assert len(tm.getTasks()) == 1 - tm.add(_testGetTasks, 'testGetTasks1') - assert len(tm.getTasks()) == 2 - assert (tm.getTasks()[0].name == 'testGetTasks1' or - tm.getTasks()[1].name == 'testGetTasks1') - tm.add(_testGetTasks, 'testGetTasks2') - tm.add(_testGetTasks, 'testGetTasks3') - assert len(tm.getTasks()) == 4 - tm.remove('testGetTasks2') - assert len(tm.getTasks()) == 3 - tm.remove('testGetTasks1') - tm.remove('testGetTasks3') - assert len(tm.getTasks()) == 1 - _testGetTasks = None - tm._checkMemLeaks() - - # getDoLaters - def _testGetDoLaters(): - pass - # the doLaterProcessor is always running - assert len(tm.getDoLaters()) == 0 - tm.doMethodLater(.1, _testGetDoLaters, 'testDoLater1') - assert len(tm.getDoLaters()) == 1 - assert tm.getDoLaters()[0].name == 'testDoLater1' - tm.doMethodLater(.1, _testGetDoLaters, 'testDoLater2') - tm.doMethodLater(.1, _testGetDoLaters, 'testDoLater3') - assert len(tm.getDoLaters()) == 3 - tm.remove('testDoLater2') - assert len(tm.getDoLaters()) == 2 - tm.remove('testDoLater1') - tm.remove('testDoLater3') - assert len(tm.getDoLaters()) == 0 - _testGetDoLaters = None - tm._checkMemLeaks() - - # duplicate named doLaters removed via taskMgr.remove - def _testDupNameDoLaters(): - pass - # the doLaterProcessor is always running - tm.doMethodLater(.1, _testDupNameDoLaters, 'testDupNameDoLater') - tm.doMethodLater(.1, _testDupNameDoLaters, 'testDupNameDoLater') - assert len(tm.getDoLaters()) == 2 - tm.remove('testDupNameDoLater') - assert len(tm.getDoLaters()) == 0 - _testDupNameDoLaters = None - tm._checkMemLeaks() - - # duplicate named doLaters removed via remove() - def _testDupNameDoLatersRemove(): - pass - # the doLaterProcessor is always running - dl1 = tm.doMethodLater(.1, _testDupNameDoLatersRemove, 'testDupNameDoLaterRemove') - dl2 = tm.doMethodLater(.1, _testDupNameDoLatersRemove, 'testDupNameDoLaterRemove') - assert len(tm.getDoLaters()) == 2 - dl2.remove() - assert len(tm.getDoLaters()) == 1 - dl1.remove() - assert len(tm.getDoLaters()) == 0 - _testDupNameDoLatersRemove = None - # nameDict etc. isn't cleared out right away with task.remove() - tm._checkMemLeaks() - - # getTasksNamed - def _testGetTasksNamed(task): - return task.cont - assert len(tm.getTasksNamed('testGetTasksNamed')) == 0 - tm.add(_testGetTasksNamed, 'testGetTasksNamed') - assert len(tm.getTasksNamed('testGetTasksNamed')) == 1 - assert tm.getTasksNamed('testGetTasksNamed')[0].name == 'testGetTasksNamed' - tm.add(_testGetTasksNamed, 'testGetTasksNamed') - tm.add(_testGetTasksNamed, 'testGetTasksNamed') - assert len(tm.getTasksNamed('testGetTasksNamed')) == 3 - tm.remove('testGetTasksNamed') - assert len(tm.getTasksNamed('testGetTasksNamed')) == 0 - _testGetTasksNamed = None - tm._checkMemLeaks() - - # removeTasksMatching - def _testRemoveTasksMatching(task): - return task.cont - tm.add(_testRemoveTasksMatching, 'testRemoveTasksMatching') - assert len(tm.getTasksNamed('testRemoveTasksMatching')) == 1 - tm.removeTasksMatching('testRemoveTasksMatching') - assert len(tm.getTasksNamed('testRemoveTasksMatching')) == 0 - tm.add(_testRemoveTasksMatching, 'testRemoveTasksMatching1') - tm.add(_testRemoveTasksMatching, 'testRemoveTasksMatching2') - assert len(tm.getTasksNamed('testRemoveTasksMatching1')) == 1 - assert len(tm.getTasksNamed('testRemoveTasksMatching2')) == 1 - tm.removeTasksMatching('testRemoveTasksMatching*') - assert len(tm.getTasksNamed('testRemoveTasksMatching1')) == 0 - assert len(tm.getTasksNamed('testRemoveTasksMatching2')) == 0 - tm.add(_testRemoveTasksMatching, 'testRemoveTasksMatching1a') - tm.add(_testRemoveTasksMatching, 'testRemoveTasksMatching2a') - assert len(tm.getTasksNamed('testRemoveTasksMatching1a')) == 1 - assert len(tm.getTasksNamed('testRemoveTasksMatching2a')) == 1 - tm.removeTasksMatching('testRemoveTasksMatching?a') - assert len(tm.getTasksNamed('testRemoveTasksMatching1a')) == 0 - assert len(tm.getTasksNamed('testRemoveTasksMatching2a')) == 0 - _testRemoveTasksMatching = None - tm._checkMemLeaks() - - # create Task object and add to mgr - l = [] - def _testTaskObj(task, l=l): - l.append(None) - return task.cont - t = Task(_testTaskObj) - tm.add(t, 'testTaskObj') - tm.step() - assert len(l) == 1 - tm.step() - assert len(l) == 2 - tm.remove('testTaskObj') - tm.step() - assert len(l) == 2 - _testTaskObj = None - tm._checkMemLeaks() - - # remove Task via task.remove() - l = [] - def _testTaskObjRemove(task, l=l): - l.append(None) - return task.cont - t = Task(_testTaskObjRemove) - tm.add(t, 'testTaskObjRemove') - tm.step() - assert len(l) == 1 - tm.step() - assert len(l) == 2 - t.remove() - tm.step() - assert len(l) == 2 - del t - _testTaskObjRemove = None - tm._checkMemLeaks() - - """ - # this test fails, and it's not clear what the correct behavior should be. - # priority passed to Task.__init__ is always overridden by taskMgr.add() - # even if no priority is specified, and calling Task.setPriority() has no - # effect on the taskMgr's behavior. - # set/get Task priority - l = [] - def _testTaskObjPriority(arg, task, l=l): - l.append(arg) - return task.cont - t1 = Task(_testTaskObjPriority, priority=1) - t2 = Task(_testTaskObjPriority, priority=2) - tm.add(t1, 'testTaskObjPriority1', extraArgs=['a',], appendTask=True) - tm.add(t2, 'testTaskObjPriority2', extraArgs=['b',], appendTask=True) - tm.step() - assert len(l) == 2 - assert l == ['a', 'b'] - assert t1.getPriority() == 1 - assert t2.getPriority() == 2 - t1.setPriority(3) - assert t1.getPriority() == 3 - tm.step() - assert len(l) == 4 - assert l == ['a', 'b', 'b', 'a',] - t1.remove() - t2.remove() - tm.step() - assert len(l) == 4 - del t1 - del t2 - _testTaskObjPriority = None - tm._checkMemLeaks() - """ - - del l - tm.destroy() - del tm - - runTests() - del runTests + pass # 'if __debug__' is hint for CVS diff output """