Task.py 42 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121
  1. """Undocumented Module"""
  2. __all__ = ['Task', 'TaskPriorityList', 'TaskManager']
  3. # This module may not import pandac.PandaModules, since it is imported
  4. # by the Toontown Launcher before the complete PandaModules have been
  5. # downloaded. Instead, it imports only libpandaexpressModules, the
  6. # subset of PandaModules that we know is available immediately.
  7. # Methods that require more advanced C++ methods may import the
  8. # appropriate files within their own scope.
  9. from pandac.libpandaexpressModules import *
  10. from direct.directnotify.DirectNotifyGlobal import *
  11. from direct.showbase.PythonUtil import *
  12. from direct.showbase.MessengerGlobal import *
  13. import time
  14. import fnmatch
  15. import string
  16. import signal
  17. from libheapq import heappush, heappop, heapify
  18. import types
  19. if __debug__:
  20. # For pstats
  21. from pandac.PandaModules import PStatCollector
  22. def print_exc_plus():
  23. """
  24. Print the usual traceback information, followed by a listing of all the
  25. local variables in each frame.
  26. """
  27. import sys
  28. import traceback
  29. tb = sys.exc_info()[2]
  30. while 1:
  31. if not tb.tb_next:
  32. break
  33. tb = tb.tb_next
  34. stack = []
  35. f = tb.tb_frame
  36. while f:
  37. stack.append(f)
  38. f = f.f_back
  39. stack.reverse()
  40. traceback.print_exc()
  41. print "Locals by frame, innermost last"
  42. for frame in stack:
  43. print
  44. print "Frame %s in %s at line %s" % (frame.f_code.co_name,
  45. frame.f_code.co_filename,
  46. frame.f_lineno)
  47. for key, value in frame.f_locals.items():
  48. print "\t%20s = " % key,
  49. #We have to be careful not to cause a new error in our error
  50. #printer! Calling str() on an unknown object could cause an
  51. #error we don't want.
  52. try:
  53. print value
  54. except:
  55. print "<ERROR WHILE PRINTING VALUE>"
  56. class Task:
  57. # This enum is a copy of the one at the top-level.
  58. exit = -1
  59. done = 0
  60. cont = 1
  61. again = 2
  62. if __debug__:
  63. debugTaskTraceback = 0 # base.config.GetBool('debug-task-traceback', 0)
  64. count = 0
  65. def __init__(self, callback, priority = 0):
  66. if __debug__:
  67. if self.debugTaskTraceback:
  68. self.debugInitTraceback = StackTrace("Task "+str(callback), 1, 10)
  69. # Unique ID for each task
  70. self.id = Task.count
  71. Task.count += 1
  72. self.__call__ = callback
  73. self.__priority = priority
  74. self.__removed = 0
  75. self.dt = 0.0
  76. if TaskManager.taskTimerVerbose:
  77. self.avgDt = 0.0
  78. self.maxDt = 0.0
  79. self.runningTotal = 0.0
  80. self.pstats = None
  81. self.extraArgs = None
  82. # Used for doLaters
  83. self.wakeTime = 0.0
  84. # for repeating doLaters
  85. self.delayTime = 0.0
  86. self.time = 0.0
  87. # # Used for putting into the doLaterList
  88. # # the heapq calls __cmp__ via the rich compare function
  89. # def __cmp__(self, other):
  90. # if isinstance(other, Task):
  91. # if self.wakeTime < other.wakeTime:
  92. # return -1
  93. # elif self.wakeTime > other.wakeTime:
  94. # return 1
  95. # # If the wakeTimes happen to be the same, just
  96. # # sort them based on id
  97. # else:
  98. # return cmp(id(self), id(other))
  99. # # This is important for people doing a (task != None) and such.
  100. # else:
  101. # return cmp(id(self), id(other))
  102. # # According to the Python manual (3.3.1), if you define a cmp operator
  103. # # you should also define a hash operator or your objects will not be
  104. # # usable in dictionaries. Since no two task objects are unique, we can
  105. # # just return the unique id.
  106. # def __hash__(self):
  107. # return self.id
  108. def remove(self):
  109. if not self.__removed:
  110. self.__removed = 1
  111. # Remove any refs to real objects
  112. # In case we hang around the doLaterList for a while
  113. del self.__call__
  114. del self.extraArgs
  115. def isRemoved(self):
  116. return self.__removed
  117. def getPriority(self):
  118. return self.__priority
  119. def setPriority(self, pri):
  120. self.__priority = pri
  121. def setStartTimeFrame(self, startTime, startFrame):
  122. self.starttime = startTime
  123. self.startframe = startFrame
  124. def setCurrentTimeFrame(self, currentTime, currentFrame):
  125. # Calculate and store this task's time (relative to when it started)
  126. self.time = currentTime - self.starttime
  127. self.frame = currentFrame - self.startframe
  128. def setupPStats(self, name):
  129. if __debug__ and TaskManager.taskTimerVerbose:
  130. self.pstats = PStatCollector("App:Show code:" + name)
  131. def finishTask(self, verbose):
  132. if hasattr(self, "uponDeath"):
  133. self.uponDeath(self)
  134. if verbose:
  135. # We regret to announce...
  136. messenger.send('TaskManager-removeTask', sentArgs = [self, self.name])
  137. del self.uponDeath
  138. def __repr__(self):
  139. if hasattr(self, 'name'):
  140. return ('Task id: %s, name %s' % (self.id, self.name))
  141. else:
  142. return ('Task id: %s, no name' % (self.id))
  143. def pause(delayTime):
  144. def func(self):
  145. if (self.time < self.delayTime):
  146. return cont
  147. else:
  148. return done
  149. task = Task(func)
  150. task.name = 'pause'
  151. task.delayTime = delayTime
  152. return task
  153. Task.pause = staticmethod(pause)
  154. def sequence(*taskList):
  155. return make_sequence(taskList)
  156. Task.sequence = staticmethod(sequence)
  157. def make_sequence(taskList):
  158. def func(self):
  159. frameFinished = 0
  160. taskDoneStatus = -1
  161. while not frameFinished:
  162. task = self.taskList[self.index]
  163. # If this is a new task, set its start time and frame
  164. if self.index > self.prevIndex:
  165. task.setStartTimeFrame(self.time, self.frame)
  166. self.prevIndex = self.index
  167. # Calculate this task's time since it started
  168. task.setCurrentTimeFrame(self.time, self.frame)
  169. # Execute the current task
  170. ret = task(task)
  171. # Check the return value from the task
  172. if ret == cont:
  173. # If this current task wants to continue,
  174. # come back to it next frame
  175. taskDoneStatus = cont
  176. frameFinished = 1
  177. elif ret == done:
  178. # If this task is done, increment the index so that next frame
  179. # we will start executing the next task on the list
  180. self.index = self.index + 1
  181. taskDoneStatus = cont
  182. frameFinished = 0
  183. elif ret == exit:
  184. # If this task wants to exit, the sequence exits
  185. taskDoneStatus = exit
  186. frameFinished = 1
  187. # If we got to the end of the list, this sequence is done
  188. if self.index >= len(self.taskList):
  189. # TaskManager.notify.debug('sequence done: ' + self.name)
  190. frameFinished = 1
  191. taskDoneStatus = done
  192. return taskDoneStatus
  193. task = Task(func)
  194. task.name = 'sequence'
  195. task.taskList = taskList
  196. task.prevIndex = -1
  197. task.index = 0
  198. return task
  199. def resetSequence(task):
  200. # Should this automatically be done as part of spawnTaskNamed?
  201. # Or should one have to create a new task instance every time
  202. # one wishes to spawn a task (currently sequences and can
  203. # only be fired off once
  204. task.index = 0
  205. task.prevIndex = -1
  206. def loop(*taskList):
  207. return make_loop(taskList)
  208. Task.loop = staticmethod(loop)
  209. def make_loop(taskList):
  210. def func(self):
  211. frameFinished = 0
  212. taskDoneStatus = -1
  213. while (not frameFinished):
  214. task = self.taskList[self.index]
  215. # If this is a new task, set its start time and frame
  216. if (self.index > self.prevIndex):
  217. task.setStartTimeFrame(self.time, self.frame)
  218. self.prevIndex = self.index
  219. # Calculate this task's time since it started
  220. task.setCurrentTimeFrame(self.time, self.frame)
  221. # Execute the current task
  222. ret = task(task)
  223. # Check the return value from the task
  224. if (ret == cont):
  225. # If this current task wants to continue,
  226. # come back to it next frame
  227. taskDoneStatus = cont
  228. frameFinished = 1
  229. elif (ret == done):
  230. # If this task is done, increment the index so that next frame
  231. # we will start executing the next task on the list
  232. # TODO: we should go to the next frame now
  233. self.index = self.index + 1
  234. taskDoneStatus = cont
  235. frameFinished = 0
  236. elif (ret == exit):
  237. # If this task wants to exit, the sequence exits
  238. taskDoneStatus = exit
  239. frameFinished = 1
  240. if (self.index >= len(self.taskList)):
  241. # If we got to the end of the list, wrap back around
  242. self.prevIndex = -1
  243. self.index = 0
  244. frameFinished = 1
  245. return taskDoneStatus
  246. task = Task(func)
  247. task.name = 'loop'
  248. task.taskList = taskList
  249. task.prevIndex = -1
  250. task.index = 0
  251. return task
  252. class TaskPriorityList(list):
  253. def __init__(self, priority):
  254. self.__priority = priority
  255. self.__emptyIndex = 0
  256. def getPriority(self):
  257. return self.__priority
  258. def add(self, task):
  259. if (self.__emptyIndex >= len(self)):
  260. self.append(task)
  261. self.__emptyIndex += 1
  262. else:
  263. self[self.__emptyIndex] = task
  264. self.__emptyIndex += 1
  265. def remove(self, i):
  266. assert i <= len(self)
  267. if (len(self) == 1) and (i == 1):
  268. self[i] = None
  269. self.__emptyIndex = 0
  270. else:
  271. # Swap the last element for this one
  272. lastElement = self[self.__emptyIndex-1]
  273. self[i] = lastElement
  274. self[self.__emptyIndex-1] = None
  275. self.__emptyIndex -= 1
  276. class TaskManager:
  277. # These class vars are generally overwritten by Config variables which
  278. # are read in at the start of a show (ShowBase.py or AIStart.py)
  279. notify = None
  280. # TODO: there is a bit of a bug when you default this to 0. The first
  281. # task we make, the doLaterProcessor, needs to have this set to 1 or
  282. # else we get an error.
  283. taskTimerVerbose = 1
  284. extendedExceptions = 0
  285. pStatsTasks = 0
  286. doLaterCleanupCounter = 2000
  287. OsdPrefix = 'task.'
  288. def __init__(self):
  289. self.running = 0
  290. self.stepping = 0
  291. self.taskList = []
  292. # Dictionary of priority to newTaskLists
  293. self.pendingTaskDict = {}
  294. # List of tasks scheduled to execute in the future
  295. self.__doLaterList = []
  296. self._profileNextFrame = False
  297. # We copy this value in from __builtins__ when it gets set.
  298. # But since the TaskManager might have to run before it gets
  299. # set--before it can even be available--we also have to have
  300. # special-case code that handles the possibility that we don't
  301. # have a globalClock yet.
  302. self.globalClock = None
  303. # To help cope with the possibly-missing globalClock, we get a
  304. # handle to Panda's low-level TrueClock object for measuring
  305. # small intervals.
  306. self.trueClock = TrueClock.getGlobalPtr()
  307. self.currentTime, self.currentFrame = self.__getTimeFrame()
  308. if (TaskManager.notify == None):
  309. TaskManager.notify = directNotify.newCategory("TaskManager")
  310. self.fKeyboardInterrupt = 0
  311. self.interruptCount = 0
  312. self.resumeFunc = None
  313. self.fVerbose = 0
  314. # Dictionary of task name to list of tasks with that name
  315. self.nameDict = {}
  316. # A default task.
  317. self.add(self.__doLaterProcessor, "doLaterProcessor", -10)
  318. def stepping(self, value):
  319. self.stepping = value
  320. def setVerbose(self, value):
  321. self.fVerbose = value
  322. messenger.send('TaskManager-setVerbose', sentArgs = [value])
  323. def keyboardInterruptHandler(self, signalNumber, stackFrame):
  324. self.fKeyboardInterrupt = 1
  325. self.interruptCount += 1
  326. if self.interruptCount == 2:
  327. # The user must really want to interrupt this process
  328. # Next time around use the default interrupt handler
  329. signal.signal(signal.SIGINT, signal.default_int_handler)
  330. def hasTaskNamed(self, taskName):
  331. # TODO: check pending task list
  332. # Get the tasks with this name
  333. tasks = self.nameDict.get(taskName)
  334. # If we found some, see if any of them are still active (not removed)
  335. if tasks:
  336. for task in tasks:
  337. if not task.isRemoved():
  338. return 1
  339. # Didnt find any, return 0
  340. return 0
  341. def getTasksNamed(self, taskName):
  342. # TODO: check pending tasks
  343. # Get the tasks with this name
  344. tasks = self.nameDict.get(taskName, [])
  345. # Filter out the tasks that have been removed
  346. if tasks:
  347. tasks = filter(lambda task: not task.isRemoved(), tasks)
  348. return tasks
  349. def __doLaterFilter(self):
  350. # Filter out all the tasks that have been removed like a mark and
  351. # sweep garbage collector. Returns the number of tasks that have
  352. # been removed Warning: this creates an entirely new doLaterList.
  353. oldLen = len(self.__doLaterList)
  354. self.__doLaterList = filter(
  355. lambda task: not task.isRemoved(), self.__doLaterList)
  356. # Re heapify to maintain ordering after filter
  357. heapify(self.__doLaterList)
  358. newLen = len(self.__doLaterList)
  359. return oldLen - newLen
  360. def __doLaterProcessor(self, task):
  361. # Removing the tasks during the for loop is a bad idea
  362. # Instead we just flag them as removed
  363. # Later, somebody else cleans them out
  364. currentTime = self.__getTime()
  365. while self.__doLaterList:
  366. # Check the first one on the list to see if it is ready
  367. dl = self.__doLaterList[0]
  368. if dl.isRemoved():
  369. # Get rid of this task forever
  370. heappop(self.__doLaterList)
  371. continue
  372. # If the time now is less than the start of the doLater + delay
  373. # then we are not ready yet, continue to next one
  374. elif currentTime < dl.wakeTime:
  375. # Since the list is sorted, the first one we get to, that
  376. # is not ready to go, we can return
  377. break
  378. else:
  379. # Take it off the doLaterList, set its time, and make
  380. # it pending
  381. heappop(self.__doLaterList)
  382. dl.setStartTimeFrame(self.currentTime, self.currentFrame)
  383. self.__addPendingTask(dl)
  384. continue
  385. # Every nth pass, let's clean out the list of removed tasks
  386. # This is basically a mark and sweep garbage collection of doLaters
  387. if ((task.frame % self.doLaterCleanupCounter) == 0):
  388. numRemoved = self.__doLaterFilter()
  389. # TaskManager.notify.debug(
  390. # "filtered %s removed doLaters" % numRemoved)
  391. return cont
  392. def doMethodLater(self, delayTime, funcOrTask, name, extraArgs=None,
  393. priority=0, uponDeath=None, appendTask=False):
  394. if delayTime < 0:
  395. self.notify.warning('doMethodLater: added task: %s with negative delay: %s' % (name, delayTime))
  396. if isinstance(funcOrTask, Task):
  397. task = funcOrTask
  398. elif callable(funcOrTask):
  399. task = Task(funcOrTask, priority)
  400. else:
  401. self.notify.error('doMethodLater: Tried to add a task that was not a Task or a func')
  402. task.setPriority(priority)
  403. task.name = name
  404. task.extraArgs = extraArgs
  405. if uponDeath:
  406. task.uponDeath = uponDeath
  407. # if told to, append the task object to the extra args list so the
  408. # method called will be able to access any properties on the task
  409. if (appendTask == True and extraArgs != None):
  410. extraArgs.append(task)
  411. # TaskManager.notify.debug('spawning doLater: %s' % (task))
  412. # Add this task to the nameDict
  413. nameList = self.nameDict.get(name)
  414. if nameList:
  415. nameList.append(task)
  416. else:
  417. self.nameDict[name] = [task]
  418. currentTime = self.__getTime()
  419. # Cache the time we should wake up for easier sorting
  420. task.delayTime = delayTime
  421. task.wakeTime = currentTime + delayTime
  422. # Push this onto the doLaterList. The heap maintains the sorting.
  423. heappush(self.__doLaterList, task)
  424. if self.fVerbose:
  425. # Alert the world, a new task is born!
  426. messenger.send('TaskManager-spawnDoLater',
  427. sentArgs = [task, task.name, task.id])
  428. return task
  429. def add(self, funcOrTask, name, priority=0, extraArgs=None, uponDeath=None):
  430. """
  431. Add a new task to the taskMgr.
  432. You can add a Task object or a method that takes one argument.
  433. """
  434. # TaskManager.notify.debug('add: %s' % (name))
  435. if isinstance(funcOrTask, Task):
  436. task = funcOrTask
  437. elif callable(funcOrTask):
  438. task = Task(funcOrTask, priority)
  439. else:
  440. self.notify.error(
  441. 'add: Tried to add a task that was not a Task or a func')
  442. task.setPriority(priority)
  443. task.name = name
  444. task.extraArgs = extraArgs
  445. if uponDeath:
  446. task.uponDeath = uponDeath
  447. currentTime = self.__getTime()
  448. task.setStartTimeFrame(currentTime, self.currentFrame)
  449. nameList = self.nameDict.get(name)
  450. if nameList:
  451. nameList.append(task)
  452. else:
  453. self.nameDict[name] = [task]
  454. # Put it on the list for the end of this frame
  455. self.__addPendingTask(task)
  456. return task
  457. def __addPendingTask(self, task):
  458. # TaskManager.notify.debug('__addPendingTask: %s' % (task.name))
  459. pri = task.getPriority()
  460. taskPriList = self.pendingTaskDict.get(pri)
  461. if not taskPriList:
  462. taskPriList = TaskPriorityList(pri)
  463. self.pendingTaskDict[pri] = taskPriList
  464. taskPriList.add(task)
  465. def __addNewTask(self, task):
  466. # The taskList is really an ordered list of TaskPriorityLists
  467. # search back from the end of the list until we find a
  468. # taskList with a lower priority, or we hit the start of the list
  469. taskPriority = task.getPriority()
  470. index = len(self.taskList) - 1
  471. while (1):
  472. if (index < 0):
  473. newList = TaskPriorityList(taskPriority)
  474. newList.add(task)
  475. # Add the new list to the beginning of the taskList
  476. self.taskList.insert(0, newList)
  477. break
  478. taskListPriority = self.taskList[index].getPriority()
  479. if (taskListPriority == taskPriority):
  480. self.taskList[index].add(task)
  481. break
  482. elif (taskListPriority > taskPriority):
  483. index = index - 1
  484. elif (taskListPriority < taskPriority):
  485. # Time to insert
  486. newList = TaskPriorityList(taskPriority)
  487. newList.add(task)
  488. # Insert this new priority level
  489. # If we are already at the end, just append it
  490. if (index == len(self.taskList)-1):
  491. self.taskList.append(newList)
  492. else:
  493. # Otherwise insert it
  494. self.taskList.insert(index+1, newList)
  495. break
  496. if __debug__:
  497. if self.pStatsTasks and task.name != "igloop":
  498. # Get the PStats name for the task. By convention,
  499. # this is everything until the first hyphen; the part
  500. # of the task name following the hyphen is generally
  501. # used to differentiate particular tasks that do the
  502. # same thing to different objects.
  503. name = task.name
  504. hyphen = name.find('-')
  505. if hyphen >= 0:
  506. name = name[0:hyphen]
  507. task.setupPStats(name)
  508. if self.fVerbose:
  509. # Alert the world, a new task is born!
  510. messenger.send(
  511. 'TaskManager-spawnTask', sentArgs = [task, task.name, index])
  512. return task
  513. def remove(self, taskOrName):
  514. if type(taskOrName) == type(''):
  515. return self.__removeTasksNamed(taskOrName)
  516. elif isinstance(taskOrName, Task):
  517. return self.__removeTasksEqual(taskOrName)
  518. else:
  519. self.notify.error('remove takes a string or a Task')
  520. def removeTasksMatching(self, taskPattern):
  521. """removeTasksMatching(self, string taskPattern)
  522. Removes tasks whose names match the pattern, which can include
  523. standard shell globbing characters like *, ?, and [].
  524. """
  525. # TaskManager.notify.debug('removing tasks matching: ' + taskPattern)
  526. num = 0
  527. keyList = filter(
  528. lambda key: fnmatch.fnmatchcase(key, taskPattern),
  529. self.nameDict.keys())
  530. for key in keyList:
  531. num += self.__removeTasksNamed(key)
  532. return num
  533. def __removeTasksEqual(self, task):
  534. # Remove this task from the nameDict (should be a short list)
  535. if self.__removeTaskFromNameDict(task):
  536. # TaskManager.notify.debug(
  537. # '__removeTasksEqual: removing task: %s' % (task))
  538. # Flag the task for removal from the real list
  539. task.remove()
  540. task.finishTask(self.fVerbose)
  541. return 1
  542. else:
  543. return 0
  544. def __removeTasksNamed(self, taskName):
  545. tasks = self.nameDict.get(taskName)
  546. if not tasks:
  547. return 0
  548. # TaskManager.notify.debug(
  549. # '__removeTasksNamed: removing tasks named: %s' % (taskName))
  550. for task in tasks:
  551. # Flag for removal
  552. task.remove()
  553. task.finishTask(self.fVerbose)
  554. # Record the number of tasks removed
  555. num = len(tasks)
  556. # Blow away the nameDict entry completely
  557. del self.nameDict[taskName]
  558. return num
  559. def __removeTaskFromNameDict(self, task):
  560. taskName = task.name
  561. # If this is the only task with that name, remove the dict entry
  562. tasksWithName = self.nameDict.get(taskName)
  563. if tasksWithName:
  564. if task in tasksWithName:
  565. # If this is the last element, just remove the entry
  566. # from the dictionary
  567. if len(tasksWithName) == 1:
  568. del self.nameDict[taskName]
  569. else:
  570. tasksWithName.remove(task)
  571. return 1
  572. return 0
  573. def __executeTask(self, task):
  574. task.setCurrentTimeFrame(self.currentTime, self.currentFrame)
  575. if not self.taskTimerVerbose:
  576. startTime = self.trueClock.getShortTime()
  577. # don't record timing info
  578. if task.extraArgs != None:
  579. ret = task(*task.extraArgs)
  580. else:
  581. ret = task(task)
  582. endTime = self.trueClock.getShortTime()
  583. # Record the dt
  584. dt = endTime - startTime
  585. task.dt = dt
  586. else:
  587. # Run the task and check the return value
  588. if task.pstats:
  589. task.pstats.start()
  590. startTime = self.trueClock.getShortTime()
  591. if task.extraArgs != None:
  592. ret = task(*task.extraArgs)
  593. else:
  594. ret = task(task)
  595. endTime = self.trueClock.getShortTime()
  596. if task.pstats:
  597. task.pstats.stop()
  598. # Record the dt
  599. dt = endTime - startTime
  600. task.dt = dt
  601. # See if this is the new max
  602. if dt > task.maxDt:
  603. task.maxDt = dt
  604. # Record the running total of all dts so we can compute an average
  605. task.runningTotal = task.runningTotal + dt
  606. if (task.frame > 0):
  607. task.avgDt = (task.runningTotal / task.frame)
  608. else:
  609. task.avgDt = 0
  610. return ret
  611. def __repeatDoMethod(self, task):
  612. """
  613. Called when a task execute function returns Task.again because
  614. it wants the task to execute again after the same or a modified
  615. delay (set 'delayTime' on the task object to change the delay)
  616. """
  617. if (not task.isRemoved()):
  618. # be sure to ask the globalClock for the current frame time
  619. # rather than use a cached value; globalClock's frame time may
  620. # have been synced since the start of this frame
  621. currentTime = self.__getTime()
  622. # Cache the time we should wake up for easier sorting
  623. task.wakeTime = currentTime + task.delayTime
  624. # Push this onto the doLaterList. The heap maintains the sorting.
  625. heappush(self.__doLaterList, task)
  626. if self.fVerbose:
  627. # Alert the world, a new task is born!
  628. messenger.send('TaskManager-againDoLater',
  629. sentArgs = [task, task.name, task.id])
  630. def __stepThroughList(self, taskPriList):
  631. # Traverse the taskPriList with an iterator
  632. i = 0
  633. while (i < len(taskPriList)):
  634. task = taskPriList[i]
  635. # See if we are at the end of the real tasks
  636. if task is None:
  637. break
  638. # See if this task has been removed in show code
  639. if task.isRemoved():
  640. # assert TaskManager.notify.debug(
  641. # '__stepThroughList: task is flagged for removal %s' % (task))
  642. # If it was removed in show code, it will need finishTask run
  643. # If it was removed by the taskMgr, it will not, but that is ok
  644. # because finishTask is safe to call twice
  645. task.finishTask(self.fVerbose)
  646. taskPriList.remove(i)
  647. # Do not increment the iterator
  648. continue
  649. # Now actually execute the task
  650. ret = self.__executeTask(task)
  651. # See if the task is done
  652. if (ret == cont):
  653. # Leave it for next frame, its not done yet
  654. pass
  655. elif (ret == again):
  656. # repeat doLater again after a delay
  657. self.__repeatDoMethod(task)
  658. taskPriList.remove(i)
  659. continue
  660. elif ((ret == done) or (ret == exit) or (ret == None)):
  661. # assert TaskManager.notify.debug(
  662. # '__stepThroughList: task is finished %s' % (task))
  663. # Remove the task
  664. if not task.isRemoved():
  665. # assert TaskManager.notify.debug(
  666. # '__stepThroughList: task not removed %s' % (task))
  667. task.remove()
  668. # Note: Should not need to remove from doLaterList here
  669. # because this task is not in the doLaterList
  670. task.finishTask(self.fVerbose)
  671. self.__removeTaskFromNameDict(task)
  672. else:
  673. # assert TaskManager.notify.debug(
  674. # '__stepThroughList: task already removed %s' % (task))
  675. self.__removeTaskFromNameDict(task)
  676. taskPriList.remove(i)
  677. # Do not increment the iterator
  678. continue
  679. else:
  680. raise StandardError, \
  681. "Task named %s did not return cont, exit, done, or None" % \
  682. (task.name,)
  683. # Move to the next element
  684. i += 1
  685. def __addPendingTasksToTaskList(self):
  686. # Now that we are all done, add any left over pendingTasks
  687. # generated in priority levels lower or higher than where
  688. # we were when we iterated
  689. for taskList in self.pendingTaskDict.values():
  690. for task in taskList:
  691. if (task and not task.isRemoved()):
  692. # assert TaskManager.notify.debug(
  693. # 'step: moving %s from pending to taskList' % (task.name))
  694. self.__addNewTask(task)
  695. self.pendingTaskDict.clear()
  696. def profileNextFrame(self):
  697. self._profileNextFrame = True
  698. @profiled()
  699. def _profiledFrame(self, *args, **kArgs):
  700. return self.step(*args, **kArgs)
  701. def step(self):
  702. # assert TaskManager.notify.debug('step: begin')
  703. self.currentTime, self.currentFrame = self.__getTimeFrame()
  704. # Replace keyboard interrupt handler during task list processing
  705. # so we catch the keyboard interrupt but don't handle it until
  706. # after task list processing is complete.
  707. self.fKeyboardInterrupt = 0
  708. self.interruptCount = 0
  709. signal.signal(signal.SIGINT, self.keyboardInterruptHandler)
  710. # Traverse the task list in order because it is in priority order
  711. priIndex = 0
  712. while priIndex < len(self.taskList):
  713. taskPriList = self.taskList[priIndex]
  714. pri = taskPriList.getPriority()
  715. # assert TaskManager.notify.debug(
  716. # 'step: running through taskList at pri: %s, priIndex: %s' %
  717. # (pri, priIndex))
  718. self.__stepThroughList(taskPriList)
  719. # Now see if that generated any pending tasks for this taskPriList
  720. pendingTasks = self.pendingTaskDict.get(pri)
  721. while pendingTasks:
  722. # assert TaskManager.notify.debug('step: running through pending tasks at pri: %s' % (pri))
  723. # Remove them from the pendingTaskDict
  724. del self.pendingTaskDict[pri]
  725. # Execute them
  726. self.__stepThroughList(pendingTasks)
  727. # Add these to the real taskList
  728. for task in pendingTasks:
  729. if (task and not task.isRemoved()):
  730. # assert TaskManager.notify.debug('step: moving %s from pending to taskList' % (task.name))
  731. self.__addNewTask(task)
  732. # See if we generated any more for this pri level
  733. pendingTasks = self.pendingTaskDict.get(pri)
  734. # Any new tasks that were made pending should be converted
  735. # to real tasks now in case they need to run this frame at a
  736. # later priority level
  737. self.__addPendingTasksToTaskList()
  738. # Go to the next priority level
  739. priIndex += 1
  740. # Add new pending tasks
  741. self.__addPendingTasksToTaskList()
  742. # Restore default interrupt handler
  743. signal.signal(signal.SIGINT, signal.default_int_handler)
  744. if self.fKeyboardInterrupt:
  745. raise KeyboardInterrupt
  746. def run(self):
  747. # Set the clock to have last frame's time in case we were
  748. # Paused at the prompt for a long time
  749. if self.globalClock:
  750. t = self.globalClock.getFrameTime()
  751. timeDelta = t - globalClock.getRealTime()
  752. self.globalClock.setRealTime(t)
  753. messenger.send("resetClock", [timeDelta])
  754. if self.resumeFunc != None:
  755. self.resumeFunc()
  756. if self.stepping:
  757. self.step()
  758. else:
  759. self.running = 1
  760. while self.running:
  761. try:
  762. if self._profileNextFrame:
  763. self._profiledFrame()
  764. self._profileNextFrame = False
  765. else:
  766. self.step()
  767. except KeyboardInterrupt:
  768. self.stop()
  769. except IOError, (errno, strerror):
  770. # Since upgrading to Python 2.4.1, pausing the execution
  771. # often gives this IOError during the sleep function:
  772. # IOError: [Errno 4] Interrupted function call
  773. # So, let's just handle that specific exception and stop.
  774. # All other IOErrors should still get raised.
  775. # Only problem: legit IOError 4s will be obfuscated.
  776. if errno == 4:
  777. self.stop()
  778. else:
  779. raise
  780. except:
  781. if self.extendedExceptions:
  782. self.stop()
  783. print_exc_plus()
  784. else:
  785. raise
  786. def stop(self):
  787. # Set a flag so we will stop before beginning next frame
  788. self.running = 0
  789. def __tryReplaceTaskMethod(self, task, oldMethod, newFunction):
  790. import new
  791. if (task is None) or task.isRemoved():
  792. return 0
  793. method = task.__call__
  794. if (type(method) == types.MethodType):
  795. function = method.im_func
  796. else:
  797. function = method
  798. #print ('function: ' + `function` + '\n' +
  799. # 'method: ' + `method` + '\n' +
  800. # 'oldMethod: ' + `oldMethod` + '\n' +
  801. # 'newFunction: ' + `newFunction` + '\n')
  802. if (function == oldMethod):
  803. newMethod = new.instancemethod(newFunction,
  804. method.im_self,
  805. method.im_class)
  806. task.__call__ = newMethod
  807. # Found a match
  808. return 1
  809. return 0
  810. def replaceMethod(self, oldMethod, newFunction):
  811. numFound = 0
  812. # Look through the regular tasks
  813. for taskPriList in self.taskList:
  814. for task in taskPriList:
  815. numFound += self.__tryReplaceTaskMethod(task, oldMethod, newFunction)
  816. # Look through the pending tasks
  817. for pri, taskList in self.pendingTaskDict.items():
  818. for task in taskList:
  819. numFound += self.__tryReplaceTaskMethod(task, oldMethod, newFunction)
  820. # Look through the doLaters
  821. for task in self.__doLaterList:
  822. numFound += self.__tryReplaceTaskMethod(task, oldMethod, newFunction)
  823. return numFound
  824. def __repr__(self):
  825. taskNameWidth = 32
  826. dtWidth = 10
  827. priorityWidth = 10
  828. totalDt = 0
  829. totalAvgDt = 0
  830. str = "The taskMgr is handling:\n"
  831. str += ('taskList'.ljust(taskNameWidth)
  832. + 'dt(ms)'.rjust(dtWidth)
  833. + 'avg'.rjust(dtWidth)
  834. + 'max'.rjust(dtWidth)
  835. + 'priority'.rjust(priorityWidth)
  836. + '\n')
  837. str += '-------------------------------------------------------------------------\n'
  838. dtfmt = '%%%d.2f' % (dtWidth)
  839. for taskPriList in self.taskList:
  840. priority = `taskPriList.getPriority()`
  841. for task in taskPriList:
  842. if task is None:
  843. break
  844. if task.isRemoved():
  845. taskName = '(R)' + task.name
  846. else:
  847. taskName = task.name
  848. if self.taskTimerVerbose:
  849. totalDt = totalDt + task.dt
  850. totalAvgDt = totalAvgDt + task.avgDt
  851. str += (taskName.ljust(taskNameWidth)
  852. + dtfmt % (task.dt*1000)
  853. + dtfmt % (task.avgDt*1000)
  854. + dtfmt % (task.maxDt*1000)
  855. + priority.rjust(priorityWidth)
  856. + '\n')
  857. else:
  858. str += (task.name.ljust(taskNameWidth)
  859. + '----'.rjust(dtWidth)
  860. + '----'.rjust(dtWidth)
  861. + '----'.rjust(dtWidth)
  862. + priority.rjust(priorityWidth)
  863. + '\n')
  864. str += '-------------------------------------------------------------------------\n'
  865. str += 'pendingTasks\n'
  866. str += '-------------------------------------------------------------------------\n'
  867. for pri, taskList in self.pendingTaskDict.items():
  868. for task in taskList:
  869. if task.isRemoved():
  870. taskName = '(PR)' + task.name
  871. else:
  872. taskName = '(P)' + task.name
  873. if (self.taskTimerVerbose):
  874. str += (' ' + taskName.ljust(taskNameWidth-2)
  875. + dtfmt % (pri)
  876. + '\n')
  877. else:
  878. str += (' ' + taskName.ljust(taskNameWidth-2)
  879. + '----'.rjust(dtWidth)
  880. + '\n')
  881. str += '-------------------------------------------------------------------------\n'
  882. if (self.taskTimerVerbose):
  883. str += ('total'.ljust(taskNameWidth)
  884. + dtfmt % (totalDt*1000)
  885. + dtfmt % (totalAvgDt*1000)
  886. + '\n')
  887. else:
  888. str += ('total'.ljust(taskNameWidth)
  889. + '----'.rjust(dtWidth)
  890. + '----'.rjust(dtWidth)
  891. + '\n')
  892. str += '-------------------------------------------------------------------------\n'
  893. str += ('doLaterList'.ljust(taskNameWidth)
  894. + 'waitTime(s)'.rjust(dtWidth)
  895. + '\n')
  896. str += '-------------------------------------------------------------------------\n'
  897. # When we print, show the doLaterList in actual sorted order.
  898. # The priority heap is not actually in order - it is a tree
  899. # Make a shallow copy so we can sort it
  900. sortedDoLaterList = self.__doLaterList[:]
  901. sortedDoLaterList.sort(lambda a, b: cmp(a.wakeTime, b.wakeTime))
  902. sortedDoLaterList.reverse()
  903. for task in sortedDoLaterList:
  904. remainingTime = ((task.wakeTime) - self.currentTime)
  905. if task.isRemoved():
  906. taskName = '(R)' + task.name
  907. else:
  908. taskName = task.name
  909. str += (' ' + taskName.ljust(taskNameWidth-2)
  910. + dtfmt % (remainingTime)
  911. + '\n')
  912. str += '-------------------------------------------------------------------------\n'
  913. str += "End of taskMgr info\n"
  914. return str
  915. def resetStats(self):
  916. # WARNING: this screws up your do-later timings
  917. if self.taskTimerVerbose:
  918. for task in self.taskList:
  919. task.dt = 0
  920. task.avgDt = 0
  921. task.maxDt = 0
  922. task.runningTotal = 0
  923. task.setStartTimeFrame(self.currentTime, self.currentFrame)
  924. def popupControls(self):
  925. from direct.tkpanels import TaskManagerPanel
  926. return TaskManagerPanel.TaskManagerPanel(self)
  927. def __getTimeFrame(self):
  928. # WARNING: If you are testing tasks without an igLoop,
  929. # you must manually tick the clock
  930. # Ask for the time last frame
  931. if self.globalClock:
  932. return self.globalClock.getFrameTime(), self.globalClock.getFrameCount()
  933. # OK, we don't have a globalClock yet. This is therefore
  934. # running before the first frame.
  935. return self.trueClock.getShortTime(), 0
  936. def __getTime(self):
  937. if self.globalClock:
  938. return self.globalClock.getFrameTime()
  939. return self.trueClock.getShortTime()
  940. def startOsd(self):
  941. self.add(self.doOsd, 'taskMgr.doOsd')
  942. self._osdEnabled = None
  943. def osdEnabled(self):
  944. return hasattr(self, '_osdEnabled')
  945. def stopOsd(self):
  946. onScreenDebug.removeAllWithPrefix(TaskManager.OsdPrefix)
  947. self.remove('taskMgr.doOsd')
  948. del self._osdEnabled
  949. def doOsd(self, task):
  950. if not onScreenDebug.enabled:
  951. return
  952. prefix = TaskManager.OsdPrefix
  953. onScreenDebug.removeAllWithPrefix(prefix)
  954. taskNameWidth = 32
  955. dtWidth = 10
  956. priorityWidth = 10
  957. totalDt = 0
  958. totalAvgDt = 0
  959. i = 0
  960. onScreenDebug.add(
  961. ('%s%02i.taskList' % (prefix, i)).ljust(taskNameWidth),
  962. '%s %s %s %s' % (
  963. 'dt(ms)'.rjust(dtWidth),
  964. 'avg'.rjust(dtWidth),
  965. 'max'.rjust(dtWidth),
  966. 'priority'.rjust(priorityWidth),))
  967. i += 1
  968. for taskPriList in self.taskList:
  969. priority = `taskPriList.getPriority()`
  970. for task in taskPriList:
  971. if task is None:
  972. break
  973. if task.isRemoved():
  974. taskName = '(R)' + task.name
  975. else:
  976. taskName = task.name
  977. totalDt = totalDt + task.dt
  978. totalAvgDt = totalAvgDt + task.avgDt
  979. onScreenDebug.add(
  980. ('%s%02i.%s' % (prefix, i, task.name)).ljust(taskNameWidth),
  981. '%s %s %s %s' % (
  982. dtfmt % (task.dt*1000),
  983. dtfmt % (task.avgDt*1000),
  984. dtfmt % (task.maxDt*1000),
  985. priority.rjust(priorityWidth)))
  986. i += 1
  987. onScreenDebug.add(('%s%02i.total' % (prefix, i)).ljust(taskNameWidth),
  988. '%s %s' % (
  989. dtfmt % (totalDt*1000),
  990. dtfmt % (totalAvgDt*1000),))
  991. return cont
  992. # These constants are moved to the top level of the module,
  993. # to make it easier for legacy code. In general though, putting
  994. # constants at the top level of a module is deprecated.
  995. exit = Task.exit
  996. done = Task.done
  997. cont = Task.cont
  998. again = Task.again
  999. """
  1000. import Task
  1001. def goo(task):
  1002. print 'goo'
  1003. return Task.done
  1004. def bar(task):
  1005. print 'bar'
  1006. taskMgr.add(goo, 'goo')
  1007. return Task.done
  1008. def foo(task):
  1009. print 'foo'
  1010. taskMgr.add(bar, 'bar')
  1011. return Task.done
  1012. taskMgr.add(foo, 'foo')
  1013. """