Просмотр исходного кода

Merge branch 'master' into webgl-port

rdb 2 лет назад
Родитель
Сommit
3826b7df44
100 измененных файлов с 3240 добавлено и 4016 удалено
  1. 12 58
      .github/workflows/ci.yml
  2. 23 0
      .github/workflows/mypy.yml
  3. 3 1
      .gitignore
  4. 4 0
      BACKERS.md
  5. 8 0
      CMakeLists.txt
  6. 3 3
      contrib/src/panda3dtoolsgui/Panda3DToolsGUI.py
  7. 7 16
      direct/src/actor/Actor.py
  8. 3 6
      direct/src/cluster/ClusterClient.py
  9. 3 7
      direct/src/cluster/ClusterServer.py
  10. 438 312
      direct/src/dcparser/dcParser.cxx.prebuilt
  11. 61 45
      direct/src/dcparser/dcParser.h.prebuilt
  12. 5 1
      direct/src/dcparser/dcParser.yxx
  13. 1 1
      direct/src/directbase/DirectStart.py
  14. 1 1
      direct/src/directbase/TestStart.py
  15. 1 1
      direct/src/directbase/ThreeUpStart.py
  16. 6 2
      direct/src/directdevices/DirectJoybox.py
  17. 4 4
      direct/src/directnotify/RotatingLog.py
  18. 1 3
      direct/src/directtools/DirectLights.py
  19. 39 4
      direct/src/dist/FreezeTool.py
  20. 18 7
      direct/src/dist/commands.py
  21. 3 1
      direct/src/distributed/AsyncRequest.py
  22. 0 38
      direct/src/distributed/CRDataCache.py
  23. 7 0
      direct/src/distributed/CachedDOData.py
  24. 3 0
      direct/src/distributed/ClientRepositoryBase.py
  25. 2 2
      direct/src/distributed/DistributedObjectAI.py
  26. 1 1
      direct/src/distributed/DistributedObjectUD.py
  27. 2 8
      direct/src/distributed/DoCollectionManager.py
  28. 5 4
      direct/src/distributed/DoInterestManager.py
  29. 4 2
      direct/src/distributed/GridParent.py
  30. 139 137
      direct/src/distributed/MsgTypes.py
  31. 15 18
      direct/src/distributed/MsgTypesCMU.py
  32. 3 1
      direct/src/fsm/ClassicFSM.py
  33. 3 1
      direct/src/fsm/State.py
  34. 4 153
      direct/src/fsm/StatePush.py
  35. 3 1
      direct/src/gui/DirectDialog.py
  36. 4 4
      direct/src/gui/DirectGuiBase.py
  37. 1 1
      direct/src/gui/DirectGuiGlobals.py
  38. 3 1
      direct/src/interval/FunctionInterval.py
  39. 6 6
      direct/src/interval/IntervalTest.py
  40. 1 3
      direct/src/leveleditor/HotKeyUI.py
  41. 1 1
      direct/src/leveleditor/LevelEditorStart.py
  42. 2 1
      direct/src/leveleditor/testData.py
  43. 3 1
      direct/src/motiontrail/MotionTrail.py
  44. 0 0
      direct/src/physics/__init__.py
  45. 1 3
      direct/src/showbase/BulletinBoard.py
  46. 4 3
      direct/src/showbase/ContainerLeakDetector.py
  47. 7 10
      direct/src/showbase/ContainerReport.py
  48. 0 144
      direct/src/showbase/CountedResource.py
  49. 1 1
      direct/src/showbase/DConfig.py
  50. 3 29
      direct/src/showbase/DistancePhasedNode.py
  51. 5 14
      direct/src/showbase/ExceptionVarDump.py
  52. 1 2
      direct/src/showbase/GarbageReport.py
  53. 1 3
      direct/src/showbase/JobManager.py
  54. 4 12
      direct/src/showbase/Messenger.py
  55. 3 11
      direct/src/showbase/ObjectPool.py
  56. 4 2
      direct/src/showbase/ObjectReport.py
  57. 1 3
      direct/src/showbase/OnScreenDebug.py
  58. 54 98
      direct/src/showbase/PythonUtil.py
  59. 5 3
      direct/src/showbase/ShowBase.py
  60. 4 2
      direct/src/showbase/ShowBaseGlobal.py
  61. 1 1
      direct/src/showbase/VerboseImport.py
  62. 2 2
      direct/src/showutil/TexMemWatcher.py
  63. 2 2
      direct/src/stdpy/pickle.py
  64. 3 111
      direct/src/stdpy/threading.py
  65. 1 90
      direct/src/stdpy/threading2.py
  66. 1 527
      direct/src/task/Task.py
  67. 1 2
      direct/src/tkpanels/FSMInspector.py
  68. 2 6
      direct/src/tkpanels/Inspector.py
  69. 3 3
      direct/src/tkpanels/NotifyPanel.py
  70. 2 24
      direct/src/tkpanels/ParticlePanel.py
  71. 0 8
      direct/src/tkpanels/Placer.py
  72. 1 3
      direct/src/tkpanels/TaskManagerPanel.py
  73. 2 6
      direct/src/tkwidgets/AppShell.py
  74. 0 15
      direct/src/tkwidgets/Dial.py
  75. 0 42
      direct/src/tkwidgets/EntryScale.py
  76. 0 39
      direct/src/tkwidgets/Floater.py
  77. 0 14
      direct/src/tkwidgets/VectorWidgets.py
  78. 1 2
      direct/src/tkwidgets/WidgetPropertiesDialog.py
  79. 4 2
      direct/src/wxwidgets/ViewPort.py
  80. 2 1
      direct/src/wxwidgets/WxPandaStart.py
  81. 1 1
      direct/src/wxwidgets/WxPandaWindow.py
  82. 200 0
      doc/CODING_STYLE.md
  83. 16 1
      dtool/CompilerFlags.cmake
  84. 3 1
      dtool/Package.cmake
  85. 1884 1881
      dtool/src/cppparser/cppBison.cxx.prebuilt
  86. 10 1
      dtool/src/cppparser/cppBison.yxx
  87. 1 0
      dtool/src/dtoolbase/dtoolbase.h
  88. 17 1
      dtool/src/dtoolbase/typeHandle.cxx
  89. 2 1
      dtool/src/dtoolbase/typeHandle.h
  90. 20 1
      dtool/src/dtoolbase/typeHandle_ext.cxx
  91. 3 2
      dtool/src/dtoolbase/typeRegistry.cxx
  92. 6 1
      dtool/src/dtoolbase/typeRegistry.h
  93. 18 1
      dtool/src/dtoolbase/typeRegistryNode.I
  94. 27 7
      dtool/src/dtoolbase/typeRegistryNode.cxx
  95. 8 3
      dtool/src/dtoolbase/typeRegistryNode.h
  96. 4 4
      dtool/src/dtoolutil/executionEnvironment.cxx
  97. 17 8
      dtool/src/dtoolutil/filename.cxx
  98. 5 0
      dtool/src/dtoolutil/filename.h
  99. 15 5
      dtool/src/interrogate/functionRemap.cxx
  100. 1 0
      dtool/src/interrogate/functionRemap.h

+ 12 - 58
.github/workflows/ci.yml

@@ -40,7 +40,7 @@ jobs:
           eigen: NO
 
         - profile: macos-eigen-coverage-unity-xcode
-          os: macOS-10.15
+          os: macOS-11
           config: Coverage
           unity: YES
           generator: Xcode
@@ -50,7 +50,7 @@ jobs:
           eigen: YES
 
         - profile: macos-nometa-standard-makefile
-          os: macOS-10.15
+          os: macOS-11
           config: Standard
           unity: NO
           generator: Unix Makefiles
@@ -87,7 +87,7 @@ jobs:
         fetch-depth: 10
 
     - name: Self-destruct makepanda
-      run: python makepanda/selfdestruct.py --yes
+      run: python3 makepanda/selfdestruct.py --yes
 
     - name: Install dependencies (macOS)
       if: runner.os == 'macOS'
@@ -113,7 +113,7 @@ jobs:
         libeigen3-dev libfreetype6-dev libgl1-mesa-dev libjpeg-dev libode-dev
         libopenal-dev libpng-dev libssl-dev libvorbis-dev libx11-dev
         libxcursor-dev libxrandr-dev nvidia-cg-toolkit zlib1g-dev
-        python3-setuptools
+        python3-setuptools python3-tk
 
     - name: Cache dependencies (Windows)
       if: runner.os == 'Windows'
@@ -180,38 +180,6 @@ jobs:
       run: cmake --build . --config ${{ matrix.config }} --parallel 4
       # END A
 
-    - name: Setup Python (Python 3.7)
-      if: contains(matrix.python, 'YES')
-      uses: actions/setup-python@v4
-      with:
-        python-version: '3.7'
-    - name: Configure (Python 3.7)
-      if: contains(matrix.python, 'YES')
-      working-directory: build
-      shell: bash
-      run: >
-        cmake -DWANT_PYTHON_VERSION=3.7 -DHAVE_PYTHON=YES
-        -DPython_FIND_REGISTRY=NEVER -DPython_ROOT="$pythonLocation" .
-    - name: Build (Python 3.7)
-      if: contains(matrix.python, 'YES')
-      # BEGIN A
-      working-directory: build
-      run: cmake --build . --config ${{ matrix.config }} --parallel 4
-      # END A
-    - name: Test (Python 3.7)
-      # BEGIN B
-      if: contains(matrix.python, 'YES')
-      working-directory: build
-      shell: bash
-      env:
-        PYTHONPATH: ${{ matrix.config }}
-      run: |
-        PYTHON_EXECUTABLE=$(grep 'Python_EXECUTABLE:' CMakeCache.txt | sed 's/.*=//')
-        $PYTHON_EXECUTABLE -m pip install pytest pytest-cov
-        export COVERAGE_FILE=.coverage.$RANDOM LLVM_PROFILE_FILE=$PWD/pid-%p.profraw
-        $PYTHON_EXECUTABLE -m pytest ../tests --cov=.
-      # END B
-
     - name: Setup Python (Python 3.8)
       if: contains(matrix.python, 'YES')
       uses: actions/setup-python@v4
@@ -239,7 +207,7 @@ jobs:
         PYTHONPATH: ${{ matrix.config }}
       run: |
         PYTHON_EXECUTABLE=$(grep 'Python_EXECUTABLE:' CMakeCache.txt | sed 's/.*=//')
-        $PYTHON_EXECUTABLE -m pip install pytest pytest-cov
+        $PYTHON_EXECUTABLE -m pip install -r ../requirements-test.txt
         export COVERAGE_FILE=.coverage.$RANDOM LLVM_PROFILE_FILE=$PWD/pid-%p.profraw
         $PYTHON_EXECUTABLE -m pytest ../tests --cov=.
       # END B
@@ -271,7 +239,7 @@ jobs:
         PYTHONPATH: ${{ matrix.config }}
       run: |
         PYTHON_EXECUTABLE=$(grep 'Python_EXECUTABLE:' CMakeCache.txt | sed 's/.*=//')
-        $PYTHON_EXECUTABLE -m pip install pytest pytest-cov
+        $PYTHON_EXECUTABLE -m pip install -r ../requirements-test.txt
         export COVERAGE_FILE=.coverage.$RANDOM LLVM_PROFILE_FILE=$PWD/pid-%p.profraw
         $PYTHON_EXECUTABLE -m pytest ../tests --cov=.
       # END B
@@ -303,7 +271,7 @@ jobs:
         PYTHONPATH: ${{ matrix.config }}
       run: |
         PYTHON_EXECUTABLE=$(grep 'Python_EXECUTABLE:' CMakeCache.txt | sed 's/.*=//')
-        $PYTHON_EXECUTABLE -m pip install pytest pytest-cov
+        $PYTHON_EXECUTABLE -m pip install -r ../requirements-test.txt
         export COVERAGE_FILE=.coverage.$RANDOM LLVM_PROFILE_FILE=$PWD/pid-%p.profraw
         $PYTHON_EXECUTABLE -m pytest ../tests --cov=.
       # END B
@@ -335,7 +303,7 @@ jobs:
         PYTHONPATH: ${{ matrix.config }}
       run: |
         PYTHON_EXECUTABLE=$(grep 'Python_EXECUTABLE:' CMakeCache.txt | sed 's/.*=//')
-        $PYTHON_EXECUTABLE -m pip install pytest pytest-cov
+        $PYTHON_EXECUTABLE -m pip install -r ../requirements-test.txt
         export COVERAGE_FILE=.coverage.$RANDOM LLVM_PROFILE_FILE=$PWD/pid-%p.profraw
         $PYTHON_EXECUTABLE -m pytest ../tests --cov=.
       # END B
@@ -398,7 +366,7 @@ jobs:
     - name: Test Python 3.11
       shell: bash
       run: |
-        python -m pip install pytest
+        python -m pip install -r requirements-test.txt
         PYTHONPATH=built LD_LIBRARY_PATH=built/lib DYLD_LIBRARY_PATH=built/lib python -m pytest
 
     - name: Set up Python 3.10
@@ -412,7 +380,7 @@ jobs:
     - name: Test Python 3.10
       shell: bash
       run: |
-        python -m pip install pytest
+        python -m pip install -r requirements-test.txt
         PYTHONPATH=built LD_LIBRARY_PATH=built/lib DYLD_LIBRARY_PATH=built/lib python -m pytest
 
     - name: Set up Python 3.9
@@ -426,7 +394,7 @@ jobs:
     - name: Test Python 3.9
       shell: bash
       run: |
-        python -m pip install pytest
+        python -m pip install -r requirements-test.txt
         PYTHONPATH=built LD_LIBRARY_PATH=built/lib DYLD_LIBRARY_PATH=built/lib python -m pytest
 
     - name: Set up Python 3.8
@@ -440,21 +408,7 @@ jobs:
     - name: Test Python 3.8
       shell: bash
       run: |
-        python -m pip install pytest
-        PYTHONPATH=built LD_LIBRARY_PATH=built/lib DYLD_LIBRARY_PATH=built/lib python -m pytest
-
-    - name: Set up Python 3.7
-      uses: actions/setup-python@v4
-      with:
-        python-version: '3.7'
-    - name: Build Python 3.7
-      shell: bash
-      run: |
-        python makepanda/makepanda.py --git-commit=${{github.sha}} --outputdir=built --everything --no-eigen --python-incdir="$pythonLocation/include" --python-libdir="$pythonLocation/lib" --verbose --threads=4 --windows-sdk=10
-    - name: Test Python 3.7
-      shell: bash
-      run: |
-        python -m pip install pytest
+        python -m pip install -r requirements-test.txt
         PYTHONPATH=built LD_LIBRARY_PATH=built/lib DYLD_LIBRARY_PATH=built/lib python -m pytest
 
     - name: Make installer

+ 23 - 0
.github/workflows/mypy.yml

@@ -0,0 +1,23 @@
+name: Run Mypy
+on: [push, pull_request]
+
+jobs:
+  mypy:
+    runs-on: ${{ matrix.os }}
+    strategy:
+      matrix:
+        os: [ubuntu-latest, macos-latest, windows-latest]
+        python-version: ['3.8', '3.11']
+      fail-fast: false
+    steps:
+      - uses: actions/checkout@v3
+      - name: Set up Python ${{ matrix.python-version }}
+        uses: actions/setup-python@v4
+        with:
+          python-version: ${{ matrix.python-version }}
+      - name: Install dependencies
+        run: |
+          python -m pip install --upgrade pip
+          pip install mypy==1.4.0
+      - name: Run mypy on direct
+        run: python tests/run_mypy.py

+ 3 - 1
.gitignore

@@ -17,6 +17,7 @@ vgcore.*
 *.save.1
 *.sublime-workspace
 .vscode/
+.idea/
 
 # Temporary build files
 /_vfsimporter.*
@@ -51,9 +52,10 @@ cmake_install.cmake
 install_manifest.txt
 CTestTestfile.cmake
 
-# Windows
+# Operating system
 Thumbs.db
 ehthumbs.db
+.DS_Store
 
 # macOS
 .DS_Store

+ 4 - 0
BACKERS.md

@@ -17,6 +17,8 @@ This is a list of all the people who are contributing financially to Panda3D.  I
 * Max Voss
 * Hawkheart
 * Veronica
+* Cody Sevier
+* Marek Alexa
 
 ## Enthusiasts
 
@@ -28,6 +30,8 @@ This is a list of all the people who are contributing financially to Panda3D.  I
 * SureBet
 * Gyedo Jeon
 * GameDev JONI
+* Max Rodriguez
+* Jethro Schoppenhorst
 
 ## Backers
 

+ 8 - 0
CMakeLists.txt

@@ -24,6 +24,9 @@ endif()
 
 # Set defaults for macOS, must be before project().
 if(APPLE)
+  # Needed for enable_language(OBJCXX)
+  cmake_minimum_required(VERSION 3.16)
+
   set(CMAKE_OSX_DEPLOYMENT_TARGET "10.9" CACHE STRING "Minimum macOS version to target")
   set(CMAKE_XCODE_ATTRIBUTE_CLANG_CXX_LIBRARY "libc++")
 
@@ -46,6 +49,11 @@ project(Panda3D VERSION ${_version})
 unset(_version)
 unset(_s)
 
+if(APPLE)
+  # Allows separating out C++ flags from ObjC++ flags
+  enable_language(OBJCXX)
+endif()
+
 # Determine the possible build types.  Must be *after* calling project().
 set(_configs Standard Release RelWithDebInfo Debug MinSizeRel)
 if(CMAKE_CXX_COMPILER_ID MATCHES "(AppleClang|Clang|GCC)")

+ 3 - 3
contrib/src/panda3dtoolsgui/Panda3DToolsGUI.py

@@ -1905,7 +1905,7 @@ class main(wx.Frame):
                             self.txaExtraLines.append(line)
                 txafile.close()
             except:
-                print "Error opening .txa file!"
+                print("Error opening .txa file!")
             self.palettize_saveTxaTxt.SetValue(os.path.join(dirname + os.sep , filename))
         dlg.Destroy()
 
@@ -2705,7 +2705,7 @@ class main(wx.Frame):
             selectedItemIndex = int(self.batchTree.GetItemText(selectedItemId).split()[0])-1
             batchItem = self.batchList[selectedItemIndex]
 
-            print '\n'+self.BuildCommand(batchItem)
+            print('\n'+self.BuildCommand(batchItem))
 
             if (batchItem['cmd'].count('maya2egg')):
                 # Display Maya2Egg Tool Panel
@@ -2840,7 +2840,7 @@ class main(wx.Frame):
                                 self.txaExtraLines.append(line)
                     txafile.close()
                 except:
-                    print "Error opening .txa file!"
+                    print("Error opening .txa file!")
 
             self.batchItemNameTxt.SetValue(batchItem['label'])
 

+ 7 - 16
direct/src/actor/Actor.py

@@ -280,9 +280,7 @@ class Actor(DirectObject, NodePath):
                         self.setLODNode(node = lodNode)
                         # preserve numerical order for lod's
                         # this will make it easier to set ranges
-                        sortedKeys = list(models.keys())
-                        sortedKeys.sort()
-                        for lodName in sortedKeys:
+                        for lodName in sorted(models):
                             # make a node under the LOD switch
                             # for each lod (just because!)
                             self.addLOD(str(lodName))
@@ -302,9 +300,7 @@ class Actor(DirectObject, NodePath):
                         # it is a single part actor w/LOD
                         self.setLODNode(node = lodNode)
                         # preserve order of LOD's
