Browse Source

showbase: Annotate some debug functions (#1767)

WMOkiishi 3 months ago
parent
commit
67e3ccada7

+ 2 - 2
direct/src/directtools/DirectUtil.py

@@ -1,4 +1,4 @@
-from panda3d.core import VBase4
+from panda3d.core import NodePath, VBase4
 from direct.task.Task import Task
 from direct.task.TaskManagerGlobal import taskMgr
 
@@ -55,7 +55,7 @@ def lerpBackgroundColor(r, g, b, duration):
 
 # Set direct drawing style for an object
 # Never light object or draw in wireframe
-def useDirectRenderStyle(nodePath, priority = 0):
+def useDirectRenderStyle(nodePath: NodePath, priority: int = 0) -> None:
     """
     Function to force a node path to use direct render style:
     no lighting, and no wireframe

+ 27 - 15
direct/src/showbase/BufferViewer.py

@@ -12,6 +12,8 @@ Or, you can enable the following variable in your Config.prc::
     show-buffers true
 """
 
+from __future__ import annotations
+
 __all__ = ['BufferViewer']
 
 from panda3d.core import (
@@ -39,14 +41,19 @@ from direct.task.TaskManagerGlobal import taskMgr
 from direct.directnotify.DirectNotifyGlobal import directNotify
 from direct.showbase.DirectObject import DirectObject
 import math
+from typing import Literal, Union
+
+# The following variables are typing constructs used in annotations
+# to succinctly express complex type structures.
+_Texture = Union[Texture, GraphicsOutput, Literal['all']]
 
 
 class BufferViewer(DirectObject):
     notify = directNotify.newCategory('BufferViewer')
 
-    def __init__(self, win, parent):
+    def __init__(self, win: GraphicsOutput | None, parent: NodePath) -> None:
         """Access: private.  Constructor."""
-        self.enabled = 0
+        self.enabled = False
         size = ConfigVariableDouble('buffer-viewer-size', '0 0')
         self.sizex = size[0]
         self.sizey = size[1]
@@ -59,23 +66,23 @@ class BufferViewer(DirectObject):
         self.win = win
         self.engine = GraphicsEngine.getGlobalPtr()
         self.renderParent = parent
-        self.cards = []
+        self.cards: list[NodePath] = []
         self.cardindex = 0
         self.cardmaker = CardMaker("cubemaker")
         self.cardmaker.setFrame(-1,1,-1,1)
         self.task = 0
-        self.dirty = 1
+        self.dirty = True
         self.accept("render-texture-targets-changed", self.refreshReadout)
         if ConfigVariableBool("show-buffers", 0):
-            self.enable(1)
+            self.enable(True)
 
-    def refreshReadout(self):
+    def refreshReadout(self) -> None:
         """Force the readout to be refreshed.  This is usually invoked
         by GraphicsOutput::add_render_texture (via an event handler).
         However, it is also possible to invoke it manually.  Currently,
         the only time I know of that this is necessary is after a
         window resize (and I ought to fix that)."""
-        self.dirty = 1
+        self.dirty = True
 
         # Call enabled again, mainly to ensure that the task has been
         # started.
@@ -95,14 +102,14 @@ class BufferViewer(DirectObject):
         """Returns true if the buffer viewer is currently enabled."""
         return self.enabled
 
-    def enable(self, x):
+    def enable(self, x: bool) -> None:
         """Turn the buffer viewer on or off.  The initial state of the
         buffer viewer depends on the Config variable 'show-buffers'."""
         if x != 0 and x != 1:
             BufferViewer.notify.error('invalid parameter to BufferViewer.enable')
             return
         self.enabled = x
-        self.dirty = 1
+        self.dirty = True
         if (x and self.task == 0):
             self.task = taskMgr.add(self.maintainReadout, "buffer-viewer-maintain-readout",
                                     priority=1)
@@ -218,7 +225,11 @@ class BufferViewer(DirectObject):
         self.renderParent = renderParent
         self.dirty = 1
 
-    def analyzeTextureSet(self, x, set):
+    def analyzeTextureSet(
+        self,
+        x: _Texture | GraphicsEngine | list[_Texture | GraphicsEngine],
+        set: dict[Texture, int],
+    ) -> None:
         """Access: private.  Converts a list of GraphicsObject,
         GraphicsEngine, and Texture into a table of Textures."""
 
@@ -240,7 +251,7 @@ class BufferViewer(DirectObject):
         else:
             return
 
-    def makeFrame(self, sizex, sizey):
+    def makeFrame(self, sizex: int, sizey: int) -> NodePath:
         """Access: private.  Each texture card is displayed with
         a two-pixel wide frame (a ring of black and a ring of white).
         This routine builds the frame geometry.  It is necessary to
@@ -287,7 +298,7 @@ class BufferViewer(DirectObject):
         geomnode.addGeom(geom)
         return NodePath(geomnode)
 
-    def maintainReadout(self, task):
+    def maintainReadout(self, task: object) -> int:
         """Access: private.  Whenever necessary, rebuilds the entire
         display from scratch.  This is only done when the configuration
         parameters have changed."""
@@ -295,7 +306,7 @@ class BufferViewer(DirectObject):
         # If nothing has changed, don't update.
         if not self.dirty:
             return Task.cont
-        self.dirty = 0
+        self.dirty = False
 
         # Delete the old set of cards.
         for card in self.cards:
@@ -308,8 +319,8 @@ class BufferViewer(DirectObject):
             return Task.done
 
         # Generate the include and exclude sets.
-        exclude = {}
-        include = {}
+        exclude: dict[Texture, int] = {}
+        include: dict[Texture, int] = {}
         self.analyzeTextureSet(self.exclude, exclude)
         self.analyzeTextureSet(self.include, include)
 
@@ -407,6 +418,7 @@ class BufferViewer(DirectObject):
 
         bordersize = 4.0
 
+        assert self.win is not None
         if float(self.sizex) == 0.0 and float(self.sizey) == 0.0:
             sizey = int(0.4266666667 * self.win.getYSize())
             sizex = (sizey * aspectx) // aspecty

+ 4 - 4
direct/src/showbase/ExceptionVarDump.py

@@ -35,7 +35,7 @@ def _varDump__init__(self, *args, **kArgs):
 sReentry = 0
 
 
-def _varDump__print(exc):
+def _varDump__print(exc) -> None:
     global sReentry
     global notify
     if sReentry > 0:
@@ -176,7 +176,7 @@ def _excepthookDumpVars(eType, eValue, tb):
     oldExcepthook(eType, eValue, origTb)
 
 
-def install(log, upload):
+def install(log: bool, upload: bool) -> None:
     """Installs the exception hook."""
     global oldExcepthook
     global wantStackDumpLog
@@ -192,8 +192,8 @@ def install(log, upload):
         # thrown by the interpreter don't get created until the
         # stack has been unwound and an except block has been reached
         if not hasattr(Exception, '_moved__init__'):
-            Exception._moved__init__ = Exception.__init__
-            Exception.__init__ = _varDump__init__
+            Exception._moved__init__ = Exception.__init__  # type: ignore[attr-defined]
+            Exception.__init__ = _varDump__init__  # type: ignore[method-assign]
     else:
         if sys.excepthook is not _excepthookDumpVars:
             oldExcepthook = sys.excepthook

+ 29 - 12
direct/src/showbase/GarbageReport.py

@@ -1,5 +1,7 @@
 """Contains utility classes for debugging memory leaks."""
 
+from __future__ import annotations
+
 __all__ = ['FakeObject', '_createGarbage', 'GarbageReport', 'GarbageLogger']
 
 from direct.directnotify.DirectNotifyGlobal import directNotify
@@ -10,6 +12,7 @@ from direct.showbase.JobManagerGlobal import jobMgr
 from direct.showbase.MessengerGlobal import messenger
 from panda3d.core import ConfigVariableBool
 import gc
+from collections.abc import Callable
 
 GarbageCycleCountAnnounceEvent = 'announceGarbageCycleDesc2num'
 
@@ -41,9 +44,21 @@ class GarbageReport(Job):
     If you just want to dump the report to the log, use GarbageLogger."""
     notify = directNotify.newCategory("GarbageReport")
 
-    def __init__(self, name, log=True, verbose=False, fullReport=False, findCycles=True,
-                 threaded=False, doneCallback=None, autoDestroy=False, priority=None,
-                 safeMode=False, delOnly=False, collect=True):
+    def __init__(
+        self,
+        name: str,
+        log: bool = True,
+        verbose: bool = False,
+        fullReport: bool = False,
+        findCycles: bool = True,
+        threaded: bool = False,
+        doneCallback: Callable[[GarbageReport], object] | None = None,
+        autoDestroy: bool = False,
+        priority: int | None = None,
+        safeMode: bool = False,
+        delOnly: bool = False,
+        collect: bool = True
+    ) -> None:
         # if autoDestroy is True, GarbageReport will self-destroy after logging
         # if false, caller is responsible for calling destroy()
         # if threaded is True, processing will be performed over multiple frames
@@ -399,7 +414,7 @@ class GarbageReport(Job):
         if self._args.autoDestroy:
             self.destroy()
 
-    def destroy(self):
+    def destroy(self) -> None:
         #print 'GarbageReport.destroy'
         del self._args
         del self.garbage
@@ -417,13 +432,13 @@ class GarbageReport(Job):
             del self._reportStr
         Job.destroy(self)
 
-    def getNumCycles(self):
+    def getNumCycles(self) -> int:
         # if the job hasn't run yet, we don't have a numCycles yet
         return self.numCycles
 
-    def getDesc2numDict(self):
+    def getDesc2numDict(self) -> dict[str, int]:
         # dict of python-syntax leak -> number of that type of leak
-        desc2num = {}
+        desc2num: dict[str, int] = {}
         for cycleBySyntax in self.cyclesBySyntax:
             desc2num.setdefault(cycleBySyntax, 0)
             desc2num[cycleBySyntax] += 1
@@ -563,7 +578,7 @@ class _CFGLGlobals:
     LastNumCycles = 0
 
 
-def checkForGarbageLeaks():
+def checkForGarbageLeaks() -> int:
     gc.collect()
     numGarbage = len(gc.garbage)
     if numGarbage > 0 and ConfigVariableBool('auto-garbage-logging', False):
@@ -576,6 +591,7 @@ def checkForGarbageLeaks():
             messenger.send(GarbageCycleCountAnnounceEvent, [gr.getDesc2numDict()])
             gr.destroy()
         notify = directNotify.newCategory("GarbageDetect")
+        func: Callable[[str], object]
         if ConfigVariableBool('allow-garbage-cycles', True):
             func = notify.warning
         else:
@@ -584,7 +600,8 @@ def checkForGarbageLeaks():
     return numGarbage
 
 
-def b_checkForGarbageLeaks(wantReply=False):
+def b_checkForGarbageLeaks(wantReply: bool = False) -> int:
+    from direct.showbase.ShowBaseGlobal import base, __dev__
     if not __dev__:
         return 0
     # does a garbage collect on the client and the AI
@@ -592,10 +609,10 @@ def b_checkForGarbageLeaks(wantReply=False):
     # logs leak info and terminates (if configured to do so)
     try:
         # if this is the client, tell the AI to check for leaks too
-        base.cr.timeManager
+        base.cr.timeManager  # type: ignore[attr-defined]
     except Exception:
         pass
     else:
-        if base.cr.timeManager:
-            base.cr.timeManager.d_checkForGarbageLeaks(wantReply=wantReply)
+        if base.cr.timeManager:  # type: ignore[attr-defined]
+            base.cr.timeManager.d_checkForGarbageLeaks(wantReply=wantReply)  # type: ignore[attr-defined]
     return checkForGarbageLeaks()

+ 1 - 1
direct/src/showbase/Messenger.py

@@ -596,7 +596,7 @@ class Messenger:
                         break
         return matches
 
-    def __methodRepr(self, method):
+    def __methodRepr(self, method: object) -> str:
         """
         return string version of class.method or method.
         """

+ 13 - 7
direct/src/showbase/OnScreenDebug.py

@@ -1,7 +1,11 @@
 """Contains the OnScreenDebug class."""
 
+from __future__ import annotations
+
 __all__ = ['OnScreenDebug']
 
+from typing import Any
+
 from panda3d.core import (
     ConfigVariableBool,
     ConfigVariableDouble,
@@ -18,13 +22,13 @@ class OnScreenDebug:
 
     enabled = ConfigVariableBool("on-screen-debug-enabled", False)
 
-    def __init__(self):
-        self.onScreenText = None
+    def __init__(self) -> None:
+        self.onScreenText: OnscreenText.OnscreenText | None = None
         self.frame = 0
         self.text = ""
-        self.data = {}
+        self.data: dict[str, tuple[int, Any]] = {}
 
-    def load(self):
+    def load(self) -> None:
         if self.onScreenText:
             return
 
@@ -40,6 +44,7 @@ class OnScreenDebug:
         fgColor.setW(ConfigVariableDouble("on-screen-debug-fg-alpha", 0.85).value)
         bgColor.setW(ConfigVariableDouble("on-screen-debug-bg-alpha", 0.85).value)
 
+        from direct.showbase.ShowBaseGlobal import base
         font = base.loader.loadFont(fontPath)
         if not font.isValid():
             print("failed to load OnScreenDebug font %s" % fontPath)
@@ -47,15 +52,16 @@ class OnScreenDebug:
         self.onScreenText = OnscreenText.OnscreenText(
                 parent = base.a2dTopLeft, pos = (0.0, -0.1),
                 fg=fgColor, bg=bgColor, scale = (fontScale, fontScale, 0.0),
-                align = TextNode.ALeft, mayChange = 1, font = font)
+                align = TextNode.ALeft, mayChange = True, font = font)
         # Make sure readout is never lit or drawn in wireframe
         DirectUtil.useDirectRenderStyle(self.onScreenText)
 
-    def render(self):
+    def render(self) -> None:
         if not self.enabled:
             return
         if not self.onScreenText:
             self.load()
+        assert self.onScreenText is not None
         self.onScreenText.clearText()
         for k, v in sorted(self.data.items()):
             if v[0] == self.frame:
@@ -75,7 +81,7 @@ class OnScreenDebug:
         self.onScreenText.appendText(self.text)
         self.frame += 1
 
-    def clear(self):
+    def clear(self) -> None:
         self.text = ""
         if self.onScreenText:
             self.onScreenText.clearText()

+ 17 - 10
direct/src/showbase/PythonUtil.py

@@ -233,7 +233,7 @@ if __debug__:
 
     #-----------------------------------------------------------------------------
 
-    def traceFunctionCall(frame):
+    def traceFunctionCall(frame: types.FrameType) -> str:
         """
         return a string that shows the call frame with calling arguments.
         e.g.
@@ -274,7 +274,7 @@ if __debug__:
                 r+="*** undefined ***"
         return r+')'
 
-    def traceParentCall():
+    def traceParentCall() -> str:
         return traceFunctionCall(sys._getframe(2))
 
     def printThisCall():
@@ -1002,7 +1002,7 @@ def lineupPos(i, num, spacing):
     return pos - ((float(spacing) * (num-1))/2.)
 
 
-def formatElapsedSeconds(seconds):
+def formatElapsedSeconds(seconds: float) -> str:
     """
     Returns a string of the form "mm:ss" or "hh:mm:ss" or "n days",
     representing the indicated elapsed time in seconds.
@@ -1408,14 +1408,16 @@ def _getSafeReprNotify():
     return safeReprNotify
 
 
-def safeRepr(obj):
+def safeRepr(obj: object) -> str:
     global dtoolSuperBase
     if dtoolSuperBase is None:
         _getDtoolSuperBase()
+    assert dtoolSuperBase is not None
 
     global safeReprNotify
     if safeReprNotify is None:
         _getSafeReprNotify()
+    assert safeReprNotify is not None
 
     if isinstance(obj, dtoolSuperBase):
         # repr of C++ object could crash, particularly if the object has been deleted
@@ -1447,7 +1449,12 @@ def safeReprTypeOnFail(obj):
         return '<** FAILED REPR OF %s instance at %s **>' % (obj.__class__.__name__, hex(id(obj)))
 
 
-def fastRepr(obj, maxLen=200, strFactor=10, _visitedIds=None):
+def fastRepr(
+    obj: object,
+    maxLen: int = 200,
+    strFactor: int = 10,
+    _visitedIds: set[int] | None = None,
+) -> str:
     """ caps the length of iterable types, so very large objects will print faster.
     also prevents infinite recursion """
     try:
@@ -1456,9 +1463,9 @@ def fastRepr(obj, maxLen=200, strFactor=10, _visitedIds=None):
         if id(obj) in _visitedIds:
             return '<ALREADY-VISITED %s>' % itype(obj)
         if type(obj) in (tuple, list):
+            assert isinstance(obj, (tuple, list))
             s = ''
-            s += {tuple: '(',
-                  list:  '[',}[type(obj)]
+            s += '(' if type(obj) == tuple else '['
             if maxLen is not None and len(obj) > maxLen:
                 o = obj[:maxLen]
                 ellips = '...'
@@ -1471,8 +1478,7 @@ def fastRepr(obj, maxLen=200, strFactor=10, _visitedIds=None):
                 s += ', '
             _visitedIds.remove(id(obj))
             s += ellips
-            s += {tuple: ')',
-                  list:  ']',}[type(obj)]
+            s += ')' if type(obj) == tuple else ']'
             return s
         elif type(obj) is dict:
             s = '{'
@@ -1624,7 +1630,7 @@ class Sync:
                               self._name, self._series, self._value)
 
 
-def itype(obj):
+def itype(obj: object) -> type | str:
     # version of type that gives more complete information about instance types
     global dtoolSuperBase
     t = type(obj)
@@ -1632,6 +1638,7 @@ def itype(obj):
     # check if this is a C++ object
     if dtoolSuperBase is None:
         _getDtoolSuperBase()
+    assert dtoolSuperBase is not None
     if isinstance(obj, dtoolSuperBase):
         return "<type 'instance' of %s>" % (obj.__class__)
     return t

+ 4 - 2
direct/src/showbase/ShowBase.py

@@ -177,6 +177,8 @@ class ShowBase(DirectObject.DirectObject):
     aspect2d: NodePath
     pixel2d: NodePath
 
+    a2dTopLeft: NodePath
+
     cluster: Any | None
 
     def __init__(self, fStartDirect: bool = True, windowType: str | None = None) -> None:
@@ -438,7 +440,7 @@ class ShowBase(DirectObject.DirectObject):
         self.useTrackball()
 
         #: `.Loader.Loader` object.
-        self.loader = ShowBaseGlobal.loader
+        self.loader: Loader.Loader = ShowBaseGlobal.loader
         self.loader._init_base(self)
         self.graphicsEngine.setDefaultLoader(self.loader.loader)
 
@@ -654,7 +656,7 @@ class ShowBase(DirectObject.DirectObject):
     def getExitErrorCode(self):
         return 0
 
-    def printEnvDebugInfo(self):
+    def printEnvDebugInfo(self) -> None:
         """Print some information about the environment that we are running
         in.  Stuff like the model paths and other paths.  Feel free to
         add stuff to this.