Kaynağa Gözat

Merge branch 'master' into webgl-port

rdb 2 yıl önce
ebeveyn
işleme
3826b7df44
100 değiştirilmiş dosya ile 3240 ekleme ve 4016 silme
  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
           eigen: NO
 
 
         - profile: macos-eigen-coverage-unity-xcode
         - profile: macos-eigen-coverage-unity-xcode
-          os: macOS-10.15
+          os: macOS-11
           config: Coverage
           config: Coverage
           unity: YES
           unity: YES
           generator: Xcode
           generator: Xcode
@@ -50,7 +50,7 @@ jobs:
           eigen: YES
           eigen: YES
 
 
         - profile: macos-nometa-standard-makefile
         - profile: macos-nometa-standard-makefile
-          os: macOS-10.15
+          os: macOS-11
           config: Standard
           config: Standard
           unity: NO
           unity: NO
           generator: Unix Makefiles
           generator: Unix Makefiles
@@ -87,7 +87,7 @@ jobs:
         fetch-depth: 10
         fetch-depth: 10
 
 
     - name: Self-destruct makepanda
     - name: Self-destruct makepanda
-      run: python makepanda/selfdestruct.py --yes
+      run: python3 makepanda/selfdestruct.py --yes
 
 
     - name: Install dependencies (macOS)
     - name: Install dependencies (macOS)
       if: runner.os == 'macOS'
       if: runner.os == 'macOS'
@@ -113,7 +113,7 @@ jobs:
         libeigen3-dev libfreetype6-dev libgl1-mesa-dev libjpeg-dev libode-dev
         libeigen3-dev libfreetype6-dev libgl1-mesa-dev libjpeg-dev libode-dev
         libopenal-dev libpng-dev libssl-dev libvorbis-dev libx11-dev
         libopenal-dev libpng-dev libssl-dev libvorbis-dev libx11-dev
         libxcursor-dev libxrandr-dev nvidia-cg-toolkit zlib1g-dev
         libxcursor-dev libxrandr-dev nvidia-cg-toolkit zlib1g-dev
-        python3-setuptools
+        python3-setuptools python3-tk
 
 
     - name: Cache dependencies (Windows)
     - name: Cache dependencies (Windows)
       if: runner.os == 'Windows'
       if: runner.os == 'Windows'
@@ -180,38 +180,6 @@ jobs:
       run: cmake --build . --config ${{ matrix.config }} --parallel 4
       run: cmake --build . --config ${{ matrix.config }} --parallel 4
       # END A
       # 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)
     - name: Setup Python (Python 3.8)
       if: contains(matrix.python, 'YES')
       if: contains(matrix.python, 'YES')
       uses: actions/setup-python@v4
       uses: actions/setup-python@v4
@@ -239,7 +207,7 @@ jobs:
         PYTHONPATH: ${{ matrix.config }}
         PYTHONPATH: ${{ matrix.config }}
       run: |
       run: |
         PYTHON_EXECUTABLE=$(grep 'Python_EXECUTABLE:' CMakeCache.txt | sed 's/.*=//')
         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
         export COVERAGE_FILE=.coverage.$RANDOM LLVM_PROFILE_FILE=$PWD/pid-%p.profraw
         $PYTHON_EXECUTABLE -m pytest ../tests --cov=.
         $PYTHON_EXECUTABLE -m pytest ../tests --cov=.
       # END B
       # END B
@@ -271,7 +239,7 @@ jobs:
         PYTHONPATH: ${{ matrix.config }}
         PYTHONPATH: ${{ matrix.config }}
       run: |
       run: |
         PYTHON_EXECUTABLE=$(grep 'Python_EXECUTABLE:' CMakeCache.txt | sed 's/.*=//')
         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
         export COVERAGE_FILE=.coverage.$RANDOM LLVM_PROFILE_FILE=$PWD/pid-%p.profraw
         $PYTHON_EXECUTABLE -m pytest ../tests --cov=.
         $PYTHON_EXECUTABLE -m pytest ../tests --cov=.
       # END B
       # END B
@@ -303,7 +271,7 @@ jobs:
         PYTHONPATH: ${{ matrix.config }}
         PYTHONPATH: ${{ matrix.config }}
       run: |
       run: |
         PYTHON_EXECUTABLE=$(grep 'Python_EXECUTABLE:' CMakeCache.txt | sed 's/.*=//')
         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
         export COVERAGE_FILE=.coverage.$RANDOM LLVM_PROFILE_FILE=$PWD/pid-%p.profraw
         $PYTHON_EXECUTABLE -m pytest ../tests --cov=.
         $PYTHON_EXECUTABLE -m pytest ../tests --cov=.
       # END B
       # END B
@@ -335,7 +303,7 @@ jobs:
         PYTHONPATH: ${{ matrix.config }}
         PYTHONPATH: ${{ matrix.config }}
       run: |
       run: |
         PYTHON_EXECUTABLE=$(grep 'Python_EXECUTABLE:' CMakeCache.txt | sed 's/.*=//')
         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
         export COVERAGE_FILE=.coverage.$RANDOM LLVM_PROFILE_FILE=$PWD/pid-%p.profraw
         $PYTHON_EXECUTABLE -m pytest ../tests --cov=.
         $PYTHON_EXECUTABLE -m pytest ../tests --cov=.
       # END B
       # END B
@@ -398,7 +366,7 @@ jobs:
     - name: Test Python 3.11
     - name: Test Python 3.11
       shell: bash
       shell: bash
       run: |
       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
         PYTHONPATH=built LD_LIBRARY_PATH=built/lib DYLD_LIBRARY_PATH=built/lib python -m pytest
 
 
     - name: Set up Python 3.10
     - name: Set up Python 3.10
@@ -412,7 +380,7 @@ jobs:
     - name: Test Python 3.10
     - name: Test Python 3.10
       shell: bash
       shell: bash
       run: |
       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
         PYTHONPATH=built LD_LIBRARY_PATH=built/lib DYLD_LIBRARY_PATH=built/lib python -m pytest
 
 
     - name: Set up Python 3.9
     - name: Set up Python 3.9
@@ -426,7 +394,7 @@ jobs:
     - name: Test Python 3.9
     - name: Test Python 3.9
       shell: bash
       shell: bash
       run: |
       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
         PYTHONPATH=built LD_LIBRARY_PATH=built/lib DYLD_LIBRARY_PATH=built/lib python -m pytest
 
 
     - name: Set up Python 3.8
     - name: Set up Python 3.8
@@ -440,21 +408,7 @@ jobs:
     - name: Test Python 3.8
     - name: Test Python 3.8
       shell: bash
       shell: bash
       run: |
       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
         PYTHONPATH=built LD_LIBRARY_PATH=built/lib DYLD_LIBRARY_PATH=built/lib python -m pytest
 
 
     - name: Make installer
     - 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
 *.save.1
 *.sublime-workspace
 *.sublime-workspace
 .vscode/
 .vscode/
+.idea/
 
 
 # Temporary build files
 # Temporary build files
 /_vfsimporter.*
 /_vfsimporter.*
@@ -51,9 +52,10 @@ cmake_install.cmake
 install_manifest.txt
 install_manifest.txt
 CTestTestfile.cmake
 CTestTestfile.cmake
 
 
-# Windows
+# Operating system
 Thumbs.db
 Thumbs.db
 ehthumbs.db
 ehthumbs.db
+.DS_Store
 
 
 # macOS
 # macOS
 .DS_Store
 .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
 * Max Voss
 * Hawkheart
 * Hawkheart
 * Veronica
 * Veronica
+* Cody Sevier
+* Marek Alexa
 
 
 ## Enthusiasts
 ## Enthusiasts
 
 
@@ -28,6 +30,8 @@ This is a list of all the people who are contributing financially to Panda3D.  I
 * SureBet
 * SureBet
 * Gyedo Jeon
 * Gyedo Jeon
 * GameDev JONI
 * GameDev JONI
+* Max Rodriguez
+* Jethro Schoppenhorst
 
 
 ## Backers
 ## Backers
 
 

+ 8 - 0
CMakeLists.txt

@@ -24,6 +24,9 @@ endif()
 
 
 # Set defaults for macOS, must be before project().
 # Set defaults for macOS, must be before project().
 if(APPLE)
 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_OSX_DEPLOYMENT_TARGET "10.9" CACHE STRING "Minimum macOS version to target")
   set(CMAKE_XCODE_ATTRIBUTE_CLANG_CXX_LIBRARY "libc++")
   set(CMAKE_XCODE_ATTRIBUTE_CLANG_CXX_LIBRARY "libc++")
 
 
@@ -46,6 +49,11 @@ project(Panda3D VERSION ${_version})
 unset(_version)
 unset(_version)
 unset(_s)
 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().
 # Determine the possible build types.  Must be *after* calling project().
 set(_configs Standard Release RelWithDebInfo Debug MinSizeRel)
 set(_configs Standard Release RelWithDebInfo Debug MinSizeRel)
 if(CMAKE_CXX_COMPILER_ID MATCHES "(AppleClang|Clang|GCC)")
 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)
                             self.txaExtraLines.append(line)
                 txafile.close()
                 txafile.close()
             except:
             except:
