Task.py 43 KB

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