Task.py 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938
  1. from libpandaexpressModules import *
  2. from DirectNotifyGlobal import *
  3. from PythonUtil import *
  4. from MessengerGlobal import *
  5. import time
  6. import fnmatch
  7. import string
  8. import signal
  9. from bisect import bisect
  10. # MRM: Need to make internal task variables like time, name, index
  11. # more unique (less likely to have name clashes)
  12. exit = -1
  13. done = 0
  14. cont = 1
  15. # Task needs this because it might run before __builtin__.globalClock
  16. # can be set.
  17. globalClock = ClockObject.getGlobalClock()
  18. def print_exc_plus():
  19. """
  20. Print the usual traceback information, followed by a listing of all the
  21. local variables in each frame.
  22. """
  23. import sys
  24. import traceback
  25. tb = sys.exc_info()[2]
  26. while 1:
  27. if not tb.tb_next:
  28. break
  29. tb = tb.tb_next
  30. stack = []
  31. f = tb.tb_frame
  32. while f:
  33. stack.append(f)
  34. f = f.f_back
  35. stack.reverse()
  36. traceback.print_exc()
  37. print "Locals by frame, innermost last"
  38. for frame in stack:
  39. print
  40. print "Frame %s in %s at line %s" % (frame.f_code.co_name,
  41. frame.f_code.co_filename,
  42. frame.f_lineno)
  43. for key, value in frame.f_locals.items():
  44. print "\t%20s = " % key,
  45. #We have to be careful not to cause a new error in our error
  46. #printer! Calling str() on an unknown object could cause an
  47. #error we don't want.
  48. try:
  49. print value
  50. except:
  51. print "<ERROR WHILE PRINTING VALUE>"
  52. class Task:
  53. count = 0
  54. def __init__(self, callback, priority = 0):
  55. # Unique ID for each task
  56. self.id = Task.count
  57. Task.count += 1
  58. self.__call__ = callback
  59. self.__priority = priority
  60. self.dt = 0.0
  61. self.maxDt = 0.0
  62. self.avgDt = 0.0
  63. self.runningTotal = 0.0
  64. self.pstats = None
  65. self.__removed = 0
  66. self.__onDoLaterList = 0
  67. def setOnDoLaterList(self, status):
  68. self.__onDoLaterList = status
  69. def isOnDoLaterList(self):
  70. return self.__onDoLaterList
  71. def remove(self):
  72. self.__removed = 1
  73. # Remove any refs to real objects
  74. # In case we hang around the doLaterList for a while
  75. del self.__call__
  76. def isRemoved(self):
  77. return self.__removed
  78. def getPriority(self):
  79. return self.__priority
  80. def setPriority(self, pri):
  81. self.__priority = pri
  82. def setStartTimeFrame(self, startTime, startFrame):
  83. self.starttime = startTime
  84. self.startframe = startFrame
  85. def setCurrentTimeFrame(self, currentTime, currentFrame):
  86. # Calculate and store this task's time (relative to when it started)
  87. self.time = currentTime - self.starttime
  88. self.frame = currentFrame - self.startframe
  89. def setupPStats(self, name):
  90. if __debug__:
  91. import PStatCollector
  92. self.pstats = PStatCollector.PStatCollector("App:Show code:" + name)
  93. def finishTask(self, verbose):
  94. if hasattr(self, "uponDeath"):
  95. self.uponDeath(self)
  96. if verbose:
  97. # We regret to announce...
  98. messenger.send('TaskManager-removeTask', sentArgs = [self, self.name])
  99. del self.uponDeath
  100. def __repr__(self):
  101. if hasattr(self, 'name'):
  102. return ('Task id: %s, name %s' % (self.id, self.name))
  103. else:
  104. return ('Task id: %s, no name' % (self.id))
  105. def pause(delayTime):
  106. def func(self):
  107. if (self.time < self.delayTime):
  108. return cont
  109. else:
  110. return done
  111. task = Task(func)
  112. task.name = 'pause'
  113. task.delayTime = delayTime
  114. return task
  115. def sequence(*taskList):
  116. return make_sequence(taskList)
  117. def make_sequence(taskList):
  118. def func(self):
  119. frameFinished = 0
  120. taskDoneStatus = -1
  121. while (not frameFinished):
  122. task = self.taskList[self.index]
  123. # If this is a new task, set its start time and frame
  124. if (self.index > self.prevIndex):
  125. task.setStartTimeFrame(self.time, self.frame)
  126. self.prevIndex = self.index
  127. # Calculate this task's time since it started
  128. task.setCurrentTimeFrame(self.time, self.frame)
  129. # Execute the current task
  130. ret = task(task)
  131. # Check the return value from the task
  132. # If this current task wants to continue,
  133. # come back to it next frame
  134. if (ret == cont):
  135. taskDoneStatus = cont
  136. frameFinished = 1
  137. # If this task is done, increment the index so that next frame
  138. # we will start executing the next task on the list
  139. elif (ret == done):
  140. self.index = self.index + 1
  141. taskDoneStatus = cont
  142. frameFinished = 0
  143. # If this task wants to exit, the sequence exits
  144. elif (ret == exit):
  145. taskDoneStatus = exit
  146. frameFinished = 1
  147. # If we got to the end of the list, this sequence is done
  148. if (self.index >= len(self.taskList)):
  149. if TaskManager.notify.getDebug():
  150. TaskManager.notify.debug('sequence done: ' + self.name)
  151. frameFinished = 1
  152. taskDoneStatus = done
  153. return taskDoneStatus
  154. task = Task(func)
  155. task.name = 'sequence'
  156. task.taskList = taskList
  157. task.prevIndex = -1
  158. task.index = 0
  159. return task
  160. def resetSequence(task):
  161. # Should this automatically be done as part of spawnTaskNamed?
  162. # Or should one have to create a new task instance every time
  163. # one wishes to spawn a task (currently sequences and can
  164. # only be fired off once
  165. task.index = 0
  166. task.prevIndex = -1
  167. def loop(*taskList):
  168. return make_loop(taskList)
  169. def make_loop(taskList):
  170. def func(self):
  171. frameFinished = 0
  172. taskDoneStatus = -1
  173. while (not frameFinished):
  174. task = self.taskList[self.index]
  175. # If this is a new task, set its start time and frame
  176. if (self.index > self.prevIndex):
  177. task.setStartTimeFrame(self.time, self.frame)
  178. self.prevIndex = self.index
  179. # Calculate this task's time since it started
  180. task.setCurrentTimeFrame(self.time, self.frame)
  181. # Execute the current task
  182. ret = task(task)
  183. # Check the return value from the task
  184. # If this current task wants to continue,
  185. # come back to it next frame
  186. if (ret == cont):
  187. taskDoneStatus = cont
  188. frameFinished = 1
  189. # If this task is done, increment the index so that next frame
  190. # we will start executing the next task on the list
  191. # TODO: we should go to the next frame now
  192. elif (ret == done):
  193. self.index = self.index + 1
  194. taskDoneStatus = cont
  195. frameFinished = 0
  196. # If this task wants to exit, the sequence exits
  197. elif (ret == exit):
  198. taskDoneStatus = exit
  199. frameFinished = 1
  200. # If we got to the end of the list, wrap back around
  201. if (self.index >= len(self.taskList)):
  202. self.prevIndex = -1
  203. self.index = 0
  204. frameFinished = 1
  205. return taskDoneStatus
  206. task = Task(func)
  207. task.name = 'loop'
  208. task.taskList = taskList
  209. task.prevIndex = -1
  210. task.index = 0
  211. return task
  212. class TaskPriorityList(list):
  213. def __init__(self, priority):
  214. self.__priority = priority
  215. self.__emptyIndex = 0
  216. def getPriority(self):
  217. return self.__priority
  218. def getEmptyIndex(self):
  219. return self.__emptyIndex
  220. def setEmptyIndex(self, index):
  221. self.__emptyIndex = index
  222. def add(self, task):
  223. if (self.__emptyIndex >= len(self)):
  224. self.append(task)
  225. self.__emptyIndex += 1
  226. else:
  227. self[self.__emptyIndex] = task
  228. self.__emptyIndex += 1
  229. def remove(self, i):
  230. assert(i <= len(self))
  231. if (len(self) == 1) and (i == 1):
  232. self[i] = None
  233. self.__emptyIndex = 0
  234. else:
  235. # Swap the last element for this one
  236. lastElement = self[self.__emptyIndex-1]
  237. self[i] = lastElement
  238. self[self.__emptyIndex-1] = None
  239. self.__emptyIndex -= 1
  240. class DoLaterList(list):
  241. """
  242. This is a list that maintains sorted order of wakeTimes on tasks
  243. """
  244. def __init__(self):
  245. list.__init__(self)
  246. def peek(self):
  247. return self[-1]
  248. def add(self, task):
  249. """
  250. Add task, keeping the list reverse sorted so we can pop off the end efficiently.
  251. This does a binary search for the index to insert into.
  252. Returns the index at which task was inserted.
  253. """
  254. lo = 0
  255. hi = len(self)
  256. while lo < hi:
  257. mid = (lo+hi)//2
  258. if task.wakeTime > self[mid].wakeTime:
  259. hi = mid
  260. else:
  261. lo = mid+1
  262. list.insert(self, lo, task)
  263. return lo
  264. def remove(self, task):
  265. """
  266. This does a binary search for the index of the task
  267. Then deletes the entry from the list
  268. """
  269. lo = 0
  270. hi = len(self)
  271. while lo < hi:
  272. mid = (lo+hi)//2
  273. if task is self[mid]:
  274. del self[mid]
  275. return 1
  276. elif task.wakeTime > self[mid].wakeTime:
  277. hi = mid
  278. else:
  279. lo = mid+1
  280. return 0
  281. class TaskManager:
  282. notify = None
  283. def __init__(self):
  284. self.running = 0
  285. self.stepping = 0
  286. self.taskList = []
  287. # Dictionary of priority to newTaskLists
  288. self.pendingTaskDict = {}
  289. self.doLaterList = DoLaterList()
  290. self.currentTime, self.currentFrame = self.__getTimeFrame()
  291. if (TaskManager.notify == None):
  292. TaskManager.notify = directNotify.newCategory("TaskManager")
  293. self.taskTimerVerbose = 0
  294. self.extendedExceptions = 0
  295. self.fKeyboardInterrupt = 0
  296. self.interruptCount = 0
  297. self.pStatsTasks = 0
  298. self.resumeFunc = None
  299. self.fVerbose = 0
  300. # Dictionary of task name to list of tasks with that name
  301. self.nameDict = {}
  302. self.add(self.__doLaterProcessor, "doLaterProcessor", -10)
  303. def stepping(self, value):
  304. self.stepping = value
  305. def setVerbose(self, value):
  306. self.fVerbose = value
  307. messenger.send('TaskManager-setVerbose', sentArgs = [value])
  308. def keyboardInterruptHandler(self, signalNumber, stackFrame):
  309. self.fKeyboardInterrupt = 1
  310. self.interruptCount += 1
  311. if self.interruptCount == 2:
  312. # The user must really want to interrupt this process
  313. # Next time around use the default interrupt handler
  314. signal.signal(signal.SIGINT, signal.default_int_handler)
  315. def hasTaskNamed(self, taskName):
  316. # TODO: check pending task list
  317. # Get the tasks with this name
  318. tasks = self.nameDict.get(taskName)
  319. # If we found some, see if any of them are still active (not removed)
  320. if tasks:
  321. for task in tasks:
  322. if not task.isRemoved():
  323. return 1
  324. # Didnt find any, return 0
  325. return 0
  326. def getTasksNamed(self, taskName):
  327. # TODO: check pending tasks
  328. # Get the tasks with this name
  329. tasks = self.nameDict.get(taskName, [])
  330. # Filter out the tasks that have been removed
  331. if tasks:
  332. tasks = filter(lambda task: not task.isRemoved(), tasks)
  333. return tasks
  334. def __doLaterProcessor(self, task):
  335. # Removing the tasks during the for loop is a bad idea
  336. # Instead we just flag them as removed
  337. # Later, somebody else cleans them out
  338. while self.doLaterList:
  339. # Check the first one on the list (actually the last one on
  340. # the list since it is reverse sorted)
  341. dl = self.doLaterList.peek()
  342. if dl.isRemoved():
  343. # Get rid of this task forever
  344. self.doLaterList.pop()
  345. dl.setOnDoLaterList(0)
  346. continue
  347. # If the time now is less than the start of the doLater + delay
  348. # then we are not ready yet, continue to next one
  349. elif task.time < dl.wakeTime:
  350. # Since the list is sorted, the first one we get to, that
  351. # is not ready to go, we can return
  352. break
  353. else:
  354. if TaskManager.notify.getDebug():
  355. TaskManager.notify.debug('__doLaterProcessor: spawning %s' % (dl))
  356. self.doLaterList.pop()
  357. dl.setStartTimeFrame(self.currentTime, self.currentFrame)
  358. # No longer on the doLaterList
  359. dl.setOnDoLaterList(0)
  360. self.__addPendingTask(dl)
  361. continue
  362. return cont
  363. def __spawnDoLater(self, task):
  364. if TaskManager.notify.getDebug():
  365. TaskManager.notify.debug('spawning doLater: %s' % (task))
  366. # Add this task to the nameDict
  367. nameList = self.nameDict.setdefault(task.name, [])
  368. nameList.append(task)
  369. # be sure to ask the globalClock for the current frame time
  370. # rather than use a cached value; globalClock's frame time may
  371. # have been synced since the start of this frame
  372. currentTime = globalClock.getFrameTime()
  373. task.setStartTimeFrame(currentTime, self.currentFrame)
  374. # Cache the time we should wake up for easier sorting
  375. task.wakeTime = task.starttime + task.delayTime
  376. # For more efficient removal, note that it is on the doLaterList
  377. task.setOnDoLaterList(1)
  378. index = self.doLaterList.add(task)
  379. if self.fVerbose:
  380. # Alert the world, a new task is born!
  381. messenger.send('TaskManager-spawnDoLater',
  382. sentArgs = [task, task.name, task.id])
  383. return task
  384. def doLater(self, delayTime, task, taskName):
  385. if TaskManager.notify.getDebug():
  386. TaskManager.notify.debug('doLater: %s' % (taskName))
  387. task.delayTime = delayTime
  388. task.name = taskName
  389. return self.__spawnDoLater(task)
  390. def doMethodLater(self, delayTime, func, taskName):
  391. task = Task(func)
  392. return self.doLater(delayTime, task, taskName)
  393. def add(self, funcOrTask, name, priority = 0):
  394. """
  395. Add a new task to the taskMgr.
  396. You can add a Task object or a method that takes one argument.
  397. """
  398. if TaskManager.notify.getDebug():
  399. TaskManager.notify.debug('add: %s' % (name))
  400. if isinstance(funcOrTask, Task):
  401. funcOrTask.setPriority(priority)
  402. return self.__spawnTaskNamed(funcOrTask, name)
  403. elif callable(funcOrTask):
  404. return self.__spawnMethodNamed(funcOrTask, name, priority)
  405. else:
  406. self.notify.error('add: Tried to add a task that was not a Task or a func')
  407. def __spawnMethodNamed(self, func, name, priority=0):
  408. task = Task(func, priority)
  409. return self.__spawnTaskNamed(task, name)
  410. def __spawnTaskNamed(self, task, name):
  411. if TaskManager.notify.getDebug():
  412. TaskManager.notify.debug('__spawnTaskNamed: %s' % (name))
  413. # Init params
  414. task.name = name
  415. # be sure to ask the globalClock for the current frame time
  416. # rather than use a cached value; globalClock's frame time may
  417. # have been synced since the start of this frame
  418. currentTime = globalClock.getFrameTime()
  419. task.setStartTimeFrame(currentTime, self.currentFrame)
  420. nameList = self.nameDict.setdefault(name, [])
  421. nameList.append(task)
  422. # Put it on the list for the end of this frame
  423. self.__addPendingTask(task)
  424. return task
  425. def __addPendingTask(self, task):
  426. if TaskManager.notify.getDebug():
  427. TaskManager.notify.debug('__addPendingTask: %s' % (task.name))
  428. pri = task.getPriority()
  429. if self.pendingTaskDict.has_key(pri):
  430. taskPriList = self.pendingTaskDict[pri]
  431. else:
  432. taskPriList = TaskPriorityList(pri)
  433. self.pendingTaskDict[pri] = taskPriList
  434. taskPriList.add(task)
  435. def __addNewTask(self, task):
  436. # The taskList is really an ordered list of TaskPriorityLists
  437. # search back from the end of the list until we find a
  438. # taskList with a lower priority, or we hit the start of the list
  439. taskPriority = task.getPriority()
  440. index = len(self.taskList) - 1
  441. while (1):
  442. if (index < 0):
  443. newList = TaskPriorityList(taskPriority)
  444. newList.add(task)
  445. # Add the new list to the beginning of the taskList
  446. self.taskList.insert(0, newList)
  447. break
  448. taskListPriority = self.taskList[index].getPriority()
  449. if (taskListPriority == taskPriority):
  450. self.taskList[index].add(task)
  451. break
  452. elif (taskListPriority > taskPriority):
  453. index = index - 1
  454. elif (taskListPriority < taskPriority):
  455. # Time to insert
  456. newList = TaskPriorityList(taskPriority)
  457. newList.add(task)
  458. # Insert this new priority level
  459. # If we are already at the end, just append it
  460. if (index == len(self.taskList)-1):
  461. self.taskList.append(newList)
  462. else:
  463. # Otherwise insert it
  464. self.taskList.insert(index+1, newList)
  465. break
  466. if __debug__:
  467. if self.pStatsTasks and task.name != "igloop":
  468. # Get the PStats name for the task. By convention,
  469. # this is everything until the first hyphen; the part
  470. # of the task name following the hyphen is generally
  471. # used to differentiate particular tasks that do the
  472. # same thing to different objects.
  473. name = task.name
  474. hyphen = name.find('-')
  475. if hyphen >= 0:
  476. name = name[0:hyphen]
  477. task.setupPStats(name)
  478. if self.fVerbose:
  479. # Alert the world, a new task is born!
  480. messenger.send('TaskManager-spawnTask', sentArgs = [task, task.name, index])
  481. return task
  482. def remove(self, taskOrName):
  483. if type(taskOrName) == type(''):
  484. return self.__removeTasksNamed(taskOrName)
  485. elif isinstance(taskOrName, Task):
  486. return self.__removeTasksEqual(taskOrName)
  487. else:
  488. self.notify.error('remove takes a string or a Task')
  489. def removeTasksMatching(self, taskPattern):
  490. """removeTasksMatching(self, string taskPattern)
  491. Removes tasks whose names match the pattern, which can include
  492. standard shell globbing characters like *, ?, and [].
  493. """
  494. if TaskManager.notify.getDebug():
  495. TaskManager.notify.debug('removing tasks matching: ' + taskPattern)
  496. num = 0
  497. keyList = filter(lambda key: fnmatch.fnmatchcase(key, taskPattern), self.nameDict.keys())
  498. for key in keyList:
  499. num += self.__removeTasksNamed(key)
  500. return num
  501. def __removeTasksEqual(self, task):
  502. # Remove this task from the nameDict (should be a short list)
  503. if self.__removeTaskFromNameDict(task):
  504. if TaskManager.notify.getDebug():
  505. TaskManager.notify.debug('__removeTasksEqual: removing task: %s' % (task))
  506. # Flag the task for removal from the real list
  507. task.remove()
  508. if task.isOnDoLaterList():
  509. self.doLaterList.remove(task)
  510. # Cleanup stuff
  511. task.finishTask(self.fVerbose)
  512. return 1
  513. else:
  514. return 0
  515. def __removeTasksNamed(self, taskName):
  516. if not self.nameDict.has_key(taskName):
  517. return 0
  518. if TaskManager.notify.getDebug():
  519. TaskManager.notify.debug('__removeTasksNamed: removing tasks named: %s' % (taskName))
  520. for task in self.nameDict[taskName]:
  521. # Flag for removal
  522. task.remove()
  523. if task.isOnDoLaterList():
  524. self.doLaterList.remove(task)
  525. # Cleanup stuff
  526. task.finishTask(self.fVerbose)
  527. # Record the number of tasks removed
  528. num = len(self.nameDict[taskName])
  529. # Blow away the nameDict entry completely
  530. del self.nameDict[taskName]
  531. return num
  532. def __removeTaskFromNameDict(self, task):
  533. taskName = task.name
  534. # If this is the only task with that name, remove the dict entry
  535. tasksWithName = self.nameDict.get(taskName)
  536. if tasksWithName:
  537. if task in tasksWithName:
  538. tasksWithName.remove(task)
  539. if len(tasksWithName) == 0:
  540. del self.nameDict[taskName]
  541. return 1
  542. return 0
  543. def __executeTask(self, task):
  544. task.setCurrentTimeFrame(self.currentTime, self.currentFrame)
  545. if not self.taskTimerVerbose:
  546. # don't record timing info
  547. ret = task(task)
  548. else:
  549. # Run the task and check the return value
  550. if task.pstats:
  551. task.pstats.start()
  552. startTime = globalClock.getRealTime()
  553. ret = task(task)
  554. endTime = globalClock.getRealTime()
  555. if task.pstats:
  556. task.pstats.stop()
  557. # Record the dt
  558. dt = endTime - startTime
  559. task.dt = dt
  560. # See if this is the new max
  561. if dt > task.maxDt:
  562. task.maxDt = dt
  563. # Record the running total of all dts so we can compute an average
  564. task.runningTotal = task.runningTotal + dt
  565. if (task.frame > 0):
  566. task.avgDt = (task.runningTotal / task.frame)
  567. else:
  568. task.avgDt = 0
  569. return ret
  570. def __stepThroughList(self, taskPriList):
  571. # Traverse the taskPriList with an iterator
  572. i = 0
  573. while (i < len(taskPriList)):
  574. task = taskPriList[i]
  575. # See if we are at the end of the real tasks
  576. if task is None:
  577. break
  578. # See if this task has been removed in show code
  579. if task.isRemoved():
  580. if TaskManager.notify.getDebug():
  581. assert(TaskManager.notify.debug('__stepThroughList: task is flagged for removal %s' % (task)))
  582. # If it was removed in show code, it will need finishTask run
  583. # If it was removed by the taskMgr, it will not, but that is ok
  584. # because finishTask is safe to call twice
  585. task.finishTask(self.fVerbose)
  586. taskPriList.remove(i)
  587. # Do not increment the iterator
  588. continue
  589. # Now actually execute the task
  590. ret = self.__executeTask(task)
  591. # See if the task is done
  592. if (ret == cont):
  593. # Leave it for next frame, its not done yet
  594. pass
  595. elif ((ret == done) or (ret == exit) or (ret == None)):
  596. if TaskManager.notify.getDebug():
  597. assert(TaskManager.notify.debug('__stepThroughList: task is finished %s' % (task)))
  598. # Remove the task
  599. if not task.isRemoved():
  600. if TaskManager.notify.getDebug():
  601. assert(TaskManager.notify.debug('__stepThroughList: task not removed %s' % (task)))
  602. task.remove()
  603. # Note: Should not need to remove from doLaterList here because
  604. # this task is not in the doLaterList
  605. task.finishTask(self.fVerbose)
  606. self.__removeTaskFromNameDict(task)
  607. else:
  608. if TaskManager.notify.getDebug():
  609. assert(TaskManager.notify.debug('__stepThroughList: task already removed %s' % (task)))
  610. self.__removeTaskFromNameDict(task)
  611. taskPriList.remove(i)
  612. # Do not increment the iterator
  613. continue
  614. else:
  615. raise StandardError, "Task named %s did not return cont, exit, done, or None" % task.name
  616. # Move to the next element
  617. i += 1
  618. def __addPendingTasksToTaskList(self):
  619. # Now that we are all done, add any left over pendingTasks generated in
  620. # priority levels lower or higher than where we were when we iterated
  621. for taskList in self.pendingTaskDict.values():
  622. for task in taskList:
  623. if (task and not task.isRemoved()):
  624. if TaskManager.notify.getDebug():
  625. assert(TaskManager.notify.debug('step: moving %s from pending to taskList' % (task.name)))
  626. self.__addNewTask(task)
  627. self.pendingTaskDict.clear()
  628. def step(self):
  629. if TaskManager.notify.getDebug():
  630. assert(TaskManager.notify.debug('step: begin'))
  631. self.currentTime, self.currentFrame = self.__getTimeFrame()
  632. # Replace keyboard interrupt handler during task list processing
  633. # so we catch the keyboard interrupt but don't handle it until
  634. # after task list processing is complete.
  635. self.fKeyboardInterrupt = 0
  636. self.interruptCount = 0
  637. signal.signal(signal.SIGINT, self.keyboardInterruptHandler)
  638. # Traverse the task list in order because it is in priority order
  639. priIndex = 0
  640. while priIndex < len(self.taskList):
  641. taskPriList = self.taskList[priIndex]
  642. pri = taskPriList.getPriority()
  643. if TaskManager.notify.getDebug():
  644. assert(TaskManager.notify.debug('step: running through taskList at pri: %s, priIndex: %s' % (pri, priIndex)))
  645. self.__stepThroughList(taskPriList)
  646. # Now see if that generated any pending tasks for this taskPriList
  647. pendingTasks = self.pendingTaskDict.get(pri, [])
  648. while pendingTasks:
  649. if TaskManager.notify.getDebug():
  650. assert(TaskManager.notify.debug('step: running through pending tasks at pri: %s' % (pri)))
  651. # Remove them from the pendingTaskDict
  652. del self.pendingTaskDict[pri]
  653. # Execute them
  654. self.__stepThroughList(pendingTasks)
  655. # Add these to the real taskList
  656. for task in pendingTasks:
  657. if (task and not task.isRemoved()):
  658. if TaskManager.notify.getDebug():
  659. assert(TaskManager.notify.debug('step: moving %s from pending to taskList' % (task.name)))
  660. self.__addNewTask(task)
  661. # See if we generated any more for this pri level
  662. pendingTasks = self.pendingTaskDict.get(pri, [])
  663. # Any new tasks that were made pending should be converted
  664. # to real tasks now in case they need to run this frame at a
  665. # later priority level
  666. self.__addPendingTasksToTaskList()
  667. # Go to the next priority level
  668. priIndex += 1
  669. # Add new pending tasks
  670. self.__addPendingTasksToTaskList()
  671. # Restore default interrupt handler
  672. signal.signal(signal.SIGINT, signal.default_int_handler)
  673. if self.fKeyboardInterrupt:
  674. raise KeyboardInterrupt
  675. return
  676. def run(self):
  677. # Set the clock to have last frame's time in case we were
  678. # Paused at the prompt for a long time
  679. t = globalClock.getFrameTime()
  680. timeDelta = t - globalClock.getRealTime()
  681. globalClock.setRealTime(t)
  682. messenger.send("resetClock", [timeDelta])
  683. if self.resumeFunc != None:
  684. self.resumeFunc()
  685. if self.stepping:
  686. self.step()
  687. else:
  688. self.running = 1
  689. while self.running:
  690. try:
  691. self.step()
  692. except KeyboardInterrupt:
  693. self.stop()
  694. except:
  695. if self.extendedExceptions:
  696. self.stop()
  697. print_exc_plus()
  698. else:
  699. raise
  700. def stop(self):
  701. # Set a flag so we will stop before beginning next frame
  702. self.running = 0
  703. def replaceMethod(self, oldMethod, newFunction):
  704. import new
  705. for taskPriList in self.taskList:
  706. for task in taskPriList:
  707. if (task is None) or (task.isRemoved()):
  708. break
  709. method = task.__call__
  710. if (type(method) == types.MethodType):
  711. function = method.im_func
  712. else:
  713. function = method
  714. #print ('function: ' + `function` + '\n' +
  715. # 'method: ' + `method` + '\n' +
  716. # 'oldMethod: ' + `oldMethod` + '\n' +
  717. # 'newFunction: ' + `newFunction` + '\n')
  718. if (function == oldMethod):
  719. newMethod = new.instancemethod(newFunction,
  720. method.im_self,
  721. method.im_class)
  722. task.__call__ = newMethod
  723. # Found it return true
  724. return 1
  725. return 0
  726. def __repr__(self):
  727. taskNameWidth = 32
  728. dtWidth = 10
  729. priorityWidth = 10
  730. totalDt = 0
  731. totalAvgDt = 0
  732. str = ('taskList'.ljust(taskNameWidth)
  733. + 'dt(ms)'.rjust(dtWidth)
  734. + 'avg'.rjust(dtWidth)
  735. + 'max'.rjust(dtWidth)
  736. + 'priority'.rjust(priorityWidth)
  737. + '\n')
  738. str = str + '---------------------------------------------------------------\n'
  739. for taskPriList in self.taskList:
  740. priority = `taskPriList.getPriority()`
  741. for task in taskPriList:
  742. if task is None:
  743. break
  744. totalDt = totalDt + task.dt
  745. totalAvgDt = totalAvgDt + task.avgDt
  746. if task.isRemoved():
  747. taskName = '(R)' + task.name
  748. else:
  749. taskName = task.name
  750. if (self.taskTimerVerbose):
  751. import fpformat
  752. str = str + (taskName.ljust(taskNameWidth)
  753. + fpformat.fix(task.dt*1000, 2).rjust(dtWidth)
  754. + fpformat.fix(task.avgDt*1000, 2).rjust(dtWidth)
  755. + fpformat.fix(task.maxDt*1000, 2).rjust(dtWidth)
  756. + priority.rjust(priorityWidth)
  757. + '\n')
  758. else:
  759. str = str + (task.name.ljust(taskNameWidth)
  760. + '----'.rjust(dtWidth)
  761. + '----'.rjust(dtWidth)
  762. + '----'.rjust(dtWidth)
  763. + priority.rjust(priorityWidth)
  764. + '\n')
  765. str = str + '---------------------------------------------------------------\n'
  766. str = str + ' pendingTasks\n'
  767. str = str + '---------------------------------------------------------------\n'
  768. for pri, taskList in self.pendingTaskDict.items():
  769. for task in taskList:
  770. remainingTime = ((task.starttime) - self.currentTime)
  771. if task.isRemoved():
  772. taskName = '(PR)' + task.name
  773. else:
  774. taskName = '(P)' + task.name
  775. if (self.taskTimerVerbose):
  776. import fpformat
  777. str = str + (' ' + taskName.ljust(taskNameWidth-2)
  778. + fpformat.fix(pri, 2).rjust(dtWidth)
  779. + '\n')
  780. else:
  781. str = str + (' ' + taskName.ljust(taskNameWidth-2)
  782. + '----'.rjust(dtWidth)
  783. + '\n')
  784. str = str + '---------------------------------------------------------------\n'
  785. str = str + ' doLaterList\n'
  786. str = str + '---------------------------------------------------------------\n'
  787. for task in self.doLaterList:
  788. remainingTime = ((task.wakeTime) - self.currentTime)
  789. if task.isRemoved():
  790. taskName = '(R)' + task.name
  791. else:
  792. taskName = task.name
  793. if (self.taskTimerVerbose):
  794. import fpformat
  795. str = str + (' ' + taskName.ljust(taskNameWidth-2)
  796. + fpformat.fix(remainingTime, 2).rjust(dtWidth)
  797. + '\n')
  798. else:
  799. str = str + (' ' + taskName.ljust(taskNameWidth-2)
  800. + '----'.rjust(dtWidth)
  801. + '\n')
  802. str = str + '---------------------------------------------------------------\n'
  803. if (self.taskTimerVerbose):
  804. import fpformat
  805. str = str + ('total'.ljust(taskNameWidth)
  806. + fpformat.fix(totalDt*1000, 2).rjust(dtWidth)
  807. + fpformat.fix(totalAvgDt*1000, 2).rjust(dtWidth)
  808. + '\n')
  809. else:
  810. str = str + ('total'.ljust(taskNameWidth)
  811. + '----'.rjust(dtWidth)
  812. + '----'.rjust(dtWidth)
  813. + '\n')
  814. return str
  815. def resetStats(self):
  816. # WARNING: this screws up your do-later timings
  817. for task in self.taskList:
  818. task.dt = 0
  819. task.avgDt = 0
  820. task.maxDt = 0
  821. task.runningTotal = 0
  822. task.setStartTimeFrame(self.currentTime, self.currentFrame)
  823. def popupControls(self):
  824. import TaskManagerPanel
  825. return TaskManagerPanel.TaskManagerPanel(self)
  826. def __getTimeFrame(self):
  827. # WARNING: If you are testing tasks without an igloop,
  828. # you must manually tick the clock
  829. # Ask for the time last frame
  830. return globalClock.getFrameTime(), globalClock.getFrameCount()
  831. """
  832. import Task
  833. def goo(task):
  834. print 'goo'
  835. return Task.done
  836. def bar(task):
  837. print 'bar'
  838. taskMgr.add(goo, 'goo')
  839. return Task.done
  840. def foo(task):
  841. print 'foo'
  842. taskMgr.add(bar, 'bar')
  843. return Task.done
  844. taskMgr.add(foo, 'foo')
  845. """