-                print "Error opening .txa file!"
+                print("Error opening .txa file!")
             self.palettize_saveTxaTxt.SetValue(os.path.join(dirname + os.sep , filename))
             self.palettize_saveTxaTxt.SetValue(os.path.join(dirname + os.sep , filename))
         dlg.Destroy()
         dlg.Destroy()
 
 
@@ -2705,7 +2705,7 @@ class main(wx.Frame):
             selectedItemIndex = int(self.batchTree.GetItemText(selectedItemId).split()[0])-1
             selectedItemIndex = int(self.batchTree.GetItemText(selectedItemId).split()[0])-1
             batchItem = self.batchList[selectedItemIndex]
             batchItem = self.batchList[selectedItemIndex]
 
 
-            print '\n'+self.BuildCommand(batchItem)
+            print('\n'+self.BuildCommand(batchItem))
 
 
             if (batchItem['cmd'].count('maya2egg')):
             if (batchItem['cmd'].count('maya2egg')):
                 # Display Maya2Egg Tool Panel
                 # Display Maya2Egg Tool Panel
@@ -2840,7 +2840,7 @@ class main(wx.Frame):
                                 self.txaExtraLines.append(line)
                                 self.txaExtraLines.append(line)
                     txafile.close()
                     txafile.close()
                 except:
                 except:
-                    print "Error opening .txa file!"
+                    print("Error opening .txa file!")
 
 
             self.batchItemNameTxt.SetValue(batchItem['label'])
             self.batchItemNameTxt.SetValue(batchItem['label'])
 
 

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

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

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

@@ -169,12 +169,9 @@ class ClusterClient(DirectObject.DirectObject):
             self.serverList[server].sendNamedMovementDone()
             self.serverList[server].sendNamedMovementDone()
 
 
     def redoSortedPriorities(self):
     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):
     def moveObject(self, nodePath, object, serverList, offset, hasColor = True):
         self.notify.debug('moving object '+object)
         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)
             self.objectMappings.pop(name)
 
 
     def redoSortedPriorities(self):
     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,
     def addControlMapping(self, objectName, controlledName, offset = None,
                           priority = 0):
                           priority = 0):

Dosya farkı çok büyük olduğundan ihmal edildi
+ 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
 /* 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
    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
    it under the terms of the GNU General Public License as published by
@@ -15,7 +16,7 @@
    GNU General Public License for more details.
    GNU General Public License for more details.
 
 
    You should have received a copy of the GNU General Public License
    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
 /* As a special exception, you may create a larger work that contains
    part or all of the Bison parser skeleton and distribute that work
    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
    This special exception was added by the Free Software Foundation in
    version 2.2 of Bison.  */
    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
 #ifndef YY_DCYY_BUILT_TMP_DCPARSER_YXX_H_INCLUDED
 # define YY_DCYY_BUILT_TMP_DCPARSER_YXX_H_INCLUDED
 # define YY_DCYY_BUILT_TMP_DCPARSER_YXX_H_INCLUDED
 /* Debug traces.  */
 /* Debug traces.  */
@@ -40,54 +45,63 @@
 extern int dcyydebug;
 extern int dcyydebug;
 #endif
 #endif
 
 
-/* Token type.  */
+/* Token kinds.  */
 #ifndef YYTOKENTYPE
 #ifndef YYTOKENTYPE
 # define YYTOKENTYPE
 # define YYTOKENTYPE
   enum 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
 #endif
-/* Tokens.  */
+/* Token kinds.  */
+#define YYEMPTY -2
+#define YYEOF 0
+#define YYerror 256
+#define YYUNDEF 257
 #define UNSIGNED_INTEGER 258
 #define UNSIGNED_INTEGER 258
 #define SIGNED_INTEGER 259
 #define SIGNED_INTEGER 259
 #define REAL 260
 #define REAL 260
@@ -134,6 +148,8 @@ extern int dcyydebug;
 
 
 extern YYSTYPE dcyylval;
 extern YYSTYPE dcyylval;
 
 
+
 int dcyyparse (void);
 int dcyyparse (void);
 
 
+
 #endif /* !YY_DCYY_BUILT_TMP_DCPARSER_YXX_H_INCLUDED  */
 #endif /* !YY_DCYY_BUILT_TMP_DCPARSER_YXX_H_INCLUDED  */

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

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

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

@@ -6,7 +6,7 @@ from direct.showbase import ShowBase
 base = ShowBase.ShowBase()
 base = ShowBase.ShowBase()
 
 
 # Put an axis in the world:
 # 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.camera.setPosHpr(0, -10.0, 0, 0, 0, 0)
 base.camLens.setFov(52.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()
 base = ThreeUpShow.ThreeUpShow()
 
 
 # Put an axis in the world:
 # 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.camera.setPosHpr(0, -10.0, 0, 0, 0, 0)
 base.camLens.setFov(52.0)
 base.camLens.setFov(52.0)

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

@@ -41,8 +41,12 @@ class DirectJoybox(DirectObject):
     xyzMultiplier = 1.0
     xyzMultiplier = 1.0
     hprMultiplier = 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
         # See if device manager has been initialized
         if base.direct.deviceManager is None:
         if base.direct.deviceManager is None:
             base.direct.deviceManager = DirectDeviceManager()
             base.direct.deviceManager = DirectDeviceManager()

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

@@ -55,10 +55,11 @@ class RotatingLog:
         return 0
         return 0
 
 
     def filePath(self):
     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):
         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
                 return path
         # Hmm, 26 files are full?  throw the rest in z:
         # Hmm, 26 files are full?  throw the rest in z:
         # Maybe we should clear the self.sizeLimit here... maybe.
         # Maybe we should clear the self.sizeLimit here... maybe.
@@ -83,7 +84,6 @@ class RotatingLog:
             self.closed = self.file.closed
             self.closed = self.file.closed
             self.mode = self.file.mode
             self.mode = self.file.mode
             self.name = self.file.name
             self.name = self.file.name
-            self.softspace = self.file.softspace
             #self.encoding = self.file.encoding # Python 2.3
             #self.encoding = self.file.encoding # Python 2.3
             #self.newlines = self.file.newlines # Python 2.3, maybe
             #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):
     def getNameList(self):
         # Return a sorted list of all lights in the light dict
         # 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):
     def create(self, ltype):
         ltype = ltype.lower()
         ltype = ltype.lower()

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

@@ -90,6 +90,7 @@ defaultHiddenImports = {
     'scipy.special._ufuncs': ['scipy.special._ufuncs_cxx'],
     'scipy.special._ufuncs': ['scipy.special._ufuncs_cxx'],
     'scipy.stats._stats': ['scipy.special.cython_special'],
     'scipy.stats._stats': ['scipy.special.cython_special'],
     'setuptools.monkey': ['setuptools.msvc'],
     'setuptools.monkey': ['setuptools.msvc'],
+    'shapely._geometry_helpers': ['shapely._geos'],
 }
 }
 
 
 
 
@@ -370,6 +371,8 @@ extern PyAPI_FUNC(int) PyImport_ExtendInittab(struct _inittab *newtab);
 
 
 /* Main program */
 /* Main program */
 
 
