Task.py 15 KB

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