Browse Source

showbase: Annotate basic `Loader` methods (#1723)

WMOkiishi 9 months ago
parent
commit
8de98592ca
2 changed files with 54 additions and 22 deletions
  1. 51 21
      direct/src/showbase/Loader.py
  2. 3 1
      tests/showbase/test_Loader.py

+ 51 - 21
direct/src/showbase/Loader.py

@@ -2,9 +2,13 @@
 sound, music, shaders and fonts from disk.
 sound, music, shaders and fonts from disk.
 """
 """
 
 
+from __future__ import annotations
+
 __all__ = ['Loader']
 __all__ = ['Loader']
 
 
 from panda3d.core import (
 from panda3d.core import (
+    AsyncTask,
+    AudioManager,
     ConfigVariableBool,
     ConfigVariableBool,
     Filename,
     Filename,
     FontPool,
     FontPool,
@@ -24,13 +28,15 @@ from panda3d.core import (
 from panda3d.core import Loader as PandaLoader
 from panda3d.core import Loader as PandaLoader
 from direct.directnotify.DirectNotifyGlobal import directNotify
 from direct.directnotify.DirectNotifyGlobal import directNotify
 from direct.showbase.DirectObject import DirectObject
 from direct.showbase.DirectObject import DirectObject
+from . import ShowBase
 import warnings
 import warnings
 import sys
 import sys
+from typing import Any, Callable, Iterable
 
 
 # You can specify a phaseChecker callback to check
 # You can specify a phaseChecker callback to check
 # a modelPath to see if it is being loaded in the correct
 # a modelPath to see if it is being loaded in the correct
 # phase
 # phase
-phaseChecker = None
+phaseChecker: Callable[[str, LoaderOptions], object] | None = None
 
 
 
 
 class Loader(DirectObject):
 class Loader(DirectObject):
@@ -49,16 +55,23 @@ class Loader(DirectObject):
         # This indicates that this class behaves like a Future.
         # This indicates that this class behaves like a Future.
         _asyncio_future_blocking = False
         _asyncio_future_blocking = False
 
 
-        def __init__(self, loader, numObjects, gotList, callback, extraArgs):
+        def __init__(
+            self,
+            loader: Loader | None,
+            numObjects: int,
+            gotList: bool,
+            callback: Callable[..., object] | None,
+            extraArgs: list,
+        ) -> None:
             self._loader = loader
             self._loader = loader
-            self.objects = [None] * numObjects
+            self.objects: list[Any | None] = [None] * numObjects
             self.gotList = gotList
             self.gotList = gotList
             self.callback = callback
             self.callback = callback
             self.extraArgs = extraArgs
             self.extraArgs = extraArgs
-            self.requests = set()
-            self.requestList = []
+            self.requests: set[AsyncTask] = set()
+            self.requestList: list[AsyncTask] = []
 
 
-        def gotObject(self, index, object):
+        def gotObject(self, index: int, object) -> None:
             self.objects[index] = object
             self.objects[index] = object
 
 
             if not self.requests:
             if not self.requests:
@@ -79,7 +92,7 @@ class Loader(DirectObject):
                 self.requests = None
                 self.requests = None
                 self.requestList = None
                 self.requestList = None
 
 
-        def cancelled(self):
+        def cancelled(self) -> bool:
             "Returns true if the request was cancelled."
             "Returns true if the request was cancelled."
             return self.requestList is None
             return self.requestList is None
 
 
@@ -87,7 +100,7 @@ class Loader(DirectObject):
             "Returns true if all the requests were finished or cancelled."
             "Returns true if all the requests were finished or cancelled."
             return not self.requests
             return not self.requests
 
 
-        def result(self):
+        def result(self) -> Any:
             "Returns the results, suspending the thread to wait if necessary."
             "Returns the results, suspending the thread to wait if necessary."
             for r in list(self.requests):
             for r in list(self.requests):
                 r.wait()
                 r.wait()
@@ -126,26 +139,26 @@ class Loader(DirectObject):
                 yield await req
                 yield await req
 
 
     # special methods
     # special methods
-    def __init__(self, base=None):
+    def __init__(self, base: ShowBase.ShowBase | None = None) -> None:
         self.base = base
         self.base = base
         self.loader = PandaLoader.getGlobalPtr()
         self.loader = PandaLoader.getGlobalPtr()
 
 
-        self._requests = {}
+        self._requests: dict[AsyncTask, tuple[Loader._Callback, int]] = {}
 
 
         self.hook = "async_loader_%s" % (Loader.loaderIndex)
         self.hook = "async_loader_%s" % (Loader.loaderIndex)
         Loader.loaderIndex += 1
         Loader.loaderIndex += 1
 
 
-    def destroy(self):
+    def destroy(self) -> None:
         self.ignore(self.hook)
         self.ignore(self.hook)
         self.loader.stopThreads()
         self.loader.stopThreads()
         del self.base
         del self.base
 
 
-    def _init_base(self, base):
+    def _init_base(self, base: ShowBase.ShowBase) -> None:
         self.base = base
         self.base = base
         self.accept(self.hook, self.__gotAsyncObject)
         self.accept(self.hook, self.__gotAsyncObject)
 
 
     @classmethod
     @classmethod
-    def _loadPythonFileTypes(cls):
+    def _loadPythonFileTypes(cls) -> None:
         if cls._loadedPythonFileTypes:
         if cls._loadedPythonFileTypes:
             return
             return
 
 
@@ -168,10 +181,18 @@ class Loader(DirectObject):
             cls._loadedPythonFileTypes = True
             cls._loadedPythonFileTypes = True
 
 
     # model loading funcs
     # model loading funcs
-    def loadModel(self, modelPath, loaderOptions = None, noCache = None,
-                  allowInstance = False, okMissing = None,
-                  callback = None, extraArgs = [], priority = None,
-                  blocking = None):
+    def loadModel(
+        self,
+        modelPath: str | list[str] | tuple[str, ...] | set[str],
+        loaderOptions: LoaderOptions | None = None,
+        noCache: bool | None = None,
+        allowInstance: bool = False,
+        okMissing: bool | None = None,
+        callback: Callable[..., object] | None = None,
+        extraArgs: list = [],
+        priority: int | None = None,
+        blocking: bool | None = None,
+    ) -> Any:
         """
         """
         Attempts to load a model or models from one or more relative
         Attempts to load a model or models from one or more relative
         pathnames.  If the input modelPath is a string (a single model
         pathnames.  If the input modelPath is a string (a single model
@@ -255,6 +276,7 @@ class Loader(DirectObject):
         if allowInstance:
         if allowInstance:
             loaderOptions.setFlags(loaderOptions.getFlags() | LoaderOptions.LFAllowInstance)
             loaderOptions.setFlags(loaderOptions.getFlags() | LoaderOptions.LFAllowInstance)
 
 
+        modelList: Iterable[str]
         if not isinstance(modelPath, (tuple, list, set)):
         if not isinstance(modelPath, (tuple, list, set)):
             # We were given a single model pathname.
             # We were given a single model pathname.
             modelList = [modelPath]
             modelList = [modelPath]
@@ -949,7 +971,7 @@ class Loader(DirectObject):
         TexturePool.releaseTexture(texture)
         TexturePool.releaseTexture(texture)
 
 
     # sound loading funcs
     # sound loading funcs
-    def loadSfx(self, *args, **kw):
+    def loadSfx(self, *args, **kw) -> Any:
         """Loads one or more sound files, specifically designated as a
         """Loads one or more sound files, specifically designated as a
         "sound effect" file (that is, uses the sfxManager to load the
         "sound effect" file (that is, uses the sfxManager to load the
         sound).  There is no distinction between sound effect files
         sound).  There is no distinction between sound effect files
@@ -959,6 +981,7 @@ class Loader(DirectObject):
         independently of the other group."""
         independently of the other group."""
 
 
         # showbase-created sfxManager should always be at front of list
         # showbase-created sfxManager should always be at front of list
+        assert self.base is not None
         if self.base.sfxManagerList:
         if self.base.sfxManagerList:
             return self.loadSound(self.base.sfxManagerList[0], *args, **kw)
             return self.loadSound(self.base.sfxManagerList[0], *args, **kw)
         return None
         return None
@@ -976,8 +999,14 @@ class Loader(DirectObject):
         else:
         else:
             return None
             return None
 
 
-    def loadSound(self, manager, soundPath, positional = False,
-                  callback = None, extraArgs = []):
+    def loadSound(
+        self,
+        manager: AudioManager,
+        soundPath: str | tuple[str, ...] | list[str] | set[str],
+        positional: bool = False,
+        callback: Callable[..., object] | None = None,
+        extraArgs: list = [],
+    ) -> Any:
         """Loads one or more sound files, specifying the particular
         """Loads one or more sound files, specifying the particular
         AudioManager that should be used to load them.  The soundPath
         AudioManager that should be used to load them.  The soundPath
         may be either a single filename, or a list of filenames.  If a
         may be either a single filename, or a list of filenames.  If a
@@ -987,6 +1016,7 @@ class Loader(DirectObject):
 
 
         from panda3d.core import AudioLoadRequest
         from panda3d.core import AudioLoadRequest
 
 
+        soundList: Iterable[str]
         if not isinstance(soundPath, (tuple, list, set)):
         if not isinstance(soundPath, (tuple, list, set)):
             # We were given a single sound pathname or a MovieAudio instance.
             # We were given a single sound pathname or a MovieAudio instance.
             soundList = [soundPath]
             soundList = [soundPath]
@@ -1112,7 +1142,7 @@ class Loader(DirectObject):
             else:
             else:
                 callback(*(origModelList + extraArgs))
                 callback(*(origModelList + extraArgs))
 
 
-    def __gotAsyncObject(self, request):
+    def __gotAsyncObject(self, request: AsyncTask) -> None:
         """A model or sound file or some such thing has just been
         """A model or sound file or some such thing has just been
         loaded asynchronously by the sub-thread.  Add it to the list
         loaded asynchronously by the sub-thread.  Add it to the list
         of loaded objects, and call the appropriate callback when it's
         of loaded objects, and call the appropriate callback when it's

+ 3 - 1
tests/showbase/test_Loader.py

@@ -6,7 +6,9 @@ import sys
 
 
 @pytest.fixture
 @pytest.fixture
 def loader():
 def loader():
-    return Loader(base=None)
+    loader = Loader(base=None)
+    yield loader
+    loader.destroy()
 
 
 
 
 @pytest.fixture
 @pytest.fixture