+EXTRA_INIT_FUNC_DECLS
+
 int
 int
 Py_FrozenMain(int argc, char **argv)
 Py_FrozenMain(int argc, char **argv)
 {
 {
@@ -449,6 +452,8 @@ Py_FrozenMain(int argc, char **argv)
     PySys_SetArgv(argc, argv);
     PySys_SetArgv(argc, argv);
 #endif
 #endif
 
 
+EXTRA_INIT_FUNC_CALLS
+
     n = PyImport_ImportFrozenModule("__main__");
     n = PyImport_ImportFrozenModule("__main__");
     if (n == 0)
     if (n == 0)
         Py_FatalError("__main__ not frozen");
         Py_FatalError("__main__ not frozen");
@@ -839,6 +844,12 @@ class Freezer:
         # modules.
         # modules.
         self.extras = []
         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
         # Set this to true if extension modules should be linked in to
         # the resulting executable.
         # the resulting executable.
         self.linkExtensionModules = False
         self.linkExtensionModules = False
@@ -1135,15 +1146,12 @@ class Freezer:
 
 
         # Walk through the list in sorted order, so we reach parents
         # Walk through the list in sorted order, so we reach parents
         # before children.
         # before children.
-        names = list(self.modules.items())
-        names.sort()
-
         excludeDict = {}
         excludeDict = {}
         implicitParentDict = {}
         implicitParentDict = {}
         includes = []
         includes = []
         autoIncludes = []
         autoIncludes = []
         origToNewName = {}
         origToNewName = {}
-        for newName, mdef in names:
+        for newName, mdef in sorted(self.modules.items()):
             moduleName = mdef.moduleName
             moduleName = mdef.moduleName
             origToNewName[moduleName] = newName
             origToNewName[moduleName] = newName
             if mdef.implicit and '.' in newName:
             if mdef.implicit and '.' in newName:
@@ -1711,6 +1719,18 @@ class Freezer:
 
 
         if compileToExe:
         if compileToExe:
             code = self.frozenMainCode
             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'):
             if self.platform.startswith('win'):
                 code += self.frozenDllMainCode
                 code += self.frozenDllMainCode
             initCode = self.mainInitCode % {
             initCode = self.mainInitCode % {
@@ -2561,6 +2581,21 @@ class PandaModuleFinder(modulefinder.ModuleFinder):
             else:
             else:
                 code = fp.read()
                 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'
             code += b'\n' if isinstance(code, bytes) else '\n'
             co = compile(code, pathname, 'exec', optimize=self.optimize)
             co = compile(code, pathname, 'exec', optimize=self.optimize)
         elif type == imp.PY_COMPILED:
         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:
         for index in self.pypi_extra_indexes:
             pip_args += ['--extra-index-url', index]
             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 a list of paths to the downloaded whls
         return [
         return [
@@ -846,16 +859,12 @@ class build_apps(setuptools.Command):
             libdir = os.path.dirname(dtool_fn.to_os_specific())
             libdir = os.path.dirname(dtool_fn.to_os_specific())
             etcdir = os.path.join(libdir, '..', 'etc')
             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'):
                 if fn.lower().endswith('.prc'):
                     with open(os.path.join(etcdir, fn)) as f:
                     with open(os.path.join(etcdir, fn)) as f:
                         prcstring += f.read()
                         prcstring += f.read()
         else:
         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:
                 with p3dwhl.open(fn) as f:
                     prcstring += f.read().decode('utf8')
                     prcstring += f.read().decode('utf8')
 
 
@@ -1689,6 +1698,8 @@ class bdist_apps(setuptools.Command):
             setattr(self, opt, None)
             setattr(self, opt, None)
 
 
     def finalize_options(self):
     def finalize_options(self):
+        import pkg_resources
+
         # We need to massage the inputs a bit in case they came from a
         # We need to massage the inputs a bit in case they came from a
         # setup.cfg file.
         # setup.cfg file.
         self.installers = {
         self.installers = {

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

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

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

@@ -76,41 +76,3 @@ if __debug__:
         def flush(self):
         def flush(self):
             CachedDOData.flush(self)
             CachedDOData.flush(self)
             self._flushed = True
             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
         # override and destroy the cached data
         # cached data is typically created by the DistributedObject and destroyed here
         # cached data is typically created by the DistributedObject and destroyed here
         pass
         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")
                     f"Asked to update non-existent DistObj {doId} and failed to find it")
 
 
     def __doUpdateOwner(self, doId, di):
     def __doUpdateOwner(self, doId, di):
+        if not self.hasOwnerView():
+            return False
+
         ovObj = self.doId2ownerView.get(doId)
         ovObj = self.doId2ownerView.get(doId)
         if ovObj:
         if ovObj:
             odg = Datagram(di.getDatagram())
             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
         # setLocation destroys self._zoneData if we move away to
         # a different zone
         # a different zone
         if self._zoneData is None:
         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)
             self._zoneData = AIZoneData(self.air, self.parentId, self.zoneId)
         return self._zoneData
         return self._zoneData
 
 
@@ -489,7 +489,7 @@ class DistributedObjectAI(DistributedObjectBase):
         # simultaneously on different lists of avatars, although they
         # simultaneously on different lists of avatars, although they
         # should have different names.
         # should have different names.
 
 
-        from otp.ai import Barrier
+        from otp.ai import Barrier  # type: ignore[import]
         context = self.__nextBarrierContext
         context = self.__nextBarrierContext
         # We assume the context number is passed as a uint16.
         # We assume the context number is passed as a uint16.
         self.__nextBarrierContext = (self.__nextBarrierContext + 1) & 0xffff
         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
         # simultaneously on different lists of avatars, although they
         # should have different names.
         # should have different names.
 
 
-        from otp.ai import Barrier
+        from otp.ai import Barrier  # type: ignore[import]
         context = self.__nextBarrierContext
         context = self.__nextBarrierContext
         # We assume the context number is passed as a uint16.
         # We assume the context number is passed as a uint16.
         self.__nextBarrierContext = (self.__nextBarrierContext + 1) & 0xffff
         self.__nextBarrierContext = (self.__nextBarrierContext + 1) & 0xffff

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

@@ -149,10 +149,7 @@ class DoCollectionManager:
             class2count.setdefault(className, 0)
             class2count.setdefault(className, 0)
             class2count[className] += 1
             class2count[className] += 1
         count2classes = invertDictLossless(class2count)
         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()
             count2classes[count].sort()
             for name in count2classes[count]:
             for name in count2classes[count]:
                 print('%s %s' % (count, name))
                 print('%s %s' % (count, name))
@@ -166,10 +163,7 @@ class DoCollectionManager:
             class2count.setdefault(className, 0)
             class2count.setdefault(className, 0)
             class2count[className] += 1
             class2count[className] += 1
         count2classes = invertDictLossless(class2count)
         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()
             count2classes[count].sort()
             for name in count2classes[count]:
             for name in count2classes[count]:
                 # print '%s %s' % (count, name)
                 # 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.
 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 panda3d.core import ConfigVariableBool
 from .MsgTypes import CLIENT_ADD_INTEREST, CLIENT_ADD_INTEREST_MULTIPLE, CLIENT_REMOVE_INTEREST
 from .MsgTypes import CLIENT_ADD_INTEREST, CLIENT_ADD_INTEREST_MULTIPLE, CLIENT_REMOVE_INTEREST
 from direct.showbase import DirectObject
 from direct.showbase import DirectObject
@@ -98,9 +100,9 @@ class DoInterestManager(DirectObject.DirectObject):
     _ContextIdSerialNum = 100
     _ContextIdSerialNum = 100
     _ContextIdMask = 0x3FFFFFFF # avoid making Python create a long
     _ContextIdMask = 0x3FFFFFFF # avoid making Python create a long
 
 
-    _interests = {}
+    _interests: dict[int, InterestState] = {}
     if __debug__:
     if __debug__:
-        _debug_interestHistory = []
+        _debug_interestHistory: list[tuple] = []
         _debug_maxDescriptionLen = 40
         _debug_maxDescriptionLen = 40
 
 
     _SerialGen = SerialNumGen()
     _SerialGen = SerialNumGen()
@@ -512,8 +514,7 @@ class DoInterestManager(DirectObject.DirectObject):
         datagram = PyDatagram()
         datagram = PyDatagram()
         # Add message type
         # Add message type
         if isinstance(zoneIdList, list):
         if isinstance(zoneIdList, list):
-            vzl = list(zoneIdList)
-            vzl.sort()
+            vzl = sorted(zoneIdList)
             uniqueElements(vzl)
             uniqueElements(vzl)
             datagram.addUint16(CLIENT_ADD_INTEREST_MULTIPLE)
             datagram.addUint16(CLIENT_ADD_INTEREST_MULTIPLE)
             datagram.addUint32(contextId)
             datagram.addUint32(contextId)

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

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

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

@@ -1,143 +1,145 @@
 """MsgTypes module: contains distributed object message types"""
 """MsgTypes module: contains distributed object message types"""
 
 
+from __future__ import annotations
+
 from direct.showbase.PythonUtil import invertDictLossless
 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
 # create id->name table for debugging
 MsgId2Names = invertDictLossless(MsgName2Id)
 MsgId2Names = invertDictLossless(MsgName2Id)
@@ -146,7 +148,7 @@ MsgId2Names = invertDictLossless(MsgName2Id)
 globals().update(MsgName2Id)
 globals().update(MsgName2Id)
 
 
 # These messages are ignored when the client is headed to the quiet zone
 # 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
     # We mustn't ignore updates, because some updates for localToon
     # are always important.
     # are always important.

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

@@ -5,25 +5,22 @@ implementation. """
 
 
 from direct.showbase.PythonUtil import invertDictLossless
 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
 # create id->name table for debugging
 MsgId2Names = invertDictLossless(MsgName2Id)
 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.
     existing code.  New code should use the :mod:`.FSM` module instead.
 """
 """
 
 
+from __future__ import annotations
+
 __all__ = ['ClassicFSM']
 __all__ = ['ClassicFSM']
 
 
 from direct.directnotify.DirectNotifyGlobal import directNotify
 from direct.directnotify.DirectNotifyGlobal import directNotify
@@ -13,7 +15,7 @@ from direct.showbase.MessengerGlobal import messenger
 import weakref
 import weakref
 
 
 if __debug__:
 if __debug__:
-    _debugFsms = {}
+    _debugFsms: dict[str, weakref.ref] = {}
 
 
     def printDebugFsmList():
     def printDebugFsmList():
         for k in sorted(_debugFsms.keys()):
         for k in sorted(_debugFsms.keys()):

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

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

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

@@ -1,6 +1,8 @@
 # classes for event-driven programming
 # classes for event-driven programming
 # http://en.wikipedia.org/wiki/Event-driven_programming
 # http://en.wikipedia.org/wiki/Event-driven_programming
 
 
+from __future__ import annotations
+
 __all__ = ['StateVar', 'FunctionCall', 'EnterExit', 'Pulse', 'EventPulse',
 __all__ = ['StateVar', 'FunctionCall', 'EnterExit', 'Pulse', 'EventPulse',
            'EventArgument', ]
            'EventArgument', ]
 
 
@@ -46,11 +48,6 @@ class PushesStateChanges:
         for subscriber in self._subscribers:
         for subscriber in self._subscribers:
             subscriber._recvStatePush(self)
             subscriber._recvStatePush(self)
 
 
-if __debug__:
-    psc = PushesStateChanges(0)
-    assert psc.getState() == 0
-    psc.destroy()
-    del psc
 
 
 class ReceivesStateChanges:
 class ReceivesStateChanges:
     # base class for objects that subscribe to state changes from PushesStateChanges objects
     # base class for objects that subscribe to state changes from PushesStateChanges objects
@@ -83,10 +80,6 @@ class ReceivesStateChanges:
     def _recvStatePush(self, source):
     def _recvStatePush(self, source):
         pass
         pass
 
 
-if __debug__:
-    rsc = ReceivesStateChanges(None)
-    rsc.destroy()
-    del rsc
 
 
 class StateVar(PushesStateChanges):
 class StateVar(PushesStateChanges):
     # coder-friendly object that allows values to be set on it and pushes those values
     # 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):
     def get(self):
         return PushesStateChanges.getState(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):
 class StateChangeNode(PushesStateChanges, ReceivesStateChanges):
     # base class that can be used to create a state-change notification chain
     # 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
         # got a state push, apply new state to self
         self._handlePotentialStateChange(source._value)
         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:
 class ReceivesMultipleStateChanges:
     # base class for objects that subscribe to state changes from multiple PushesStateChanges
     # base class for objects that subscribe to state changes from multiple PushesStateChanges
@@ -179,15 +140,6 @@ class ReceivesMultipleStateChanges:
     def _recvMultiStatePush(self, key, source):
     def _recvMultiStatePush(self, key, source):
         pass
         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):
 class FunctionCall(ReceivesMultipleStateChanges, PushesStateChanges):
     # calls func with provided args whenever arguments' state changes
     # calls func with provided args whenever arguments' state changes
@@ -249,47 +201,6 @@ class FunctionCall(ReceivesMultipleStateChanges, PushesStateChanges):
             self._func(*self._bakedArgs, **self._bakedKargs)
             self._func(*self._bakedArgs, **self._bakedKargs)
             PushesStateChanges._handleStateChange(self)
             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):
 class EnterExit(StateChangeNode):
     # call enterFunc when our state becomes true, exitFunc when it becomes false
     # call enterFunc when our state becomes true, exitFunc when it becomes false
@@ -314,33 +225,6 @@ class EnterExit(StateChangeNode):
             self._exitFunc()
             self._exitFunc()
         StateChangeNode._handleStateChange(self)
         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):
 class Pulse(PushesStateChanges):
     # changes state to True then immediately to False whenever sendPulse is called
     # changes state to True then immediately to False whenever sendPulse is called
@@ -351,25 +235,6 @@ class Pulse(PushesStateChanges):
         self._handlePotentialStateChange(True)
         self._handlePotentialStateChange(True)
         self._handlePotentialStateChange(False)
         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):
 class EventPulse(Pulse, DirectObject):
     # sends a True-False "pulse" whenever a specific messenger message is sent
     # sends a True-False "pulse" whenever a specific messenger message is sent
@@ -381,6 +246,7 @@ class EventPulse(Pulse, DirectObject):
         self.ignoreAll()
         self.ignoreAll()
         Pulse.destroy(self)
         Pulse.destroy(self)
 
 
+
 class EventArgument(PushesStateChanges, DirectObject):
 class EventArgument(PushesStateChanges, DirectObject):
     # tracks a particular argument to a particular messenger event
     # tracks a particular argument to a particular messenger event
     def __init__(self, event, index=0):
     def __init__(self, event, index=0):
@@ -396,6 +262,7 @@ class EventArgument(PushesStateChanges, DirectObject):
     def _handleEvent(self, *args):
     def _handleEvent(self, *args):
         self._handlePotentialStateChange(args[self._index])
         self._handlePotentialStateChange(args[self._index])
 
 
+
 class AttrSetter(StateChangeNode):
 class AttrSetter(StateChangeNode):
     def __init__(self, source, object, attrName):
     def __init__(self, source, object, attrName):
         self._object = object
         self._object = object
@@ -406,19 +273,3 @@ class AttrSetter(StateChangeNode):
     def _handleStateChange(self):
     def _handleStateChange(self):
         setattr(self._object, self._attrName, self._value)
         setattr(self._object, self._attrName, self._value)
         StateChangeNode._handleStateChange(self)
         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.
 in-depth explanation and an example of how to use this class.
 """
 """
 
 
+from __future__ import annotations
+
 __all__ = [
 __all__ = [
     'findDialog', 'cleanupDialog', 'DirectDialog', 'OkDialog',
     'findDialog', 'cleanupDialog', 'DirectDialog', 'OkDialog',
     'OkCancelDialog', 'YesNoDialog', 'YesNoCancelDialog', 'RetryCancelDialog',
     'OkCancelDialog', 'YesNoDialog', 'YesNoCancelDialog', 'RetryCancelDialog',
@@ -45,7 +47,7 @@ def cleanupDialog(uniqueName):
 
 
 class DirectDialog(DirectFrame):
 class DirectDialog(DirectFrame):
 
 
-    AllDialogs = {}
+    AllDialogs: dict[str, DirectDialog] = {}
     PanelIndex = 0
     PanelIndex = 0
 
 
     def __init__(self, parent=None, **kw):
     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.
     see if any keywords are left unused.  If so, an error is raised.
 """
 """
 
 
+from __future__ import annotations
+
 __all__ = ['DirectGuiBase', 'DirectGuiWidget']
 __all__ = ['DirectGuiBase', 'DirectGuiWidget']
 
 
 
 
@@ -616,9 +618,7 @@ class DirectGuiBase(DirectObject.DirectObject):
 
 
     def components(self):
     def components(self):
         # Return a list of all components.
         # Return a list of all components.
-        names = list(self.__componentInfo.keys())
-        names.sort()
-        return names
+        return sorted(self.__componentInfo)
 
 
     def hascomponent(self, component):
     def hascomponent(self, component):
         return component in self.__componentInfo
         return component in self.__componentInfo
@@ -682,7 +682,7 @@ class DirectGuiWidget(DirectGuiBase, NodePath):
     else:
     else:
         inactiveInitState = DGG.DISABLED
         inactiveInitState = DGG.DISABLED
 
 
-    guiDict = {}
+    guiDict: dict[str, DirectGuiWidget] = {}
 
 
     def __init__(self, parent = None, **kw):
     def __init__(self, parent = None, **kw):
         # Direct gui widgets are node paths
         # 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
 that can be used during widget construction
 """
 """
 
 
-__all__ = []
+__all__ = ()
 
 
 from panda3d.core import (
 from panda3d.core import (
     KeyboardButton,
     KeyboardButton,

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

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

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

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

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

@@ -116,9 +116,7 @@ class HotKeyPanel(ScrolledPanel):
 
 
     def updateUI(self):
     def updateUI(self):
         vbox = wx.BoxSizer(wx.VERTICAL)
         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]
             keyDesc = base.direct.hotKeyMap[key]
             itemPanel = wx.Panel(self)
             itemPanel = wx.Panel(self)
             sizer = wx.BoxSizer(wx.HORIZONTAL)
             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
     from direct.showbase.ShowBase import ShowBase
     base = ShowBase()
     base = ShowBase()
 
 
-    base.le = LevelEditor.LevelEditor()
+    base.le = LevelEditor.LevelEditor()  # type: ignore[attr-defined]
     # You should define LevelEditor instance as
     # You should define LevelEditor instance as
     # base.le so it can be reached in global scope
     # 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 panda3d.core import Point3, VBase3
+from direct.showbase.ShowBaseGlobal import base
 
 
 if hasattr(base, 'le'):
 if hasattr(base, 'le'):
     objectMgr = base.le.objectMgr
     objectMgr = base.le.objectMgr
@@ -6,7 +7,7 @@ if hasattr(base, 'le'):
     ui.sceneGraphUI.reset()
     ui.sceneGraphUI.reset()
 
 
 else:
 else:
-    objectMgr = base.objectMgr
+    objectMgr = base.objectMgr  # type: ignore[attr-defined]
 # temporary place holder for nodepath
 # temporary place holder for nodepath
 objects = {}
 objects = {}
 
 

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

@@ -1,3 +1,5 @@
+from __future__ import annotations
+
 from panda3d.core import (
 from panda3d.core import (
     BoundingVolume,
     BoundingVolume,
     ColorBlendAttrib,
     ColorBlendAttrib,
@@ -92,7 +94,7 @@ class MotionTrail(NodePath, DirectObject):
     notify = directNotify.newCategory("MotionTrail")
     notify = directNotify.newCategory("MotionTrail")
 
 
     task_added = False
     task_added = False
-    motion_trail_list = []
+    motion_trail_list: list[MotionTrail] = []
     motion_trail_task_name = "motion_trail_task"
     motion_trail_task_name = "motion_trail_task"
 
 
     global_enable = True
     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):
     def __repr__(self):
         str  = 'Bulletin Board Contents\n'
         str  = 'Bulletin Board Contents\n'
         str += '======================='
         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])
             str += '\n%s: %s' % (postName, self._dict[postName])
         return str
         return str

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

@@ -1,3 +1,5 @@
+from __future__ import annotations
+
 from direct.directnotify.DirectNotifyGlobal import directNotify
 from direct.directnotify.DirectNotifyGlobal import directNotify
 import direct.showbase.DConfig as config
 import direct.showbase.DConfig as config
 from direct.showbase.PythonUtil import makeFlywheelGen, flywheel
 from direct.showbase.PythonUtil import makeFlywheelGen, flywheel
@@ -11,7 +13,6 @@ import types
 import weakref
 import weakref
 import random
 import random
 import builtins
 import builtins
-import sys
 
 
 
 
 deadEndTypes = frozenset((
 deadEndTypes = frozenset((
@@ -566,7 +567,7 @@ class FindContainers(Job):
                 curObjRef = None
                 curObjRef = None
 
 
                 # types.CellType was added in Python 3.8
                 # 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
                     child = curObj.cell_contents
                     hasLength = self._hasLength(child)
                     hasLength = self._hasLength(child)
                     notDeadEnd = not self._isDeadEnd(child)
                     notDeadEnd = not self._isDeadEnd(child)
@@ -983,7 +984,7 @@ class ContainerLeakDetector(Job):
     """
     """
     notify = directNotify.newCategory("ContainerLeakDetector")
     notify = directNotify.newCategory("ContainerLeakDetector")
     # set of containers that should not be examined
     # set of containers that should not be examined
-    PrivateIds = set()
+    PrivateIds: set[int] = set()
 
 
     def __init__(self, name, firstCheckDelay = None):
     def __init__(self, name, firstCheckDelay = None):
         Job.__init__(self, name)
         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.directnotify.DirectNotifyGlobal import directNotify
 from direct.showbase.PythonUtil import Queue, invertDictLossless
 from direct.showbase.PythonUtil import Queue, invertDictLossless
 from direct.showbase.PythonUtil import safeRepr
 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.JobManagerGlobal import jobMgr
 from direct.showbase.ContainerLeakDetector import deadEndTypes
 from direct.showbase.ContainerLeakDetector import deadEndTypes
 import types
 import types
-import sys
 import io
 import io
 
 
 
 
 class ContainerReport(Job):
 class ContainerReport(Job):
     notify = directNotify.newCategory("ContainerReport")
     notify = directNotify.newCategory("ContainerReport")
     # set of containers that should not be included in the report
     # 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):
     def __init__(self, name, log=False, limit=None, threaded=False):
         Job.__init__(self, name)
         Job.__init__(self, name)
@@ -122,7 +123,7 @@ class ContainerReport(Job):
                 continue
                 continue
 
 
             # types.CellType was added in Python 3.8
             # 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
                 child = parentObj.cell_contents
                 if self._examine(child):
                 if self._examine(child):
                     assert (self._queue.back() is child)
                     assert (self._queue.back() is child)
@@ -228,14 +229,11 @@ class ContainerReport(Job):
         if type not in self._type2id2len:
         if type not in self._type2id2len:
             return
             return
         len2ids = invertDictLossless(self._type2id2len[type])
         len2ids = invertDictLossless(self._type2id2len[type])
-        lengths = list(len2ids.keys())
-        lengths.sort()
-        lengths.reverse()
         print('=====')
         print('=====')
         print('===== %s' % type)
         print('===== %s' % type)
         count = 0
         count = 0
         stop = False
         stop = False
-        for l in lengths:
+        for l in sorted(len2ids, reverse=True):
             #len2ids[l].sort()
             #len2ids[l].sort()
             pathStrList = list()
             pathStrList = list()
             for id in len2ids[l]:
             for id in len2ids[l]:
@@ -257,9 +255,8 @@ class ContainerReport(Job):
         for type in initialTypes:
         for type in initialTypes:
             for i in self._outputType(type, **kArgs):
             for i in self._outputType(type, **kArgs):
                 yield None
                 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):
             for i in self._outputType(type, **kArgs):
                 yield None
                 yield None
 
 

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

@@ -81,147 +81,3 @@ class CountedResource(object):
 
 
     def __del__(self):
     def __del__(self):
         self.decrementCounter()
         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."
 "This module contains a deprecated shim emulating the old DConfig API."
 
 
-__all__ = []
+__all__ = ()
 
 
 from panda3d.core import (ConfigFlags, ConfigVariableBool, ConfigVariableInt,
 from panda3d.core import (ConfigFlags, ConfigVariableBool, ConfigVariableInt,
                           ConfigVariableDouble, ConfigVariableString)
                           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.showbase.DirectObject import DirectObject
 from direct.directnotify.DirectNotifyGlobal import directNotify
 from direct.directnotify.DirectNotifyGlobal import directNotify
 from panda3d.core import (
 from panda3d.core import (
@@ -62,7 +64,7 @@ class DistancePhasedNode(PhasedObject, DirectObject, NodePath):
 
 
     notify = directNotify.newCategory("DistancePhasedObject")
     notify = directNotify.newCategory("DistancePhasedObject")
     __InstanceSequence = 0
     __InstanceSequence = 0
-    __InstanceDeque = []
+    __InstanceDeque: list[int] = []
 
 
     @staticmethod
     @staticmethod
     def __allocateId():
     def __allocateId():
@@ -327,31 +329,3 @@ class BufferedDistancePhasedNode(DistancePhasedNode):
         for x,sphere in enumerate(self._colSpheres[phase+1:]):
         for x,sphere in enumerate(self._colSpheres[phase+1:]):
             sphere.node().modifySolid(0).setRadius(self.bufferParamList[x+phase+1][1][0])
             sphere.node().modifySolid(0).setRadius(self.bufferParamList[x+phase+1][1][0])
             sphere.node().markInternalBoundsStale()
             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
 # from its main exception handler
 wantStackDumpLog = False
 wantStackDumpLog = False
 wantStackDumpUpload = False
 wantStackDumpUpload = False
-variableDumpReasons = []
+variableDumpReasons: list = []
 dumpOnExceptionInit = False
 dumpOnExceptionInit = False
 
 
 
 
@@ -116,15 +116,10 @@ def _excepthookDumpVars(eType, eValue, tb):
         for name, obj in frame.f_locals.items():
         for name, obj in frame.f_locals.items():
             if name in codeNames:
             if name in codeNames:
                 name2obj[name] = obj
                 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()
         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])
             stateStack.push([name, name2obj[name], traversedIds])
 
 
         while len(stateStack) > 0:
         while len(stateStack) > 0:
@@ -150,14 +145,10 @@ def _excepthookDumpVars(eType, eValue, tb):
                                 continue
                                 continue
                         attrName2obj[attrName] = attr
                         attrName2obj[attrName] = attr
                 if len(attrName2obj) > 0:
                 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 = set(traversedIds)
                     ids.add(id(obj))
                     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]
                         obj = attrName2obj[attrName]
                         stateStack.push(['%s.%s' % (name, attrName), obj, ids])
                         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:
             if self._args.fullReport:
                 garbageIndices = range(self.numGarbage)
                 garbageIndices = range(self.numGarbage)
             else:
             else:
-                garbageIndices = list(self.cycleIds)
-                garbageIndices.sort()
+                garbageIndices = sorted(self.cycleIds)
             numGarbage = len(garbageIndices)
             numGarbage = len(garbageIndices)
 
 
             # log each individual item with a number in front of it
             # 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):
     def _getSortedPriorities(self):
         # returns all job priorities in ascending order
         # 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):
     def _process(self, task=None):
         if self._useOverflowTime is 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).
         return a matching event (needle) if found (in haystack).
         This is primarily a debugging tool.
         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:
             if repr(event).find(needle) >= 0:
                 return {event: self.__callbacks[event]}
                 return {event: self.__callbacks[event]}
 
 
@@ -553,9 +551,7 @@ class Messenger:
         This is primarily a debugging tool.
         This is primarily a debugging tool.
         """
         """
         matches = {}
         matches = {}
-        keys = list(self.__callbacks.keys())
-        keys.sort()
-        for event in keys:
+        for event in sorted(self.__callbacks):
             if repr(event).find(needle) >= 0:
             if repr(event).find(needle) >= 0:
                 matches[event] = self.__callbacks[event]
                 matches[event] = self.__callbacks[event]
                 # if the limit is not None, decrement and
                 # if the limit is not None, decrement and
@@ -596,9 +592,7 @@ class Messenger:
         Compact version of event, acceptor pairs
         Compact version of event, acceptor pairs
         """
         """
         str = "The messenger is currently handling:\n" + "="*64 + "\n"
         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)
             str += self.__eventRepr(event)
         # Print out the object: event dictionary too
         # Print out the object: event dictionary too
         str += "="*64 + "\n"
         str += "="*64 + "\n"
@@ -617,9 +611,7 @@ class Messenger:
         """
         """
         str = 'Messenger\n'
         str = 'Messenger\n'
         str = str + '='*50 + '\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]
             acceptorDict = self.__callbacks[event]
             str = str + 'Event: ' + event + '\n'
             str = str + 'Event: ' + event + '\n'
             for key in list(acceptorDict.keys()):
             for key in list(acceptorDict.keys()):

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

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

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

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

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

@@ -57,9 +57,7 @@ class OnScreenDebug:
         if not self.onScreenText:
         if not self.onScreenText:
             self.load()
             self.load()
         self.onScreenText.clearText()
         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:
             if v[0] == self.frame:
                 # It was updated this frame (key equals value):
                 # It was updated this frame (key equals value):
                 #isNew = " is"
                 #isNew = " is"

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

@@ -1,5 +1,7 @@
 """Contains miscellaneous utility functions and classes."""
 """Contains miscellaneous utility functions and classes."""
 
 
+from __future__ import annotations
+
 __all__ = [
 __all__ = [
 
 
     'indent', 'doc', 'adjust', 'difference', 'intersection', 'union',
     'indent', 'doc', 'adjust', 'difference', 'intersection', 'union',
@@ -11,7 +13,7 @@ __all__ = [
     'boolEqual', 'lineupPos', 'formatElapsedSeconds', 'solveQuadratic',
     'boolEqual', 'lineupPos', 'formatElapsedSeconds', 'solveQuadratic',
     'findPythonModule', 'mostDerivedLast', 'clampScalar', 'weightedChoice',
     'findPythonModule', 'mostDerivedLast', 'clampScalar', 'weightedChoice',
     'randFloat', 'normalDistrib', 'weightedRand', 'randUint31', 'randInt32',
     'randFloat', 'normalDistrib', 'weightedRand', 'randUint31', 'randInt32',
-    'SerialNumGen', 'serialNum', 'uniqueName', 'Singleton',
+    'SerialNumGen', 'SerialMaskedGen', 'serialNum', 'uniqueName', 'Singleton',
     'SingletonError', 'printListEnum', 'safeRepr', 'fastRepr',
     'SingletonError', 'printListEnum', 'safeRepr', 'fastRepr',
     'isDefaultValue', 'ScratchPad', 'Sync', 'itype', 'getNumberedTypedString',
     'isDefaultValue', 'ScratchPad', 'Sync', 'itype', 'getNumberedTypedString',
     'getNumberedTypedSortedString', 'printNumberedTyped', 'DelayedCall',
     'getNumberedTypedSortedString', 'printNumberedTyped', 'DelayedCall',
@@ -39,6 +41,7 @@ import time
 import builtins
 import builtins
 import importlib
 import importlib
 import functools
 import functools
+from typing import Callable
 
 
 __report_indent = 3
 __report_indent = 3
 
 
@@ -699,9 +702,9 @@ if __debug__:
         return profileDecorator
         return profileDecorator
 
 
     # intercept profile-related file operations to avoid disk access
     # intercept profile-related file operations to avoid disk access
-    movedOpenFuncs = []
-    movedDumpFuncs = []
-    movedLoadFuncs = []
+    movedOpenFuncs: list[Callable] = []
+    movedDumpFuncs: list[Callable] = []
+    movedLoadFuncs: list[Callable] = []
     profileFilenames = set()
     profileFilenames = set()
     profileFilenameList = Stack()
     profileFilenameList = Stack()
     profileFilename2file = {}
     profileFilename2file = {}
@@ -1899,7 +1902,7 @@ class SubframeCall:
 
 
 
 
 class PStatScope:
 class PStatScope:
-    collectors = {}
+    collectors: dict = {}
 
 
     def __init__(self, level = None):
     def __init__(self, level = None):
         self.levels = []
         self.levels = []
@@ -2458,18 +2461,6 @@ def formatTimeCompact(seconds):
     return result
     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):
 def formatTimeExact(seconds):
     # like formatTimeCompact but leaves off '0 seconds', '0 minutes' etc. for
     # like formatTimeCompact but leaves off '0 seconds', '0 minutes' etc. for
     # times that are e.g. 1 hour, 3 days etc.
     # times that are e.g. 1 hour, 3 days etc.
@@ -2496,19 +2487,6 @@ def formatTimeExact(seconds):
     return result
     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:
 class AlphabetCounter:
     # object that produces 'A', 'B', 'C', ... 'AA', 'AB', etc.
     # object that produces 'A', 'B', 'C', ... 'AA', 'AB', etc.
     def __init__(self):
     def __init__(self):
@@ -2540,28 +2518,6 @@ class AlphabetCounter:
     __next__ = next
     __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:
 class Default:
     # represents 'use the default value'
     # represents 'use the default value'
     # useful for keyword arguments to virtual methods
     # useful for keyword arguments to virtual methods
@@ -2665,52 +2621,52 @@ class PriorityCallbacks:
             callback()
             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__:
 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__:
 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__:
 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
 # This needs to be available early for DirectGUI imports
 import sys
 import sys
 import builtins
 import builtins
-builtins.config = DConfig
+builtins.config = DConfig  # type: ignore[attr-defined]
 
 
 from direct.directnotify.DirectNotifyGlobal import directNotify, giveNotify
 from direct.directnotify.DirectNotifyGlobal import directNotify, giveNotify
 from .MessengerGlobal import messenger
 from .MessengerGlobal import messenger
@@ -691,6 +691,7 @@ class ShowBase(DirectObject.DirectObject):
             allowAccessibilityShortcutKeys(True)
             allowAccessibilityShortcutKeys(True)
             self.__disabledStickyKeys = False
             self.__disabledStickyKeys = False
 
 
+        self.__directObject.ignoreAll()
         self.ignoreAll()
         self.ignoreAll()
         self.shutdown()
         self.shutdown()
 
 
@@ -3314,8 +3315,9 @@ class ShowBase(DirectObject.DirectObject):
         init_app_for_gui()
         init_app_for_gui()
 
 
         # Disable the Windows message loop, since Tcl wants to handle this all
         # 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):
         if ConfigVariableBool('tk-main-loop', True):
             # Put Tkinter in charge of the main loop.  It really
             # 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
 since ShowBase may not have been created yet; instead, ShowBase dynamically
 adds itself to this module's scope when instantiated."""
 adds itself to this module's scope when instantiated."""
 
 
-__all__ = []
+__all__ = ()
 
 
 from .ShowBase import ShowBase, WindowControls # pylint: disable=unused-import
 from .ShowBase import ShowBase, WindowControls # pylint: disable=unused-import
 from direct.directnotify.DirectNotifyGlobal import directNotify, giveNotify # 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
 __dev__ = ConfigVariableBool('want-dev', __debug__).value
 
 
+base: ShowBase
+
 #: The global instance of the :ref:`virtual-file-system`, as obtained using
 #: The global instance of the :ref:`virtual-file-system`, as obtained using
 #: :meth:`panda3d.core.VirtualFileSystem.getGlobalPtr()`.
 #: :meth:`panda3d.core.VirtualFileSystem.getGlobalPtr()`.
 vfs = VirtualFileSystem.getGlobalPtr()
 vfs = VirtualFileSystem.getGlobalPtr()
@@ -81,7 +83,7 @@ def inspect(anObject):
 
 
 
 
 import builtins
 import builtins
-builtins.inspect = inspect
+builtins.inspect = inspect  # type: ignore[attr-defined]
 
 
 # this also appears in AIBaseGlobal
 # this also appears in AIBaseGlobal
 if (not __debug__) and __dev__:
 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.
 the standard output as they happen.
 """
 """
 
 
-__all__ = []
+__all__ = ()
 
 
 
 
 import sys
 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
         # Sort the regions from largest to smallest to maximize
         # packing effectiveness.
         # 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:
         for tr in texRecords:
             self.placeTexture(tr)
             self.placeTexture(tr)

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

@@ -45,7 +45,7 @@ BasePickler = pickle._Pickler
 BaseUnpickler = pickle._Unpickler
 BaseUnpickler = pickle._Unpickler
 
 
 
 
-class Pickler(BasePickler):
+class Pickler(BasePickler):  # type: ignore[misc, valid-type]
 
 
     def __init__(self, *args, **kw):
     def __init__(self, *args, **kw):
         self.bamWriter = BamWriter()
         self.bamWriter = BamWriter()
@@ -148,7 +148,7 @@ class Pickler(BasePickler):
         self.save_reduce(obj=obj, *rv)
         self.save_reduce(obj=obj, *rv)
 
 
 
 
-class Unpickler(BaseUnpickler):
+class Unpickler(BaseUnpickler):  # type: ignore[misc, valid-type]
 
 
     def __init__(self, *args, **kw):
     def __init__(self, *args, **kw):
         self.bamReader = BamReader()
         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
 # Copy these static methods from Panda's Thread object.  These are
 # useful if you may be running in Panda's SIMPLE_THREADS compilation
 # useful if you may be running in Panda's SIMPLE_THREADS compilation
 # mode.
 # 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):
 class Thread(ThreadBase):
@@ -123,10 +123,7 @@ class Thread(ThreadBase):
         self.__dict__['ident'] = threadId
         self.__dict__['ident'] = threadId
 
 
     def __del__(self):
     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):
     def is_alive(self):
         thread = self.__thread
         thread = self.__thread
@@ -435,108 +432,3 @@ def setprofile(func):
 
 
 def stack_size(size = None):
 def stack_size(size = None):
     raise ThreadError
     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:
 else:
     # Disable this when using "python -O"
     # Disable this when using "python -O"
-    class _Verbose(object):
+    class _Verbose(object):  # type: ignore[no-redef]
         def __init__(self, verbose=None):
         def __init__(self, verbose=None):
             pass
             pass
         def _note(self, *args):
         def _note(self, *args):
@@ -762,92 +762,3 @@ def main_thread():
 ##     from thread import _local as local
 ##     from thread import _local as local
 ## except ImportError:
 ## except ImportError:
 ##     from _threading_local import local
 ##     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
     signal = None
 else:
 else:
     try:
     try:
-        import _signal as signal
+        import _signal as signal  # type: ignore[import, no-redef]
     except ImportError:
     except ImportError:
         signal = None
         signal = None
 
 
@@ -801,532 +801,6 @@ class TaskManager:
     #        time.sleep(delta)
     #        time.sleep(delta)
     #        delta = minFinTime - self.globalClock.getRealTime()
     #        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__:
 if __debug__:
     def checkLeak():
     def checkLeak():

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

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

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

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

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

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

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

@@ -1174,9 +1174,7 @@ class ParticlePanel(AppShell):
         self.particlesLabelMenu.add_separator()
         self.particlesLabelMenu.add_separator()
         # Add in a checkbutton for each effect (to toggle on/off)
         # Add in a checkbutton for each effect (to toggle on/off)
         particles = self.particleEffect.getParticlesList()
         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)
             particle = self.particleEffect.getParticlesNamed(name)
             self.particlesLabelMenu.add_command(
             self.particlesLabelMenu.add_command(
                 label = name,
                 label = name,
@@ -1199,9 +1197,7 @@ class ParticlePanel(AppShell):
         self.forceGroupLabelMenu.add_separator()
         self.forceGroupLabelMenu.add_separator()
         # Add in a checkbutton for each effect (to toggle on/off)
         # Add in a checkbutton for each effect (to toggle on/off)
         forceGroupList = self.particleEffect.getForceGroupList()
         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)
             force = self.particleEffect.getForceGroupNamed(name)
             self.forceGroupLabelMenu.add_command(
             self.forceGroupLabelMenu.add_command(
                 label = name,
                 label = name,
@@ -2913,21 +2909,3 @@ class ParticlePanel(AppShell):
                            min = 0.01,
                            min = 0.01,
                            value = force.getRadius())
                            value = force.getRadius())
         self.createForceActiveWidget(frame, pageName, forceName, force)
         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):
 def place(nodePath):
     return Placer(nodePath = 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
         # Get a list of task names
         taskNames = []
         taskNames = []
         self.__taskDict = {}
         self.__taskDict = {}
-        tasks = self.taskMgr.getTasks()
-        tasks.sort(key = lambda t: t.getName())
         count = 0
         count = 0
-        for task in tasks:
+        for task in sorted(self.taskMgr.getTasks(), key=lambda t: t.getName()):
             taskNames.append(task.getName())
             taskNames.append(task.getName())
             self.__taskDict[count] = task
             self.__taskDict[count] = task
             count += 1
             count += 1

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

@@ -21,11 +21,11 @@ import builtins
 
 
 # Create toplevel widget dictionary
 # Create toplevel widget dictionary
 if not hasattr(builtins, "widgetDict"):
 if not hasattr(builtins, "widgetDict"):
-    builtins.widgetDict = {}
+    builtins.widgetDict = {}  # type: ignore[attr-defined]
 
 
 # Create toplevel variable dictionary
 # Create toplevel variable dictionary
 if not hasattr(builtins, "variableDict"):
 if not hasattr(builtins, "variableDict"):
-    builtins.variableDict = {}
+    builtins.variableDict = {}  # type: ignore[attr-defined]
 
 
 
 
 def resetWidgetDict():
 def resetWidgetDict():
@@ -550,7 +550,3 @@ class TestAppShell(AppShell):
     def createInterface(self):
     def createInterface(self):
         self.createButtons()
         self.createButtons()
         self.createMain()
         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 """
         """ User redefinable callback executed on button release """
         if self['postCallback']:
         if self['postCallback']:
             self['postCallback'](*self['callbackData'])
             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])
         messenger.send('RGBPanel_setColor', [nodePath, r, g, b, a])
     esg['postCallback'] = onRelease
     esg['postCallback'] = onRelease
     return esg
     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):
     def reset(self):
         self.set(self['value'])
         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]
             initialcolor = tuple(self.get()[:3]))[0]
         if color:
         if color:
             self.set((color[0], color[1], color[2], self.getAt(3)))
             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.propertyDict = propertyDict
         self.propertyList = propertyList
         self.propertyList = propertyList
         if self.propertyList is None:
         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
         # Use default parent if none specified
         if not parent:
         if not parent:
             parent = tk._default_root
             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
 Modified by Summer 2010 Carnegie Mellon University ETC PandaLE team: fixed a bug in Viewport.Close
 """
 """
 
 
+from __future__ import annotations
+
 __all__ = ["Viewport", "ViewportManager"]
 __all__ = ["Viewport", "ViewportManager"]
 
 
 from panda3d.core import (
 from panda3d.core import (
@@ -36,7 +38,7 @@ VPPERSPECTIVE = 13
 
 
 class ViewportManager:
 class ViewportManager:
     """Manages the global viewport stuff."""
     """Manages the global viewport stuff."""
-    viewports = []
+    viewports: list[Viewport] = []
     gsg = None
     gsg = None
 
 
     @staticmethod
     @staticmethod
@@ -58,7 +60,7 @@ class ViewportManager:
             v.Layout(*args, **kwargs)
             v.Layout(*args, **kwargs)
 
 
 
 
-class Viewport(WxPandaWindow, DirectObject):
+class Viewport(WxPandaWindow, DirectObject):  # type: ignore[misc, valid-type]
     """Class representing a 3D Viewport."""
     """Class representing a 3D Viewport."""
     CREATENEW  = CREATENEW
     CREATENEW  = CREATENEW
     VPLEFT     = VPLEFT
     VPLEFT     = VPLEFT

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

@@ -1,2 +1,3 @@
+from direct.showbase.ShowBaseGlobal import base
 from .WxPandaShell import WxPandaShell
 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'):
 if not hasattr(wxgl, 'GLCanvas'):
     OpenGLPandaWindow = None
     OpenGLPandaWindow = None
 else:
 else:
-    class OpenGLPandaWindow(wxgl.GLCanvas):
+    class OpenGLPandaWindow(wxgl.GLCanvas):  # type: ignore[no-redef]
         """ This class implements a Panda3D "window" that actually draws
         """ This class implements a Panda3D "window" that actually draws
         within the wx GLCanvas object.  It is supported whenever OpenGL is
         within the wx GLCanvas object.  It is supported whenever OpenGL is
         Panda's rendering engine, and GLCanvas is available in wx. """
         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()
 else()
   set(CMAKE_C_FLAGS_STANDARD "-O3")
   set(CMAKE_C_FLAGS_STANDARD "-O3")
   set(CMAKE_CXX_FLAGS_STANDARD "-O3")
   set(CMAKE_CXX_FLAGS_STANDARD "-O3")
+  set(CMAKE_OBJCXX_FLAGS_STANDARD "-O3")
 endif()
 endif()
 set(CMAKE_SHARED_LINKER_FLAGS_STANDARD "")
 set(CMAKE_SHARED_LINKER_FLAGS_STANDARD "")
 set(CMAKE_MODULE_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")
     "${CMAKE_C_FLAGS_DEBUG} -fprofile-instr-generate -fcoverage-mapping")
   set(CMAKE_CXX_FLAGS_COVERAGE
   set(CMAKE_CXX_FLAGS_COVERAGE
     "${CMAKE_CXX_FLAGS_DEBUG} -fprofile-instr-generate -fcoverage-mapping")
     "${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
   set(CMAKE_SHARED_LINKER_FLAGS_COVERAGE
     "${CMAKE_SHARED_LINKER_FLAGS_DEBUG} -fprofile-instr-generate")
     "${CMAKE_SHARED_LINKER_FLAGS_DEBUG} -fprofile-instr-generate")
@@ -101,11 +104,14 @@ endif()
 # Set warning levels
 # Set warning levels
 if(MSVC)
 if(MSVC)
   set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /W3")
   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()
 else()
   set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall")
   set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall")
   set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall")
   set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall")
+  if(APPLE)
+    set(CMAKE_OBJCXX_FLAGS "${CMAKE_OBJCXX_FLAGS} -Wall")
+  endif()
 
 
 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_MINSIZEREL "${CMAKE_CXX_FLAGS_MINSIZEREL} ${release_flags}")
   set(CMAKE_CXX_FLAGS_STANDARD "${CMAKE_CXX_FLAGS_STANDARD} ${standard_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)
   if(MSVC)
     # Clang behaving as MSVC
     # Clang behaving as MSVC
     set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-unused-command-line-argument")
     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.
 # and stops us from identifying cases where ENABLE_EXPORTS is needed.
 set(CMAKE_SHARED_LIBRARY_LINK_C_FLAGS "")
 set(CMAKE_SHARED_LIBRARY_LINK_C_FLAGS "")
 set(CMAKE_SHARED_LIBRARY_LINK_CXX_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
 # As long as we're figuring out compiler flags, figure out the flags for
 # turning C++ exception support on and off
 # turning C++ exception support on and off

+ 3 - 1
dtool/Package.cmake

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

Dosya farkı çok büyük olduğundan ihmal edildi
+ 1884 - 1881
dtool/src/cppparser/cppBison.cxx.prebuilt


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

@@ -2971,8 +2971,17 @@ base_specification:
         ;
         ;
 
 
 enum:
 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;
   current_enum = nullptr;
   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
 // flag to link in Python, we'll just excerpt the forward declaration of
 // PyObject.
 // PyObject.
 typedef struct _object PyObject;
 typedef struct _object PyObject;
+typedef struct _typeobject PyTypeObject;
 
 
 #ifndef HAVE_EIGEN
 #ifndef HAVE_EIGEN
 // If we don't have the Eigen library, don't define LINMATH_ALIGN.
 // 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.
  * Returns the internal void pointer that is stored for interrogate's benefit.
  */
  */
-PyObject *TypeHandle::
+PyTypeObject *TypeHandle::
 get_python_type() const {
 get_python_type() const {
   TypeRegistryNode *rnode = TypeRegistry::ptr()->look_up(*this, nullptr);
   TypeRegistryNode *rnode = TypeRegistry::ptr()->look_up(*this, nullptr);
   if (rnode != nullptr) {
   if (rnode != nullptr) {
@@ -164,6 +164,22 @@ get_python_type() const {
     return nullptr;
     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
 #endif
 
 
 std::ostream &
 std::ostream &

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

@@ -140,7 +140,8 @@ PUBLISHED:
 
 
 public:
 public:
 #ifdef HAVE_PYTHON
 #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
 #endif // HAVE_PYTHON
 
 
   void *allocate_array(size_t size) RETURNS_ALIGNED(MEMORY_HOOK_ALIGNMENT);
   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,
   // 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.
   // 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) {
   if (py_type != nullptr && *_this == ((Dtool_PyTypedObject *)py_type)->_type) {
     PyObject *func = PyObject_GetAttrString((PyObject *)&Dtool_TypeHandle, "make");
     PyObject *func = PyObject_GetAttrString((PyObject *)&Dtool_TypeHandle, "make");
     return Py_BuildValue("N(O)", func, py_type);
     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.
  * of interrogate, which expects this to contain a Dtool_PyTypedObject.
  */
  */
 void TypeRegistry::
 void TypeRegistry::
-record_python_type(TypeHandle type, PyObject *python_type) {
+record_python_type(TypeHandle type, PyTypeObject *cls, PythonWrapFunc *wrap_func) {
   _lock.lock();
   _lock.lock();
 
 
   TypeRegistryNode *rnode = look_up(type, nullptr);
   TypeRegistryNode *rnode = look_up(type, nullptr);
   if (rnode != nullptr) {
   if (rnode != nullptr) {
-    rnode->_python_type = python_type;
+    rnode->_python_type = cls;
+    rnode->_python_wrap_func = wrap_func;
   }
   }
 
 
   _lock.unlock();
   _lock.unlock();

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

@@ -40,13 +40,18 @@ public:
   // convenience function, defined in register_type.h.
   // convenience function, defined in register_type.h.
   bool register_type(TypeHandle &type_handle, const std::string &name);
   bool register_type(TypeHandle &type_handle, const std::string &name);
 
 
+#ifdef HAVE_PYTHON
+  typedef PyObject *PythonWrapFunc(void *ptr, PyTypeObject *cast_from);
+#endif
+
 PUBLISHED:
 PUBLISHED:
   TypeHandle register_dynamic_type(const std::string &name);
   TypeHandle register_dynamic_type(const std::string &name);
 
 
   void record_derivation(TypeHandle child, TypeHandle parent);
   void record_derivation(TypeHandle child, TypeHandle parent);
   void record_alternate_name(TypeHandle type, const std::string &name);
   void record_alternate_name(TypeHandle type, const std::string &name);
 #ifdef HAVE_PYTHON
 #ifdef HAVE_PYTHON
-  void record_python_type(TypeHandle type, PyObject *python_type);
+  void record_python_type(TypeHandle type, PyTypeObject *cls,
+                          PythonWrapFunc *wrap_func);
 #endif
 #endif
 
 
   TypeHandle find_type(const std::string &name) const;
   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.
  * Returns the Python type object associated with this node.
  */
  */
-INLINE PyObject *TypeRegistryNode::
+INLINE PyTypeObject *TypeRegistryNode::
 get_python_type() const {
 get_python_type() const {
   if (_python_type != nullptr || _parent_classes.empty()) {
   if (_python_type != nullptr || _parent_classes.empty()) {
     return _python_type;
     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
  * Recurses through the parent nodes to find the best Python type object to
  * represent objects of this type.
  * represent objects of this type.
  */
  */
-PyObject *TypeRegistryNode::
+PyTypeObject *TypeRegistryNode::
 r_get_python_type() const {
 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) {
     if (parent->_python_type != nullptr) {
       return parent->_python_type;
       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) {
       if (py_type != nullptr) {
         return py_type;
         return py_type;
       }
       }
@@ -330,6 +328,28 @@ r_get_python_type() const {
   return nullptr;
   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
  * 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
  * 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 {
 class EXPCL_DTOOL_DTOOLBASE TypeRegistryNode {
 public:
 public:
+  typedef PyObject *PythonWrapFunc(void *ptr, PyTypeObject *cast_from);
+
   TypeRegistryNode(TypeHandle handle, const std::string &name, TypeHandle &ref);
   TypeRegistryNode(TypeHandle handle, const std::string &name, TypeHandle &ref);
 
 
   static bool is_derived_from(const TypeRegistryNode *child,
   static bool is_derived_from(const TypeRegistryNode *child,
@@ -38,7 +40,8 @@ public:
   static TypeHandle get_parent_towards(const TypeRegistryNode *child,
   static TypeHandle get_parent_towards(const TypeRegistryNode *child,
                                        const TypeRegistryNode *base);
                                        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 clear_subtree();
   void define_subtree();
   void define_subtree();
@@ -49,7 +52,8 @@ public:
   typedef std::vector<TypeRegistryNode *> Classes;
   typedef std::vector<TypeRegistryNode *> Classes;
   Classes _parent_classes;
   Classes _parent_classes;
   Classes _child_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];
   patomic<size_t> _memory_usage[TypeHandle::MC_limit];
 
 
@@ -81,7 +85,8 @@ private:
   void r_build_subtrees(TypeRegistryNode *top,
   void r_build_subtrees(TypeRegistryNode *top,
                         int bit_count, SubtreeMaskType bits);
                         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,
   static bool check_derived_from(const TypeRegistryNode *child,
                                  const TypeRegistryNode *base);
                                  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
 #ifdef PREREAD_ENVIRONMENT
   return false;
   return false;
-#elif defined(_MSC_VER)
+#elif defined(_WIN32)
   size_t size = 0;
   size_t size = 0;
   getenv_s(&size, nullptr, 0, var.c_str());
   getenv_s(&size, nullptr, 0, var.c_str());
   return size != 0;
   return size != 0;
@@ -305,7 +305,7 @@ ns_get_environment_variable(const string &var) const {
   }
   }
 
 
 #ifndef PREREAD_ENVIRONMENT
 #ifndef PREREAD_ENVIRONMENT
-#ifdef _MSC_VER
+#ifdef _WIN32
   std::string value(128, '\0');
   std::string value(128, '\0');
   size_t size = value.size();
   size_t size = value.size();
   while (getenv_s(&size, &value[0], size, var.c_str()) == ERANGE) {
   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) {
 ns_set_environment_variable(const string &var, const string &value) {
   _variables[var] = value;
   _variables[var] = value;
 
 
-#ifdef _MSC_VER
+#ifdef _WIN32
   _putenv_s(var.c_str(), value.c_str());
   _putenv_s(var.c_str(), value.c_str());
 #else
 #else
   setenv(var.c_str(), value.c_str(), 1);
   setenv(var.c_str(), value.c_str(), 1);
@@ -464,7 +464,7 @@ ns_clear_shadow(const string &var) {
 
 
 #ifdef PREREAD_ENVIRONMENT
 #ifdef PREREAD_ENVIRONMENT
   // Now we have to replace the value in the table.
   // Now we have to replace the value in the table.
-#ifdef _MSC_VER
+#ifdef _WIN32
   std::string value(128, '\0');
   std::string value(128, '\0');
   size_t size = value.size();
   size_t size = value.size();
   while (getenv_s(&size, &value[0], size, var.c_str()) == ERANGE) {
   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.
  * execute permission.
+ *
+ * @see VirtualFileSystem::exists() for checking whether the filename exists in
+ * the virtual file system.
  */
  */
 bool Filename::
 bool Filename::
 exists() const {
 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::
 bool Filename::
 is_regular_file() const {
 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::
 bool Filename::
 is_writable() const {
 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::
 bool Filename::
 is_directory() const {
 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
  * 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
  * for file existence and searching a searchpath, as well as the best way to
  * open an fstream for reading or writing.
  * 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 {
 class EXPCL_DTOOL_DTOOLUTIL Filename {
 PUBLISHED:
 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";
       call << separator << "self";
       separator = ", ";
       separator = ", ";
     }
     }
+    if (_flags & F_explicit_cls) {
+      call << separator << "cls";
+      separator = ", ";
+    }
 
 
     size_t pn;
     size_t pn;
     size_t num_parameters = pexprs.size();
     size_t num_parameters = pexprs.size();
@@ -781,14 +785,20 @@ setup_properties(const InterrogateFunction &ifunc, InterfaceMaker *interface_mak
     first_param = 1;
     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())) {
       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
     // 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
     // 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) {
   if (_parameters.size() == first_param) {

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

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

Bu fark içinde çok fazla dosya değişikliği olduğu için bazı dosyalar gösterilmiyor