Task.py 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892
  1. """ This module defines a Python-level wrapper around the C++
  2. :class:`~panda3d.core.AsyncTaskManager` interface. It replaces the old
  3. full-Python implementation of the Task system.
  4. For more information about the task system, consult the
  5. :ref:`tasks-and-event-handling` page in the programming manual.
  6. """
  7. from __future__ import annotations
  8. __all__ = ['Task', 'TaskManager',
  9. 'cont', 'done', 'again', 'pickup', 'exit',
  10. 'sequence', 'loop', 'pause']
  11. from direct.directnotify.DirectNotifyGlobal import directNotify
  12. from direct.showbase.PythonUtil import Functor, ScratchPad
  13. from direct.showbase.MessengerGlobal import messenger
  14. from typing import Any, Callable, Coroutine, Final, Generator, Sequence, TypeVar, Union
  15. import types
  16. import random
  17. import importlib
  18. import sys
  19. # On Android, there's no use handling SIGINT, and in fact we can't, since we
  20. # run the application in a separate thread from the main thread.
  21. signal: types.ModuleType | None
  22. if hasattr(sys, 'getandroidapilevel'):
  23. signal = None
  24. else:
  25. try:
  26. import _signal as signal # type: ignore[import-not-found, no-redef]
  27. except ImportError:
  28. signal = None
  29. from panda3d.core import (
  30. AsyncTask,
  31. AsyncTaskPause,
  32. AsyncTaskManager,
  33. AsyncTaskSequence,
  34. ClockObject,
  35. ConfigVariableBool,
  36. GlobPattern,
  37. PythonTask,
  38. Thread,
  39. )
  40. from direct.extensions_native import HTTPChannel_extensions # pylint: disable=unused-import
  41. # The following variables are typing constructs used in annotations
  42. # to succinctly express all the types that can be converted into tasks.
  43. _T = TypeVar('_T', covariant=True)
  44. _TaskCoroutine = Union[Coroutine[Any, None, _T], Generator[Any, None, _T]]
  45. _TaskFunction = Callable[..., Union[int, _TaskCoroutine[Union[int, None]], None]]
  46. _FuncOrTask = Union[_TaskFunction, _TaskCoroutine[Any], AsyncTask]
  47. def print_exc_plus() -> None:
  48. """
  49. Print the usual traceback information, followed by a listing of all the
  50. local variables in each frame.
  51. """
  52. import traceback
  53. tb = sys.exc_info()[2]
  54. assert tb is not None
  55. while 1:
  56. if not tb.tb_next:
  57. break
  58. tb = tb.tb_next
  59. stack = []
  60. f: types.FrameType | None = tb.tb_frame
  61. while f:
  62. stack.append(f)
  63. f = f.f_back
  64. stack.reverse()
  65. traceback.print_exc()
  66. print("Locals by frame, innermost last")
  67. for frame in stack:
  68. print("")
  69. print("Frame %s in %s at line %s" % (frame.f_code.co_name,
  70. frame.f_code.co_filename,
  71. frame.f_lineno))
  72. for key, value in list(frame.f_locals.items()):
  73. #We have to be careful not to cause a new error in our error
  74. #printer! Calling str() on an unknown object could cause an
  75. #error we don't want.
  76. try:
  77. valueStr = str(value)
  78. except Exception:
  79. valueStr = "<ERROR WHILE PRINTING VALUE>"
  80. print("\t%20s = %s" % (key, valueStr))
  81. # For historical purposes, we remap the C++-defined enumeration to
  82. # these Python names, and define them both at the module level, here,
  83. # and at the class level (below). The preferred access is via the
  84. # class level.
  85. done: Final = AsyncTask.DSDone
  86. cont: Final = AsyncTask.DSCont
  87. again: Final = AsyncTask.DSAgain
  88. pickup: Final = AsyncTask.DSPickup
  89. exit: Final = AsyncTask.DSExit
  90. #: Task aliases to :class:`panda3d.core.PythonTask` for historical purposes.
  91. Task = PythonTask
  92. # Copy the module-level enums above into the class level. This funny
  93. # syntax is necessary because it's a C++-wrapped extension type, not a
  94. # true Python class.
  95. # We can't override 'done', which is already a known method. We have a
  96. # special check in PythonTask for when the method is being returned.
  97. #Task.DtoolClassDict['done'] = done
  98. Task.DtoolClassDict['cont'] = cont
  99. Task.DtoolClassDict['again'] = again
  100. Task.DtoolClassDict['pickup'] = pickup
  101. Task.DtoolClassDict['exit'] = exit
  102. # Alias the AsyncTaskPause constructor as Task.pause().
  103. pause = AsyncTaskPause
  104. Task.DtoolClassDict['pause'] = staticmethod(pause)
  105. gather = Task.gather
  106. shield = Task.shield
  107. def sequence(*taskList: AsyncTask) -> AsyncTaskSequence:
  108. seq = AsyncTaskSequence('sequence')
  109. for task in taskList:
  110. seq.addTask(task)
  111. return seq
  112. Task.DtoolClassDict['sequence'] = staticmethod(sequence)
  113. def loop(*taskList: AsyncTask) -> AsyncTaskSequence:
  114. seq = AsyncTaskSequence('loop')
  115. for task in taskList:
  116. seq.addTask(task)
  117. seq.setRepeatCount(-1)
  118. return seq
  119. Task.DtoolClassDict['loop'] = staticmethod(loop)
  120. class TaskManager:
  121. notify = directNotify.newCategory("TaskManager")
  122. taskTimerVerbose = ConfigVariableBool('task-timer-verbose', False)
  123. extendedExceptions = ConfigVariableBool('extended-exceptions', False)
  124. pStatsTasks = ConfigVariableBool('pstats-tasks', False)
  125. MaxEpochSpeed = 1.0/30.0
  126. __prevHandler: Any
  127. def __init__(self) -> None:
  128. self.mgr = AsyncTaskManager.getGlobalPtr()
  129. self.resumeFunc: Callable[[], object] | None = None
  130. self.globalClock = self.mgr.getClock()
  131. self.stepping = False
  132. self.running = False
  133. self.destroyed = False
  134. self.fKeyboardInterrupt = False
  135. self.interruptCount = 0
  136. if signal:
  137. self.__prevHandler = signal.default_int_handler
  138. self._frameProfileQueue: list[tuple[int, Any, Callable[[], object] | None]] = []
  139. # this will be set when it's safe to import StateVar
  140. self._profileFrames: Any = None
  141. self._frameProfiler = None
  142. self._profileTasks: Any = None
  143. self._taskProfiler = None
  144. self._taskProfileInfo = ScratchPad(
  145. taskId = None,
  146. profiled = False,
  147. session = None,
  148. )
  149. def finalInit(self) -> None:
  150. # This function should be called once during startup, after
  151. # most things are imported.
  152. from direct.fsm.StatePush import StateVar
  153. self._profileTasks = StateVar(False)
  154. self.setProfileTasks(ConfigVariableBool('profile-task-spikes', 0).getValue())
  155. self._profileFrames = StateVar(False)
  156. self.setProfileFrames(ConfigVariableBool('profile-frames', 0).getValue())
  157. def destroy(self) -> None:
  158. # This should be safe to call multiple times.
  159. self.running = False
  160. self.notify.info("TaskManager.destroy()")
  161. self.destroyed = True
  162. self._frameProfileQueue.clear()
  163. self.mgr.cleanup()
  164. def __getClock(self) -> ClockObject:
  165. return self.mgr.getClock()
  166. def setClock(self, clockObject: ClockObject) -> None:
  167. self.mgr.setClock(clockObject)
  168. self.globalClock = clockObject
  169. clock = property(__getClock, setClock)
  170. def invokeDefaultHandler(self, signalNumber, stackFrame):
  171. print('*** allowing mid-frame keyboard interrupt.')
  172. # Restore default interrupt handler
  173. if signal:
  174. signal.signal(signal.SIGINT, self.__prevHandler)
  175. # and invoke it
  176. raise KeyboardInterrupt
  177. def keyboardInterruptHandler(self, signalNumber, stackFrame):
  178. self.fKeyboardInterrupt = 1
  179. self.interruptCount += 1
  180. if self.interruptCount == 1:
  181. print('* interrupt by keyboard')
  182. elif self.interruptCount == 2:
  183. print('** waiting for end of frame before interrupting...')
  184. # The user must really want to interrupt this process
  185. # Next time around invoke the default handler
  186. signal.signal(signal.SIGINT, self.invokeDefaultHandler)
  187. def getCurrentTask(self) -> AsyncTask | None:
  188. """ Returns the task currently executing on this thread, or
  189. None if this is being called outside of the task manager. """
  190. return Thread.getCurrentThread().getCurrentTask()
  191. def hasTaskChain(self, chainName: str) -> bool:
  192. """ Returns true if a task chain with the indicated name has
  193. already been defined, or false otherwise. Note that
  194. setupTaskChain() will implicitly define a task chain if it has
  195. not already been defined, or modify an existing one if it has,
  196. so in most cases there is no need to check this method
  197. first. """
  198. return self.mgr.findTaskChain(chainName) is not None
  199. def setupTaskChain(
  200. self,
  201. chainName: str,
  202. numThreads: int | None = None,
  203. tickClock: bool | None = None,
  204. threadPriority: int | None = None,
  205. frameBudget: float | None = None,
  206. frameSync: bool | None = None,
  207. timeslicePriority: bool | None = None,
  208. ) -> None:
  209. """Defines a new task chain. Each task chain executes tasks
  210. potentially in parallel with all of the other task chains (if
  211. numThreads is more than zero). When a new task is created, it
  212. may be associated with any of the task chains, by name (or you
  213. can move a task to another task chain with
  214. task.setTaskChain()). You can have any number of task chains,
  215. but each must have a unique name.
  216. numThreads is the number of threads to allocate for this task
  217. chain. If it is 1 or more, then the tasks on this task chain
  218. will execute in parallel with the tasks on other task chains.
  219. If it is greater than 1, then the tasks on this task chain may
  220. execute in parallel with themselves (within tasks of the same
  221. sort value).
  222. If tickClock is True, then this task chain will be responsible
  223. for ticking the global clock each frame (and thereby
  224. incrementing the frame counter). There should be just one
  225. task chain responsible for ticking the clock, and usually it
  226. is the default, unnamed task chain.
  227. threadPriority specifies the priority level to assign to
  228. threads on this task chain. It may be one of TPLow, TPNormal,
  229. TPHigh, or TPUrgent. This is passed to the underlying
  230. threading system to control the way the threads are scheduled.
  231. frameBudget is the maximum amount of time (in seconds) to
  232. allow this task chain to run per frame. Set it to -1 to mean
  233. no limit (the default). It's not directly related to
  234. threadPriority.
  235. frameSync is true to force the task chain to sync to the
  236. clock. When this flag is false, the default, the task chain
  237. will finish all of its tasks and then immediately start from
  238. the first task again, regardless of the clock frame. When it
  239. is true, the task chain will finish all of its tasks and then
  240. wait for the clock to tick to the next frame before resuming
  241. the first task. This only makes sense for threaded tasks
  242. chains; non-threaded task chains are automatically
  243. synchronous.
  244. timeslicePriority is False in the default mode, in which each
  245. task runs exactly once each frame, round-robin style,
  246. regardless of the task's priority value; or True to change the
  247. meaning of priority so that certain tasks are run less often,
  248. in proportion to their time used and to their priority value.
  249. See AsyncTaskManager.setTimeslicePriority() for more.
  250. """
  251. chain = self.mgr.makeTaskChain(chainName)
  252. if numThreads is not None:
  253. chain.setNumThreads(numThreads)
  254. if tickClock is not None:
  255. chain.setTickClock(tickClock)
  256. if threadPriority is not None:
  257. chain.setThreadPriority(threadPriority)
  258. if frameBudget is not None:
  259. chain.setFrameBudget(frameBudget)
  260. if frameSync is not None:
  261. chain.setFrameSync(frameSync)
  262. if timeslicePriority is not None:
  263. chain.setTimeslicePriority(timeslicePriority)
  264. def hasTaskNamed(self, taskName: str) -> bool:
  265. """Returns true if there is at least one task, active or
  266. sleeping, with the indicated name. """
  267. return bool(self.mgr.findTask(taskName))
  268. def getTasksNamed(self, taskName: str) -> list[AsyncTask]:
  269. """Returns a list of all tasks, active or sleeping, with the
  270. indicated name. """
  271. return list(self.mgr.findTasks(taskName))
  272. def getTasksMatching(self, taskPattern: GlobPattern | str) -> list[AsyncTask]:
  273. """Returns a list of all tasks, active or sleeping, with a
  274. name that matches the pattern, which can include standard
  275. shell globbing characters like \\*, ?, and []. """
  276. return list(self.mgr.findTasksMatching(GlobPattern(taskPattern)))
  277. def getAllTasks(self) -> list[AsyncTask]:
  278. """Returns list of all tasks, active and sleeping, in
  279. arbitrary order. """
  280. return list(self.mgr.getTasks())
  281. def getTasks(self) -> list[AsyncTask]:
  282. """Returns list of all active tasks in arbitrary order. """
  283. return list(self.mgr.getActiveTasks())
  284. def getDoLaters(self) -> list[AsyncTask]:
  285. """Returns list of all sleeping tasks in arbitrary order. """
  286. return list(self.mgr.getSleepingTasks())
  287. def doMethodLater(
  288. self,
  289. delayTime: float,
  290. funcOrTask: _FuncOrTask,
  291. name: str | None,
  292. extraArgs: Sequence | None = None,
  293. sort: int | None = None,
  294. priority: int | None = None,
  295. taskChain: str | None = None,
  296. uponDeath: Callable[[], object] | None = None,
  297. appendTask: bool = False,
  298. owner = None,
  299. ) -> AsyncTask:
  300. """Adds a task to be performed at some time in the future.
  301. This is identical to `add()`, except that the specified
  302. delayTime is applied to the Task object first, which means
  303. that the task will not begin executing until at least the
  304. indicated delayTime (in seconds) has elapsed.
  305. After delayTime has elapsed, the task will become active, and
  306. will run in the soonest possible frame thereafter. If you
  307. wish to specify a task that will run in the next frame, use a
  308. delayTime of 0.
  309. """
  310. if delayTime < 0:
  311. assert self.notify.warning('doMethodLater: added task: %s with negative delay: %s' % (name, delayTime))
  312. task = self.__setupTask(funcOrTask, name, priority, sort, extraArgs, taskChain, appendTask, owner, uponDeath)
  313. task.setDelay(delayTime)
  314. self.mgr.add(task)
  315. return task
  316. do_method_later = doMethodLater
  317. def add(
  318. self,
  319. funcOrTask: _FuncOrTask,
  320. name: str | None = None,
  321. sort: int | None = None,
  322. extraArgs: Sequence | None = None,
  323. priority: int | None = None,
  324. uponDeath: Callable[[], object] | None = None,
  325. appendTask: bool = False,
  326. taskChain: str | None = None,
  327. owner = None,
  328. delay: float | None = None,
  329. ) -> AsyncTask:
  330. """
  331. Add a new task to the taskMgr. The task will begin executing
  332. immediately, or next frame if its sort value has already
  333. passed this frame.
  334. Parameters:
  335. funcOrTask: either an existing Task object (not already
  336. added to the task manager), or a callable function
  337. object. If this is a function, a new Task object will be
  338. created and returned. You may also pass in a coroutine
  339. object.
  340. name (str): the name to assign to the Task. Required,
  341. unless you are passing in a Task object that already has
  342. a name.
  343. extraArgs (list): the list of arguments to pass to the task
  344. function. If this is omitted, the list is just the task
  345. object itself.
  346. appendTask (bool): If this is true, then the task object
  347. itself will be appended to the end of the extraArgs list
  348. before calling the function.
  349. sort (int): the sort value to assign the task. The default
  350. sort is 0. Within a particular task chain, it is
  351. guaranteed that the tasks with a lower sort value will
  352. all run before tasks with a higher sort value run.
  353. priority (int): the priority at which to run the task. The
  354. default priority is 0. Higher priority tasks are run
  355. sooner, and/or more often. For historical purposes, if
  356. you specify a priority without also specifying a sort,
  357. the priority value is understood to actually be a sort
  358. value. (Previously, there was no priority value, only a
  359. sort value, and it was called "priority".)
  360. uponDeath (bool): a function to call when the task
  361. terminates, either because it has run to completion, or
  362. because it has been explicitly removed.
  363. taskChain (str): the name of the task chain to assign the
  364. task to.
  365. owner: an optional Python object that is declared as the
  366. "owner" of this task for maintenance purposes. The
  367. owner must have two methods:
  368. ``owner._addTask(self, task)``, which is called when the
  369. task begins, and ``owner._clearTask(self, task)``, which
  370. is called when the task terminates. This is all the
  371. ownermeans.
  372. delay: an optional amount of seconds to wait before starting
  373. the task (equivalent to doMethodLater).
  374. Returns:
  375. The new Task object that has been added, or the original
  376. Task object that was passed in.
  377. """
  378. task = self.__setupTask(funcOrTask, name, priority, sort, extraArgs, taskChain, appendTask, owner, uponDeath)
  379. if delay is not None:
  380. task.setDelay(delay)
  381. self.mgr.add(task)
  382. return task
  383. def __setupTask(
  384. self,
  385. funcOrTask: _FuncOrTask,
  386. name: str | None,
  387. priority: int | None,
  388. sort: int | None,
  389. extraArgs: Sequence | None,
  390. taskChain: str | None,
  391. appendTask: bool,
  392. owner,
  393. uponDeath: Callable[[], object] | None,
  394. ) -> AsyncTask:
  395. wasTask = False
  396. if isinstance(funcOrTask, AsyncTask):
  397. task = funcOrTask
  398. wasTask = True
  399. elif hasattr(funcOrTask, '__call__') or \
  400. hasattr(funcOrTask, 'cr_await') or \
  401. isinstance(funcOrTask, types.GeneratorType):
  402. # It's a function, coroutine, or something emulating a coroutine.
  403. task = PythonTask(funcOrTask)
  404. if name is None:
  405. name = getattr(funcOrTask, '__qualname__', None) or \
  406. getattr(funcOrTask, '__name__', None)
  407. else:
  408. self.notify.error(
  409. 'add: Tried to add a task that was not a Task or a func')
  410. if hasattr(task, 'setArgs'):
  411. # It will only accept arguments if it's a PythonTask.
  412. if extraArgs is None:
  413. if wasTask:
  414. extraArgs = task.getArgs()
  415. #do not append the task to an existing task. It was already there
  416. #from the last time it was addeed
  417. appendTask = False
  418. else:
  419. extraArgs = []
  420. appendTask = True
  421. task.setArgs(extraArgs, appendTask)
  422. elif extraArgs is not None:
  423. self.notify.error(
  424. 'Task %s does not accept arguments.' % (repr(task)))
  425. if name is not None:
  426. task.setName(name)
  427. assert task.hasName()
  428. # For historical reasons, if priority is specified but not
  429. # sort, it really means sort.
  430. if priority is not None and sort is None:
  431. task.setSort(priority)
  432. else:
  433. if priority is not None:
  434. task.setPriority(priority)
  435. if sort is not None:
  436. task.setSort(sort)
  437. if taskChain is not None:
  438. task.setTaskChain(taskChain)
  439. if owner is not None:
  440. task.setOwner(owner)
  441. if uponDeath is not None:
  442. task.setUponDeath(uponDeath)
  443. return task
  444. def remove(self, taskOrName: AsyncTask | str | list[AsyncTask | str]) -> int:
  445. """Removes a task from the task manager. The task is stopped,
  446. almost as if it had returned task.done. (But if the task is
  447. currently executing, it will finish out its current frame
  448. before being removed.) You may specify either an explicit
  449. Task object, or the name of a task. If you specify a name,
  450. all tasks with the indicated name are removed. Returns the
  451. number of tasks removed. """
  452. if isinstance(taskOrName, AsyncTask):
  453. return self.mgr.remove(taskOrName)
  454. elif isinstance(taskOrName, list):
  455. count = 0
  456. for task in taskOrName:
  457. count += self.remove(task)
  458. return count
  459. else:
  460. tasks = self.mgr.findTasks(taskOrName)
  461. return self.mgr.remove(tasks)
  462. def removeTasksMatching(self, taskPattern: GlobPattern | str) -> int:
  463. """Removes all tasks whose names match the pattern, which can
  464. include standard shell globbing characters like \\*, ?, and [].
  465. See also :meth:`remove()`.
  466. Returns the number of tasks removed.
  467. """
  468. tasks = self.mgr.findTasksMatching(GlobPattern(taskPattern))
  469. return self.mgr.remove(tasks)
  470. def step(self) -> None:
  471. """Invokes the task manager for one frame, and then returns.
  472. Normally, this executes each task exactly once, though task
  473. chains that are in sub-threads or that have frame budgets
  474. might execute their tasks differently. """
  475. startFrameTime = self.globalClock.getRealTime()
  476. # Replace keyboard interrupt handler during task list processing
  477. # so we catch the keyboard interrupt but don't handle it until
  478. # after task list processing is complete.
  479. self.fKeyboardInterrupt = False
  480. self.interruptCount = 0
  481. if signal:
  482. self.__prevHandler = signal.signal(signal.SIGINT, self.keyboardInterruptHandler)
  483. try:
  484. self.mgr.poll()
  485. # This is the spot for an internal yield function
  486. nextTaskTime = self.mgr.getNextWakeTime()
  487. self.doYield(startFrameTime, nextTaskTime)
  488. finally:
  489. # Restore previous interrupt handler
  490. if signal:
  491. signal.signal(signal.SIGINT, self.__prevHandler)
  492. self.__prevHandler = signal.default_int_handler
  493. if self.fKeyboardInterrupt:
  494. raise KeyboardInterrupt
  495. def run(self) -> None:
  496. """Starts the task manager running. Does not return until an
  497. exception is encountered (including KeyboardInterrupt). """
  498. if sys.platform == 'emscripten':
  499. return
  500. # Set the clock to have last frame's time in case we were
  501. # Paused at the prompt for a long time
  502. t = self.globalClock.getFrameTime()
  503. timeDelta = t - self.globalClock.getRealTime()
  504. self.globalClock.setRealTime(t)
  505. messenger.send("resetClock", [timeDelta])
  506. if self.resumeFunc is not None:
  507. self.resumeFunc()
  508. if self.stepping:
  509. self.step()
  510. else:
  511. self.running = True
  512. while self.running:
  513. try:
  514. if len(self._frameProfileQueue) > 0:
  515. numFrames, session, callback = self._frameProfileQueue.pop(0)
  516. def _profileFunc(numFrames: int = numFrames) -> None:
  517. self._doProfiledFrames(numFrames)
  518. session.setFunc(_profileFunc)
  519. session.run()
  520. del _profileFunc
  521. if callback:
  522. callback()
  523. session.release()
  524. else:
  525. self.step()
  526. except KeyboardInterrupt:
  527. self.stop()
  528. except SystemExit:
  529. self.stop()
  530. raise
  531. except IOError as ioError:
  532. code, message = self._unpackIOError(ioError)
  533. # Since upgrading to Python 2.4.1, pausing the execution
  534. # often gives this IOError during the sleep function:
  535. # IOError: [Errno 4] Interrupted function call
  536. # So, let's just handle that specific exception and stop.
  537. # All other IOErrors should still get raised.
  538. # Only problem: legit IOError 4s will be obfuscated.
  539. if code == 4:
  540. self.stop()
  541. else:
  542. raise
  543. except Exception as e:
  544. if self.extendedExceptions:
  545. self.stop()
  546. print_exc_plus()
  547. else:
  548. from direct.showbase import ExceptionVarDump
  549. if ExceptionVarDump.wantStackDumpLog and \
  550. ExceptionVarDump.dumpOnExceptionInit:
  551. ExceptionVarDump._varDump__print(e)
  552. raise
  553. except:
  554. if self.extendedExceptions:
  555. self.stop()
  556. print_exc_plus()
  557. else:
  558. raise
  559. self.mgr.stopThreads()
  560. def _unpackIOError(self, ioError):
  561. # IOError unpack from http://www.python.org/doc/essays/stdexceptions/
  562. # this needs to be in its own method, exceptions that occur inside
  563. # a nested try block are not caught by the inner try block's except
  564. try:
  565. (code, message) = ioError
  566. except Exception:
  567. code = 0
  568. message = ioError
  569. return code, message
  570. def stop(self) -> None:
  571. # Set a flag so we will stop before beginning next frame
  572. self.running = False
  573. def __tryReplaceTaskMethod(self, task, oldMethod, newFunction):
  574. if not isinstance(task, PythonTask):
  575. return 0
  576. method = task.getFunction()
  577. if isinstance(method, types.MethodType):
  578. function = method.__func__
  579. else:
  580. function = method
  581. if function == oldMethod:
  582. newMethod = types.MethodType(newFunction, method.__self__)
  583. task.setFunction(newMethod)
  584. # Found a match
  585. return 1
  586. return 0
  587. def replaceMethod(self, oldMethod, newFunction):
  588. numFound = 0
  589. for task in self.getAllTasks():
  590. numFound += self.__tryReplaceTaskMethod(task, oldMethod, newFunction)
  591. return numFound
  592. def popupControls(self):
  593. # Don't use a regular import, to prevent ModuleFinder from picking
  594. # it up as a dependency when building a .p3d package.
  595. TaskManagerPanel = importlib.import_module('direct.tkpanels.TaskManagerPanel')
  596. return TaskManagerPanel.TaskManagerPanel(self)
  597. def getProfileSession(self, name=None):
  598. # call to get a profile session that you can modify before passing to profileFrames()
  599. if name is None:
  600. name = 'taskMgrFrameProfile'
  601. # Defer this import until we need it: some Python
  602. # distributions don't provide the profile and pstats modules.
  603. PS = importlib.import_module('direct.showbase.ProfileSession')
  604. return PS.ProfileSession(name)
  605. def profileFrames(self, num=None, session=None, callback=None):
  606. if num is None:
  607. num = 1
  608. if session is None:
  609. session = self.getProfileSession()
  610. # make sure the profile session doesn't get destroyed before we're done with it
  611. session.acquire()
  612. self._frameProfileQueue.append((num, session, callback))
  613. def _doProfiledFrames(self, numFrames):
  614. for i in range(numFrames):
  615. self.step()
  616. def getProfileFrames(self):
  617. return self._profileFrames.get()
  618. def getProfileFramesSV(self):
  619. return self._profileFrames
  620. def setProfileFrames(self, profileFrames):
  621. self._profileFrames.set(profileFrames)
  622. if (not self._frameProfiler) and profileFrames:
  623. # import here due to import dependencies
  624. FP = importlib.import_module('direct.task.FrameProfiler')
  625. self._frameProfiler = FP.FrameProfiler()
  626. def getProfileTasks(self):
  627. return self._profileTasks.get()
  628. def getProfileTasksSV(self):
  629. return self._profileTasks
  630. def setProfileTasks(self, profileTasks):
  631. self._profileTasks.set(profileTasks)
  632. if (not self._taskProfiler) and profileTasks:
  633. # import here due to import dependencies
  634. TP = importlib.import_module('direct.task.TaskProfiler')
  635. self._taskProfiler = TP.TaskProfiler()
  636. def logTaskProfiles(self, name=None):
  637. if self._taskProfiler:
  638. self._taskProfiler.logProfiles(name)
  639. def flushTaskProfiles(self, name=None):
  640. if self._taskProfiler:
  641. self._taskProfiler.flush(name)
  642. def _setProfileTask(self, task):
  643. if self._taskProfileInfo.session:
  644. self._taskProfileInfo.session.release()
  645. self._taskProfileInfo.session = None
  646. self._taskProfileInfo = ScratchPad(
  647. taskFunc = task.getFunction(),
  648. taskArgs = task.getArgs(),
  649. task = task,
  650. profiled = False,
  651. session = None,
  652. )
  653. # Temporarily replace the task's own function with our
  654. # _profileTask method.
  655. task.setFunction(self._profileTask)
  656. task.setArgs([self._taskProfileInfo], True)
  657. def _profileTask(self, profileInfo, task):
  658. # This is called instead of the task function when we have
  659. # decided to profile a task.
  660. assert profileInfo.task == task
  661. # don't profile the same task twice in a row
  662. assert not profileInfo.profiled
  663. # Restore the task's proper function for next time.
  664. appendTask = False
  665. taskArgs = profileInfo.taskArgs
  666. if taskArgs and taskArgs[-1] == task:
  667. appendTask = True
  668. taskArgs = taskArgs[:-1]
  669. task.setArgs(taskArgs, appendTask)
  670. task.setFunction(profileInfo.taskFunc)
  671. # Defer this import until we need it: some Python
  672. # distributions don't provide the profile and pstats modules.
  673. PS = importlib.import_module('direct.showbase.ProfileSession')
  674. profileSession = PS.ProfileSession('profiled-task-%s' % task.getName(),
  675. Functor(profileInfo.taskFunc, *profileInfo.taskArgs))
  676. ret = profileSession.run()
  677. # set these values *after* profiling in case we're profiling the TaskProfiler
  678. profileInfo.session = profileSession
  679. profileInfo.profiled = True
  680. return ret
  681. def _hasProfiledDesignatedTask(self):
  682. # have we run a profile of the designated task yet?
  683. return self._taskProfileInfo.profiled
  684. def _getLastTaskProfileSession(self):
  685. return self._taskProfileInfo.session
  686. def _getRandomTask(self):
  687. # Figure out when the next frame is likely to expire, so we
  688. # won't grab any tasks that are sleeping for a long time.
  689. now = self.globalClock.getFrameTime()
  690. avgFrameRate = self.globalClock.getAverageFrameRate()
  691. if avgFrameRate < .00001:
  692. avgFrameDur = 0.
  693. else:
  694. avgFrameDur = (1. / self.globalClock.getAverageFrameRate())
  695. next = now + avgFrameDur
  696. # Now grab a task at random, until we find one that we like.
  697. tasks = self.mgr.getTasks()
  698. i = random.randrange(tasks.getNumTasks())
  699. task = tasks.getTask(i)
  700. while not isinstance(task, PythonTask) or \
  701. task.getWakeTime() > next:
  702. tasks.removeTask(i)
  703. i = random.randrange(tasks.getNumTasks())
  704. task = tasks.getTask(i)
  705. return task
  706. def __repr__(self) -> str:
  707. return str(self.mgr)
  708. # In the event we want to do frame time managment, this is the
  709. # function to replace or overload.
  710. def doYield(self, frameStartTime: float, nextScheduledTaskTime: float) -> None:
  711. pass
  712. #def doYieldExample(self, frameStartTime, nextScheduledTaskTime):
  713. # minFinTime = frameStartTime + self.MaxEpochSpeed
  714. # if nextScheduledTaskTime > 0 and nextScheduledTaskTime < minFinTime:
  715. # print(' Adjusting Time')
  716. # minFinTime = nextScheduledTaskTime
  717. # delta = minFinTime - self.globalClock.getRealTime()
  718. # while delta > 0.002:
  719. # print ' sleep %s'% (delta)
  720. # time.sleep(delta)
  721. # delta = minFinTime - self.globalClock.getRealTime()
  722. if __debug__:
  723. def checkLeak():
  724. import gc
  725. gc.enable()
  726. from direct.showbase.DirectObject import DirectObject
  727. from direct.task.TaskManagerGlobal import taskMgr
  728. class TestClass(DirectObject):
  729. def doTask(self, task):
  730. return task.done
  731. obj = TestClass()
  732. startRefCount = sys.getrefcount(obj)
  733. print('sys.getrefcount(obj): %s' % sys.getrefcount(obj))
  734. print('** addTask')
  735. t = obj.addTask(obj.doTask, 'test')
  736. print('sys.getrefcount(obj): %s' % sys.getrefcount(obj))
  737. print('task.getRefCount(): %s' % t.getRefCount())
  738. print('** removeTask')
  739. obj.removeTask('test')
  740. print('sys.getrefcount(obj): %s' % sys.getrefcount(obj))
  741. print('task.getRefCount(): %s' % t.getRefCount())
  742. print('** step')
  743. taskMgr.step()
  744. taskMgr.step()
  745. taskMgr.step()
  746. print('sys.getrefcount(obj): %s' % sys.getrefcount(obj))
  747. print('task.getRefCount(): %s' % t.getRefCount())
  748. print('** task release')
  749. t = None
  750. print('sys.getrefcount(obj): %s' % sys.getrefcount(obj))
  751. assert sys.getrefcount(obj) == startRefCount