Task.py 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671
  1. from libpandaexpressModules import *
  2. from DirectNotifyGlobal import *
  3. from PythonUtil import *
  4. import time
  5. import fnmatch
  6. import string
  7. # MRM: Need to make internal task variables like time, name, index
  8. # more unique (less likely to have name clashes)
  9. exit = -1
  10. done = 0
  11. cont = 1
  12. def print_exc_plus():
  13. """
  14. Print the usual traceback information, followed by a listing of all the
  15. local variables in each frame.
  16. """
  17. import sys
  18. import traceback
  19. tb = sys.exc_info()[2]
  20. while 1:
  21. if not tb.tb_next:
  22. break
  23. tb = tb.tb_next
  24. stack = []
  25. f = tb.tb_frame
  26. while f:
  27. stack.append(f)
  28. f = f.f_back
  29. stack.reverse()
  30. traceback.print_exc()
  31. print "Locals by frame, innermost last"
  32. for frame in stack:
  33. print
  34. print "Frame %s in %s at line %s" % (frame.f_code.co_name,
  35. frame.f_code.co_filename,
  36. frame.f_lineno)
  37. for key, value in frame.f_locals.items():
  38. print "\t%20s = " % key,
  39. #We have to be careful not to cause a new error in our error
  40. #printer! Calling str() on an unknown object could cause an
  41. #error we don't want.
  42. try:
  43. print value
  44. except:
  45. print "<ERROR WHILE PRINTING VALUE>"
  46. # Store the global clock
  47. globalClock = ClockObject.getGlobalClock()
  48. def getTimeFrame():
  49. # WARNING: If you are testing tasks without an igloop,
  50. # you must manually tick the clock
  51. # Ask for the time last frame
  52. t = globalClock.getFrameTime()
  53. # Get the new frame count
  54. f = globalClock.getFrameCount()
  55. return t, f
  56. class Task:
  57. count = 0
  58. def __init__(self, callback, priority = 0):
  59. # Unique ID for each task
  60. self.id = Task.count
  61. Task.count += 1
  62. self.__call__ = callback
  63. self._priority = priority
  64. self.uponDeath = None
  65. self.dt = 0.0
  66. self.maxDt = 0.0
  67. self.avgDt = 0.0
  68. self.runningTotal = 0.0
  69. self.pstats = None
  70. def getPriority(self):
  71. return self._priority
  72. def setPriority(self, pri):
  73. self._priority = pri
  74. def setStartTimeFrame(self, startTime, startFrame):
  75. self.starttime = startTime
  76. self.startframe = startFrame
  77. def setCurrentTimeFrame(self, currentTime, currentFrame):
  78. # Calculate and store this task's time (relative to when it started)
  79. self.time = currentTime - self.starttime
  80. self.frame = currentFrame - self.startframe
  81. def setupPStats(self, name):
  82. if __debug__:
  83. import PStatCollector
  84. self.pstats = PStatCollector.PStatCollector("App:Show code:" + name)
  85. def doLater(delayTime, task, taskName):
  86. task.name = taskName
  87. # make a sequence out of the delay and the task
  88. seq = sequence(pause(delayTime), task)
  89. return seq
  90. def pause(delayTime):
  91. def func(self):
  92. if (self.time < self.delayTime):
  93. return cont
  94. else:
  95. # Time is up, return done
  96. # TaskManager.notify.debug('pause done: ' + self.name)
  97. return done
  98. task = Task(func)
  99. task.name = 'pause'
  100. task.delayTime = delayTime
  101. return task
  102. def release():
  103. def func(self):
  104. # A release is immediately done
  105. # TaskManager.notify.debug('release done: ' + self.name)
  106. return done
  107. task = Task(func)
  108. task.name = 'release'
  109. return task
  110. def sequence(*taskList):
  111. return make_sequence(taskList)
  112. def make_sequence(taskList):
  113. def func(self):
  114. frameFinished = 0
  115. taskDoneStatus = -1
  116. while (not frameFinished):
  117. task = self.taskList[self.index]
  118. # If this is a new task, set its start time and frame
  119. if (self.index > self.prevIndex):
  120. task.setStartTimeFrame(self.time, self.frame)
  121. self.prevIndex = self.index
  122. # Calculate this task's time since it started
  123. task.setCurrentTimeFrame(self.time, self.frame)
  124. # Execute the current task
  125. ret = task(task)
  126. # Check the return value from the task
  127. # If this current task wants to continue,
  128. # come back to it next frame
  129. if (ret == cont):
  130. taskDoneStatus = cont
  131. frameFinished = 1
  132. # If this task is done, increment the index so that next frame
  133. # we will start executing the next task on the list
  134. elif (ret == done):
  135. self.index = self.index + 1
  136. taskDoneStatus = cont
  137. frameFinished = 0
  138. # If this task wants to exit, the sequence exits
  139. elif (ret == exit):
  140. taskDoneStatus = exit
  141. frameFinished = 1
  142. # If we got to the end of the list, this sequence is done
  143. if (self.index >= len(self.taskList)):
  144. # TaskManager.notify.debug('sequence done: ' + self.name)
  145. frameFinished = 1
  146. taskDoneStatus = done
  147. return taskDoneStatus
  148. task = Task(func)
  149. task.name = 'sequence'
  150. task.taskList = taskList
  151. task.prevIndex = -1
  152. task.index = 0
  153. return task
  154. def resetSequence(task):
  155. # Should this automatically be done as part of spawnTaskNamed?
  156. # Or should one have to create a new task instance every time
  157. # one wishes to spawn a task (currently sequences and can
  158. # only be fired off once
  159. task.index = 0
  160. task.prevIndex = -1
  161. def loop(*taskList):
  162. return make_loop(taskList)
  163. def make_loop(taskList):
  164. def func(self):
  165. frameFinished = 0
  166. taskDoneStatus = -1
  167. while (not frameFinished):
  168. task = self.taskList[self.index]
  169. # If this is a new task, set its start time and frame
  170. if (self.index > self.prevIndex):
  171. task.setStartTimeFrame(self.time, self.frame)
  172. self.prevIndex = self.index
  173. # Calculate this task's time since it started
  174. task.setCurrentTimeFrame(self.time, self.frame)
  175. # Execute the current task
  176. ret = task(task)
  177. # Check the return value from the task
  178. # If this current task wants to continue,
  179. # come back to it next frame
  180. if (ret == cont):
  181. taskDoneStatus = cont
  182. frameFinished = 1
  183. # If this task is done, increment the index so that next frame
  184. # we will start executing the next task on the list
  185. # TODO: we should go to the next frame now
  186. elif (ret == done):
  187. self.index = self.index + 1
  188. taskDoneStatus = cont
  189. frameFinished = 0
  190. # If this task wants to exit, the sequence exits
  191. elif (ret == exit):
  192. taskDoneStatus = exit
  193. frameFinished = 1
  194. # If we got to the end of the list, wrap back around
  195. if (self.index >= len(self.taskList)):
  196. self.prevIndex = -1
  197. self.index = 0
  198. frameFinished = 1
  199. return taskDoneStatus
  200. task = Task(func)
  201. task.name = 'loop'
  202. task.taskList = taskList
  203. task.prevIndex = -1
  204. task.index = 0
  205. return task
  206. def makeSpawner(task, taskName, taskMgr):
  207. def func(self):
  208. self.taskMgr.spawnTaskNamed(self.task, self.taskName)
  209. return done
  210. newTask = Task(func)
  211. newTask.name = taskName + "-spawner"
  212. newTask.task = task
  213. newTask.taskName = taskName
  214. newTask.taskMgr = taskMgr
  215. return newTask
  216. def makeSequenceFromTimeline(timelineList, taskMgr):
  217. timeline = []
  218. lastPause = 0
  219. sortedList = list(timelineList)
  220. sortedList.sort()
  221. for triple in sortedList:
  222. t = triple[0] - lastPause
  223. lastPause = triple[0]
  224. task = triple[1]
  225. taskName = triple[2]
  226. timeline.append(pause(t))
  227. timeline.append(makeSpawner(task, taskName, taskMgr))
  228. return make_sequence(timeline)
  229. def timeline(*timelineList):
  230. def func(self):
  231. # Step our sub task manager (returns the number of tasks remaining)
  232. lenTaskList = self.taskMgr.step()
  233. # The sequence start time is the same as our start time
  234. self.sequence.time = self.time
  235. self.sequence.frame = self.frame
  236. if (not self.sequenceDone):
  237. # Execute the sequence for this frame
  238. seqRet = self.sequence(self.sequence)
  239. # See if sequence is done
  240. if (seqRet == done):
  241. self.sequenceDone = 1
  242. # See if the timeline is done
  243. if (lenTaskList == 0):
  244. # TaskManager.notify.debug('timeline done: ' + self.name)
  245. return done
  246. else:
  247. return cont
  248. else:
  249. return cont
  250. else:
  251. return cont
  252. task = Task(func)
  253. task.name = 'timeline'
  254. task.taskMgr = TaskManager()
  255. task.sequence = makeSequenceFromTimeline(timelineList, task.taskMgr)
  256. task.sequenceDone = 0
  257. return task
  258. class TaskManager:
  259. notify = None
  260. def __init__(self):
  261. self.running = 0
  262. self.stepping = 0
  263. self.taskList = []
  264. self.currentTime, self.currentFrame = getTimeFrame()
  265. if (TaskManager.notify == None):
  266. TaskManager.notify = directNotify.newCategory("TaskManager")
  267. self.taskTimerVerbose = 0
  268. self.pStatsTasks = 0
  269. self.resumeFunc = None
  270. self.fVerbose = 0
  271. def stepping(self, value):
  272. self.stepping = value
  273. def setVerbose(self, value):
  274. self.fVerbose = value
  275. messenger.send('TaskManager-setVerbose', sentArgs = [value])
  276. def spawnMethodNamed(self, func, name):
  277. task = Task(func)
  278. return self.spawnTaskNamed(task, name)
  279. def spawnTaskNamed(self, task, name):
  280. if TaskManager.notify.getDebug():
  281. TaskManager.notify.debug('spawning task named: ' + name)
  282. task.name = name
  283. task.setStartTimeFrame(self.currentTime, self.currentFrame)
  284. # search back from the end of the list until we find a
  285. # task with a lower priority, or we hit the start of the list
  286. index = len(self.taskList) - 1
  287. while (1):
  288. if (index < 0):
  289. break
  290. if (self.taskList[index].getPriority() <= task.getPriority()):
  291. break
  292. index = index - 1
  293. index = index + 1
  294. self.taskList.insert(index, task)
  295. if __debug__:
  296. if self.pStatsTasks and name != "igloop":
  297. # Get the PStats name for the task. By convention,
  298. # this is everything until the first hyphen; the part
  299. # of the task name following the hyphen is generally
  300. # used to differentiate particular tasks that do the
  301. # same thing to different objects.
  302. hyphen = name.find('-')
  303. if hyphen >= 0:
  304. name = name[0:hyphen]
  305. task.setupPStats(name)
  306. if self.fVerbose:
  307. # Alert the world, a new task is born!
  308. messenger.send('TaskManager-spawnTask',
  309. sentArgs = [task, name, index])
  310. return task
  311. def doMethodLater(self, delayTime, func, taskName):
  312. task = Task(func)
  313. seq = doLater(delayTime, task, taskName)
  314. seq.laterTask = task
  315. return self.spawnTaskNamed(seq, 'doLater-' + taskName)
  316. def removeAllTasks(self):
  317. # Make a shallow copy so we do not modify the list in place
  318. taskListCopy = self.taskList[:]
  319. for task in taskListCopy:
  320. self.removeTask(task)
  321. def removeTask(self, task):
  322. # if TaskManager.notify.getDebug():
  323. # TaskManager.notify.debug('removing task: ' + `task`)
  324. if task in self.taskList:
  325. self.taskList.remove(task)
  326. if task.uponDeath:
  327. task.uponDeath(task)
  328. if self.fVerbose:
  329. # We regret to announce...
  330. messenger.send('TaskManager-removeTask',
  331. sentArgs = [task, task.name])
  332. def removeTasksNamed(self, taskName):
  333. if TaskManager.notify.getDebug():
  334. TaskManager.notify.debug('removing tasks named: ' + taskName)
  335. # Find the tasks that match by name and make a list of them
  336. removedTasks = []
  337. for task in self.taskList:
  338. if (task.name == taskName):
  339. removedTasks.append(task)
  340. # Now iterate through the tasks we need to remove and remove them
  341. for task in removedTasks:
  342. self.removeTask(task)
  343. # Return the number of tasks removed
  344. return len(removedTasks)
  345. def hasTaskNamed(self, taskName):
  346. for task in self.taskList:
  347. if (task.name == taskName):
  348. return 1
  349. return 0
  350. def removeTasksMatching(self, taskPattern):
  351. """removeTasksMatching(self, string taskPattern)
  352. Removes tasks whose names match the pattern, which can include
  353. standard shell globbing characters like *, ?, and [].
  354. """
  355. if TaskManager.notify.getDebug():
  356. TaskManager.notify.debug('removing tasks matching: ' + taskPattern)
  357. removedTasks = []
  358. # Find the tasks that match by name and make a list of them
  359. for task in self.taskList:
  360. if (fnmatch.fnmatchcase(task.name, taskPattern)):
  361. removedTasks.append(task)
  362. # Now iterate through the tasks we need to remove and remove them
  363. for task in removedTasks:
  364. self.removeTask(task)
  365. # Return the number of tasks removed
  366. return len(removedTasks)
  367. def step(self):
  368. if TaskManager.notify.getDebug():
  369. TaskManager.notify.debug('step')
  370. self.currentTime, self.currentFrame = getTimeFrame()
  371. for task in self.taskList:
  372. task.setCurrentTimeFrame(self.currentTime, self.currentFrame)
  373. if not self.taskTimerVerbose:
  374. # don't record timing info
  375. ret = task(task)
  376. else:
  377. # Run the task and check the return value
  378. if task.pstats:
  379. task.pstats.start()
  380. startTime = time.clock()
  381. ret = task(task)
  382. endTime = time.clock()
  383. if task.pstats:
  384. task.pstats.stop()
  385. # Record the dt
  386. dt = endTime - startTime
  387. task.dt = dt
  388. # See if this is the new max
  389. if dt > task.maxDt:
  390. task.maxDt = dt
  391. # Record the running total of all dts so we can compute an average
  392. task.runningTotal = task.runningTotal + dt
  393. if (task.frame > 0):
  394. task.avgDt = (task.runningTotal / task.frame)
  395. else:
  396. task.avgDt = 0
  397. # See if the task is done
  398. if (ret == cont):
  399. continue
  400. elif (ret == done):
  401. self.removeTask(task)
  402. elif (ret == exit):
  403. self.removeTask(task)
  404. else:
  405. raise "Task named %s did not return cont, exit, or done" % task.name
  406. return len(self.taskList)
  407. def run(self):
  408. # Set the clock to have last frame's time in case we were
  409. # Paused at the prompt for a long time
  410. t = globalClock.getFrameTime()
  411. globalClock.setRealTime(t)
  412. if self.resumeFunc != None:
  413. self.resumeFunc()
  414. if self.stepping:
  415. self.step()
  416. else:
  417. self.running = 1
  418. while self.running:
  419. try:
  420. self.step()
  421. except KeyboardInterrupt:
  422. self.stop()
  423. except:
  424. # self.stop()
  425. # print_exc_plus()
  426. raise
  427. def stop(self):
  428. # Set a flag so we will stop before beginning next frame
  429. self.running = 0
  430. def replaceMethod(self, oldMethod, newFunction):
  431. import new
  432. for task in self.taskList:
  433. method = task.__call__
  434. if (type(method) == types.MethodType):
  435. function = method.im_func
  436. else:
  437. function = method
  438. #print ('function: ' + `function` + '\n' +
  439. # 'method: ' + `method` + '\n' +
  440. # 'oldMethod: ' + `oldMethod` + '\n' +
  441. # 'newFunction: ' + `newFunction` + '\n')
  442. if (function == oldMethod):
  443. newMethod = new.instancemethod(newFunction,
  444. method.im_self,
  445. method.im_class)
  446. task.__call__ = newMethod
  447. # Found it return true
  448. return 1
  449. return 0
  450. def __repr__(self):
  451. import fpformat
  452. taskNameWidth = 32
  453. dtWidth = 7
  454. priorityWidth = 10
  455. totalDt = 0
  456. totalAvgDt = 0
  457. str = ('taskList'.ljust(taskNameWidth)
  458. + 'dt(ms)'.rjust(dtWidth)
  459. + 'avg'.rjust(dtWidth)
  460. + 'max'.rjust(dtWidth)
  461. + 'priority'.rjust(priorityWidth)
  462. + '\n')
  463. str = str + '---------------------------------------------------------------\n'
  464. for task in self.taskList:
  465. totalDt = totalDt + task.dt
  466. totalAvgDt = totalAvgDt + task.avgDt
  467. if (self.taskTimerVerbose):
  468. str = str + (task.name.ljust(taskNameWidth)
  469. + fpformat.fix(task.dt*1000, 2).rjust(dtWidth)
  470. + fpformat.fix(task.avgDt*1000, 2).rjust(dtWidth)
  471. + fpformat.fix(task.maxDt*1000, 2).rjust(dtWidth)
  472. + `task.getPriority()`.rjust(priorityWidth)
  473. + '\n')
  474. else:
  475. str = str + (task.name.ljust(taskNameWidth)
  476. + '----'.rjust(dtWidth)
  477. + '----'.rjust(dtWidth)
  478. + '----'.rjust(dtWidth)
  479. + `task.getPriority()`.rjust(priorityWidth)
  480. + '\n')
  481. str = str + '---------------------------------------------------------------\n'
  482. if (self.taskTimerVerbose):
  483. str = str + ('total'.ljust(taskNameWidth)
  484. + fpformat.fix(totalDt*1000, 2).rjust(dtWidth)
  485. + fpformat.fix(totalAvgDt*1000, 2).rjust(dtWidth)
  486. + '\n')
  487. else:
  488. str = str + ('total'.ljust(taskNameWidth)
  489. + '----'.rjust(dtWidth)
  490. + '----'.rjust(dtWidth)
  491. + '\n')
  492. return str
  493. def resetStats(self):
  494. for task in self.taskList:
  495. task.dt = 0
  496. task.avgDt = 0
  497. task.maxDt = 0
  498. task.runningTotal = 0
  499. task.setStartTimeFrame(self.currentTime, self.currentFrame)
  500. def popupControls(self):
  501. from TaskManagerPanel import *
  502. return TaskManagerPanel(self)
  503. """
  504. import Task
  505. from ShowBaseGlobal import * # to get taskMgr, and run()
  506. # sequence
  507. def seq1(state):
  508. print 'seq1'
  509. return Task.done
  510. def seq2(state):
  511. print 'seq2'
  512. return Task.done
  513. t = Task.sequence(Task.pause(1.0), Task.Task(seq1), Task.release(),
  514. Task.pause(3.0), Task.Task(seq2))
  515. taskMgr.spawnTaskNamed(t, 'sequence')
  516. run()
  517. # If you want it to loop, make a loop
  518. t = Task.loop(Task.pause(1.0), Task.Task(seq1), Task.release(),
  519. Task.pause(3.0), Task.Task(seq2))
  520. taskMgr.spawnTaskNamed(t, 'sequence')
  521. run()
  522. # timeline
  523. def keyframe1(state):
  524. print 'keyframe1'
  525. return Task.done
  526. def keyframe2(state):
  527. print 'keyframe2'
  528. return Task.done
  529. def keyframe3(state):
  530. print 'keyframe3'
  531. return Task.done
  532. testtl = Task.timeline(
  533. (0.5, Task.Task(keyframe1), 'key1'),
  534. (0.6, Task.Task(keyframe2), 'key2'),
  535. (0.7, Task.Task(keyframe3), 'key3')
  536. )
  537. t = taskMgr.spawnTaskNamed(testtl, 'timeline')
  538. run()
  539. # do later - returns a sequence
  540. def foo(state):
  541. print 'later...'
  542. return Task.done
  543. seq = Task.doLater(3.0, Task.Task(foo), 'fooLater')
  544. t = taskMgr.spawnTaskNamed(seq, 'doLater-fooLater')
  545. run()
  546. # tasks with state
  547. # Combined with uponDeath
  548. someValue = 1
  549. def func(state):
  550. if (state.someValue > 10):
  551. print 'true!'
  552. return Task.done
  553. else:
  554. state.someValue = state.someValue + 1
  555. return Task.cont
  556. def deathFunc(state):
  557. print 'Value at death: ', state.someValue
  558. task = Task.Task(func)
  559. # set task state here
  560. task.someValue = someValue
  561. # Use instance variable uponDeath to specify function
  562. # to perform on task removal
  563. # Default value of uponDeath is None
  564. task.uponDeath = deathFunc
  565. t = taskMgr.spawnTaskNamed(task, 'funcTask')
  566. run()
  567. """