Task.py 18 KB

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