|
|
@@ -6,6 +6,8 @@ For more information about the task system, consult the
|
|
|
:ref:`tasks-and-event-handling` page in the programming manual.
|
|
|
"""
|
|
|
|
|
|
+from __future__ import annotations
|
|
|
+
|
|
|
__all__ = ['Task', 'TaskManager',
|
|
|
'cont', 'done', 'again', 'pickup', 'exit',
|
|
|
'sequence', 'loop', 'pause']
|
|
|
@@ -13,7 +15,7 @@ __all__ = ['Task', 'TaskManager',
|
|
|
from direct.directnotify.DirectNotifyGlobal import directNotify
|
|
|
from direct.showbase.PythonUtil import Functor, ScratchPad
|
|
|
from direct.showbase.MessengerGlobal import messenger
|
|
|
-from typing import Any, Optional
|
|
|
+from typing import Any, Callable, Coroutine, Final, Generator, Sequence, TypeVar, Union
|
|
|
import types
|
|
|
import random
|
|
|
import importlib
|
|
|
@@ -21,7 +23,7 @@ import sys
|
|
|
|
|
|
# On Android, there's no use handling SIGINT, and in fact we can't, since we
|
|
|
# run the application in a separate thread from the main thread.
|
|
|
-signal: Optional[types.ModuleType]
|
|
|
+signal: types.ModuleType | None
|
|
|
if hasattr(sys, 'getandroidapilevel'):
|
|
|
signal = None
|
|
|
else:
|
|
|
@@ -43,8 +45,15 @@ from panda3d.core import (
|
|
|
)
|
|
|
from direct.extensions_native import HTTPChannel_extensions # pylint: disable=unused-import
|
|
|
|
|
|
+# The following variables are typing constructs used in annotations
|
|
|
+# to succinctly express all the types that can be converted into tasks.
|
|
|
+_T = TypeVar('_T', covariant=True)
|
|
|
+_TaskCoroutine = Union[Coroutine[Any, None, _T], Generator[Any, None, _T]]
|
|
|
+_TaskFunction = Callable[..., Union[int, _TaskCoroutine[Union[int, None]], None]]
|
|
|
+_FuncOrTask = Union[_TaskFunction, _TaskCoroutine[Any], AsyncTask]
|
|
|
+
|
|
|
|
|
|
-def print_exc_plus():
|
|
|
+def print_exc_plus() -> None:
|
|
|
"""
|
|
|
Print the usual traceback information, followed by a listing of all the
|
|
|
local variables in each frame.
|
|
|
@@ -52,12 +61,13 @@ def print_exc_plus():
|
|
|
import traceback
|
|
|
|
|
|
tb = sys.exc_info()[2]
|
|
|
+ assert tb is not None
|
|
|
while 1:
|
|
|
if not tb.tb_next:
|
|
|
break
|
|
|
tb = tb.tb_next
|
|
|
stack = []
|
|
|
- f = tb.tb_frame
|
|
|
+ f: types.FrameType | None = tb.tb_frame
|
|
|
while f:
|
|
|
stack.append(f)
|
|
|
f = f.f_back
|
|
|
@@ -84,11 +94,11 @@ def print_exc_plus():
|
|
|
# these Python names, and define them both at the module level, here,
|
|
|
# and at the class level (below). The preferred access is via the
|
|
|
# class level.
|
|
|
-done = AsyncTask.DSDone
|
|
|
-cont = AsyncTask.DSCont
|
|
|
-again = AsyncTask.DSAgain
|
|
|
-pickup = AsyncTask.DSPickup
|
|
|
-exit = AsyncTask.DSExit
|
|
|
+done: Final = AsyncTask.DSDone
|
|
|
+cont: Final = AsyncTask.DSCont
|
|
|
+again: Final = AsyncTask.DSAgain
|
|
|
+pickup: Final = AsyncTask.DSPickup
|
|
|
+exit: Final = AsyncTask.DSExit
|
|
|
|
|
|
#: Task aliases to :class:`panda3d.core.PythonTask` for historical purposes.
|
|
|
Task = PythonTask
|
|
|
@@ -112,7 +122,7 @@ gather = Task.gather
|
|
|
shield = Task.shield
|
|
|
|
|
|
|
|
|
-def sequence(*taskList):
|
|
|
+def sequence(*taskList: AsyncTask) -> AsyncTaskSequence:
|
|
|
seq = AsyncTaskSequence('sequence')
|
|
|
for task in taskList:
|
|
|
seq.addTask(task)
|
|
|
@@ -122,7 +132,7 @@ def sequence(*taskList):
|
|
|
Task.DtoolClassDict['sequence'] = staticmethod(sequence)
|
|
|
|
|
|
|
|
|
-def loop(*taskList):
|
|
|
+def loop(*taskList: AsyncTask) -> AsyncTaskSequence:
|
|
|
seq = AsyncTaskSequence('loop')
|
|
|
for task in taskList:
|
|
|
seq.addTask(task)
|
|
|
@@ -144,10 +154,10 @@ class TaskManager:
|
|
|
|
|
|
__prevHandler: Any
|
|
|
|
|
|
- def __init__(self):
|
|
|
+ def __init__(self) -> None:
|
|
|
self.mgr = AsyncTaskManager.getGlobalPtr()
|
|
|
|
|
|
- self.resumeFunc = None
|
|
|
+ self.resumeFunc: Callable[[], object] | None = None
|
|
|
self.globalClock = self.mgr.getClock()
|
|
|
self.stepping = False
|
|
|
self.running = False
|
|
|
@@ -157,12 +167,12 @@ class TaskManager:
|
|
|
if signal:
|
|
|
self.__prevHandler = signal.default_int_handler
|
|
|
|
|
|
- self._frameProfileQueue = []
|
|
|
+ self._frameProfileQueue: list[tuple[int, Any, Callable[[], object] | None]] = []
|
|
|
|
|
|
# this will be set when it's safe to import StateVar
|
|
|
- self._profileFrames = None
|
|
|
+ self._profileFrames: Any = None
|
|
|
self._frameProfiler = None
|
|
|
- self._profileTasks = None
|
|
|
+ self._profileTasks: Any = None
|
|
|
self._taskProfiler = None
|
|
|
self._taskProfileInfo = ScratchPad(
|
|
|
taskId = None,
|
|
|
@@ -170,7 +180,7 @@ class TaskManager:
|
|
|
session = None,
|
|
|
)
|
|
|
|
|
|
- def finalInit(self):
|
|
|
+ def finalInit(self) -> None:
|
|
|
# This function should be called once during startup, after
|
|
|
# most things are imported.
|
|
|
from direct.fsm.StatePush import StateVar
|
|
|
@@ -179,7 +189,7 @@ class TaskManager:
|
|
|
self._profileFrames = StateVar(False)
|
|
|
self.setProfileFrames(ConfigVariableBool('profile-frames', 0).getValue())
|
|
|
|
|
|
- def destroy(self):
|
|
|
+ def destroy(self) -> None:
|
|
|
# This should be safe to call multiple times.
|
|
|
self.running = False
|
|
|
self.notify.info("TaskManager.destroy()")
|
|
|
@@ -187,10 +197,10 @@ class TaskManager:
|
|
|
self._frameProfileQueue.clear()
|
|
|
self.mgr.cleanup()
|
|
|
|
|
|
- def __getClock(self):
|
|
|
+ def __getClock(self) -> ClockObject:
|
|
|
return self.mgr.getClock()
|
|
|
|
|
|
- def setClock(self, clockObject):
|
|
|
+ def setClock(self, clockObject: ClockObject) -> None:
|
|
|
self.mgr.setClock(clockObject)
|
|
|
self.globalClock = clockObject
|
|
|
|
|
|
@@ -215,13 +225,13 @@ class TaskManager:
|
|
|
# Next time around invoke the default handler
|
|
|
signal.signal(signal.SIGINT, self.invokeDefaultHandler)
|
|
|
|
|
|
- def getCurrentTask(self):
|
|
|
+ def getCurrentTask(self) -> AsyncTask | None:
|
|
|
""" Returns the task currently executing on this thread, or
|
|
|
None if this is being called outside of the task manager. """
|
|
|
|
|
|
return Thread.getCurrentThread().getCurrentTask()
|
|
|
|
|
|
- def hasTaskChain(self, chainName):
|
|
|
+ def hasTaskChain(self, chainName: str) -> bool:
|
|
|
""" Returns true if a task chain with the indicated name has
|
|
|
already been defined, or false otherwise. Note that
|
|
|
setupTaskChain() will implicitly define a task chain if it has
|
|
|
@@ -231,9 +241,16 @@ class TaskManager:
|
|
|
|
|
|
return self.mgr.findTaskChain(chainName) is not None
|
|
|
|
|
|
- def setupTaskChain(self, chainName, numThreads = None, tickClock = None,
|
|
|
- threadPriority = None, frameBudget = None,
|
|
|
- frameSync = None, timeslicePriority = None):
|
|
|
+ def setupTaskChain(
|
|
|
+ self,
|
|
|
+ chainName: str,
|
|
|
+ numThreads: int | None = None,
|
|
|
+ tickClock: bool | None = None,
|
|
|
+ threadPriority: int | None = None,
|
|
|
+ frameBudget: float | None = None,
|
|
|
+ frameSync: bool | None = None,
|
|
|
+ timeslicePriority: bool | None = None,
|
|
|
+ ) -> None:
|
|
|
"""Defines a new task chain. Each task chain executes tasks
|
|
|
potentially in parallel with all of the other task chains (if
|
|
|
numThreads is more than zero). When a new task is created, it
|
|
|
@@ -297,40 +314,50 @@ class TaskManager:
|
|
|
if timeslicePriority is not None:
|
|
|
chain.setTimeslicePriority(timeslicePriority)
|
|
|
|
|
|
- def hasTaskNamed(self, taskName):
|
|
|
+ def hasTaskNamed(self, taskName: str) -> bool:
|
|
|
"""Returns true if there is at least one task, active or
|
|
|
sleeping, with the indicated name. """
|
|
|
|
|
|
return bool(self.mgr.findTask(taskName))
|
|
|
|
|
|
- def getTasksNamed(self, taskName):
|
|
|
+ def getTasksNamed(self, taskName: str) -> list[AsyncTask]:
|
|
|
"""Returns a list of all tasks, active or sleeping, with the
|
|
|
indicated name. """
|
|
|
return list(self.mgr.findTasks(taskName))
|
|
|
|
|
|
- def getTasksMatching(self, taskPattern):
|
|
|
+ def getTasksMatching(self, taskPattern: GlobPattern | str) -> list[AsyncTask]:
|
|
|
"""Returns a list of all tasks, active or sleeping, with a
|
|
|
name that matches the pattern, which can include standard
|
|
|
shell globbing characters like \\*, ?, and []. """
|
|
|
|
|
|
return list(self.mgr.findTasksMatching(GlobPattern(taskPattern)))
|
|
|
|
|
|
- def getAllTasks(self):
|
|
|
+ def getAllTasks(self) -> list[AsyncTask]:
|
|
|
"""Returns list of all tasks, active and sleeping, in
|
|
|
arbitrary order. """
|
|
|
return list(self.mgr.getTasks())
|
|
|
|
|
|
- def getTasks(self):
|
|
|
+ def getTasks(self) -> list[AsyncTask]:
|
|
|
"""Returns list of all active tasks in arbitrary order. """
|
|
|
return list(self.mgr.getActiveTasks())
|
|
|
|
|
|
- def getDoLaters(self):
|
|
|
+ def getDoLaters(self) -> list[AsyncTask]:
|
|
|
"""Returns list of all sleeping tasks in arbitrary order. """
|
|
|
return list(self.mgr.getSleepingTasks())
|
|
|
|
|
|
- def doMethodLater(self, delayTime, funcOrTask, name, extraArgs = None,
|
|
|
- sort = None, priority = None, taskChain = None,
|
|
|
- uponDeath = None, appendTask = False, owner = None):
|
|
|
+ def doMethodLater(
|
|
|
+ self,
|
|
|
+ delayTime: float,
|
|
|
+ funcOrTask: _FuncOrTask,
|
|
|
+ name: str | None,
|
|
|
+ extraArgs: Sequence | None = None,
|
|
|
+ sort: int | None = None,
|
|
|
+ priority: int | None = None,
|
|
|
+ taskChain: str | None = None,
|
|
|
+ uponDeath: Callable[[], object] | None = None,
|
|
|
+ appendTask: bool = False,
|
|
|
+ owner = None,
|
|
|
+ ) -> AsyncTask:
|
|
|
"""Adds a task to be performed at some time in the future.
|
|
|
This is identical to `add()`, except that the specified
|
|
|
delayTime is applied to the Task object first, which means
|
|
|
@@ -353,9 +380,19 @@ class TaskManager:
|
|
|
|
|
|
do_method_later = doMethodLater
|
|
|
|
|
|
- def add(self, funcOrTask, name = None, sort = None, extraArgs = None,
|
|
|
- priority = None, uponDeath = None, appendTask = False,
|
|
|
- taskChain = None, owner = None, delay = None):
|
|
|
+ def add(
|
|
|
+ self,
|
|
|
+ funcOrTask: _FuncOrTask,
|
|
|
+ name: str | None = None,
|
|
|
+ sort: int | None = None,
|
|
|
+ extraArgs: Sequence | None = None,
|
|
|
+ priority: int | None = None,
|
|
|
+ uponDeath: Callable[[], object] | None = None,
|
|
|
+ appendTask: bool = False,
|
|
|
+ taskChain: str | None = None,
|
|
|
+ owner = None,
|
|
|
+ delay: float | None = None,
|
|
|
+ ) -> AsyncTask:
|
|
|
"""
|
|
|
Add a new task to the taskMgr. The task will begin executing
|
|
|
immediately, or next frame if its sort value has already
|
|
|
@@ -422,7 +459,18 @@ class TaskManager:
|
|
|
self.mgr.add(task)
|
|
|
return task
|
|
|
|
|
|
- def __setupTask(self, funcOrTask, name, priority, sort, extraArgs, taskChain, appendTask, owner, uponDeath):
|
|
|
+ def __setupTask(
|
|
|
+ self,
|
|
|
+ funcOrTask: _FuncOrTask,
|
|
|
+ name: str | None,
|
|
|
+ priority: int | None,
|
|
|
+ sort: int | None,
|
|
|
+ extraArgs: Sequence | None,
|
|
|
+ taskChain: str | None,
|
|
|
+ appendTask: bool,
|
|
|
+ owner,
|
|
|
+ uponDeath: Callable[[], object] | None,
|
|
|
+ ) -> AsyncTask:
|
|
|
wasTask = False
|
|
|
if isinstance(funcOrTask, AsyncTask):
|
|
|
task = funcOrTask
|
|
|
@@ -480,7 +528,7 @@ class TaskManager:
|
|
|
|
|
|
return task
|
|
|
|
|
|
- def remove(self, taskOrName):
|
|
|
+ def remove(self, taskOrName: AsyncTask | str | list[AsyncTask | str]) -> int:
|
|
|
"""Removes a task from the task manager. The task is stopped,
|
|
|
almost as if it had returned task.done. (But if the task is
|
|
|
currently executing, it will finish out its current frame
|
|
|
@@ -492,13 +540,15 @@ class TaskManager:
|
|
|
if isinstance(taskOrName, AsyncTask):
|
|
|
return self.mgr.remove(taskOrName)
|
|
|
elif isinstance(taskOrName, list):
|
|
|
+ count = 0
|
|
|
for task in taskOrName:
|
|
|
- self.remove(task)
|
|
|
+ count += self.remove(task)
|
|
|
+ return count
|
|
|
else:
|
|
|
tasks = self.mgr.findTasks(taskOrName)
|
|
|
return self.mgr.remove(tasks)
|
|
|
|
|
|
- def removeTasksMatching(self, taskPattern):
|
|
|
+ def removeTasksMatching(self, taskPattern: GlobPattern | str) -> int:
|
|
|
"""Removes all tasks whose names match the pattern, which can
|
|
|
include standard shell globbing characters like \\*, ?, and [].
|
|
|
See also :meth:`remove()`.
|
|
|
@@ -508,7 +558,7 @@ class TaskManager:
|
|
|
tasks = self.mgr.findTasksMatching(GlobPattern(taskPattern))
|
|
|
return self.mgr.remove(tasks)
|
|
|
|
|
|
- def step(self):
|
|
|
+ def step(self) -> None:
|
|
|
"""Invokes the task manager for one frame, and then returns.
|
|
|
Normally, this executes each task exactly once, though task
|
|
|
chains that are in sub-threads or that have frame budgets
|
|
|
@@ -519,7 +569,7 @@ class TaskManager:
|
|
|
# Replace keyboard interrupt handler during task list processing
|
|
|
# so we catch the keyboard interrupt but don't handle it until
|
|
|
# after task list processing is complete.
|
|
|
- self.fKeyboardInterrupt = 0
|
|
|
+ self.fKeyboardInterrupt = False
|
|
|
self.interruptCount = 0
|
|
|
|
|
|
if signal:
|
|
|
@@ -541,7 +591,7 @@ class TaskManager:
|
|
|
if self.fKeyboardInterrupt:
|
|
|
raise KeyboardInterrupt
|
|
|
|
|
|
- def run(self):
|
|
|
+ def run(self) -> None:
|
|
|
"""Starts the task manager running. Does not return until an
|
|
|
exception is encountered (including KeyboardInterrupt). """
|
|
|
|
|
|
@@ -567,11 +617,11 @@ class TaskManager:
|
|
|
if len(self._frameProfileQueue) > 0:
|
|
|
numFrames, session, callback = self._frameProfileQueue.pop(0)
|
|
|
|
|
|
- def _profileFunc(numFrames=numFrames):
|
|
|
+ def _profileFunc(numFrames: int = numFrames) -> None:
|
|
|
self._doProfiledFrames(numFrames)
|
|
|
session.setFunc(_profileFunc)
|
|
|
session.run()
|
|
|
- _profileFunc = None
|
|
|
+ del _profileFunc
|
|
|
if callback:
|
|
|
callback()
|
|
|
session.release()
|
|
|
@@ -624,7 +674,7 @@ class TaskManager:
|
|
|
message = ioError
|
|
|
return code, message
|
|
|
|
|
|
- def stop(self):
|
|
|
+ def stop(self) -> None:
|
|
|
# Set a flag so we will stop before beginning next frame
|
|
|
self.running = False
|
|
|
|
|
|
@@ -789,12 +839,12 @@ class TaskManager:
|
|
|
task = tasks.getTask(i)
|
|
|
return task
|
|
|
|
|
|
- def __repr__(self):
|
|
|
+ def __repr__(self) -> str:
|
|
|
return str(self.mgr)
|
|
|
|
|
|
# In the event we want to do frame time managment, this is the
|
|
|
# function to replace or overload.
|
|
|
- def doYield(self, frameStartTime, nextScheduledTaskTime):
|
|
|
+ def doYield(self, frameStartTime: float, nextScheduledTaskTime: float) -> None:
|
|
|
pass
|
|
|
|
|
|
#def doYieldExample(self, frameStartTime, nextScheduledTaskTime):
|