Browse Source

Merge branch 'master' into shaderpipeline

rdb 2 years ago
parent
commit
685f5421bb
100 changed files with 1956 additions and 1122 deletions
  1. 112 48
      .github/workflows/ci.yml
  2. 3 11
      BACKERS.md
  3. 5 5
      README.md
  4. 10 8
      contrib/src/rplight/pssmCameraRig.cxx
  5. 2 2
      direct/src/dcparser/dcClass.cxx
  6. 17 8
      direct/src/dist/FreezeTool.py
  7. 66 19
      direct/src/dist/commands.py
  8. 30 0
      direct/src/dist/pefile.py
  9. 1 1
      direct/src/distributed/ConnectionRepository.py
  10. 3 2
      direct/src/distributed/cConnectionRepository.cxx
  11. 7 0
      direct/src/filter/CommonFilters.py
  12. 1 1
      direct/src/interval/Interval.py
  13. 1 1
      direct/src/interval/MetaInterval.py
  14. 1 1
      direct/src/interval/cInterval.cxx
  15. 170 72
      direct/src/motiontrail/MotionTrail.py
  16. 285 434
      direct/src/motiontrail/cMotionTrail.cxx
  17. 37 39
      direct/src/motiontrail/cMotionTrail.h
  18. 26 6
      direct/src/showbase/ContainerLeakDetector.py
  19. 28 14
      direct/src/showbase/ContainerReport.py
  20. 5 2
      direct/src/showbase/EventManager.py
  21. 6 7
      direct/src/showbase/GarbageReport.py
  22. 1 1
      direct/src/showbase/Job.py
  23. 46 35
      direct/src/showbase/ShowBase.py
  24. 0 1
      direct/src/showutil/Effects.py
  25. 96 0
      doc/ReleaseNotes
  26. 1 0
      dtool/src/dtoolbase/deletedBufferChain.cxx
  27. 2 2
      dtool/src/dtoolbase/memoryHook.I
  28. 18 18
      dtool/src/dtoolbase/memoryHook.cxx
  29. 5 5
      dtool/src/dtoolbase/memoryHook.h
  30. 1 0
      dtool/src/dtoolbase/patomic.I
  31. 0 19
      dtool/src/dtoolbase/typeHandle.cxx
  32. 5 3
      dtool/src/dtoolbase/typeHandle.h
  33. 0 18
      dtool/src/dtoolbase/typeRegistry.cxx
  34. 0 3
      dtool/src/dtoolbase/typeRegistry.h
  35. 0 8
      dtool/src/dtoolbase/typedObject.I
  36. 0 2
      dtool/src/dtoolbase/typedObject.h
  37. 4 0
      dtool/src/dtoolutil/executionEnvironment.cxx
  38. 32 21
      dtool/src/dtoolutil/filename.cxx
  39. 11 8
      dtool/src/dtoolutil/filename.h
  40. 10 10
      dtool/src/dtoolutil/textEncoder.h
  41. 7 2
      dtool/src/interrogate/functionRemap.cxx
  42. 25 10
      dtool/src/interrogate/interfaceMakerPythonNative.cxx
  43. 1 1
      dtool/src/interrogate/interrogate.cxx
  44. 3 3
      dtool/src/interrogatedb/dtool_super_base.cxx
  45. 4 0
      dtool/src/interrogatedb/py_compat.h
  46. 12 18
      dtool/src/interrogatedb/py_panda.cxx
  47. 3 2
      dtool/src/interrogatedb/py_panda.h
  48. 3 0
      dtool/src/parser-inc/Python.h
  49. 0 71
      dtool/src/parser-inc/tinyxml.h
  50. 2 0
      dtool/src/prc/CMakeLists.txt
  51. 3 1
      dtool/src/prc/configVariable.h
  52. 11 0
      dtool/src/prc/configVariableFilename.I
  53. 1 0
      dtool/src/prc/configVariableFilename.h
  54. 73 2
      dtool/src/prc/encryptStreamBuf.cxx
  55. 48 0
      dtool/src/prc/notify_ext.cxx
  56. 37 0
      dtool/src/prc/notify_ext.h
  57. 1 0
      dtool/src/prc/p3prc_ext_composite.cxx
  58. 4 0
      dtool/src/prc/pnotify.h
  59. 3 2
      dtool/src/prc/streamReader.h
  60. 3 1
      dtool/src/prc/streamWriter.h
  61. 31 8
      makepanda/installer.nsi
  62. 52 24
      makepanda/installpanda.py
  63. 31 37
      makepanda/makepanda.py
  64. 6 2
      makepanda/makepandacore.py
  65. 4 0
      makepanda/makewheel.py
  66. 0 11
      makepanda/panda3d.desktop
  67. 12 0
      makepanda/pstats.desktop
  68. 2 2
      makepanda/pview.desktop
  69. 4 2
      panda/CMakeLists.txt
  70. 1 1
      panda/metalibs/panda/CMakeLists.txt
  71. 8 0
      panda/src/audio/audioSound.I
  72. 1 1
      panda/src/audio/audioSound.cxx
  73. 6 1
      panda/src/audio/audioSound.h
  74. 11 16
      panda/src/audio/nullAudioManager.cxx
  75. 1 1
      panda/src/audio/nullAudioSound.cxx
  76. 1 2
      panda/src/audiotraits/fmodAudioSound.cxx
  77. 1 1
      panda/src/audiotraits/openalAudioSound.cxx
  78. 0 1
      panda/src/audiotraits/openalAudioSound.h
  79. 54 21
      panda/src/cocoadisplay/cocoaGraphicsWindow.mm
  80. 9 1
      panda/src/cocoadisplay/cocoaPandaAppDelegate.h
  81. 48 0
      panda/src/cocoadisplay/cocoaPandaAppDelegate.mm
  82. 1 0
      panda/src/cocoadisplay/cocoaPandaWindowDelegate.h
  83. 13 4
      panda/src/cocoadisplay/cocoaPandaWindowDelegate.mm
  84. 0 8
      panda/src/collide/collisionHandler.cxx
  85. 5 6
      panda/src/collide/collisionHandler.h
  86. 2 0
      panda/src/display/CMakeLists.txt
  87. 2 0
      panda/src/display/config_display.cxx
  88. 3 1
      panda/src/display/frameBufferProperties.h
  89. 5 0
      panda/src/display/graphicsEngine.cxx
  90. 89 1
      panda/src/display/graphicsOutput.cxx
  91. 7 0
      panda/src/display/graphicsOutput.h
  92. 3 1
      panda/src/display/graphicsPipeSelection.h
  93. 5 1
      panda/src/display/graphicsStateGuardian.cxx
  94. 10 7
      panda/src/display/graphicsStateGuardian.h
  95. 4 2
      panda/src/display/graphicsWindow.h
  96. 1 0
      panda/src/display/p3display_composite2.cxx
  97. 39 0
      panda/src/display/screenshotRequest.I
  98. 104 0
      panda/src/display/screenshotRequest.cxx
  99. 73 0
      panda/src/display/screenshotRequest.h
  100. 15 11
      panda/src/display/windowProperties.h

+ 112 - 48
.github/workflows/ci.yml

@@ -92,10 +92,10 @@ jobs:
     - name: Install dependencies (macOS)
     - name: Install dependencies (macOS)
       if: runner.os == 'macOS'
       if: runner.os == 'macOS'
       run: |
       run: |
-        curl -O https://www.panda3d.org/download/panda3d-1.10.10/panda3d-1.10.10-tools-mac.tar.gz
-        tar -xf panda3d-1.10.10-tools-mac.tar.gz
-        mv panda3d-1.10.10/thirdparty thirdparty
-        rmdir panda3d-1.10.10
+        curl -O https://www.panda3d.org/download/panda3d-1.10.13/panda3d-1.10.13-tools-mac.tar.gz
+        tar -xf panda3d-1.10.13-tools-mac.tar.gz
+        mv panda3d-1.10.13/thirdparty thirdparty
+        rmdir panda3d-1.10.13
 
 
         # Temporary hack so that pzip can run, since we are about to remove Cg anyway.
         # Temporary hack so that pzip can run, since we are about to remove Cg anyway.
         install_name_tool -id "$(pwd)/thirdparty/darwin-libs-a/nvidiacg/lib/libCg.dylib" thirdparty/darwin-libs-a/nvidiacg/lib/libCg.dylib
         install_name_tool -id "$(pwd)/thirdparty/darwin-libs-a/nvidiacg/lib/libCg.dylib" thirdparty/darwin-libs-a/nvidiacg/lib/libCg.dylib
@@ -124,16 +124,16 @@ jobs:
       uses: actions/cache@v1
       uses: actions/cache@v1
       with:
       with:
         path: thirdparty
         path: thirdparty
-        key: ci-cmake-${{ runner.OS }}-thirdparty-v1.10.10-r1
+        key: ci-cmake-${{ runner.OS }}-thirdparty-v1.10.13-r1
     - name: Install dependencies (Windows)
     - name: Install dependencies (Windows)
       if: runner.os == 'Windows'
       if: runner.os == 'Windows'
       shell: powershell
       shell: powershell
       run: |
       run: |
         if (!(Test-Path thirdparty/win-libs-vc14-x64)) {
         if (!(Test-Path thirdparty/win-libs-vc14-x64)) {
           $wc = New-Object System.Net.WebClient
           $wc = New-Object System.Net.WebClient
-          $wc.DownloadFile("https://www.panda3d.org/download/panda3d-1.10.10/panda3d-1.10.10-tools-win64.zip", "thirdparty-tools.zip")
+          $wc.DownloadFile("https://www.panda3d.org/download/panda3d-1.10.13/panda3d-1.10.13-tools-win64.zip", "thirdparty-tools.zip")
           Expand-Archive -Path thirdparty-tools.zip
           Expand-Archive -Path thirdparty-tools.zip
-          Move-Item -Path thirdparty-tools/panda3d-1.10.10/thirdparty -Destination .
+          Move-Item -Path thirdparty-tools/panda3d-1.10.13/thirdparty -Destination .
         }
         }
 
 
     - name: ccache (non-Windows)
     - name: ccache (non-Windows)
@@ -173,7 +173,7 @@ jobs:
         -D CMAKE_UNITY_BUILD=${{ matrix.unity }}
         -D CMAKE_UNITY_BUILD=${{ matrix.unity }}
         -D CMAKE_BUILD_TYPE="${{ matrix.config }}"
         -D CMAKE_BUILD_TYPE="${{ matrix.config }}"
         -D BUILD_METALIBS=${{ matrix.metalibs }}
         -D BUILD_METALIBS=${{ matrix.metalibs }}
-        -D HAVE_PYTHON=${{ matrix.python }}
+        -D HAVE_PYTHON=${{ runner.os != 'Windows' && matrix.python || 'NO' }}
         -D HAVE_EIGEN=${{ matrix.eigen }}
         -D HAVE_EIGEN=${{ matrix.eigen }}
         ..
         ..
 
 
@@ -184,25 +184,25 @@ 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.6)
+    - name: Setup Python (Python 3.7)
       if: contains(matrix.python, 'YES')
       if: contains(matrix.python, 'YES')
-      uses: actions/setup-python@v2
+      uses: actions/setup-python@v4
       with:
       with:
-        python-version: 3.6
-    - name: Configure (Python 3.6)
+        python-version: '3.7'
+    - name: Configure (Python 3.7)
       if: contains(matrix.python, 'YES')
       if: contains(matrix.python, 'YES')
       working-directory: build
       working-directory: build
       shell: bash
       shell: bash
       run: >
       run: >
-        cmake -DWANT_PYTHON_VERSION=3.6
+        cmake -DWANT_PYTHON_VERSION=3.7 -DHAVE_PYTHON=YES
         -DPython_FIND_REGISTRY=NEVER -DPython_ROOT="$pythonLocation" .
         -DPython_FIND_REGISTRY=NEVER -DPython_ROOT="$pythonLocation" .
-    - name: Build (Python 3.6)
+    - name: Build (Python 3.7)
       if: contains(matrix.python, 'YES')
       if: contains(matrix.python, 'YES')
       # BEGIN A
       # BEGIN A
       working-directory: build
       working-directory: build
       run: cmake --build . --config ${{ matrix.config }} --parallel 4
       run: cmake --build . --config ${{ matrix.config }} --parallel 4
       # END A
       # END A
-    - name: Test (Python 3.6)
+    - name: Test (Python 3.7)
       # BEGIN B
       # BEGIN B
       if: contains(matrix.python, 'YES')
       if: contains(matrix.python, 'YES')
       working-directory: build
       working-directory: build
@@ -216,25 +216,25 @@ jobs:
         $PYTHON_EXECUTABLE -m pytest ../tests --cov=.
         $PYTHON_EXECUTABLE -m pytest ../tests --cov=.
       # END B
       # END B
 
 
-    - name: Setup Python (Python 3.7)
+    - name: Setup Python (Python 3.8)
       if: contains(matrix.python, 'YES')
       if: contains(matrix.python, 'YES')
-      uses: actions/setup-python@v2
+      uses: actions/setup-python@v4
       with:
       with:
-        python-version: 3.7
-    - name: Configure (Python 3.7)
+        python-version: '3.8'
+    - name: Configure (Python 3.8)
       if: contains(matrix.python, 'YES')
       if: contains(matrix.python, 'YES')
       working-directory: build
       working-directory: build
       shell: bash
       shell: bash
       run: >
       run: >
-        cmake -DWANT_PYTHON_VERSION=3.7
+        cmake -DWANT_PYTHON_VERSION=3.8 -DHAVE_PYTHON=YES
         -DPython_FIND_REGISTRY=NEVER -DPython_ROOT="$pythonLocation" .
         -DPython_FIND_REGISTRY=NEVER -DPython_ROOT="$pythonLocation" .
-    - name: Build (Python 3.7)
+    - name: Build (Python 3.8)
       if: contains(matrix.python, 'YES')
       if: contains(matrix.python, 'YES')
       # BEGIN A
       # BEGIN A
       working-directory: build
       working-directory: build
       run: cmake --build . --config ${{ matrix.config }} --parallel 4
       run: cmake --build . --config ${{ matrix.config }} --parallel 4
       # END A
       # END A
-    - name: Test (Python 3.7)
+    - name: Test (Python 3.8)
       # BEGIN B
       # BEGIN B
       if: contains(matrix.python, 'YES')
       if: contains(matrix.python, 'YES')
       working-directory: build
       working-directory: build
@@ -248,25 +248,25 @@ jobs:
         $PYTHON_EXECUTABLE -m pytest ../tests --cov=.
         $PYTHON_EXECUTABLE -m pytest ../tests --cov=.
       # END B
       # END B
 
 
-    - name: Setup Python (Python 3.8)
+    - name: Setup Python (Python 3.9)
       if: contains(matrix.python, 'YES')
       if: contains(matrix.python, 'YES')
-      uses: actions/setup-python@v2
+      uses: actions/setup-python@v4
       with:
       with:
-        python-version: 3.8
-    - name: Configure (Python 3.8)
+        python-version: '3.9'
+    - name: Configure (Python 3.9)
       if: contains(matrix.python, 'YES')
       if: contains(matrix.python, 'YES')
       working-directory: build
       working-directory: build
       shell: bash
       shell: bash
       run: >
       run: >
-        cmake -DWANT_PYTHON_VERSION=3.8
+        cmake -DWANT_PYTHON_VERSION=3.9 -DHAVE_PYTHON=YES
         -DPython_FIND_REGISTRY=NEVER -DPython_ROOT="$pythonLocation" .
         -DPython_FIND_REGISTRY=NEVER -DPython_ROOT="$pythonLocation" .
-    - name: Build (Python 3.8)
+    - name: Build (Python 3.9)
       if: contains(matrix.python, 'YES')
       if: contains(matrix.python, 'YES')
       # BEGIN A
       # BEGIN A
       working-directory: build
       working-directory: build
       run: cmake --build . --config ${{ matrix.config }} --parallel 4
       run: cmake --build . --config ${{ matrix.config }} --parallel 4
       # END A
       # END A
-    - name: Test (Python 3.8)
+    - name: Test (Python 3.9)
       # BEGIN B
       # BEGIN B
       if: contains(matrix.python, 'YES')
       if: contains(matrix.python, 'YES')
       working-directory: build
       working-directory: build
@@ -280,25 +280,57 @@ jobs:
         $PYTHON_EXECUTABLE -m pytest ../tests --cov=.
         $PYTHON_EXECUTABLE -m pytest ../tests --cov=.
       # END B
       # END B
 
 
-    - name: Setup Python (Python 3.9)
+    - name: Setup Python (Python 3.10)
       if: contains(matrix.python, 'YES')
       if: contains(matrix.python, 'YES')
-      uses: actions/setup-python@v2
+      uses: actions/setup-python@v4
       with:
       with:
-        python-version: 3.9
-    - name: Configure (Python 3.9)
+        python-version: '3.10'
+    - name: Configure (Python 3.10)
       if: contains(matrix.python, 'YES')
       if: contains(matrix.python, 'YES')
       working-directory: build
       working-directory: build
       shell: bash
       shell: bash
       run: >
       run: >
-        cmake -DWANT_PYTHON_VERSION=3.9
+        cmake -DWANT_PYTHON_VERSION=3.10 -DHAVE_PYTHON=YES
         -DPython_FIND_REGISTRY=NEVER -DPython_ROOT="$pythonLocation" .
         -DPython_FIND_REGISTRY=NEVER -DPython_ROOT="$pythonLocation" .
-    - name: Build (Python 3.9)
+    - name: Build (Python 3.10)
       if: contains(matrix.python, 'YES')
       if: contains(matrix.python, 'YES')
       # BEGIN A
       # BEGIN A
       working-directory: build
       working-directory: build
       run: cmake --build . --config ${{ matrix.config }} --parallel 4
       run: cmake --build . --config ${{ matrix.config }} --parallel 4
       # END A
       # END A
-    - name: Test (Python 3.9)
+    - name: Test (Python 3.10)
+      # 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.11)
+      if: contains(matrix.python, 'YES')
+      uses: actions/setup-python@v4
+      with:
+        python-version: '3.11'
+    - name: Configure (Python 3.11)
+      if: contains(matrix.python, 'YES')
+      working-directory: build
+      shell: bash
+      run: >
+        cmake -DWANT_PYTHON_VERSION=3.11 -DHAVE_PYTHON=YES
+        -DPython_FIND_REGISTRY=NEVER -DPython_ROOT="$pythonLocation" .
+    - name: Build (Python 3.11)
+      if: contains(matrix.python, 'YES')
+      # BEGIN A
+      working-directory: build
+      run: cmake --build . --config ${{ matrix.config }} --parallel 4
+      # END A
+    - name: Test (Python 3.11)
       # BEGIN B
       # BEGIN B
       if: contains(matrix.python, 'YES')
       if: contains(matrix.python, 'YES')
       working-directory: build
       working-directory: build
@@ -347,21 +379,50 @@ jobs:
       shell: powershell
       shell: powershell
       run: |
       run: |
         $wc = New-Object System.Net.WebClient
         $wc = New-Object System.Net.WebClient
-        $wc.DownloadFile("https://www.panda3d.org/download/panda3d-1.10.11/panda3d-1.10.11-tools-win64.zip", "thirdparty-tools.zip")
+        $wc.DownloadFile("https://www.panda3d.org/download/panda3d-1.10.13/panda3d-1.10.13-tools-win64.zip", "thirdparty-tools.zip")
         Expand-Archive -Path thirdparty-tools.zip
         Expand-Archive -Path thirdparty-tools.zip
-        Move-Item -Path thirdparty-tools/panda3d-1.10.11/thirdparty -Destination .
+        Move-Item -Path thirdparty-tools/panda3d-1.10.13/thirdparty -Destination .
     - name: Get thirdparty packages (macOS)
     - name: Get thirdparty packages (macOS)
       if: runner.os == 'macOS'
       if: runner.os == 'macOS'
       run: |
       run: |
-        curl -O https://www.panda3d.org/download/panda3d-1.10.11/panda3d-1.10.11-tools-mac.tar.gz
-        tar -xf panda3d-1.10.11-tools-mac.tar.gz
-        mv panda3d-1.10.11/thirdparty thirdparty
-        rmdir panda3d-1.10.11
+        curl -O https://www.panda3d.org/download/panda3d-1.10.13/panda3d-1.10.13-tools-mac.tar.gz
+        tar -xf panda3d-1.10.13-tools-mac.tar.gz
+        mv panda3d-1.10.13/thirdparty thirdparty
+        rmdir panda3d-1.10.13
         (cd thirdparty/darwin-libs-a && rm -rf rocket)
         (cd thirdparty/darwin-libs-a && rm -rf rocket)
+
+    - name: Set up Python 3.11
+      uses: actions/setup-python@v4
+      with:
+        python-version: '3.11'
+    - name: Build Python 3.11
+      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.11
+      shell: bash
+      run: |
+        python -m pip install pytest
+        PYTHONPATH=built LD_LIBRARY_PATH=built/lib DYLD_LIBRARY_PATH=built/lib python -m pytest
+
+    - name: Set up Python 3.10
+      uses: actions/setup-python@v4
+      with:
+        python-version: '3.10'
+    - name: Build Python 3.10
+      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.10
+      shell: bash
+      run: |
+        python -m pip install pytest
+        PYTHONPATH=built LD_LIBRARY_PATH=built/lib DYLD_LIBRARY_PATH=built/lib python -m pytest
+
     - name: Set up Python 3.9
     - name: Set up Python 3.9
-      uses: actions/setup-python@v2
+      uses: actions/setup-python@v4
       with:
       with:
-        python-version: 3.9
+        python-version: '3.9'
     - name: Build Python 3.9
     - name: Build Python 3.9
       shell: bash
       shell: bash
       run: |
       run: |
@@ -371,10 +432,11 @@ jobs:
       run: |
       run: |
         python -m pip install pytest
         python -m pip install pytest
         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
-      uses: actions/setup-python@v2
+      uses: actions/setup-python@v4
       with:
       with:
-        python-version: 3.8
+        python-version: '3.8'
     - name: Build Python 3.8
     - name: Build Python 3.8
       shell: bash
       shell: bash
       run: |
       run: |
@@ -384,10 +446,11 @@ jobs:
       run: |
       run: |
         python -m pip install pytest
         python -m pip install pytest
         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.7
     - name: Set up Python 3.7
-      uses: actions/setup-python@v2
+      uses: actions/setup-python@v4
       with:
       with:
-        python-version: 3.7
+        python-version: '3.7'
     - name: Build Python 3.7
     - name: Build Python 3.7
       shell: bash
       shell: bash
       run: |
       run: |
@@ -397,6 +460,7 @@ jobs:
       run: |
       run: |
         python -m pip install pytest
         python -m pip install pytest
         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
       run: |
       run: |
         python makepanda/makepackage.py --verbose --lzma
         python makepanda/makepackage.py --verbose --lzma

+ 3 - 11
BACKERS.md

@@ -2,20 +2,12 @@
 
 
 This is a list of all the people who are contributing financially to Panda3D.  If you'd like to join them, visit [our campaign on OpenCollective](https://opencollective.com/panda3d)!
 This is a list of all the people who are contributing financially to Panda3D.  If you'd like to join them, visit [our campaign on OpenCollective](https://opencollective.com/panda3d)!
 
 