-                        sortedKeys = list(models.keys())
-                        sortedKeys.sort()
-                        for lodName in sortedKeys:
+                        for lodName in sorted(models):
                             self.addLOD(str(lodName))
                             # pass in dictionary of parts
                             self.loadModel(models[lodName], lodName=lodName,
@@ -323,9 +319,7 @@ class Actor(DirectObject, NodePath):
                         if isinstance(models, dict):
                             if isinstance(models[next(iter(models))], dict):
                                 # then we have a multi-part w/ LOD
-                                sortedKeys = list(models.keys())
-                                sortedKeys.sort()
-                                for lodName in sortedKeys:
+                                for lodName in sorted(models):
                                     # iterate over both dicts
                                     for partName in anims:
                                         self.loadAnims(
@@ -336,9 +330,7 @@ class Actor(DirectObject, NodePath):
                                     self.loadAnims(anims[partName], partName)
                     elif isinstance(models, dict):
                         # then we have single-part w/ LOD
-                        sortedKeys = list(models.keys())
-                        sortedKeys.sort()
-                        for lodName in sortedKeys:
+                        for lodName in sorted(models):
                             self.loadAnims(anims, lodName=lodName)
                     else:
                         # else it is single-part w/o LOD
@@ -603,9 +595,6 @@ class Actor(DirectObject, NodePath):
         return bundles
 
     def __updateSortedLODNames(self):
-        # Cache the sorted LOD names so we don't have to grab them
-        # and sort them every time somebody asks for the list
-        self.__sortedLODNames = list(self.__partBundleDict.keys())
         # Reverse sort the doing a string->int
         def sortKey(x):
             if not str(x).isdigit():
@@ -622,7 +611,9 @@ class Actor(DirectObject, NodePath):
             else:
                 return int(x)
 
-        self.__sortedLODNames.sort(key=sortKey, reverse=True)
+        # Cache the sorted LOD names so we don't have to grab them
+        # and sort them every time somebody asks for the list
+        self.__sortedLODNames = sorted(self.__partBundleDict, key=sortKey, reverse=True)
 
     def getLODNames(self):
         """

+ 3 - 6
direct/src/cluster/ClusterClient.py

@@ -169,12 +169,9 @@ class ClusterClient(DirectObject.DirectObject):
             self.serverList[server].sendNamedMovementDone()
 
     def redoSortedPriorities(self):
-        self.sortedControlMappings = []
-        for key in self.controlMappings:
-            self.sortedControlMappings.append([self.controlPriorities[key],
-                                               key])
-
-        self.sortedControlMappings.sort()
+        self.sortedControlMappings = sorted(
+            [self.controlPriorities[key], key] for key in self.controlMappings
+        )
 
     def moveObject(self, nodePath, object, serverList, offset, hasColor = True):
         self.notify.debug('moving object '+object)

+ 3 - 7
direct/src/cluster/ClusterServer.py

@@ -137,13 +137,9 @@ class ClusterServer(DirectObject.DirectObject):
             self.objectMappings.pop(name)
 
     def redoSortedPriorities(self):
-
-        self.sortedControlMappings = []
-        for key in self.objectMappings:
-            self.sortedControlMappings.append([self.controlPriorities[key],
-                                               key])
-
-        self.sortedControlMappings.sort()
+        self.sortedControlMappings = sorted(
+            [self.controlPriorities[key], key] for key in self.objectMappings
+        )
 
     def addControlMapping(self, objectName, controlledName, offset = None,
                           priority = 0):

Разница между файлами не показана из-за своего большого размера
+ 438 - 312
direct/src/dcparser/dcParser.cxx.prebuilt


+ 61 - 45
direct/src/dcparser/dcParser.h.prebuilt

@@ -1,8 +1,9 @@
-/* A Bison parser, made by GNU Bison 3.1.  */
+/* A Bison parser, made by GNU Bison 3.8.2.  */
 
 /* Bison interface for Yacc-like parsers in C
 
-   Copyright (C) 1984, 1989-1990, 2000-2015, 2018 Free Software Foundation, Inc.
+   Copyright (C) 1984, 1989-1990, 2000-2015, 2018-2021 Free Software Foundation,
+   Inc.
 
    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
@@ -15,7 +16,7 @@
    GNU General Public License for more details.
 
    You should have received a copy of the GNU General Public License
-   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
 
 /* As a special exception, you may create a larger work that contains
    part or all of the Bison parser skeleton and distribute that work
@@ -30,6 +31,10 @@
    This special exception was added by the Free Software Foundation in
    version 2.2 of Bison.  */
 
+/* DO NOT RELY ON FEATURES THAT ARE NOT DOCUMENTED in the manual,
+   especially those whose name start with YY_ or yy_.  They are
+   private implementation details that can be changed or removed.  */
+
 #ifndef YY_DCYY_BUILT_TMP_DCPARSER_YXX_H_INCLUDED
 # define YY_DCYY_BUILT_TMP_DCPARSER_YXX_H_INCLUDED
 /* Debug traces.  */
@@ -40,54 +45,63 @@
 extern int dcyydebug;
 #endif
 
-/* Token type.  */
+/* Token kinds.  */
 #ifndef YYTOKENTYPE
 # define YYTOKENTYPE
   enum yytokentype
   {
-    UNSIGNED_INTEGER = 258,
-    SIGNED_INTEGER = 259,
-    REAL = 260,
-    STRING = 261,
-    IDENTIFIER = 262,
-    HEX_STRING = 263,
-    KEYWORD = 264,
-    KW_DCLASS = 265,
-    KW_STRUCT = 266,
-    KW_FROM = 267,
-    KW_IMPORT = 268,
-    KW_TYPEDEF = 269,
-    KW_KEYWORD = 270,
-    KW_SWITCH = 271,
-    KW_CASE = 272,
-    KW_DEFAULT = 273,
-    KW_BREAK = 274,
-    KW_INT8 = 275,
-    KW_INT16 = 276,
-    KW_INT32 = 277,
-    KW_INT64 = 278,
-    KW_UINT8 = 279,
-    KW_UINT16 = 280,
-    KW_UINT32 = 281,
-    KW_UINT64 = 282,
-    KW_FLOAT64 = 283,
-    KW_STRING = 284,
-    KW_BLOB = 285,
-    KW_BLOB32 = 286,
-    KW_INT8ARRAY = 287,
-    KW_INT16ARRAY = 288,
-    KW_INT32ARRAY = 289,
-    KW_UINT8ARRAY = 290,
-    KW_UINT16ARRAY = 291,
-    KW_UINT32ARRAY = 292,
-    KW_UINT32UINT8ARRAY = 293,
-    KW_CHAR = 294,
-    START_DC = 295,
-    START_PARAMETER_VALUE = 296,
-    START_PARAMETER_DESCRIPTION = 297
+    YYEMPTY = -2,
+    YYEOF = 0,                     /* "end of file"  */
+    YYerror = 256,                 /* error  */
+    YYUNDEF = 257,                 /* "invalid token"  */
+    UNSIGNED_INTEGER = 258,        /* UNSIGNED_INTEGER  */
+    SIGNED_INTEGER = 259,          /* SIGNED_INTEGER  */
+    REAL = 260,                    /* REAL  */
+    STRING = 261,                  /* STRING  */
+    IDENTIFIER = 262,              /* IDENTIFIER  */
+    HEX_STRING = 263,              /* HEX_STRING  */
+    KEYWORD = 264,                 /* KEYWORD  */
+    KW_DCLASS = 265,               /* KW_DCLASS  */
+    KW_STRUCT = 266,               /* KW_STRUCT  */
+    KW_FROM = 267,                 /* KW_FROM  */
+    KW_IMPORT = 268,               /* KW_IMPORT  */
+    KW_TYPEDEF = 269,              /* KW_TYPEDEF  */
+    KW_KEYWORD = 270,              /* KW_KEYWORD  */
+    KW_SWITCH = 271,               /* KW_SWITCH  */
+    KW_CASE = 272,                 /* KW_CASE  */
+    KW_DEFAULT = 273,              /* KW_DEFAULT  */
+    KW_BREAK = 274,                /* KW_BREAK  */
+    KW_INT8 = 275,                 /* KW_INT8  */
+    KW_INT16 = 276,                /* KW_INT16  */
+    KW_INT32 = 277,                /* KW_INT32  */
+    KW_INT64 = 278,                /* KW_INT64  */
+    KW_UINT8 = 279,                /* KW_UINT8  */
+    KW_UINT16 = 280,               /* KW_UINT16  */
+    KW_UINT32 = 281,               /* KW_UINT32  */
+    KW_UINT64 = 282,               /* KW_UINT64  */
+    KW_FLOAT64 = 283,              /* KW_FLOAT64  */
+    KW_STRING = 284,               /* KW_STRING  */
+    KW_BLOB = 285,                 /* KW_BLOB  */
+    KW_BLOB32 = 286,               /* KW_BLOB32  */
+    KW_INT8ARRAY = 287,            /* KW_INT8ARRAY  */
+    KW_INT16ARRAY = 288,           /* KW_INT16ARRAY  */
+    KW_INT32ARRAY = 289,           /* KW_INT32ARRAY  */
+    KW_UINT8ARRAY = 290,           /* KW_UINT8ARRAY  */
+    KW_UINT16ARRAY = 291,          /* KW_UINT16ARRAY  */
+    KW_UINT32ARRAY = 292,          /* KW_UINT32ARRAY  */
+    KW_UINT32UINT8ARRAY = 293,     /* KW_UINT32UINT8ARRAY  */
+    KW_CHAR = 294,                 /* KW_CHAR  */
+    START_DC = 295,                /* START_DC  */
+    START_PARAMETER_VALUE = 296,   /* START_PARAMETER_VALUE  */
+    START_PARAMETER_DESCRIPTION = 297 /* START_PARAMETER_DESCRIPTION  */
   };
+  typedef enum yytokentype yytoken_kind_t;
 #endif
-/* Tokens.  */
+/* Token kinds.  */
+#define YYEMPTY -2
+#define YYEOF 0
+#define YYerror 256
+#define YYUNDEF 257
 #define UNSIGNED_INTEGER 258
 #define SIGNED_INTEGER 259
 #define REAL 260
@@ -134,6 +148,8 @@ extern int dcyydebug;
 
 extern YYSTYPE dcyylval;
 
+
 int dcyyparse (void);
 
+
 #endif /* !YY_DCYY_BUILT_TMP_DCPARSER_YXX_H_INCLUDED  */

+ 5 - 1
direct/src/dcparser/dcParser.yxx

@@ -297,6 +297,7 @@ dclass_or_struct:
 dclass:
         KW_DCLASS optional_name
 {
+  $<u.dclass>$ = current_class;
   current_class = new DCClass(dc_file, $2, false, false);
 }
         dclass_derivation '{' dclass_fields '}'
@@ -401,6 +402,7 @@ dclass_field:
 struct:
         KW_STRUCT optional_name
 {
+  $<u.dclass>$ = current_class;
   current_class = new DCClass(dc_file, $2, true, false);
 }
         struct_derivation '{' struct_fields '}'
@@ -488,6 +490,7 @@ struct_field:
 atomic_field:
         optional_name '('
 {
+  $<u.atomic>$ = current_atomic;
   if (current_class == nullptr) {
     yyerror("Cannot define a method outside of a struct or class.");
     DCClass *temp_class = new DCClass(dc_file, "temp", false, false);  // memory leak.
@@ -1243,12 +1246,13 @@ optional_name:
 switch:
         KW_SWITCH optional_name '(' parameter_or_atomic ')' '{'
 {
+  $<u.dswitch>$ = current_switch;
   current_switch = new DCSwitch($2, $4);
 }
         switch_fields '}'
 {
   $$ = current_switch;
-  current_switch = (DCSwitch *)$<u.parameter>7;
+  current_switch = $<u.dswitch>7;
 }
         ;
 

+ 1 - 1
direct/src/directbase/DirectStart.py

@@ -15,7 +15,7 @@ to and may be replaced by the following code:
    base = ShowBase()
 """
 
-__all__ = []
+__all__ = ()
 
 if __debug__:
     print('Using deprecated DirectStart interface.')

+ 1 - 1
direct/src/directbase/TestStart.py

@@ -6,7 +6,7 @@ from direct.showbase import ShowBase
 base = ShowBase.ShowBase()
 
 # Put an axis in the world:
-base.loader.loadModel("models/misc/xyzAxis").reparentTo(render)
+base.loader.loadModel("models/misc/xyzAxis").reparentTo(base.render)
 
 base.camera.setPosHpr(0, -10.0, 0, 0, 0, 0)
 base.camLens.setFov(52.0)

+ 1 - 1
direct/src/directbase/ThreeUpStart.py

@@ -8,7 +8,7 @@ from direct.showbase import ThreeUpShow
 base = ThreeUpShow.ThreeUpShow()
 
 # Put an axis in the world:
-base.loader.loadModel("models/misc/xyzAxis").reparentTo(render)
+base.loader.loadModel("models/misc/xyzAxis").reparentTo(base.render)
 
 base.camera.setPosHpr(0, -10.0, 0, 0, 0, 0)
 base.camLens.setFov(52.0)

+ 6 - 2
direct/src/directdevices/DirectJoybox.py

@@ -41,8 +41,12 @@ class DirectJoybox(DirectObject):
     xyzMultiplier = 1.0
     hprMultiplier = 1.0
 
-    def __init__(self, device = 'CerealBox', nodePath = base.direct.camera,
-                 headingNP = base.direct.camera):
+    def __init__(self, device = 'CerealBox', nodePath = None, headingNP = None):
+        from direct.showbase.ShowBaseGlobal import base
+        if nodePath is None:
+            nodePath = base.direct.camera
+        if headingNP is None:
+            headingNP = base.direct.camera
         # See if device manager has been initialized
         if base.direct.deviceManager is None:
             base.direct.deviceManager = DirectDeviceManager()

+ 4 - 4
direct/src/directnotify/RotatingLog.py

@@ -55,10 +55,11 @@ class RotatingLog:
         return 0
 
     def filePath(self):
-        dateString=time.strftime("%Y_%m_%d_%H", time.localtime())
+        dateString = time.strftime("%Y_%m_%d_%H", time.localtime())
         for i in range(26):
-            path="%s_%s_%s.log"%(self.path, dateString, chr(i+97))
-            if not os.path.exists(path) or os.stat(path)[6] < self.sizeLimit:
+            limit = self.sizeLimit
+            path = "%s_%s_%s.log" % (self.path, dateString, chr(i+97))
+            if limit is None or not os.path.exists(path) or os.stat(path)[6] < limit:
                 return path
         # Hmm, 26 files are full?  throw the rest in z:
         # Maybe we should clear the self.sizeLimit here... maybe.
@@ -83,7 +84,6 @@ class RotatingLog:
             self.closed = self.file.closed
             self.mode = self.file.mode
             self.name = self.file.name
-            self.softspace = self.file.softspace
             #self.encoding = self.file.encoding # Python 2.3
             #self.newlines = self.file.newlines # Python 2.3, maybe
 

+ 1 - 3
direct/src/directtools/DirectLights.py

@@ -66,9 +66,7 @@ class DirectLights(NodePath):
 
     def getNameList(self):
         # Return a sorted list of all lights in the light dict
-        nameList = [x.getName() for x in self.lightDict.values()]
-        nameList.sort()
-        return nameList
+        return sorted(x.getName() for x in self.lightDict.values())
 
     def create(self, ltype):
         ltype = ltype.lower()

+ 39 - 4
direct/src/dist/FreezeTool.py

@@ -90,6 +90,7 @@ defaultHiddenImports = {
     'scipy.special._ufuncs': ['scipy.special._ufuncs_cxx'],
     'scipy.stats._stats': ['scipy.special.cython_special'],
     'setuptools.monkey': ['setuptools.msvc'],
+    'shapely._geometry_helpers': ['shapely._geos'],
 }
 
 
@@ -370,6 +371,8 @@ extern PyAPI_FUNC(int) PyImport_ExtendInittab(struct _inittab *newtab);
 
 /* Main program */
 
+EXTRA_INIT_FUNC_DECLS
+
 int
 Py_FrozenMain(int argc, char **argv)
 {
@@ -449,6 +452,8 @@ Py_FrozenMain(int argc, char **argv)
     PySys_SetArgv(argc, argv);
 #endif
 
+EXTRA_INIT_FUNC_CALLS
+
     n = PyImport_ImportFrozenModule("__main__");
     if (n == 0)
         Py_FatalError("__main__ not frozen");
@@ -839,6 +844,12 @@ class Freezer:
         # modules.
         self.extras = []
 
+        # This is a list of init functions that must be called after
+        # Py_Initialize(), but before importing __main__.  This is a
+        # tuple of (return type, name).  They should use C calling
+        # convention.
+        self.extraInitFuncs = []
+
         # Set this to true if extension modules should be linked in to
         # the resulting executable.
         self.linkExtensionModules = False
@@ -1135,15 +1146,12 @@ class Freezer:
 
         # Walk through the list in sorted order, so we reach parents
         # before children.
-        names = list(self.modules.items())
-        names.sort()
-
         excludeDict = {}
         implicitParentDict = {}
         includes = []
         autoIncludes = []
         origToNewName = {}
-        for newName, mdef in names:
+        for newName, mdef in sorted(self.modules.items()):
             moduleName = mdef.moduleName
             origToNewName[moduleName] = newName
             if mdef.implicit and '.' in newName:
@@ -1711,6 +1719,18 @@ class Freezer:
 
         if compileToExe:
             code = self.frozenMainCode
+
+            decls = ''
+            calls = ''
+            for func in self.extraInitFuncs:
+                if isinstance(func, str):
+                    func = ('void', func)
+                decls += f'extern {func[0]} {func[1]}();\n'
+                calls += f'    {func[1]}();\n';
+
+            code = code.replace('EXTRA_INIT_FUNC_DECLS', decls)
+            code = code.replace('EXTRA_INIT_FUNC_CALLS', calls)
+
             if self.platform.startswith('win'):
                 code += self.frozenDllMainCode
             initCode = self.mainInitCode % {
@@ -2561,6 +2581,21 @@ class PandaModuleFinder(modulefinder.ModuleFinder):
             else:
                 code = fp.read()
 
+            # Strip out delvewheel patch (see GitHub issue #1492)
+            if isinstance(code, bytes):
+                # Don't look for \n at the end, it may also be \r\n
+                start_marker = b'# start delvewheel patch'
+                end_marker = b'# end delvewheel patch'
+            else:
+                start_marker = '# start delvewheel patch'
+                end_marker = '# end delvewheel patch'
+
+            start = code.find(start_marker)
+            while start >= 0:
+                end = code.find(end_marker, start) + len(end_marker)
+                code = code[:start] + code[end:]
+                start = code.find(start_marker)
+
             code += b'\n' if isinstance(code, bytes) else '\n'
             co = compile(code, pathname, 'exec', optimize=self.optimize)
         elif type == imp.PY_COMPILED:

+ 18 - 7
direct/src/dist/commands.py

@@ -634,7 +634,20 @@ class build_apps(setuptools.Command):
         for index in self.pypi_extra_indexes:
             pip_args += ['--extra-index-url', index]
 
-        subprocess.check_call([sys.executable, '-m', 'pip'] + pip_args)
+        try:
+            subprocess.check_call([sys.executable, '-m', 'pip'] + pip_args)
+        except:
+            # Display a more helpful message for these common issues.
+            if platform.startswith('manylinux2010_') and sys.version_info >= (3, 11):
+                new_platform = platform.replace('manylinux2010_', 'manylinux2014_')
+                self.announce('This error likely occurs because {} is not a supported target as of Python 3.11.\nChange the target platform to {} instead.'.format(platform, new_platform), distutils.log.ERROR)
+            elif platform.startswith('manylinux1_') and sys.version_info >= (3, 10):
+                new_platform = platform.replace('manylinux1_', 'manylinux2014_')
+                self.announce('This error likely occurs because {} is not a supported target as of Python 3.10.\nChange the target platform to {} instead.'.format(platform, new_platform), distutils.log.ERROR)
+            elif platform.startswith('macosx_10_6_') and sys.version_info >= (3, 8):
+                new_platform = platform.replace('macosx_10_6_', 'macosx_10_9_')
+                self.announce('This error likely occurs because {} is not a supported target as of Python 3.8.\nChange the target platform to {} instead.'.format(platform, new_platform), distutils.log.ERROR)
+            raise
 
         # Return a list of paths to the downloaded whls
         return [
@@ -846,16 +859,12 @@ class build_apps(setuptools.Command):
             libdir = os.path.dirname(dtool_fn.to_os_specific())
             etcdir = os.path.join(libdir, '..', 'etc')
 
-            etcfiles = os.listdir(etcdir)
-            etcfiles.sort(reverse=True)
-            for fn in etcfiles:
+            for fn in sorted(os.listdir(etcdir), reverse=True):
                 if fn.lower().endswith('.prc'):
                     with open(os.path.join(etcdir, fn)) as f:
                         prcstring += f.read()
         else:
-            etcfiles = [i for i in p3dwhl.namelist() if i.endswith('.prc')]
-            etcfiles.sort(reverse=True)
-            for fn in etcfiles:
+            for fn in sorted((i for i in p3dwhl.namelist() if i.endswith('.prc')), reverse=True):
                 with p3dwhl.open(fn) as f:
                     prcstring += f.read().decode('utf8')
 
@@ -1689,6 +1698,8 @@ class bdist_apps(setuptools.Command):
             setattr(self, opt, None)
 
     def finalize_options(self):
+        import pkg_resources
+
         # We need to massage the inputs a bit in case they came from a
         # setup.cfg file.
         self.installers = {

+ 3 - 1
direct/src/distributed/AsyncRequest.py

@@ -1,3 +1,5 @@
+from __future__ import annotations
+
 #from otp.ai.AIBaseGlobal import *
 from direct.directnotify import DirectNotifyGlobal
 from direct.showbase.DirectObject import DirectObject
@@ -39,7 +41,7 @@ class AsyncRequest(DirectObject):
     will be called again when the new self.neededObjects is complete.  You
     may repeat this as necessary.
     """
-    _asyncRequests = {}
+    _asyncRequests: dict[int, AsyncRequest] = {}
 
     notify = DirectNotifyGlobal.directNotify.newCategory('AsyncRequest')
 

+ 0 - 38
direct/src/distributed/CRDataCache.py

@@ -76,41 +76,3 @@ if __debug__:
         def flush(self):
             CachedDOData.flush(self)
             self._flushed = True
-
-    dc = CRDataCache()
-    dc._startMemLeakCheck()
-
-    cd = CachedDOData()
-    cd.foo = 34
-    dc.setCachedData(1, 'testCachedData', cd)
-    del cd
-    cd = CachedDOData()
-    cd.bar = 45
-    dc.setCachedData(1, 'testCachedData2', cd)
-    del cd
-    assert dc.hasCachedData(1)
-    assert dc.hasCachedData(1)
-    assert not dc.hasCachedData(2)
-    # data is dict of dataName->data
-    data = dc.popCachedData(1)
-    assert len(data) == 2
-    assert 'testCachedData' in data
-    assert 'testCachedData2' in data
-    assert data['testCachedData'].foo == 34
-    assert data['testCachedData2'].bar == 45
-    for cd in data.values():
-        cd.flush()
-    del data
-    dc._checkMemLeaks()
-
-    cd = CachedDOData()
-    cd.bar = 1234
-    dc.setCachedData(43, 'testCachedData2', cd)
-    del cd
-    assert dc.hasCachedData(43)
-    dc.flush()
-    dc._checkMemLeaks()
-
-    dc._stopMemLeakCheck()
-    dc.destroy()
-    del dc

+ 7 - 0
direct/src/distributed/CachedDOData.py

@@ -20,3 +20,10 @@ class CachedDOData:
         # override and destroy the cached data
         # cached data is typically created by the DistributedObject and destroyed here
         pass
+
+    # These next two methods tell mypy to allow arbitrary attributes.
+    def __getattribute__(self, name: str):
+        return object.__getattribute__(self, name)
+
+    def __setattr__(self, name: str, value) -> None:
+        object.__setattr__(self, name, value)

+ 3 - 0
direct/src/distributed/ClientRepositoryBase.py

@@ -469,6 +469,9 @@ class ClientRepositoryBase(ConnectionRepository):
                     f"Asked to update non-existent DistObj {doId} and failed to find it")
 
     def __doUpdateOwner(self, doId, di):
+        if not self.hasOwnerView():
+            return False
+
         ovObj = self.doId2ownerView.get(doId)
         if ovObj:
             odg = Datagram(di.getDatagram())

+ 2 - 2
direct/src/distributed/DistributedObjectAI.py

@@ -299,7 +299,7 @@ class DistributedObjectAI(DistributedObjectBase):
         # setLocation destroys self._zoneData if we move away to
         # a different zone
         if self._zoneData is None:
-            from otp.ai.AIZoneData import AIZoneData
+            from otp.ai.AIZoneData import AIZoneData  # type: ignore[import]
             self._zoneData = AIZoneData(self.air, self.parentId, self.zoneId)
         return self._zoneData
 
@@ -489,7 +489,7 @@ class DistributedObjectAI(DistributedObjectBase):
         # simultaneously on different lists of avatars, although they
         # should have different names.
 
-        from otp.ai import Barrier
+        from otp.ai import Barrier  # type: ignore[import]
         context = self.__nextBarrierContext
         # We assume the context number is passed as a uint16.
         self.__nextBarrierContext = (self.__nextBarrierContext + 1) & 0xffff

+ 1 - 1
direct/src/distributed/DistributedObjectUD.py

@@ -424,7 +424,7 @@ class DistributedObjectUD(DistributedObjectBase):
         # simultaneously on different lists of avatars, although they
         # should have different names.
 
-        from otp.ai import Barrier
+        from otp.ai import Barrier  # type: ignore[import]
         context = self.__nextBarrierContext
         # We assume the context number is passed as a uint16.
         self.__nextBarrierContext = (self.__nextBarrierContext + 1) & 0xffff

+ 2 - 8
direct/src/distributed/DoCollectionManager.py

@@ -149,10 +149,7 @@ class DoCollectionManager:
             class2count.setdefault(className, 0)
             class2count[className] += 1
         count2classes = invertDictLossless(class2count)
-        counts = list(count2classes.keys())
-        counts.sort()
-        counts.reverse()
-        for count in counts:
+        for count in sorted(count2classes, reverse=True):
             count2classes[count].sort()
             for name in count2classes[count]:
                 print('%s %s' % (count, name))
@@ -166,10 +163,7 @@ class DoCollectionManager:
             class2count.setdefault(className, 0)
             class2count[className] += 1
         count2classes = invertDictLossless(class2count)
-        counts = list(count2classes.keys())
-        counts.sort()
-        counts.reverse()
-        for count in counts:
+        for count in sorted(count2classes, reverse=True):
             count2classes[count].sort()
             for name in count2classes[count]:
                 # print '%s %s' % (count, name)

+ 5 - 4
direct/src/distributed/DoInterestManager.py

@@ -7,6 +7,8 @@ zone, remove interest in that zone.
 p.s. A great deal of this code is just code moved from ClientRepository.py.
 """
 
+from __future__ import annotations
+
 from panda3d.core import ConfigVariableBool
 from .MsgTypes import CLIENT_ADD_INTEREST, CLIENT_ADD_INTEREST_MULTIPLE, CLIENT_REMOVE_INTEREST
 from direct.showbase import DirectObject
@@ -98,9 +100,9 @@ class DoInterestManager(DirectObject.DirectObject):
     _ContextIdSerialNum = 100
     _ContextIdMask = 0x3FFFFFFF # avoid making Python create a long
 
-    _interests = {}
+    _interests: dict[int, InterestState] = {}
     if __debug__:
-        _debug_interestHistory = []
+        _debug_interestHistory: list[tuple] = []
         _debug_maxDescriptionLen = 40
 
     _SerialGen = SerialNumGen()
@@ -512,8 +514,7 @@ class DoInterestManager(DirectObject.DirectObject):
         datagram = PyDatagram()
         # Add message type
         if isinstance(zoneIdList, list):
-            vzl = list(zoneIdList)
-            vzl.sort()
+            vzl = sorted(zoneIdList)
             uniqueElements(vzl)
             datagram.addUint16(CLIENT_ADD_INTEREST_MULTIPLE)
             datagram.addUint32(contextId)

+ 4 - 2
direct/src/distributed/GridParent.py

@@ -1,3 +1,5 @@
+from __future__ import annotations
+
 from panda3d.core import NodePath
 
 #
@@ -13,8 +15,8 @@ from panda3d.core import NodePath
 class GridParent:
 
     # this lets GridParents share CellOrigins
-    GridZone2CellOrigin = {}
-    GridZone2count = {}
+    GridZone2CellOrigin: dict[tuple, NodePath] = {}
+    GridZone2count: dict[tuple, int] = {}
     @staticmethod
     def getCellOrigin(grid, zoneId):
         tup = (grid, zoneId)

+ 139 - 137
direct/src/distributed/MsgTypes.py

@@ -1,143 +1,145 @@
 """MsgTypes module: contains distributed object message types"""
 
+from __future__ import annotations
+
 from direct.showbase.PythonUtil import invertDictLossless
 
-MsgName2Id = {
-    'CLIENT_HELLO':                                  1,
-    'CLIENT_HELLO_RESP':                             2,
-
-    # Sent by the client when it's leaving.
-    'CLIENT_DISCONNECT':                             3,
-
-    # Sent by the server when it is dropping the connection deliberately.
-    'CLIENT_EJECT':                                  4,
-
-    'CLIENT_HEARTBEAT':                              5,
-
-    'CLIENT_OBJECT_SET_FIELD':                       120,
-    'CLIENT_OBJECT_SET_FIELDS':                      121,
-    'CLIENT_OBJECT_LEAVING':                         132,
-    'CLIENT_OBJECT_LEAVING_OWNER':                   161,
-    'CLIENT_ENTER_OBJECT_REQUIRED':                  142,
-    'CLIENT_ENTER_OBJECT_REQUIRED_OTHER':            143,
-    'CLIENT_ENTER_OBJECT_REQUIRED_OWNER':            172,
-    'CLIENT_ENTER_OBJECT_REQUIRED_OTHER_OWNER':      173,
-
-    'CLIENT_DONE_INTEREST_RESP':                     204,
-
-    'CLIENT_ADD_INTEREST':                           200,
-    'CLIENT_ADD_INTEREST_MULTIPLE':                  201,
-    'CLIENT_REMOVE_INTEREST':                        203,
-    'CLIENT_OBJECT_LOCATION':                        140,
-
-
-    # These are sent internally inside the Astron cluster.
-
-    # Message Director control messages:
-    'CONTROL_CHANNEL':                                  1,
-    'CONTROL_ADD_CHANNEL':                              9000,
-    'CONTROL_REMOVE_CHANNEL':                           9001,
-    'CONTROL_ADD_RANGE':                                9002,
-    'CONTROL_REMOVE_RANGE':                             9003,
-    'CONTROL_ADD_POST_REMOVE':                          9010,
-    'CONTROL_CLEAR_POST_REMOVES':                       9011,
-
-    # State Server control messages:
-    'STATESERVER_CREATE_OBJECT_WITH_REQUIRED':          2000,
-    'STATESERVER_CREATE_OBJECT_WITH_REQUIRED_OTHER':    2001,
-    'STATESERVER_DELETE_AI_OBJECTS':                    2009,
-    'STATESERVER_OBJECT_GET_FIELD':                     2010,
-    'STATESERVER_OBJECT_GET_FIELD_RESP':                2011,
-    'STATESERVER_OBJECT_GET_FIELDS':                    2012,
-    'STATESERVER_OBJECT_GET_FIELDS_RESP':               2013,
-    'STATESERVER_OBJECT_GET_ALL':                       2014,
-    'STATESERVER_OBJECT_GET_ALL_RESP':                  2015,
-    'STATESERVER_OBJECT_SET_FIELD':                     2020,
-    'STATESERVER_OBJECT_SET_FIELDS':                    2021,
-    'STATESERVER_OBJECT_DELETE_FIELD_RAM':              2030,
-    'STATESERVER_OBJECT_DELETE_FIELDS_RAM':             2031,
-    'STATESERVER_OBJECT_DELETE_RAM':                    2032,
-    'STATESERVER_OBJECT_SET_LOCATION':                          2040,
-    'STATESERVER_OBJECT_CHANGING_LOCATION':                     2041,
-    'STATESERVER_OBJECT_ENTER_LOCATION_WITH_REQUIRED':          2042,
-    'STATESERVER_OBJECT_ENTER_LOCATION_WITH_REQUIRED_OTHER':    2043,
-    'STATESERVER_OBJECT_GET_LOCATION':                          2044,
-    'STATESERVER_OBJECT_GET_LOCATION_RESP':                     2045,
-    'STATESERVER_OBJECT_SET_AI':                                2050,
-    'STATESERVER_OBJECT_CHANGING_AI':                           2051,
-    'STATESERVER_OBJECT_ENTER_AI_WITH_REQUIRED':                2052,
-    'STATESERVER_OBJECT_ENTER_AI_WITH_REQUIRED_OTHER':          2053,
-    'STATESERVER_OBJECT_GET_AI':                                2054,
-    'STATESERVER_OBJECT_GET_AI_RESP':                           2055,
-    'STATESERVER_OBJECT_SET_OWNER':                             2060,
-    'STATESERVER_OBJECT_CHANGING_OWNER':                        2061,
-    'STATESERVER_OBJECT_ENTER_OWNER_WITH_REQUIRED':             2062,
-    'STATESERVER_OBJECT_ENTER_OWNER_WITH_REQUIRED_OTHER':       2063,
-    'STATESERVER_OBJECT_GET_OWNER':                             2064,
-    'STATESERVER_OBJECT_GET_OWNER_RESP':                        2065,
-    'STATESERVER_OBJECT_GET_ZONE_OBJECTS':              2100,
-    'STATESERVER_OBJECT_GET_ZONES_OBJECTS':             2102,
-    'STATESERVER_OBJECT_GET_CHILDREN':                  2104,
-    'STATESERVER_OBJECT_GET_ZONE_COUNT':                2110,
-    'STATESERVER_OBJECT_GET_ZONE_COUNT_RESP':           2111,
-    'STATESERVER_OBJECT_GET_ZONES_COUNT':               2112,
-    'STATESERVER_OBJECT_GET_ZONES_COUNT_RESP':          2113,
-    'STATESERVER_OBJECT_GET_CHILD_COUNT':               2114,
-    'STATESERVER_OBJECT_GET_CHILD_COUNT_RESP':          2115,
-    'STATESERVER_OBJECT_DELETE_ZONE':                   2120,
-    'STATESERVER_OBJECT_DELETE_ZONES':                  2122,
-    'STATESERVER_OBJECT_DELETE_CHILDREN':               2124,
-    # DBSS-backed-object messages:
-    'DBSS_OBJECT_ACTIVATE_WITH_DEFAULTS':        2200,
-    'DBSS_OBJECT_ACTIVATE_WITH_DEFAULTS_OTHER':  2201,
-    'DBSS_OBJECT_GET_ACTIVATED':                 2207,
-    'DBSS_OBJECT_GET_ACTIVATED_RESP':            2208,
-    'DBSS_OBJECT_DELETE_FIELD_DISK':             2230,
-    'DBSS_OBJECT_DELETE_FIELDS_DISK':            2231,
-    'DBSS_OBJECT_DELETE_DISK':                   2232,
-
-    # Database Server control messages:
-    'DBSERVER_CREATE_OBJECT':                       3000,
-    'DBSERVER_CREATE_OBJECT_RESP':                  3001,
-    'DBSERVER_OBJECT_GET_FIELD':                    3010,
-    'DBSERVER_OBJECT_GET_FIELD_RESP':               3011,
-    'DBSERVER_OBJECT_GET_FIELDS':                   3012,
-    'DBSERVER_OBJECT_GET_FIELDS_RESP':              3013,
-    'DBSERVER_OBJECT_GET_ALL':                      3014,
-    'DBSERVER_OBJECT_GET_ALL_RESP':                 3015,
-    'DBSERVER_OBJECT_SET_FIELD':                    3020,
-    'DBSERVER_OBJECT_SET_FIELDS':                   3021,
-    'DBSERVER_OBJECT_SET_FIELD_IF_EQUALS':          3022,
-    'DBSERVER_OBJECT_SET_FIELD_IF_EQUALS_RESP':     3023,
-    'DBSERVER_OBJECT_SET_FIELDS_IF_EQUALS':         3024,
-    'DBSERVER_OBJECT_SET_FIELDS_IF_EQUALS_RESP':    3025,
-    'DBSERVER_OBJECT_SET_FIELD_IF_EMPTY':           3026,
-    'DBSERVER_OBJECT_SET_FIELD_IF_EMPTY_RESP':      3027,
-    'DBSERVER_OBJECT_DELETE_FIELD':                 3030,
-    'DBSERVER_OBJECT_DELETE_FIELDS':                3031,
-    'DBSERVER_OBJECT_DELETE':                       3032,
-
-    # Client Agent control messages:
-    'CLIENTAGENT_SET_STATE':                        1000,
-    'CLIENTAGENT_SET_CLIENT_ID':                    1001,
-    'CLIENTAGENT_SEND_DATAGRAM':                    1002,
-    'CLIENTAGENT_EJECT':                            1004,
-    'CLIENTAGENT_DROP':                             1005,
-    'CLIENTAGENT_GET_NETWORK_ADDRESS':              1006,
-    'CLIENTAGENT_GET_NETWORK_ADDRESS_RESP':         1007,
-    'CLIENTAGENT_DECLARE_OBJECT':                   1010,
-    'CLIENTAGENT_UNDECLARE_OBJECT':                 1011,
-    'CLIENTAGENT_ADD_SESSION_OBJECT':               1012,
-    'CLIENTAGENT_REMOVE_SESSION_OBJECT':            1013,
-    'CLIENTAGENT_SET_FIELDS_SENDABLE':              1014,
-    'CLIENTAGENT_OPEN_CHANNEL':                     1100,
-    'CLIENTAGENT_CLOSE_CHANNEL':                    1101,
-    'CLIENTAGENT_ADD_POST_REMOVE':                  1110,
-    'CLIENTAGENT_CLEAR_POST_REMOVES':               1111,
-    'CLIENTAGENT_ADD_INTEREST':                     1200,
-    'CLIENTAGENT_ADD_INTEREST_MULTIPLE':            1201,
-    'CLIENTAGENT_REMOVE_INTEREST':                  1203,
-}
+CLIENT_HELLO =                                 1
+CLIENT_HELLO_RESP =                            2
+
+# Sent by the client when it's leaving.
+CLIENT_DISCONNECT =                            3
+
+# Sent by the server when it is dropping the connection deliberately.
+CLIENT_EJECT =                                 4
+
+CLIENT_HEARTBEAT =                             5
+
+CLIENT_OBJECT_SET_FIELD =                      120
+CLIENT_OBJECT_SET_FIELDS =                     121
+CLIENT_OBJECT_LEAVING =                        132
+CLIENT_OBJECT_LEAVING_OWNER =                  161
+CLIENT_ENTER_OBJECT_REQUIRED =                 142
+CLIENT_ENTER_OBJECT_REQUIRED_OTHER =           143
+CLIENT_ENTER_OBJECT_REQUIRED_OWNER =           172
+CLIENT_ENTER_OBJECT_REQUIRED_OTHER_OWNER =     173
+
+CLIENT_DONE_INTEREST_RESP =                    204
+
+CLIENT_ADD_INTEREST =                          200
+CLIENT_ADD_INTEREST_MULTIPLE =                 201
+CLIENT_REMOVE_INTEREST =                       203
+CLIENT_OBJECT_LOCATION =                       140
+
+
+# These are sent internally inside the Astron cluster.
+
+# Message Director control messages:
+CONTROL_CHANNEL =                                 1
+CONTROL_ADD_CHANNEL =                             9000
+CONTROL_REMOVE_CHANNEL =                          9001
+CONTROL_ADD_RANGE =                               9002
+CONTROL_REMOVE_RANGE =                            9003
+CONTROL_ADD_POST_REMOVE =                         9010
+CONTROL_CLEAR_POST_REMOVES =                      9011
+
+# State Server control messages:
+STATESERVER_CREATE_OBJECT_WITH_REQUIRED =         2000
+STATESERVER_CREATE_OBJECT_WITH_REQUIRED_OTHER =   2001
+STATESERVER_DELETE_AI_OBJECTS =                   2009
+STATESERVER_OBJECT_GET_FIELD =                    2010
+STATESERVER_OBJECT_GET_FIELD_RESP =               2011
+STATESERVER_OBJECT_GET_FIELDS =                   2012
+STATESERVER_OBJECT_GET_FIELDS_RESP =              2013
+STATESERVER_OBJECT_GET_ALL =                      2014
+STATESERVER_OBJECT_GET_ALL_RESP =                 2015
+STATESERVER_OBJECT_SET_FIELD =                    2020
+STATESERVER_OBJECT_SET_FIELDS =                   2021
+STATESERVER_OBJECT_DELETE_FIELD_RAM =             2030
+STATESERVER_OBJECT_DELETE_FIELDS_RAM =            2031
+STATESERVER_OBJECT_DELETE_RAM =                   2032
+STATESERVER_OBJECT_SET_LOCATION =                         2040
+STATESERVER_OBJECT_CHANGING_LOCATION =                    2041
+STATESERVER_OBJECT_ENTER_LOCATION_WITH_REQUIRED =         2042
+STATESERVER_OBJECT_ENTER_LOCATION_WITH_REQUIRED_OTHER =   2043
+STATESERVER_OBJECT_GET_LOCATION =                         2044
+STATESERVER_OBJECT_GET_LOCATION_RESP =                    2045
+STATESERVER_OBJECT_SET_AI =                               2050
+STATESERVER_OBJECT_CHANGING_AI =                          2051
+STATESERVER_OBJECT_ENTER_AI_WITH_REQUIRED =               2052
+STATESERVER_OBJECT_ENTER_AI_WITH_REQUIRED_OTHER =         2053
+STATESERVER_OBJECT_GET_AI =                               2054
+STATESERVER_OBJECT_GET_AI_RESP =                          2055
+STATESERVER_OBJECT_SET_OWNER =                            2060
+STATESERVER_OBJECT_CHANGING_OWNER =                       2061
+STATESERVER_OBJECT_ENTER_OWNER_WITH_REQUIRED =            2062
+STATESERVER_OBJECT_ENTER_OWNER_WITH_REQUIRED_OTHER =      2063
+STATESERVER_OBJECT_GET_OWNER =                            2064
+STATESERVER_OBJECT_GET_OWNER_RESP =                       2065
+STATESERVER_OBJECT_GET_ZONE_OBJECTS =             2100
+STATESERVER_OBJECT_GET_ZONES_OBJECTS =            2102
+STATESERVER_OBJECT_GET_CHILDREN =                 2104
+STATESERVER_OBJECT_GET_ZONE_COUNT =               2110
+STATESERVER_OBJECT_GET_ZONE_COUNT_RESP =          2111
+STATESERVER_OBJECT_GET_ZONES_COUNT =              2112
+STATESERVER_OBJECT_GET_ZONES_COUNT_RESP =         2113
+STATESERVER_OBJECT_GET_CHILD_COUNT =              2114
+STATESERVER_OBJECT_GET_CHILD_COUNT_RESP =         2115
+STATESERVER_OBJECT_DELETE_ZONE =                  2120
+STATESERVER_OBJECT_DELETE_ZONES =                 2122
+STATESERVER_OBJECT_DELETE_CHILDREN =              2124
+# DBSS-backed-object messages:
+DBSS_OBJECT_ACTIVATE_WITH_DEFAULTS =       2200
+DBSS_OBJECT_ACTIVATE_WITH_DEFAULTS_OTHER = 2201
+DBSS_OBJECT_GET_ACTIVATED =                2207
+DBSS_OBJECT_GET_ACTIVATED_RESP =           2208
+DBSS_OBJECT_DELETE_FIELD_DISK =            2230
+DBSS_OBJECT_DELETE_FIELDS_DISK =           2231
+DBSS_OBJECT_DELETE_DISK =                  2232
+
+# Database Server control messages:
+DBSERVER_CREATE_OBJECT =                      3000
+DBSERVER_CREATE_OBJECT_RESP =                 3001
+DBSERVER_OBJECT_GET_FIELD =                   3010
+DBSERVER_OBJECT_GET_FIELD_RESP =              3011
+DBSERVER_OBJECT_GET_FIELDS =                  3012
+DBSERVER_OBJECT_GET_FIELDS_RESP =             3013
+DBSERVER_OBJECT_GET_ALL =                     3014
+DBSERVER_OBJECT_GET_ALL_RESP =                3015
+DBSERVER_OBJECT_SET_FIELD =                   3020
+DBSERVER_OBJECT_SET_FIELDS =                  3021
+DBSERVER_OBJECT_SET_FIELD_IF_EQUALS =         3022
+DBSERVER_OBJECT_SET_FIELD_IF_EQUALS_RESP =    3023
+DBSERVER_OBJECT_SET_FIELDS_IF_EQUALS =        3024
+DBSERVER_OBJECT_SET_FIELDS_IF_EQUALS_RESP =   3025
+DBSERVER_OBJECT_SET_FIELD_IF_EMPTY =          3026
+DBSERVER_OBJECT_SET_FIELD_IF_EMPTY_RESP =     3027
+DBSERVER_OBJECT_DELETE_FIELD =                3030
+DBSERVER_OBJECT_DELETE_FIELDS =               3031
+DBSERVER_OBJECT_DELETE =                      3032
+
+# Client Agent control messages:
+CLIENTAGENT_SET_STATE =                       1000
+CLIENTAGENT_SET_CLIENT_ID =                   1001
+CLIENTAGENT_SEND_DATAGRAM =                   1002
+CLIENTAGENT_EJECT =                           1004
+CLIENTAGENT_DROP =                            1005
+CLIENTAGENT_GET_NETWORK_ADDRESS =             1006
+CLIENTAGENT_GET_NETWORK_ADDRESS_RESP =        1007
+CLIENTAGENT_DECLARE_OBJECT =                  1010
+CLIENTAGENT_UNDECLARE_OBJECT =                1011
+CLIENTAGENT_ADD_SESSION_OBJECT =              1012
+CLIENTAGENT_REMOVE_SESSION_OBJECT =           1013
+CLIENTAGENT_SET_FIELDS_SENDABLE =             1014
+CLIENTAGENT_OPEN_CHANNEL =                    1100
+CLIENTAGENT_CLOSE_CHANNEL =                   1101
+CLIENTAGENT_ADD_POST_REMOVE =                 1110
+CLIENTAGENT_CLEAR_POST_REMOVES =              1111
+CLIENTAGENT_ADD_INTEREST =                    1200
+CLIENTAGENT_ADD_INTEREST_MULTIPLE =           1201
+CLIENTAGENT_REMOVE_INTEREST =                 1203
+
+MsgName2Id = {name: value for name, value in globals().items() if isinstance(value, int)}
 
 # create id->name table for debugging
 MsgId2Names = invertDictLossless(MsgName2Id)
@@ -146,7 +148,7 @@ MsgId2Names = invertDictLossless(MsgName2Id)
 globals().update(MsgName2Id)
 
 # These messages are ignored when the client is headed to the quiet zone
-QUIET_ZONE_IGNORED_LIST = [
+QUIET_ZONE_IGNORED_LIST: list[int] = [
 
     # We mustn't ignore updates, because some updates for localToon
     # are always important.

+ 15 - 18
direct/src/distributed/MsgTypesCMU.py

@@ -5,25 +5,22 @@ implementation. """
 
 from direct.showbase.PythonUtil import invertDictLossless
 
-MsgName2Id = {
-    'SET_DOID_RANGE_CMU'                      : 9001,
-    'CLIENT_OBJECT_GENERATE_CMU'              : 9002,
-    'OBJECT_GENERATE_CMU'                     : 9003,
-    'OBJECT_UPDATE_FIELD_CMU'                 : 9004,
-    'OBJECT_DISABLE_CMU'                      : 9005,
-    'OBJECT_DELETE_CMU'                       : 9006,
-    'REQUEST_GENERATES_CMU'                   : 9007,
-    'CLIENT_DISCONNECT_CMU'                   : 9008,
-    'CLIENT_SET_INTEREST_CMU'                 : 9009,
-    'OBJECT_SET_ZONE_CMU'                     : 9010,
-    'CLIENT_HEARTBEAT_CMU'                    : 9011,
-    'CLIENT_OBJECT_UPDATE_FIELD_TARGETED_CMU'  : 9011,
+SET_DOID_RANGE_CMU                      = 9001
+CLIENT_OBJECT_GENERATE_CMU              = 9002
+OBJECT_GENERATE_CMU                     = 9003
+OBJECT_UPDATE_FIELD_CMU                 = 9004
+OBJECT_DISABLE_CMU                      = 9005
+OBJECT_DELETE_CMU                       = 9006
+REQUEST_GENERATES_CMU                   = 9007
+CLIENT_DISCONNECT_CMU                   = 9008
+CLIENT_SET_INTEREST_CMU                 = 9009
+OBJECT_SET_ZONE_CMU                     = 9010
+CLIENT_HEARTBEAT_CMU                    = 9011
+CLIENT_OBJECT_UPDATE_FIELD_TARGETED_CMU  = 9011
 
-    'CLIENT_OBJECT_UPDATE_FIELD' : 120,  # Matches MsgTypes.CLIENT_OBJECT_SET_FIELD
-}
+CLIENT_OBJECT_UPDATE_FIELD = 120  # Matches MsgTypes.CLIENT_OBJECT_SET_FIELD
+
+MsgName2Id = {name: value for name, value in globals().items() if isinstance(value, int)}
 
 # create id->name table for debugging
 MsgId2Names = invertDictLossless(MsgName2Id)
-
-# put msg names in module scope, assigned to msg value
-globals().update(MsgName2Id)

+ 3 - 1
direct/src/fsm/ClassicFSM.py

@@ -5,6 +5,8 @@ Note:
     existing code.  New code should use the :mod:`.FSM` module instead.
 """
 
+from __future__ import annotations
+
 __all__ = ['ClassicFSM']
 
 from direct.directnotify.DirectNotifyGlobal import directNotify
@@ -13,7 +15,7 @@ from direct.showbase.MessengerGlobal import messenger
 import weakref
 
 if __debug__:
-    _debugFsms = {}
+    _debugFsms: dict[str, weakref.ref] = {}
 
     def printDebugFsmList():
         for k in sorted(_debugFsms.keys()):

+ 3 - 1
direct/src/fsm/State.py

@@ -1,5 +1,7 @@
 """State module: contains State class"""
 
+from __future__ import annotations
+
 __all__ = ['State']
 
 from direct.directnotify.DirectNotifyGlobal import directNotify
@@ -18,7 +20,7 @@ class State(DirectObject):
     # should not cause any leaks.
     if __debug__:
         import weakref
-        States = weakref.WeakKeyDictionary()
+        States: weakref.WeakKeyDictionary[State, int] = weakref.WeakKeyDictionary()
 
         @classmethod
         def replaceMethod(cls, oldFunction, newFunction):

+ 4 - 153
direct/src/fsm/StatePush.py

@@ -1,6 +1,8 @@
 # classes for event-driven programming
 # http://en.wikipedia.org/wiki/Event-driven_programming
 
+from __future__ import annotations
+
 __all__ = ['StateVar', 'FunctionCall', 'EnterExit', 'Pulse', 'EventPulse',
            'EventArgument', ]
 
@@ -46,11 +48,6 @@ class PushesStateChanges:
         for subscriber in self._subscribers:
             subscriber._recvStatePush(self)
 
-if __debug__:
-    psc = PushesStateChanges(0)
-    assert psc.getState() == 0
-    psc.destroy()
-    del psc
 
 class ReceivesStateChanges:
     # base class for objects that subscribe to state changes from PushesStateChanges objects
@@ -83,10 +80,6 @@ class ReceivesStateChanges:
     def _recvStatePush(self, source):
         pass
 
-if __debug__:
-    rsc = ReceivesStateChanges(None)
-    rsc.destroy()
-    del rsc
 
 class StateVar(PushesStateChanges):
     # coder-friendly object that allows values to be set on it and pushes those values
@@ -97,13 +90,6 @@ class StateVar(PushesStateChanges):
     def get(self):
         return PushesStateChanges.getState(self)
 
-if __debug__:
-    sv = StateVar(0)
-    assert sv.get() == 0
-    sv.set(1)
-    assert sv.get() == 1
-    sv.destroy()
-    del sv
 
 class StateChangeNode(PushesStateChanges, ReceivesStateChanges):
     # base class that can be used to create a state-change notification chain
@@ -120,31 +106,6 @@ class StateChangeNode(PushesStateChanges, ReceivesStateChanges):
         # got a state push, apply new state to self
         self._handlePotentialStateChange(source._value)
 
-if __debug__:
-    sv = StateVar(0)
-    assert sv.get() == 0
-    scn = StateChangeNode(sv)
-    assert scn.getState() == 0
-    sv.set(1)
-    assert sv.get() == 1
-    assert scn.getState() == 1
-    scn2 = StateChangeNode(scn)
-    assert scn2.getState() == 1
-    sv.set(2)
-    assert scn2.getState() == 2
-    scn3 = StateChangeNode(scn)
-    assert scn3.getState() == 2
-    sv.set(3)
-    assert scn2.getState() == 3
-    assert scn3.getState() == 3
-    scn3.destroy()
-    scn2.destroy()
-    scn.destroy()
-    sv.destroy()
-    del scn3
-    del scn2
-    del scn
-    del sv
 
 class ReceivesMultipleStateChanges:
     # base class for objects that subscribe to state changes from multiple PushesStateChanges
@@ -179,15 +140,6 @@ class ReceivesMultipleStateChanges:
     def _recvMultiStatePush(self, key, source):
         pass
 
-if __debug__:
-    rsc = ReceivesMultipleStateChanges()
-    sv = StateVar(0)
-    sv2 = StateVar('b')
-    rsc._subscribeTo(sv, 'a')
-    rsc._subscribeTo(sv2, 2)
-    rsc._unsubscribe('a')
-    rsc.destroy()
-    del rsc
 
 class FunctionCall(ReceivesMultipleStateChanges, PushesStateChanges):
     # calls func with provided args whenever arguments' state changes
@@ -249,47 +201,6 @@ class FunctionCall(ReceivesMultipleStateChanges, PushesStateChanges):
             self._func(*self._bakedArgs, **self._bakedKargs)
             PushesStateChanges._handleStateChange(self)
 
-if __debug__:
-    l = []
-    def handler1(value, l=l):
-        l.append(value)
-    assert not l
-    sv = StateVar(0)
-    fc = FunctionCall(handler1, sv)
-    assert not l
-    fc.pushCurrentState()
-    assert l == [0,]
-    sv.set(1)
-    assert l == [0,1,]
-    sv.set(2)
-    assert l == [0,1,2,]
-    fc.destroy()
-    sv.destroy()
-    del fc
-    del sv
-    del handler1
-    del l
-
-    l = []
-    def handler2(value, kDummy=None, kValue=None, l=l):
-        l.append((value, kValue))
-    assert not l
-    sv = StateVar(0)
-    ksv = StateVar('a')
-    fc = FunctionCall(handler2, sv, kValue=ksv)
-    assert not l
-    fc.pushCurrentState()
-    assert l == [(0,'a',),]
-    sv.set(1)
-    assert l == [(0,'a'),(1,'a'),]
-    ksv.set('b')
-    assert l == [(0,'a'),(1,'a'),(1,'b'),]
-    fc.destroy()
-    sv.destroy()
-    del fc
-    del sv
-    del handler2
-    del l
 
 class EnterExit(StateChangeNode):
     # call enterFunc when our state becomes true, exitFunc when it becomes false
@@ -314,33 +225,6 @@ class EnterExit(StateChangeNode):
             self._exitFunc()
         StateChangeNode._handleStateChange(self)
 
-if __debug__:
-    l = []
-    def enter(l=l):
-        l.append(1)
-    def exit(l=l):
-        l.append(0)
-    sv = StateVar(0)
-    ee = EnterExit(sv, enter, exit)
-    sv.set(0)
-    assert not l
-    sv.set(1)
-    assert l == [1,]
-    sv.set(2)
-    assert l == [1,]
-    sv.set(0)
-    assert l == [1,0,]
-    sv.set(True)
-    assert l == [1,0,1,]
-    sv.set(False)
-    assert l == [1,0,1,0,]
-    ee.destroy()
-    sv.destroy()
-    del ee
-    del sv
-    del enter
-    del exit
-    del l
 
 class Pulse(PushesStateChanges):
     # changes state to True then immediately to False whenever sendPulse is called
@@ -351,25 +235,6 @@ class Pulse(PushesStateChanges):
         self._handlePotentialStateChange(True)
         self._handlePotentialStateChange(False)
 
-if __debug__:
-    l = []
-    def handler(value, l=l):
-        l.append(value)
-    p = Pulse()
-    fc = FunctionCall(handler, p)
-    assert not l
-    fc.pushCurrentState()
-    assert l == [False, ]
-    p.sendPulse()
-    assert l == [False, True, False, ]
-    p.sendPulse()
-    assert l == [False, True, False, True, False, ]
-    fc.destroy()
-    p.destroy()
-    del fc
-    del p
-    del l
-    del handler
 
 class EventPulse(Pulse, DirectObject):
     # sends a True-False "pulse" whenever a specific messenger message is sent
@@ -381,6 +246,7 @@ class EventPulse(Pulse, DirectObject):
         self.ignoreAll()
         Pulse.destroy(self)
 
+
 class EventArgument(PushesStateChanges, DirectObject):
     # tracks a particular argument to a particular messenger event
     def __init__(self, event, index=0):
@@ -396,6 +262,7 @@ class EventArgument(PushesStateChanges, DirectObject):
     def _handleEvent(self, *args):
         self._handlePotentialStateChange(args[self._index])
 
+
 class AttrSetter(StateChangeNode):
     def __init__(self, source, object, attrName):
         self._object = object
@@ -406,19 +273,3 @@ class AttrSetter(StateChangeNode):
     def _handleStateChange(self):
         setattr(self._object, self._attrName, self._value)
         StateChangeNode._handleStateChange(self)
-
-if __debug__:
-    from direct.showbase.PythonUtil import ScratchPad
-    o = ScratchPad()
-    svar = StateVar(0)
-    aset = AttrSetter(svar, o, 'testAttr')
-    assert hasattr(o, 'testAttr')
-    assert o.testAttr == 0
-    svar.set('red')
-    assert o.testAttr == 'red'
-    aset.destroy()
-    svar.destroy()
-    o.destroy()
-    del aset
-    del svar
-    del o

+ 3 - 1
direct/src/gui/DirectDialog.py

@@ -4,6 +4,8 @@ See the :ref:`directdialog` page in the programming manual for a more
 in-depth explanation and an example of how to use this class.
 """
 
+from __future__ import annotations
+
 __all__ = [
     'findDialog', 'cleanupDialog', 'DirectDialog', 'OkDialog',
     'OkCancelDialog', 'YesNoDialog', 'YesNoCancelDialog', 'RetryCancelDialog',
@@ -45,7 +47,7 @@ def cleanupDialog(uniqueName):
 
 class DirectDialog(DirectFrame):
 
-    AllDialogs = {}
+    AllDialogs: dict[str, DirectDialog] = {}
     PanelIndex = 0
 
     def __init__(self, parent=None, **kw):

+ 4 - 4
direct/src/gui/DirectGuiBase.py

@@ -84,6 +84,8 @@ Code overview:
     see if any keywords are left unused.  If so, an error is raised.
 """
 
+from __future__ import annotations
+
 __all__ = ['DirectGuiBase', 'DirectGuiWidget']
 
 
@@ -616,9 +618,7 @@ class DirectGuiBase(DirectObject.DirectObject):
 
     def components(self):
         # Return a list of all components.
-        names = list(self.__componentInfo.keys())
-        names.sort()
-        return names
+        return sorted(self.__componentInfo)
 
     def hascomponent(self, component):
         return component in self.__componentInfo
@@ -682,7 +682,7 @@ class DirectGuiWidget(DirectGuiBase, NodePath):
     else:
         inactiveInitState = DGG.DISABLED
 
-    guiDict = {}
+    guiDict: dict[str, DirectGuiWidget] = {}
 
     def __init__(self, parent = None, **kw):
         # Direct gui widgets are node paths

+ 1 - 1
direct/src/gui/DirectGuiGlobals.py

@@ -3,7 +3,7 @@ Global definitions used by Direct Gui Classes and handy constants
 that can be used during widget construction
 """
 
-__all__ = []
+__all__ = ()
 
 from panda3d.core import (
     KeyboardButton,

+ 3 - 1
direct/src/interval/FunctionInterval.py

@@ -1,5 +1,7 @@
 """FunctionInterval module: contains the FunctionInterval class"""
 
+from __future__ import annotations
+
 __all__ = ['FunctionInterval', 'EventInterval', 'AcceptInterval', 'IgnoreInterval', 'ParentInterval', 'WrtParentInterval', 'PosInterval', 'HprInterval', 'ScaleInterval', 'PosHprInterval', 'HprScaleInterval', 'PosHprScaleInterval', 'Func', 'Wait']
 
 from panda3d.direct import WaitInterval
@@ -23,7 +25,7 @@ class FunctionInterval(Interval.Interval):
     # should not cause any leaks.
     if __debug__:
         import weakref
-        FunctionIntervals = weakref.WeakKeyDictionary()
+        FunctionIntervals: weakref.WeakKeyDictionary[FunctionInterval, int] = weakref.WeakKeyDictionary()
 
         @classmethod
         def replaceMethod(cls, oldFunction, newFunction):

+ 6 - 6
direct/src/interval/IntervalTest.py

@@ -1,6 +1,6 @@
 """Undocumented Module"""
 
-__all__ = []
+__all__ = ()
 
 
 if __name__ == "__main__":
@@ -26,7 +26,7 @@ if __name__ == "__main__":
     base = ShowBase()
 
     boat = base.loader.loadModel('models/misc/smiley')
-    boat.reparentTo(render)
+    boat.reparentTo(base.render)
 
     donald = Actor()
     donald.loadModel("phase_6/models/char/donald-wheel-1000")
@@ -34,7 +34,7 @@ if __name__ == "__main__":
     donald.reparentTo(boat)
 
     dock = base.loader.loadModel('models/misc/smiley')
-    dock.reparentTo(render)
+    dock.reparentTo(base.render)
 
     sound = base.loader.loadSfx('phase_6/audio/sfx/SZ_DD_waterlap.mp3')
     foghorn = base.loader.loadSfx('phase_6/audio/sfx/SZ_DD_foghorn.mp3')
@@ -93,7 +93,7 @@ if __name__ == "__main__":
     foghornSound = SoundInterval(foghorn, name='foghorn')
     soundTrack2 = Track([(foghornStartTime, foghornSound)], 'soundtrack2')
 
-    mtrack = MultiTrack([boatTrack, dockTrack, soundTrack, soundTrack2, waterEventTrack,
+    mtrack = MultiTrack([boatTrack, dockTrack, soundTrack, soundTrack2, waterEventTrack,  # type: ignore[name-defined]
                          donaldSteerTrack])
     # Print out MultiTrack parameters
     print(mtrack)
@@ -175,11 +175,11 @@ if __name__ == "__main__":
     # Just to take time
     i2 = LerpPosInterval(base.camera, 2.0, Point3(0, 10, 5))
     # This will be relative to end of camera move
-    i3 = FunctionInterval(printPreviousEnd)
+    i3 = FunctionInterval(printPreviousEnd)  # type: ignore[assignment]
     # Just to take time
     i4 = LerpPosInterval(base.camera, 2.0, Point3(0, 0, 5))
     # This will be relative to the start of the camera move
-    i5 = FunctionInterval(printPreviousStart)
+    i5 = FunctionInterval(printPreviousStart)  # type: ignore[assignment]
     # This will be relative to track start
     i6 = FunctionInterval(printTrackStart)
     # This will print some arguments

+ 1 - 3
direct/src/leveleditor/HotKeyUI.py

@@ -116,9 +116,7 @@ class HotKeyPanel(ScrolledPanel):
 
     def updateUI(self):
         vbox = wx.BoxSizer(wx.VERTICAL)
-        keys = list(base.direct.hotKeyMap.keys())
-        keys.sort()
-        for key in keys:
+        for key in sorted(base.direct.hotKeyMap):
             keyDesc = base.direct.hotKeyMap[key]
             itemPanel = wx.Panel(self)
             sizer = wx.BoxSizer(wx.HORIZONTAL)

+ 1 - 1
direct/src/leveleditor/LevelEditorStart.py

@@ -4,7 +4,7 @@ if __name__ == '__main__':
     from direct.showbase.ShowBase import ShowBase
     base = ShowBase()
 
-    base.le = LevelEditor.LevelEditor()
+    base.le = LevelEditor.LevelEditor()  # type: ignore[attr-defined]
     # You should define LevelEditor instance as
     # base.le so it can be reached in global scope
 

+ 2 - 1
direct/src/leveleditor/testData.py

@@ -1,4 +1,5 @@
 from panda3d.core import Point3, VBase3
+from direct.showbase.ShowBaseGlobal import base
 
 if hasattr(base, 'le'):
     objectMgr = base.le.objectMgr
@@ -6,7 +7,7 @@ if hasattr(base, 'le'):
     ui.sceneGraphUI.reset()
 
 else:
-    objectMgr = base.objectMgr
+    objectMgr = base.objectMgr  # type: ignore[attr-defined]
 # temporary place holder for nodepath
 objects = {}
 

+ 3 - 1
direct/src/motiontrail/MotionTrail.py

@@ -1,3 +1,5 @@
+from __future__ import annotations
+
 from panda3d.core import (
     BoundingVolume,
     ColorBlendAttrib,
@@ -92,7 +94,7 @@ class MotionTrail(NodePath, DirectObject):
     notify = directNotify.newCategory("MotionTrail")
 
     task_added = False
-    motion_trail_list = []
+    motion_trail_list: list[MotionTrail] = []
     motion_trail_task_name = "motion_trail_task"
 
     global_enable = True

+ 0 - 0
direct/src/physics/__init__.py


+ 1 - 3
direct/src/showbase/BulletinBoard.py

@@ -55,8 +55,6 @@ class BulletinBoard:
     def __repr__(self):
         str  = 'Bulletin Board Contents\n'
         str += '======================='
-        keys = list(self._dict.keys())
-        keys.sort()
-        for postName in keys:
+        for postName in sorted(self._dict):
             str += '\n%s: %s' % (postName, self._dict[postName])
         return str

+ 4 - 3
direct/src/showbase/ContainerLeakDetector.py

@@ -1,3 +1,5 @@
+from __future__ import annotations
+
 from direct.directnotify.DirectNotifyGlobal import directNotify
 import direct.showbase.DConfig as config
 from direct.showbase.PythonUtil import makeFlywheelGen, flywheel
@@ -11,7 +13,6 @@ import types
 import weakref
 import random
 import builtins
-import sys
 
 
 deadEndTypes = frozenset((
@@ -566,7 +567,7 @@ class FindContainers(Job):
                 curObjRef = None
 
                 # types.CellType was added in Python 3.8
-                if sys.version_info >= (3, 8) and type(curObj) is types.CellType:
+                if type(curObj) is types.CellType:
                     child = curObj.cell_contents
                     hasLength = self._hasLength(child)
                     notDeadEnd = not self._isDeadEnd(child)
@@ -983,7 +984,7 @@ class ContainerLeakDetector(Job):
     """
     notify = directNotify.newCategory("ContainerLeakDetector")
     # set of containers that should not be examined
-    PrivateIds = set()
+    PrivateIds: set[int] = set()
 
     def __init__(self, name, firstCheckDelay = None):
         Job.__init__(self, name)

+ 7 - 10
direct/src/showbase/ContainerReport.py

@@ -1,3 +1,5 @@
+from __future__ import annotations
+
 from direct.directnotify.DirectNotifyGlobal import directNotify
 from direct.showbase.PythonUtil import Queue, invertDictLossless
 from direct.showbase.PythonUtil import safeRepr
@@ -5,14 +7,13 @@ from direct.showbase.Job import Job
 from direct.showbase.JobManagerGlobal import jobMgr
 from direct.showbase.ContainerLeakDetector import deadEndTypes
 import types
-import sys
 import io
 
 
 class ContainerReport(Job):
     notify = directNotify.newCategory("ContainerReport")
     # set of containers that should not be included in the report
-    PrivateIds = set()
+    PrivateIds: set[int] = set()
 
     def __init__(self, name, log=False, limit=None, threaded=False):
         Job.__init__(self, name)
@@ -122,7 +123,7 @@ class ContainerReport(Job):
                 continue
 
             # types.CellType was added in Python 3.8
-            if sys.version_info >= (3, 8) and type(parentObj) is types.CellType:
+            if type(parentObj) is types.CellType:
                 child = parentObj.cell_contents
                 if self._examine(child):
                     assert (self._queue.back() is child)
@@ -228,14 +229,11 @@ class ContainerReport(Job):
         if type not in self._type2id2len:
             return
         len2ids = invertDictLossless(self._type2id2len[type])
-        lengths = list(len2ids.keys())
-        lengths.sort()
-        lengths.reverse()
         print('=====')
         print('===== %s' % type)
         count = 0
         stop = False
-        for l in lengths:
+        for l in sorted(len2ids, reverse=True):
             #len2ids[l].sort()
             pathStrList = list()
             for id in len2ids[l]:
@@ -257,9 +255,8 @@ class ContainerReport(Job):
         for type in initialTypes:
             for i in self._outputType(type, **kArgs):
                 yield None
-        otherTypes = list(set(self._type2id2len.keys()).difference(set(initialTypes)))
-        otherTypes.sort(key=lambda obj: obj.__name__)
-        for type in otherTypes:
+        otherTypes = set(self._type2id2len).difference(initialTypes)
+        for type in sorted(otherTypes, key=lambda obj: obj.__name__):
             for i in self._outputType(type, **kArgs):
                 yield None
 

+ 0 - 144
direct/src/showbase/CountedResource.py

@@ -81,147 +81,3 @@ class CountedResource(object):
 
     def __del__(self):
         self.decrementCounter()
-
-
-if __debug__ and __name__ == '__main__':
-    class MouseResource(CountedResource):
-        """
-        A simple class to demonstrate the acquisition of a resource.
-        """
-        @classmethod
-        def acquire(cls):
-            # The call to the super-class's acquire() is
-            # not necessary at the moment, but may be in
-            # the future, so do it now for good measure.
-            super(MouseResource, cls).acquire()
-
-            # Now acquire the resource this class is
-            # managing.
-            print('-- Acquire Mouse')
-
-        @classmethod
-        def release(cls):
-            # First, release the resource this class is
-            # managing.
-            print('-- Release Mouse')
-
-            # The call to the super-class's release() is
-            # not necessary at the moment, but may be in
-            # the future, so do it now for good measure.
-            super(MouseResource, cls).release()
-
-
-        def __init__(self):
-            super(MouseResource, self).__init__()
-
-        def __del__(self):
-            super(MouseResource, self).__del__()
-
-    class CursorResource(CountedResource):
-        """
-        A class to demonstrate how to implement a dependent
-        resource.  Notice how this class also inherits from
-        CountedResource.  Instead of subclassing MouseCounter,
-        we will just acquire it in our __init__() and release
-        it in our __del__().
-        """
-        @classmethod
-        def acquire(cls):
-            super(CursorResource, cls).acquire()
-            print('-- Acquire Cursor')
-
-        @classmethod
-        def release(cls):
-            print('-- Release Cursor')
-
-            super(CursorResource, cls).release()
-
-        def __init__(self):
-            # The required resource references should
-            # be stored on 'self' since we want to
-            # release it when the object is deleted.
-            self.__mouseResource = MouseResource()
-
-            # Call the super-classes __init__()
-            # after all required resources are
-            # referenced.
-            super(CursorResource, self).__init__()
-
-        def __del__(self):
-            # Free up the most dependent resource
-            # first, the one this class is managing.
-            super(CursorResource, self).__del__()
-
-            # Now unlink any required resources.
-            del self.__mouseResource
-
-    class InvalidResource(MouseResource):
-        @classmethod
-        def acquire(cls):
-            super(InvalidResource, cls).acquire()
-            print('-- Acquire Invalid')
-
-        @classmethod
-        def release(cls):
-            print('-- Release Invalid')
-            super(InvalidResource, cls).release()
-
-    print('\nAllocate Mouse')
-    m = MouseResource()
-    print('Free up Mouse')
-    del m
-
-    print('\nAllocate Cursor')
-    c = CursorResource()
-    print('Free up Cursor')
-    del c
-
-    print('\nAllocate Mouse then Cursor')
-    m = MouseResource()
-    c = CursorResource()
-    print('Free up Cursor')
-    del c
-    print('Free up Mouse')
-    del m
-
-    print('\nAllocate Mouse then Cursor')
-    m = MouseResource()
-    c = CursorResource()
-    print('Free up Mouse')
-    del m
-    print('Free up Cursor')
-    del c
-
-    print('\nAllocate Cursor then Mouse')
-    c = CursorResource()
-    m = MouseResource()
-    print('Free up Mouse')
-    del m
-    print('Free up Cursor')
-    del c
-
-    print('\nAllocate Cursor then Mouse')
-    c = CursorResource()
-    m = MouseResource()
-    print('Free up Cursor')
-    del c
-
-    # example of an invalid subclass
-    try:
-        print('\nAllocate Invalid')
-        i = InvalidResource()
-        print('Free up Invalid')
-    except AssertionError as e:
-        print(e)
-    print('')
-
-    print('Free up Mouse')
-    del m
-
-    def demoFunc():
-        print('\nAllocate Cursor within function')
-        c = CursorResource()
-
-        print('Cursor will be freed on function exit')
-
-    demoFunc()

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

@@ -1,6 +1,6 @@
 "This module contains a deprecated shim emulating the old DConfig API."
 
-__all__ = []
+__all__ = ()
 
 from panda3d.core import (ConfigFlags, ConfigVariableBool, ConfigVariableInt,
                           ConfigVariableDouble, ConfigVariableString)

+ 3 - 29
direct/src/showbase/DistancePhasedNode.py

@@ -1,3 +1,5 @@
+from __future__ import annotations
+
 from direct.showbase.DirectObject import DirectObject
 from direct.directnotify.DirectNotifyGlobal import directNotify
 from panda3d.core import (
@@ -62,7 +64,7 @@ class DistancePhasedNode(PhasedObject, DirectObject, NodePath):
 
     notify = directNotify.newCategory("DistancePhasedObject")
     __InstanceSequence = 0
-    __InstanceDeque = []
+    __InstanceDeque: list[int] = []
 
     @staticmethod
     def __allocateId():
@@ -327,31 +329,3 @@ class BufferedDistancePhasedNode(DistancePhasedNode):
         for x,sphere in enumerate(self._colSpheres[phase+1:]):
             sphere.node().modifySolid(0).setRadius(self.bufferParamList[x+phase+1][1][0])
             sphere.node().markInternalBoundsStale()
-
-
-if __debug__ and 0:
-    cSphere = CollisionSphere(0, 0, 0, 0.1)
-    cNode = CollisionNode('camCol')
-    cNode.addSolid(cSphere)
-    cNodePath = NodePath(cNode)
-    cNodePath.reparentTo(base.cam)
-    # cNodePath.show()
-    # cNodePath.setPos(25,0,0)
-
-    base.cTrav = CollisionTraverser()
-
-    eventHandler = CollisionHandlerEvent()
-    eventHandler.addInPattern('enter%in')
-    eventHandler.addOutPattern('exit%in')
-
-    # messenger.toggleVerbose()
-    base.cTrav.addCollider(cNodePath, eventHandler)
-
-    p = BufferedDistancePhasedNode('p', {'At': (10, 20), 'Near': (100, 200), 'Far': (1000, 1020)},
-                                   autoCleanup=False,
-                                   fromCollideNode=cNodePath,
-                                   )
-
-    p.reparentTo(render)
-    p._DistancePhasedNode__oneTimeCollide()
-    base.eventMgr.doEvents()

+ 5 - 14
direct/src/showbase/ExceptionVarDump.py

@@ -69,7 +69,7 @@ oldExcepthook = None
 # from its main exception handler
 wantStackDumpLog = False
 wantStackDumpUpload = False
-variableDumpReasons = []
+variableDumpReasons: list = []
 dumpOnExceptionInit = False
 
 
@@ -116,15 +116,10 @@ def _excepthookDumpVars(eType, eValue, tb):
         for name, obj in frame.f_locals.items():
             if name in codeNames:
                 name2obj[name] = obj
-        # show them in alphabetical order
-        names = list(name2obj.keys())
-        names.sort()
-        # push them in reverse order so they'll be popped in the correct order
-        names.reverse()
 
         traversedIds = set()
-
-        for name in names:
+        # push them in reverse alphabetical order so they'll be popped in the correct order
+        for name in sorted(name2obj, reverse=True):
             stateStack.push([name, name2obj[name], traversedIds])
 
         while len(stateStack) > 0:
@@ -150,14 +145,10 @@ def _excepthookDumpVars(eType, eValue, tb):
                                 continue
                         attrName2obj[attrName] = attr
                 if len(attrName2obj) > 0:
-                    # show them in alphabetical order
-                    attrNames = list(attrName2obj.keys())
-                    attrNames.sort()
-                    # push them in reverse order so they'll be popped in the correct order
-                    attrNames.reverse()
                     ids = set(traversedIds)
                     ids.add(id(obj))
-                    for attrName in attrNames:
+                    # push them in reverse alphabetical order so they'll be popped in the correct order
+                    for attrName in sorted(attrName2obj, reverse=True):
                         obj = attrName2obj[attrName]
                         stateStack.push(['%s.%s' % (name, attrName), obj, ids])
 

+ 1 - 2
direct/src/showbase/GarbageReport.py

@@ -294,8 +294,7 @@ class GarbageReport(Job):
             if self._args.fullReport:
                 garbageIndices = range(self.numGarbage)
             else:
-                garbageIndices = list(self.cycleIds)
-                garbageIndices.sort()
+                garbageIndices = sorted(self.cycleIds)
             numGarbage = len(garbageIndices)
 
             # log each individual item with a number in front of it

+ 1 - 3
direct/src/showbase/JobManager.py

@@ -141,9 +141,7 @@ class JobManager:
 
     def _getSortedPriorities(self):
         # returns all job priorities in ascending order
-        priorities = list(self._pri2jobId2job.keys())
-        priorities.sort()
-        return priorities
+        return sorted(self._pri2jobId2job)
 
     def _process(self, task=None):
         if self._useOverflowTime is None:

+ 4 - 12
direct/src/showbase/Messenger.py

@@ -540,9 +540,7 @@ class Messenger:
         return a matching event (needle) if found (in haystack).
         This is primarily a debugging tool.
         """
-        keys = list(self.__callbacks.keys())
-        keys.sort()
-        for event in keys:
+        for event in sorted(self.__callbacks):
             if repr(event).find(needle) >= 0:
                 return {event: self.__callbacks[event]}
 
@@ -553,9 +551,7 @@ class Messenger:
         This is primarily a debugging tool.
         """
         matches = {}
-        keys = list(self.__callbacks.keys())
-        keys.sort()
-        for event in keys:
+        for event in sorted(self.__callbacks):
             if repr(event).find(needle) >= 0:
                 matches[event] = self.__callbacks[event]
                 # if the limit is not None, decrement and
@@ -596,9 +592,7 @@ class Messenger:
         Compact version of event, acceptor pairs
         """
         str = "The messenger is currently handling:\n" + "="*64 + "\n"
-        keys = list(self.__callbacks.keys())
-        keys.sort()
-        for event in keys:
+        for event in sorted(self.__callbacks):
             str += self.__eventRepr(event)
         # Print out the object: event dictionary too
         str += "="*64 + "\n"
@@ -617,9 +611,7 @@ class Messenger:
         """
         str = 'Messenger\n'
         str = str + '='*50 + '\n'
-        keys = list(self.__callbacks.keys())
-        keys.sort()
-        for event in keys:
+        for event in sorted(self.__callbacks):
             acceptorDict = self.__callbacks[event]
             str = str + 'Event: ' + event + '\n'
             for key in list(acceptorDict.keys()):

+ 3 - 11
direct/src/showbase/ObjectPool.py

@@ -90,10 +90,7 @@ class ObjectPool:
     def typeFreqStr(self):
         s  =   'Object Pool: Type Frequencies'
         s += '\n============================='
-        counts = list(set(self._count2types.keys()))
-        counts.sort()
-        counts.reverse()
-        for count in counts:
+        for count in sorted(self._count2types, reverse=True):
             types = makeList(self._count2types[count])
             for typ in types:
                 s += '\n%s\t%s' % (count, typ)
@@ -102,12 +99,10 @@ class ObjectPool:
     def printObjsByType(self):
         print('Object Pool: Objects By Type')
         print('\n============================')
-        counts = list(set(self._count2types.keys()))
-        counts.sort()
         # print types with the smallest number of instances first, in case
         # there's a large group that waits a long time before printing
         #counts.reverse()
-        for count in counts:
+        for count in sorted(self._count2types):
             types = makeList(self._count2types[count])
             for typ in types:
                 print('TYPE: %s, %s objects' % (repr(typ), len(self._type2objs[typ])))
@@ -115,10 +110,7 @@ class ObjectPool:
 
     def printReferrers(self, numEach=3):
         """referrers of the first few of each type of object"""
-        counts = list(set(self._count2types.keys()))
-        counts.sort()
-        counts.reverse()
-        for count in counts:
+        for count in sorted(self._count2types, reverse=True):
             types = makeList(self._count2types[count])
             for typ in types:
                 print('\n\nTYPE: %s' % repr(typ))

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

@@ -11,6 +11,8 @@
 >>> o.diff(o2)
 """
 
+from __future__ import annotations
+
 __all__ = ['ExclusiveObjectPool', 'ObjectReport']
 
 from direct.directnotify.DirectNotifyGlobal import directNotify
@@ -26,8 +28,8 @@ import builtins
 class ExclusiveObjectPool(DirectObject):
     # ObjectPool specialization that excludes particular objects
     # IDs of objects to globally exclude from reporting
-    _ExclObjs = []
-    _ExclObjIds = {}
+    _ExclObjs: list[object] = []
+    _ExclObjIds: dict[int, int] = {}
     _SyncMaster = Sync('ExclusiveObjectPool.ExcludedObjectList')
     _SerialNumGen = SerialNumGen()
 

+ 1 - 3
direct/src/showbase/OnScreenDebug.py

@@ -57,9 +57,7 @@ class OnScreenDebug:
         if not self.onScreenText:
             self.load()
         self.onScreenText.clearText()
-        entries = list(self.data.items())
-        entries.sort()
-        for k, v in entries:
+        for k, v in sorted(self.data.items()):
             if v[0] == self.frame:
                 # It was updated this frame (key equals value):
                 #isNew = " is"

+ 54 - 98
direct/src/showbase/PythonUtil.py

@@ -1,5 +1,7 @@
 """Contains miscellaneous utility functions and classes."""
 
+from __future__ import annotations
+
 __all__ = [
 
     'indent', 'doc', 'adjust', 'difference', 'intersection', 'union',
@@ -11,7 +13,7 @@ __all__ = [
     'boolEqual', 'lineupPos', 'formatElapsedSeconds', 'solveQuadratic',
     'findPythonModule', 'mostDerivedLast', 'clampScalar', 'weightedChoice',
     'randFloat', 'normalDistrib', 'weightedRand', 'randUint31', 'randInt32',
-    'SerialNumGen', 'serialNum', 'uniqueName', 'Singleton',
+    'SerialNumGen', 'SerialMaskedGen', 'serialNum', 'uniqueName', 'Singleton',
     'SingletonError', 'printListEnum', 'safeRepr', 'fastRepr',
     'isDefaultValue', 'ScratchPad', 'Sync', 'itype', 'getNumberedTypedString',
     'getNumberedTypedSortedString', 'printNumberedTyped', 'DelayedCall',
@@ -39,6 +41,7 @@ import time
 import builtins
 import importlib
 import functools
+from typing import Callable
 
 __report_indent = 3
 
@@ -699,9 +702,9 @@ if __debug__:
         return profileDecorator
 
     # intercept profile-related file operations to avoid disk access
-    movedOpenFuncs = []
-    movedDumpFuncs = []
-    movedLoadFuncs = []
+    movedOpenFuncs: list[Callable] = []
+    movedDumpFuncs: list[Callable] = []
+    movedLoadFuncs: list[Callable] = []
     profileFilenames = set()
     profileFilenameList = Stack()
     profileFilename2file = {}
@@ -1899,7 +1902,7 @@ class SubframeCall:
 
 
 class PStatScope:
-    collectors = {}
+    collectors: dict = {}
 
     def __init__(self, level = None):
         self.levels = []
@@ -2458,18 +2461,6 @@ def formatTimeCompact(seconds):
     return result
 
 
-if __debug__ and __name__ == '__main__':
-    ftc = formatTimeCompact
-    assert ftc(0) == '0s'
-    assert ftc(1) == '1s'
-    assert ftc(60) == '1m0s'
-    assert ftc(64) == '1m4s'
-    assert ftc(60*60) == '1h0m0s'
-    assert ftc(24*60*60) == '1d0h0m0s'
-    assert ftc(24*60*60 + 2*60*60 + 34*60 + 12) == '1d2h34m12s'
-    del ftc
-
-
 def formatTimeExact(seconds):
     # like formatTimeCompact but leaves off '0 seconds', '0 minutes' etc. for
     # times that are e.g. 1 hour, 3 days etc.
@@ -2496,19 +2487,6 @@ def formatTimeExact(seconds):
     return result
 
 
-if __debug__ and __name__ == '__main__':
-    fte = formatTimeExact
-    assert fte(0) == '0s'
-    assert fte(1) == '1s'
-    assert fte(2) == '2s'
-    assert fte(61) == '1m1s'
-    assert fte(60) == '1m'
-    assert fte(60*60) == '1h'
-    assert fte(24*60*60) == '1d'
-    assert fte((24*60*60) + (2 * 60)) == '1d0h2m'
-    del fte
-
-
 class AlphabetCounter:
     # object that produces 'A', 'B', 'C', ... 'AA', 'AB', etc.
     def __init__(self):
@@ -2540,28 +2518,6 @@ class AlphabetCounter:
     __next__ = next
 
 
-if __debug__ and __name__ == '__main__':
-    def testAlphabetCounter():
-        tempList = []
-        ac = AlphabetCounter()
-        for i in range(26*3):
-            tempList.append(ac.next())
-        assert tempList == [ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
-                            'AA','AB','AC','AD','AE','AF','AG','AH','AI','AJ','AK','AL','AM','AN','AO','AP','AQ','AR','AS','AT','AU','AV','AW','AX','AY','AZ',
-                            'BA','BB','BC','BD','BE','BF','BG','BH','BI','BJ','BK','BL','BM','BN','BO','BP','BQ','BR','BS','BT','BU','BV','BW','BX','BY','BZ',]
-        ac = AlphabetCounter()
-        num  = 26 # A-Z
-        num += (26*26) # AA-ZZ
-        num += 26 # AAZ
-        num += 1 # ABA
-        num += 2 # ABC
-        for i in range(num):
-            x = ac.next()
-        assert x == 'ABC'
-    testAlphabetCounter()
-    del testAlphabetCounter
-
-
 class Default:
     # represents 'use the default value'
     # useful for keyword arguments to virtual methods
@@ -2665,52 +2621,52 @@ class PriorityCallbacks:
             callback()
 
 
-builtins.Functor = Functor
-builtins.Stack = Stack
-builtins.Queue = Queue
-builtins.SerialNumGen = SerialNumGen
-builtins.SerialMaskedGen = SerialMaskedGen
-builtins.ScratchPad = ScratchPad
-builtins.uniqueName = uniqueName
-builtins.serialNum = serialNum
+builtins.Functor = Functor  # type: ignore[attr-defined]
+builtins.Stack = Stack  # type: ignore[attr-defined]
+builtins.Queue = Queue  # type: ignore[attr-defined]
+builtins.SerialNumGen = SerialNumGen  # type: ignore[attr-defined]
+builtins.SerialMaskedGen = SerialMaskedGen  # type: ignore[attr-defined]
+builtins.ScratchPad = ScratchPad  # type: ignore[attr-defined]
+builtins.uniqueName = uniqueName  # type: ignore[attr-defined]
+builtins.serialNum = serialNum  # type: ignore[attr-defined]
 if __debug__:
-    builtins.profiled = profiled
-    builtins.exceptionLogged = exceptionLogged
-builtins.itype = itype
-builtins.appendStr = appendStr
-builtins.bound = bound
-builtins.clamp = clamp
-builtins.lerp = lerp
-builtins.makeList = makeList
-builtins.makeTuple = makeTuple
+    builtins.profiled = profiled  # type: ignore[attr-defined]
+    builtins.exceptionLogged = exceptionLogged  # type: ignore[attr-defined]
+builtins.itype = itype  # type: ignore[attr-defined]
+builtins.appendStr = appendStr  # type: ignore[attr-defined]
+builtins.bound = bound  # type: ignore[attr-defined]
+builtins.clamp = clamp  # type: ignore[attr-defined]
+builtins.lerp = lerp  # type: ignore[attr-defined]
+builtins.makeList = makeList  # type: ignore[attr-defined]
+builtins.makeTuple = makeTuple  # type: ignore[attr-defined]
 if __debug__:
-    builtins.printStack = printStack
-    builtins.printReverseStack = printReverseStack
-    builtins.printVerboseStack = printVerboseStack
-builtins.DelayedCall = DelayedCall
-builtins.DelayedFunctor = DelayedFunctor
-builtins.FrameDelayedCall = FrameDelayedCall
-builtins.SubframeCall = SubframeCall
-builtins.invertDict = invertDict
-builtins.invertDictLossless = invertDictLossless
-builtins.getBase = getBase
-builtins.getRepository = getRepository
-builtins.safeRepr = safeRepr
-builtins.fastRepr = fastRepr
-builtins.nullGen = nullGen
-builtins.flywheel = flywheel
-builtins.loopGen = loopGen
+    builtins.printStack = printStack  # type: ignore[attr-defined]
+    builtins.printReverseStack = printReverseStack  # type: ignore[attr-defined]
+    builtins.printVerboseStack = printVerboseStack  # type: ignore[attr-defined]
+builtins.DelayedCall = DelayedCall  # type: ignore[attr-defined]
+builtins.DelayedFunctor = DelayedFunctor  # type: ignore[attr-defined]
+builtins.FrameDelayedCall = FrameDelayedCall  # type: ignore[attr-defined]
+builtins.SubframeCall = SubframeCall  # type: ignore[attr-defined]
+builtins.invertDict = invertDict  # type: ignore[attr-defined]
+builtins.invertDictLossless = invertDictLossless  # type: ignore[attr-defined]
+builtins.getBase = getBase  # type: ignore[attr-defined]
+builtins.getRepository = getRepository  # type: ignore[attr-defined]
+builtins.safeRepr = safeRepr  # type: ignore[attr-defined]
+builtins.fastRepr = fastRepr  # type: ignore[attr-defined]
+builtins.nullGen = nullGen  # type: ignore[attr-defined]
+builtins.flywheel = flywheel  # type: ignore[attr-defined]
+builtins.loopGen = loopGen  # type: ignore[attr-defined]
 if __debug__:
-    builtins.StackTrace = StackTrace
-builtins.report = report
-builtins.pstatcollect = pstatcollect
-builtins.MiniLog = MiniLog
-builtins.MiniLogSentry = MiniLogSentry
-builtins.logBlock = logBlock
-builtins.HierarchyException = HierarchyException
-builtins.deeptype = deeptype
-builtins.Default = Default
-builtins.configIsToday = configIsToday
-builtins.typeName = typeName
-builtins.safeTypeName = safeTypeName
-builtins.histogramDict = histogramDict
+    builtins.StackTrace = StackTrace  # type: ignore[attr-defined]
+builtins.report = report  # type: ignore[attr-defined]
+builtins.pstatcollect = pstatcollect  # type: ignore[attr-defined]
+builtins.MiniLog = MiniLog  # type: ignore[attr-defined]
+builtins.MiniLogSentry = MiniLogSentry  # type: ignore[attr-defined]
+builtins.logBlock = logBlock  # type: ignore[attr-defined]
+builtins.HierarchyException = HierarchyException  # type: ignore[attr-defined]
+builtins.deeptype = deeptype  # type: ignore[attr-defined]
+builtins.Default = Default  # type: ignore[attr-defined]
+builtins.configIsToday = configIsToday  # type: ignore[attr-defined]
+builtins.typeName = typeName  # type: ignore[attr-defined]
+builtins.safeTypeName = safeTypeName  # type: ignore[attr-defined]
+builtins.histogramDict = histogramDict  # type: ignore[attr-defined]

+ 5 - 3
direct/src/showbase/ShowBase.py

@@ -120,7 +120,7 @@ from direct.extensions_native import NodePath_extensions # pylint: disable=unuse
 # This needs to be available early for DirectGUI imports
 import sys
 import builtins
-builtins.config = DConfig
+builtins.config = DConfig  # type: ignore[attr-defined]
 
 from direct.directnotify.DirectNotifyGlobal import directNotify, giveNotify
 from .MessengerGlobal import messenger
@@ -691,6 +691,7 @@ class ShowBase(DirectObject.DirectObject):
             allowAccessibilityShortcutKeys(True)
             self.__disabledStickyKeys = False
 
+        self.__directObject.ignoreAll()
         self.ignoreAll()
         self.shutdown()
 
@@ -3314,8 +3315,9 @@ class ShowBase(DirectObject.DirectObject):
         init_app_for_gui()
 
         # Disable the Windows message loop, since Tcl wants to handle this all
-        # on its own.
-        ConfigVariableBool('disable-message-loop', False).value = True
+        # on its own, except if the Panda window is on a separate thread.
+        if self.graphicsEngine.getThreadingModel().getDrawStage() == 0:
+            ConfigVariableBool('disable-message-loop', False).value = True
 
         if ConfigVariableBool('tk-main-loop', True):
             # Put Tkinter in charge of the main loop.  It really

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

@@ -11,7 +11,7 @@ Note that you cannot directly import :data:`~builtins.base` from this module
 since ShowBase may not have been created yet; instead, ShowBase dynamically
 adds itself to this module's scope when instantiated."""
 
-__all__ = []
+__all__ = ()
 
 from .ShowBase import ShowBase, WindowControls # pylint: disable=unused-import
 from direct.directnotify.DirectNotifyGlobal import directNotify, giveNotify # pylint: disable=unused-import
@@ -23,6 +23,8 @@ import warnings
 
 __dev__ = ConfigVariableBool('want-dev', __debug__).value
 
+base: ShowBase
+
 #: The global instance of the :ref:`virtual-file-system`, as obtained using
 #: :meth:`panda3d.core.VirtualFileSystem.getGlobalPtr()`.
 vfs = VirtualFileSystem.getGlobalPtr()
@@ -81,7 +83,7 @@ def inspect(anObject):
 
 
 import builtins
-builtins.inspect = inspect
+builtins.inspect = inspect  # type: ignore[attr-defined]
 
 # this also appears in AIBaseGlobal
 if (not __debug__) and __dev__:

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

@@ -3,7 +3,7 @@ This module hooks into Python's import mechanism to print out all imports to
 the standard output as they happen.
 """
 
-__all__ = []
+__all__ = ()
 
 
 import sys

+ 2 - 2
direct/src/showutil/TexMemWatcher.py

@@ -756,8 +756,8 @@ class TexMemWatcher(DirectObject):
 
         # Sort the regions from largest to smallest to maximize
         # packing effectiveness.
-        texRecords = list(self.texRecordsByTex.values())
-        texRecords.sort(key = lambda tr: (tr.tw, tr.th), reverse = True)
+        texRecords = sorted(self.texRecordsByTex.values(),
+                            key=lambda tr: (tr.tw, tr.th), reverse=True)
 
         for tr in texRecords:
             self.placeTexture(tr)

+ 2 - 2
direct/src/stdpy/pickle.py

@@ -45,7 +45,7 @@ BasePickler = pickle._Pickler
 BaseUnpickler = pickle._Unpickler
 
 
-class Pickler(BasePickler):
+class Pickler(BasePickler):  # type: ignore[misc, valid-type]
 
     def __init__(self, *args, **kw):
         self.bamWriter = BamWriter()
@@ -148,7 +148,7 @@ class Pickler(BasePickler):
         self.save_reduce(obj=obj, *rv)
 
 
-class Unpickler(BaseUnpickler):
+class Unpickler(BaseUnpickler):  # type: ignore[misc, valid-type]
 
     def __init__(self, *args, **kw):
         self.bamReader = BamReader()

+ 3 - 111
direct/src/stdpy/threading.py

@@ -83,8 +83,8 @@ class ThreadBase:
 # Copy these static methods from Panda's Thread object.  These are
 # useful if you may be running in Panda's SIMPLE_THREADS compilation
 # mode.
-ThreadBase.forceYield = core.Thread.forceYield
-ThreadBase.considerYield = core.Thread.considerYield
+ThreadBase.forceYield = core.Thread.forceYield  # type: ignore[attr-defined]
+ThreadBase.considerYield = core.Thread.considerYield  # type: ignore[attr-defined]
 
 
 class Thread(ThreadBase):
@@ -123,10 +123,7 @@ class Thread(ThreadBase):
         self.__dict__['ident'] = threadId
 
     def __del__(self):
-        # On interpreter shutdown, the _thread module might have
-        # already been cleaned up.
-        if _thread and _thread._remove_thread_id:
-            _thread._remove_thread_id(self.ident)
+        _thread._remove_thread_id(self.ident)
 
     def is_alive(self):
         thread = self.__thread
@@ -435,108 +432,3 @@ def setprofile(func):
 
 def stack_size(size = None):
     raise ThreadError
-
-
-if __debug__:
-    def _test():
-        from collections import deque
-
-        _sleep = core.Thread.sleep
-
-        _VERBOSE = False
-
-        class _Verbose(object):
-
-            def __init__(self, verbose=None):
-                if verbose is None:
-                    verbose = _VERBOSE
-                self.__verbose = verbose
-
-            def _note(self, format, *args):
-                if self.__verbose:
-                    format = format % args
-                    format = "%s: %s\n" % (
-                        currentThread().getName(), format)
-                    _sys.stderr.write(format)
-
-        class BoundedQueue(_Verbose):
-
-            def __init__(self, limit):
-                _Verbose.__init__(self)
-                self.mon = Lock(name = "BoundedQueue.mon")
-                self.rc = Condition(self.mon)
-                self.wc = Condition(self.mon)
-                self.limit = limit
-                self.queue = deque()
-
-            def put(self, item):
-                self.mon.acquire()
-                while len(self.queue) >= self.limit:
-                    self._note("put(%s): queue full", item)
-                    self.wc.wait()
-                self.queue.append(item)
-                self._note("put(%s): appended, length now %d",
-                           item, len(self.queue))
-                self.rc.notify()
-                self.mon.release()
-
-            def get(self):
-                self.mon.acquire()
-                while not self.queue:
-                    self._note("get(): queue empty")
-                    self.rc.wait()
-                item = self.queue.popleft()
-                self._note("get(): got %s, %d left", item, len(self.queue))
-                self.wc.notify()
-                self.mon.release()
-                return item
-
-        class ProducerThread(Thread):
-
-            def __init__(self, queue, quota):
-                Thread.__init__(self, name="Producer")
-                self.queue = queue
-                self.quota = quota
-
-            def run(self):
-                from random import random
-                counter = 0
-                while counter < self.quota:
-                    counter = counter + 1
-                    self.queue.put("%s.%d" % (self.getName(), counter))
-                    _sleep(random() * 0.00001)
-
-        class ConsumerThread(Thread):
-
-            def __init__(self, queue, count):
-                Thread.__init__(self, name="Consumer")
-                self.queue = queue
-                self.count = count
-
-            def run(self):
-                while self.count > 0:
-                    item = self.queue.get()
-                    print(item)
-                    self.count = self.count - 1
-
-        NP = 3
-        QL = 4
-        NI = 5
-
-        Q = BoundedQueue(QL)
-        P = []
-        for i in range(NP):
-            t = ProducerThread(Q, NI)
-            t.setName("Producer-%d" % (i+1))
-            P.append(t)
-        C = ConsumerThread(Q, NI*NP)
-        for t in P:
-            t.start()
-            _sleep(0.000001)
-        C.start()
-        for t in P:
-            t.join()
-        C.join()
-
-    if __name__ == '__main__':
-        _test()

+ 1 - 90
direct/src/stdpy/threading2.py

@@ -65,7 +65,7 @@ if __debug__:
 
 else:
     # Disable this when using "python -O"
-    class _Verbose(object):
+    class _Verbose(object):  # type: ignore[no-redef]
         def __init__(self, verbose=None):
             pass
         def _note(self, *args):
@@ -762,92 +762,3 @@ def main_thread():
 ##     from thread import _local as local
 ## except ImportError:
 ##     from _threading_local import local
-
-
-# Self-test code
-if __debug__:
-    def _test():
-        from collections import deque
-
-        class BoundedQueue(_Verbose):
-
-            def __init__(self, limit):
-                _Verbose.__init__(self)
-                self.mon = RLock()
-                self.rc = Condition(self.mon)
-                self.wc = Condition(self.mon)
-                self.limit = limit
-                self.queue = deque()
-
-            def put(self, item):
-                self.mon.acquire()
-                while len(self.queue) >= self.limit:
-                    self._note("put(%s): queue full", item)
-                    self.wc.wait()
-                self.queue.append(item)
-                self._note("put(%s): appended, length now %d",
-                           item, len(self.queue))
-                self.rc.notify()
-                self.mon.release()
-
-            def get(self):
-                self.mon.acquire()
-                while not self.queue:
-                    self._note("get(): queue empty")
-                    self.rc.wait()
-                item = self.queue.popleft()
-                self._note("get(): got %s, %d left", item, len(self.queue))
-                self.wc.notify()
-                self.mon.release()
-                return item
-
-        class ProducerThread(Thread):
-
-            def __init__(self, queue, quota):
-                Thread.__init__(self, name="Producer")
-                self.queue = queue
-                self.quota = quota
-
-            def run(self):
-                from random import random
-                counter = 0
-                while counter < self.quota:
-                    counter = counter + 1
-                    self.queue.put("%s.%d" % (self.getName(), counter))
-                    _sleep(random() * 0.00001)
-
-
-        class ConsumerThread(Thread):
-
-            def __init__(self, queue, count):
-                Thread.__init__(self, name="Consumer")
-                self.queue = queue
-                self.count = count
-
-            def run(self):
-                while self.count > 0:
-                    item = self.queue.get()
-                    print(item)
-                    self.count = self.count - 1
-
-        NP = 3
-        QL = 4
-        NI = 5
-
-        Q = BoundedQueue(QL)
-        P = []
-        for i in range(NP):
-            t = ProducerThread(Q, NI)
-            t.setName("Producer-%d" % (i+1))
-            P.append(t)
-        C = ConsumerThread(Q, NI*NP)
-        for t in P:
-            t.start()
-            _sleep(0.000001)
-        C.start()
-        for t in P:
-            t.join()
-        C.join()
-
-    if __name__ == '__main__':
-        _test()

+ 1 - 527
direct/src/task/Task.py

@@ -24,7 +24,7 @@ if hasattr(sys, 'getandroidapilevel'):
     signal = None
 else:
     try:
-        import _signal as signal
+        import _signal as signal  # type: ignore[import, no-redef]
     except ImportError:
         signal = None
 
@@ -801,532 +801,6 @@ class TaskManager:
     #        time.sleep(delta)
     #        delta = minFinTime - self.globalClock.getRealTime()
 
-    if __debug__:
-        # to catch memory leaks during the tests at the bottom of the file
-        def _startTrackingMemLeaks(self):
-            pass
-
-        def _stopTrackingMemLeaks(self):
-            pass
-
-        def _checkMemLeaks(self):
-            pass
-
-    def _runTests(self):
-        if __debug__:
-            tm = TaskManager()
-            tm.setClock(ClockObject())
-            tm.setupTaskChain("default", tickClock = True)
-
-            # check for memory leaks after every test
-            tm._startTrackingMemLeaks()
-            tm._checkMemLeaks()
-
-            # run-once task
-            l = []
-
-            def _testDone(task, l=l):
-                l.append(None)
-                return task.done
-            tm.add(_testDone, 'testDone')
-            tm.step()
-            assert len(l) == 1
-            tm.step()
-            assert len(l) == 1
-            _testDone = None
-            tm._checkMemLeaks()
-
-            # remove by name
-            def _testRemoveByName(task):
-                return task.done
-            tm.add(_testRemoveByName, 'testRemoveByName')
-            assert tm.remove('testRemoveByName') == 1
-            assert tm.remove('testRemoveByName') == 0
-            _testRemoveByName = None
-            tm._checkMemLeaks()
-
-            # duplicate named tasks
-            def _testDupNamedTasks(task):
-                return task.done
-            tm.add(_testDupNamedTasks, 'testDupNamedTasks')
-            tm.add(_testDupNamedTasks, 'testDupNamedTasks')
-            assert tm.remove('testRemoveByName') == 0
-            _testDupNamedTasks = None
-            tm._checkMemLeaks()
-
-            # continued task
-            l = []
-
-            def _testCont(task, l = l):
-                l.append(None)
-                return task.cont
-            tm.add(_testCont, 'testCont')
-            tm.step()
-            assert len(l) == 1
-            tm.step()
-            assert len(l) == 2
-            tm.remove('testCont')
-            _testCont = None
-            tm._checkMemLeaks()
-
-            # continue until done task
-            l = []
-
-            def _testContDone(task, l = l):
-                l.append(None)
-                if len(l) >= 2:
-                    return task.done
-                else:
-                    return task.cont
-            tm.add(_testContDone, 'testContDone')
-            tm.step()
-            assert len(l) == 1
-            tm.step()
-            assert len(l) == 2
-            tm.step()
-            assert len(l) == 2
-            assert not tm.hasTaskNamed('testContDone')
-            _testContDone = None
-            tm._checkMemLeaks()
-
-            # hasTaskNamed
-            def _testHasTaskNamed(task):
-                return task.done
-            tm.add(_testHasTaskNamed, 'testHasTaskNamed')
-            assert tm.hasTaskNamed('testHasTaskNamed')
-            tm.step()
-            assert not tm.hasTaskNamed('testHasTaskNamed')
-            _testHasTaskNamed = None
-            tm._checkMemLeaks()
-
-            # task sort
-            l = []
-
-            def _testPri1(task, l = l):
-                l.append(1)
-                return task.cont
-
-            def _testPri2(task, l = l):
-                l.append(2)
-                return task.cont
-            tm.add(_testPri1, 'testPri1', sort = 1)
-            tm.add(_testPri2, 'testPri2', sort = 2)
-            tm.step()
-            assert len(l) == 2
-            assert l == [1, 2,]
-            tm.step()
-            assert len(l) == 4
-            assert l == [1, 2, 1, 2,]
-            tm.remove('testPri1')
-            tm.remove('testPri2')
-            _testPri1 = None
-            _testPri2 = None
-            tm._checkMemLeaks()
-
-            # task extraArgs
-            l = []
-
-            def _testExtraArgs(arg1, arg2, l=l):
-                l.extend([arg1, arg2,])
-                return done
-            tm.add(_testExtraArgs, 'testExtraArgs', extraArgs=[4,5])
-            tm.step()
-            assert len(l) == 2
-            assert l == [4, 5,]
-            _testExtraArgs = None
-            tm._checkMemLeaks()
-
-            # task appendTask
-            l = []
-
-            def _testAppendTask(arg1, arg2, task, l=l):
-                l.extend([arg1, arg2,])
-                return task.done
-            tm.add(_testAppendTask, '_testAppendTask', extraArgs=[4,5], appendTask=True)
-            tm.step()
-            assert len(l) == 2
-            assert l == [4, 5,]
-            _testAppendTask = None
-            tm._checkMemLeaks()
-
-            # task uponDeath
-            l = []
-
-            def _uponDeathFunc(task, l=l):
-                l.append(task.name)
-
-            def _testUponDeath(task):
-                return done
-            tm.add(_testUponDeath, 'testUponDeath', uponDeath=_uponDeathFunc)
-            tm.step()
-            assert len(l) == 1
-            assert l == ['testUponDeath']
-            _testUponDeath = None
-            _uponDeathFunc = None
-            tm._checkMemLeaks()
-
-            # task owner
-            class _TaskOwner:
-                def _addTask(self, task):
-                    self.addedTaskName = task.name
-
-                def _clearTask(self, task):
-                    self.clearedTaskName = task.name
-            to = _TaskOwner()
-            l = []
-
-            def _testOwner(task):
-                return done
-            tm.add(_testOwner, 'testOwner', owner=to)
-            tm.step()
-            assert getattr(to, 'addedTaskName', None) == 'testOwner'
-            assert getattr(to, 'clearedTaskName', None) == 'testOwner'
-            _testOwner = None
-            del to
-            _TaskOwner = None
-            tm._checkMemLeaks()
-
-            doLaterTests = [0,]
-
-            # doLater
-            l = []
-
-            def _testDoLater1(task, l=l):
-                l.append(1)
-
-            def _testDoLater2(task, l=l):
-                l.append(2)
-
-            def _monitorDoLater(task, tm=tm, l=l, doLaterTests=doLaterTests):
-                if task.time > .03:
-                    assert l == [1, 2,]
-                    doLaterTests[0] -= 1
-                    return task.done
-                return task.cont
-            tm.doMethodLater(.01, _testDoLater1, 'testDoLater1')
-            tm.doMethodLater(.02, _testDoLater2, 'testDoLater2')
-            doLaterTests[0] += 1
-            # make sure we run this task after the doLaters if they all occur on the same frame
-            tm.add(_monitorDoLater, 'monitorDoLater', sort=10)
-            _testDoLater1 = None
-            _testDoLater2 = None
-            _monitorDoLater = None
-            # don't check until all the doLaters are finished
-            #tm._checkMemLeaks()
-
-            # doLater sort
-            l = []
-
-            def _testDoLaterPri1(task, l=l):
-                l.append(1)
-
-            def _testDoLaterPri2(task, l=l):
-                l.append(2)
-
-            def _monitorDoLaterPri(task, tm=tm, l=l, doLaterTests=doLaterTests):
-                if task.time > .02:
-                    assert l == [1, 2,]
-                    doLaterTests[0] -= 1
-                    return task.done
-                return task.cont
-            tm.doMethodLater(.01, _testDoLaterPri1, 'testDoLaterPri1', sort=1)
-            tm.doMethodLater(.01, _testDoLaterPri2, 'testDoLaterPri2', sort=2)
-            doLaterTests[0] += 1
-            # make sure we run this task after the doLaters if they all occur on the same frame
-            tm.add(_monitorDoLaterPri, 'monitorDoLaterPri', sort=10)
-            _testDoLaterPri1 = None
-            _testDoLaterPri2 = None
-            _monitorDoLaterPri = None
-            # don't check until all the doLaters are finished
-            #tm._checkMemLeaks()
-
-            # doLater extraArgs
-            l = []
-
-            def _testDoLaterExtraArgs(arg1, l=l):
-                l.append(arg1)
-
-            def _monitorDoLaterExtraArgs(task, tm=tm, l=l, doLaterTests=doLaterTests):
-                if task.time > .02:
-                    assert l == [3,]
-                    doLaterTests[0] -= 1
-                    return task.done
-                return task.cont
-            tm.doMethodLater(.01, _testDoLaterExtraArgs, 'testDoLaterExtraArgs', extraArgs=[3,])
-            doLaterTests[0] += 1
-            # make sure we run this task after the doLaters if they all occur on the same frame
-            tm.add(_monitorDoLaterExtraArgs, 'monitorDoLaterExtraArgs', sort=10)
-            _testDoLaterExtraArgs = None
-            _monitorDoLaterExtraArgs = None
-            # don't check until all the doLaters are finished
-            #tm._checkMemLeaks()
-
-            # doLater appendTask
-            l = []
-
-            def _testDoLaterAppendTask(arg1, task, l=l):
-                assert task.name == 'testDoLaterAppendTask'
-                l.append(arg1)
-
-            def _monitorDoLaterAppendTask(task, tm=tm, l=l, doLaterTests=doLaterTests):
-                if task.time > .02:
-                    assert l == [4,]
-                    doLaterTests[0] -= 1
-                    return task.done
-                return task.cont
-            tm.doMethodLater(.01, _testDoLaterAppendTask, 'testDoLaterAppendTask',
-                             extraArgs=[4,], appendTask=True)
-            doLaterTests[0] += 1
-            # make sure we run this task after the doLaters if they all occur on the same frame
-            tm.add(_monitorDoLaterAppendTask, 'monitorDoLaterAppendTask', sort=10)
-            _testDoLaterAppendTask = None
-            _monitorDoLaterAppendTask = None
-            # don't check until all the doLaters are finished
-            #tm._checkMemLeaks()
-
-            # doLater uponDeath
-            l = []
-
-            def _testUponDeathFunc(task, l=l):
-                assert task.name == 'testDoLaterUponDeath'
-                l.append(10)
-
-            def _testDoLaterUponDeath(arg1, l=l):
-                return done
-
-            def _monitorDoLaterUponDeath(task, tm=tm, l=l, doLaterTests=doLaterTests):
-                if task.time > .02:
-                    assert l == [10,]
-                    doLaterTests[0] -= 1
-                    return task.done
-                return task.cont
-            tm.doMethodLater(.01, _testDoLaterUponDeath, 'testDoLaterUponDeath',
-                             uponDeath=_testUponDeathFunc)
-            doLaterTests[0] += 1
-            # make sure we run this task after the doLaters if they all occur on the same frame
-            tm.add(_monitorDoLaterUponDeath, 'monitorDoLaterUponDeath', sort=10)
-            _testUponDeathFunc = None
-            _testDoLaterUponDeath = None
-            _monitorDoLaterUponDeath = None
-            # don't check until all the doLaters are finished
-            #tm._checkMemLeaks()
-
-            # doLater owner
-            class _DoLaterOwner:
-                def _addTask(self, task):
-                    self.addedTaskName = task.name
-
-                def _clearTask(self, task):
-                    self.clearedTaskName = task.name
-            doLaterOwner = _DoLaterOwner()
-            l = []
-
-            def _testDoLaterOwner(l=l):
-                pass
-
-            def _monitorDoLaterOwner(task, tm=tm, l=l, doLaterOwner=doLaterOwner,
-                                     doLaterTests=doLaterTests):
-                if task.time > .02:
-                    assert getattr(doLaterOwner, 'addedTaskName', None) == 'testDoLaterOwner'
-                    assert getattr(doLaterOwner, 'clearedTaskName', None) == 'testDoLaterOwner'
-                    doLaterTests[0] -= 1
-                    return task.done
-                return task.cont
-            tm.doMethodLater(.01, _testDoLaterOwner, 'testDoLaterOwner',
-                             owner=doLaterOwner)
-            doLaterTests[0] += 1
-            # make sure we run this task after the doLaters if they all occur on the same frame
-            tm.add(_monitorDoLaterOwner, 'monitorDoLaterOwner', sort=10)
-            _testDoLaterOwner = None
-            _monitorDoLaterOwner = None
-            del doLaterOwner
-            _DoLaterOwner = None
-            # don't check until all the doLaters are finished
-            #tm._checkMemLeaks()
-
-            # run the doLater tests
-            while doLaterTests[0] > 0:
-                tm.step()
-            del doLaterTests
-            tm._checkMemLeaks()
-
-            # getTasks
-            def _testGetTasks(task):
-                return task.cont
-            # No doLaterProcessor in the new world.
-            assert len(tm.getTasks()) == 0
-            tm.add(_testGetTasks, 'testGetTasks1')
-            assert len(tm.getTasks()) == 1
-            assert (tm.getTasks()[0].name == 'testGetTasks1' or
-                    tm.getTasks()[1].name == 'testGetTasks1')
-            tm.add(_testGetTasks, 'testGetTasks2')
-            tm.add(_testGetTasks, 'testGetTasks3')
-            assert len(tm.getTasks()) == 3
-            tm.remove('testGetTasks2')
-            assert len(tm.getTasks()) == 2
-            tm.remove('testGetTasks1')
-            tm.remove('testGetTasks3')
-            assert len(tm.getTasks()) == 0
-            _testGetTasks = None
-            tm._checkMemLeaks()
-
-            # getDoLaters
-            def _testGetDoLaters():
-                pass
-            assert len(tm.getDoLaters()) == 0
-            tm.doMethodLater(.1, _testGetDoLaters, 'testDoLater1')
-            assert len(tm.getDoLaters()) == 1
-            assert tm.getDoLaters()[0].name == 'testDoLater1'
-            tm.doMethodLater(.1, _testGetDoLaters, 'testDoLater2')
-            tm.doMethodLater(.1, _testGetDoLaters, 'testDoLater3')
-            assert len(tm.getDoLaters()) == 3
-            tm.remove('testDoLater2')
-            assert len(tm.getDoLaters()) == 2
-            tm.remove('testDoLater1')
-            tm.remove('testDoLater3')
-            assert len(tm.getDoLaters()) == 0
-            _testGetDoLaters = None
-            tm._checkMemLeaks()
-
-            # duplicate named doLaters removed via taskMgr.remove
-            def _testDupNameDoLaters():
-                pass
-            # the doLaterProcessor is always running
-            tm.doMethodLater(.1, _testDupNameDoLaters, 'testDupNameDoLater')
-            tm.doMethodLater(.1, _testDupNameDoLaters, 'testDupNameDoLater')
-            assert len(tm.getDoLaters()) == 2
-            tm.remove('testDupNameDoLater')
-            assert len(tm.getDoLaters()) == 0
-            _testDupNameDoLaters = None
-            tm._checkMemLeaks()
-
-            # duplicate named doLaters removed via remove()
-            def _testDupNameDoLatersRemove():
-                pass
-            # the doLaterProcessor is always running
-            dl1 = tm.doMethodLater(.1, _testDupNameDoLatersRemove, 'testDupNameDoLaterRemove')
-            dl2 = tm.doMethodLater(.1, _testDupNameDoLatersRemove, 'testDupNameDoLaterRemove')
-            assert len(tm.getDoLaters()) == 2
-            dl2.remove()
-            assert len(tm.getDoLaters()) == 1
-            dl1.remove()
-            assert len(tm.getDoLaters()) == 0
-            _testDupNameDoLatersRemove = None
-            # nameDict etc. isn't cleared out right away with task.remove()
-            tm._checkMemLeaks()
-
-            # getTasksNamed
-            def _testGetTasksNamed(task):
-                return task.cont
-            assert len(tm.getTasksNamed('testGetTasksNamed')) == 0
-            tm.add(_testGetTasksNamed, 'testGetTasksNamed')
-            assert len(tm.getTasksNamed('testGetTasksNamed')) == 1
-            assert tm.getTasksNamed('testGetTasksNamed')[0].name == 'testGetTasksNamed'
-            tm.add(_testGetTasksNamed, 'testGetTasksNamed')
-            tm.add(_testGetTasksNamed, 'testGetTasksNamed')
-            assert len(tm.getTasksNamed('testGetTasksNamed')) == 3
-            tm.remove('testGetTasksNamed')
-            assert len(tm.getTasksNamed('testGetTasksNamed')) == 0
-            _testGetTasksNamed = None
-            tm._checkMemLeaks()
-
-            # removeTasksMatching
-            def _testRemoveTasksMatching(task):
-                return task.cont
-            tm.add(_testRemoveTasksMatching, 'testRemoveTasksMatching')
-            assert len(tm.getTasksNamed('testRemoveTasksMatching')) == 1
-            tm.removeTasksMatching('testRemoveTasksMatching')
-            assert len(tm.getTasksNamed('testRemoveTasksMatching')) == 0
-            tm.add(_testRemoveTasksMatching, 'testRemoveTasksMatching1')
-            tm.add(_testRemoveTasksMatching, 'testRemoveTasksMatching2')
-            assert len(tm.getTasksNamed('testRemoveTasksMatching1')) == 1
-            assert len(tm.getTasksNamed('testRemoveTasksMatching2')) == 1
-            tm.removeTasksMatching('testRemoveTasksMatching*')
-            assert len(tm.getTasksNamed('testRemoveTasksMatching1')) == 0
-            assert len(tm.getTasksNamed('testRemoveTasksMatching2')) == 0
-            tm.add(_testRemoveTasksMatching, 'testRemoveTasksMatching1a')
-            tm.add(_testRemoveTasksMatching, 'testRemoveTasksMatching2a')
-            assert len(tm.getTasksNamed('testRemoveTasksMatching1a')) == 1
-            assert len(tm.getTasksNamed('testRemoveTasksMatching2a')) == 1
-            tm.removeTasksMatching('testRemoveTasksMatching?a')
-            assert len(tm.getTasksNamed('testRemoveTasksMatching1a')) == 0
-            assert len(tm.getTasksNamed('testRemoveTasksMatching2a')) == 0
-            _testRemoveTasksMatching = None
-            tm._checkMemLeaks()
-
-            # create Task object and add to mgr
-            l = []
-
-            def _testTaskObj(task, l=l):
-                l.append(None)
-                return task.cont
-            t = Task(_testTaskObj)
-            tm.add(t, 'testTaskObj')
-            tm.step()
-            assert len(l) == 1
-            tm.step()
-            assert len(l) == 2
-            tm.remove('testTaskObj')
-            tm.step()
-            assert len(l) == 2
-            _testTaskObj = None
-            tm._checkMemLeaks()
-
-            # remove Task via task.remove()
-            l = []
-
-            def _testTaskObjRemove(task, l=l):
-                l.append(None)
-                return task.cont
-            t = Task(_testTaskObjRemove)
-            tm.add(t, 'testTaskObjRemove')
-            tm.step()
-            assert len(l) == 1
-            tm.step()
-            assert len(l) == 2
-            t.remove()
-            tm.step()
-            assert len(l) == 2
-            del t
-            _testTaskObjRemove = None
-            tm._checkMemLeaks()
-
-            # this test fails, and it's not clear what the correct behavior should be.
-            # sort passed to Task.__init__ is always overridden by taskMgr.add()
-            # even if no sort is specified, and calling Task.setSort() has no
-            # effect on the taskMgr's behavior.
-            # set/get Task sort
-            #l = []
-            #def _testTaskObjSort(arg, task, l=l):
-            #    l.append(arg)
-            #    return task.cont
-            #t1 = Task(_testTaskObjSort, sort=1)
-            #t2 = Task(_testTaskObjSort, sort=2)
-            #tm.add(t1, 'testTaskObjSort1', extraArgs=['a',], appendTask=True)
-            #tm.add(t2, 'testTaskObjSort2', extraArgs=['b',], appendTask=True)
-            #tm.step()
-            #assert len(l) == 2
-            #assert l == ['a', 'b']
-            #assert t1.getSort() == 1
-            #assert t2.getSort() == 2
-            #t1.setSort(3)
-            #assert t1.getSort() == 3
-            #tm.step()
-            #assert len(l) == 4
-            #assert l == ['a', 'b', 'b', 'a',]
-            #t1.remove()
-            #t2.remove()
-            #tm.step()
-            #assert len(l) == 4
-            #del t1
-            #del t2
-            #_testTaskObjSort = None
-            #tm._checkMemLeaks()
-
-            del l
-            tm.destroy()
-            del tm
-
 
 if __debug__:
     def checkLeak():

+ 1 - 2
direct/src/tkpanels/FSMInspector.py

@@ -358,8 +358,7 @@ class FSMInspector(AppShell):
 
     def printLayout(self):
         dict = self.stateInspectorDict
-        keys = list(dict.keys())
-        keys.sort()
+        keys = sorted(dict)
         print("ClassicFSM.ClassicFSM('%s', [" % self.name)
         for key in keys[:-1]:
             si = dict[key]

+ 2 - 6
direct/src/tkpanels/Inspector.py

@@ -95,9 +95,7 @@ class Inspector:
 
     def initializePartsList(self):
         self._partsList = []
-        keys = self.namedParts()
-        keys.sort()
-        for each in keys:
+        for each in sorted(self.namedParts()):
             self._partsList.append(each)
             #if not callable(getattr(self.object, each)):
             #    self._partsList.append(each)
@@ -202,9 +200,7 @@ class DictionaryInspector(Inspector):
 
     def initializePartsList(self):
         Inspector.initializePartsList(self)
-        keys = list(self.object.keys())
-        keys.sort()
-        for each in keys:
+        for each in sorted(self.object):
             self._partsList.append(each)
 
     def partNumber(self, partNumber):

+ 3 - 3
direct/src/tkpanels/NotifyPanel.py

@@ -128,12 +128,12 @@ class NotifyPanel:
         topCategory = Notify.ptr().getTopCategory()
         return self._getPandaCategories(topCategory)
 
-    def _getPandaCategoriesAsList(self, pc, list):
+    def _getPandaCategoriesAsList(self, pc, catList):
         for item in pc:
             if isinstance(item, list):
-                self._getPandaCategoriesAsList(item, list)
+                self._getPandaCategoriesAsList(item, catList)
             else:
-                list.append(item)
+                catList.append(item)
 
     def getPandaCategoriesAsList(self):
         pc = self.getPandaCategories()

+ 2 - 24
direct/src/tkpanels/ParticlePanel.py

@@ -1174,9 +1174,7 @@ class ParticlePanel(AppShell):
         self.particlesLabelMenu.add_separator()
         # Add in a checkbutton for each effect (to toggle on/off)
         particles = self.particleEffect.getParticlesList()
-        names = [x.getName() for x in particles]
-        names.sort()
-        for name in names:
+        for name in sorted(x.getName() for x in particles):
             particle = self.particleEffect.getParticlesNamed(name)
             self.particlesLabelMenu.add_command(
                 label = name,
@@ -1199,9 +1197,7 @@ class ParticlePanel(AppShell):
         self.forceGroupLabelMenu.add_separator()
         # Add in a checkbutton for each effect (to toggle on/off)
         forceGroupList = self.particleEffect.getForceGroupList()
-        names = [x.getName() for x in forceGroupList]
-        names.sort()
-        for name in names:
+        for name in sorted(x.getName() for x in forceGroupList):
             force = self.particleEffect.getForceGroupNamed(name)
             self.forceGroupLabelMenu.add_command(
                 label = name,
@@ -2913,21 +2909,3 @@ class ParticlePanel(AppShell):
                            min = 0.01,
                            value = force.getRadius())
         self.createForceActiveWidget(frame, pageName, forceName, force)
-
-######################################################################
-
-
-# Create demo in root window for testing.
-if __name__ == '__main__':
-    try:
-        base
-    except NameError:
-        from direct.showbase.ShowBase import ShowBase
-        base = ShowBase()
-
-    root = Pmw.initialise()
-    pp = ParticlePanel()
-    base.pp=pp
-    #ve = VectorEntry(Toplevel(), relief = GROOVE)
-    #ve.pack()
-    base.run()

+ 0 - 8
direct/src/tkpanels/Placer.py

@@ -786,11 +786,3 @@ class Placer(AppShell):
 
 def place(nodePath):
     return Placer(nodePath = nodePath)
-
-######################################################################
-
-
-# Create demo in root window for testing.
-if __name__ == '__main__':
-    root = Pmw.initialise()
-    widget = Placer()

+ 1 - 3
direct/src/tkpanels/TaskManagerPanel.py

@@ -151,10 +151,8 @@ class TaskManagerWidget(DirectObject):
         # Get a list of task names
         taskNames = []
         self.__taskDict = {}
-        tasks = self.taskMgr.getTasks()
-        tasks.sort(key = lambda t: t.getName())
         count = 0
-        for task in tasks:
+        for task in sorted(self.taskMgr.getTasks(), key=lambda t: t.getName()):
             taskNames.append(task.getName())
             self.__taskDict[count] = task
             count += 1

+ 2 - 6
direct/src/tkwidgets/AppShell.py

@@ -21,11 +21,11 @@ import builtins
 
 # Create toplevel widget dictionary
 if not hasattr(builtins, "widgetDict"):
-    builtins.widgetDict = {}
+    builtins.widgetDict = {}  # type: ignore[attr-defined]
 
 # Create toplevel variable dictionary
 if not hasattr(builtins, "variableDict"):
-    builtins.variableDict = {}
+    builtins.variableDict = {}  # type: ignore[attr-defined]
 
 
 def resetWidgetDict():
@@ -550,7 +550,3 @@ class TestAppShell(AppShell):
     def createInterface(self):
         self.createButtons()
         self.createMain()
-
-
-if __name__ == '__main__':
-    test = TestAppShell(balloon_state='none')

+ 0 - 15
direct/src/tkwidgets/Dial.py

@@ -422,18 +422,3 @@ class DialWidget(Pmw.MegaWidget):
         """ User redefinable callback executed on button release """
         if self['postCallback']:
             self['postCallback'](*self['callbackData'])
-
-
-if __name__ == '__main__':
-    tl = tk.Toplevel()
-    d = Dial(tl)
-    d2 = Dial(tl, dial_numSegments = 12, max = 360,
-              dial_fRollover = 0, value = 180)
-    d3 = Dial(tl, dial_numSegments = 12, max = 90, min = -90,
-              dial_fRollover = 0)
-    d4 = Dial(tl, dial_numSegments = 16, max = 256,
-              dial_fRollover = 0)
-    d.pack(expand = 1, fill = tk.X)
-    d2.pack(expand = 1, fill = tk.X)
-    d3.pack(expand = 1, fill = tk.X)
-    d4.pack(expand = 1, fill = tk.X)

+ 0 - 42
direct/src/tkwidgets/EntryScale.py

@@ -517,45 +517,3 @@ def rgbPanel(nodePath, callback = None):
         messenger.send('RGBPanel_setColor', [nodePath, r, g, b, a])
     esg['postCallback'] = onRelease
     return esg
-
-
-## SAMPLE CODE
-if __name__ == '__main__':
-    # Initialise Tkinter and Pmw.
-    root = tk.Toplevel()
-    root.title('Pmw EntryScale demonstration')
-
-    # Dummy command
-    def printVal(val):
-        print(val)
-
-    # Create and pack a EntryScale megawidget.
-    mega1 = EntryScale(root, command = printVal)
-    mega1.pack(side = 'left', expand = 1, fill = 'x')
-
-    # These are things you can set/configure
-    # Starting value for entryScale
-    #mega1['value'] = 123.456
-    #mega1['text'] = 'Drive delta X'
-    #mega1['min'] = 0.0
-    #mega1['max'] = 1000.0
-    #mega1['resolution'] = 1.0
-    # To change the color of the label:
-    #mega1.label['foreground'] = 'Red'
-    # Max change/update, default is 100
-    # To have really fine control, for example
-    # mega1['maxVelocity'] = 0.1
-    # Number of digits to the right of the decimal point, default = 2
-    # mega1['numDigits'] = 5
-
-    # To create a entryScale group to set an RGBA value:
-    group1 = EntryScaleGroup(root, dim = 4,
-                          title = 'Simple RGBA Panel',
-                          labels = ('R', 'G', 'B', 'A'),
-                          Valuator_min = 0.0,
-                          Valuator_max = 255.0,
-                          Valuator_resolution = 1.0,
-                          command = printVal)
-
-    # Uncomment this if you aren't running in IDLE
-    #root.mainloop()

+ 0 - 39
direct/src/tkwidgets/Floater.py

@@ -329,42 +329,3 @@ class FloaterGroup(Pmw.MegaToplevel):
 
     def reset(self):
         self.set(self['value'])
-
-
-## SAMPLE CODE
-if __name__ == '__main__':
-    # Initialise Tkinter and Pmw.
-    root = tk.Toplevel()
-    root.title('Pmw Floater demonstration')
-
-    # Dummy command
-    def printVal(val):
-        print(val)
-
-    # Create and pack a Floater megawidget.
-    mega1 = Floater(root, command = printVal)
-    mega1.pack(side = 'left', expand = 1, fill = 'x')
-
-    # These are things you can set/configure
-    # Starting value for floater
-    #mega1['value'] = 123.456
-    #mega1['text'] = 'Drive delta X'
-    # To change the color of the label:
-    #mega1.label['foreground'] = 'Red'
-    # Max change/update, default is 100
-    # To have really fine control, for example
-    #mega1['maxVelocity'] = 0.1
-    # Number of digits to the right of the decimal point, default = 2
-    #mega1['numDigits'] = 5
-
-    # To create a floater group to set an RGBA value:
-    group1 = FloaterGroup(root, dim = 4,
-                          title = 'Simple RGBA Panel',
-                          labels = ('R', 'G', 'B', 'A'),
-                          Valuator_min = 0.0,
-                          Valuator_max = 255.0,
-                          Valuator_resolution = 1.0,
-                          command = printVal)
-
-    # Uncomment this if you aren't running in IDLE
-    #root.mainloop()

+ 0 - 14
direct/src/tkwidgets/VectorWidgets.py

@@ -329,17 +329,3 @@ class ColorEntry(VectorEntry):
             initialcolor = tuple(self.get()[:3]))[0]
         if color:
             self.set((color[0], color[1], color[2], self.getAt(3)))
-
-
-if __name__ == '__main__':
-    root = tk.Toplevel()
-    root.title('Vector Widget demo')
-
-    ve = VectorEntry(root)
-    ve.pack()
-    v3e = Vector3Entry(root)
-    v3e.pack()
-    v4e = Vector4Entry(root)
-    v4e.pack()
-    ce = ColorEntry(root)
-    ce.pack()

+ 1 - 2
direct/src/tkwidgets/WidgetPropertiesDialog.py

@@ -21,8 +21,7 @@ class WidgetPropertiesDialog(tk.Toplevel):
         self.propertyDict = propertyDict
         self.propertyList = propertyList
         if self.propertyList is None:
-            self.propertyList = list(self.propertyDict.keys())
-            self.propertyList.sort()
+            self.propertyList = sorted(self.propertyDict)
         # Use default parent if none specified
         if not parent:
             parent = tk._default_root

+ 4 - 2
direct/src/wxwidgets/ViewPort.py

@@ -6,6 +6,8 @@ Modified by gjeon.
 Modified by Summer 2010 Carnegie Mellon University ETC PandaLE team: fixed a bug in Viewport.Close
 """
 
+from __future__ import annotations
+
 __all__ = ["Viewport", "ViewportManager"]
 
 from panda3d.core import (
@@ -36,7 +38,7 @@ VPPERSPECTIVE = 13
 
 class ViewportManager:
     """Manages the global viewport stuff."""
-    viewports = []
+    viewports: list[Viewport] = []
     gsg = None
 
     @staticmethod
@@ -58,7 +60,7 @@ class ViewportManager:
             v.Layout(*args, **kwargs)
 
 
-class Viewport(WxPandaWindow, DirectObject):
+class Viewport(WxPandaWindow, DirectObject):  # type: ignore[misc, valid-type]
     """Class representing a 3D Viewport."""
     CREATENEW  = CREATENEW
     VPLEFT     = VPLEFT

+ 2 - 1
direct/src/wxwidgets/WxPandaStart.py

@@ -1,2 +1,3 @@
+from direct.showbase.ShowBaseGlobal import base
 from .WxPandaShell import WxPandaShell
-base.app = WxPandaShell()
+base.app = WxPandaShell()  # type: ignore[attr-defined]

+ 1 - 1
direct/src/wxwidgets/WxPandaWindow.py

@@ -81,7 +81,7 @@ class EmbeddedPandaWindow(wx.Window):
 if not hasattr(wxgl, 'GLCanvas'):
     OpenGLPandaWindow = None
 else:
-    class OpenGLPandaWindow(wxgl.GLCanvas):
+    class OpenGLPandaWindow(wxgl.GLCanvas):  # type: ignore[no-redef]
         """ This class implements a Panda3D "window" that actually draws
         within the wx GLCanvas object.  It is supported whenever OpenGL is
         Panda's rendering engine, and GLCanvas is available in wx. """

+ 200 - 0
doc/CODING_STYLE.md

@@ -0,0 +1,200 @@
+Panda3D C++ Style Guide
+=======================
+
+Rather than try to justify every decision made, this document attempts to be as
+at-a-glance as possible.  Examples provided where necessary to illustrate best
+practices.
+
+Note that this style guide is, for the most part, not very strict, and there
+are exceptions to every rule.  However, this guide does serve to illustrate the
+preferred style for the entire codebase.
+
+Naming conventions
+------------------
+
+- **Classes**: `CamelCaseFirstUpper`
+- **Filenames**: `lowerCamelCase.h`, `lowerCamelCase.cxx`
+  - Note, every header file must have a unique filename.  They are all
+    installed into the same path.
+- **Functions**: `lower_snake_case_without_prefix`
+- **Local variables**: `lower_snake_case_without_prefix`
+- **Member variables**: `_lower_snake_case_with_initial_score`
+- **Enum constants**: `SE_enum_value` (for an enum type named SomeEnum)
+- **Enum class constants**: `ENUM_VALUE`
+
+Indentation
+-----------
+
+Always use two spaces to indent.  Tabs should never appear in a C++ file.
+
+Access modifiers (`public`/`private`/`protected`/`PUBLISHED`) are never
+indented relative to the class they are in, and do not affect the indentation
+of the surrounding lines.
+
+Spaces
+------
+
+Always put one space after a control keyword, before the condition's
+parentheses.  Don't pad the inside of parentheses with spaces unless there are
+several nested parenthetical expressions and the extra spaces contribute
+substantially to readability.
+
+    if (x == y) {
+
+Don't place a space after a function name (declarations, definitions, or calls).
+
+    void function_name();
+    ...
+    function_name();
+
+Always place a space after each comma in an arguments list.
+
+    function_call(x, y, z);
+
+Always pad binary operators with a space on either side.
+
+    a || b
+
+Never end a line with trailing whitespace.
+
+Function definitions
+--------------------
+
+The recipe for a function definition is:
+
+1. JavaDoc comment describing the purpose of the function
+2. Function return value and class
+3. **At the beginning of its own line,** the function's name, followed by the
+   parameter list and anything else necessary for the function's signature.
+4. Opening brace on the same line, not its own line.
+5. Single empty line before next definition.
+
+For example:
+
+    /**
+     * String documenting the function, in JavaDoc style.
+     */
+    void ClassName::
+    function_name(int x, int y) const {
+      ...
+    }
+
+    /**
+     * ...
+     */
+    ...
+
+If the function is a constructor with an initializer list, the `:` goes on the
+same line as the constructor name, and the initializers are listed *one per
+line*, indented by two spaces.
+
+    ClassName::
+    ClassName() :
+      _a("a"),
+      _b("b"),
+      _c("c")
+    {
+    }
+
+Empty function bodies in a .cxx/.I file still contain a single line break (i.e.
+to put `}` on its own line).  In a .h file, an empty function can be given as
+`{}`
+
+Braces
+------
+
+Always put an opening `{` on the same line as the statement that calls for the
+brace.  There is exactly one exception to this: constructors with initializer
+lists.
+
+    SomeClass::
+    SomeClass() :
+      _one(1),
+      _two(2)
+    {
+      if (_one < _two) {
+        ...
+      }
+    }
+
+Comments
+--------
+
+Comments should be properly-formatted English, with proper capitalization and
+punctuation rules.  There are two spaces between sentences.
+
+Note that `//` is preferred over `/*` except in some special circumstances.
+
+Except for commented-out lines of code, there should be a space between the
+first word and the `//`.
+
+    // The following line is commented out.
+    //if (value == 0) {
+
+Line limits and continuations
+-----------------------------
+
+Lines should be limited to 80 columns.  This is not a hard limit, and lines
+should not be wrapped if doing so hurts readability, except comments, which
+should always be wrapped.
+
+Continuation lines shall be aligned to the same column as appropriate, or
+indented two spaces otherwise:
+
+    if ((a &&
+         b) ||
+        c) {
+      ...
+    }
+
+When choosing where to break a line, these are the preferred places, from best
+to worst:
+
+1. If inside a comment, break the comment to as many lines as appropriate, but
+   only after moving the comment to its own line.
+2. After a comma in an argument/parameter list.
+3. After a binary operator (leaving the binary operator at the end of the line)
+4. In a function declaration, after the function's return type but before the
+   function's name. (Indented by two spaces.)
+5. In a log statement with `<<`, before a `<<`. (Avoid ending a line with `<<`)
+6. Around a string literal, at a natural word boundary, with a space left on
+   the previous line (e.g. `"This is a "` / `"continued string literal."`)
+7. After the `=` in an assignment operator.
+8. As a last resort, after the opening `(` in a function call.
+
+If none of these rules can apply cleanly, it is okay to have lines extend past
+the 80-column boundary.
+
+Alignment
+---------
+
+Avoid the temptation to align multiple assignments in consecutive lines of code.
+While it may look neater, it decreases the maintainability of the code.
+
+Asterisks and ampersands indicating a pointer or reference type should always
+be aligned against the name, and not the type:
+
+    Type *x, &y;
+
+Grouping
+--------
+
+Try to group logically-similar lines, separating them with a single blank line.
+
+Modern language features
+------------------------
+
+Panda3D is a C++11 project.  The use of the following modern language features
+is greatly encouraged:
+
+1. `nullptr` over `NULL`
+2. `std::move` and move semantics
+3. Range-based for loops
+
+Using `auto` can be okay, such as when storing an iterator, but consider
+creating a typedef for the container type instead.
+
+Avoid using `std::function` in cases where a lambda can be accepted directly
+(using a template function), since it has extra overhead over lambdas.
+
+C++14 and C++17 features should be avoided for now.

+ 16 - 1
dtool/CompilerFlags.cmake

@@ -16,6 +16,7 @@ if(MSVC)
 else()
   set(CMAKE_C_FLAGS_STANDARD "-O3")
   set(CMAKE_CXX_FLAGS_STANDARD "-O3")
+  set(CMAKE_OBJCXX_FLAGS_STANDARD "-O3")
 endif()
 set(CMAKE_SHARED_LINKER_FLAGS_STANDARD "")
 set(CMAKE_MODULE_LINKER_FLAGS_STANDARD "")
@@ -27,6 +28,8 @@ if(CMAKE_CXX_COMPILER_ID MATCHES "(AppleClang|Clang)")
     "${CMAKE_C_FLAGS_DEBUG} -fprofile-instr-generate -fcoverage-mapping")
   set(CMAKE_CXX_FLAGS_COVERAGE
     "${CMAKE_CXX_FLAGS_DEBUG} -fprofile-instr-generate -fcoverage-mapping")
+  set(CMAKE_OBJCXX_FLAGS_COVERAGE
+    "${CMAKE_OBJCXX_FLAGS_DEBUG} -fprofile-instr-generate -fcoverage-mapping")
 
   set(CMAKE_SHARED_LINKER_FLAGS_COVERAGE
     "${CMAKE_SHARED_LINKER_FLAGS_DEBUG} -fprofile-instr-generate")
@@ -101,11 +104,14 @@ endif()
 # Set warning levels
 if(MSVC)
   set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /W3")
-  set(CMAKE_CCXX_FLAGS "${CMAKE_CXX_FLAGS} /W3")
+  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W3")
 
 else()
   set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall")
   set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall")
+  if(APPLE)
+    set(CMAKE_OBJCXX_FLAGS "${CMAKE_OBJCXX_FLAGS} -Wall")
+  endif()
 
 endif()
 
@@ -133,6 +139,14 @@ if(CMAKE_CXX_COMPILER_ID MATCHES "(GNU|Clang)")
   set(CMAKE_CXX_FLAGS_MINSIZEREL "${CMAKE_CXX_FLAGS_MINSIZEREL} ${release_flags}")
   set(CMAKE_CXX_FLAGS_STANDARD "${CMAKE_CXX_FLAGS_STANDARD} ${standard_flags}")
 
+  if(APPLE)
+    set(CMAKE_OBJCXX_FLAGS "${CMAKE_OBJCXX_FLAGS} ${global_flags}")
+    set(CMAKE_OBJCXX_FLAGS_RELEASE "${CMAKE_OBJCXX_FLAGS_RELEASE} ${global_flags}")
+    set(CMAKE_OBJCXX_FLAGS_RELWITHDEBINFO "${CMAKE_OBJCXX_FLAGS_RELWITHDEBINFO} ${global_flags}")
+    set(CMAKE_OBJCXX_FLAGS_MINSIZEREL "${CMAKE_OBJCXX_FLAGS_MINSIZEREL} ${global_flags}")
+    set(CMAKE_OBJCXX_FLAGS_STANDARD "${CMAKE_OBJCXX_FLAGS_STANDARD} ${global_flags}")
+  endif()
+
   if(MSVC)
     # Clang behaving as MSVC
     set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-unused-command-line-argument")
@@ -151,6 +165,7 @@ endif()
 # and stops us from identifying cases where ENABLE_EXPORTS is needed.
 set(CMAKE_SHARED_LIBRARY_LINK_C_FLAGS "")
 set(CMAKE_SHARED_LIBRARY_LINK_CXX_FLAGS "")
+set(CMAKE_SHARED_LIBRARY_LINK_OBJCXX_FLAGS "")
 
 # As long as we're figuring out compiler flags, figure out the flags for
 # turning C++ exception support on and off

+ 3 - 1
dtool/Package.cmake

@@ -518,7 +518,9 @@ package_status(OPUS "Opus")
 #
 
 # FMOD Ex
-find_package(FMODEx QUIET)
+if(NOT APPLE)
+  find_package(FMODEx QUIET)
+endif()
 
 package_option(FMODEx
   "This enables support for the FMOD Ex sound library,

Разница между файлами не показана из-за своего большого размера
+ 1884 - 1881
dtool/src/cppparser/cppBison.cxx.prebuilt


+ 10 - 1
dtool/src/cppparser/cppBison.yxx

@@ -2971,8 +2971,17 @@ base_specification:
         ;
 
 enum:
-        enum_decl '{' enum_body '}'
+        enum_decl
 {
+  if (current_enum->_scope != nullptr) {
+    push_scope(current_enum->_scope);
+  }
+}
+        '{' enum_body '}'
+{
+  if (current_enum->_scope != nullptr) {
+    pop_scope();
+  }
   $$ = current_enum;
   current_enum = nullptr;
 }

+ 1 - 0
dtool/src/dtoolbase/dtoolbase.h

@@ -130,6 +130,7 @@
 // flag to link in Python, we'll just excerpt the forward declaration of
 // PyObject.
 typedef struct _object PyObject;
+typedef struct _typeobject PyTypeObject;
 
 #ifndef HAVE_EIGEN
 // If we don't have the Eigen library, don't define LINMATH_ALIGN.

+ 17 - 1
dtool/src/dtoolbase/typeHandle.cxx

@@ -155,7 +155,7 @@ deallocate_array(void *ptr) {
 /**
  * Returns the internal void pointer that is stored for interrogate's benefit.
  */
-PyObject *TypeHandle::
+PyTypeObject *TypeHandle::
 get_python_type() const {
   TypeRegistryNode *rnode = TypeRegistry::ptr()->look_up(*this, nullptr);
   if (rnode != nullptr) {
@@ -164,6 +164,22 @@ get_python_type() const {
     return nullptr;
   }
 }
+
+/**
+ * Returns a Python wrapper object corresponding to the given C++ pointer.
+ */
+PyObject *TypeHandle::
+wrap_python(void *ptr, PyTypeObject *cast_from) const {
+  if (ptr == nullptr) {
+    return nullptr;
+  }
+  TypeRegistryNode *rnode = TypeRegistry::ptr()->look_up(*this, nullptr);
+  if (rnode != nullptr) {
+    return rnode->wrap_python(ptr, cast_from);
+  } else {
+    return nullptr;
+  }
+}
 #endif
 
 std::ostream &

+ 2 - 1
dtool/src/dtoolbase/typeHandle.h

@@ -140,7 +140,8 @@ PUBLISHED:
 
 public:
 #ifdef HAVE_PYTHON
-  PyObject *get_python_type() const;
+  PyTypeObject *get_python_type() const;
+  PyObject *wrap_python(void *ptr, PyTypeObject *cast_from = nullptr) const;
 #endif // HAVE_PYTHON
 
   void *allocate_array(size_t size) RETURNS_ALIGNED(MEMORY_HOOK_ALIGNMENT);

+ 20 - 1
dtool/src/dtoolbase/typeHandle_ext.cxx

@@ -46,7 +46,26 @@ __reduce__() const {
 
   // If we have a Python binding registered for it, that's the preferred method,
   // since it ensures that the appropriate module gets loaded by pickle.
-  PyObject *py_type = _this->get_python_type();
+  PyTypeObject *py_type = _this->get_python_type();
+  if (py_type != nullptr && py_type->tp_dict != nullptr) {
+    // Look for a get_class_type method, if it returns this handle.
+    PyObject *func = PyDict_GetItemString(py_type->tp_dict, "get_class_type");
+    if (func != nullptr && PyCallable_Check(func)) {
+      PyObject *result = PyObject_CallNoArgs(func);
+      TypeHandle *result_handle = nullptr;
+      if (result == nullptr) {
+        // Never mind.
+        PyErr_Clear();
+      }
+      else if (DtoolInstance_GetPointer(result, result_handle, Dtool_TypeHandle) &&
+               *result_handle == *_this) {
+        // It returned the correct result, so we can use this.
+        return Py_BuildValue("O()", func);
+      }
+    }
+  }
+
+  // Fall back to TypeHandle::make(), if would produce the correct result.
   if (py_type != nullptr && *_this == ((Dtool_PyTypedObject *)py_type)->_type) {
     PyObject *func = PyObject_GetAttrString((PyObject *)&Dtool_TypeHandle, "make");
     return Py_BuildValue("N(O)", func, py_type);

+ 3 - 2
dtool/src/dtoolbase/typeRegistry.cxx

@@ -213,12 +213,13 @@ record_alternate_name(TypeHandle type, const string &name) {
  * of interrogate, which expects this to contain a Dtool_PyTypedObject.
  */
 void TypeRegistry::
-record_python_type(TypeHandle type, PyObject *python_type) {
+record_python_type(TypeHandle type, PyTypeObject *cls, PythonWrapFunc *wrap_func) {
   _lock.lock();
 
   TypeRegistryNode *rnode = look_up(type, nullptr);
   if (rnode != nullptr) {
-    rnode->_python_type = python_type;
+    rnode->_python_type = cls;
+    rnode->_python_wrap_func = wrap_func;
   }
 
   _lock.unlock();

+ 6 - 1
dtool/src/dtoolbase/typeRegistry.h

@@ -40,13 +40,18 @@ public:
   // convenience function, defined in register_type.h.
   bool register_type(TypeHandle &type_handle, const std::string &name);
 
+#ifdef HAVE_PYTHON
+  typedef PyObject *PythonWrapFunc(void *ptr, PyTypeObject *cast_from);
+#endif
+
 PUBLISHED:
   TypeHandle register_dynamic_type(const std::string &name);
 
   void record_derivation(TypeHandle child, TypeHandle parent);
   void record_alternate_name(TypeHandle type, const std::string &name);
 #ifdef HAVE_PYTHON
-  void record_python_type(TypeHandle type, PyObject *python_type);
+  void record_python_type(TypeHandle type, PyTypeObject *cls,
+                          PythonWrapFunc *wrap_func);
 #endif
 
   TypeHandle find_type(const std::string &name) const;

+ 18 - 1
dtool/src/dtoolbase/typeRegistryNode.I

@@ -14,7 +14,7 @@
 /**
  * Returns the Python type object associated with this node.
  */
-INLINE PyObject *TypeRegistryNode::
+INLINE PyTypeObject *TypeRegistryNode::
 get_python_type() const {
   if (_python_type != nullptr || _parent_classes.empty()) {
     return _python_type;
@@ -24,6 +24,23 @@ get_python_type() const {
   }
 }
 
+/**
+ * Returns a Python wrapper object corresponding to the given C++ pointer.
+ */
+INLINE PyObject *TypeRegistryNode::
+wrap_python(void *ptr, PyTypeObject *cast_from) const {
+  if (_python_wrap_func != nullptr) {
+    return _python_wrap_func(ptr, cast_from);
+  }
+  else if (_parent_classes.empty()) {
+    return nullptr;
+  }
+  else {
+    // Recurse through parent classes.
+    return r_wrap_python(ptr, cast_from);
+  }
+}
+
 /**
  *
  */

+ 27 - 7
dtool/src/dtoolbase/typeRegistryNode.cxx

@@ -311,16 +311,14 @@ r_build_subtrees(TypeRegistryNode *top, int bit_count,
  * Recurses through the parent nodes to find the best Python type object to
  * represent objects of this type.
  */
-PyObject *TypeRegistryNode::
+PyTypeObject *TypeRegistryNode::
 r_get_python_type() const {
-  Classes::const_iterator ni;
-  for (ni = _parent_classes.begin(); ni != _parent_classes.end(); ++ni) {
-    const TypeRegistryNode *parent = *ni;
+  for (const TypeRegistryNode *parent : _parent_classes) {
     if (parent->_python_type != nullptr) {
       return parent->_python_type;
-
-    } else if (!parent->_parent_classes.empty()) {
-      PyObject *py_type = parent->r_get_python_type();
+    }
+    else if (!parent->_parent_classes.empty()) {
+      PyTypeObject *py_type = parent->r_get_python_type();
       if (py_type != nullptr) {
         return py_type;
       }
@@ -330,6 +328,28 @@ r_get_python_type() const {
   return nullptr;
 }
 
+/**
+ * Creates a Python wrapper object to represent the given C++ pointer, which
+ * must be exactly of the correct type, unless cast_from is set to one of its
+ * base classes, in which case it will be cast appropriately.
+ */
+PyObject *TypeRegistryNode::
+r_wrap_python(void *ptr, PyTypeObject *cast_from) const {
+  for (const TypeRegistryNode *parent : _parent_classes) {
+    if (parent->_python_wrap_func != nullptr) {
+      return parent->_python_wrap_func(ptr, cast_from);
+    }
+    else if (!parent->_parent_classes.empty()) {
+      PyObject *wrapper = parent->r_wrap_python(ptr, cast_from);
+      if (wrapper != nullptr) {
+        return wrapper;
+      }
+    }
+  }
+
+  return nullptr;
+}
+
 /**
  * A recursive function to double-check the result of is_derived_from().  This
  * is the slow, examine-the-whole-graph approach, as opposed to the clever and

+ 8 - 3
dtool/src/dtoolbase/typeRegistryNode.h

@@ -30,6 +30,8 @@
  */
 class EXPCL_DTOOL_DTOOLBASE TypeRegistryNode {
 public:
+  typedef PyObject *PythonWrapFunc(void *ptr, PyTypeObject *cast_from);
+
   TypeRegistryNode(TypeHandle handle, const std::string &name, TypeHandle &ref);
 
   static bool is_derived_from(const TypeRegistryNode *child,
@@ -38,7 +40,8 @@ public:
   static TypeHandle get_parent_towards(const TypeRegistryNode *child,
                                        const TypeRegistryNode *base);
 
-  INLINE PyObject *get_python_type() const;
+  INLINE PyTypeObject *get_python_type() const;
+  INLINE PyObject *wrap_python(void *ptr, PyTypeObject *cast_from) const;
 
   void clear_subtree();
   void define_subtree();
@@ -49,7 +52,8 @@ public:
   typedef std::vector<TypeRegistryNode *> Classes;
   Classes _parent_classes;
   Classes _child_classes;
-  PyObject *_python_type = nullptr;
+  PyTypeObject *_python_type = nullptr;
+  PythonWrapFunc *_python_wrap_func = nullptr;
 
   patomic<size_t> _memory_usage[TypeHandle::MC_limit];
 
@@ -81,7 +85,8 @@ private:
   void r_build_subtrees(TypeRegistryNode *top,
                         int bit_count, SubtreeMaskType bits);
 
-  PyObject *r_get_python_type() const;
+  PyTypeObject *r_get_python_type() const;
+  PyObject *r_wrap_python(void *ptr, PyTypeObject *cast_from) const;
 
   static bool check_derived_from(const TypeRegistryNode *child,
                                  const TypeRegistryNode *base);

+ 4 - 4
dtool/src/dtoolutil/executionEnvironment.cxx

@@ -261,7 +261,7 @@ ns_has_environment_variable(const string &var) const {
 
 #ifdef PREREAD_ENVIRONMENT
   return false;
-#elif defined(_MSC_VER)
+#elif defined(_WIN32)
   size_t size = 0;
   getenv_s(&size, nullptr, 0, var.c_str());
   return size != 0;
@@ -305,7 +305,7 @@ ns_get_environment_variable(const string &var) const {
   }
 
 #ifndef PREREAD_ENVIRONMENT
-#ifdef _MSC_VER
+#ifdef _WIN32
   std::string value(128, '\0');
   size_t size = value.size();
   while (getenv_s(&size, &value[0], size, var.c_str()) == ERANGE) {
@@ -436,7 +436,7 @@ void ExecutionEnvironment::
 ns_set_environment_variable(const string &var, const string &value) {
   _variables[var] = value;
 
-#ifdef _MSC_VER
+#ifdef _WIN32
   _putenv_s(var.c_str(), value.c_str());
 #else
   setenv(var.c_str(), value.c_str(), 1);
@@ -464,7 +464,7 @@ ns_clear_shadow(const string &var) {
 
 #ifdef PREREAD_ENVIRONMENT
   // Now we have to replace the value in the table.
-#ifdef _MSC_VER
+#ifdef _WIN32
   std::string value(128, '\0');
   size_t size = value.size();
   while (getenv_s(&size, &value[0], size, var.c_str()) == ERANGE) {

+ 17 - 8
dtool/src/dtoolutil/filename.cxx

@@ -1289,9 +1289,12 @@ to_os_long_name() const {
 }
 
 /**
- * Returns true if the filename exists on the disk, false otherwise.  If the
- * type is indicated to be executable, this also tests that the file has
+ * Returns true if the filename exists on the physical disk, false otherwise.
+ * If the type is indicated to be executable, this also tests that the file has
  * execute permission.
+ *
+ * @see VirtualFileSystem::exists() for checking whether the filename exists in
+ * the virtual file system.
  */
 bool Filename::
 exists() const {
@@ -1320,8 +1323,11 @@ exists() const {
 }
 
 /**
- * Returns true if the filename exists and is the name of a regular file (i.e.
- * not a directory or device), false otherwise.
+ * Returns true if the filename exists on the physical disk and is the name of
+ * a regular file (i.e. not a directory or device), false otherwise.
+ *
+ * @see VirtualFileSystem::is_regular_file() for checking whether the filename
+ * exists and is a regular file in the virtual file system.
  */
 bool Filename::
 is_regular_file() const {
@@ -1350,8 +1356,8 @@ is_regular_file() const {
 }
 
 /**
- * Returns true if the filename exists and is either a directory or a regular
- * file that can be written to, or false otherwise.
+ * Returns true if the filename exists on the physical disk and is either a
+ * directory or a regular file that can be written to, or false otherwise.
  */
 bool Filename::
 is_writable() const {
@@ -1382,8 +1388,11 @@ is_writable() const {
 }
 
 /**
- * Returns true if the filename exists and is a directory name, false
- * otherwise.
+ * Returns true if the filename exists on the physical disk and is a directory
+ * name, false otherwise.
+ *
+ * @see VirtualFileSystem::is_directory() for checking whether the filename
+ * exists as a directory in the virtual file system.
  */
 bool Filename::
 is_directory() const {

+ 5 - 0
dtool/src/dtoolutil/filename.h

@@ -36,6 +36,11 @@ class DSearchPath;
  * convention, and it knows how to perform basic OS-specific I/O, like testing
  * for file existence and searching a searchpath, as well as the best way to
  * open an fstream for reading or writing.
+ *
+ * Note that the methods of Filename that interact with the filesystem (such
+ * as exists(), open_read(), etc.) directly interface with the operating system
+ * and are not aware of Panda's virtual file system.  To interact with the VFS,
+ * use the methods on VirtualFileSystem instead.
  */
 class EXPCL_DTOOL_DTOOLUTIL Filename {
 PUBLISHED:

+ 15 - 5
dtool/src/interrogate/functionRemap.cxx

@@ -494,6 +494,10 @@ get_call_str(const string &container, const vector_string &pexprs) const {
       call << separator << "self";
       separator = ", ";
     }
+    if (_flags & F_explicit_cls) {
+      call << separator << "cls";
+      separator = ", ";
+    }
 
     size_t pn;
     size_t num_parameters = pexprs.size();
@@ -781,14 +785,20 @@ setup_properties(const InterrogateFunction &ifunc, InterfaceMaker *interface_mak
     first_param = 1;
   }
 
-  if (_parameters.size() > first_param && _parameters[first_param]._name == "self" &&
+  if (_parameters.size() > first_param &&
       TypeManager::is_pointer_to_PyObject(_parameters[first_param]._remap->get_orig_type())) {
     // Here's a special case.  If the first parameter of a nonstatic method
     // is a PyObject * called "self", then we will automatically fill it in
-    // from the this pointer, and remove it from the generated parameter
-    // list.
-    _parameters.erase(_parameters.begin() + first_param);
-    _flags |= F_explicit_self;
+    // from the this pointer, and remove it from the generated parameter list.
+    // For static methods, we offer "cls" instead, containing the type object.
+    if (_parameters[first_param]._name == "self") {
+      _parameters.erase(_parameters.begin() + first_param);
+      _flags |= F_explicit_self;
+    }
+    else if (!_has_this && _parameters[first_param]._name == "cls") {
+      _parameters.erase(_parameters.begin() + first_param);
+      _flags |= F_explicit_cls;
+    }
   }
 
   if (_parameters.size() == first_param) {

+ 1 - 0
dtool/src/interrogate/functionRemap.h

@@ -101,6 +101,7 @@ public:
     F_divide_integer     = 0x2000,
     F_hash               = 0x4000,
     F_explicit_args      = 0x8000,
+    F_explicit_cls       =0x10000,
   };
 
   typedef std::vector<Parameter> Parameters;

Некоторые файлы не были показаны из-за большого количества измененных файлов