-## Gold Sponsors
-
-![Gold Sponsors](https://opencollective.com/panda3d/tiers/gold-sponsor.svg?avatarHeight=48&width=600)
-
-* [tcdude](https://opencollective.com/tizilogic)
-
 ## Bronze Sponsors
 ## Bronze Sponsors
 
 
 [<img src="https://www.panda3d.org/wp-content/uploads/2021/02/changecrab_logo.png" alt="ChangeCrab" height="48">](https://changecrab.com/) ![Bronze Sponsors](https://opencollective.com/panda3d/tiers/bronze-sponsor.svg?avatarHeight=48&width=600)
 [<img src="https://www.panda3d.org/wp-content/uploads/2021/02/changecrab_logo.png" alt="ChangeCrab" height="48">](https://changecrab.com/) ![Bronze Sponsors](https://opencollective.com/panda3d/tiers/bronze-sponsor.svg?avatarHeight=48&width=600)
 
 
-* [Mitchell Stokes](https://opencollective.com/mitchell-stokes)
 * [Daniel Stokes](https://opencollective.com/daniel-stokes)
 * [Daniel Stokes](https://opencollective.com/daniel-stokes)
 * [David Rose](https://opencollective.com/david-rose)
 * [David Rose](https://opencollective.com/david-rose)
-* [ChangeCrab](https://changecrab.com)
 
 
 ## Benefactors
 ## Benefactors
 
 
@@ -24,17 +16,17 @@ This is a list of all the people who are contributing financially to Panda3D.  I
 * Sam Edwards
 * Sam Edwards
 * Max Voss
 * Max Voss
 * Hawkheart
 * Hawkheart
-* Dan Mlodecki
+* Veronica
 
 
 ## Enthusiasts
 ## Enthusiasts
 
 
 ![Enthusiasts](https://opencollective.com/panda3d/tiers/enthusiast.svg?avatarHeight=48&width=600)
 ![Enthusiasts](https://opencollective.com/panda3d/tiers/enthusiast.svg?avatarHeight=48&width=600)
 
 
 * Eric Thomson
 * Eric Thomson
-* Kyle Roach
 * Brian Lach
 * Brian Lach
-* C0MPU73R
 * Maxwell Dreytser
 * Maxwell Dreytser
+* SureBet
+* Gyedo Jeon
 
 
 ## Backers
 ## Backers
 
 

+ 5 - 5
README.md

@@ -64,8 +64,8 @@ depending on whether you are on a 32-bit or 64-bit system, or you can
 [click here](https://github.com/rdb/panda3d-thirdparty) for instructions on
 [click here](https://github.com/rdb/panda3d-thirdparty) for instructions on
 building them from source.
 building them from source.
 
 
-- https://www.panda3d.org/download/panda3d-1.10.12/panda3d-1.10.12-tools-win64.zip
-- https://www.panda3d.org/download/panda3d-1.10.12/panda3d-1.10.12-tools-win32.zip
+- https://www.panda3d.org/download/panda3d-1.10.13/panda3d-1.10.13-tools-win64.zip
+- https://www.panda3d.org/download/panda3d-1.10.13/panda3d-1.10.13-tools-win32.zip
 
 
 After acquiring these dependencies, you can build Panda3D from the command
 After acquiring these dependencies, you can build Panda3D from the command
 prompt using the following command.  Change the `--msvc-version` option based
 prompt using the following command.  Change the `--msvc-version` option based
@@ -80,10 +80,10 @@ makepanda\makepanda.bat --everything --installer --msvc-version=14.3 --windows-s
 When the build succeeds, it will produce an .exe file that you can use to
 When the build succeeds, it will produce an .exe file that you can use to
 install Panda3D on your system.
 install Panda3D on your system.
 
 
-Note: you may choose to remove --no-eigen and build with Eigen support in
+**Note:** you may choose to remove `--no-eigen` and build with Eigen support in
 order to improve runtime performance.  However, this will cause the build to
 order to improve runtime performance.  However, this will cause the build to
 take hours to complete, as Eigen is a heavily template-based library, and the
 take hours to complete, as Eigen is a heavily template-based library, and the
-the MSVC compiler does not perform well under these circumstances.
+MSVC compiler does not perform well under those circumstances.
 
 
 Linux
 Linux
 -----
 -----
@@ -136,7 +136,7 @@ macOS
 -----
 -----
 
 
 On macOS, you will need to download a set of precompiled thirdparty packages in order to
 On macOS, you will need to download a set of precompiled thirdparty packages in order to
-compile Panda3D, which can be acquired from [here](https://www.panda3d.org/download/panda3d-1.10.12/panda3d-1.10.12-tools-mac.tar.gz).
+compile Panda3D, which can be acquired from [here](https://www.panda3d.org/download/panda3d-1.10.13/panda3d-1.10.13-tools-mac.tar.gz).
 
 
 After placing the thirdparty directory inside the panda3d source directory,
 After placing the thirdparty directory inside the panda3d source directory,
 you may build Panda3D using a command like the following:
 you may build Panda3D using a command like the following:

+ 10 - 8
contrib/src/rplight/pssmCameraRig.cxx

@@ -296,9 +296,10 @@ void PSSMCameraRig::compute_pssm_splits(const LMatrix4& transform, float max_dis
 
 
     // Reset the film size, offset and far-plane
     // Reset the film size, offset and far-plane
     Camera* cam = DCAST(Camera, _cam_nodes[i].node());
     Camera* cam = DCAST(Camera, _cam_nodes[i].node());
-    cam->get_lens()->set_film_size(1, 1);
-    cam->get_lens()->set_film_offset(0, 0);
-    cam->get_lens()->set_near_far(1, 100);
+    Lens *lens = cam->get_lens();
+    lens->set_film_size(1, 1);
+    lens->set_film_offset(0, 0);
+    lens->set_near_far(1, 100);
 
 
     // Find a good initial position
     // Find a good initial position
     _cam_nodes[i].set_pos(cam_start);
     _cam_nodes[i].set_pos(cam_start);
@@ -320,16 +321,16 @@ void PSSMCameraRig::compute_pssm_splits(const LMatrix4& transform, float max_dis
       if (_max_film_sizes[i].get_x() < film_size.get_x()) _max_film_sizes[i].set_x(film_size.get_x());
       if (_max_film_sizes[i].get_x() < film_size.get_x()) _max_film_sizes[i].set_x(film_size.get_x());
       if (_max_film_sizes[i].get_y() < film_size.get_y()) _max_film_sizes[i].set_y(film_size.get_y());
       if (_max_film_sizes[i].get_y() < film_size.get_y()) _max_film_sizes[i].set_y(film_size.get_y());
 
 
-      cam->get_lens()->set_film_size(_max_film_sizes[i] * filmsize_bias);
+      lens->set_film_size(_max_film_sizes[i] * filmsize_bias);
     } else {
     } else {
       // If we don't use a fixed film size, we can just set the film size
       // If we don't use a fixed film size, we can just set the film size
       // on the lens.
       // on the lens.
-      cam->get_lens()->set_film_size(film_size * filmsize_bias);
+      lens->set_film_size(film_size * filmsize_bias);
     }
     }
 
 
     // Compute new film offset
     // Compute new film offset
-    cam->get_lens()->set_film_offset(film_offset);
-    cam->get_lens()->set_near_far(10, best_max_extent.get_z());
+    lens->set_film_offset(film_offset);
+    lens->set_near_far(10, best_max_extent.get_z());
     _camera_nearfar[i] = LVecBase2(10, best_max_extent.get_z());
     _camera_nearfar[i] = LVecBase2(10, best_max_extent.get_z());
 
 
     // Compute the camera MVP
     // Compute the camera MVP
@@ -399,7 +400,8 @@ void PSSMCameraRig::update(NodePath cam_node, const LVecBase3 &light_vector) {
   }
   }
 
 
   // Do the actual PSSM
   // Do the actual PSSM
-  compute_pssm_splits( transform, _pssm_distance / lens->get_far(), light_vector );
+  double far_recip = std::max(1.0 / (double)lens->get_far(), (double)lens_far_limit);
+  compute_pssm_splits( transform, _pssm_distance * far_recip, light_vector );
 
 
   _update_collector.stop();
   _update_collector.stop();
 }
 }

+ 2 - 2
direct/src/dcparser/dcClass.cxx

@@ -24,8 +24,8 @@ using std::string;
 
 
 #ifdef WITHIN_PANDA
 #ifdef WITHIN_PANDA
 #ifndef CPPPARSER
 #ifndef CPPPARSER
-PStatCollector DCClass::_update_pcollector("App:Show code:readerPollTask:Update");
-PStatCollector DCClass::_generate_pcollector("App:Show code:readerPollTask:Generate");
+PStatCollector DCClass::_update_pcollector("App:Tasks:readerPollTask:Update");
+PStatCollector DCClass::_generate_pcollector("App:Tasks:readerPollTask:Generate");
 #endif  // CPPPARSER
 #endif  // CPPPARSER
 
 
 ConfigVariableBool dc_multiple_inheritance
 ConfigVariableBool dc_multiple_inheritance

+ 17 - 8
direct/src/dist/FreezeTool.py

@@ -787,7 +787,7 @@ class Freezer:
             return 'ModuleDef(%s)' % (', '.join(args))
             return 'ModuleDef(%s)' % (', '.join(args))
 
 
     def __init__(self, previous = None, debugLevel = 0,
     def __init__(self, previous = None, debugLevel = 0,
-                 platform = None, path=None, hiddenImports=None):
+                 platform = None, path=None, hiddenImports=None, optimize=None):
         # Normally, we are freezing for our own platform.  Change this
         # Normally, we are freezing for our own platform.  Change this
         # if untrue.
         # if untrue.
         self.platform = platform or PandaSystem.getPlatform()
         self.platform = platform or PandaSystem.getPlatform()
@@ -917,7 +917,13 @@ class Freezer:
                     ('.so', 'rb', 3),
                     ('.so', 'rb', 3),
                 ]
                 ]
 
 
-        self.mf = PandaModuleFinder(excludes=['doctest'], suffixes=suffixes, path=path)
+        if optimize is None or optimize < 0:
+            self.optimize = sys.flags.optimize
+        else:
+            self.optimize = optimize
+
+        self.mf = PandaModuleFinder(excludes=['doctest'], suffixes=suffixes,
+                                    path=path, optimize=self.optimize)
 
 
     def excludeFrom(self, freezer):
     def excludeFrom(self, freezer):
         """ Excludes all modules that have already been processed by
         """ Excludes all modules that have already been processed by
@@ -1413,7 +1419,7 @@ class Freezer:
                 else:
                 else:
                     filename += '.pyo'
                     filename += '.pyo'
                 if multifile.findSubfile(filename) < 0:
                 if multifile.findSubfile(filename) < 0:
-                    code = compile('', moduleName, 'exec', optimize=2)
+                    code = compile('', moduleName, 'exec', optimize=self.optimize)
                     self.__addPyc(multifile, filename, code, compressionLevel)
                     self.__addPyc(multifile, filename, code, compressionLevel)
 
 
             moduleDirs[str] = True
             moduleDirs[str] = True
@@ -1493,7 +1499,7 @@ class Freezer:
                 source = open(sourceFilename.toOsSpecific(), 'r').read()
                 source = open(sourceFilename.toOsSpecific(), 'r').read()
                 if source and source[-1] != '\n':
                 if source and source[-1] != '\n':
                     source = source + '\n'
                     source = source + '\n'
-                code = compile(source, str(sourceFilename), 'exec', optimize=2)
+                code = compile(source, str(sourceFilename), 'exec', optimize=self.optimize)
 
 
         self.__addPyc(multifile, filename, code, compressionLevel)
         self.__addPyc(multifile, filename, code, compressionLevel)
 
 
@@ -1572,7 +1578,7 @@ class Freezer:
             # trouble importing it as a builtin module.  Synthesize a frozen
             # trouble importing it as a builtin module.  Synthesize a frozen
             # module that loads it as builtin.
             # module that loads it as builtin.
             if '.' in moduleName and self.linkExtensionModules:
             if '.' in moduleName and self.linkExtensionModules:
-                code = compile('import sys;del sys.modules["%s"];import imp;imp.init_builtin("%s")' % (moduleName, moduleName), moduleName, 'exec', optimize=2)
+                code = compile('import sys;del sys.modules["%s"];import imp;imp.init_builtin("%s")' % (moduleName, moduleName), moduleName, 'exec', optimize=self.optimize)
                 code = marshal.dumps(code)
                 code = marshal.dumps(code)
                 mangledName = self.mangleName(moduleName)
                 mangledName = self.mangleName(moduleName)
                 moduleDefs.append(self.makeModuleDef(mangledName, code))
                 moduleDefs.append(self.makeModuleDef(mangledName, code))
@@ -1845,7 +1851,7 @@ class Freezer:
                     code = 'import sys;del sys.modules["%s"];import sys,os,imp;imp.load_dynamic("%s",os.path.join(sys.path[0], "%s%s"))' % (moduleName, moduleName, moduleName, modext)
                     code = 'import sys;del sys.modules["%s"];import sys,os,imp;imp.load_dynamic("%s",os.path.join(sys.path[0], "%s%s"))' % (moduleName, moduleName, moduleName, modext)
                 else:
                 else:
                     code = 'import sys;del sys.modules["%s"];import sys,os,imp;imp.load_dynamic("%s",os.path.join(os.path.dirname(sys.executable), "%s%s"))' % (moduleName, moduleName, moduleName, modext)
                     code = 'import sys;del sys.modules["%s"];import sys,os,imp;imp.load_dynamic("%s",os.path.join(os.path.dirname(sys.executable), "%s%s"))' % (moduleName, moduleName, moduleName, modext)
-                code = compile(code, moduleName, 'exec', optimize=2)
+                code = compile(code, moduleName, 'exec', optimize=self.optimize)
                 code = marshal.dumps(code)
                 code = marshal.dumps(code)
                 moduleList.append((moduleName, len(pool), len(code)))
                 moduleList.append((moduleName, len(pool), len(code)))
                 pool += code
                 pool += code
@@ -1946,6 +1952,8 @@ class Freezer:
                 flags |= 1
                 flags |= 1
             if log_filename_strftime:
             if log_filename_strftime:
                 flags |= 2
                 flags |= 2
+            if self.optimize < 2:
+                flags |= 4 # keep_docstrings
 
 
             # Compose the header we will be writing to the stub, to tell it
             # Compose the header we will be writing to the stub, to tell it
             # where to find the module data blob, as well as other variables.
             # where to find the module data blob, as well as other variables.
@@ -2377,6 +2385,7 @@ class PandaModuleFinder(modulefinder.ModuleFinder):
         """
         """
 
 
         self.suffixes = kw.pop('suffixes', imp.get_suffixes())
         self.suffixes = kw.pop('suffixes', imp.get_suffixes())
+        self.optimize = kw.pop('optimize', -1)
 
 
         modulefinder.ModuleFinder.__init__(self, *args, **kw)
         modulefinder.ModuleFinder.__init__(self, *args, **kw)
 
 
@@ -2530,7 +2539,7 @@ class PandaModuleFinder(modulefinder.ModuleFinder):
 
 
         if type is _PKG_NAMESPACE_DIRECTORY:
         if type is _PKG_NAMESPACE_DIRECTORY:
             m = self.add_module(fqname)
             m = self.add_module(fqname)
-            m.__code__ = compile('', '', 'exec', optimize=2)
+            m.__code__ = compile('', '', 'exec', optimize=self.optimize)
             m.__path__ = pathname
             m.__path__ = pathname
             return m
             return m
 
 
@@ -2542,7 +2551,7 @@ class PandaModuleFinder(modulefinder.ModuleFinder):
                 code = fp.read()
                 code = fp.read()
 
 
             code += b'\n' if isinstance(code, bytes) else '\n'
             code += b'\n' if isinstance(code, bytes) else '\n'
-            co = compile(code, pathname, 'exec', optimize=2)
+            co = compile(code, pathname, 'exec', optimize=self.optimize)
         elif type == imp.PY_COMPILED:
         elif type == imp.PY_COMPILED:
             if sys.version_info >= (3, 7):
             if sys.version_info >= (3, 7):
                 try:
                 try:

+ 66 - 19
direct/src/dist/commands.py

@@ -49,24 +49,39 @@ def _parse_dict(input):
     return d
     return d
 
 
 
 
+def _register_python_loaders():
+    # We need this method so that we don't depend on direct.showbase.Loader.
+    if getattr(_register_python_loaders, 'done', None):
+        return
 
 
-def egg2bam(_build_cmd, srcpath, dstpath):
+    _register_python_loaders.done = True
+
+    registry = p3d.LoaderFileTypeRegistry.getGlobalPtr()
+
+    for entry_point in pkg_resources.iter_entry_points('panda3d.loaders'):
+        registry.register_deferred_type(entry_point)
+
+
+def _model_to_bam(_build_cmd, srcpath, dstpath):
     if dstpath.endswith('.gz') or dstpath.endswith('.pz'):
     if dstpath.endswith('.gz') or dstpath.endswith('.pz'):
         dstpath = dstpath[:-3]
         dstpath = dstpath[:-3]
     dstpath = dstpath + '.bam'
     dstpath = dstpath + '.bam'
-    try:
-        subprocess.check_call([
-            'egg2bam',
-            '-o', dstpath,
-            '-pd', os.path.dirname(os.path.abspath(srcpath)),
-            '-ps', 'rel',
-            srcpath
-        ])
-    except FileNotFoundError:
-        raise RuntimeError('egg2bam failed: egg2bam was not found in the PATH')
-    except (subprocess.CalledProcessError, OSError) as err:
-        raise RuntimeError('egg2bam failed: {}'.format(err))
-    return dstpath
+
+    src_fn = p3d.Filename.from_os_specific(srcpath)
+    dst_fn = p3d.Filename.from_os_specific(dstpath)
+
+    _register_python_loaders()
+
+    loader = p3d.Loader.get_global_ptr()
+    options = p3d.LoaderOptions(p3d.LoaderOptions.LF_report_errors |
+                                p3d.LoaderOptions.LF_no_ram_cache)
+    node = loader.load_sync(src_fn, options)
+    if not node:
+        raise IOError('Failed to load model: %s' % (srcpath))
+
+    if not p3d.NodePath(node).write_bam_file(dst_fn):
+        raise IOError('Failed to write .bam file: %s' % (dstpath))
+
 
 
 macosx_binary_magics = (
 macosx_binary_magics = (
     b'\xFE\xED\xFA\xCE', b'\xCE\xFA\xED\xFE',
     b'\xFE\xED\xFA\xCE', b'\xCE\xFA\xED\xFE',
@@ -243,7 +258,6 @@ class build_apps(setuptools.Command):
         ('platforms=', 'p', 'a list of platforms to build for'),
         ('platforms=', 'p', 'a list of platforms to build for'),
     ]
     ]
     default_file_handlers = {
     default_file_handlers = {
-        '.egg': egg2bam,
     }
     }
 
 
     def initialize_options(self):
     def initialize_options(self):
@@ -277,13 +291,16 @@ class build_apps(setuptools.Command):
         self.log_filename = None
         self.log_filename = None
         self.log_filename_strftime = True
         self.log_filename_strftime = True
         self.log_append = False
         self.log_append = False
+        self.prefer_discrete_gpu = False
         self.requirements_path = os.path.join(os.getcwd(), 'requirements.txt')
         self.requirements_path = os.path.join(os.getcwd(), 'requirements.txt')
+        self.strip_docstrings = True
         self.use_optimized_wheels = True
         self.use_optimized_wheels = True
         self.optimized_wheel_index = ''
         self.optimized_wheel_index = ''
         self.pypi_extra_indexes = [
         self.pypi_extra_indexes = [
             'https://archive.panda3d.org/thirdparty',
             'https://archive.panda3d.org/thirdparty',
         ]
         ]
         self.file_handlers = {}
         self.file_handlers = {}
+        self.bam_model_extensions = ['.egg', '.gltf', '.glb']
         self.exclude_dependencies = [
         self.exclude_dependencies = [
             # Windows
             # Windows
             'kernel32.dll', 'user32.dll', 'wsock32.dll', 'ws2_32.dll',
             'kernel32.dll', 'user32.dll', 'wsock32.dll', 'ws2_32.dll',
@@ -444,6 +461,15 @@ class build_apps(setuptools.Command):
         for glob in self.exclude_dependencies:
         for glob in self.exclude_dependencies:
             glob.case_sensitive = False
             glob.case_sensitive = False
 
 
+        # bam_model_extensions registers a 2bam handler for each given extension.
+        # They can override a default handler, but not a custom handler.
+        if self.bam_model_extensions:
+            for ext in self.bam_model_extensions:
+                ext = '.' + ext.lstrip('.')
+                assert ext not in self.file_handlers, \
+                    'Extension {} occurs in both file_handlers and bam_model_extensions!'.format(ext)
+                self.file_handlers[ext] = _model_to_bam
+
         tmp = self.default_file_handlers.copy()
         tmp = self.default_file_handlers.copy()
         tmp.update(self.file_handlers)
         tmp.update(self.file_handlers)
         self.file_handlers = tmp
         self.file_handlers = tmp
@@ -625,11 +651,16 @@ class build_apps(setuptools.Command):
             self.icon_objects.get('*', None),
             self.icon_objects.get('*', None),
         )
         )
 
 
-        if icon is not None:
+        if icon is not None or self.prefer_discrete_gpu:
             pef = pefile.PEFile()
             pef = pefile.PEFile()
             pef.open(runtime, 'r+')
             pef.open(runtime, 'r+')
-            pef.add_icon(icon)
-            pef.add_resource_section()
+            if icon is not None:
+                pef.add_icon(icon)
+                pef.add_resource_section()
+            if self.prefer_discrete_gpu:
+                if not pef.rename_export("SymbolPlaceholder___________________", "AmdPowerXpressRequestHighPerformance") or \
+                   not pef.rename_export("SymbolPlaceholder__", "NvOptimusEnablement"):
+                    self.warn("Failed to apply prefer_discrete_gpu, newer target Panda3D version may be required")
             pef.write_changes()
             pef.write_changes()
             pef.close()
             pef.close()
 
 
@@ -944,7 +975,8 @@ class build_apps(setuptools.Command):
             freezer = FreezeTool.Freezer(
             freezer = FreezeTool.Freezer(
                 platform=platform,
                 platform=platform,
                 path=path,
                 path=path,
-                hiddenImports=self.hidden_imports
+                hiddenImports=self.hidden_imports,
+                optimize=2 if self.strip_docstrings else 1
             )
             )
             freezer.addModule('__main__', filename=mainscript)
             freezer.addModule('__main__', filename=mainscript)
             if platform.startswith('android'):
             if platform.startswith('android'):
@@ -1617,6 +1649,10 @@ class bdist_apps(setuptools.Command):
         'manylinux_2_24_ppc64': ['gztar'],
         'manylinux_2_24_ppc64': ['gztar'],
         'manylinux_2_24_ppc64le': ['gztar'],
         'manylinux_2_24_ppc64le': ['gztar'],
         'manylinux_2_24_s390x': ['gztar'],
         'manylinux_2_24_s390x': ['gztar'],
+        'manylinux_2_28_x86_64': ['gztar'],
+        'manylinux_2_28_aarch64': ['gztar'],
+        'manylinux_2_28_ppc64le': ['gztar'],
+        'manylinux_2_28_s390x': ['gztar'],
         'android': ['aab'],
         'android': ['aab'],
         # Everything else defaults to ['zip']
         # Everything else defaults to ['zip']
     }
     }
@@ -1710,3 +1746,14 @@ class bdist_apps(setuptools.Command):
                     continue
                     continue
 
 
                 self.installer_functions[installer](self, basename, build_dir)
                 self.installer_functions[installer](self, basename, build_dir)
+
+
+def finalize_distribution_options(dist):
+    """Entry point for compatibility with setuptools>=61, see #1394."""
+
+    options = dist.get_option_dict('build_apps')
+    if options.get('gui_apps') or options.get('console_apps'):
+        # Make sure this is set to avoid auto-discovery taking place.
+        if getattr(dist.metadata, 'py_modules', None) is None and \
+           getattr(dist.metadata, 'packages', None) is None:
+            dist.py_modules = []

+ 30 - 0
direct/src/dist/pefile.py

@@ -600,6 +600,36 @@ class PEFile(object):
         if self.res_rva.addr and self.res_rva.size:
         if self.res_rva.addr and self.res_rva.size:
             self.resources.unpack_from(self.vmem, self.res_rva.addr)
             self.resources.unpack_from(self.vmem, self.res_rva.addr)
 
 
+    def _mark_address_modified(self, rva):
+        for section in self.sections:
+            if rva >= section.vaddr and rva - section.vaddr <= section.size:
+                section.modified = True
+
+    def rename_export(self, old_name, new_name):
+        """ Renames a symbol in the export table. """
+
+        assert len(new_name) <= len(old_name)
+
+        new_name = new_name.ljust(len(old_name) + 1, '\0').encode('ascii')
+
+        start = self.exp_rva.addr
+        expdir = expdirtab(*unpack('<IIHHIIIIIII', self.vmem[start:start+40]))
+        if expdir.nnames == 0 or expdir.ordinals == 0 or expdir.names == 0:
+            return False
+
+        nptr = expdir.names
+        for i in range(expdir.nnames):
+            name_rva, = unpack('<I', self.vmem[nptr:nptr+4])
+            if name_rva != 0:
+                name = _unpack_zstring(self.vmem, name_rva)
+                if name == old_name:
+                    self.vmem[name_rva:name_rva+len(new_name)] = new_name
+                    self._mark_address_modified(name_rva)
+                    return True
+            nptr += 4
+
+        return False
+
     def get_export_address(self, symbol_name):
     def get_export_address(self, symbol_name):
         """ Finds the virtual address for a named export symbol. """
         """ Finds the virtual address for a named export symbol. """
 
 

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

@@ -120,7 +120,7 @@ class ConnectionRepository(
 
 
         self._serverAddress = ''
         self._serverAddress = ''
 
 
-        if self.config.GetBool('gc-save-all', 1):
+        if self.config.GetBool('gc-save-all', 0):
             # set gc to preserve every object involved in a cycle, even ones that
             # set gc to preserve every object involved in a cycle, even ones that
             # would normally be freed automatically during garbage collect
             # would normally be freed automatically during garbage collect
             # allows us to find and fix these cycles, reducing or eliminating the
             # allows us to find and fix these cycles, reducing or eliminating the

+ 3 - 2
direct/src/distributed/cConnectionRepository.cxx

@@ -35,7 +35,7 @@ using std::string;
 const string CConnectionRepository::_overflow_event_name = "CRDatagramOverflow";
 const string CConnectionRepository::_overflow_event_name = "CRDatagramOverflow";
 
 
 #ifndef CPPPARSER
 #ifndef CPPPARSER
-PStatCollector CConnectionRepository::_update_pcollector("App:Show code:readerPollTask:Update");
+PStatCollector CConnectionRepository::_update_pcollector("App:Tasks:readerPollTask:Update");
 #endif  // CPPPARSER
 #endif  // CPPPARSER
 
 
 /**
 /**
@@ -580,7 +580,8 @@ flush() {
 
 
   #ifdef HAVE_OPENSSL
   #ifdef HAVE_OPENSSL
   if (_http_conn) {
   if (_http_conn) {
-    return _http_conn->flush();
+    _http_conn->flush();
+    return !_http_conn->is_closed();
   }
   }
   #endif  // HAVE_OPENSSL
   #endif  // HAVE_OPENSSL
 
 

+ 7 - 0
direct/src/filter/CommonFilters.py

@@ -466,6 +466,13 @@ class CommonFilters:
             return task.cont
             return task.cont
 
 
     def setMSAA(self, samples):
     def setMSAA(self, samples):
+        """Enables multisample anti-aliasing on the render-to-texture buffer.
+        If you enable this, it is recommended to leave any multisample request
+        on the main framebuffer OFF (ie. don't set framebuffer-multisample true
+        in Config.prc), since it would be a waste of resources otherwise.
+
+        .. versionadded:: 1.10.13
+        """
         fullrebuild = "MSAA" not in self.configuration or self.configuration["MSAA"].samples != samples
         fullrebuild = "MSAA" not in self.configuration or self.configuration["MSAA"].samples != samples
         newconfig = FilterConfig()
         newconfig = FilterConfig()
         newconfig.samples = samples
         newconfig.samples = samples

+ 1 - 1
direct/src/interval/Interval.py

@@ -41,7 +41,7 @@ class Interval(DirectObject):
         self.pstats = None
         self.pstats = None
         if __debug__ and TaskManager.taskTimerVerbose:
         if __debug__ and TaskManager.taskTimerVerbose:
             self.pname = name.split('-', 1)[0]
             self.pname = name.split('-', 1)[0]
-            self.pstats = PStatCollector("App:Show code:ivalLoop:%s" % (self.pname))
+            self.pstats = PStatCollector("App:Tasks:ivalLoop:%s" % (self.pname))
 
 
         # Set true if the interval should be invoked if it was
         # Set true if the interval should be invoked if it was
         # completely skipped over during initialize or finalize, false
         # completely skipped over during initialize or finalize, false

+ 1 - 1
direct/src/interval/MetaInterval.py

@@ -106,7 +106,7 @@ class MetaInterval(CMetaInterval):
         self.pstats = None
         self.pstats = None
         if __debug__ and TaskManager.taskTimerVerbose:
         if __debug__ and TaskManager.taskTimerVerbose:
             self.pname = name.split('-', 1)[0]
             self.pname = name.split('-', 1)[0]
-            self.pstats = PStatCollector("App:Show code:ivalLoop:%s" % (self.pname))
+            self.pstats = PStatCollector("App:Tasks:ivalLoop:%s" % (self.pname))
 
 
         self.pythonIvals = []
         self.pythonIvals = []
 
 

+ 1 - 1
direct/src/interval/cInterval.cxx

@@ -22,7 +22,7 @@
 using std::ostream;
 using std::ostream;
 using std::string;
 using std::string;
 
 
-PStatCollector CInterval::_root_pcollector("App:Show code:ivalLoop");
+PStatCollector CInterval::_root_pcollector("App:Tasks:ivalLoop");
 TypeHandle CInterval::_type_handle;
 TypeHandle CInterval::_type_handle;
 
 
 static inline string
 static inline string

+ 170 - 72
direct/src/motiontrail/MotionTrail.py

@@ -47,6 +47,30 @@ class MotionTrailFrame:
 
 
 
 
 class MotionTrail(NodePath, DirectObject):
 class MotionTrail(NodePath, DirectObject):
+    """Generates smooth geometry-based motion trails behind a moving object.
+
+    To use this class, first define the shape of the cross-section polygon that
+    is to be extruded along the motion trail by calling `add_vertex()` and
+    `set_vertex_color()`.  When this is done, call `update_vertices()`.
+
+    To generate the motion trail, either call `register_motion_trail()`
+    to have Panda update it automatically, or periodically call the method
+    `update_motion_trail()` with the current time and the new transform.
+
+    The duration of the sample history is specified by `time_window`.  A larger
+    time window creates longer motion trails (given constant speed).  Samples
+    that are no longer within the time window are automatically discarded.
+
+    The `use_nurbs` option can be used to create smooth interpolated curves
+    from the samples.  This option is useful for animations that lack sampling
+    to begin with, animations that move very quickly, or low frame rates, or if
+    `sampling_time` is used to artificially slow down the update frequency.
+
+    By default, the optimized C++ implementation (provided by `.CMotionTrail`)
+    is used to generate the motion trails.  If for some reason you want to use
+    the pure-Python implementation instead, set `want-python-motion-trails` to
+    true in Config.prc.
+    """
 
 
     notify = directNotify.newCategory("MotionTrail")
     notify = directNotify.newCategory("MotionTrail")
 
 
@@ -58,9 +82,16 @@ class MotionTrail(NodePath, DirectObject):
 
 
     @classmethod
     @classmethod
     def setGlobalEnable(cls, enable):
     def setGlobalEnable(cls, enable):
+        """Set this to False to have the task stop updating all motion trails.
+        This does not prevent updating them manually using the
+        `update_motion_trail()` method.
+        """
         cls.global_enable = enable
         cls.global_enable = enable
 
 
     def __init__(self, name, parent_node_path):
     def __init__(self, name, parent_node_path):
+        """Creates the motion trail with the given name and parents it to the
+        given root node.
+        """
         NodePath.__init__(self, name)
         NodePath.__init__(self, name)
 
 
         # required initialization
         # required initialization
@@ -91,8 +122,17 @@ class MotionTrail(NodePath, DirectObject):
         # default options
         # default options
         self.continuous_motion_trail = True
         self.continuous_motion_trail = True
         self.color_scale = 1.0
         self.color_scale = 1.0
+
+        #: How long the time window is for which the trail is computed.  Can be
+        #: increased to obtain a longer trail, decreased for a shorter trail.
         self.time_window = 1.0
         self.time_window = 1.0
+
+        #: How often the trail updates, in seconds.  The default is 0.0, which
+        #: has the trail updated every frame for the smoothest result.  Higher
+        #: values will generate a choppier trail.  The `use_nurbs` option can
+        #: compensate partially for this choppiness, however.
         self.sampling_time = 0.0
         self.sampling_time = 0.0
+
         self.square_t = True
         self.square_t = True
 
 
 #        self.task_transform = False
 #        self.task_transform = False
@@ -100,7 +140,12 @@ class MotionTrail(NodePath, DirectObject):
 
 
         # node path states
         # node path states
         self.reparentTo(parent_node_path)
         self.reparentTo(parent_node_path)
+
+        #: A `.GeomNode` object containing the generated geometry.  By default
+        #: parented to the MotionTrail itself, but can be reparented elsewhere
+        #: if necessary.
         self.geom_node = GeomNode("motion_trail")
         self.geom_node = GeomNode("motion_trail")
+        self.geom_node.setBoundsType(BoundingVolume.BT_box)
         self.geom_node_path = self.attachNewNode(self.geom_node)
         self.geom_node_path = self.attachNewNode(self.geom_node)
         node_path = self.geom_node_path
         node_path = self.geom_node_path
 
 
@@ -127,10 +172,13 @@ class MotionTrail(NodePath, DirectObject):
 
 
             MotionTrail.task_added = True
             MotionTrail.task_added = True
 
 
-
         self.relative_to_render = False
         self.relative_to_render = False
 
 
+        #: Set this to True to use a NURBS curve to generate a smooth trail,
+        #: even if the underlying animation or movement is janky.
         self.use_nurbs = False
         self.use_nurbs = False
+
+        #: This can be changed to fine-tune the resolution of the NURBS curve.
         self.resolution_distance = 0.5
         self.resolution_distance = 0.5
 
 
         self.cmotion_trail = CMotionTrail()
         self.cmotion_trail = CMotionTrail()
@@ -142,14 +190,13 @@ class MotionTrail(NodePath, DirectObject):
         else:
         else:
             self.use_python_version = False
             self.use_python_version = False
 
 
-        return
-
     def delete(self):
     def delete(self):
+        """Completely cleans up the motion trail object.
+        """
         self.reset_motion_trail()
         self.reset_motion_trail()
         self.reset_motion_trail_geometry()
         self.reset_motion_trail_geometry()
         self.cmotion_trail.resetVertexList()
         self.cmotion_trail.resetVertexList()
         self.removeNode()
         self.removeNode()
-        return
 
 
     def print_matrix(self, matrix):
     def print_matrix(self, matrix):
         separator = ' '
         separator = ' '
@@ -206,11 +253,32 @@ class MotionTrail(NodePath, DirectObject):
 
 
         return Task.cont
         return Task.cont
 
 
-    def add_vertex(self, vertex_id, vertex_function, context):
-        motion_trail_vertex = MotionTrailVertex(vertex_id, vertex_function, context)
+    def add_vertex(self, vertex_id, vertex_function=None, context=None, *,
+                   start_color=(1.0, 1.0, 1.0, 1.0), end_color=(0.0, 0.0, 0.0, 1.0)):
+        """This must be called initially to define the polygon that forms the
+        cross-section of the generated motion trail geometry.  The first
+        argument is a user-defined vertex identifier, the second is a function
+        that will be called with three parameters that should return the
+        position of the vertex as a `.Vec4` object, and the third is an
+        arbitrary context object that is passed as last argument to the
+        provided function.
+
+        After calling this, you must call `update_vertices()` before the
+        changes will fully take effect.
+
+        As of Panda3D 1.10.13, you may alternatively simply pass in a single
+        argument containing the vertex position as a `.Vec4` or `.Point3`.
+        """
+        if vertex_function is None:
+            motion_trail_vertex = MotionTrailVertex(None, None, context)
+            motion_trail_vertex.vertex = Vec4(vertex_id)
+        else:
+            motion_trail_vertex = MotionTrailVertex(vertex_id, vertex_function, context)
+        motion_trail_vertex.start_color = Vec4(start_color)
+        motion_trail_vertex.end_color = Vec4(end_color)
         total_vertices = len(self.vertex_list)
         total_vertices = len(self.vertex_list)
 
 
-        self.vertex_list [total_vertices : total_vertices] = [motion_trail_vertex]
+        self.vertex_list[total_vertices : total_vertices] = [motion_trail_vertex]
 
 
         self.total_vertices = len(self.vertex_list)
         self.total_vertices = len(self.vertex_list)
 
 
@@ -219,15 +287,24 @@ class MotionTrail(NodePath, DirectObject):
         return motion_trail_vertex
         return motion_trail_vertex
 
 
     def set_vertex_color(self, vertex_id, start_color, end_color):
     def set_vertex_color(self, vertex_id, start_color, end_color):
+        """Sets the start and end color of the vertex with the given index,
+        which must have been previously added by `add_vertex()`.  The motion
+        trail will contain a smooth gradient between these colors.  By default,
+        the motion trail fades from white to black (which, with the default
+        additive blending mode, makes it show up as a purely white motion trail
+        that fades out towards the end).
+        """
         if vertex_id >= 0 and vertex_id < self.total_vertices:
         if vertex_id >= 0 and vertex_id < self.total_vertices:
-            motion_trail_vertex = self.vertex_list [vertex_id]
+            motion_trail_vertex = self.vertex_list[vertex_id]
             motion_trail_vertex.start_color = start_color
             motion_trail_vertex.start_color = start_color
             motion_trail_vertex.end_color = end_color
             motion_trail_vertex.end_color = end_color
 
 
         self.modified_vertices = True
         self.modified_vertices = True
-        return
 
 
     def set_texture(self, texture):
     def set_texture(self, texture):
+        """Defines the texture that should be applied to the trail geometry.
+        This also enables generation of UV coordinates.
+        """
         self.texture = texture
         self.texture = texture
         if texture:
         if texture:
             self.geom_node_path.setTexture(texture)
             self.geom_node_path.setTexture(texture)
@@ -237,17 +314,21 @@ class MotionTrail(NodePath, DirectObject):
             self.geom_node_path.clearTexture()
             self.geom_node_path.clearTexture()
 
 
         self.modified_vertices = True
         self.modified_vertices = True
-        return
 
 
     def update_vertices(self):
     def update_vertices(self):
+        """This must be called after the list of vertices defining the
+        cross-section shape of the motion trail has been defined by
+        `add_vertex()` and `set_vertex_color()`.
+        """
         total_vertices = len(self.vertex_list)
         total_vertices = len(self.vertex_list)
 
 
         self.total_vertices = total_vertices
         self.total_vertices = total_vertices
         if total_vertices >= 2:
         if total_vertices >= 2:
             vertex_index = 0
             vertex_index = 0
             while vertex_index < total_vertices:
             while vertex_index < total_vertices:
-                motion_trail_vertex = self.vertex_list [vertex_index]
-                motion_trail_vertex.vertex = motion_trail_vertex.vertex_function(motion_trail_vertex, motion_trail_vertex.vertex_id, motion_trail_vertex.context)
+                motion_trail_vertex = self.vertex_list[vertex_index]
+                if motion_trail_vertex.vertex_function is not None:
+                    motion_trail_vertex.vertex = motion_trail_vertex.vertex_function(motion_trail_vertex, motion_trail_vertex.vertex_id, motion_trail_vertex.context)
                 vertex_index += 1
                 vertex_index += 1
 
 
             # calculate v coordinate
             # calculate v coordinate
@@ -257,7 +338,7 @@ class MotionTrail(NodePath, DirectObject):
             float_total_vertices = 0.0
             float_total_vertices = 0.0
             float_total_vertices = total_vertices - 1.0
             float_total_vertices = total_vertices - 1.0
             while vertex_index < total_vertices:
             while vertex_index < total_vertices:
-                motion_trail_vertex = self.vertex_list [vertex_index]
+                motion_trail_vertex = self.vertex_list[vertex_index]
                 motion_trail_vertex.v = float_vertex_index / float_total_vertices
                 motion_trail_vertex.v = float_vertex_index / float_total_vertices
                 vertex_index += 1
                 vertex_index += 1
                 float_vertex_index += 1.0
                 float_vertex_index += 1.0
@@ -265,7 +346,6 @@ class MotionTrail(NodePath, DirectObject):
 #                print "motion_trail_vertex.v", motion_trail_vertex.v
 #                print "motion_trail_vertex.v", motion_trail_vertex.v
 
 
         self.modified_vertices = True
         self.modified_vertices = True
-        return
 
 
     def transferVertices(self):
     def transferVertices(self):
 
 
@@ -278,22 +358,24 @@ class MotionTrail(NodePath, DirectObject):
             vertex_index = 0
             vertex_index = 0
             total_vertices = len(self.vertex_list)
             total_vertices = len(self.vertex_list)
             while vertex_index < total_vertices:
             while vertex_index < total_vertices:
-                motion_trail_vertex = self.vertex_list [vertex_index]
+                motion_trail_vertex = self.vertex_list[vertex_index]
                 self.cmotion_trail.addVertex(motion_trail_vertex.vertex, motion_trail_vertex.start_color, motion_trail_vertex.end_color, motion_trail_vertex.v)
                 self.cmotion_trail.addVertex(motion_trail_vertex.vertex, motion_trail_vertex.start_color, motion_trail_vertex.end_color, motion_trail_vertex.v)
                 vertex_index += 1
                 vertex_index += 1
 
 
             self.modified_vertices = False
             self.modified_vertices = False
 
 
-        return
-
     def register_motion_trail(self):
     def register_motion_trail(self):
+        """Adds this motion trail to the list of trails that are updated
+        automatically every frame.  Be careful not to call this twice.
+        """
         MotionTrail.motion_trail_list = MotionTrail.motion_trail_list + [self]
         MotionTrail.motion_trail_list = MotionTrail.motion_trail_list + [self]
-        return
 
 
     def unregister_motion_trail(self):
     def unregister_motion_trail(self):
+        """Removes this motion trail from the list of trails that are updated
+        automatically every frame.  If it is not on that list, does nothing.
+        """
         if self in MotionTrail.motion_trail_list:
         if self in MotionTrail.motion_trail_list:
             MotionTrail.motion_trail_list.remove(self)
             MotionTrail.motion_trail_list.remove(self)
-        return
 
 
     def begin_geometry(self):
     def begin_geometry(self):
         self.vertex_index = 0
         self.vertex_index = 0
@@ -314,21 +396,21 @@ class MotionTrail(NodePath, DirectObject):
 
 
     def add_geometry_quad(self, v0, v1, v2, v3, c0, c1, c2, c3, t0, t1, t2, t3):
     def add_geometry_quad(self, v0, v1, v2, v3, c0, c1, c2, c3, t0, t1, t2, t3):
 
 
-        self.vertex_writer.addData3f(v0 [0], v0 [1], v0 [2])
-        self.vertex_writer.addData3f(v1 [0], v1 [1], v1 [2])
-        self.vertex_writer.addData3f(v2 [0], v2 [1], v2 [2])
-        self.vertex_writer.addData3f(v3 [0], v3 [1], v3 [2])
+        self.vertex_writer.addData3(v0[0], v0[1], v0[2])
+        self.vertex_writer.addData3(v1[0], v1[1], v1[2])
+        self.vertex_writer.addData3(v2[0], v2[1], v2[2])
+        self.vertex_writer.addData3(v3[0], v3[1], v3[2])
 
 
-        self.color_writer.addData4f(c0)
-        self.color_writer.addData4f(c1)
-        self.color_writer.addData4f(c2)
-        self.color_writer.addData4f(c3)
+        self.color_writer.addData4(c0)
+        self.color_writer.addData4(c1)
+        self.color_writer.addData4(c2)
+        self.color_writer.addData4(c3)
 
 
         if self.texture is not None:
         if self.texture is not None:
-            self.texture_writer.addData2f(t0)
-            self.texture_writer.addData2f(t1)
-            self.texture_writer.addData2f(t2)
-            self.texture_writer.addData2f(t3)
+            self.texture_writer.addData2(t0)
+            self.texture_writer.addData2(t1)
+            self.texture_writer.addData2(t2)
+            self.texture_writer.addData2(t3)
 
 
         vertex_index = self.vertex_index
         vertex_index = self.vertex_index
 
 
@@ -352,7 +434,10 @@ class MotionTrail(NodePath, DirectObject):
         self.geom_node.addGeom(self.geometry)
         self.geom_node.addGeom(self.geometry)
 
 
     def check_for_update(self, current_time):
     def check_for_update(self, current_time):
-
+        """Returns true if the motion trail is overdue for an update based on
+        the configured `sampling_time` (by default 0.0 to update continuously),
+        and is not currently paused.
+        """
         state = False
         state = False
         if (current_time - self.last_update_time) >= self.sampling_time:
         if (current_time - self.last_update_time) >= self.sampling_time:
             state = True
             state = True
@@ -365,9 +450,12 @@ class MotionTrail(NodePath, DirectObject):
         return state
         return state
 
 
     def update_motion_trail(self, current_time, transform):
     def update_motion_trail(self, current_time, transform):
-
+        """If the trail is overdue for an update based on the given time in
+        seconds, updates it, extracting the new object position from the given
+        transform matrix.
+        """
         if len(self.frame_list) >= 1:
         if len(self.frame_list) >= 1:
-            if transform == self.frame_list [0].transform:
+            if transform == self.frame_list[0].transform:
                 # ignore duplicate transform updates
                 # ignore duplicate transform updates
                 return
                 return
 
 
@@ -378,7 +466,7 @@ class MotionTrail(NodePath, DirectObject):
                 elapsed_time = current_time - self.fade_start_time
                 elapsed_time = current_time - self.fade_start_time
 
 
                 if elapsed_time < 0.0:
                 if elapsed_time < 0.0:
-                    print("elapsed_time < 0: %f" %(elapsed_time))
+                    print("elapsed_time < 0: %f" % (elapsed_time))
                     elapsed_time = 0.0
                     elapsed_time = 0.0
 
 
                 if elapsed_time < self.fade_time:
                 if elapsed_time < self.fade_time:
@@ -397,13 +485,13 @@ class MotionTrail(NodePath, DirectObject):
             last_frame_index = len(self.frame_list) - 1
             last_frame_index = len(self.frame_list) - 1
 
 
             while index <= last_frame_index:
             while index <= last_frame_index:
-                motion_trail_frame = self.frame_list [last_frame_index - index]
+                motion_trail_frame = self.frame_list[last_frame_index - index]
                 if motion_trail_frame.time >= minimum_time:
                 if motion_trail_frame.time >= minimum_time:
                     break
                     break
                 index += 1
                 index += 1
 
 
             if index > 0:
             if index > 0:
-                self.frame_list [last_frame_index - index: last_frame_index + 1] = []
+                self.frame_list[last_frame_index - index: last_frame_index + 1] = []
 
 
             # add new frame to beginning of list
             # add new frame to beginning of list
             motion_trail_frame = MotionTrailFrame(current_time, transform)
             motion_trail_frame = MotionTrailFrame(current_time, transform)
@@ -416,15 +504,14 @@ class MotionTrail(NodePath, DirectObject):
             #
             #
             #index = 0
             #index = 0
             #while index < total_frames:
             #while index < total_frames:
-            #    motion_trail_frame = self.frame_list [index]
+            #    motion_trail_frame = self.frame_list[index]
             #    print("frame time", index, motion_trail_frame.time)
             #    print("frame time", index, motion_trail_frame.time)
             #    index += 1
             #    index += 1
 
 
-            if (total_frames >= 2) and(self.total_vertices >= 2):
-
+            if total_frames >= 2 and self.total_vertices >= 2:
                 self.begin_geometry()
                 self.begin_geometry()
                 total_segments = total_frames - 1
                 total_segments = total_frames - 1
-                last_motion_trail_frame = self.frame_list [total_segments]
+                last_motion_trail_frame = self.frame_list[total_segments]
                 minimum_time = last_motion_trail_frame.time
                 minimum_time = last_motion_trail_frame.time
                 delta_time = current_time - minimum_time
                 delta_time = current_time - minimum_time
 
 
@@ -432,7 +519,7 @@ class MotionTrail(NodePath, DirectObject):
                     inverse_matrix = Mat4(transform)
                     inverse_matrix = Mat4(transform)
                     inverse_matrix.invertInPlace()
                     inverse_matrix.invertInPlace()
 
 
-                if self.use_nurbs and(total_frames >= 5):
+                if self.use_nurbs and total_frames >= 5:
 
 
                     total_distance = 0.0
                     total_distance = 0.0
                     vector = Vec3()
                     vector = Vec3()
@@ -452,10 +539,10 @@ class MotionTrail(NodePath, DirectObject):
                     # add vertices to each NurbsCurveEvaluator
                     # add vertices to each NurbsCurveEvaluator
                     segment_index = 0
                     segment_index = 0
                     while segment_index < total_segments:
                     while segment_index < total_segments:
-                        motion_trail_frame_start = self.frame_list [segment_index]
-                        motion_trail_frame_end = self.frame_list [segment_index + 1]
+                        motion_trail_frame_start = self.frame_list[segment_index]
+                        motion_trail_frame_end = self.frame_list[segment_index + 1]
 
 
-                        vertex_segement_index = 0
+                        vertex_segment_index = 0
 
 
                         if self.calculate_relative_matrix:
                         if self.calculate_relative_matrix:
                             start_transform = Mat4()
                             start_transform = Mat4()
@@ -468,34 +555,34 @@ class MotionTrail(NodePath, DirectObject):
                             start_transform = motion_trail_frame_start.transform
                             start_transform = motion_trail_frame_start.transform
                             end_transform = motion_trail_frame_end.transform
                             end_transform = motion_trail_frame_end.transform
 
 
-                        motion_trail_vertex_start = self.vertex_list [0]
+                        motion_trail_vertex_start = self.vertex_list[0]
 
 
                         v0 = start_transform.xform(motion_trail_vertex_start.vertex)
                         v0 = start_transform.xform(motion_trail_vertex_start.vertex)
                         v2 = end_transform.xform(motion_trail_vertex_start.vertex)
                         v2 = end_transform.xform(motion_trail_vertex_start.vertex)
 
 
-                        nurbs_curve_evaluator = nurbs_curve_evaluator_list [vertex_segement_index]
+                        nurbs_curve_evaluator = nurbs_curve_evaluator_list [vertex_segment_index]
 
 
                         nurbs_curve_evaluator.setVertex(segment_index, v0)
                         nurbs_curve_evaluator.setVertex(segment_index, v0)
 
 
-                        while vertex_segement_index < total_vertex_segments:
+                        while vertex_segment_index < total_vertex_segments:
 
 
-                            motion_trail_vertex_start = self.vertex_list [vertex_segement_index]
-                            motion_trail_vertex_end = self.vertex_list [vertex_segement_index + 1]
+                            motion_trail_vertex_start = self.vertex_list[vertex_segment_index]
+                            motion_trail_vertex_end = self.vertex_list[vertex_segment_index + 1]
 
 
                             v1 = start_transform.xform(motion_trail_vertex_end.vertex)
                             v1 = start_transform.xform(motion_trail_vertex_end.vertex)
                             v3 = end_transform.xform(motion_trail_vertex_end.vertex)
                             v3 = end_transform.xform(motion_trail_vertex_end.vertex)
 
 
-                            nurbs_curve_evaluator = nurbs_curve_evaluator_list [vertex_segement_index + 1]
+                            nurbs_curve_evaluator = nurbs_curve_evaluator_list [vertex_segment_index + 1]
 
 
                             nurbs_curve_evaluator.setVertex(segment_index, v1)
                             nurbs_curve_evaluator.setVertex(segment_index, v1)
 
 
-                            if vertex_segement_index == (total_vertex_segments - 1):
+                            if vertex_segment_index == (total_vertex_segments - 1):
                                 v = v1 - v3
                                 v = v1 - v3
                                 vector.set(v[0], v[1], v[2])
                                 vector.set(v[0], v[1], v[2])
                                 distance = vector.length()
                                 distance = vector.length()
                                 total_distance += distance
                                 total_distance += distance
 
 
-                            vertex_segement_index += 1
+                            vertex_segment_index += 1
 
 
                         segment_index += 1
                         segment_index += 1
 
 
@@ -531,7 +618,7 @@ class MotionTrail(NodePath, DirectObject):
                     curve_segment_index = 0.0
                     curve_segment_index = 0.0
                     while curve_segment_index < total_curve_segments:
                     while curve_segment_index < total_curve_segments:
 
 
-                        vertex_segement_index = 0
+                        vertex_segment_index = 0
 
 
                         st = curve_segment_index / total_curve_segments
                         st = curve_segment_index / total_curve_segments
                         et = (curve_segment_index + 1.0) / total_curve_segments
                         et = (curve_segment_index + 1.0) / total_curve_segments
@@ -545,7 +632,7 @@ class MotionTrail(NodePath, DirectObject):
                             start_t *= start_t
                             start_t *= start_t
                             end_t *= end_t
                             end_t *= end_t
 
 
-                        motion_trail_vertex_start = self.vertex_list [0]
+                        motion_trail_vertex_start = self.vertex_list[0]
 
 
                         vertex_start_color = motion_trail_vertex_start.end_color + (motion_trail_vertex_start.start_color - motion_trail_vertex_start.end_color)
                         vertex_start_color = motion_trail_vertex_start.end_color + (motion_trail_vertex_start.start_color - motion_trail_vertex_start.end_color)
                         color_start_t = color_scale * start_t
                         color_start_t = color_scale * start_t
@@ -556,13 +643,13 @@ class MotionTrail(NodePath, DirectObject):
                         t0 = Vec2(one_minus_x(st), motion_trail_vertex_start.v)
                         t0 = Vec2(one_minus_x(st), motion_trail_vertex_start.v)
                         t2 = Vec2(one_minus_x(et), motion_trail_vertex_start.v)
                         t2 = Vec2(one_minus_x(et), motion_trail_vertex_start.v)
 
 
-                        while vertex_segement_index < total_vertex_segments:
+                        while vertex_segment_index < total_vertex_segments:
 
 
-                            motion_trail_vertex_start = self.vertex_list [vertex_segement_index]
-                            motion_trail_vertex_end = self.vertex_list [vertex_segement_index + 1]
+                            motion_trail_vertex_start = self.vertex_list[vertex_segment_index]
+                            motion_trail_vertex_end = self.vertex_list[vertex_segment_index + 1]
 
 
-                            start_nurbs_curve_result = nurbs_curve_result_list [vertex_segement_index]
-                            end_nurbs_curve_result = nurbs_curve_result_list [vertex_segement_index + 1]
+                            start_nurbs_curve_result = nurbs_curve_result_list [vertex_segment_index]
+                            end_nurbs_curve_result = nurbs_curve_result_list [vertex_segment_index + 1]
 
 
                             start_nurbs_start_t = start_nurbs_curve_result.getStartT()
                             start_nurbs_start_t = start_nurbs_curve_result.getStartT()
                             start_nurbs_end_t = start_nurbs_curve_result.getEndT()
                             start_nurbs_end_t = start_nurbs_curve_result.getEndT()
@@ -597,17 +684,15 @@ class MotionTrail(NodePath, DirectObject):
                             t0 = t1
                             t0 = t1
                             t2 = t3
                             t2 = t3
 
 
-                            vertex_segement_index += 1
+                            vertex_segment_index += 1
 
 
                         curve_segment_index += 1.0
                         curve_segment_index += 1.0
 
 
-
                 else:
                 else:
-
                     segment_index = 0
                     segment_index = 0
                     while segment_index < total_segments:
                     while segment_index < total_segments:
-                        motion_trail_frame_start = self.frame_list [segment_index]
-                        motion_trail_frame_end = self.frame_list [segment_index + 1]
+                        motion_trail_frame_start = self.frame_list[segment_index]
+                        motion_trail_frame_end = self.frame_list[segment_index + 1]
 
 
                         start_t = (motion_trail_frame_start.time - minimum_time) / delta_time
                         start_t = (motion_trail_frame_start.time - minimum_time) / delta_time
                         end_t = (motion_trail_frame_end.time - minimum_time) / delta_time
                         end_t = (motion_trail_frame_end.time - minimum_time) / delta_time
@@ -619,7 +704,7 @@ class MotionTrail(NodePath, DirectObject):
                             start_t *= start_t
                             start_t *= start_t
                             end_t *= end_t
                             end_t *= end_t
 
 
-                        vertex_segement_index = 0
+                        vertex_segment_index = 0
                         total_vertex_segments = self.total_vertices - 1
                         total_vertex_segments = self.total_vertices - 1
 
 
                         if self.calculate_relative_matrix:
                         if self.calculate_relative_matrix:
@@ -631,7 +716,7 @@ class MotionTrail(NodePath, DirectObject):
                             start_transform = motion_trail_frame_start.transform
                             start_transform = motion_trail_frame_start.transform
                             end_transform = motion_trail_frame_end.transform
                             end_transform = motion_trail_frame_end.transform
 
 
-                        motion_trail_vertex_start = self.vertex_list [0]
+                        motion_trail_vertex_start = self.vertex_list[0]
 
 
                         v0 = start_transform.xform(motion_trail_vertex_start.vertex)
                         v0 = start_transform.xform(motion_trail_vertex_start.vertex)
                         v2 = end_transform.xform(motion_trail_vertex_start.vertex)
                         v2 = end_transform.xform(motion_trail_vertex_start.vertex)
@@ -645,10 +730,10 @@ class MotionTrail(NodePath, DirectObject):
                         t0 = Vec2(st, motion_trail_vertex_start.v)
                         t0 = Vec2(st, motion_trail_vertex_start.v)
                         t2 = Vec2(et, motion_trail_vertex_start.v)
                         t2 = Vec2(et, motion_trail_vertex_start.v)
 
 
-                        while vertex_segement_index < total_vertex_segments:
+                        while vertex_segment_index < total_vertex_segments:
 
 
-                            motion_trail_vertex_start = self.vertex_list [vertex_segement_index]
-                            motion_trail_vertex_end = self.vertex_list [vertex_segement_index + 1]
+                            motion_trail_vertex_start = self.vertex_list[vertex_segment_index]
+                            motion_trail_vertex_end = self.vertex_list[vertex_segment_index + 1]
 
 
                             v1 = start_transform.xform(motion_trail_vertex_end.vertex)
                             v1 = start_transform.xform(motion_trail_vertex_end.vertex)
                             v3 = end_transform.xform(motion_trail_vertex_end.vertex)
                             v3 = end_transform.xform(motion_trail_vertex_end.vertex)
@@ -675,24 +760,37 @@ class MotionTrail(NodePath, DirectObject):
                             t0 = t1
                             t0 = t1
                             t2 = t3
                             t2 = t3
 
 
-                            vertex_segement_index += 1
+                            vertex_segment_index += 1
 
 
                         segment_index += 1
                         segment_index += 1
 
 
                 self.end_geometry()
                 self.end_geometry()
 
 
     def enable_motion_trail(self, enable):
     def enable_motion_trail(self, enable):
+        """Sets whether the motion trail is currently enabled.  Every motion
+        trail starts off as being enabled, passing False to this method prevents
+        it from being updated.
+        """
         self.enable = enable
         self.enable = enable
 
 
     def reset_motion_trail(self):
     def reset_motion_trail(self):
+        """Call this to have the motion trail restart from nothing on the next
+        update.
+        """
         self.frame_list = []
         self.frame_list = []
         self.cmotion_trail.reset()
         self.cmotion_trail.reset()
 
 
     def reset_motion_trail_geometry(self):
     def reset_motion_trail_geometry(self):
+        """Destroys the currently generated motion trail geometry immediately.
+        However, it will be fully regenerated on the next call to update, see
+        `reset_motion_trail()` to prevent this.
+        """
         if self.geom_node is not None:
         if self.geom_node is not None:
             self.geom_node.removeAllGeoms()
             self.geom_node.removeAllGeoms()
 
 
     def attach_motion_trail(self):
     def attach_motion_trail(self):
+        """Alias of `reset_motion_trail()`.
+        """
         self.reset_motion_trail()
         self.reset_motion_trail()
 
 
     def begin_motion_trail(self):
     def begin_motion_trail(self):
@@ -733,7 +831,7 @@ class MotionTrail(NodePath, DirectObject):
             frame_index = 0
             frame_index = 0
             total_frames = len(self.frame_list)
             total_frames = len(self.frame_list)
             while frame_index < total_frames:
             while frame_index < total_frames:
-                motion_trail_frame = self.frame_list [frame_index]
+                motion_trail_frame = self.frame_list[frame_index]
                 motion_trail_frame.time += delta_time
                 motion_trail_frame.time += delta_time
                 frame_index += 1
                 frame_index += 1
 
 

File diff suppressed because it is too large
+ 285 - 434
direct/src/motiontrail/cMotionTrail.cxx


+ 37 - 39
direct/src/motiontrail/cMotionTrail.h

@@ -21,13 +21,11 @@
 #include "geomVertexWriter.h"
 #include "geomVertexWriter.h"
 #include "geomTriangles.h"
 #include "geomTriangles.h"
 #include "luse.h"
 #include "luse.h"
-#include "memoryBase.h"
 #include "nurbsCurveEvaluator.h"
 #include "nurbsCurveEvaluator.h"
 #include "plist.h"
 #include "plist.h"
 #include "epvector.h"
 #include "epvector.h"
 
 
-class CMotionTrailVertex : public MemoryBase {
-public:
+struct CMotionTrailVertex : public MemoryBase {
   LPoint4 _vertex;
   LPoint4 _vertex;
   LVecBase4 _start_color;
   LVecBase4 _start_color;
   LVecBase4 _end_color;
   LVecBase4 _end_color;
@@ -36,9 +34,8 @@ public:
   PT(NurbsCurveEvaluator) _nurbs_curve_evaluator;
   PT(NurbsCurveEvaluator) _nurbs_curve_evaluator;
 };
 };
 
 
-class CMotionTrailFrame {
-public:
-  UnalignedLMatrix4 _transform;
+struct CMotionTrailFrame : public MemoryBase {
+  LMatrix4 _transform;
   PN_stdfloat _time;
   PN_stdfloat _time;
 };
 };
 
 
@@ -72,8 +69,8 @@ public:
  */
  */
 class EXPCL_DIRECT_MOTIONTRAIL CMotionTrail : public TypedReferenceCount {
 class EXPCL_DIRECT_MOTIONTRAIL CMotionTrail : public TypedReferenceCount {
 PUBLISHED:
 PUBLISHED:
-  CMotionTrail();
-  ~CMotionTrail();
+  CMotionTrail() = default;
+  ~CMotionTrail() = default;
 
 
   void reset();
   void reset();
   void reset_vertex_list();
   void reset_vertex_list();
@@ -81,64 +78,66 @@ PUBLISHED:
   void enable(bool enable);
   void enable(bool enable);
 
 
   void set_geom_node(GeomNode *geom_node);
   void set_geom_node(GeomNode *geom_node);
-  void add_vertex(LVector4 *vertex, LVector4 *start_color, LVector4 *end_color, PN_stdfloat v);
+  void add_vertex(const LVector4 &vertex, const LVector4 &start_color,
+                  const LVector4 &end_color, PN_stdfloat v);
 
 
-  void set_parameters(PN_stdfloat sampling_time, PN_stdfloat time_window, bool use_texture, bool calculate_relative_matrix, bool use_nurbs, PN_stdfloat resolution_distance);
+  void set_parameters(PN_stdfloat sampling_time, PN_stdfloat time_window,
+                      bool use_texture, bool calculate_relative_matrix,
+                      bool use_nurbs, PN_stdfloat resolution_distance);
 
 
   int check_for_update(PN_stdfloat current_time);
   int check_for_update(PN_stdfloat current_time);
-  void update_motion_trail(PN_stdfloat current_time, LMatrix4 *transform);
+  void update_motion_trail(PN_stdfloat current_time, const LMatrix4 &transform);
 
 
 public:
 public:
+  void begin_geometry(int num_quads);
+  void add_geometry_quad(
+    const LPoint3 &v0, const LPoint3 &v1, const LPoint3 &v2, const LPoint3 &v3,
+    const LVector4 &c0, const LVector4 &c1, const LVector4 &c2, const LVector4 &c3,
+    const LVector2 &t0, const LVector2 &t1, const LVector2 &t2, const LVector2 &t3);
+  void end_geometry(const LPoint3 &min_vertex, const LPoint3 &max_vertex);
 
 
-  void begin_geometry();
-  void add_geometry_quad(LVector3 &v0, LVector3 &v1, LVector3 &v2, LVector3 &v3, LVector4 &c0, LVector4 &c1, LVector4 &c2, LVector4 &c3, LVector2 &t0, LVector2 &t1, LVector2 &t2, LVector2 &t3);
-  void add_geometry_quad(LVector4 &v0, LVector4 &v1, LVector4 &v2, LVector4 &v3, LVector4 &c0, LVector4 &c1, LVector4 &c2, LVector4 &c3, LVector2 &t0, LVector2 &t1, LVector2 &t2, LVector2 &t3);
-  void end_geometry();
-
-  int _active;
-  int _enable;
+  bool _active = true;
+  bool _enable = true;
 
 
-  int _pause;
-  PN_stdfloat _pause_time;
+  bool _pause = false;
+  PN_stdfloat _pause_time = 0.0f;
 
 
-  int _fade;
-  int _fade_end;
-  PN_stdfloat _fade_time;
-  PN_stdfloat _fade_start_time;
-  PN_stdfloat _fade_color_scale;
+  bool _fade = false;
+  bool _fade_end = false;
+  PN_stdfloat _fade_time = 0.0f;
+  PN_stdfloat _fade_start_time = 0.0f;
+  PN_stdfloat _fade_color_scale = 1.0f;
 
 
-  PN_stdfloat _last_update_time;
+  PN_stdfloat _last_update_time = 0.0f;
 
 
   typedef epvector<CMotionTrailVertex> VertexList;
   typedef epvector<CMotionTrailVertex> VertexList;
   VertexList _vertex_list;
   VertexList _vertex_list;
-  typedef plist<CMotionTrailFrame> FrameList;
+  typedef pdeque<CMotionTrailFrame> FrameList;
   FrameList _frame_list;
   FrameList _frame_list;
+  PN_stdfloat _vertex_bounds_radius = 0.0f;
 
 
   // parameters
   // parameters
-  PN_stdfloat _color_scale;
-  PN_stdfloat _sampling_time;
-  PN_stdfloat _time_window;
-  bool _square_t;
-  bool _use_texture;
-  int _calculate_relative_matrix;
+  PN_stdfloat _color_scale = 1.0f;
+  PN_stdfloat _sampling_time = 0.0f;
+  PN_stdfloat _time_window = 1.0f;
+  bool _square_t = true;
+  bool _use_texture = false;
+  bool _calculate_relative_matrix = false;
 
 
   // nurbs parameters
   // nurbs parameters
-  bool _use_nurbs;
-  PN_stdfloat _resolution_distance;
+  bool _use_nurbs = false;
+  PN_stdfloat _resolution_distance = 0.5f;
 
 
   // geom
   // geom
   PT(GeomNode) _geom_node;
   PT(GeomNode) _geom_node;
 
 
   // real-time data
   // real-time data
-  int _vertex_index;
   PT(GeomVertexData) _vertex_data;
   PT(GeomVertexData) _vertex_data;
   GeomVertexWriter _vertex_writer;
   GeomVertexWriter _vertex_writer;
   GeomVertexWriter _color_writer;
   GeomVertexWriter _color_writer;
   GeomVertexWriter _texture_writer;
   GeomVertexWriter _texture_writer;
   PT(GeomTriangles) _triangles;
   PT(GeomTriangles) _triangles;
 
 
-  CMotionTrailVertex *_vertex_array;
-
 public:
 public:
   static TypeHandle get_class_type() {
   static TypeHandle get_class_type() {
     return _type_handle;
     return _type_handle;
@@ -155,7 +154,6 @@ public:
 
 
 private:
 private:
   static TypeHandle _type_handle;
   static TypeHandle _type_handle;
-
 };
 };
 
 
 #endif
 #endif

+ 26 - 6
direct/src/showbase/ContainerLeakDetector.py

@@ -11,12 +11,15 @@ import weakref
 import random
 import random
 import builtins
 import builtins
 
 
-deadEndTypes = (bool, types.BuiltinFunctionType,
-                types.BuiltinMethodType, complex,
-                float, int,
-                type(None), type(NotImplemented),
-                type, types.CodeType, types.FunctionType,
-                bytes, str, tuple)
+deadEndTypes = frozenset((
+    types.BuiltinFunctionType, types.BuiltinMethodType,
+    types.CodeType, types.FunctionType,
+    types.GeneratorType, types.CoroutineType,
+    types.AsyncGeneratorType,
+    bool, complex, float, int, type,
+    bytes, str, list, tuple,
+    type(None), type(NotImplemented)
+))
 
 
 
 
 def _createContainerLeak():
 def _createContainerLeak():
@@ -547,6 +550,23 @@ class FindContainers(Job):
                 # if we hit a dead end, start over from another container
                 # if we hit a dead end, start over from another container
                 curObjRef = None
                 curObjRef = None
 
 
+                # types.CellType was added in Python 3.8
+                if sys.version_info >= (3, 8) and type(curObj) is types.CellType:
+                    child = curObj.cell_contents
+                    hasLength = self._hasLength(child)
+                    notDeadEnd = not self._isDeadEnd(child)
+                    if hasLength or notDeadEnd:
+                        objRef = ObjectRef(Indirection(evalStr='.cell_contents'),
+                                           id(child), parentObjRef)
+                        yield None
+                        if hasLength:
+                            for i in self._addContainerGen(child, objRef):
+                                yield None
+                        if notDeadEnd:
+                            self._addDiscoveredStartRef(child, objRef)
+                            curObjRef = objRef
+                    continue
+
                 if hasattr(curObj, '__dict__'):
                 if hasattr(curObj, '__dict__'):
                     child = curObj.__dict__
                     child = curObj.__dict__
                     hasLength = self._hasLength(child)
                     hasLength = self._hasLength(child)

+ 28 - 14
direct/src/showbase/ContainerReport.py

@@ -3,7 +3,10 @@ from direct.showbase.PythonUtil import Queue, invertDictLossless
 from direct.showbase.PythonUtil import safeRepr
 from direct.showbase.PythonUtil import safeRepr
 from direct.showbase.Job import Job
 from direct.showbase.Job import Job
 from direct.showbase.JobManagerGlobal import jobMgr
 from direct.showbase.JobManagerGlobal import jobMgr
+from direct.showbase.ContainerLeakDetector import deadEndTypes
 import types
 import types
+import sys
+import io
 
 
 
 
 class ContainerReport(Job):
 class ContainerReport(Job):
@@ -89,14 +92,6 @@ class ContainerReport(Job):
             if isinstance(parentObj, (str, bytes)):
             if isinstance(parentObj, (str, bytes)):
                 continue
                 continue
 
 
-            if type(parentObj) in (types.ModuleType, types.InstanceType):
-                child = parentObj.__dict__
-                if self._examine(child):
-                    assert self._queue.back() is child
-                    self._instanceDictIds.add(id(child))
-                    self._id2pathStr[id(child)] = str(self._id2pathStr[id(parentObj)])
-                continue
-
             if isinstance(parentObj, dict):
             if isinstance(parentObj, dict):
                 key = None
                 key = None
                 attr = None
                 attr = None
@@ -126,7 +121,25 @@ class ContainerReport(Job):
                 del attr
                 del attr
                 continue
                 continue
 
 
-            if type(parentObj) is not types.FileType:
+            # types.CellType was added in Python 3.8
+            if sys.version_info >= (3, 8) and type(parentObj) is types.CellType:
+                child = parentObj.cell_contents
+                if self._examine(child):
+                    assert (self._queue.back() is child)
+                    self._instanceDictIds.add(id(child))
+                    self._id2pathStr[id(child)] = str(self._id2pathStr[id(parentObj)]) + '.cell_contents'
+                continue
+
+            if hasattr(parentObj, '__dict__'):
+                # Instance of a class
+                child = parentObj.__dict__
+                if self._examine(child):
+                    assert (self._queue.back() is child)
+                    self._instanceDictIds.add(id(child))
+                    self._id2pathStr[id(child)] = str(self._id2pathStr[id(parentObj)])
+                continue
+
+            if not isinstance(parentObj, io.TextIOWrapper):
                 try:
                 try:
                     itr = iter(parentObj)
                     itr = iter(parentObj)
                 except:
                 except:
@@ -161,7 +174,10 @@ class ContainerReport(Job):
                 childName = None
                 childName = None
                 child = None
                 child = None
                 for childName in childNames:
                 for childName in childNames:
-                    child = getattr(parentObj, childName)
+                    try:
+                        child = getattr(parentObj, childName)
+                    except:
+                        continue
                     if id(child) not in self._visitedIds:
                     if id(child) not in self._visitedIds:
                         self._visitedIds.add(id(child))
                         self._visitedIds.add(id(child))
                         if self._examine(child):
                         if self._examine(child):
@@ -198,9 +214,7 @@ class ContainerReport(Job):
             self._type2id2len[type(obj)][objId] = length
             self._type2id2len[type(obj)][objId] = length
     def _examine(self, obj):
     def _examine(self, obj):
         # return False if it's an object that can't contain or lead to other objects
         # return False if it's an object that can't contain or lead to other objects
-        if type(obj) in (bool, types.BuiltinFunctionType, types.BuiltinMethodType,
-                         complex, float, int, type(None), type(NotImplemented),
-                         type, types.CodeType, types.FunctionType):
+        if type(obj) in deadEndTypes:
             return False
             return False
         # if it's an internal object, ignore it
         # if it's an internal object, ignore it
         if id(obj) in ContainerReport.PrivateIds:
         if id(obj) in ContainerReport.PrivateIds:
@@ -243,7 +257,7 @@ class ContainerReport(Job):
             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 = list(set(self._type2id2len.keys()).difference(set(initialTypes)))
-        otherTypes.sort()
+        otherTypes.sort(key=lambda obj: obj.__name__)
         for type in otherTypes:
         for type in otherTypes:
             for i in self._outputType(type, **kArgs):
             for i in self._outputType(type, **kArgs):
                 yield None
                 yield None

+ 5 - 2
direct/src/showbase/EventManager.py

@@ -137,11 +137,11 @@ class EventManager:
             hyphen = name.find('-')
             hyphen = name.find('-')
             if hyphen >= 0:
             if hyphen >= 0:
                 name = name[0:hyphen]
                 name = name[0:hyphen]
-            pstatCollector = PStatCollector('App:Show code:eventManager:' + name)
+            pstatCollector = PStatCollector('App:Tasks:eventManager:' + name)
             pstatCollector.start()
             pstatCollector.start()
             if self.eventHandler:
             if self.eventHandler:
                 cppPstatCollector = PStatCollector(
                 cppPstatCollector = PStatCollector(
-                    'App:Show code:eventManager:' + name + ':C++')
+                    'App:Tasks:eventManager:' + name + ':C++')
 
 
             messenger.send(eventName, paramList)
             messenger.send(eventName, paramList)
 
 
@@ -180,3 +180,6 @@ class EventManager:
         # since the task removal itself might also fire off an event.
         # since the task removal itself might also fire off an event.
         if self.eventQueue is not None:
         if self.eventQueue is not None:
             self.eventQueue.clear()
             self.eventQueue.clear()
+
+    do_events = doEvents
+    process_event = processEvent

+ 6 - 7
direct/src/showbase/GarbageReport.py

@@ -8,9 +8,8 @@ from direct.showbase.PythonUtil import itype, deeptype, fastRepr
 from direct.showbase.Job import Job
 from direct.showbase.Job import Job
 from direct.showbase.JobManagerGlobal import jobMgr
 from direct.showbase.JobManagerGlobal import jobMgr
 from direct.showbase.MessengerGlobal import messenger
 from direct.showbase.MessengerGlobal import messenger
-import direct.showbase.DConfig as config
+from panda3d.core import ConfigVariableBool
 import gc
 import gc
-import types
 
 
 GarbageCycleCountAnnounceEvent = 'announceGarbageCycleDesc2num'
 GarbageCycleCountAnnounceEvent = 'announceGarbageCycleDesc2num'
 
 
@@ -213,7 +212,7 @@ class GarbageReport(Job):
                     startIndex = 0
                     startIndex = 0
                     # + 1 to include a reference back to the first object
                     # + 1 to include a reference back to the first object
                     endIndex = numObjs + 1
                     endIndex = numObjs + 1
-                    if type(objs[-1]) is types.InstanceType and type(objs[0]) is dict:
+                    if type(objs[0]) is dict and hasattr(objs[-1], '__dict__'):
                         startIndex -= 1
                         startIndex -= 1
                         endIndex -= 1
                         endIndex -= 1
 
 
@@ -222,7 +221,7 @@ class GarbageReport(Job):
                             numToSkip -= 1
                             numToSkip -= 1
                             continue
                             continue
                         obj = objs[index]
                         obj = objs[index]
-                        if type(obj) is types.InstanceType:
+                        if hasattr(obj, '__dict__'):
                             if not objAlreadyRepresented:
                             if not objAlreadyRepresented:
                                 cycleBySyntax += '%s' % obj.__class__.__name__
                                 cycleBySyntax += '%s' % obj.__class__.__name__
                             cycleBySyntax += '.'
                             cycleBySyntax += '.'
@@ -307,7 +306,7 @@ class GarbageReport(Job):
             while n > 0:
             while n > 0:
                 yield None
                 yield None
                 digits += 1
                 digits += 1
-                n /= 10
+                n = n // 10
             digits = digits
             digits = digits
             format = '%0' + '%s' % digits + 'i:%s \t%s'
             format = '%0' + '%s' % digits + 'i:%s \t%s'
 
 
@@ -562,7 +561,7 @@ class _CFGLGlobals:
 def checkForGarbageLeaks():
 def checkForGarbageLeaks():
     gc.collect()
     gc.collect()
     numGarbage = len(gc.garbage)
     numGarbage = len(gc.garbage)
-    if numGarbage > 0 and config.GetBool('auto-garbage-logging', 0):
+    if numGarbage > 0 and ConfigVariableBool('auto-garbage-logging', False):
         if numGarbage != _CFGLGlobals.LastNumGarbage:
         if numGarbage != _CFGLGlobals.LastNumGarbage:
             print("")
             print("")
             gr = GarbageReport('found garbage', threaded=False, collect=False)
             gr = GarbageReport('found garbage', threaded=False, collect=False)
@@ -572,7 +571,7 @@ def checkForGarbageLeaks():
             messenger.send(GarbageCycleCountAnnounceEvent, [gr.getDesc2numDict()])
             messenger.send(GarbageCycleCountAnnounceEvent, [gr.getDesc2numDict()])
             gr.destroy()
             gr.destroy()
         notify = directNotify.newCategory("GarbageDetect")
         notify = directNotify.newCategory("GarbageDetect")
-        if config.GetBool('allow-garbage-cycles', 1):
+        if ConfigVariableBool('allow-garbage-cycles', True):
             func = notify.warning
             func = notify.warning
         else:
         else:
             func = notify.error
             func = notify.error

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

@@ -35,7 +35,7 @@ class Job(DirectObject):
         self._priority = Job.Priorities.Normal
         self._priority = Job.Priorities.Normal
         self._finished = False
         self._finished = False
         if __debug__:
         if __debug__:
-            self._pstats = PStatCollector("App:Show code:jobManager:%s" % self._name)
+            self._pstats = PStatCollector("App:Tasks:jobManager:%s" % self._name)
 
 
     def destroy(self):
     def destroy(self):
         del self._name
         del self._name

+ 46 - 35
direct/src/showbase/ShowBase.py

@@ -1151,7 +1151,7 @@ class ShowBase(DirectObject.DirectObject):
         Creates the render scene graph, the primary scene graph for
         Creates the render scene graph, the primary scene graph for
         rendering 3-d geometry.
         rendering 3-d geometry.
         """
         """
-        ## This is the root of the 3-D scene graph.
+        #: This is the root of the 3-D scene graph.
         self.render = NodePath('render')
         self.render = NodePath('render')
         self.render.setAttrib(RescaleNormalAttrib.makeDefault())
         self.render.setAttrib(RescaleNormalAttrib.makeDefault())
 
 
@@ -1170,7 +1170,7 @@ class ShowBase(DirectObject.DirectObject):
         # for the benefit of creating DirectGui elements before ShowBase.
         # for the benefit of creating DirectGui elements before ShowBase.
         from . import ShowBaseGlobal
         from . import ShowBaseGlobal
 
 
-        ## This is the root of the 2-D scene graph.
+        #: This is the root of the 2-D scene graph.
         self.render2d = ShowBaseGlobal.render2d
         self.render2d = ShowBaseGlobal.render2d
 
 
         # Set up some overrides to turn off certain properties which
         # Set up some overrides to turn off certain properties which
@@ -1191,12 +1191,12 @@ class ShowBase(DirectObject.DirectObject):
         self.render2d.setMaterialOff(1)
         self.render2d.setMaterialOff(1)
         self.render2d.setTwoSided(1)
         self.render2d.setTwoSided(1)
 
 
-        ## The normal 2-d DisplayRegion has an aspect ratio that
-        ## matches the window, but its coordinate system is square.
-        ## This means anything we parent to render2d gets stretched.
-        ## For things where that makes a difference, we set up
-        ## aspect2d, which scales things back to the right aspect
-        ## ratio along the X axis (Z is still from -1 to 1)
+        #: The normal 2-d DisplayRegion has an aspect ratio that
+        #: matches the window, but its coordinate system is square.
+        #: This means anything we parent to render2d gets stretched.
+        #: For things where that makes a difference, we set up
+        #: aspect2d, which scales things back to the right aspect
+        #: ratio along the X axis (Z is still from -1 to 1)
         self.aspect2d = ShowBaseGlobal.aspect2d
         self.aspect2d = ShowBaseGlobal.aspect2d
 
 
         aspectRatio = self.getAspectRatio()
         aspectRatio = self.getAspectRatio()
@@ -1204,13 +1204,13 @@ class ShowBase(DirectObject.DirectObject):
 
 
         self.a2dBackground = self.aspect2d.attachNewNode("a2dBackground")
         self.a2dBackground = self.aspect2d.attachNewNode("a2dBackground")
 
 
-        ## The Z position of the top border of the aspect2d screen.
+        #: The Z position of the top border of the aspect2d screen.
         self.a2dTop = 1.0
         self.a2dTop = 1.0
-        ## The Z position of the bottom border of the aspect2d screen.
+        #: The Z position of the bottom border of the aspect2d screen.
         self.a2dBottom = -1.0
         self.a2dBottom = -1.0
-        ## The X position of the left border of the aspect2d screen.
+        #: The X position of the left border of the aspect2d screen.
         self.a2dLeft = -aspectRatio
         self.a2dLeft = -aspectRatio
-        ## The X position of the right border of the aspect2d screen.
+        #: The X position of the right border of the aspect2d screen.
         self.a2dRight = aspectRatio
         self.a2dRight = aspectRatio
 
 
         self.a2dTopCenter = self.aspect2d.attachNewNode("a2dTopCenter")
         self.a2dTopCenter = self.aspect2d.attachNewNode("a2dTopCenter")
@@ -1250,9 +1250,9 @@ class ShowBase(DirectObject.DirectObject):
         self.a2dBottomRight.setPos(self.a2dRight, 0, self.a2dBottom)
         self.a2dBottomRight.setPos(self.a2dRight, 0, self.a2dBottom)
         self.a2dBottomRightNs.setPos(self.a2dRight, 0, self.a2dBottom)
         self.a2dBottomRightNs.setPos(self.a2dRight, 0, self.a2dBottom)
 
 
-        ## This special root, pixel2d, uses units in pixels that are relative
-        ## to the window. The upperleft corner of the window is (0, 0),
-        ## the lowerleft corner is (xsize, -ysize), in this coordinate system.
+        #: This special root, pixel2d, uses units in pixels that are relative
+        #: to the window. The upperleft corner of the window is (0, 0),
+        #: the lowerleft corner is (xsize, -ysize), in this coordinate system.
         self.pixel2d = self.render2d.attachNewNode(PGTop("pixel2d"))
         self.pixel2d = self.render2d.attachNewNode(PGTop("pixel2d"))
         self.pixel2d.setPos(-1, 0, 1)
         self.pixel2d.setPos(-1, 0, 1)
         xsize, ysize = self.getSize()
         xsize, ysize = self.getSize()
@@ -1282,25 +1282,25 @@ class ShowBase(DirectObject.DirectObject):
         self.render2dp.setMaterialOff(1)
         self.render2dp.setMaterialOff(1)
         self.render2dp.setTwoSided(1)
         self.render2dp.setTwoSided(1)
 
 
-        ## The normal 2-d DisplayRegion has an aspect ratio that
-        ## matches the window, but its coordinate system is square.
-        ## This means anything we parent to render2dp gets stretched.
-        ## For things where that makes a difference, we set up
-        ## aspect2dp, which scales things back to the right aspect
-        ## ratio along the X axis (Z is still from -1 to 1)
+        #: The normal 2-d DisplayRegion has an aspect ratio that
+        #: matches the window, but its coordinate system is square.
+        #: This means anything we parent to render2dp gets stretched.
+        #: For things where that makes a difference, we set up
+        #: aspect2dp, which scales things back to the right aspect
+        #: ratio along the X axis (Z is still from -1 to 1)
         self.aspect2dp = self.render2dp.attachNewNode(PGTop("aspect2dp"))
         self.aspect2dp = self.render2dp.attachNewNode(PGTop("aspect2dp"))
         self.aspect2dp.node().setStartSort(16384)
         self.aspect2dp.node().setStartSort(16384)
 
 
         aspectRatio = self.getAspectRatio()
         aspectRatio = self.getAspectRatio()
         self.aspect2dp.setScale(1.0 / aspectRatio, 1.0, 1.0)
         self.aspect2dp.setScale(1.0 / aspectRatio, 1.0, 1.0)
 
 
-        ## The Z position of the top border of the aspect2dp screen.
+        #: The Z position of the top border of the aspect2dp screen.
         self.a2dpTop = 1.0
         self.a2dpTop = 1.0
-        ## The Z position of the bottom border of the aspect2dp screen.
+        #: The Z position of the bottom border of the aspect2dp screen.
         self.a2dpBottom = -1.0
         self.a2dpBottom = -1.0
-        ## The X position of the left border of the aspect2dp screen.
+        #: The X position of the left border of the aspect2dp screen.
         self.a2dpLeft = -aspectRatio
         self.a2dpLeft = -aspectRatio
-        ## The X position of the right border of the aspect2dp screen.
+        #: The X position of the right border of the aspect2dp screen.
         self.a2dpRight = aspectRatio
         self.a2dpRight = aspectRatio
 
 
         self.a2dpTopCenter = self.aspect2dp.attachNewNode("a2dpTopCenter")
         self.a2dpTopCenter = self.aspect2dp.attachNewNode("a2dpTopCenter")
@@ -1324,9 +1324,9 @@ class ShowBase(DirectObject.DirectObject):
         self.a2dpBottomLeft.setPos(self.a2dpLeft, 0, self.a2dpBottom)
         self.a2dpBottomLeft.setPos(self.a2dpLeft, 0, self.a2dpBottom)
         self.a2dpBottomRight.setPos(self.a2dpRight, 0, self.a2dpBottom)
         self.a2dpBottomRight.setPos(self.a2dpRight, 0, self.a2dpBottom)
 
 
-        ## This special root, pixel2d, uses units in pixels that are relative
-        ## to the window. The upperleft corner of the window is (0, 0),
-        ## the lowerleft corner is (xsize, -ysize), in this coordinate system.
+        #: This special root, pixel2dp, uses units in pixels that are relative
+        #: to the window. The upperleft corner of the window is (0, 0),
+        #: the lowerleft corner is (xsize, -ysize), in this coordinate system.
         self.pixel2dp = self.render2dp.attachNewNode(PGTop("pixel2dp"))
         self.pixel2dp = self.render2dp.attachNewNode(PGTop("pixel2dp"))
         self.pixel2dp.node().setStartSort(16384)
         self.pixel2dp.node().setStartSort(16384)
         self.pixel2dp.setPos(-1, 0, 1)
         self.pixel2dp.setPos(-1, 0, 1)
@@ -1647,11 +1647,11 @@ class ShowBase(DirectObject.DirectObject):
 
 
         mw = self.buttonThrowers[0].getParent()
         mw = self.buttonThrowers[0].getParent()
 
 
-        ## A special ButtonThrower to generate keyboard events and
-        ## include the time from the OS.  This is separate only to
-        ## support legacy code that did not expect a time parameter; it
-        ## will eventually be folded into the normal ButtonThrower,
-        ## above.
+        #: A special ButtonThrower to generate keyboard events and
+        #: include the time from the OS.  This is separate only to
+        #: support legacy code that did not expect a time parameter; it
+        #: will eventually be folded into the normal ButtonThrower,
+        #: above.
         self.timeButtonThrower = mw.attachNewNode(ButtonThrower('timeButtons'))
         self.timeButtonThrower = mw.attachNewNode(ButtonThrower('timeButtons'))
         self.timeButtonThrower.node().setPrefix('time-')
         self.timeButtonThrower.node().setPrefix('time-')
         self.timeButtonThrower.node().setTimeFlag(1)
         self.timeButtonThrower.node().setTimeFlag(1)
@@ -2713,7 +2713,7 @@ class ShowBase(DirectObject.DirectObject):
 
 
     def screenshot(self, namePrefix = 'screenshot',
     def screenshot(self, namePrefix = 'screenshot',
                    defaultFilename = 1, source = None,
                    defaultFilename = 1, source = None,
-                   imageComment=""):
+                   imageComment="", blocking=True):
         """ Captures a screenshot from the main window or from the
         """ Captures a screenshot from the main window or from the
         specified window or Texture and writes it to a filename in the
         specified window or Texture and writes it to a filename in the
         current directory (or to a specified directory).
         current directory (or to a specified directory).
@@ -2735,6 +2735,13 @@ class ShowBase(DirectObject.DirectObject):
         generated by makeCubeMap(), namePrefix should contain the hash
         generated by makeCubeMap(), namePrefix should contain the hash
         mark ('#') character.
         mark ('#') character.
 
 
+        Normally, this call will block until the screenshot is fully
+        written.  To write the screenshot in a background thread
+        instead, pass blocking = False.  In this case, the return value
+        is a future that can be awaited.
+
+        A "screenshot" event will be sent once the screenshot is saved.
+
         :returns: The filename if successful, or None if there is a problem.
         :returns: The filename if successful, or None if there is a problem.
         """
         """
 
 
@@ -2751,8 +2758,12 @@ class ShowBase(DirectObject.DirectObject):
                 saved = source.write(filename, 0, 0, 1, 0)
                 saved = source.write(filename, 0, 0, 1, 0)
             else:
             else:
                 saved = source.write(filename)
                 saved = source.write(filename)
-        else:
+        elif blocking:
             saved = source.saveScreenshot(filename, imageComment)
             saved = source.saveScreenshot(filename, imageComment)
+        else:
+            request = source.saveAsyncScreenshot(filename, imageComment)
+            request.addDoneCallback(lambda fut, filename=filename: messenger.send('screenshot', [filename]))
+            return request
 
 
         if saved:
         if saved:
             # Announce to anybody that a screenshot has been taken
             # Announce to anybody that a screenshot has been taken

+ 0 - 1
direct/src/showutil/Effects.py

@@ -105,7 +105,6 @@ def createBounce(nodeObj, numBounces, startValues, totalTime, amplitude,
 
 
         newVec3 = Vec3(startValues)
         newVec3 = Vec3(startValues)
         newVec3.setCell(index, currBounceVal)
         newVec3.setCell(index, currBounceVal)
-        print("### newVec3 = %s" % newVec3)
 
 
         # create the right type of lerp
         # create the right type of lerp
         if ((bounceType == SX_BOUNCE) or (bounceType == SY_BOUNCE) or
         if ((bounceType == SX_BOUNCE) or (bounceType == SY_BOUNCE) or

+ 96 - 0
doc/ReleaseNotes

@@ -1,3 +1,99 @@
+-----------------------  RELEASE 1.10.13  -----------------------
+
+This is a significant release containing many important bug fixes and a couple
+of interesting new features.
+
+Rendering
+* Fix single texture stage limit when `gl-version 3 2` or higher is set (#1404)
+* Fix some render-to-texture bugs with multithreaded pipeline (incl. #1364)
+* Fix failure to unset divisor after rendering with hardware instancing
+* Add "MSAA" filter to CommonFilters class as convenience to enable MSAA
+* Fix multisample FBOs with MRT resolving aux target into color target
+* Fix shader generator not responding to fog color changes
+* Fix OpenGL error when downloading GL_LUMINANCE8 texture
+
+Windowing
+* Fix incorrect "without" event generation when mouse leaves window (#1400)
+* Windows: Fix lost "up" events when dragging cursor outside window while
+  multiple mouse buttons are pressed down (#1396)
+* macOS: Fix crash with threading-model on newer macOS versions (#1286)
+* macOS: Fix black screen when going fullscreen on Apple M1-based macs (#1316)
+* macOS: Fix window overlapping Dock when requesting very large height
+* macOS: Improve application termination handling, now sends proper exit events
+* X11: tinydisplay handles window resizes more efficiently
+* X11: Work around window not rendering at first on swaywm (#1087)
+
+Deployment
+* Not all code was being built with optimize level 2
+* Add `keep_docstrings` option to switch to optimize level 1 (#1341)
+* Add `prefer_discrete_gpu` option to force dedicated GPU on Windows (#680)
+* Add `bam_model_extensions` for converting non-egg models to .bam (#714)
+* Work around autodiscovery error when using `setuptools>=61.0.0` (#1394)
+* Default Linux target to `manylinux2014_x86_64` on Python 3.11+
+
+PStats
+* Add support for Python profiling with `pstats-python-profiler` Config.prc var
+* Fix PStats crash at launch from pip installs in newer Linux distros (#1391)
+* Performance improvements to time-based strip chart views
+* Time-based strip charts now can show start/stop count in corner of graphs
+* Optimize client performance when sending a large number of samples
+* Fix dropped frames by changing value for `pstats-max-queue-size` from 1 to 4
+* Server accept clients using PStats protocol version 2.3
+
+Assimp
+* Fix assertion when loading meshes with multiple primitive types
+* Add `assimp-collapse-dummy-root-node` option to remove root node (see #366)
+* Import custom object properties as tags
+* Add support for additional texture maps, including PBR textures
+* Support reading tangent and binormal vectors
+* Improve performance when loading geometry
+* Fix problems reading external files (see #366)
+* Support reading alpha mode when loading .glTF models
+* Add support for texture transforms
+* Add support for texture wrapping modes
+* Fix memory corruption bugs
+
+Build
+* Fixes to `pview.desktop` file on Linux
+* makepanda: Fix problems when building on arm64 on macOS without `--arch` flag
+* makepanda: Fix detection issues with newer macOS / XCode versions
+* makepanda: Fix motiontrail header files not being copied
+* Windows: Fix HTTPClient not working when nativenet module is disabled
+* macOS: Fix OpenCV library refusing to load in arm64 build (#1393)
+* Fix compiler error when compiling for e2k (MCST Elbrus 2000) (#1367)
+
+Miscellaneous
+* Add new motion trails sample program
+* `MotionTrail.add_vertex()` method now directly accepts a vertex position
+* Significant performance optimization of C++-based motion trail implementation
+* Fix race condition when destructing/constructing NodePaths in thread (#1366)
+* Add implementation of capsule-into-polygon collision test (#1369)
+* Fix texture transforms sometimes not being flattened (#1392)
+* Fix support for `#pragma include <file.glsl>` in GLSL shaders
+* Fix `ShaderBuffer.prepare()` not doing anything
+* Implement deepcopy for PointerToArray
+* Fix Texture deepcopy keeping a reference to the original RAM image
+* Fix bf-cbc encryption no longer working when building with OpenSSL 3.0
+* PandaNode bounds_type property was erroneously marked read-only
+* Fix warnings when copying OdeTriMeshGeom objects
+* Fix a crash when using `Notify.set_ostream_ptr()` from Python (#1371)
+* Fix GarbageReport not working with Python 3 (#1304)
+* Make `mat.cols[n]` and `mat.rows[n]` assignable
+* Fix `ExecutionEnvironment.args` being empty on Linux
+* Add various useful functions to interrogatedb module
+* Fix Python 3 issues unpacking uint types in Python 3 (#1380)
+* Fix interrogate syntax error with C++11-style attributes in declarators
+* Fix double-precision color values not being clamped by GeomVertexWriter
+* Fix regression with BufferViewer in double-precision build (#1365)
+* Fix `PandaNode.nested_vertices` not updating properly
+* Prevent Panda calculating bounding volume of Geom with custom bounding volume
+* Add `do_events()` and `process_event()` snake_case aliases in eventMgr
+* Support second arg of None in `replace_texture()` / `replace_material()`
+* Support `os.fspath()` for ConfigVariableFilename objects (#1406)
+* rplight: Fix PSSM calculation failing with infinite far distance (#1397)
+* Remove spurious print in `direct.showutil.Effects.createBounce()` (#1383)
+* Fix assorted compiler warnings
+
 -----------------------  RELEASE 1.10.12  -----------------------
 -----------------------  RELEASE 1.10.12  -----------------------
 
 
 Recommended maintenance release containing primarily bug fixes.
 Recommended maintenance release containing primarily bug fixes.

+ 1 - 0
dtool/src/dtoolbase/deletedBufferChain.cxx

@@ -52,6 +52,7 @@ allocate(size_t size, TypeHandle type_handle) {
 #ifdef USE_DELETED_CHAIN
 #ifdef USE_DELETED_CHAIN
   // TAU_PROFILE("void *DeletedBufferChain::allocate(size_t, TypeHandle)", "
   // TAU_PROFILE("void *DeletedBufferChain::allocate(size_t, TypeHandle)", "
   // ", TAU_USER);
   // ", TAU_USER);
+  // If this triggers, maybe you forgot ALLOC_DELETED_CHAIN in a subclass?
   assert(size <= _buffer_size);
   assert(size <= _buffer_size);
 
 
   // Determine how much space to allocate.
   // Determine how much space to allocate.

+ 2 - 2
dtool/src/dtoolbase/memoryHook.I

@@ -18,7 +18,7 @@
 INLINE void MemoryHook::
 INLINE void MemoryHook::
 inc_heap(size_t size) {
 inc_heap(size_t size) {
 #ifdef DO_MEMORY_USAGE
 #ifdef DO_MEMORY_USAGE
-  AtomicAdjust::add(_requested_heap_size, (AtomicAdjust::Integer)size);
+  _requested_heap_size.fetch_add(size, std::memory_order_relaxed);
 #endif  // DO_MEMORY_USAGE
 #endif  // DO_MEMORY_USAGE
 }
 }
 
 
@@ -30,7 +30,7 @@ INLINE void MemoryHook::
 dec_heap(size_t size) {
 dec_heap(size_t size) {
 #ifdef DO_MEMORY_USAGE
 #ifdef DO_MEMORY_USAGE
   // assert((int)size <= _requested_heap_size);
   // assert((int)size <= _requested_heap_size);
-  AtomicAdjust::add(_requested_heap_size, -(AtomicAdjust::Integer)size);
+  _requested_heap_size.fetch_sub(size, std::memory_order_relaxed);
 #endif  // DO_MEMORY_USAGE
 #endif  // DO_MEMORY_USAGE
 }
 }
 
 

+ 18 - 18
dtool/src/dtoolbase/memoryHook.cxx

@@ -203,10 +203,10 @@ ptr_to_alloc(void *ptr, size_t &size) {
  */
  */
 MemoryHook::
 MemoryHook::
 MemoryHook(const MemoryHook &copy) :
 MemoryHook(const MemoryHook &copy) :
-  _total_heap_single_size(copy._total_heap_single_size),
-  _total_heap_array_size(copy._total_heap_array_size),
-  _requested_heap_size(copy._requested_heap_size),
-  _total_mmap_size(copy._total_mmap_size),
+  _total_heap_single_size(copy._total_heap_single_size.load(std::memory_order_relaxed)),
+  _total_heap_array_size(copy._total_heap_array_size.load(std::memory_order_relaxed)),
+  _requested_heap_size(copy._requested_heap_size.load(std::memory_order_relaxed)),
+  _total_mmap_size(copy._total_mmap_size.load(std::memory_order_relaxed)),
   _max_heap_size(copy._max_heap_size),
   _max_heap_size(copy._max_heap_size),
   _page_size(copy._page_size) {
   _page_size(copy._page_size) {
 }
 }
@@ -250,9 +250,9 @@ heap_alloc_single(size_t size) {
   size = get_ptr_size(alloc);
   size = get_ptr_size(alloc);
   inflated_size = size;
   inflated_size = size;
 #endif
 #endif
-  AtomicAdjust::add(_total_heap_single_size, (AtomicAdjust::Integer)size);
-  if ((size_t)AtomicAdjust::get(_total_heap_single_size) +
-      (size_t)AtomicAdjust::get(_total_heap_array_size) >
+  _total_heap_single_size.fetch_add(size, std::memory_order_relaxed);
+  if (_total_heap_single_size.load(std::memory_order_relaxed) +
+      _total_heap_array_size.load(std::memory_order_relaxed) >
       _max_heap_size) {
       _max_heap_size) {
     overflow_heap_size();
     overflow_heap_size();
   }
   }
@@ -275,8 +275,8 @@ heap_free_single(void *ptr) {
   void *alloc = ptr_to_alloc(ptr, size);
   void *alloc = ptr_to_alloc(ptr, size);
 
 
 #ifdef DO_MEMORY_USAGE
 #ifdef DO_MEMORY_USAGE
-  assert((int)size <= _total_heap_single_size);
-  AtomicAdjust::add(_total_heap_single_size, -(AtomicAdjust::Integer)size);
+  assert((int)size <= _total_heap_single_size.load(std::memory_order_relaxed));
+  _total_heap_single_size.fetch_sub(size, std::memory_order_relaxed);
 #endif  // DO_MEMORY_USAGE
 #endif  // DO_MEMORY_USAGE
 
 
 #ifdef MEMORY_HOOK_MALLOC_LOCK
 #ifdef MEMORY_HOOK_MALLOC_LOCK
@@ -327,9 +327,9 @@ heap_alloc_array(size_t size) {
   size = get_ptr_size(alloc);
   size = get_ptr_size(alloc);
   inflated_size = size;
   inflated_size = size;
 #endif
 #endif
-  AtomicAdjust::add(_total_heap_array_size, (AtomicAdjust::Integer)size);
-  if ((size_t)AtomicAdjust::get(_total_heap_single_size) +
-      (size_t)AtomicAdjust::get(_total_heap_array_size) >
+  _total_heap_array_size.fetch_add(size, std::memory_order_relaxed);
+  if (_total_heap_single_size.load(std::memory_order_relaxed) +
+      _total_heap_array_size.load(std::memory_order_relaxed) >
       _max_heap_size) {
       _max_heap_size) {
     overflow_heap_size();
     overflow_heap_size();
   }
   }
@@ -383,8 +383,8 @@ heap_realloc_array(void *ptr, size_t size) {
   size = get_ptr_size(alloc1);
   size = get_ptr_size(alloc1);
   inflated_size = size;
   inflated_size = size;
 #endif
 #endif
-  assert((AtomicAdjust::Integer)orig_size <= _total_heap_array_size);
-  AtomicAdjust::add(_total_heap_array_size, (AtomicAdjust::Integer)size-(AtomicAdjust::Integer)orig_size);
+  assert(orig_size <= _total_heap_array_size.load(std::memory_order_relaxed));
+  _total_heap_array_size.fetch_add(size - orig_size, std::memory_order_relaxed);
 #endif  // DO_MEMORY_USAGE
 #endif  // DO_MEMORY_USAGE
 
 
   // Align this to the requested boundary.
   // Align this to the requested boundary.
@@ -424,7 +424,7 @@ heap_free_array(void *ptr) {
 
 
 #ifdef DO_MEMORY_USAGE
 #ifdef DO_MEMORY_USAGE
   assert((int)size <= _total_heap_array_size);
   assert((int)size <= _total_heap_array_size);
-  AtomicAdjust::add(_total_heap_array_size, -(AtomicAdjust::Integer)size);
+  _total_heap_array_size.fetch_sub(size, std::memory_order_relaxed);
 #endif  // DO_MEMORY_USAGE
 #endif  // DO_MEMORY_USAGE
 
 
 #ifdef MEMORY_HOOK_MALLOC_LOCK
 #ifdef MEMORY_HOOK_MALLOC_LOCK
@@ -489,7 +489,7 @@ mmap_alloc(size_t size, bool allow_exec) {
   assert((size % _page_size) == 0);
   assert((size % _page_size) == 0);
 
 
 #ifdef DO_MEMORY_USAGE
 #ifdef DO_MEMORY_USAGE
-  _total_mmap_size += size;
+  _total_mmap_size.fetch_add(size, std::memory_order_relaxed);
 #endif
 #endif
 
 
 #ifdef _WIN32
 #ifdef _WIN32
@@ -544,8 +544,8 @@ mmap_free(void *ptr, size_t size) {
   assert((size % _page_size) == 0);
   assert((size % _page_size) == 0);
 
 
 #ifdef DO_MEMORY_USAGE
 #ifdef DO_MEMORY_USAGE
-  assert((int)size <= _total_mmap_size);
-  _total_mmap_size -= size;
+  assert((int)size <= _total_mmap_size.load(std::memory_order_relaxed));
+  _total_mmap_size.fetch_sub(size, std::memory_order_relaxed);
 #endif
 #endif
 
 
 #ifdef _WIN32
 #ifdef _WIN32

+ 5 - 5
dtool/src/dtoolbase/memoryHook.h

@@ -16,7 +16,7 @@
 
 
 #include "dtoolbase.h"
 #include "dtoolbase.h"
 #include "numeric_types.h"
 #include "numeric_types.h"
-#include "atomicAdjust.h"
+#include "patomic.h"
 #include "mutexImpl.h"
 #include "mutexImpl.h"
 #include <map>
 #include <map>
 
 
@@ -66,10 +66,10 @@ public:
   INLINE static size_t get_ptr_size(void *ptr);
   INLINE static size_t get_ptr_size(void *ptr);
 
 
 protected:
 protected:
-  TVOLATILE AtomicAdjust::Integer _total_heap_single_size = 0;
-  TVOLATILE AtomicAdjust::Integer _total_heap_array_size = 0;
-  TVOLATILE AtomicAdjust::Integer _requested_heap_size = 0;
-  TVOLATILE AtomicAdjust::Integer _total_mmap_size = 0;
+  patomic<size_t> _total_heap_single_size { 0u };
+  patomic<size_t> _total_heap_array_size { 0u };
+  patomic<size_t> _requested_heap_size { 0u };
+  patomic<size_t> _total_mmap_size { 0u };
 
 
   // If the allocated heap size crosses this threshold, we call
   // If the allocated heap size crosses this threshold, we call
   // overflow_heap_size().
   // overflow_heap_size().

+ 1 - 0
dtool/src/dtoolbase/patomic.I

@@ -63,6 +63,7 @@ template<class T>
 ALWAYS_INLINE T patomic<T>::
 ALWAYS_INLINE T patomic<T>::
 operator=(T desired) noexcept {
 operator=(T desired) noexcept {
   _value = desired;
   _value = desired;
+  return desired;
 }
 }
 
 
 /**
 /**

+ 0 - 19
dtool/src/dtoolbase/typeHandle.cxx

@@ -166,25 +166,6 @@ get_python_type() const {
 }
 }
 #endif
 #endif
 
 
-/**
- * Return the Index of the BEst fit Classs from a set
- */
-int TypeHandle::
-get_best_parent_from_Set(const std::set< int > &legal_vals) const {
-  if (legal_vals.find(_index) != legal_vals.end()) {
-    return _index;
-  }
-
-  for (int pi = 0; pi < get_num_parent_classes(); ++pi) {
-    TypeHandle ph = get_parent_class(pi);
-    int val = ph.get_best_parent_from_Set(legal_vals);
-    if (val > 0) {
-      return val;
-    }
-  }
-  return -1;
-}
-
 std::ostream &
 std::ostream &
 operator << (std::ostream &out, TypeHandle::MemoryClass mem_class) {
 operator << (std::ostream &out, TypeHandle::MemoryClass mem_class) {
   switch (mem_class) {
   switch (mem_class) {

+ 5 - 3
dtool/src/dtoolbase/typeHandle.h

@@ -97,7 +97,9 @@ PUBLISHED:
   // its value, it  might happen after the value had already been set
   // its value, it  might happen after the value had already been set
   // previously by another static initializer!
   // previously by another static initializer!
 
 
+#ifdef HAVE_PYTHON
   EXTENSION(static TypeHandle make(PyTypeObject *classobj));
   EXTENSION(static TypeHandle make(PyTypeObject *classobj));
+#endif
 
 
   INLINE bool operator == (const TypeHandle &other) const;
   INLINE bool operator == (const TypeHandle &other) const;
   INLINE bool operator != (const TypeHandle &other) const;
   INLINE bool operator != (const TypeHandle &other) const;
@@ -121,8 +123,6 @@ PUBLISHED:
   INLINE TypeHandle get_parent_towards(TypeHandle ancestor,
   INLINE TypeHandle get_parent_towards(TypeHandle ancestor,
                                        TypedObject *object = nullptr) const;
                                        TypedObject *object = nullptr) const;
 
 
-  int get_best_parent_from_Set(const std::set< int > &legal_vals) const;
-
   size_t get_memory_usage(MemoryClass memory_class) const;
   size_t get_memory_usage(MemoryClass memory_class) const;
   void inc_memory_usage(MemoryClass memory_class, size_t size);
   void inc_memory_usage(MemoryClass memory_class, size_t size);
   void dec_memory_usage(MemoryClass memory_class, size_t size);
   void dec_memory_usage(MemoryClass memory_class, size_t size);
@@ -137,13 +137,15 @@ PUBLISHED:
   MAKE_SEQ_PROPERTY(parent_classes, get_num_parent_classes, get_parent_class);
   MAKE_SEQ_PROPERTY(parent_classes, get_num_parent_classes, get_parent_class);
   MAKE_SEQ_PROPERTY(child_classes, get_num_child_classes, get_child_class);
   MAKE_SEQ_PROPERTY(child_classes, get_num_child_classes, get_child_class);
 
 
+#ifdef HAVE_PYTHON
   EXTENSION(PyObject *__reduce__() const);
   EXTENSION(PyObject *__reduce__() const);
   EXTENSION(void __setstate__(PyObject *));
   EXTENSION(void __setstate__(PyObject *));
+#endif // HAVE_PYTHON
 
 
 public:
 public:
 #ifdef HAVE_PYTHON
 #ifdef HAVE_PYTHON
   PyObject *get_python_type() const;
   PyObject *get_python_type() const;
-#endif
+#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);
   void *reallocate_array(void *ptr, size_t size) RETURNS_ALIGNED(MEMORY_HOOK_ALIGNMENT);
   void *reallocate_array(void *ptr, size_t size) RETURNS_ALIGNED(MEMORY_HOOK_ALIGNMENT);

+ 0 - 18
dtool/src/dtoolbase/typeRegistry.cxx

@@ -695,21 +695,3 @@ look_up_invalid(TypeHandle handle, TypedObject *object) const {
 
 
   return _handle_registry[handle._index];
   return _handle_registry[handle._index];
 }
 }
-
-/**
-
- */
-extern "C" int
-get_best_parent_from_Set(int id, const std::set<int> &this_set) {
-  // most common case..
-  if (this_set.find(id) != this_set.end()) {
-    return id;
-  }
-
-  TypeHandle th = TypeRegistry::ptr()->find_type_by_id(id);
-  if (th == TypeHandle::none()) {
-    return -1;
-  }
-
-  return th.get_best_parent_from_Set(this_set);
-}

+ 0 - 3
dtool/src/dtoolbase/typeRegistry.h

@@ -118,9 +118,6 @@ private:
   friend class TypeHandle;
   friend class TypeHandle;
 };
 };
 
 
-// Helper function to allow for "C" interaction into the type system
-extern "C" EXPCL_DTOOL_DTOOLBASE  int get_best_parent_from_Set(int id, const std::set<int> &this_set);
-
 #include "typeHandle.h"
 #include "typeHandle.h"
 
 
 #include "typeRegistry.I"
 #include "typeRegistry.I"

+ 0 - 8
dtool/src/dtoolbase/typedObject.I

@@ -43,14 +43,6 @@ is_exact_type(TypeHandle handle) const {
   return get_type() == handle;
   return get_type() == handle;
 }
 }
 
 
-/**
- *
- */
-INLINE int TypedObject::
-get_best_parent_from_Set(const std::set<int> &inset) const {
-  return get_type().get_best_parent_from_Set(inset);
-}
-
 /**
 /**
  * Returns the object, upcast (if necessary) to a TypedObject pointer.
  * Returns the object, upcast (if necessary) to a TypedObject pointer.
  */
  */

+ 0 - 2
dtool/src/dtoolbase/typedObject.h

@@ -106,8 +106,6 @@ PUBLISHED:
   INLINE bool is_exact_type(TypeHandle handle) const;
   INLINE bool is_exact_type(TypeHandle handle) const;
 
 
 public:
 public:
-  INLINE int get_best_parent_from_Set(const std::set<int> &) const;
-
   // Derived classes should override this function to call init_type().  It
   // Derived classes should override this function to call init_type().  It
   // will only be called in error situations when the type was for some reason
   // will only be called in error situations when the type was for some reason
   // not properly initialized.
   // not properly initialized.

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

@@ -418,6 +418,10 @@ ns_get_environment_variable(const string &var) const {
   } else if (var == "XDG_DATA_HOME") {
   } else if (var == "XDG_DATA_HOME") {
     Filename home_dir = Filename::get_home_directory();
     Filename home_dir = Filename::get_home_directory();
     return home_dir.get_fullpath() + "/.local/share";
     return home_dir.get_fullpath() + "/.local/share";
+
+  } else if (var == "XDG_STATE_HOME") {
+    Filename home_dir = Filename::get_home_directory();
+    return home_dir.get_fullpath() + "/.local/state";
   }
   }
 #endif // _WIN32
 #endif // _WIN32
 
 

+ 32 - 21
dtool/src/dtoolutil/filename.cxx

@@ -16,7 +16,6 @@
 #include "dSearchPath.h"
 #include "dSearchPath.h"
 #include "executionEnvironment.h"
 #include "executionEnvironment.h"
 #include "vector_string.h"
 #include "vector_string.h"
-#include "atomicAdjust.h"
 
 
 #include <stdio.h>  // For rename() and tempnam()
 #include <stdio.h>  // For rename() and tempnam()
 #include <time.h>   // for clock() and time()
 #include <time.h>   // for clock() and time()
@@ -60,10 +59,10 @@ using std::wstring;
 
 
 TextEncoder::Encoding Filename::_filesystem_encoding = TextEncoder::E_utf8;
 TextEncoder::Encoding Filename::_filesystem_encoding = TextEncoder::E_utf8;
 
 
-TVOLATILE AtomicAdjust::Pointer Filename::_home_directory;
-TVOLATILE AtomicAdjust::Pointer Filename::_temp_directory;
-TVOLATILE AtomicAdjust::Pointer Filename::_user_appdata_directory;
-TVOLATILE AtomicAdjust::Pointer Filename::_common_appdata_directory;
+patomic<Filename *> Filename::_home_directory(nullptr);
+patomic<Filename *> Filename::_temp_directory(nullptr);
+patomic<Filename *> Filename::_user_appdata_directory(nullptr);
+patomic<Filename *> Filename::_common_appdata_directory(nullptr);
 TypeHandle Filename::_type_handle;
 TypeHandle Filename::_type_handle;
 
 
 #ifdef ANDROID
 #ifdef ANDROID
@@ -486,7 +485,8 @@ temporary(const string &dirname, const string &prefix, const string &suffix,
  */
  */
 const Filename &Filename::
 const Filename &Filename::
 get_home_directory() {
 get_home_directory() {
-  if (AtomicAdjust::get_ptr(_home_directory) == nullptr) {
+  Filename *curdir = _home_directory.load(std::memory_order_consume);
+  if (curdir == nullptr) {
     Filename home_directory;
     Filename home_directory;
 
 
     // In all environments except Windows, check $HOME first.
     // In all environments except Windows, check $HOME first.
@@ -538,14 +538,16 @@ get_home_directory() {
     }
     }
 
 
     Filename *newdir = new Filename(home_directory);
     Filename *newdir = new Filename(home_directory);
-    if (AtomicAdjust::compare_and_exchange_ptr(_home_directory, nullptr, newdir) != nullptr) {
+    if (_home_directory.compare_exchange_strong(curdir, newdir, std::memory_order_release, std::memory_order_consume)) {
+      return *newdir;
+    } else {
       // Didn't store it.  Must have been stored by someone else.
       // Didn't store it.  Must have been stored by someone else.
-      assert(_home_directory != nullptr);
+      assert(curdir != nullptr);
       delete newdir;
       delete newdir;
     }
     }
   }
   }
 
 
-  return (*(Filename *)_home_directory);
+  return *curdir;
 }
 }
 
 
 /**
 /**
@@ -553,7 +555,8 @@ get_home_directory() {
  */
  */
 const Filename &Filename::
 const Filename &Filename::
 get_temp_directory() {
 get_temp_directory() {
-  if (AtomicAdjust::get_ptr(_temp_directory) == nullptr) {
+  Filename *curdir = _temp_directory.load(std::memory_order_consume);
+  if (curdir == nullptr) {
     Filename temp_directory;
     Filename temp_directory;
 
 
 #ifdef _WIN32
 #ifdef _WIN32
@@ -586,14 +589,16 @@ get_temp_directory() {
     }
     }
 
 
     Filename *newdir = new Filename(temp_directory);
     Filename *newdir = new Filename(temp_directory);
-    if (AtomicAdjust::compare_and_exchange_ptr(_temp_directory, nullptr, newdir) != nullptr) {
+    if (_temp_directory.compare_exchange_strong(curdir, newdir, std::memory_order_release, std::memory_order_consume)) {
+      return *newdir;
+    } else {
       // Didn't store it.  Must have been stored by someone else.
       // Didn't store it.  Must have been stored by someone else.
-      assert(_temp_directory != nullptr);
+      assert(curdir != nullptr);
       delete newdir;
       delete newdir;
     }
     }
   }
   }
 
 
-  return (*(Filename *)_temp_directory);
+  return *curdir;
 }
 }
 
 
 /**
 /**
@@ -603,7 +608,8 @@ get_temp_directory() {
  */
  */
 const Filename &Filename::
 const Filename &Filename::
 get_user_appdata_directory() {
 get_user_appdata_directory() {
-  if (AtomicAdjust::get_ptr(_user_appdata_directory) == nullptr) {
+  Filename *curdir = _user_appdata_directory.load(std::memory_order_consume);
+  if (curdir == nullptr) {
     Filename user_appdata_directory;
     Filename user_appdata_directory;
 
 
 #ifdef _WIN32
 #ifdef _WIN32
@@ -643,14 +649,16 @@ get_user_appdata_directory() {
     }
     }
 
 
     Filename *newdir = new Filename(user_appdata_directory);
     Filename *newdir = new Filename(user_appdata_directory);
-    if (AtomicAdjust::compare_and_exchange_ptr(_user_appdata_directory, nullptr, newdir) != nullptr) {
+    if (_user_appdata_directory.compare_exchange_strong(curdir, newdir, std::memory_order_release, std::memory_order_consume)) {
+      return *newdir;
+    } else {
       // Didn't store it.  Must have been stored by someone else.
       // Didn't store it.  Must have been stored by someone else.
-      assert(_user_appdata_directory != nullptr);
+      assert(curdir != nullptr);
       delete newdir;
       delete newdir;
     }
     }
   }
   }
 
 
-  return (*(Filename *)_user_appdata_directory);
+  return *curdir;
 }
 }
 
 
 /**
 /**
@@ -659,7 +667,8 @@ get_user_appdata_directory() {
  */
  */
 const Filename &Filename::
 const Filename &Filename::
 get_common_appdata_directory() {
 get_common_appdata_directory() {
-  if (AtomicAdjust::get_ptr(_common_appdata_directory) == nullptr) {
+  Filename *curdir = _common_appdata_directory.load(std::memory_order_consume);
+  if (curdir == nullptr) {
     Filename common_appdata_directory;
     Filename common_appdata_directory;
 
 
 #ifdef _WIN32
 #ifdef _WIN32
@@ -693,14 +702,16 @@ get_common_appdata_directory() {
     }
     }
 
 
     Filename *newdir = new Filename(common_appdata_directory);
     Filename *newdir = new Filename(common_appdata_directory);
-    if (AtomicAdjust::compare_and_exchange_ptr(_common_appdata_directory, nullptr, newdir) != nullptr) {
+    if (_common_appdata_directory.compare_exchange_strong(curdir, newdir, std::memory_order_release, std::memory_order_consume)) {
+      return *newdir;
+    } else {
       // Didn't store it.  Must have been stored by someone else.
       // Didn't store it.  Must have been stored by someone else.
-      assert(_common_appdata_directory != nullptr);
+      assert(curdir != nullptr);
       delete newdir;
       delete newdir;
     }
     }
   }
   }
 
 
-  return (*(Filename *)_common_appdata_directory);
+  return *curdir;
 }
 }
 
 
 /**
 /**

+ 11 - 8
dtool/src/dtoolutil/filename.h

@@ -20,6 +20,7 @@
 #include "register_type.h"
 #include "register_type.h"
 #include "vector_string.h"
 #include "vector_string.h"
 #include "textEncoder.h"
 #include "textEncoder.h"
+#include "patomic.h"
 
 
 #include <assert.h>
 #include <assert.h>
 
 
@@ -69,7 +70,7 @@ PUBLISHED:
   EXTENSION(Filename(PyObject *path));
   EXTENSION(Filename(PyObject *path));
 
 
   EXTENSION(PyObject *__reduce__(PyObject *self) const);
   EXTENSION(PyObject *__reduce__(PyObject *self) const);
-#endif
+#endif // HAVE_PYTHON
 
 
   // Static constructors to explicitly create a filename that refers to a text
   // Static constructors to explicitly create a filename that refers to a text
   // or binary file.  This is in lieu of calling set_text() or set_binary() or
   // or binary file.  This is in lieu of calling set_text() or set_binary() or
@@ -113,8 +114,10 @@ PUBLISHED:
   INLINE size_t length() const;
   INLINE size_t length() const;
   INLINE char operator [] (size_t n) const;
   INLINE char operator [] (size_t n) const;
 
 
+#ifdef HAVE_PYTHON
   EXTENSION(PyObject *__repr__() const);
   EXTENSION(PyObject *__repr__() const);
   EXTENSION(PyObject *__fspath__() const);
   EXTENSION(PyObject *__fspath__() const);
+#endif // HAVE_PYTHON
 
 
   INLINE std::string substr(size_t begin) const;
   INLINE std::string substr(size_t begin) const;
   INLINE std::string substr(size_t begin, size_t end) const;
   INLINE std::string substr(size_t begin, size_t end) const;
@@ -201,7 +204,7 @@ PUBLISHED:
   bool scan_directory(vector_string &contents) const;
   bool scan_directory(vector_string &contents) const;
 #ifdef HAVE_PYTHON
 #ifdef HAVE_PYTHON
   EXTENSION(PyObject *scan_directory() const);
   EXTENSION(PyObject *scan_directory() const);
-#endif
+#endif // HAVE_PYTHON
 
 
   bool open_read(std::ifstream &stream) const;
   bool open_read(std::ifstream &stream) const;
   bool open_write(std::ofstream &stream, bool truncate = true) const;
   bool open_write(std::ofstream &stream, bool truncate = true) const;
@@ -265,15 +268,15 @@ protected:
   int _flags;
   int _flags;
 
 
   static TextEncoder::Encoding _filesystem_encoding;
   static TextEncoder::Encoding _filesystem_encoding;
-  static TVOLATILE AtomicAdjust::Pointer _home_directory;
-  static TVOLATILE AtomicAdjust::Pointer _temp_directory;
-  static TVOLATILE AtomicAdjust::Pointer _user_appdata_directory;
-  static TVOLATILE AtomicAdjust::Pointer _common_appdata_directory;
+  static patomic<Filename *> _home_directory;
+  static patomic<Filename *> _temp_directory;
+  static patomic<Filename *> _user_appdata_directory;
+  static patomic<Filename *> _common_appdata_directory;
 
 
 #ifdef ANDROID
 #ifdef ANDROID
 public:
 public:
   static std::string _internal_data_dir;
   static std::string _internal_data_dir;
-#endif
+#endif // ANDROID
 
 
 public:
 public:
   static TypeHandle get_class_type() {
   static TypeHandle get_class_type() {
@@ -294,4 +297,4 @@ INLINE std::ostream &operator << (std::ostream &out, const Filename &n) {
 
 
 #include "filename.I"
 #include "filename.I"
 
 
-#endif
+#endif // !FILENAME_H

+ 10 - 10
dtool/src/dtoolutil/textEncoder.h

@@ -54,28 +54,28 @@ PUBLISHED:
   INLINE static Encoding get_default_encoding();
   INLINE static Encoding get_default_encoding();
   MAKE_PROPERTY(default_encoding, get_default_encoding, set_default_encoding);
   MAKE_PROPERTY(default_encoding, get_default_encoding, set_default_encoding);
 
 
-#ifdef CPPPARSER
+#if defined(CPPPARSER) && defined(HAVE_PYTHON)
   EXTEND void set_text(PyObject *text);
   EXTEND void set_text(PyObject *text);
   EXTEND void set_text(PyObject *text, Encoding encoding);
   EXTEND void set_text(PyObject *text, Encoding encoding);
-#else
+#else // CPPPARSER && HAVE_PYTHON
   INLINE void set_text(const std::string &text);
   INLINE void set_text(const std::string &text);
   INLINE void set_text(const std::string &text, Encoding encoding);
   INLINE void set_text(const std::string &text, Encoding encoding);
-#endif
+#endif // CPPPARSER && HAVE_PYTHON
   INLINE void clear_text();
   INLINE void clear_text();
   INLINE bool has_text() const;
   INLINE bool has_text() const;
 
 
   void make_upper();
   void make_upper();
   void make_lower();
   void make_lower();
 
 
-#ifdef CPPPARSER
+#if defined(CPPPARSER) && defined(HAVE_PYTHON)
   EXTEND PyObject *get_text() const;
   EXTEND PyObject *get_text() const;
   EXTEND PyObject *get_text(Encoding encoding) const;
   EXTEND PyObject *get_text(Encoding encoding) const;
   EXTEND void append_text(PyObject *text);
   EXTEND void append_text(PyObject *text);
-#else
+#else // CPPPARSER && HAVE_PYTHON
   INLINE std::string get_text() const;
   INLINE std::string get_text() const;
   INLINE std::string get_text(Encoding encoding) const;
   INLINE std::string get_text(Encoding encoding) const;
   INLINE void append_text(const std::string &text);
   INLINE void append_text(const std::string &text);
-#endif
+#endif // CPPPARSER && HAVE_PYTHON
   INLINE void append_unicode_char(char32_t character);
   INLINE void append_unicode_char(char32_t character);
   INLINE size_t get_num_chars() const;
   INLINE size_t get_num_chars() const;
   INLINE int get_unicode_char(size_t index) const;
   INLINE int get_unicode_char(size_t index) const;
@@ -108,19 +108,19 @@ PUBLISHED:
   std::wstring get_wtext_as_ascii() const;
   std::wstring get_wtext_as_ascii() const;
   bool is_wtext() const;
   bool is_wtext() const;
 
 
-#ifdef CPPPARSER
+#if defined(CPPPARSER) && defined(HAVE_PYTHON)
   EXTEND static PyObject *encode_wchar(char32_t ch, Encoding encoding);
   EXTEND static PyObject *encode_wchar(char32_t ch, Encoding encoding);
   EXTEND INLINE PyObject *encode_wtext(const std::wstring &wtext) const;
   EXTEND INLINE PyObject *encode_wtext(const std::wstring &wtext) const;
   EXTEND static PyObject *encode_wtext(const std::wstring &wtext, Encoding encoding);
   EXTEND static PyObject *encode_wtext(const std::wstring &wtext, Encoding encoding);
   EXTEND INLINE PyObject *decode_text(PyObject *text) const;
   EXTEND INLINE PyObject *decode_text(PyObject *text) const;
   EXTEND static PyObject *decode_text(PyObject *text, Encoding encoding);
   EXTEND static PyObject *decode_text(PyObject *text, Encoding encoding);
-#else
+#else // CPPPARSER && HAVE_PYTHON
   static std::string encode_wchar(char32_t ch, Encoding encoding);
   static std::string encode_wchar(char32_t ch, Encoding encoding);
   INLINE std::string encode_wtext(const std::wstring &wtext) const;
   INLINE std::string encode_wtext(const std::wstring &wtext) const;
   static std::string encode_wtext(const std::wstring &wtext, Encoding encoding);
   static std::string encode_wtext(const std::wstring &wtext, Encoding encoding);
   INLINE std::wstring decode_text(const std::string &text) const;
   INLINE std::wstring decode_text(const std::string &text) const;
   static std::wstring decode_text(const std::string &text, Encoding encoding);
   static std::wstring decode_text(const std::string &text, Encoding encoding);
-#endif
+#endif // CPPPARSER && HAVE_PYTHON
 
 
   MAKE_PROPERTY(text, get_text, set_text);
   MAKE_PROPERTY(text, get_text, set_text);
 
 
@@ -156,4 +156,4 @@ INLINE std::ostream & operator << (std::ostream &out, const std::wstring &str);
 
 
 #include "textEncoder.I"
 #include "textEncoder.I"
 
 
-#endif
+#endif // !TEXTENCODER_H

+ 7 - 2
dtool/src/interrogate/functionRemap.cxx

@@ -872,8 +872,13 @@ setup_properties(const InterrogateFunction &ifunc, InterfaceMaker *interface_mak
     } else if (fname == "make") {
     } else if (fname == "make") {
       if (!_has_this && _parameters.size() >= 1 &&
       if (!_has_this && _parameters.size() >= 1 &&
           TypeManager::is_pointer(_return_type->get_new_type())) {
           TypeManager::is_pointer(_return_type->get_new_type())) {
-        // We can use this for coercion.
-        _flags |= F_coerce_constructor;
+        // We can use this for coercion, except if this is a kwargs param that
+        // does not have a default value.
+        if ((_flags & F_explicit_args) == 0 ||
+            _parameters.size() != first_param + 2 ||
+            _parameters[first_param + 1]._remap->has_default_value()) {
+          _flags |= F_coerce_constructor;
+        }
       }
       }
 
 
       if (_args_type == InterfaceMaker::AT_varargs) {
       if (_args_type == InterfaceMaker::AT_varargs) {

+ 25 - 10
dtool/src/interrogate/interfaceMakerPythonNative.cxx

@@ -1221,22 +1221,33 @@ write_class_details(ostream &out, Object *obj) {
     out << "  return nullptr;\n";
     out << "  return nullptr;\n";
     out << "}\n\n";
     out << "}\n\n";
 
 
-    out << "static void *Dtool_DowncastInterface_" << ClassName << "(void *from_this, Dtool_PyTypedObject *from_type) {\n";
+    out << "static Dtool_PyInstDef *Dtool_Wrap_" << ClassName << "(void *from_this, Dtool_PyTypedObject *from_type) {\n";
     out << "  if (from_this == nullptr || from_type == nullptr) {\n";
     out << "  if (from_this == nullptr || from_type == nullptr) {\n";
     out << "    return nullptr;\n";
     out << "    return nullptr;\n";
     out << "  }\n";
     out << "  }\n";
-    out << "  if (from_type == Dtool_Ptr_" << ClassName << ") {\n";
-    out << "    return from_this;\n";
+    out << "  " << cClassName << " *to_this;\n";
+    out << "  if (from_type == &Dtool_" << ClassName << ") {\n";
+    out << "    to_this = (" << cClassName << "*)from_this;\n";
     out << "  }\n";
     out << "  }\n";
     for (di = details.begin(); di != details.end(); di++) {
     for (di = details.begin(); di != details.end(); di++) {
       if (di->second._can_downcast && di->second._is_legal_py_class) {
       if (di->second._can_downcast && di->second._is_legal_py_class) {
-        out << "  if (from_type == Dtool_Ptr_" << make_safe_name(di->second._to_class_name) << ") {\n";
+        out << "  else if (from_type == Dtool_Ptr_" << make_safe_name(di->second._to_class_name) << ") {\n";
         out << "    " << di->second._to_class_name << "* other_this = (" << di->second._to_class_name << "*)from_this;\n" ;
         out << "    " << di->second._to_class_name << "* other_this = (" << di->second._to_class_name << "*)from_this;\n" ;
-        out << "    return (" << cClassName << "*)other_this;\n";
+        out << "    to_this = (" << cClassName << "*)other_this;\n";
         out << "  }\n";
         out << "  }\n";
       }
       }
     }
     }
-    out << "  return nullptr;\n";
+    out << "  else {\n";
+    out << "    return nullptr;\n";
+    out << "  }\n";
+    out << "  // Allocate a new Python instance\n";
+    out << "  Dtool_PyInstDef *self = (Dtool_PyInstDef *)PyType_GenericAlloc(&Dtool_" << ClassName << "._PyType, 0);\n";
+    out << "  self->_signature = PY_PANDA_SIGNATURE;\n";
+    out << "  self->_My_Type = &Dtool_" << ClassName << ";\n";
+    out << "  self->_ptr_to_object = to_this;\n";
+    out << "  self->_memory_rules = false;\n";
+    out << "  self->_is_const = false;\n";
+    out << "  return self;\n";
     out << "}\n\n";
     out << "}\n\n";
   }
   }
 }
 }
@@ -3251,7 +3262,7 @@ write_module_class(ostream &out, Object *obj) {
   out << "  TypeHandle::none(),\n";
   out << "  TypeHandle::none(),\n";
   out << "  Dtool_PyModuleClassInit_" << ClassName << ",\n";
   out << "  Dtool_PyModuleClassInit_" << ClassName << ",\n";
   out << "  Dtool_UpcastInterface_" << ClassName << ",\n";
   out << "  Dtool_UpcastInterface_" << ClassName << ",\n";
-  out << "  Dtool_DowncastInterface_" << ClassName << ",\n";
+  out << "  Dtool_Wrap_" << ClassName << ",\n";
 
 
   int has_coerce = has_coerce_constructor(obj->_itype._cpptype->as_struct_type());
   int has_coerce = has_coerce_constructor(obj->_itype._cpptype->as_struct_type());
   if (has_coerce > 0) {
   if (has_coerce > 0) {
@@ -4819,9 +4830,13 @@ write_function_instance(ostream &out, FunctionRemap *remap,
     // The function handles the arguments by itself.
     // The function handles the arguments by itself.
     expected_params += "*args";
     expected_params += "*args";
     pexprs.push_back("args");
     pexprs.push_back("args");
-    if (args_type == AT_keyword_args) {
-      expected_params += ", **kwargs";
-      pexprs.push_back("kwds");
+    if (remap->_args_type == AT_keyword_args) {
+      if (args_type == AT_keyword_args) {
+        expected_params += ", **kwargs";
+        pexprs.push_back("kwds");
+      } else {
+        pexprs.push_back("nullptr");
+      }
     }
     }
     num_params = 0;
     num_params = 0;
   }
   }

+ 1 - 1
dtool/src/interrogate/interrogate.cxx

@@ -513,7 +513,7 @@ main(int argc, char **argv) {
   for (i = 1; i < argc; ++i) {
   for (i = 1; i < argc; ++i) {
     Filename filename = Filename::from_os_specific(argv[i]);
     Filename filename = Filename::from_os_specific(argv[i]);
     if (!parser.parse_file(filename)) {
     if (!parser.parse_file(filename)) {
-      cerr << "Error parsing file: '" << argv[i] << "'\n";
+      cerr << "interrogate failed to parse file: '" << argv[i] << "'\n";
       exit(1);
       exit(1);
     }
     }
     builder.add_source_file(filename.to_os_generic());
     builder.add_source_file(filename.to_os_generic());

+ 3 - 3
dtool/src/interrogatedb/dtool_super_base.cxx

@@ -39,11 +39,11 @@ static void Dtool_PyModuleClassInit_DTOOL_SUPER_BASE(PyObject *module) {
   }
   }
 }
 }
 
 
-static void *Dtool_DowncastInterface_DTOOL_SUPER_BASE(void *from_this, Dtool_PyTypedObject *from_type) {
+static void *Dtool_UpcastInterface_DTOOL_SUPER_BASE(PyObject *self, Dtool_PyTypedObject *requested_type) {
   return nullptr;
   return nullptr;
 }
 }
 
 
-static void *Dtool_UpcastInterface_DTOOL_SUPER_BASE(PyObject *self, Dtool_PyTypedObject *requested_type) {
+static Dtool_PyInstDef *Dtool_Wrap_DTOOL_SUPER_BASE(void *from_this, Dtool_PyTypedObject *from_type) {
   return nullptr;
   return nullptr;
 }
 }
 
 
@@ -140,7 +140,7 @@ Dtool_PyTypedObject *Dtool_GetSuperBase() {
     TypeHandle::none(),
     TypeHandle::none(),
     Dtool_PyModuleClassInit_DTOOL_SUPER_BASE,
     Dtool_PyModuleClassInit_DTOOL_SUPER_BASE,
     Dtool_UpcastInterface_DTOOL_SUPER_BASE,
     Dtool_UpcastInterface_DTOOL_SUPER_BASE,
-    Dtool_DowncastInterface_DTOOL_SUPER_BASE,
+    Dtool_Wrap_DTOOL_SUPER_BASE,
     nullptr,
     nullptr,
     nullptr,
     nullptr,
   };
   };

+ 4 - 0
dtool/src/interrogatedb/py_compat.h

@@ -211,6 +211,10 @@ INLINE PyObject *_PyLong_Lshift(PyObject *a, size_t shiftby) {
 
 
 /* Python 3.9 */
 /* Python 3.9 */
 
 
+#ifndef PyCFunction_CheckExact
+#  define PyCFunction_CheckExact(op) (Py_TYPE(op) == &PyCFunction_Type)
+#endif
+
 #if PY_VERSION_HEX < 0x03090000
 #if PY_VERSION_HEX < 0x03090000
 INLINE PyObject *PyObject_CallNoArgs(PyObject *func) {
 INLINE PyObject *PyObject_CallNoArgs(PyObject *func) {
 #if PY_VERSION_HEX >= 0x03080000
 #if PY_VERSION_HEX >= 0x03080000

+ 12 - 18
dtool/src/interrogatedb/py_panda.cxx

@@ -458,31 +458,24 @@ PyObject *DTool_CreatePyInstanceTyped(void *local_this_in, Dtool_PyTypedObject &
     Dtool_PyTypedObject *target_class = (Dtool_PyTypedObject *)TypeHandle::from_index(type_index).get_python_type();
     Dtool_PyTypedObject *target_class = (Dtool_PyTypedObject *)TypeHandle::from_index(type_index).get_python_type();
     if (target_class != nullptr) {
     if (target_class != nullptr) {
       // cast to the type...
       // cast to the type...
-      void *new_local_this = target_class->_Dtool_DowncastInterface(local_this_in, &known_class_type);
-      if (new_local_this != nullptr) {
-        // ask class to allocate an instance..
-        Dtool_PyInstDef *self = (Dtool_PyInstDef *) target_class->_PyType.tp_new(&target_class->_PyType, nullptr, nullptr);
-        if (self != nullptr) {
-          self->_ptr_to_object = new_local_this;
-          self->_memory_rules = memory_rules;
-          self->_is_const = is_const;
-          // self->_signature = PY_PANDA_SIGNATURE;
-          self->_My_Type = target_class;
-          return (PyObject *)self;
-        }
+      Dtool_PyInstDef *self = target_class->_Dtool_WrapInterface(local_this_in, &known_class_type);
+      if (self != nullptr) {
+        self->_memory_rules = memory_rules;
+        self->_is_const = is_const;
+        return (PyObject *)self;
       }
       }
     }
     }
   }
   }
 
 
   // if we get this far .. just wrap the thing in the known type ?? better
   // if we get this far .. just wrap the thing in the known type ?? better
   // than aborting...I guess....
   // than aborting...I guess....
-  Dtool_PyInstDef *self = (Dtool_PyInstDef *) known_class_type._PyType.tp_new(&known_class_type._PyType, nullptr, nullptr);
+  Dtool_PyInstDef *self = (Dtool_PyInstDef *)PyType_GenericAlloc(&known_class_type._PyType, 0);
   if (self != nullptr) {
   if (self != nullptr) {
+    self->_signature = PY_PANDA_SIGNATURE;
+    self->_My_Type = &known_class_type;
     self->_ptr_to_object = local_this_in;
     self->_ptr_to_object = local_this_in;
     self->_memory_rules = memory_rules;
     self->_memory_rules = memory_rules;
     self->_is_const = is_const;
     self->_is_const = is_const;
-    // self->_signature = PY_PANDA_SIGNATURE;
-    self->_My_Type = &known_class_type;
   }
   }
   return (PyObject *)self;
   return (PyObject *)self;
 }
 }
@@ -497,13 +490,14 @@ PyObject *DTool_CreatePyInstance(void *local_this, Dtool_PyTypedObject &in_class
     return Py_None;
     return Py_None;
   }
   }
 
 
-  Dtool_PyTypedObject *classdef = &in_classdef;
-  Dtool_PyInstDef *self = (Dtool_PyInstDef *) classdef->_PyType.tp_new(&classdef->_PyType, nullptr, nullptr);
+  Dtool_PyInstDef *self = (Dtool_PyInstDef *)PyType_GenericAlloc(&in_classdef._PyType, 0);
   if (self != nullptr) {
   if (self != nullptr) {
+    self->_signature = PY_PANDA_SIGNATURE;
+    self->_My_Type = &in_classdef;
     self->_ptr_to_object = local_this;
     self->_ptr_to_object = local_this;
     self->_memory_rules = memory_rules;
     self->_memory_rules = memory_rules;
     self->_is_const = is_const;
     self->_is_const = is_const;
-    self->_My_Type = classdef;
+    self->_My_Type = &in_classdef;
   }
   }
   return (PyObject *)self;
   return (PyObject *)self;
 }
 }

+ 3 - 2
dtool/src/interrogatedb/py_panda.h

@@ -35,12 +35,13 @@ using namespace std;
 #define IMPORT_THIS extern
 #define IMPORT_THIS extern
 #endif
 #endif
 
 
+struct Dtool_PyInstDef;
 struct Dtool_PyTypedObject;
 struct Dtool_PyTypedObject;
 
 
 // used to stamp dtool instance..
 // used to stamp dtool instance..
 #define PY_PANDA_SIGNATURE 0xbeaf
 #define PY_PANDA_SIGNATURE 0xbeaf
 typedef void *(*UpcastFunction)(PyObject *,Dtool_PyTypedObject *);
 typedef void *(*UpcastFunction)(PyObject *,Dtool_PyTypedObject *);
-typedef void *(*DowncastFunction)(void *, Dtool_PyTypedObject *);
+typedef Dtool_PyInstDef *(*WrapFunction)(void *, Dtool_PyTypedObject *);
 typedef void *(*CoerceFunction)(PyObject *, void *);
 typedef void *(*CoerceFunction)(PyObject *, void *);
 typedef void (*ModuleClassInitFunction)(PyObject *module);
 typedef void (*ModuleClassInitFunction)(PyObject *module);
 
 
@@ -78,7 +79,7 @@ struct Dtool_PyTypedObject {
   ModuleClassInitFunction _Dtool_ModuleClassInit;
   ModuleClassInitFunction _Dtool_ModuleClassInit;
 
 
   UpcastFunction _Dtool_UpcastInterface;    // The Upcast Function By Slot
   UpcastFunction _Dtool_UpcastInterface;    // The Upcast Function By Slot
-  DowncastFunction _Dtool_DowncastInterface; // The Downcast Function By Slot
+  WrapFunction _Dtool_WrapInterface; // The Downcast Function By Slot
 
 
   CoerceFunction _Dtool_ConstCoerce;
   CoerceFunction _Dtool_ConstCoerce;
   CoerceFunction _Dtool_Coerce;
   CoerceFunction _Dtool_Coerce;

+ 3 - 0
dtool/src/parser-inc/Python.h

@@ -25,6 +25,9 @@ typedef _object PyObject;
 struct _typeobject;
 struct _typeobject;
 typedef _typeobject PyTypeObject;
 typedef _typeobject PyTypeObject;
 
 
+struct _frame;
+typedef _frame PyFrameObject;
+
 typedef struct {} PyStringObject;
 typedef struct {} PyStringObject;
 typedef struct {} PyUnicodeObject;
 typedef struct {} PyUnicodeObject;
 
 

+ 0 - 71
dtool/src/parser-inc/tinyxml.h

@@ -1,71 +0,0 @@
-#ifndef TINYXML_H
-#define TINYXML_H
-
-// A simple header to mirror the subset of the tinyxml interface we
-// wish to expose to interrogate.  This is intended to protect us from
-// having to run interrogate directly on the tinyxml.h header file.
-
-class TiXmlBase;
-class TiXmlNode;
-class TiXmlElement;
-class TiXmlDocument;
-
-class TiXmlBase {
-};
-
-
-class TiXmlNode : public TiXmlBase {
-public:
-  const char *Value() const;
-  void SetValue(const char *_value);
-
-  TiXmlNode *InsertEndChild(const TiXmlNode &addThis);
-  bool RemoveChild( TiXmlNode* removeThis );
-  
-  const TiXmlElement *NextSiblingElement() const;
-  TiXmlElement *NextSiblingElement();
-
-  const TiXmlElement* NextSiblingElement(const char *) const;
-  TiXmlElement* NextSiblingElement(const char *_next);
-
-  const TiXmlElement* FirstChildElement() const;
-  TiXmlElement* FirstChildElement();
-
-  const TiXmlElement* FirstChildElement( const char * _value ) const;
-  TiXmlElement* FirstChildElement( const char * _value );
-
-  virtual TiXmlNode* Clone() const;
-};
-
-
-class TiXmlElement : public TiXmlNode {
-public:
-  TiXmlElement(const char * in_value);
-  TiXmlElement( const TiXmlElement& );
-
-  const char* Attribute( const char* name ) const;
-  void SetAttribute( const char* name, const char * _value );
-  void RemoveAttribute( const char * name );
-};
-
-class TiXmlDeclaration : public TiXmlNode {
-public:
-  TiXmlDeclaration(const char* _version,
-                   const char* _encoding,
-                   const char* _standalone);
-};
-
-
-class TiXmlDocument : public TiXmlNode {
-public:
-  TiXmlDocument();
-  TiXmlDocument(const char * documentName);
-
-  bool LoadFile();
-  bool SaveFile() const;
-  bool LoadFile(const char * filename);
-  bool SaveFile(const char * filename) const;
-};
-
-
-#endif

+ 2 - 0
dtool/src/prc/CMakeLists.txt

@@ -76,6 +76,8 @@ endif()
 set(P3PRC_IGATEEXT
 set(P3PRC_IGATEEXT
   configVariable_ext.cxx
   configVariable_ext.cxx
   configVariable_ext.h
   configVariable_ext.h
+  notify_ext.cxx
+  notify_ext.h
   streamReader_ext.cxx
   streamReader_ext.cxx
   streamReader_ext.h
   streamReader_ext.h
   streamWriter_ext.cxx
   streamWriter_ext.cxx

+ 3 - 1
dtool/src/prc/configVariable.h

@@ -44,7 +44,9 @@ PUBLISHED:
 
 
   INLINE size_t get_num_words() const;
   INLINE size_t get_num_words() const;
 
 
+#ifdef HAVE_PYTHON
   EXTENSION(PyObject *__reduce__(PyObject *self) const);
   EXTENSION(PyObject *__reduce__(PyObject *self) const);
+#endif // HAVE_PYTHON
 
 
 protected:
 protected:
   INLINE const ConfigDeclaration *get_default_value() const;
   INLINE const ConfigDeclaration *get_default_value() const;
@@ -74,4 +76,4 @@ protected:
 
 
 #include "configVariable.I"
 #include "configVariable.I"
 
 
-#endif
+#endif // !CONFIGVARIABLE_H

+ 11 - 0
dtool/src/prc/configVariableFilename.I

@@ -226,6 +226,17 @@ __bool__() const {
   return !get_value().empty();
   return !get_value().empty();
 }
 }
 
 
+/**
+ * Allows a ConfigVariableFilename object to be passed to any Python function
+ * that accepts an os.PathLike object.
+ *
+ * @since 1.10.13
+ */
+INLINE std::wstring ConfigVariableFilename::
+__fspath__() const {
+  return get_ref_value().to_os_specific_w();
+}
+
 /**
 /**
  * Returns the variable's value, as a reference into the config variable
  * Returns the variable's value, as a reference into the config variable
  * itself.  This is the internal method that implements get_value(), which
  * itself.  This is the internal method that implements get_value(), which

+ 1 - 0
dtool/src/prc/configVariableFilename.h

@@ -61,6 +61,7 @@ PUBLISHED:
   INLINE void set_word(size_t n, const Filename &value);
   INLINE void set_word(size_t n, const Filename &value);
 
 
   INLINE bool __bool__() const;
   INLINE bool __bool__() const;
+  INLINE std::wstring __fspath__() const;
 
 
 private:
 private:
   void reload_cache();
   void reload_cache();

+ 73 - 2
dtool/src/prc/encryptStreamBuf.cxx

@@ -23,6 +23,32 @@
 #include <openssl/rand.h>
 #include <openssl/rand.h>
 #include <openssl/evp.h>
 #include <openssl/evp.h>
 
 
+#if OPENSSL_VERSION_MAJOR >= 3
+#include <openssl/provider.h>
+
+/**
+ * Tries to load the legacy provider in OpenSSL.  Returns true if the provider
+ * was just loaded, false if it was already loaded or couldn't be loaded.
+ */
+static bool load_legacy_provider() {
+  static bool tried = false;
+  if (!tried) {
+    tried = true;
+    if (OSSL_PROVIDER_try_load(nullptr, "legacy", 1) != nullptr) {
+      if (prc_cat.is_debug()) {
+        prc_cat.debug()
+          << "Loaded legacy OpenSSL provider.\n";
+      }
+      return true;
+    } else {
+      prc_cat.warning()
+        << "Failed to load legacy OpenSSL provider.\n";
+    }
+  }
+  return false;
+}
+#endif  // OPENSSL_VERSION_MAJOR
+
 // The iteration count is scaled by this factor for writing to the stream.
 // The iteration count is scaled by this factor for writing to the stream.
 static const int iteration_count_factor = 1000;
 static const int iteration_count_factor = 1000;
 
 
@@ -37,10 +63,10 @@ EncryptStreamBuf() {
   _owns_dest = false;
   _owns_dest = false;
 
 
   ConfigVariableString encryption_algorithm
   ConfigVariableString encryption_algorithm
-    ("encryption-algorithm", "bf-cbc",
+    ("encryption-algorithm", "aes-256-cbc",
      PRC_DESC("This defines the OpenSSL encryption algorithm which is used to "
      PRC_DESC("This defines the OpenSSL encryption algorithm which is used to "
               "encrypt any streams created by the current runtime.  The default is "
               "encrypt any streams created by the current runtime.  The default is "
-              "Blowfish; the complete set of available algorithms is defined by "
+              "AES-256; the complete set of available algorithms is defined by "
               "the current version of OpenSSL.  This value is used only to control "
               "the current version of OpenSSL.  This value is used only to control "
               "encryption; the correct algorithm will automatically be selected on "
               "encryption; the correct algorithm will automatically be selected on "
               "decryption."));
               "decryption."));
@@ -122,7 +148,31 @@ open_read(std::istream *source, bool owns_source, const std::string &password) {
   int key_length = sr.get_uint16();
   int key_length = sr.get_uint16();
   int count = sr.get_uint16();
   int count = sr.get_uint16();
 
 
+#if OPENSSL_VERSION_MAJOR >= 3
+  // First, convert the cipher's nid to its full name.
+  const char *cipher_name = OBJ_nid2ln(nid);
+
+  const EVP_CIPHER *cipher = nullptr;
+  if (cipher_name != nullptr) {
+    // Now, fetch the cipher known by this name.
+    cipher = EVP_CIPHER_fetch(nullptr, cipher_name, nullptr);
+
+    if (cipher == nullptr && EVP_get_cipherbynid(nid) != nullptr) {
+      if (load_legacy_provider()) {
+        cipher = EVP_CIPHER_fetch(nullptr, cipher_name, nullptr);
+      }
+
+      if (cipher == nullptr) {
+        prc_cat.error()
+          << "No implementation available for encryption algorithm in stream: "
+          << cipher_name << "\n";
+        return;
+      }
+    }
+  }
+#else
   const EVP_CIPHER *cipher = EVP_get_cipherbynid(nid);
   const EVP_CIPHER *cipher = EVP_get_cipherbynid(nid);
+#endif
 
 
   if (cipher == nullptr) {
   if (cipher == nullptr) {
     prc_cat.error()
     prc_cat.error()
@@ -220,8 +270,29 @@ open_write(std::ostream *dest, bool owns_dest, const std::string &password) {
   _dest = dest;
   _dest = dest;
   _owns_dest = owns_dest;
   _owns_dest = owns_dest;
 
 
+#if OPENSSL_VERSION_MAJOR >= 3
+  // This checks that there is actually an implementation available.
+  const EVP_CIPHER *cipher =
+    EVP_CIPHER_fetch(nullptr, _algorithm.c_str(), nullptr);
+
+  if (cipher == nullptr &&
+      EVP_get_cipherbyname(_algorithm.c_str()) != nullptr) {
+    // The cipher does exist, though, do we need to load the legacy provider?
+    if (load_legacy_provider()) {
+      cipher = EVP_CIPHER_fetch(nullptr, _algorithm.c_str(), nullptr);
+    }
+
+    if (cipher == nullptr) {
+      prc_cat.error()
+        << "No implementation available for encryption algorithm: "
+        << _algorithm << "\n";
+      return;
+    }
+  }
+#else
   const EVP_CIPHER *cipher =
   const EVP_CIPHER *cipher =
     EVP_get_cipherbyname(_algorithm.c_str());
     EVP_get_cipherbyname(_algorithm.c_str());
+#endif
 
 
   if (cipher == nullptr) {
   if (cipher == nullptr) {
     prc_cat.error()
     prc_cat.error()

+ 48 - 0
dtool/src/prc/notify_ext.cxx

@@ -0,0 +1,48 @@
+/**
+ * PANDA 3D SOFTWARE
+ * Copyright (c) Carnegie Mellon University.  All rights reserved.
+ *
+ * All use of this software is subject to the terms of the revised BSD
+ * license.  You should have received a copy of this license along
+ * with this source code in a file named "LICENSE."
+ *
+ * @file notify_ext.cxx
+ * @author rdb
+ * @date 2022-12-09
+ */
+
+#include "notify_ext.h"
+
+#ifdef HAVE_PYTHON
+
+/**
+ * Changes the ostream that all subsequent Notify messages will be written to.
+ * If the previous ostream was set with delete_later = true, this will delete
+ * the previous ostream.  If ostream_ptr is None, this resets the default to
+ * cerr.
+ */
+void Extension<Notify>::
+set_ostream_ptr(PyObject *ostream_ptr, bool delete_later) {
+  extern struct Dtool_PyTypedObject Dtool_std_ostream;
+
+  if (ostream_ptr == Py_None) {
+    _this->set_ostream_ptr(nullptr, false);
+    return;
+  }
+
+  std::ostream *ptr = (std::ostream *)DTOOL_Call_GetPointerThisClass(ostream_ptr, &Dtool_std_ostream, 1, "Notify.set_ostream_ptr", false, true);
+  if (ptr == nullptr) {
+    return;
+  }
+
+  // Since we now have a reference to this class on the C++ end, make sure
+  // that the ostream isn't being destructed when its Python wrapper expires.
+  // Note that this may cause a memory leak if delete_later is not set, but
+  // since these pointers are usually set once for the rest of time, this is
+  // considered less of a problem than having the Python object destroy the
+  // object while C++ is still using it.  See GitHub #1371.
+  ((Dtool_PyInstDef *)ostream_ptr)->_memory_rules = false;
+  _this->set_ostream_ptr(ptr, delete_later);
+}
+
+#endif  // HAVE_PYTHON

+ 37 - 0
dtool/src/prc/notify_ext.h

@@ -0,0 +1,37 @@
+/**
+ * PANDA 3D SOFTWARE
+ * Copyright (c) Carnegie Mellon University.  All rights reserved.
+ *
+ * All use of this software is subject to the terms of the revised BSD
+ * license.  You should have received a copy of this license along
+ * with this source code in a file named "LICENSE."
+ *
+ * @file notify_ext.h
+ * @author rdb
+ * @date 2022-12-09
+ */
+
+#ifndef NOTIFY_EXT_H
+#define NOTIFY_EXT_H
+
+#include "dtoolbase.h"
+
+#ifdef HAVE_PYTHON
+
+#include "extension.h"
+#include "pnotify.h"
+#include "py_panda.h"
+
+/**
+ * This class defines the extension methods for Notify, which are called
+ * instead of any C++ methods with the same prototype.
+ */
+template<>
+class Extension<Notify> : public ExtensionBase<Notify> {
+public:
+  void set_ostream_ptr(PyObject *ostream_ptr, bool delete_later);
+};
+
+#endif  // HAVE_PYTHON
+
+#endif  // NOTIFY_EXT_H

+ 1 - 0
dtool/src/prc/p3prc_ext_composite.cxx

@@ -1,3 +1,4 @@
 #include "configVariable_ext.cxx"
 #include "configVariable_ext.cxx"
+#include "notify_ext.cxx"
 #include "streamReader_ext.cxx"
 #include "streamReader_ext.cxx"
 #include "streamWriter_ext.cxx"
 #include "streamWriter_ext.cxx"

+ 4 - 0
dtool/src/prc/pnotify.h

@@ -35,7 +35,11 @@ PUBLISHED:
   Notify();
   Notify();
   ~Notify();
   ~Notify();
 
 
+#if defined(CPPPARSER) && defined(HAVE_PYTHON)
+  EXTEND void set_ostream_ptr(PyObject *ostream_ptr, bool delete_later);
+#else
   void set_ostream_ptr(std::ostream *ostream_ptr, bool delete_later);
   void set_ostream_ptr(std::ostream *ostream_ptr, bool delete_later);
+#endif
   std::ostream *get_ostream_ptr() const;
   std::ostream *get_ostream_ptr() const;
 
 
   typedef bool AssertHandler(const char *expression, int line,
   typedef bool AssertHandler(const char *expression, int line,

+ 3 - 2
dtool/src/prc/streamReader.h

@@ -68,11 +68,12 @@ PUBLISHED:
 
 
   BLOCKING void skip_bytes(size_t size);
   BLOCKING void skip_bytes(size_t size);
   BLOCKING size_t extract_bytes(unsigned char *into, size_t size);
   BLOCKING size_t extract_bytes(unsigned char *into, size_t size);
+#ifdef HAVE_PYTHON
   EXTENSION(PyObject *extract_bytes(size_t size));
   EXTENSION(PyObject *extract_bytes(size_t size));
 
 
   EXTENSION(PyObject *readline());
   EXTENSION(PyObject *readline());
   EXTENSION(PyObject *readlines());
   EXTENSION(PyObject *readlines());
-
+#endif // HAVE_PYTHON
 public:
 public:
   BLOCKING vector_uchar extract_bytes(size_t size);
   BLOCKING vector_uchar extract_bytes(size_t size);
   BLOCKING std::string readline();
   BLOCKING std::string readline();
@@ -84,4 +85,4 @@ private:
 
 
 #include "streamReader.I"
 #include "streamReader.I"
 
 
-#endif
+#endif // !STREAMREADER_H

+ 3 - 1
dtool/src/prc/streamWriter.h

@@ -70,7 +70,9 @@ PUBLISHED:
   BLOCKING INLINE void add_fixed_string(const std::string &str, size_t size);
   BLOCKING INLINE void add_fixed_string(const std::string &str, size_t size);
 
 
   BLOCKING void pad_bytes(size_t size);
   BLOCKING void pad_bytes(size_t size);
+#ifdef HAVE_PYTHON
   EXTENSION(void append_data(PyObject *data));
   EXTENSION(void append_data(PyObject *data));
+#endif // HAVE_PYTHON
 
 
   BLOCKING INLINE void flush();
   BLOCKING INLINE void flush();
 
 
@@ -87,4 +89,4 @@ private:
 
 
 #include "streamWriter.I"
 #include "streamWriter.I"
 
 
-#endif
+#endif // !STREAMWRITER_H

+ 31 - 8
makepanda/installer.nsi

@@ -320,7 +320,6 @@ Section "Tools and utilities" SecTools
 
 
     SetOutPath "$INSTDIR\bin"
     SetOutPath "$INSTDIR\bin"
     File /r /x deploy-stub.exe /x deploy-stubw.exe "${BUILT}\bin\*.exe"
     File /r /x deploy-stub.exe /x deploy-stubw.exe "${BUILT}\bin\*.exe"
-    File /nonfatal /r "${BUILT}\bin\*.p3d"
     SetOutPath "$INSTDIR\NSIS"
     SetOutPath "$INSTDIR\NSIS"
     File /r /x CVS "${NSISDIR}\*"
     File /r /x CVS "${NSISDIR}\*"
 
 
@@ -343,6 +342,13 @@ Section "Tools and utilities" SecTools
     WriteRegStr HKCU "Software\Classes\Panda3D.Multifile\shell" "" "open"
     WriteRegStr HKCU "Software\Classes\Panda3D.Multifile\shell" "" "open"
     WriteRegStr HKCU "Software\Classes\Panda3D.Multifile\shell\extract" "" "Extract here"
     WriteRegStr HKCU "Software\Classes\Panda3D.Multifile\shell\extract" "" "Extract here"
     WriteRegStr HKCU "Software\Classes\Panda3D.Multifile\shell\extract\command" "" '"$INSTDIR\bin\multify.exe" -xf "%1"'
     WriteRegStr HKCU "Software\Classes\Panda3D.Multifile\shell\extract\command" "" '"$INSTDIR\bin\multify.exe" -xf "%1"'
+
+    IfFileExists "$INSTDIR\bin\pstats.exe" 0 SkipPStatsFileAssociation
+    WriteRegStr HKCU "Software\Classes\Panda3D.PStatsSession" "" "PStats Session"
+    WriteRegStr HKCU "Software\Classes\Panda3D.PStatsSession\DefaultIcon" "" "%SystemRoot%\system32\imageres.dll,144"
+    WriteRegStr HKCU "Software\Classes\Panda3D.PStatsSession\shell" "" "open"
+    WriteRegStr HKCU "Software\Classes\Panda3D.PStatsSession\shell\open\command" "" '"$INSTDIR\bin\pstats.exe" "%1"'
+    SkipPStatsFileAssociation:
 SectionEnd
 SectionEnd
 
 
 SectionGroup "Python modules" SecGroupPython
 SectionGroup "Python modules" SecGroupPython
@@ -375,6 +381,7 @@ SectionGroup "Python modules" SecGroupPython
         !insertmacro PyBindingSection 3.9-32 .cp39-win32.pyd
         !insertmacro PyBindingSection 3.9-32 .cp39-win32.pyd
         !insertmacro PyBindingSection 3.10-32 .cp310-win32.pyd
         !insertmacro PyBindingSection 3.10-32 .cp310-win32.pyd
         !insertmacro PyBindingSection 3.11-32 .cp311-win32.pyd
         !insertmacro PyBindingSection 3.11-32 .cp311-win32.pyd
+        !insertmacro PyBindingSection 3.12-32 .cp312-win32.pyd
     !else
     !else
         !insertmacro PyBindingSection 3.5 .cp35-win_amd64.pyd
         !insertmacro PyBindingSection 3.5 .cp35-win_amd64.pyd
         !insertmacro PyBindingSection 3.6 .cp36-win_amd64.pyd
         !insertmacro PyBindingSection 3.6 .cp36-win_amd64.pyd
@@ -383,6 +390,7 @@ SectionGroup "Python modules" SecGroupPython
         !insertmacro PyBindingSection 3.9 .cp39-win_amd64.pyd
         !insertmacro PyBindingSection 3.9 .cp39-win_amd64.pyd
         !insertmacro PyBindingSection 3.10 .cp310-win_amd64.pyd
         !insertmacro PyBindingSection 3.10 .cp310-win_amd64.pyd
         !insertmacro PyBindingSection 3.11 .cp311-win_amd64.pyd
         !insertmacro PyBindingSection 3.11 .cp311-win_amd64.pyd
+        !insertmacro PyBindingSection 3.12 .cp312-win_amd64.pyd
     !endif
     !endif
 SectionGroupEnd
 SectionGroupEnd
 
 
@@ -492,6 +500,7 @@ Function .onInit
         !insertmacro MaybeEnablePyBindingSection 3.9-32
         !insertmacro MaybeEnablePyBindingSection 3.9-32
         !insertmacro MaybeEnablePyBindingSection 3.10-32
         !insertmacro MaybeEnablePyBindingSection 3.10-32
         !insertmacro MaybeEnablePyBindingSection 3.11-32
         !insertmacro MaybeEnablePyBindingSection 3.11-32
+        !insertmacro MaybeEnablePyBindingSection 3.12-32
         ${EndIf}
         ${EndIf}
     !else
     !else
         !insertmacro MaybeEnablePyBindingSection 3.5
         !insertmacro MaybeEnablePyBindingSection 3.5
@@ -502,6 +511,7 @@ Function .onInit
         !insertmacro MaybeEnablePyBindingSection 3.9
         !insertmacro MaybeEnablePyBindingSection 3.9
         !insertmacro MaybeEnablePyBindingSection 3.10
         !insertmacro MaybeEnablePyBindingSection 3.10
         !insertmacro MaybeEnablePyBindingSection 3.11
         !insertmacro MaybeEnablePyBindingSection 3.11
+        !insertmacro MaybeEnablePyBindingSection 3.12
         ${EndIf}
         ${EndIf}
     !endif
     !endif
 
 
@@ -519,6 +529,10 @@ Function .onInit
         SectionSetFlags ${SecPyBindings3.11} ${SF_RO}
         SectionSetFlags ${SecPyBindings3.11} ${SF_RO}
         SectionSetInstTypes ${SecPyBindings3.11} 0
         SectionSetInstTypes ${SecPyBindings3.11} 0
     !endif
     !endif
+    !ifdef SecPyBindings3.12
+        SectionSetFlags ${SecPyBindings3.12} ${SF_RO}
+        SectionSetInstTypes ${SecPyBindings3.12} 0
+    !endif
     ${EndUnless}
     ${EndUnless}
 FunctionEnd
 FunctionEnd
 
 
@@ -632,6 +646,9 @@ Section "Sample programs" SecSamples
     WriteINIStr $INSTDIR\Manual.url "InternetShortcut" "URL" "https://docs.panda3d.org/${MAJOR_VER}"
     WriteINIStr $INSTDIR\Manual.url "InternetShortcut" "URL" "https://docs.panda3d.org/${MAJOR_VER}"
     WriteINIStr $INSTDIR\Samples.url "InternetShortcut" "URL" "https://docs.panda3d.org/${MAJOR_VER}/python/more-resources/samples/index"
     WriteINIStr $INSTDIR\Samples.url "InternetShortcut" "URL" "https://docs.panda3d.org/${MAJOR_VER}/python/more-resources/samples/index"
     SetOutPath $INSTDIR
     SetOutPath $INSTDIR
+    IfFileExists "$INSTDIR\bin\pstats.exe" 0 SkipPStatsShortcut
+    CreateShortCut "$SMPROGRAMS\${TITLE}\Panda3D Stats Monitor.lnk" "$INSTDIR\bin\pstats.exe" "" "%SystemRoot%\system32\imageres.dll" 144 "" "" "Panda3D Stats Monitor"
+    SkipPStatsShortcut:
     CreateShortCut "$SMPROGRAMS\${TITLE}\Panda3D Manual.lnk" "$INSTDIR\Manual.url" "" "$INSTDIR\pandaIcon.ico" 0 "" "" "Panda3D Manual"
     CreateShortCut "$SMPROGRAMS\${TITLE}\Panda3D Manual.lnk" "$INSTDIR\Manual.url" "" "$INSTDIR\pandaIcon.ico" 0 "" "" "Panda3D Manual"
     CreateShortCut "$SMPROGRAMS\${TITLE}\Panda3D Website.lnk" "$INSTDIR\Website.url" "" "$INSTDIR\pandaIcon.ico" 0 "" "" "Panda3D Website"
     CreateShortCut "$SMPROGRAMS\${TITLE}\Panda3D Website.lnk" "$INSTDIR\Website.url" "" "$INSTDIR\pandaIcon.ico" 0 "" "" "Panda3D Website"
     CreateShortCut "$SMPROGRAMS\${TITLE}\Sample Program Manual.lnk" "$INSTDIR\Samples.url" "" "$INSTDIR\pandaIcon.ico" 0 "" "" "Sample Program Manual"
     CreateShortCut "$SMPROGRAMS\${TITLE}\Sample Program Manual.lnk" "$INSTDIR\Samples.url" "" "$INSTDIR\pandaIcon.ico" 0 "" "" "Sample Program Manual"
@@ -732,13 +749,7 @@ Section -post
     DetailPrint "Registering file type associations..."
     DetailPrint "Registering file type associations..."
     SetDetailsPrint listonly
     SetDetailsPrint listonly
 
 
-    ; Even though we need the runtime to run these, we might as well tell
-    ; Windows what this kind of file is.
-    WriteRegStr HKCU "Software\Classes\.p3d" "" "Panda3D applet"
-    WriteRegStr HKCU "Software\Classes\.p3d" "Content Type" "application/x-panda3d"
-    WriteRegStr HKCU "Software\Classes\.p3d" "PerceivedType" "application"
-
-    ; Register various model files
+    ; Register various file extensions
     WriteRegStr HKCU "Software\Classes\.egg" "" "Panda3D.Model"
     WriteRegStr HKCU "Software\Classes\.egg" "" "Panda3D.Model"
     WriteRegStr HKCU "Software\Classes\.egg" "Content Type" "application/x-egg"
     WriteRegStr HKCU "Software\Classes\.egg" "Content Type" "application/x-egg"
     WriteRegStr HKCU "Software\Classes\.egg" "PerceivedType" "gamemedia"
     WriteRegStr HKCU "Software\Classes\.egg" "PerceivedType" "gamemedia"
@@ -752,6 +763,9 @@ Section -post
     WriteRegStr HKCU "Software\Classes\.prc" "" "inifile"
     WriteRegStr HKCU "Software\Classes\.prc" "" "inifile"
     WriteRegStr HKCU "Software\Classes\.prc" "Content Type" "text/plain"
     WriteRegStr HKCU "Software\Classes\.prc" "Content Type" "text/plain"
     WriteRegStr HKCU "Software\Classes\.prc" "PerceivedType" "text"
     WriteRegStr HKCU "Software\Classes\.prc" "PerceivedType" "text"
+    WriteRegStr HKCU "Software\Classes\.pstats" "" "Panda3D.PStatsSession"
+    WriteRegStr HKCU "Software\Classes\.pstats" "Content Type" "application/vnd.panda3d.pstats"
+    WriteRegStr HKCU "Software\Classes\.pstats" "PerceivedType" "application"
 
 
     ; For convenience, if nobody registered .pyd, we will.
     ; For convenience, if nobody registered .pyd, we will.
     ReadRegStr $0 HKCR "Software\Classes\.pyd" ""
     ReadRegStr $0 HKCR "Software\Classes\.pyd" ""
@@ -812,6 +826,11 @@ Section Uninstall
     DeleteRegKey HKCU "Software\Classes\Panda3D.Multifile\DefaultIcon"
     DeleteRegKey HKCU "Software\Classes\Panda3D.Multifile\DefaultIcon"
     DeleteRegKey HKCU "Software\Classes\Panda3D.Multifile\shell"
     DeleteRegKey HKCU "Software\Classes\Panda3D.Multifile\shell"
 
 
+    ReadRegStr $0 HKCU "Software\Classes\Panda3D.PStatsSession\DefaultIcon" ""
+    StrCmp $0 "$INSTDIR\bin\pstats.exe" 0 +3
+    DeleteRegKey HKCU "Software\Classes\Panda3D.PStatsSession\DefaultIcon"
+    DeleteRegKey HKCU "Software\Classes\Panda3D.PStatsSession\shell"
+
     !ifdef INCLUDE_PYVER
     !ifdef INCLUDE_PYVER
         ReadRegStr $0 HKLM "Software\Python\PythonCore\${INCLUDE_PYVER}\InstallPath" ""
         ReadRegStr $0 HKLM "Software\Python\PythonCore\${INCLUDE_PYVER}\InstallPath" ""
         StrCmp $0 "$INSTDIR\python" 0 +2
         StrCmp $0 "$INSTDIR\python" 0 +2
@@ -831,6 +850,7 @@ Section Uninstall
         !insertmacro RemovePythonPath 3.9-32
         !insertmacro RemovePythonPath 3.9-32
         !insertmacro RemovePythonPath 3.10-32
         !insertmacro RemovePythonPath 3.10-32
         !insertmacro RemovePythonPath 3.11-32
         !insertmacro RemovePythonPath 3.11-32
+        !insertmacro RemovePythonPath 3.12-32
     !else
     !else
         !insertmacro RemovePythonPath 3.5
         !insertmacro RemovePythonPath 3.5
         !insertmacro RemovePythonPath 3.6
         !insertmacro RemovePythonPath 3.6
@@ -839,6 +859,7 @@ Section Uninstall
         !insertmacro RemovePythonPath 3.9
         !insertmacro RemovePythonPath 3.9
         !insertmacro RemovePythonPath 3.10
         !insertmacro RemovePythonPath 3.10
         !insertmacro RemovePythonPath 3.11
         !insertmacro RemovePythonPath 3.11
+        !insertmacro RemovePythonPath 3.12
     !endif
     !endif
 
 
     SetDetailsPrint both
     SetDetailsPrint both
@@ -908,6 +929,7 @@ SectionEnd
     !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.9-32} $(DESC_SecPyBindings3.9-32)
     !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.9-32} $(DESC_SecPyBindings3.9-32)
     !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.10-32} $(DESC_SecPyBindings3.10-32)
     !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.10-32} $(DESC_SecPyBindings3.10-32)
     !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.11-32} $(DESC_SecPyBindings3.11-32)
     !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.11-32} $(DESC_SecPyBindings3.11-32)
+    !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.12-32} $(DESC_SecPyBindings3.12-32)
   !else
   !else
     !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.5} $(DESC_SecPyBindings3.5)
     !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.5} $(DESC_SecPyBindings3.5)
     !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.6} $(DESC_SecPyBindings3.6)
     !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.6} $(DESC_SecPyBindings3.6)
@@ -916,6 +938,7 @@ SectionEnd
     !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.9} $(DESC_SecPyBindings3.9)
     !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.9} $(DESC_SecPyBindings3.9)
     !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.10} $(DESC_SecPyBindings3.10)
     !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.10} $(DESC_SecPyBindings3.10)
     !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.11} $(DESC_SecPyBindings3.11)
     !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.11} $(DESC_SecPyBindings3.11)
+    !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.12} $(DESC_SecPyBindings3.12)
   !endif
   !endif
   !ifdef INCLUDE_PYVER
   !ifdef INCLUDE_PYVER
     !insertmacro MUI_DESCRIPTION_TEXT ${SecPython} $(DESC_SecPython)
     !insertmacro MUI_DESCRIPTION_TEXT ${SecPython} $(DESC_SecPython)

+ 52 - 24
makepanda/installpanda.py

@@ -18,29 +18,36 @@ from distutils.sysconfig import get_python_lib
 
 
 
 
 MIME_INFO = (
 MIME_INFO = (
-    ("egg", "model/x-egg", "EGG model file", "pview"),
-    ("bam", "model/x-bam", "Panda3D binary model file", "pview"),
-    ("egg.pz", "model/x-compressed-egg", "Compressed EGG model file", "pview"),
-    ("bam.pz", "model/x-compressed-bam", "Compressed Panda3D binary model file", "pview"),
+    # ext, mime, desc, app, magic
+    ("egg", "model/x-egg", "EGG model file", "pview", None),
+    ("bam", "model/x-bam", "Panda3D binary model file", "pview", None),
+    ("egg.pz", "model/x-compressed-egg", "Compressed EGG model file", "pview", None),
+    ("bam.pz", "model/x-compressed-bam", "Compressed Panda3D binary model file", "pview", None),
+    ("pstats", "application/vnd.panda3d.pstats", "PStats session file", "pstats", b"pstat\0\n\r"),
 )
 )
 
 
 APP_INFO = (
 APP_INFO = (
-  ("pview", "Panda3D Model Viewer", ("egg", "bam", "egg.pz", "bam.pz")),
+  ("pview", "Panda3D Model Viewer", ("egg", "bam", "egg.pz", "bam.pz"), True),
+  ("pstats", "Panda3D Profiling Tool", ("pstats",), False),
 )
 )
 
 
-def WriteApplicationsFile(fname, appinfo, mimeinfo):
+def WriteApplicationsFile(fname, appinfo, mimeinfo, bindir):
     fhandle = open(fname, "w")
     fhandle = open(fname, "w")
-    for app, desc, exts in appinfo:
+    for app, desc, exts, multiple in appinfo:
+        if not os.path.isfile(os.path.join(bindir, app)):
+            continue
+
         fhandle.write("%s\n" % (app))
         fhandle.write("%s\n" % (app))
         fhandle.write("\tcommand=%s\n" % (app))
         fhandle.write("\tcommand=%s\n" % (app))
         fhandle.write("\tname=%s\n" % (desc))
         fhandle.write("\tname=%s\n" % (desc))
-        fhandle.write("\tcan_open_multiple_files=true\n")
+        fhandle.write("\tcan_open_multiple_files=%s\n" % ('true' if multiple else 'false'))
+        fhandle.write("\tstartup_notify=true\n")
         fhandle.write("\texpects_uris=false\n")
         fhandle.write("\texpects_uris=false\n")
         fhandle.write("\trequires_terminal=false\n")
         fhandle.write("\trequires_terminal=false\n")
         fhandle.write("\tmime_types=")
         fhandle.write("\tmime_types=")
         first = True
         first = True
-        for ext, mime, desc2, app2 in mimeinfo:
-            if ext in exts:
+        for ext, mime, desc2, app2, magic in mimeinfo:
+            if app == app2 and ext in exts:
                 if first:
                 if first:
                     fhandle.write(mime)
                     fhandle.write(mime)
                     first = False
                     first = False
@@ -50,37 +57,53 @@ def WriteApplicationsFile(fname, appinfo, mimeinfo):
     fhandle.close()
     fhandle.close()
 
 
 
 
-def WriteMimeXMLFile(fname, info):
+def WriteMimeXMLFile(fname, info, bindir):
     fhandle = open(fname, "w")
     fhandle = open(fname, "w")
     fhandle.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n")
     fhandle.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n")
     fhandle.write("<mime-info xmlns=\"http://www.freedesktop.org/standards/shared-mime-info\">\n")
     fhandle.write("<mime-info xmlns=\"http://www.freedesktop.org/standards/shared-mime-info\">\n")
-    for ext, mime, desc, app in info:
+    for ext, mime, desc, app, magic in info:
+        if not os.path.isfile(os.path.join(bindir, app)):
+            continue
+
         fhandle.write("\t<mime-type type=\"%s\">\n" % (mime))
         fhandle.write("\t<mime-type type=\"%s\">\n" % (mime))
         fhandle.write("\t\t<comment xml:lang=\"en\">%s</comment>\n" % (desc))
         fhandle.write("\t\t<comment xml:lang=\"en\">%s</comment>\n" % (desc))
+        if app == "pstats":
+            fhandle.write("\t\t<generic-icon name=\"x-office-spreadsheet\"/>\n")
+        if magic:
+            magic = magic.decode('latin-1').encode('unicode-escape').decode('latin-1')
+            fhandle.write("\t\t<magic>\n\t\t\t<match type=\"string\" offset=\"0\" value=\"%s\"/>\n\t\t</magic>\n" % (magic))
         fhandle.write("\t\t<glob pattern=\"*.%s\"/>\n" % (ext))
         fhandle.write("\t\t<glob pattern=\"*.%s\"/>\n" % (ext))
         fhandle.write("\t</mime-type>\n")
         fhandle.write("\t</mime-type>\n")
     fhandle.write("</mime-info>\n")
     fhandle.write("</mime-info>\n")
     fhandle.close()
     fhandle.close()
 
 
 
 
-def WriteMimeFile(fname, info):
+def WriteMimeFile(fname, info, bindir):
     fhandle = open(fname, "w")
     fhandle = open(fname, "w")
-    for ext, mime, desc, app in info:
-        fhandle.write("%s:\n" % (mime))
+    for ext, mime, desc, app, magic in info:
+        if not os.path.isfile(os.path.join(bindir, app)):
+            continue
+
+        fhandle.write("%s\n" % (mime))
         if "." in ext:
         if "." in ext:
-            fhandle.write("\tregex,2: %s$\n" % (ext.replace(".", "\\.")))
+            fhandle.write("\tregex,2: \\.%s$\n" % (ext.replace(".", "\\.")))
         fhandle.write("\text: %s\n" % (ext))
         fhandle.write("\text: %s\n" % (ext))
         fhandle.write("\n")
         fhandle.write("\n")
     fhandle.close()
     fhandle.close()
 
 
 
 
-def WriteKeysFile(fname, info):
+def WriteKeysFile(fname, info, bindir):
     fhandle = open(fname, "w")
     fhandle = open(fname, "w")
-    for ext, mime, desc, app in info:
-        fhandle.write("%s:\n" % (mime))
+    for ext, mime, desc, app, magic in info:
+        if not os.path.isfile(os.path.join(bindir, app)):
+            continue
+
+        fhandle.write("%s\n" % (mime))
         fhandle.write("\tdescription=%s\n" % (desc))
         fhandle.write("\tdescription=%s\n" % (desc))
         fhandle.write("\tdefault_action_type=application\n")
         fhandle.write("\tdefault_action_type=application\n")
         fhandle.write("\tshort_list_application_ids_for_novice_user_level=%s\n" % (app))
         fhandle.write("\tshort_list_application_ids_for_novice_user_level=%s\n" % (app))
+        fhandle.write("\tshort_list_application_ids_for_intermediate_user_level=%s\n" % (app))
+        fhandle.write("\tshort_list_application_ids_for_advanced_user_level=%s\n" % (app))
         fhandle.write("\topen=%s %%f\n" % (app))
         fhandle.write("\topen=%s %%f\n" % (app))
         fhandle.write("\tview=%s %%f\n" % (app))
         fhandle.write("\tview=%s %%f\n" % (app))
         fhandle.write("\n")
         fhandle.write("\n")
@@ -215,12 +238,15 @@ def InstallPanda(destdir="", prefix="/usr", outputdir="built", libdir=GetLibDir(
             if base.endswith(".py") or (base.endswith(suffix) and '.' not in base[:-len(suffix)]):
             if base.endswith(".py") or (base.endswith(suffix) and '.' not in base[:-len(suffix)]):
                 oscmd(f"cp {outputdir}/panda3d/{base} {destdir}{platlib}/panda3d/{base}")
                 oscmd(f"cp {outputdir}/panda3d/{base} {destdir}{platlib}/panda3d/{base}")
 
 
-    WriteMimeFile(dest_prefix + "/share/mime-info/panda3d.mime", MIME_INFO)
-    WriteKeysFile(dest_prefix + "/share/mime-info/panda3d.keys", MIME_INFO)
-    WriteMimeXMLFile(dest_prefix + "/share/mime/packages/panda3d.xml", MIME_INFO)
-    WriteApplicationsFile(dest_prefix + "/share/application-registry/panda3d.applications", APP_INFO, MIME_INFO)
+    bindir = outputdir + "/bin"
+    WriteMimeFile(dest_prefix + "/share/mime-info/panda3d.mime", MIME_INFO, bindir)
+    WriteKeysFile(dest_prefix + "/share/mime-info/panda3d.keys", MIME_INFO, bindir)
+    WriteMimeXMLFile(dest_prefix + "/share/mime/packages/panda3d.xml", MIME_INFO, bindir)
+    WriteApplicationsFile(dest_prefix + "/share/application-registry/panda3d.applications", APP_INFO, MIME_INFO, bindir)
     if os.path.isfile(outputdir + "/bin/pview"):
     if os.path.isfile(outputdir + "/bin/pview"):
         oscmd(f"cp makepanda/pview.desktop {dest_prefix}/share/applications/pview.desktop")
         oscmd(f"cp makepanda/pview.desktop {dest_prefix}/share/applications/pview.desktop")
+    if os.path.isfile(outputdir + "/bin/pstats"):
+        oscmd(f"cp makepanda/pstats.desktop {dest_prefix}/share/applications/pstats.desktop")
 
 
     oscmd(f"cp doc/ReleaseNotes {dest_prefix}/share/panda3d/ReleaseNotes")
     oscmd(f"cp doc/ReleaseNotes {dest_prefix}/share/panda3d/ReleaseNotes")
 
 
@@ -326,5 +352,7 @@ if __name__ == "__main__":
 
 
     if not destdir:
     if not destdir:
         warn_prefix = "%sNote:%s " % (GetColor("red"), GetColor())
         warn_prefix = "%sNote:%s " % (GetColor("red"), GetColor())
-        print(warn_prefix + "You may need to call this command to update the library cache:")
+        print(warn_prefix + "You may need to call these commands to update system caches:")
         print("  sudo ldconfig")
         print("  sudo ldconfig")
+        print("  sudo update-desktop-database")
+        print("  sudo update-mime-database -n %s/share/mime" % (options.prefix))

+ 31 - 37
makepanda/makepanda.py

@@ -303,6 +303,8 @@ def parseopts(args):
         OSX_ARCHS.append("arm64")
         OSX_ARCHS.append("arm64")
     elif target_archs:
     elif target_archs:
         OSX_ARCHS = target_archs
         OSX_ARCHS = target_archs
+    elif GetTarget() == 'darwin':
+        OSX_ARCHS = (GetTargetArch(),)
 
 
     try:
     try:
         SetOptimize(int(optimize))
         SetOptimize(int(optimize))
@@ -393,8 +395,9 @@ MAJOR_VERSION = '.'.join(VERSION.split('.')[:2])
 
 
 # Now determine the distutils-style platform tag for the target system.
 # Now determine the distutils-style platform tag for the target system.
 target = GetTarget()
 target = GetTarget()
+target_arch = GetTargetArch()
 if target == 'windows':
 if target == 'windows':
-    if GetTargetArch() == 'x64':
+    if target_arch == 'x64':
         PLATFORM = 'win-amd64'
         PLATFORM = 'win-amd64'
     else:
     else:
         PLATFORM = 'win32'
         PLATFORM = 'win32'
@@ -402,7 +405,7 @@ if target == 'windows':
 elif target == 'darwin':
 elif target == 'darwin':
     arch_tag = None
     arch_tag = None
     if not OSX_ARCHS:
     if not OSX_ARCHS:
-        arch_tag = GetTargetArch()
+        arch_tag = target_arch
     elif len(OSX_ARCHS) == 1:
     elif len(OSX_ARCHS) == 1:
         arch_tag = OSX_ARCHS[0]
         arch_tag = OSX_ARCHS[0]
     elif frozenset(OSX_ARCHS) == frozenset(('i386', 'ppc')):
     elif frozenset(OSX_ARCHS) == frozenset(('i386', 'ppc')):
@@ -427,45 +430,53 @@ elif target == 'darwin':
 
 
 elif target == 'linux' and (os.path.isfile("/lib/libc-2.5.so") or os.path.isfile("/lib64/libc-2.5.so")) and os.path.isdir("/opt/python"):
 elif target == 'linux' and (os.path.isfile("/lib/libc-2.5.so") or os.path.isfile("/lib64/libc-2.5.so")) and os.path.isdir("/opt/python"):
     # This is manylinux1.  A bit of a sloppy check, though.
     # This is manylinux1.  A bit of a sloppy check, though.
-    if GetTargetArch() in ('x86_64', 'amd64'):
+    if target_arch in ('x86_64', 'amd64'):
         PLATFORM = 'manylinux1-x86_64'
         PLATFORM = 'manylinux1-x86_64'
-    elif GetTargetArch() in ('arm64', 'aarch64'):
+    elif target_arch in ('arm64', 'aarch64'):
         PLATFORM = 'manylinux1-aarch64'
         PLATFORM = 'manylinux1-aarch64'
     else:
     else:
         PLATFORM = 'manylinux1-i686'
         PLATFORM = 'manylinux1-i686'
 
 
 elif target == 'linux' and (os.path.isfile("/lib/libc-2.12.so") or os.path.isfile("/lib64/libc-2.12.so")) and os.path.isdir("/opt/python"):
 elif target == 'linux' and (os.path.isfile("/lib/libc-2.12.so") or os.path.isfile("/lib64/libc-2.12.so")) and os.path.isdir("/opt/python"):
     # Same sloppy check for manylinux2010.
     # Same sloppy check for manylinux2010.
-    if GetTargetArch() in ('x86_64', 'amd64'):
+    if target_arch in ('x86_64', 'amd64'):
         PLATFORM = 'manylinux2010-x86_64'
         PLATFORM = 'manylinux2010-x86_64'
-    elif GetTargetArch() in ('arm64', 'aarch64'):
+    elif target_arch in ('arm64', 'aarch64'):
         PLATFORM = 'manylinux2010-aarch64'
         PLATFORM = 'manylinux2010-aarch64'
     else:
     else:
         PLATFORM = 'manylinux2010-i686'
         PLATFORM = 'manylinux2010-i686'
 
 
 elif target == 'linux' and (os.path.isfile("/lib/libc-2.17.so") or os.path.isfile("/lib64/libc-2.17.so")) and os.path.isdir("/opt/python"):
 elif target == 'linux' and (os.path.isfile("/lib/libc-2.17.so") or os.path.isfile("/lib64/libc-2.17.so")) and os.path.isdir("/opt/python"):
     # Same sloppy check for manylinux2014.
     # Same sloppy check for manylinux2014.
-    if GetTargetArch() in ('x86_64', 'amd64'):
+    if target_arch in ('x86_64', 'amd64'):
         PLATFORM = 'manylinux2014-x86_64'
         PLATFORM = 'manylinux2014-x86_64'
-    elif GetTargetArch() in ('arm64', 'aarch64'):
+    elif target_arch in ('arm64', 'aarch64'):
         PLATFORM = 'manylinux2014-aarch64'
         PLATFORM = 'manylinux2014-aarch64'
     else:
     else:
         PLATFORM = 'manylinux2014-i686'
         PLATFORM = 'manylinux2014-i686'
 
 
 elif target == 'linux' and (os.path.isfile("/lib/i386-linux-gnu/libc-2.24.so") or os.path.isfile("/lib/x86_64-linux-gnu/libc-2.24.so")) and os.path.isdir("/opt/python"):
 elif target == 'linux' and (os.path.isfile("/lib/i386-linux-gnu/libc-2.24.so") or os.path.isfile("/lib/x86_64-linux-gnu/libc-2.24.so")) and os.path.isdir("/opt/python"):
     # Same sloppy check for manylinux_2_24.
     # Same sloppy check for manylinux_2_24.
-    if GetTargetArch() in ('x86_64', 'amd64'):
+    if target_arch in ('x86_64', 'amd64'):
         PLATFORM = 'manylinux_2_24-x86_64'
         PLATFORM = 'manylinux_2_24-x86_64'
-    elif GetTargetArch() in ('arm64', 'aarch64'):
+    elif target_arch in ('arm64', 'aarch64'):
         PLATFORM = 'manylinux_2_24-aarch64'
         PLATFORM = 'manylinux_2_24-aarch64'
     else:
     else:
         PLATFORM = 'manylinux_2_24-i686'
         PLATFORM = 'manylinux_2_24-i686'
 
 
+elif target == 'linux' and os.path.isfile("/lib64/libc-2.28.so") and os.path.isfile('/etc/almalinux-release') and os.path.isdir("/opt/python"):
+    # Same sloppy check for manylinux_2_28.
+    if target_arch in ('x86_64', 'amd64'):
+        PLATFORM = 'manylinux_2_28-x86_64'
+    elif target_arch in ('arm64', 'aarch64'):
+        PLATFORM = 'manylinux_2_28-aarch64'
+    else:
+        raise RuntimeError('Unhandled arch %s, please file a bug report!' % (target_arch))
+
 elif not CrossCompiling():
 elif not CrossCompiling():
     if HasTargetArch():
     if HasTargetArch():
         # Replace the architecture in the platform string.
         # Replace the architecture in the platform string.
         platform_parts = get_platform().rsplit('-', 1)
         platform_parts = get_platform().rsplit('-', 1)
-        target_arch = GetTargetArch()
         if target_arch == 'amd64':
         if target_arch == 'amd64':
             target_arch = 'x86_64'
             target_arch = 'x86_64'
         PLATFORM = platform_parts[0] + '-' + target_arch
         PLATFORM = platform_parts[0] + '-' + target_arch
@@ -474,7 +485,6 @@ elif not CrossCompiling():
         PLATFORM = get_platform()
         PLATFORM = get_platform()
 
 
 else:
 else:
-    target_arch = GetTargetArch()
     if target_arch == 'amd64':
     if target_arch == 'amd64':
         target_arch = 'x86_64'
         target_arch = 'x86_64'
     PLATFORM = '{0}-{1}'.format(target, target_arch)
     PLATFORM = '{0}-{1}'.format(target, target_arch)
@@ -998,6 +1008,9 @@ if (COMPILER=="GCC"):
             LibName("ARTOOLKIT", "-Wl,--exclude-libs,libAR.a")
             LibName("ARTOOLKIT", "-Wl,--exclude-libs,libAR.a")
             LibName("ARTOOLKIT", "-Wl,--exclude-libs,libARMulti.a")
             LibName("ARTOOLKIT", "-Wl,--exclude-libs,libARMulti.a")
 
 
+        if not PkgSkip("HARFBUZZ"):
+            LibName("HARFBUZZ", "-Wl,--exclude-libs,libharfbuzz.a")
+
         if not PkgSkip("MIMALLOC"):
         if not PkgSkip("MIMALLOC"):
             LibName("MIMALLOC", "-Wl,--exclude-libs,libmimalloc.a")
             LibName("MIMALLOC", "-Wl,--exclude-libs,libmimalloc.a")
 
 
@@ -2910,6 +2923,9 @@ Author-email: [email protected]
 ENTRY_POINTS = """[distutils.commands]
 ENTRY_POINTS = """[distutils.commands]
 build_apps = direct.dist.commands:build_apps
 build_apps = direct.dist.commands:build_apps
 bdist_apps = direct.dist.commands:bdist_apps
 bdist_apps = direct.dist.commands:bdist_apps
+
+[setuptools.finalize_distribution_options]
+build_apps = direct.dist.commands:finalize_distribution_options
 """
 """
 
 
 if not PkgSkip("DIRECT"):
 if not PkgSkip("DIRECT"):
@@ -3280,7 +3296,6 @@ if not PkgSkip("PANDAPHYSICS"):
     CopyAllHeaders('panda/src/physics')
     CopyAllHeaders('panda/src/physics')
     if not PkgSkip("PANDAPARTICLESYSTEM"):
     if not PkgSkip("PANDAPARTICLESYSTEM"):
         CopyAllHeaders('panda/src/particlesystem')
         CopyAllHeaders('panda/src/particlesystem')
-CopyAllHeaders('panda/src/dxml')
 CopyAllHeaders('panda/metalibs/panda')
 CopyAllHeaders('panda/metalibs/panda')
 CopyAllHeaders('panda/src/audiotraits')
 CopyAllHeaders('panda/src/audiotraits')
 CopyAllHeaders('panda/src/audiotraits')
 CopyAllHeaders('panda/src/audiotraits')
@@ -3839,6 +3854,7 @@ IGATEFILES=GetDirectoryContents('panda/src/pstatclient', ["*.h", "*_composite*.c
 IGATEFILES.remove("config_pstats.h")
 IGATEFILES.remove("config_pstats.h")
 TargetAdd('libp3pstatclient.in', opts=OPTS, input=IGATEFILES)
 TargetAdd('libp3pstatclient.in', opts=OPTS, input=IGATEFILES)
 TargetAdd('libp3pstatclient.in', opts=['IMOD:panda3d.core', 'ILIB:libp3pstatclient', 'SRCDIR:panda/src/pstatclient'])
 TargetAdd('libp3pstatclient.in', opts=['IMOD:panda3d.core', 'ILIB:libp3pstatclient', 'SRCDIR:panda/src/pstatclient'])
+PyTargetAdd('p3pstatclient_pStatClient_ext.obj', opts=OPTS, input='pStatClient_ext.cxx')
 
 
 #
 #
 # DIRECTORY: panda/src/gobj/
 # DIRECTORY: panda/src/gobj/
@@ -4113,24 +4129,6 @@ IGATEFILES=GetDirectoryContents('panda/src/recorder', ["*.h", "*_composite*.cxx"
 TargetAdd('libp3recorder.in', opts=OPTS, input=IGATEFILES)
 TargetAdd('libp3recorder.in', opts=OPTS, input=IGATEFILES)
 TargetAdd('libp3recorder.in', opts=['IMOD:panda3d.core', 'ILIB:libp3recorder', 'SRCDIR:panda/src/recorder'])
 TargetAdd('libp3recorder.in', opts=['IMOD:panda3d.core', 'ILIB:libp3recorder', 'SRCDIR:panda/src/recorder'])
 
 
-#
-# DIRECTORY: panda/src/dxml/
-#
-
-DefSymbol("TINYXML", "TIXML_USE_STL", "")
-
-OPTS=['DIR:panda/src/dxml', 'TINYXML']
-TargetAdd('tinyxml_composite1.obj', opts=OPTS, input='tinyxml_composite1.cxx')
-TargetAdd('libp3tinyxml.ilb', input='tinyxml_composite1.obj')
-
-OPTS=['DIR:panda/src/dxml', 'BUILDING:PANDA', 'TINYXML']
-TargetAdd('p3dxml_composite1.obj', opts=OPTS, input='p3dxml_composite1.cxx')
-
-OPTS=['DIR:panda/src/dxml', 'TINYXML']
-IGATEFILES=GetDirectoryContents('panda/src/dxml', ["*.h", "p3dxml_composite1.cxx"])
-TargetAdd('libp3dxml.in', opts=OPTS, input=IGATEFILES)
-TargetAdd('libp3dxml.in', opts=['IMOD:panda3d.core', 'ILIB:libp3dxml', 'SRCDIR:panda/src/dxml'])
-
 #
 #
 # DIRECTORY: panda/metalibs/panda/
 # DIRECTORY: panda/metalibs/panda/
 #
 #
@@ -4208,7 +4206,6 @@ TargetAdd('libpanda.dll', input='p3net_composite2.obj')
 TargetAdd('libpanda.dll', input='p3nativenet_composite1.obj')
 TargetAdd('libpanda.dll', input='p3nativenet_composite1.obj')
 TargetAdd('libpanda.dll', input='p3pandabase_pandabase.obj')
 TargetAdd('libpanda.dll', input='p3pandabase_pandabase.obj')
 TargetAdd('libpanda.dll', input='libpandaexpress.dll')
 TargetAdd('libpanda.dll', input='libpandaexpress.dll')
-TargetAdd('libpanda.dll', input='p3dxml_composite1.obj')
 TargetAdd('libpanda.dll', input='libp3dtoolconfig.dll')
 TargetAdd('libpanda.dll', input='libp3dtoolconfig.dll')
 TargetAdd('libpanda.dll', input='libp3dtool.dll')
 TargetAdd('libpanda.dll', input='libp3dtool.dll')
 
 
@@ -4254,7 +4251,6 @@ PyTargetAdd('core_module.obj', input='libp3nativenet.in')
 PyTargetAdd('core_module.obj', input='libp3net.in')
 PyTargetAdd('core_module.obj', input='libp3net.in')
 PyTargetAdd('core_module.obj', input='libp3pgui.in')
 PyTargetAdd('core_module.obj', input='libp3pgui.in')
 PyTargetAdd('core_module.obj', input='libp3movies.in')
 PyTargetAdd('core_module.obj', input='libp3movies.in')
-PyTargetAdd('core_module.obj', input='libp3dxml.in')
 
 
 if PkgSkip("FREETYPE")==0:
 if PkgSkip("FREETYPE")==0:
     PyTargetAdd('core_module.obj', input='libp3pnmtext.in')
     PyTargetAdd('core_module.obj', input='libp3pnmtext.in')
@@ -4300,7 +4296,6 @@ PyTargetAdd('core.pyd', input='libp3audio_igate.obj')
 PyTargetAdd('core.pyd', input='libp3pgui_igate.obj')
 PyTargetAdd('core.pyd', input='libp3pgui_igate.obj')
 PyTargetAdd('core.pyd', input='libp3net_igate.obj')
 PyTargetAdd('core.pyd', input='libp3net_igate.obj')
 PyTargetAdd('core.pyd', input='libp3nativenet_igate.obj')
 PyTargetAdd('core.pyd', input='libp3nativenet_igate.obj')
-PyTargetAdd('core.pyd', input='libp3dxml_igate.obj')
 
 
 if PkgSkip("FREETYPE")==0:
 if PkgSkip("FREETYPE")==0:
     PyTargetAdd('core.pyd', input="libp3pnmtext_igate.obj")
     PyTargetAdd('core.pyd', input="libp3pnmtext_igate.obj")
@@ -4310,14 +4305,13 @@ PyTargetAdd('core.pyd', input='p3putil_ext_composite.obj')
 PyTargetAdd('core.pyd', input='p3pnmimage_pfmFile_ext.obj')
 PyTargetAdd('core.pyd', input='p3pnmimage_pfmFile_ext.obj')
 PyTargetAdd('core.pyd', input='p3event_asyncFuture_ext.obj')
 PyTargetAdd('core.pyd', input='p3event_asyncFuture_ext.obj')
 PyTargetAdd('core.pyd', input='p3event_pythonTask.obj')
 PyTargetAdd('core.pyd', input='p3event_pythonTask.obj')
+PyTargetAdd('core.pyd', input='p3pstatclient_pStatClient_ext.obj')
 PyTargetAdd('core.pyd', input='p3gobj_ext_composite.obj')
 PyTargetAdd('core.pyd', input='p3gobj_ext_composite.obj')
 PyTargetAdd('core.pyd', input='p3pgraph_ext_composite.obj')
 PyTargetAdd('core.pyd', input='p3pgraph_ext_composite.obj')
 PyTargetAdd('core.pyd', input='p3display_ext_composite.obj')
 PyTargetAdd('core.pyd', input='p3display_ext_composite.obj')
 PyTargetAdd('core.pyd', input='p3collide_ext_composite.obj')
 PyTargetAdd('core.pyd', input='p3collide_ext_composite.obj')
 
 
 PyTargetAdd('core.pyd', input='core_module.obj')
 PyTargetAdd('core.pyd', input='core_module.obj')
-if not GetLinkAllStatic() and GetTarget() != 'emscripten':
-    PyTargetAdd('core.pyd', input='libp3tinyxml.ilb')
 PyTargetAdd('core.pyd', input='libp3interrogatedb.dll')
 PyTargetAdd('core.pyd', input='libp3interrogatedb.dll')
 PyTargetAdd('core.pyd', input=COMMON_PANDA_LIBS)
 PyTargetAdd('core.pyd', input=COMMON_PANDA_LIBS)
 PyTargetAdd('core.pyd', opts=['WINSOCK2'])
 PyTargetAdd('core.pyd', opts=['WINSOCK2'])
@@ -5871,7 +5865,7 @@ if not PkgSkip("PANDATOOL") and not PkgSkip("EGG"):
     TargetAdd('libp3ptloader.dll', input='libp3lwo.lib')
     TargetAdd('libp3ptloader.dll', input='libp3lwo.lib')
     TargetAdd('libp3ptloader.dll', input='libp3dxfegg.lib')
     TargetAdd('libp3ptloader.dll', input='libp3dxfegg.lib')
     TargetAdd('libp3ptloader.dll', input='libp3dxf.lib')
     TargetAdd('libp3ptloader.dll', input='libp3dxf.lib')
-    TargetAdd('libp3ptloader.dll', input='libp3objegg.lib')
+    #TargetAdd('libp3ptloader.dll', input='libp3objegg.lib')
     TargetAdd('libp3ptloader.dll', input='libp3vrmlegg.lib')
     TargetAdd('libp3ptloader.dll', input='libp3vrmlegg.lib')
     TargetAdd('libp3ptloader.dll', input='libp3vrml.lib')
     TargetAdd('libp3ptloader.dll', input='libp3vrml.lib')
     TargetAdd('libp3ptloader.dll', input='libp3xfileegg.lib')
     TargetAdd('libp3ptloader.dll', input='libp3xfileegg.lib')

+ 6 - 2
makepanda/makepandacore.py

@@ -433,7 +433,7 @@ def SetTarget(target, arch=None):
             ANDROID_ABI = 'x86_64'
             ANDROID_ABI = 'x86_64'
             ANDROID_TRIPLE = 'x86_64-linux-android'
             ANDROID_TRIPLE = 'x86_64-linux-android'
         else:
         else:
-            exit('Android architecture must be arm, armv7a, arm64, mips, mips64, x86 or x86_64')
+            exit('Android architecture must be arm, armv7a, arm64, mips, mips64, x86 or x86_64, use --arch to specify')
 
 
         ANDROID_TRIPLE += str(ANDROID_API)
         ANDROID_TRIPLE += str(ANDROID_API)
         TOOLCHAIN_PREFIX = ANDROID_TRIPLE + '-'
         TOOLCHAIN_PREFIX = ANDROID_TRIPLE + '-'
@@ -2194,6 +2194,10 @@ def SdkLocatePython(prefer_thirdparty_python=False):
             # Fall back to looking on the system.
             # Fall back to looking on the system.
             py_fwx = "/Library/Frameworks/Python.framework/Versions/" + version
             py_fwx = "/Library/Frameworks/Python.framework/Versions/" + version
 
 
+        if not os.path.exists(py_fwx):
+            # Newer macOS versions use this scheme.
+            py_fwx = "/Library/Developer/CommandLineTools/Library/Frameworks/Python3.framework/Versions/" + version
+
         if not os.path.exists(py_fwx):
         if not os.path.exists(py_fwx):
             exit("Could not locate Python installation at %s" % (py_fwx))
             exit("Could not locate Python installation at %s" % (py_fwx))
 
 
@@ -2430,7 +2434,7 @@ def SdkLocateMacOSX(archs = []):
         # Prefer pre-10.14 for now so that we can keep building FMOD.
         # Prefer pre-10.14 for now so that we can keep building FMOD.
         sdk_versions += ["10.13", "10.12", "10.11", "10.10", "10.9"]
         sdk_versions += ["10.13", "10.12", "10.11", "10.10", "10.9"]
 
 
-    sdk_versions += ["11.3", "11.1", "11.0"]
+    sdk_versions += ["13.0", "12.3", "11.3", "11.1", "11.0"]
 
 
     if 'arm64' not in archs:
     if 'arm64' not in archs:
         sdk_versions += ["10.15", "10.14"]
         sdk_versions += ["10.15", "10.14"]

+ 4 - 0
makepanda/makewheel.py

@@ -656,6 +656,8 @@ def makewheel(version, output_dir, platform=None):
                     platform = platform.replace("linux", "manylinux2014")
                     platform = platform.replace("linux", "manylinux2014")
                 elif os.path.isfile("/lib/i386-linux-gnu/libc-2.24.so") or os.path.isfile("/lib/x86_64-linux-gnu/libc-2.24.so"):
                 elif os.path.isfile("/lib/i386-linux-gnu/libc-2.24.so") or os.path.isfile("/lib/x86_64-linux-gnu/libc-2.24.so"):
                     platform = platform.replace("linux", "manylinux_2_24")
                     platform = platform.replace("linux", "manylinux_2_24")
+                elif os.path.isfile("/lib64/libc-2.28.so") and os.path.isfile('/etc/almalinux-release'):
+                    platform = platform.replace("linux", "manylinux_2_28")
 
 
     platform = platform.replace('-', '_').replace('.', '_')
     platform = platform.replace('-', '_').replace('.', '_')
 
 
@@ -887,6 +889,8 @@ if __debug__:
     entry_points += '[distutils.commands]\n'
     entry_points += '[distutils.commands]\n'
     entry_points += 'build_apps = direct.dist.commands:build_apps\n'
     entry_points += 'build_apps = direct.dist.commands:build_apps\n'
     entry_points += 'bdist_apps = direct.dist.commands:bdist_apps\n'
     entry_points += 'bdist_apps = direct.dist.commands:bdist_apps\n'
+    entry_points += '[setuptools.finalize_distribution_options]\n'
+    entry_points += 'build_apps = direct.dist.commands:finalize_distribution_options\n'
 
 
     whl.write_file_data('panda3d_tools/__init__.py', PANDA3D_TOOLS_INIT.format(tools_init))
     whl.write_file_data('panda3d_tools/__init__.py', PANDA3D_TOOLS_INIT.format(tools_init))
 
 

+ 0 - 11
makepanda/panda3d.desktop

@@ -1,11 +0,0 @@
-[Desktop Entry]
-Name=Panda3D
-Comment=Runs 3-D games and interactive applets
-TryExec=panda3d
-Exec=panda3d %U
-StartupNotify=true
-NoDisplay=true
-Terminal=false
-Type=Application
-Categories=Game;3DGraphics;Viewer;
-MimeType=application/x-panda3d;

+ 12 - 0
makepanda/pstats.desktop

@@ -0,0 +1,12 @@
+[Desktop Entry]
+Name=Panda3D Stats Monitor
+GenericName=Profiling Tool
+Comment=Analyze performance of Panda3D applications
+TryExec=pstats
+Exec=pstats %f
+StartupNotify=true
+NoDisplay=false
+Terminal=false
+Type=Application
+Categories=Utility;Development;
+MimeType=application/vnd.panda3d.pstats;

+ 2 - 2
makepanda/pview.desktop

@@ -3,10 +3,10 @@ Name=Panda3D Model Viewer
 GenericName=Model Viewer
 GenericName=Model Viewer
 Comment=View Panda3D model files
 Comment=View Panda3D model files
 TryExec=pview
 TryExec=pview
-Exec=pview %U
+Exec=pview %F
 StartupNotify=true
 StartupNotify=true
 NoDisplay=true
 NoDisplay=true
-Terminal=true
+Terminal=false
 Type=Application
 Type=Application
 Categories=Graphics;Utility;3DGraphics;Viewer;
 Categories=Graphics;Utility;3DGraphics;Viewer;
 MimeType=model/x-egg;model/x-compressed-egg;model/x-bam;model/x-compressed-bam;
 MimeType=model/x-egg;model/x-compressed-egg;model/x-bam;model/x-compressed-bam;

+ 4 - 2
panda/CMakeLists.txt

@@ -18,7 +18,6 @@ add_subdirectory(src/distort)
 add_subdirectory(src/downloader)
 add_subdirectory(src/downloader)
 add_subdirectory(src/downloadertools)
 add_subdirectory(src/downloadertools)
 add_subdirectory(src/dxgsg9)
 add_subdirectory(src/dxgsg9)
-add_subdirectory(src/dxml)
 add_subdirectory(src/egg)
 add_subdirectory(src/egg)
 add_subdirectory(src/egg2pg)
 add_subdirectory(src/egg2pg)
 add_subdirectory(src/egldisplay)
 add_subdirectory(src/egldisplay)
@@ -82,7 +81,7 @@ add_subdirectory(metalibs/pandaphysics)
 # Now add the Python modules:
 # Now add the Python modules:
 set(CORE_MODULE_COMPONENTS
 set(CORE_MODULE_COMPONENTS
   p3chan p3char p3collide p3cull p3device p3dgraph p3display
   p3chan p3char p3collide p3cull p3device p3dgraph p3display
-  p3downloader p3dxml p3event p3express p3gobj p3grutil p3gsgbase p3linmath
+  p3downloader p3event p3express p3gobj p3grutil p3gsgbase p3linmath
   p3mathutil p3movies p3parametrics p3pgraph p3pgraphnodes p3pgui
   p3mathutil p3movies p3parametrics p3pgraph p3pgraphnodes p3pgui
   p3pipeline p3pnmimage p3pstatclient p3putil p3recorder p3text p3tform
   p3pipeline p3pnmimage p3pstatclient p3putil p3recorder p3text p3tform
   p3prc p3dtoolutil p3dtoolbase
   p3prc p3dtoolutil p3dtoolbase
@@ -149,6 +148,9 @@ from .interrogatedb import *
   set(entry_points_file "[distutils.commands]
   set(entry_points_file "[distutils.commands]
 build_apps = direct.dist.commands:build_apps
 build_apps = direct.dist.commands:build_apps
 bdist_apps = direct.dist.commands:bdist_apps
 bdist_apps = direct.dist.commands:bdist_apps
+
+[setuptools.finalize_distribution_options]
+build_apps = direct.dist.commands:finalize_distribution_options
 ")
 ")
 
 
   configure_file("${PROJECT_SOURCE_DIR}/cmake/templates/METADATA.in"
   configure_file("${PROJECT_SOURCE_DIR}/cmake/templates/METADATA.in"

+ 1 - 1
panda/metalibs/panda/CMakeLists.txt

@@ -1,5 +1,5 @@
 set(PANDA_LINK_TARGETS
 set(PANDA_LINK_TARGETS
-  p3chan p3char p3collide p3cull p3device p3dgraph p3display p3dxml
+  p3chan p3char p3collide p3cull p3device p3dgraph p3display
   p3event p3gobj p3grutil p3gsgbase p3linmath p3mathutil
   p3event p3gobj p3grutil p3gsgbase p3linmath p3mathutil
   p3movies p3parametrics p3pgraph p3pgraphnodes p3pgui p3pipeline
   p3movies p3parametrics p3pgraph p3pgraphnodes p3pgui p3pipeline
   p3pnmimage p3pnmimagetypes p3pstatclient p3putil p3recorder p3text p3tform
   p3pnmimage p3pnmimagetypes p3pstatclient p3putil p3recorder p3text p3tform

+ 8 - 0
panda/src/audio/audioSound.I

@@ -10,3 +10,11 @@
  * @author jyelon
  * @author jyelon
  * @date 2007-08-01
  * @date 2007-08-01
  */
  */
+
+/**
+ * Returns true if this was created as a positional sound.
+ */
+INLINE bool AudioSound::
+is_positional() const {
+  return _positional;
+}

+ 1 - 1
panda/src/audio/audioSound.cxx

@@ -29,7 +29,7 @@ AudioSound::
  *
  *
  */
  */
 AudioSound::
 AudioSound::
-AudioSound() {
+AudioSound(bool positional) : _positional(positional) {
   // Intentionally blank.
   // Intentionally blank.
 }
 }
 
 

+ 6 - 1
panda/src/audio/audioSound.h

@@ -88,6 +88,8 @@ PUBLISHED:
   // There is no set_name(), this is intentional.
   // There is no set_name(), this is intentional.
   virtual const std::string& get_name() const = 0;
   virtual const std::string& get_name() const = 0;
 
 
+  INLINE bool is_positional() const;
+
   // return: playing time in seconds.
   // return: playing time in seconds.
   virtual PN_stdfloat length() const = 0;
   virtual PN_stdfloat length() const = 0;
 
 
@@ -134,9 +136,12 @@ PUBLISHED:
   MAKE_PROPERTY(play_rate, get_play_rate, set_play_rate);
   MAKE_PROPERTY(play_rate, get_play_rate, set_play_rate);
   MAKE_PROPERTY(active, get_active, set_active);
   MAKE_PROPERTY(active, get_active, set_active);
   MAKE_PROPERTY(name, get_name);
   MAKE_PROPERTY(name, get_name);
+  MAKE_PROPERTY(positional, is_positional);
 
 
 protected:
 protected:
-  AudioSound();
+  AudioSound(bool positional);
+
+  const bool _positional = false;
 
 
   friend class AudioManager;
   friend class AudioManager;
 
 

+ 11 - 16
panda/src/audio/nullAudioManager.cxx

@@ -16,11 +16,6 @@
 
 
 TypeHandle NullAudioManager::_type_handle;
 TypeHandle NullAudioManager::_type_handle;
 
 
-// namespace { static const string blank=""; static PN_stdfloat
-// no_listener_attributes [] = {0.0f,0.0f,0.0f, 0.0f,0.0f,0.0f,
-// 0.0f,0.0f,0.0f, 0.0f,0.0f,0.0f}; }
-
-
 /**
 /**
  *
  *
  */
  */
@@ -179,7 +174,7 @@ stop_all_sounds() {
  */
  */
 void NullAudioManager::
 void NullAudioManager::
 audio_3d_set_listener_attributes(PN_stdfloat px, PN_stdfloat py, PN_stdfloat pz, PN_stdfloat vx, PN_stdfloat vy, PN_stdfloat vz, PN_stdfloat fx, PN_stdfloat fy, PN_stdfloat fz, PN_stdfloat ux, PN_stdfloat uy, PN_stdfloat uz) {
 audio_3d_set_listener_attributes(PN_stdfloat px, PN_stdfloat py, PN_stdfloat pz, PN_stdfloat vx, PN_stdfloat vy, PN_stdfloat vz, PN_stdfloat fx, PN_stdfloat fy, PN_stdfloat fz, PN_stdfloat ux, PN_stdfloat uy, PN_stdfloat uz) {
-    // intentionally blank.
+  // intentionally blank.
 }
 }
 
 
 /**
 /**
@@ -187,7 +182,7 @@ audio_3d_set_listener_attributes(PN_stdfloat px, PN_stdfloat py, PN_stdfloat pz,
  */
  */
 void NullAudioManager::
 void NullAudioManager::
 audio_3d_get_listener_attributes(PN_stdfloat *px, PN_stdfloat *py, PN_stdfloat *pz, PN_stdfloat *vx, PN_stdfloat *vy, PN_stdfloat *vz, PN_stdfloat *fx, PN_stdfloat *fy, PN_stdfloat *fz, PN_stdfloat *ux, PN_stdfloat *uy, PN_stdfloat *uz) {
 audio_3d_get_listener_attributes(PN_stdfloat *px, PN_stdfloat *py, PN_stdfloat *pz, PN_stdfloat *vx, PN_stdfloat *vy, PN_stdfloat *vz, PN_stdfloat *fx, PN_stdfloat *fy, PN_stdfloat *fz, PN_stdfloat *ux, PN_stdfloat *uy, PN_stdfloat *uz) {
-    // intentionally blank.
+  // intentionally blank.
 }
 }
 
 
 /**
 /**
@@ -195,7 +190,7 @@ audio_3d_get_listener_attributes(PN_stdfloat *px, PN_stdfloat *py, PN_stdfloat *
  */
  */
 void NullAudioManager::
 void NullAudioManager::
 audio_3d_set_distance_factor(PN_stdfloat factor) {
 audio_3d_set_distance_factor(PN_stdfloat factor) {
-    // intentionally blank.
+  // intentionally blank.
 }
 }
 
 
 /**
 /**
@@ -203,8 +198,8 @@ audio_3d_set_distance_factor(PN_stdfloat factor) {
  */
  */
 PN_stdfloat NullAudioManager::
 PN_stdfloat NullAudioManager::
 audio_3d_get_distance_factor() const {
 audio_3d_get_distance_factor() const {
-    // intentionally blank.
-    return 0.0f;
+  // intentionally blank.
+  return 0.0f;
 }
 }
 
 
 /**
 /**
@@ -212,7 +207,7 @@ audio_3d_get_distance_factor() const {
  */
  */
 void NullAudioManager::
 void NullAudioManager::
 audio_3d_set_doppler_factor(PN_stdfloat factor) {
 audio_3d_set_doppler_factor(PN_stdfloat factor) {
-    // intentionally blank.
+  // intentionally blank.
 }
 }
 
 
 /**
 /**
@@ -220,8 +215,8 @@ audio_3d_set_doppler_factor(PN_stdfloat factor) {
  */
  */
 PN_stdfloat NullAudioManager::
 PN_stdfloat NullAudioManager::
 audio_3d_get_doppler_factor() const {
 audio_3d_get_doppler_factor() const {
-    // intentionally blank.
-    return 0.0f;
+  // intentionally blank.
+  return 0.0f;
 }
 }
 
 
 /**
 /**
@@ -229,7 +224,7 @@ audio_3d_get_doppler_factor() const {
  */
  */
 void NullAudioManager::
 void NullAudioManager::
 audio_3d_set_drop_off_factor(PN_stdfloat factor) {
 audio_3d_set_drop_off_factor(PN_stdfloat factor) {
-    // intentionally blank.
+  // intentionally blank.
 }
 }
 
 
 /**
 /**
@@ -237,6 +232,6 @@ audio_3d_set_drop_off_factor(PN_stdfloat factor) {
  */
  */
 PN_stdfloat NullAudioManager::
 PN_stdfloat NullAudioManager::
 audio_3d_get_drop_off_factor() const {
 audio_3d_get_drop_off_factor() const {
-    // intentionally blank.
-    return 0.0f;
+  // intentionally blank.
+  return 0.0f;
 }
 }

+ 1 - 1
panda/src/audio/nullAudioSound.cxx

@@ -26,7 +26,7 @@ namespace {
 /**
 /**
  * All of these functions are just stubs.
  * All of these functions are just stubs.
  */
  */
-NullAudioSound::NullAudioSound() {
+NullAudioSound::NullAudioSound() : AudioSound(false) {
   // Intentionally blank.
   // Intentionally blank.
 }
 }
 
 

+ 1 - 2
panda/src/audiotraits/fmodAudioSound.cxx

@@ -37,9 +37,8 @@ TypeHandle FmodAudioSound::_type_handle;
  * Constructor All sound will DEFAULT load as a 2D sound unless otherwise
  * Constructor All sound will DEFAULT load as a 2D sound unless otherwise
  * specified.
  * specified.
  */
  */
-
 FmodAudioSound::
 FmodAudioSound::
-FmodAudioSound(AudioManager *manager, VirtualFile *file, bool positional) {
+FmodAudioSound(AudioManager *manager, VirtualFile *file, bool positional) : AudioSound(positional) {
   ReMutexHolder holder(FmodAudioManager::_lock);
   ReMutexHolder holder(FmodAudioManager::_lock);
   audio_debug("FmodAudioSound::FmodAudioSound() Creating new sound, filename: "
   audio_debug("FmodAudioSound::FmodAudioSound() Creating new sound, filename: "
               << file->get_original_filename());
               << file->get_original_filename());

+ 1 - 1
panda/src/audiotraits/openalAudioSound.cxx

@@ -37,6 +37,7 @@ OpenALAudioSound(OpenALAudioManager* manager,
                  MovieAudio *movie,
                  MovieAudio *movie,
                  bool positional,
                  bool positional,
                  int mode) :
                  int mode) :
+  AudioSound(positional),
   _movie(movie),
   _movie(movie),
   _sd(nullptr),
   _sd(nullptr),
   _playing_loops(0),
   _playing_loops(0),
@@ -47,7 +48,6 @@ OpenALAudioSound(OpenALAudioManager* manager,
   _volume(1.0f),
   _volume(1.0f),
   _balance(0),
   _balance(0),
   _play_rate(1.0),
   _play_rate(1.0),
-  _positional(positional),
   _min_dist(1.0f),
   _min_dist(1.0f),
   _max_dist(1000000000.0f),
   _max_dist(1000000000.0f),
   _drop_off_factor(1.0f),
   _drop_off_factor(1.0f),

+ 0 - 1
panda/src/audiotraits/openalAudioSound.h

@@ -152,7 +152,6 @@ private:
   PN_stdfloat _balance; // -1..1
   PN_stdfloat _balance; // -1..1
   PN_stdfloat _play_rate; // 0..1.0
   PN_stdfloat _play_rate; // 0..1.0
 
 
-  bool _positional;
   ALfloat _location[3];
   ALfloat _location[3];
   ALfloat _velocity[3];
   ALfloat _velocity[3];
 
 

+ 54 - 21
panda/src/cocoadisplay/cocoaGraphicsWindow.mm

@@ -76,7 +76,7 @@ CocoaGraphicsWindow(GraphicsEngine *engine, GraphicsPipe *pipe,
   if (NSApp == nil) {
   if (NSApp == nil) {
     [CocoaPandaApp sharedApplication];
     [CocoaPandaApp sharedApplication];
 
 
-    CocoaPandaAppDelegate *delegate = [[CocoaPandaAppDelegate alloc] init];
+    CocoaPandaAppDelegate *delegate = [[CocoaPandaAppDelegate alloc] initWithEngine:engine];
     [NSApp setDelegate:delegate];
     [NSApp setDelegate:delegate];
 
 
     [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
     [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
@@ -186,29 +186,29 @@ begin_frame(FrameMode mode, Thread *current_thread) {
   cocoagsg->lock_context();
   cocoagsg->lock_context();
 
 
   // Set the drawable.
   // Set the drawable.
-  if (_properties.get_fullscreen()) {
-    // Fullscreen.
-    CGLSetFullScreenOnDisplay((CGLContextObj) [cocoagsg->_context CGLContextObj], CGDisplayIDToOpenGLDisplayMask(_display));
-  } else {
-    // Although not recommended, it is technically possible to use the same
-    // context with multiple different-sized windows.  If that happens, the
-    // context needs to be updated accordingly.
-    if ([cocoagsg->_context view] != _view) {
-      // XXX I'm not 100% sure that changing the view requires it to update.
-      _context_needs_update = true;
-      [cocoagsg->_context setView:_view];
+  // Although not recommended, it is technically possible to use the same
+  // context with multiple different-sized windows.  If that happens, the
+  // context needs to be updated accordingly.
+  if ([cocoagsg->_context view] != _view) {
+    // XXX I'm not 100% sure that changing the view requires it to update.
+    _context_needs_update = true;
+    [cocoagsg->_context setView:_view];
 
 
-      if (cocoadisplay_cat.is_spam()) {
-        cocoadisplay_cat.spam()
-          << "Switching context to view " << _view << "\n";
-      }
+    if (cocoadisplay_cat.is_spam()) {
+      cocoadisplay_cat.spam()
+        << "Switching context to view " << _view << "\n";
     }
     }
   }
   }
 
 
   // Update the context if necessary, to make it reallocate buffers etc.
   // Update the context if necessary, to make it reallocate buffers etc.
   if (_context_needs_update) {
   if (_context_needs_update) {
-    [cocoagsg->_context update];
-    _context_needs_update = false;
+    if ([NSThread isMainThread]) {
+      [cocoagsg->_context update];
+      _context_needs_update = false;
+    } else {
+      cocoagsg->unlock_context();
+      return false;
+    }
   }
   }
 
 
   // Lock the view for drawing.
   // Lock the view for drawing.
@@ -349,6 +349,18 @@ process_events() {
   }
   }
 
 
   [pool release];
   [pool release];
+
+  if (_context_needs_update && _gsg != nullptr) {
+    CocoaGraphicsStateGuardian *cocoagsg;
+    DCAST_INTO_V(cocoagsg, _gsg);
+
+    if (cocoagsg != nullptr && cocoagsg->_context != nil) {
+      cocoagsg->lock_context();
+      _context_needs_update = false;
+      [cocoagsg->_context update];
+      cocoagsg->unlock_context();
+    }
+  }
 }
 }
 
 
 /**
 /**
@@ -609,7 +621,7 @@ open_window() {
     }
     }
 
 
     if (_properties.get_fullscreen()) {
     if (_properties.get_fullscreen()) {
-      [_window setLevel: NSMainMenuWindowLevel + 1];
+      [_window setLevel: CGShieldingWindowLevel()];
     } else {
     } else {
       switch (_properties.get_z_order()) {
       switch (_properties.get_z_order()) {
       case WindowProperties::Z_bottom:
       case WindowProperties::Z_bottom:
@@ -807,7 +819,7 @@ set_properties_now(WindowProperties &properties) {
                 [_window setStyleMask:NSBorderlessWindowMask];
                 [_window setStyleMask:NSBorderlessWindowMask];
               }
               }
               [_window makeFirstResponder:_view];
               [_window makeFirstResponder:_view];
-              [_window setLevel:NSMainMenuWindowLevel+1];
+              [_window setLevel:CGShieldingWindowLevel()];
               [_window makeKeyAndOrderFront:nil];
               [_window makeKeyAndOrderFront:nil];
             }
             }
 
 
@@ -912,10 +924,19 @@ set_properties_now(WindowProperties &properties) {
     NSRect frame;
     NSRect frame;
     NSRect container;
     NSRect container;
     if (_window != nil) {
     if (_window != nil) {
-      frame = [_window contentRectForFrameRect:[_window frame]];
+      NSRect window_frame = [_window frame];
+      frame = [_window contentRectForFrameRect:window_frame];
       NSScreen *screen = [_window screen];
       NSScreen *screen = [_window screen];
       nassertv(screen != nil);
       nassertv(screen != nil);
       container = [screen frame];
       container = [screen frame];
+
+      // Prevent the centering from overlapping the Dock
+      if (y < 0) {
+        NSRect visible_frame = [screen visibleFrame];
+        if (window_frame.size.height == visible_frame.size.height) {
+          y = 0;
+        }
+      }
     } else {
     } else {
       frame = [_view frame];
       frame = [_view frame];
       container = [[_view superview] frame];
       container = [[_view superview] frame];
@@ -1116,6 +1137,18 @@ set_properties_now(WindowProperties &properties) {
       break;
       break;
     }
     }
   }
   }
+
+  if (_context_needs_update && _gsg != nullptr) {
+    CocoaGraphicsStateGuardian *cocoagsg;
+    DCAST_INTO_V(cocoagsg, _gsg);
+
+    if (cocoagsg != nullptr && cocoagsg->_context != nil) {
+      cocoagsg->lock_context();
+      _context_needs_update = false;
+      [cocoagsg->_context update];
+      cocoagsg->unlock_context();
+    }
+  }
 }
 }
 
 
 /**
 /**

+ 9 - 1
panda/src/cocoadisplay/cocoaPandaAppDelegate.h

@@ -14,9 +14,17 @@
 #import <Foundation/Foundation.h>
 #import <Foundation/Foundation.h>
 #import <AppKit/AppKit.h>
 #import <AppKit/AppKit.h>
 
 
+class GraphicsEngine;
+
 // Cocoa is picky about where and when certain methods are called in the initialization process.
 // Cocoa is picky about where and when certain methods are called in the initialization process.
-@interface CocoaPandaAppDelegate : NSObject<NSApplicationDelegate>
+@interface CocoaPandaAppDelegate : NSObject<NSApplicationDelegate> {
+  @private
+    GraphicsEngine *_engine;
+}
 
 
+- (id) initWithEngine:(GraphicsEngine *)engine;
 - (void)applicationDidFinishLaunching:(NSNotification *)notification;
 - (void)applicationDidFinishLaunching:(NSNotification *)notification;
+- (BOOL)applicationShouldTerminate:(NSApplication *)app;
+- (void)applicationWillTerminate:(NSNotification *)notification;
 
 
 @end
 @end

+ 48 - 0
panda/src/cocoadisplay/cocoaPandaAppDelegate.mm

@@ -12,12 +12,60 @@
 */
 */
 
 
 #import "cocoaPandaAppDelegate.h"
 #import "cocoaPandaAppDelegate.h"
+#include "graphicsEngine.h"
+#include "config_cocoadisplay.h"
 
 
 @implementation CocoaPandaAppDelegate
 @implementation CocoaPandaAppDelegate
 
 
+- (id) initWithEngine:(GraphicsEngine *)engine {
+
+  if (self = [super init]) {
+    _engine = engine;
+  }
+
+  return self;
+}
+
 - (void)applicationDidFinishLaunching:(NSNotification *)notification {
 - (void)applicationDidFinishLaunching:(NSNotification *)notification {
   // This only seems to work when called here.
   // This only seems to work when called here.
   [NSApp activateIgnoringOtherApps:YES];
   [NSApp activateIgnoringOtherApps:YES];
 }
 }
 
 
+- (BOOL)applicationShouldTerminate:(NSApplication *)app {
+  if (cocoadisplay_cat.is_debug()) {
+    cocoadisplay_cat.debug()
+      << "Received applicationShouldTerminate, requesting to close all Cocoa windows\n";
+  }
+  // Ask all the windows whether they are OK to be closed.
+  bool should_close = true;
+  for (NSWindow *window in [app windows]) {
+    id<NSWindowDelegate> delegate = [window delegate];
+    if (delegate != nil && ![delegate windowShouldClose:window]) {
+      should_close = false;
+    }
+  }
+  if (should_close) {
+    if (cocoadisplay_cat.is_debug()) {
+      cocoadisplay_cat.debug()
+        << "No window objected to close request, closing all windows\n";
+    }
+    // If so (none of them fired a close request event), close them now.
+    for (NSWindow *window in [app windows]) {
+      [window close];
+    }
+  }
+  // Give the application a chance to run its own cleanup functions.
+  return FALSE;
+}
+
+- (void)applicationWillTerminate:(NSNotification *)notification {
+  // The application is about to be closed, tell the graphics engine to close
+  // all the windows.
+  if (cocoadisplay_cat.is_debug()) {
+    cocoadisplay_cat.debug()
+      << "Received applicationWillTerminate, removing all windows\n";
+  }
+  _engine->remove_all_windows();
+}
+
 @end
 @end

+ 1 - 0
panda/src/cocoadisplay/cocoaPandaWindowDelegate.h

@@ -30,6 +30,7 @@ class CocoaGraphicsWindow;
 - (void)windowDidBecomeKey:(NSNotification *)notification;
 - (void)windowDidBecomeKey:(NSNotification *)notification;
 - (void)windowDidResignKey:(NSNotification *)notification;
 - (void)windowDidResignKey:(NSNotification *)notification;
 - (BOOL)windowShouldClose:(id)sender;
 - (BOOL)windowShouldClose:(id)sender;
+- (void)windowWillClose:(id)sender;
 
 
 // TODO: handle fullscreen on Lion.
 // TODO: handle fullscreen on Lion.
 
 

+ 13 - 4
panda/src/cocoadisplay/cocoaPandaWindowDelegate.mm

@@ -12,6 +12,7 @@
  */
  */
 
 
 #import "cocoaPandaWindowDelegate.h"
 #import "cocoaPandaWindowDelegate.h"
+#include "config_cocoadisplay.h"
 
 
 @implementation CocoaPandaWindowDelegate
 @implementation CocoaPandaWindowDelegate
 - (id) initWithGraphicsWindow:(CocoaGraphicsWindow*)window {
 - (id) initWithGraphicsWindow:(CocoaGraphicsWindow*)window {
@@ -51,11 +52,19 @@
 }
 }
 
 
 - (BOOL) windowShouldClose:(id)sender {
 - (BOOL) windowShouldClose:(id)sender {
-  bool should_close = _graphicsWindow->handle_close_request();
-  if (should_close) {
-    _graphicsWindow->handle_close_event();
+  if (cocoadisplay_cat.is_debug()) {
+    cocoadisplay_cat.debug()
+      << "Received windowShouldClose for window " << _graphicsWindow << "\n";
   }
   }
-  return should_close;
+  return _graphicsWindow->handle_close_request();
+}
+
+- (void) windowWillClose:(id)sender {
+  if (cocoadisplay_cat.is_debug()) {
+    cocoadisplay_cat.debug()
+      << "Received windowWillClose for window " << _graphicsWindow << "\n";
+  }
+  _graphicsWindow->handle_close_event();
 }
 }
 
 
 @end
 @end

+ 0 - 8
panda/src/collide/collisionHandler.cxx

@@ -15,14 +15,6 @@
 
 
 TypeHandle CollisionHandler::_type_handle;
 TypeHandle CollisionHandler::_type_handle;
 
 
-/**
- *
- */
-CollisionHandler::
-CollisionHandler() {
-  _wants_all_potential_collidees = false;
-}
-
 /**
 /**
  * Will be called by the CollisionTraverser before a new traversal is begun.
  * Will be called by the CollisionTraverser before a new traversal is begun.
  * It instructs the handler to reset itself in preparation for a number of
  * It instructs the handler to reset itself in preparation for a number of

+ 5 - 6
panda/src/collide/collisionHandler.h

@@ -28,9 +28,10 @@ class CollisionEntry;
  * dispatch detected collisions.
  * dispatch detected collisions.
  */
  */
 class EXPCL_PANDA_COLLIDE CollisionHandler : public TypedReferenceCount {
 class EXPCL_PANDA_COLLIDE CollisionHandler : public TypedReferenceCount {
-public:
-  CollisionHandler();
+PUBLISHED:
+  CollisionHandler() = default;
 
 
+public:
   virtual void begin_group();
   virtual void begin_group();
   virtual void add_entry(CollisionEntry *entry);
   virtual void add_entry(CollisionEntry *entry);
   virtual bool end_group();
   virtual bool end_group();
@@ -38,12 +39,10 @@ public:
   INLINE bool wants_all_potential_collidees() const;
   INLINE bool wants_all_potential_collidees() const;
   INLINE void set_root(const NodePath &root);
   INLINE void set_root(const NodePath &root);
 
 
-PUBLISHED:
+public:
   static TypeHandle get_class_type() {
   static TypeHandle get_class_type() {
     return _type_handle;
     return _type_handle;
   }
   }
-
-public:
   static void init_type() {
   static void init_type() {
     TypedReferenceCount::init_type();
     TypedReferenceCount::init_type();
     register_type(_type_handle, "CollisionHandler",
     register_type(_type_handle, "CollisionHandler",
@@ -55,7 +54,7 @@ public:
   virtual TypeHandle force_init_type() {init_type(); return get_class_type();}
   virtual TypeHandle force_init_type() {init_type(); return get_class_type();}
 
 
 protected:
 protected:
-  bool _wants_all_potential_collidees;
+  bool _wants_all_potential_collidees = false;
   const NodePath *_root;
   const NodePath *_root;
 
 
 private:
 private:

+ 2 - 0
panda/src/display/CMakeLists.txt

@@ -27,6 +27,7 @@ set(P3DISPLAY_HEADERS
   windowHandle.I windowHandle.h
   windowHandle.I windowHandle.h
   windowProperties.I windowProperties.h
   windowProperties.I windowProperties.h
   renderBuffer.h
   renderBuffer.h
+  screenshotRequest.I screenshotRequest.h
   stereoDisplayRegion.I stereoDisplayRegion.h
   stereoDisplayRegion.I stereoDisplayRegion.h
   displaySearchParameters.h
   displaySearchParameters.h
   displayInformation.h
   displayInformation.h
@@ -61,6 +62,7 @@ set(P3DISPLAY_SOURCES
   parasiteBuffer.cxx
   parasiteBuffer.cxx
   windowHandle.cxx
   windowHandle.cxx
   windowProperties.cxx
   windowProperties.cxx
+  screenshotRequest.cxx
   stereoDisplayRegion.cxx
   stereoDisplayRegion.cxx
   subprocessWindow.cxx
   subprocessWindow.cxx
   touchInfo.cxx
   touchInfo.cxx

+ 2 - 0
panda/src/display/config_display.cxx

@@ -29,6 +29,7 @@
 #include "nativeWindowHandle.h"
 #include "nativeWindowHandle.h"
 #include "parasiteBuffer.h"
 #include "parasiteBuffer.h"
 #include "pandaSystem.h"
 #include "pandaSystem.h"
+#include "screenshotRequest.h"
 #include "stereoDisplayRegion.h"
 #include "stereoDisplayRegion.h"
 #include "subprocessWindow.h"
 #include "subprocessWindow.h"
 #include "windowHandle.h"
 #include "windowHandle.h"
@@ -532,6 +533,7 @@ init_libdisplay() {
   MouseAndKeyboard::init_type();
   MouseAndKeyboard::init_type();
   NativeWindowHandle::init_type();
   NativeWindowHandle::init_type();
   ParasiteBuffer::init_type();
   ParasiteBuffer::init_type();
+  ScreenshotRequest::init_type();
   StandardMunger::init_type();
   StandardMunger::init_type();
   StereoDisplayRegion::init_type();
   StereoDisplayRegion::init_type();
 #ifdef SUPPORT_SUBPROCESS_WINDOW
 #ifdef SUPPORT_SUBPROCESS_WINDOW

+ 3 - 1
panda/src/display/frameBufferProperties.h

@@ -144,8 +144,10 @@ PUBLISHED:
   MAKE_PROPERTY(float_color, get_float_color, set_float_color);
   MAKE_PROPERTY(float_color, get_float_color, set_float_color);
   MAKE_PROPERTY(float_depth, get_float_depth, set_float_depth);
   MAKE_PROPERTY(float_depth, get_float_depth, set_float_depth);
 
 
+#ifdef HAVE_PYTHON
   EXTENSION(PyObject *__getstate__() const);
   EXTENSION(PyObject *__getstate__() const);
   EXTENSION(void __setstate__(PyObject *self, PyObject *state));
   EXTENSION(void __setstate__(PyObject *self, PyObject *state));
+#endif // HAVE_PYTHON
 
 
   // Other.
   // Other.
 
 
@@ -181,4 +183,4 @@ INLINE std::ostream &operator << (std::ostream &out, const FrameBufferProperties
 
 
 #include "frameBufferProperties.I"
 #include "frameBufferProperties.I"
 
 
-#endif
+#endif // !FRAMEBUFFERPROPERTIES_H

+ 5 - 0
panda/src/display/graphicsEngine.cxx

@@ -1419,6 +1419,8 @@ cull_and_draw_together(GraphicsEngine::Windows wlist,
       }
       }
 
 
       if (win->begin_frame(GraphicsOutput::FM_render, current_thread)) {
       if (win->begin_frame(GraphicsOutput::FM_render, current_thread)) {
+        win->copy_async_screenshot();
+
         if (win->is_any_clear_active()) {
         if (win->is_any_clear_active()) {
           GraphicsStateGuardian *gsg = win->get_gsg();
           GraphicsStateGuardian *gsg = win->get_gsg();
           PStatGPUTimer timer(gsg, win->get_clear_window_pcollector(), current_thread);
           PStatGPUTimer timer(gsg, win->get_clear_window_pcollector(), current_thread);
@@ -1720,6 +1722,9 @@ draw_bins(const GraphicsEngine::Windows &wlist, Thread *current_thread) {
         // a current context for PStatGPUTimer to work.
         // a current context for PStatGPUTimer to work.
         {
         {
           PStatGPUTimer timer(gsg, win->get_draw_window_pcollector(), current_thread);
           PStatGPUTimer timer(gsg, win->get_draw_window_pcollector(), current_thread);
+
+          win->copy_async_screenshot();
+
           if (win->is_any_clear_active()) {
           if (win->is_any_clear_active()) {
             PStatGPUTimer timer(gsg, win->get_clear_window_pcollector(), current_thread);
             PStatGPUTimer timer(gsg, win->get_clear_window_pcollector(), current_thread);
             win->get_gsg()->push_group_marker("Clear");
             win->get_gsg()->push_group_marker("Clear");

+ 89 - 1
panda/src/display/graphicsOutput.cxx

@@ -976,6 +976,43 @@ make_cube_map(const string &name, int size, NodePath &camera_rig,
   return buffer;
   return buffer;
 }
 }
 
 
+/**
+ * Like save_screenshot, but performs both the texture transfer and the saving
+ * to disk in the background.  Returns a future that can be awaited.
+ *
+ * This captures the frame that was last submitted by the App stage to the
+ * render_frame() call.  This may not be the latest frame shown on the screen
+ * if the multi-threaded pipeline is used, in which case the request may take
+ * several frames extra to complete.
+ */
+PT(ScreenshotRequest) GraphicsOutput::
+save_async_screenshot(const Filename &filename, const std::string &image_comment) {
+  PT(ScreenshotRequest) request = get_async_screenshot();
+  request->add_output_file(filename, image_comment);
+  return request;
+}
+
+/**
+ * Used to obtain a new Texture object containing the previously rendered frame.
+ * Unlike get_screenshot, this works asynchronously, meaning that the contents
+ * are transferred in the background.  Returns a future that can be awaited.
+ *
+ * This captures the frame that was last submitted by the App stage to the
+ * render_frame() call.  This may not be the latest frame shown on the screen
+ * if the multi-threaded pipeline is used, in which case the request may take
+ * several frames extra to complete.
+ */
+PT(ScreenshotRequest) GraphicsOutput::
+get_async_screenshot() {
+  Thread *current_thread = Thread::get_current_thread();
+  CDWriter cdata(_cycler, current_thread);
+  if (cdata->_screenshot_request == nullptr) {
+    PT(Texture) texture = new Texture("screenshot of " + get_name());
+    cdata->_screenshot_request = new ScreenshotRequest(texture);
+  }
+  return cdata->_screenshot_request;
+}
+
 /**
 /**
  * Returns a PandaNode containing a square polygon.  The dimensions are
  * Returns a PandaNode containing a square polygon.  The dimensions are
  * (-1,0,-1) to (1,0,1). The texture coordinates are such that the texture of
  * (-1,0,-1) to (1,0,1). The texture coordinates are such that the texture of
@@ -1468,6 +1505,56 @@ copy_to_textures() {
   return okflag;
   return okflag;
 }
 }
 
 
+/**
+ * Do the necessary copies for the get_async_screenshot request.
+ */
+void GraphicsOutput::
+copy_async_screenshot() {
+  Thread *current_thread = Thread::get_current_thread();
+  PT(ScreenshotRequest) request;
+  {
+    CDWriter cdata(_cycler, current_thread);
+    if (cdata->_screenshot_request == nullptr) {
+      return;
+    }
+    request = std::move(cdata->_screenshot_request);
+    cdata->_screenshot_request.clear();
+  }
+
+  // Make sure it is cleared from upstream stages as well.
+  OPEN_ITERATE_UPSTREAM_ONLY(_cycler, current_thread) {
+    CDStageWriter cdata(_cycler, pipeline_stage, current_thread);
+    if (cdata->_screenshot_request == request) {
+      cdata->_screenshot_request.clear();
+    }
+  }
+  CLOSE_ITERATE_UPSTREAM_ONLY(_cycler);
+
+  PStatTimer timer(_copy_texture_pcollector);
+
+  RenderBuffer buffer = _gsg->get_render_buffer(get_draw_buffer_type(),
+                                                get_fb_properties());
+  DisplayRegion *dr = _overlay_display_region;
+
+  Texture *texture = request->get_result();
+
+  if (_fb_properties.is_stereo()) {
+    // We've got two texture views to copy.
+    texture->set_num_views(2);
+
+    RenderBuffer left(_gsg, buffer._buffer_type & ~RenderBuffer::T_right);
+    RenderBuffer right(_gsg, buffer._buffer_type & ~RenderBuffer::T_left);
+
+    _gsg->framebuffer_copy_to_ram(texture, 0, _target_tex_page,
+                                  dr, left, request);
+    _gsg->framebuffer_copy_to_ram(texture, 1, _target_tex_page,
+                                  dr, right, request);
+  } else {
+    _gsg->framebuffer_copy_to_ram(texture, 0, _target_tex_page,
+                                  dr, buffer, request);
+  }
+}
+
 /**
 /**
  * Generates a GeomVertexData for a texture card.
  * Generates a GeomVertexData for a texture card.
  */
  */
@@ -1653,7 +1740,8 @@ CData(const GraphicsOutput::CData &copy) :
   _active(copy._active),
   _active(copy._active),
   _one_shot_frame(copy._one_shot_frame),
   _one_shot_frame(copy._one_shot_frame),
   _active_display_regions(copy._active_display_regions),
   _active_display_regions(copy._active_display_regions),
-  _active_display_regions_stale(copy._active_display_regions_stale)
+  _active_display_regions_stale(copy._active_display_regions_stale),
+  _screenshot_request(copy._screenshot_request)
 {
 {
 }
 }
 
 

+ 7 - 0
panda/src/display/graphicsOutput.h

@@ -41,6 +41,7 @@
 #include "pipelineCycler.h"
 #include "pipelineCycler.h"
 #include "updateSeq.h"
 #include "updateSeq.h"
 #include "asyncFuture.h"
 #include "asyncFuture.h"
+#include "screenshotRequest.h"
 
 
 class PNMImage;
 class PNMImage;
 class GraphicsEngine;
 class GraphicsEngine;
@@ -239,6 +240,9 @@ PUBLISHED:
       const Filename &filename, const std::string &image_comment = "");
       const Filename &filename, const std::string &image_comment = "");
   INLINE bool get_screenshot(PNMImage &image);
   INLINE bool get_screenshot(PNMImage &image);
   INLINE PT(Texture) get_screenshot();
   INLINE PT(Texture) get_screenshot();
+  PT(ScreenshotRequest) save_async_screenshot(const Filename &filename,
+                                              const std::string &image_comment = "");
+  PT(ScreenshotRequest) get_async_screenshot();
 
 
   NodePath get_texture_card();
   NodePath get_texture_card();
 
 
@@ -298,6 +302,7 @@ protected:
   void prepare_for_deletion();
   void prepare_for_deletion();
   void promote_to_copy_texture();
   void promote_to_copy_texture();
   bool copy_to_textures();
   bool copy_to_textures();
+  void copy_async_screenshot();
 
 
   INLINE void begin_frame_spam(FrameMode mode);
   INLINE void begin_frame_spam(FrameMode mode);
   INLINE void end_frame_spam(FrameMode mode);
   INLINE void end_frame_spam(FrameMode mode);
@@ -392,6 +397,8 @@ protected:
     int _one_shot_frame;
     int _one_shot_frame;
     ActiveDisplayRegions _active_display_regions;
     ActiveDisplayRegions _active_display_regions;
     bool _active_display_regions_stale;
     bool _active_display_regions_stale;
+
+    PT(ScreenshotRequest) _screenshot_request;
   };
   };
   PipelineCycler<CData> _cycler;
   PipelineCycler<CData> _cycler;
   typedef CycleDataLockedReader<CData> CDLockedReader;
   typedef CycleDataLockedReader<CData> CDLockedReader;

+ 3 - 1
panda/src/display/graphicsPipeSelection.h

@@ -52,7 +52,9 @@ PUBLISHED:
 
 
   INLINE static GraphicsPipeSelection *get_global_ptr();
   INLINE static GraphicsPipeSelection *get_global_ptr();
 
 
+#ifdef HAVE_PYTHON
   EXTENSION(PyObject *__reduce__() const);
   EXTENSION(PyObject *__reduce__() const);
+#endif // HAVE_PYTHON
 
 
 public:
 public:
   typedef PT(GraphicsPipe) PipeConstructorFunc();
   typedef PT(GraphicsPipe) PipeConstructorFunc();
@@ -94,4 +96,4 @@ private:
 
 
 #include "graphicsPipeSelection.I"
 #include "graphicsPipeSelection.I"
 
 
-#endif
+#endif // !GRAPHICSPIPESELECTION_H

+ 5 - 1
panda/src/display/graphicsStateGuardian.cxx

@@ -3027,11 +3027,15 @@ framebuffer_copy_to_texture(Texture *, int, int, const DisplayRegion *,
  * into system memory, not texture memory.  Returns true on success, false on
  * into system memory, not texture memory.  Returns true on success, false on
  * failure.
  * failure.
  *
  *
+ * If a future is given, the operation may be scheduled to occur in the
+ * background, in which case the texture will be passed as the result of the
+ * future when the operation is complete.
+ *
  * This completely redefines the ram image of the indicated texture.
  * This completely redefines the ram image of the indicated texture.
  */
  */
 bool GraphicsStateGuardian::
 bool GraphicsStateGuardian::
 framebuffer_copy_to_ram(Texture *, int, int, const DisplayRegion *,
 framebuffer_copy_to_ram(Texture *, int, int, const DisplayRegion *,
-                        const RenderBuffer &) {
+                        const RenderBuffer &, ScreenshotRequest *) {
   return false;
   return false;
 }
 }
 
 

+ 10 - 7
panda/src/display/graphicsStateGuardian.h

@@ -256,7 +256,9 @@ PUBLISHED:
   MAKE_PROPERTY(texture_quality_override, get_texture_quality_override,
   MAKE_PROPERTY(texture_quality_override, get_texture_quality_override,
                                           set_texture_quality_override);
                                           set_texture_quality_override);
 
 
+#ifdef HAVE_PYTHON
   EXTENSION(PyObject *get_prepared_textures() const);
   EXTENSION(PyObject *get_prepared_textures() const);
+#endif // HAVE_PYTHON
   typedef bool TextureCallback(TextureContext *tc, void *callback_arg);
   typedef bool TextureCallback(TextureContext *tc, void *callback_arg);
   void traverse_prepared_textures(TextureCallback *func, void *callback_arg);
   void traverse_prepared_textures(TextureCallback *func, void *callback_arg);
 
 
@@ -265,7 +267,7 @@ PUBLISHED:
   void clear_flash_texture();
   void clear_flash_texture();
   Texture *get_flash_texture() const;
   Texture *get_flash_texture() const;
   MAKE_PROPERTY(flash_texture, get_flash_texture, set_flash_texture);
   MAKE_PROPERTY(flash_texture, get_flash_texture, set_flash_texture);
-#endif
+#endif // !NDEBUG || !CPPPARSER
 
 
 PUBLISHED:
 PUBLISHED:
   virtual bool has_extension(const std::string &extension) const;
   virtual bool has_extension(const std::string &extension) const;
@@ -426,7 +428,8 @@ public:
   virtual bool framebuffer_copy_to_texture
   virtual bool framebuffer_copy_to_texture
   (Texture *tex, int view, int z, const DisplayRegion *dr, const RenderBuffer &rb);
   (Texture *tex, int view, int z, const DisplayRegion *dr, const RenderBuffer &rb);
   virtual bool framebuffer_copy_to_ram
   virtual bool framebuffer_copy_to_ram
-  (Texture *tex, int view, int z, const DisplayRegion *dr, const RenderBuffer &rb);
+  (Texture *tex, int view, int z, const DisplayRegion *dr, const RenderBuffer &rb,
+   ScreenshotRequest *request = nullptr);
 
 
   virtual void bind_light(PointLight *light_obj, const NodePath &light,
   virtual void bind_light(PointLight *light_obj, const NodePath &light,
                           int light_id);
                           int light_id);
@@ -446,7 +449,7 @@ public:
 #ifdef DO_PSTATS
 #ifdef DO_PSTATS
   static void init_frame_pstats();
   static void init_frame_pstats();
   PStatThread get_pstats_thread();
   PStatThread get_pstats_thread();
-#endif
+#endif // DO_PSTATS
 
 
 protected:
 protected:
   virtual void reissue_transforms();
   virtual void reissue_transforms();
@@ -603,7 +606,7 @@ protected:
 #ifdef DO_PSTATS
 #ifdef DO_PSTATS
   int _pstats_gpu_thread;
   int _pstats_gpu_thread;
   bool _timer_queries_active;
   bool _timer_queries_active;
-#endif
+#endif // DO_PSTATS
 
 
   bool _copy_texture_inverted;
   bool _copy_texture_inverted;
   bool _supports_multisample;
   bool _supports_multisample;
@@ -648,9 +651,9 @@ protected:
 
 
 #ifndef NDEBUG
 #ifndef NDEBUG
   PT(Texture) _flash_texture;
   PT(Texture) _flash_texture;
-#else
+#else // !NDEBUG
   PT(Texture) _flash_texture_unused;
   PT(Texture) _flash_texture_unused;
-#endif
+#endif // !NDEBUG
 
 
 public:
 public:
   // Statistics
   // Statistics
@@ -761,4 +764,4 @@ EXPCL_PANDA_DISPLAY std::ostream &operator << (std::ostream &out, GraphicsStateG
 
 
 #include "graphicsStateGuardian.I"
 #include "graphicsStateGuardian.I"
 
 
-#endif
+#endif // !GRAPHICSSTATEGUARDIAN_H

+ 4 - 2
panda/src/display/graphicsWindow.h

@@ -56,7 +56,9 @@ PUBLISHED:
   void clear_rejected_properties();
   void clear_rejected_properties();
   WindowProperties get_rejected_properties() const;
   WindowProperties get_rejected_properties() const;
 
 
+#ifdef HAVE_PYTHON
   EXTENSION(void request_properties(PyObject *args, PyObject *kwds));
   EXTENSION(void request_properties(PyObject *args, PyObject *kwds));
+#endif // HAVE_PYTHON
 
 
   INLINE bool is_closed() const;
   INLINE bool is_closed() const;
   virtual bool is_active() const;
   virtual bool is_active() const;
@@ -169,7 +171,7 @@ private:
 #ifdef HAVE_PYTHON
 #ifdef HAVE_PYTHON
   typedef pset<GraphicsWindowProc*> PythonWinProcClasses;
   typedef pset<GraphicsWindowProc*> PythonWinProcClasses;
   PythonWinProcClasses _python_window_proc_classes;
   PythonWinProcClasses _python_window_proc_classes;
-#endif
+#endif // HAVE_PYTHON
 
 
 public:
 public:
   static TypeHandle get_class_type() {
   static TypeHandle get_class_type() {
@@ -194,4 +196,4 @@ private:
 
 
 #include "graphicsWindow.I"
 #include "graphicsWindow.I"
 
 
-#endif /* GRAPHICSWINDOW_H */
+#endif // !GRAPHICSWINDOW_H

+ 1 - 0
panda/src/display/p3display_composite2.cxx

@@ -9,6 +9,7 @@
 #include "parasiteBuffer.cxx"
 #include "parasiteBuffer.cxx"
 #include "standardMunger.cxx"
 #include "standardMunger.cxx"
 #include "touchInfo.cxx"
 #include "touchInfo.cxx"
+#include "screenshotRequest.cxx"
 #include "stereoDisplayRegion.cxx"
 #include "stereoDisplayRegion.cxx"
 #include "subprocessWindow.cxx"
 #include "subprocessWindow.cxx"
 #ifdef IS_OSX
 #ifdef IS_OSX

+ 39 - 0
panda/src/display/screenshotRequest.I

@@ -0,0 +1,39 @@
+/**
+ * PANDA 3D SOFTWARE
+ * Copyright (c) Carnegie Mellon University.  All rights reserved.
+ *
+ * All use of this software is subject to the terms of the revised BSD
+ * license.  You should have received a copy of this license along
+ * with this source code in a file named "LICENSE."
+ *
+ * @file screenshotRequest.I
+ * @author rdb
+ * @date 2022-12-26
+ */
+
+/**
+ *
+ */
+INLINE ScreenshotRequest::
+ScreenshotRequest(Texture *tex) :
+  _frame_number(ClockObject::get_global_clock()->get_frame_count()) {
+  _result = tex;
+  _result_ref = tex;
+}
+
+
+/**
+ * Returns the frame number in which the request originated.
+ */
+INLINE int ScreenshotRequest::
+get_frame_number() const {
+  return _frame_number;
+}
+
+/**
+ * Returns the resulting texture.  Can always be called.
+ */
+INLINE Texture *ScreenshotRequest::
+get_result() const {
+  return (Texture *)_result;
+}

+ 104 - 0
panda/src/display/screenshotRequest.cxx

@@ -0,0 +1,104 @@
+/**
+ * PANDA 3D SOFTWARE
+ * Copyright (c) Carnegie Mellon University.  All rights reserved.
+ *
+ * All use of this software is subject to the terms of the revised BSD
+ * license.  You should have received a copy of this license along
+ * with this source code in a file named "LICENSE."
+ *
+ * @file screenshotRequest.cxx
+ * @author rdb
+ * @date 2022-12-26
+ */
+
+#include "screenshotRequest.h"
+#include "lightMutexHolder.h"
+#include "pnmImage.h"
+#include "texture.h"
+
+TypeHandle ScreenshotRequest::_type_handle;
+
+/**
+ *
+ */
+void ScreenshotRequest::
+set_view_data(int view, const void *ptr) {
+  const int z = 0;
+
+  Texture *tex = get_result();
+  PTA_uchar new_image = tex->modify_ram_image();
+  unsigned char *image_ptr = new_image.p();
+  size_t image_size = tex->get_ram_image_size();
+  if (z >= 0 || view > 0) {
+    image_size = tex->get_expected_ram_page_size();
+    if (z >= 0) {
+      image_ptr += z * image_size;
+    }
+    if (view > 0) {
+      image_ptr += (view * tex->get_z_size()) * image_size;
+      nassertd(view < tex->get_num_views()) {
+        if (set_future_state(FS_cancelled)) {
+          notify_done(false);
+        }
+        return;
+      }
+    }
+  }
+  memcpy(image_ptr, ptr, image_size);
+}
+
+/**
+ *
+ */
+void ScreenshotRequest::
+finish() {
+  Texture *tex = get_result();
+
+  ++_got_num_views;
+  if (_got_num_views < tex->get_num_views()) {
+    return;
+  }
+
+  {
+    LightMutexHolder holder(_lock);
+    if (!_output_files.empty()) {
+      PNMImage image;
+      tex->store(image);
+
+      for (const auto &item : _output_files) {
+        image.set_comment(item.second);
+        image.write(item.first);
+      }
+    }
+
+    AsyncFuture::set_result(tex);
+    _output_files.clear();
+
+    if (!set_future_state(FS_finished)) {
+      return;
+    }
+  }
+
+  notify_done(true);
+}
+
+/**
+ * Adds a filename to write the screenshot to when it is available.  If the
+ * request is already done, performs the write synchronously.
+ */
+void ScreenshotRequest::
+add_output_file(const Filename &filename, const std::string &image_comment) {
+  if (!done()) {
+    LightMutexHolder holder(_lock);
+    if (!done()) {
+      _output_files[filename] = image_comment;
+      return;
+    }
+  }
+  // Was already done, write it right away.
+  Texture *tex = get_result();
+  PNMImage image;
+  tex->store(image);
+  image.set_comment(image_comment);
+  image.write(filename);
+}

+ 73 - 0
panda/src/display/screenshotRequest.h

@@ -0,0 +1,73 @@
+/**
+ * PANDA 3D SOFTWARE
+ * Copyright (c) Carnegie Mellon University.  All rights reserved.
+ *
+ * All use of this software is subject to the terms of the revised BSD
+ * license.  You should have received a copy of this license along
+ * with this source code in a file named "LICENSE."
+ *
+ * @file screenshotRequest.h
+ * @author rdb
+ * @date 2022-12-26
+ */
+
+#ifndef SCREENSHOTREQUEST_H
+#define SCREENSHOTREQUEST_H
+
+#include "pandabase.h"
+
+#include "asyncFuture.h"
+#include "clockObject.h"
+#include "filename.h"
+#include "lightMutex.h"
+#include "pmap.h"
+#include "texture.h"
+
+/**
+ * A class representing an asynchronous request to save a screenshot.
+ */
+class EXPCL_PANDA_DISPLAY ScreenshotRequest : public AsyncFuture {
+public:
+  INLINE ScreenshotRequest(Texture *tex);
+
+  INLINE int get_frame_number() const;
+  INLINE Texture *get_result() const;
+
+  void set_view_data(int view, const void *ptr);
+  void finish();
+
+PUBLISHED:
+  void add_output_file(const Filename &filename,
+                       const std::string &image_comment = "");
+
+private:
+  // It's possible to call save_screenshot multiple times in the same frame, so
+  // rather than have to store a vector of request objects, we just allow
+  // storing multiple filenames to handle this corner case.
+  LightMutex _lock;
+  pmap<Filename, std::string> _output_files;
+  int _got_num_views = 0;
+
+  int _frame_number = 0;
+
+public:
+  static TypeHandle get_class_type() {
+    return _type_handle;
+  }
+  static void init_type() {
+    AsyncFuture::init_type();
+    register_type(_type_handle, "ScreenshotRequest",
+                  AsyncFuture::get_class_type());
+    }
+  virtual TypeHandle get_type() const {
+    return get_class_type();
+  }
+  virtual TypeHandle force_init_type() {init_type(); return get_class_type();}
+
+private:
+  static TypeHandle _type_handle;
+};
+
+#include "screenshotRequest.I"
+
+#endif

+ 15 - 11
panda/src/display/windowProperties.h

@@ -44,7 +44,9 @@ PUBLISHED:
     M_confined,
     M_confined,
   };
   };
 
 
+#ifdef HAVE_PYTHON
   EXTENSION(WindowProperties(PyObject *self, PyObject *args, PyObject *kwds));
   EXTENSION(WindowProperties(PyObject *self, PyObject *args, PyObject *kwds));
+#endif // HAVE_PYTHON
 
 
 PUBLISHED:
 PUBLISHED:
   void operator = (const WindowProperties &copy);
   void operator = (const WindowProperties &copy);
@@ -70,9 +72,9 @@ PUBLISHED:
   INLINE void set_origin(int x_origin, int y_origin);
   INLINE void set_origin(int x_origin, int y_origin);
 #ifdef CPPPARSER
 #ifdef CPPPARSER
   INLINE LPoint2i get_origin() const;
   INLINE LPoint2i get_origin() const;
-#else
+#else // CPPPARSER
   INLINE const LPoint2i &get_origin() const;
   INLINE const LPoint2i &get_origin() const;
-#endif
+#endif // CPPPARSER
   INLINE int get_x_origin() const;
   INLINE int get_x_origin() const;
   INLINE int get_y_origin() const;
   INLINE int get_y_origin() const;
   INLINE bool has_origin() const;
   INLINE bool has_origin() const;
@@ -83,9 +85,9 @@ PUBLISHED:
   INLINE void set_size(int x_size, int y_size);
   INLINE void set_size(int x_size, int y_size);
 #ifdef CPPPARSER
 #ifdef CPPPARSER
   INLINE LVector2i get_size() const;
   INLINE LVector2i get_size() const;
-#else
+#else // CPPPARSER
   INLINE const LVector2i &get_size() const;
   INLINE const LVector2i &get_size() const;
-#endif
+#endif // CPPPARSER
   INLINE int get_x_size() const;
   INLINE int get_x_size() const;
   INLINE int get_y_size() const;
   INLINE int get_y_size() const;
   INLINE bool has_size() const;
   INLINE bool has_size() const;
@@ -102,9 +104,9 @@ PUBLISHED:
   INLINE void set_title(const std::string &title);
   INLINE void set_title(const std::string &title);
 #ifdef CPPPARSER
 #ifdef CPPPARSER
   INLINE std::string get_title() const;
   INLINE std::string get_title() const;
-#else
+#else // CPPPARSER
   INLINE const std::string &get_title() const;
   INLINE const std::string &get_title() const;
-#endif
+#endif // CPPPARSER
   INLINE bool has_title() const;
   INLINE bool has_title() const;
   INLINE void clear_title();
   INLINE void clear_title();
   MAKE_PROPERTY2(title, has_title, get_title, set_title, clear_title);
   MAKE_PROPERTY2(title, has_title, get_title, set_title, clear_title);
@@ -172,9 +174,9 @@ PUBLISHED:
   INLINE void set_icon_filename(const Filename &icon_filename);
   INLINE void set_icon_filename(const Filename &icon_filename);
 #ifdef CPPPARSER
 #ifdef CPPPARSER
   INLINE Filename get_icon_filename() const;
   INLINE Filename get_icon_filename() const;
-#else
+#else // CPPPARSER
   INLINE const Filename &get_icon_filename() const;
   INLINE const Filename &get_icon_filename() const;
-#endif
+#endif // CPPPARSER
   INLINE bool has_icon_filename() const;
   INLINE bool has_icon_filename() const;
   INLINE void clear_icon_filename();
   INLINE void clear_icon_filename();
   MAKE_PROPERTY2(icon_filename, has_icon_filename, get_icon_filename,
   MAKE_PROPERTY2(icon_filename, has_icon_filename, get_icon_filename,
@@ -183,9 +185,9 @@ PUBLISHED:
   INLINE void set_cursor_filename(const Filename &cursor_filename);
   INLINE void set_cursor_filename(const Filename &cursor_filename);
 #ifdef CPPPARSER
 #ifdef CPPPARSER
   INLINE Filename get_cursor_filename() const;
   INLINE Filename get_cursor_filename() const;
-#else
+#else // CPPPARSER
   INLINE const Filename &get_cursor_filename() const;
   INLINE const Filename &get_cursor_filename() const;
-#endif
+#endif // CPPPARSER
   INLINE bool has_cursor_filename() const;
   INLINE bool has_cursor_filename() const;
   INLINE void clear_cursor_filename();
   INLINE void clear_cursor_filename();
   MAKE_PROPERTY2(cursor_filename, has_cursor_filename, get_cursor_filename,
   MAKE_PROPERTY2(cursor_filename, has_cursor_filename, get_cursor_filename,
@@ -205,8 +207,10 @@ PUBLISHED:
   MAKE_PROPERTY2(parent_window, has_parent_window, get_parent_window,
   MAKE_PROPERTY2(parent_window, has_parent_window, get_parent_window,
                                 set_parent_window, clear_parent_window);
                                 set_parent_window, clear_parent_window);
 
 
+#ifdef HAVE_PYTHON
   EXTENSION(PyObject *__getstate__(PyObject *self) const);
   EXTENSION(PyObject *__getstate__(PyObject *self) const);
   EXTENSION(void __setstate__(PyObject *self, PyObject *state));
   EXTENSION(void __setstate__(PyObject *self, PyObject *state));
+#endif // HAVE_PYTHON
 
 
   void add_properties(const WindowProperties &other);
   void add_properties(const WindowProperties &other);
 
 
@@ -278,4 +282,4 @@ INLINE std::ostream &operator << (std::ostream &out, const WindowProperties &pro
 
 
 #include "windowProperties.I"
 #include "windowProperties.I"
 
 
-#endif
+#endif // !WINDOWPROPERTIES_H

Some files were not shown because too many files changed in this diff