Merge branch 'master' into astron-master
This commit is contained in:
commit
aa7c1d76bf
|
|
@ -0,0 +1,2 @@
|
|||
open_collective: panda3d
|
||||
|
||||
|
|
@ -4,11 +4,12 @@
|
|||
/targetroot/
|
||||
/dstroot/
|
||||
|
||||
# Core dumps
|
||||
# Core dumps and traces
|
||||
core
|
||||
core.*
|
||||
vgcore.*
|
||||
*.core
|
||||
*.trace
|
||||
|
||||
# Editor files/directories
|
||||
*.save
|
||||
|
|
@ -26,6 +27,7 @@ vgcore.*
|
|||
/+DESC
|
||||
/+MANIFEST
|
||||
/pkg-plist
|
||||
/debug.ks
|
||||
|
||||
# Produced installer/executables
|
||||
/*.exe
|
||||
|
|
@ -36,6 +38,7 @@ vgcore.*
|
|||
/*.dmg
|
||||
/*.whl
|
||||
/*.txz
|
||||
/*.apk
|
||||
|
||||
# CMake
|
||||
/build/
|
||||
|
|
|
|||
|
|
@ -44,7 +44,6 @@ install:
|
|||
- $PYTHONV -m pip install pytest
|
||||
script:
|
||||
- $PYTHONV makepanda/makepanda.py --everything --git-commit $TRAVIS_COMMIT $FLAGS --threads 4
|
||||
- test -n "$SKIP_TESTS" || LD_LIBRARY_PATH=built/lib PYTHONPATH=built $PYTHONV makepanda/test_imports.py
|
||||
- test -n "$SKIP_TESTS" || LD_LIBRARY_PATH=built/lib PYTHONPATH=built $PYTHONV -m pytest -v tests
|
||||
notifications:
|
||||
irc:
|
||||
|
|
@ -54,3 +53,8 @@ notifications:
|
|||
on_failure: always
|
||||
use_notice: true
|
||||
skip_join: false
|
||||
webhooks:
|
||||
urls:
|
||||
- https://www.panda3d.org/webhooks/travis-ci.php
|
||||
on_success: change
|
||||
on_failure: always
|
||||
|
|
|
|||
|
|
@ -0,0 +1,35 @@
|
|||
# Panda3D Backers
|
||||
|
||||
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
|
||||
|
||||

|
||||
|
||||
* [tcdude](https://opencollective.com/tizilogic)
|
||||
|
||||
## Bronze Sponsors
|
||||
|
||||

|
||||
|
||||
* [Mitchell Stokes](https://opencollective.com/mitchell-stokes)
|
||||
* [Daniel Stokes](https://opencollective.com/daniel-stokes)
|
||||
* [David Rose](https://opencollective.com/david-rose)
|
||||
* [Carnetsoft](https://cs-driving-simulator.com/)
|
||||
|
||||
## Benefactors
|
||||
|
||||

|
||||
|
||||
* Sam Edwards
|
||||
* Max Voss
|
||||
|
||||
## Enthusiasts
|
||||
|
||||

|
||||
|
||||
* Eric Thomson
|
||||
|
||||
## Backers
|
||||
|
||||

|
||||
70
README.md
70
README.md
|
|
@ -1,6 +1,8 @@
|
|||
[](https://travis-ci.org/panda3d/panda3d)
|
||||
[](https://opencollective.com/panda3d)
|
||||
[](https://opencollective.com/panda3d)
|
||||
|
||||
<img src="https://avatars2.githubusercontent.com/u/590956?v=3&s=200" align="right" />
|
||||
<img src="https://avatars2.githubusercontent.com/u/590956?v=3&s=500" align="right" width="200"/>
|
||||
|
||||
Panda3D
|
||||
=======
|
||||
|
|
@ -8,12 +10,12 @@ Panda3D
|
|||
Panda3D is a game engine, a framework for 3D rendering and game development for
|
||||
Python and C++ programs. Panda3D is open-source and free for any purpose,
|
||||
including commercial ventures, thanks to its
|
||||
[liberal license](https://www.panda3d.org/license.php). To learn more about
|
||||
Panda3D's capabilities, visit the [gallery](https://www.panda3d.org/gallery.php)
|
||||
and the [feature list](https://www.panda3d.org/features.php). To learn how to
|
||||
use Panda3D, check the [documentation](https://www.panda3d.org/documentation.php)
|
||||
[liberal license](https://www.panda3d.org/license/). To learn more about
|
||||
Panda3D's capabilities, visit the [gallery](https://www.panda3d.org/gallery/)
|
||||
and the [feature list](https://www.panda3d.org/features/). To learn how to
|
||||
use Panda3D, check the [documentation](https://www.panda3d.org/documentation/)
|
||||
resources. If you get stuck, ask for help from our active
|
||||
[community](https://www.panda3d.org/community.php).
|
||||
[community](https://discourse.panda3d.org).
|
||||
|
||||
Panda3D is licensed under the Modified BSD License. See the LICENSE file for
|
||||
more details.
|
||||
|
|
@ -21,7 +23,16 @@ more details.
|
|||
Installing Panda3D
|
||||
==================
|
||||
|
||||
By far, the easiest way to install the latest development build of Panda3D
|
||||
The latest Panda3D SDK can be downloaded from
|
||||
[this page](https://www.panda3d.org/download/sdk-1-10-4-1/).
|
||||
If you are familiar with installing Python packages, you can use
|
||||
the following comand:
|
||||
|
||||
```bash
|
||||
pip install panda3d
|
||||
```
|
||||
|
||||
The easiest way to install the latest development build of Panda3D
|
||||
into an existing Python installation is using the following command:
|
||||
|
||||
```bash
|
||||
|
|
@ -31,9 +42,7 @@ pip install --pre --extra-index-url https://archive.panda3d.org/ panda3d
|
|||
If this command fails, please make sure your version of pip is up-to-date.
|
||||
|
||||
If you prefer to install the full SDK with all tools, the latest development
|
||||
builds can be obtained from this page:
|
||||
|
||||
https://www.panda3d.org/download.php?sdk&version=devel
|
||||
builds can be obtained from [this page](https://www.panda3d.org/download.php?version=devel&sdk).
|
||||
|
||||
These are automatically kept up-to-date with the latest GitHub version of Panda.
|
||||
|
||||
|
|
@ -55,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
|
||||
building them from source.
|
||||
|
||||
https://www.panda3d.org/download/panda3d-1.10.0/panda3d-1.10.0-tools-win64.zip
|
||||
https://www.panda3d.org/download/panda3d-1.10.0/panda3d-1.10.0-tools-win32.zip
|
||||
https://www.panda3d.org/download/panda3d-1.10.4.1/panda3d-1.10.4.1-tools-win64.zip
|
||||
https://www.panda3d.org/download/panda3d-1.10.4.1/panda3d-1.10.4.1-tools-win32.zip
|
||||
|
||||
After acquiring these dependencies, you may simply build Panda3D from the
|
||||
command prompt using the following command. (Change `14.1` to `14` if you are
|
||||
|
|
@ -96,14 +105,14 @@ python makepanda/makepanda.py --everything --installer --no-egl --no-gles --no-g
|
|||
You will probably see some warnings saying that it's unable to find several
|
||||
dependency packages. You should determine which ones you want to include in
|
||||
your build and install the respective development packages. You may visit
|
||||
[this manual page](https://www.panda3d.org/manual/index.php/Dependencies)
|
||||
[this manual page](https://www.panda3d.org/manual/?title=Third-party_dependencies_and_license_info)
|
||||
for an overview of the various dependencies.
|
||||
|
||||
If you are on Ubuntu, this command should cover the most frequently
|
||||
used third-party packages:
|
||||
|
||||
```bash
|
||||
sudo apt-get install build-essential pkg-config python-dev libpng-dev libjpeg-dev libtiff-dev zlib1g-dev libssl-dev libx11-dev libgl1-mesa-dev libxrandr-dev libxxf86dga-dev libxcursor-dev bison flex libfreetype6-dev libvorbis-dev libeigen3-dev libopenal-dev libode-dev libbullet-dev nvidia-cg-toolkit libgtk2.0-dev libassimp-dev libopenexr-dev
|
||||
sudo apt-get install build-essential pkg-config fakeroot python-dev libpng-dev libjpeg-dev libtiff-dev zlib1g-dev libssl-dev libx11-dev libgl1-mesa-dev libxrandr-dev libxxf86dga-dev libxcursor-dev bison flex libfreetype6-dev libvorbis-dev libeigen3-dev libopenal-dev libode-dev libbullet-dev nvidia-cg-toolkit libgtk2.0-dev libassimp-dev libopenexr-dev
|
||||
```
|
||||
|
||||
Once Panda3D has built, you can either install the .deb or .rpm package that
|
||||
|
|
@ -126,7 +135,7 @@ macOS
|
|||
-----
|
||||
|
||||
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.9.4/panda3d-1.9.4-tools-mac.tar.gz).
|
||||
compile Panda3D, which can be acquired from [here](https://www.panda3d.org/download/panda3d-1.10.4.1/panda3d-1.10.4.1-tools-mac.tar.gz).
|
||||
|
||||
After placing the thirdparty directory inside the panda3d source directory,
|
||||
you may build Panda3D using a command like the following:
|
||||
|
|
@ -135,9 +144,8 @@ you may build Panda3D using a command like the following:
|
|||
python makepanda/makepanda.py --everything --installer
|
||||
```
|
||||
|
||||
In order to make a universal build, pass the --universal flag. You may also
|
||||
target a specific minimum macOS version using the --osxtarget flag followed
|
||||
by the release number, eg. 10.6 or 10.7.
|
||||
You may target a specific minimum macOS version using the --osxtarget flag
|
||||
followed by the release number, eg. 10.7 or 10.9.
|
||||
|
||||
If the build was successful, makepanda will have generated a .dmg file in
|
||||
the source directory containing the installer. Simply open it and run the
|
||||
|
|
@ -151,7 +159,7 @@ install the requisite packages using the system package manager. To install
|
|||
the recommended set of dependencies, you can use this command:
|
||||
|
||||
```bash
|
||||
pkg install pkgconf png jpeg-turbo tiff freetype2 eigen squish openal opusfile libvorbis libX11 libGL ode bullet assimp openexr
|
||||
pkg install pkgconf bison png jpeg-turbo tiff freetype2 harfbuzz eigen squish openal opusfile libvorbis libX11 mesa-libs ode bullet assimp openexr
|
||||
```
|
||||
|
||||
You will also need to choose which version of Python you want to use.
|
||||
|
|
@ -183,7 +191,7 @@ pkg install python-dev termux-tools ndk-stl ndk-sysroot clang libvorbis-dev libo
|
|||
Then, you can build and install the .apk right away using these commands:
|
||||
|
||||
```bash
|
||||
python makepanda/makepanda.py --everything --target android-21 --installer
|
||||
python makepanda/makepanda.py --everything --target android-21 --no-tiff --installer
|
||||
xdg-open panda3d.apk
|
||||
```
|
||||
|
||||
|
|
@ -222,3 +230,25 @@ models that are necessary for the developers to reproduce the issue.
|
|||
|
||||
If you're not sure whether you've encountered a bug, feel free to ask about
|
||||
it in the forums or the IRC channel first.
|
||||
|
||||
Supporting the Project
|
||||
======================
|
||||
|
||||
If you would like to support the project financially, visit
|
||||
[our campaign on OpenCollective](https://opencollective.com/panda3d). Your
|
||||
contributions help us accelerate the development of Panda3D.
|
||||
|
||||
For the list of backers, see the [BACKERS.md](BACKERS.md) file or visit the
|
||||
[Sponsors page](https://www.panda3d.org/sponsors) on our web site. Thank you
|
||||
to everyone who has donated!
|
||||
|
||||
<a href="https://opencollective.com/panda3d" target="_blank">
|
||||
<img src="https://opencollective.com/panda3d/contribute/button@2x.png?color=blue" width=300 />
|
||||
</a>
|
||||
|
||||
### Gold Sponsors
|
||||
[](https://opencollective.com/panda3d/tiers/gold-sponsor/0/website)
|
||||
[](https://opencollective.com/panda3d/tiers/gold-sponsor/1/website)
|
||||
[](https://opencollective.com/panda3d/tiers/gold-sponsor/2/website)
|
||||
[](https://opencollective.com/panda3d/tiers/gold-sponsor/3/website)
|
||||
[](https://opencollective.com/panda3d/tiers/gold-sponsor/4/website)
|
||||
|
|
|
|||
|
|
@ -13,6 +13,17 @@
|
|||
|
||||
#include "aiBehaviors.h"
|
||||
|
||||
#include "arrival.h"
|
||||
#include "evade.h"
|
||||
#include "flee.h"
|
||||
#include "flock.h"
|
||||
#include "obstacleAvoidance.h"
|
||||
#include "pathFind.h"
|
||||
#include "pathFollow.h"
|
||||
#include "pursue.h"
|
||||
#include "seek.h"
|
||||
#include "wander.h"
|
||||
|
||||
using std::cout;
|
||||
using std::endl;
|
||||
using std::string;
|
||||
|
|
|
|||
|
|
@ -28,6 +28,9 @@ class PathFollow;
|
|||
class PathFind;
|
||||
class ObstacleAvoidance;
|
||||
|
||||
#include "flee.h"
|
||||
#include "evade.h"
|
||||
|
||||
typedef std::list<Flee, std::allocator<Flee> > ListFlee;
|
||||
typedef std::list<Evade, std::allocator<Evade> > ListEvade;
|
||||
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@
|
|||
#define _AI_GLOBALS_H
|
||||
|
||||
#include "config_ai.h"
|
||||
#include "pandaFramework.h"
|
||||
#include "textNode.h"
|
||||
#include "pandaSystem.h"
|
||||
|
||||
|
|
|
|||
|
|
@ -13,6 +13,9 @@
|
|||
|
||||
#include "arrival.h"
|
||||
|
||||
#include "pursue.h"
|
||||
#include "seek.h"
|
||||
|
||||
Arrival::Arrival(AICharacter *ai_ch, double distance) {
|
||||
_ai_char = ai_ch;
|
||||
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@
|
|||
|
||||
#include "obstacleAvoidance.h"
|
||||
|
||||
#include "aiWorld.h"
|
||||
|
||||
ObstacleAvoidance::
|
||||
ObstacleAvoidance(AICharacter *ai_char, float feeler_length) {
|
||||
_ai_char = ai_char;
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@
|
|||
|
||||
#include "pathFind.h"
|
||||
|
||||
#include "pathFollow.h"
|
||||
|
||||
using std::cout;
|
||||
using std::endl;
|
||||
using std::string;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,20 @@
|
|||
/**
|
||||
* 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 pathFind.cxx
|
||||
* @author Deepak, John, Navin
|
||||
* @date 2009-10-24
|
||||
*/
|
||||
|
||||
#include "pathFollow.h"
|
||||
|
||||
#include "pathFind.h"
|
||||
|
||||
PathFollow::PathFollow(AICharacter *ai_ch, float follow_wt) {
|
||||
_follow_weight = follow_wt;
|
||||
_curr_path_waypoint = -1;
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@
|
|||
#define SHADOWATLAS_H
|
||||
|
||||
#include "pandabase.h"
|
||||
#include "lvecBase4.h"
|
||||
#include "luse.h"
|
||||
|
||||
NotifyCategoryDecl(shadowatlas, EXPORT_CLASS, EXPORT_TEMPL);
|
||||
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@ class Actor(DirectObject, NodePath):
|
|||
|
||||
def __init__(self, filename = None, animBundle = None):
|
||||
self.filename = filename
|
||||
self.animBundle = None
|
||||
self.animBundle = animBundle
|
||||
self.animControl = None
|
||||
|
||||
def makeCopy(self):
|
||||
|
|
@ -1947,6 +1947,7 @@ class Actor(DirectObject, NodePath):
|
|||
animName = acc.getAnimName(i)
|
||||
|
||||
animDef = Actor.AnimDef()
|
||||
animDef.animBundle = animControl.getAnim()
|
||||
animDef.animControl = animControl
|
||||
self.__animControlDict[lodName][partName][animName] = animDef
|
||||
|
||||
|
|
@ -2507,7 +2508,7 @@ class Actor(DirectObject, NodePath):
|
|||
else:
|
||||
animNames = [animName]
|
||||
for animName in animNames:
|
||||
if animName is 'nothing':
|
||||
if animName == 'nothing':
|
||||
continue
|
||||
thisAnim = ''
|
||||
totalEffect = 0.
|
||||
|
|
|
|||
|
|
@ -71,90 +71,6 @@ class GravityWalker(DirectObject.DirectObject):
|
|||
self.isAirborne = 0
|
||||
self.highMark = 0
|
||||
|
||||
"""
|
||||
def spawnTest(self):
|
||||
assert self.notify.debugStateCall(self)
|
||||
if not self.wantDebugIndicator:
|
||||
return
|
||||
from pandac.PandaModules import *
|
||||
from direct.interval.IntervalGlobal import *
|
||||
from toontown.coghq import MovingPlatform
|
||||
|
||||
if hasattr(self, "platform"):
|
||||
# Remove the prior instantiation:
|
||||
self.moveIval.pause()
|
||||
del self.moveIval
|
||||
self.platform.destroy()
|
||||
del self.platform
|
||||
self.platform2.destroy()
|
||||
del self.platform2
|
||||
|
||||
model = loader.loadModel('phase_9/models/cogHQ/platform1')
|
||||
fakeId = id(self)
|
||||
self.platform = MovingPlatform.MovingPlatform()
|
||||
self.platform.setupCopyModel(fakeId, model, 'platformcollision')
|
||||
self.platformRoot = render.attachNewNode("GravityWalker-spawnTest-%s"%fakeId)
|
||||
self.platformRoot.setPos(base.localAvatar, Vec3(0.0, 0.0, 1.0))
|
||||
self.platformRoot.setHpr(base.localAvatar, Vec3.zero())
|
||||
self.platform.reparentTo(self.platformRoot)
|
||||
|
||||
self.platform2 = MovingPlatform.MovingPlatform()
|
||||
self.platform2.setupCopyModel(1+fakeId, model, 'platformcollision')
|
||||
self.platform2Root = render.attachNewNode("GravityWalker-spawnTest2-%s"%fakeId)
|
||||
self.platform2Root.setPos(base.localAvatar, Vec3(-16.0, 30.0, 1.0))
|
||||
self.platform2Root.setHpr(base.localAvatar, Vec3.zero())
|
||||
self.platform2.reparentTo(self.platform2Root)
|
||||
|
||||
duration = 5
|
||||
self.moveIval = Parallel(
|
||||
Sequence(
|
||||
WaitInterval(0.3),
|
||||
LerpPosInterval(self.platform, duration,
|
||||
Vec3(0.0, 30.0, 0.0),
|
||||
name='platformOut%s' % fakeId,
|
||||
fluid = 1),
|
||||
WaitInterval(0.3),
|
||||
LerpPosInterval(self.platform, duration,
|
||||
Vec3(0.0, 0.0, 0.0),
|
||||
name='platformBack%s' % fakeId,
|
||||
fluid = 1),
|
||||
WaitInterval(0.3),
|
||||
LerpPosInterval(self.platform, duration,
|
||||
Vec3(0.0, 0.0, 30.0),
|
||||
name='platformUp%s' % fakeId,
|
||||
fluid = 1),
|
||||
WaitInterval(0.3),
|
||||
LerpPosInterval(self.platform, duration,
|
||||
Vec3(0.0, 0.0, 0.0),
|
||||
name='platformDown%s' % fakeId,
|
||||
fluid = 1),
|
||||
),
|
||||
Sequence(
|
||||
WaitInterval(0.3),
|
||||
LerpPosInterval(self.platform2, duration,
|
||||
Vec3(0.0, -30.0, 0.0),
|
||||
name='platform2Out%s' % fakeId,
|
||||
fluid = 1),
|
||||
WaitInterval(0.3),
|
||||
LerpPosInterval(self.platform2, duration,
|
||||
Vec3(0.0, 30.0, 30.0),
|
||||
name='platform2Back%s' % fakeId,
|
||||
fluid = 1),
|
||||
WaitInterval(0.3),
|
||||
LerpPosInterval(self.platform2, duration,
|
||||
Vec3(0.0, -30.0, 0.0),
|
||||
name='platform2Up%s' % fakeId,
|
||||
fluid = 1),
|
||||
WaitInterval(0.3),
|
||||
LerpPosInterval(self.platform2, duration,
|
||||
Vec3(0.0, 0.0, 0.0),
|
||||
name='platformDown%s' % fakeId,
|
||||
fluid = 1),
|
||||
),
|
||||
name='platformIval%s' % fakeId,
|
||||
)
|
||||
self.moveIval.loop()
|
||||
"""
|
||||
def setWalkSpeed(self, forward, jump, reverse, rotate):
|
||||
assert self.notify.debugStateCall(self)
|
||||
self.avatarControlForwardSpeed=forward
|
||||
|
|
|
|||
|
|
@ -67,51 +67,6 @@ class PhysicsWalker(DirectObject.DirectObject):
|
|||
self.isAirborne = 0
|
||||
self.highMark = 0
|
||||
|
||||
"""
|
||||
def spawnTest(self):
|
||||
assert self.debugPrint("\n\nspawnTest()\n")
|
||||
if not self.wantDebugIndicator:
|
||||
return
|
||||
from pandac.PandaModules import *
|
||||
from direct.interval.IntervalGlobal import *
|
||||
from toontown.coghq import MovingPlatform
|
||||
|
||||
if hasattr(self, "platform"):
|
||||
# Remove the prior instantiation:
|
||||
self.moveIval.pause()
|
||||
del self.moveIval
|
||||
self.platform.destroy()
|
||||
del self.platform
|
||||
|
||||
model = loader.loadModel('phase_9/models/cogHQ/platform1')
|
||||
fakeId = id(self)
|
||||
self.platform = MovingPlatform.MovingPlatform()
|
||||
self.platform.setupCopyModel(fakeId, model, 'platformcollision')
|
||||
self.platformRoot = render.attachNewNode("physicsWalker-spawnTest-%s"%fakeId)
|
||||
self.platformRoot.setPos(base.localAvatar, Vec3(0.0, 3.0, 1.0))
|
||||
self.platformRoot.setHpr(base.localAvatar, Vec3.zero())
|
||||
self.platform.reparentTo(self.platformRoot)
|
||||
|
||||
startPos = Vec3(0.0, -15.0, 0.0)
|
||||
endPos = Vec3(0.0, 15.0, 0.0)
|
||||
distance = Vec3(startPos-endPos).length()
|
||||
duration = distance/4
|
||||
self.moveIval = Sequence(
|
||||
WaitInterval(0.3),
|
||||
LerpPosInterval(self.platform, duration,
|
||||
endPos, startPos=startPos,
|
||||
name='platformOut%s' % fakeId,
|
||||
fluid = 1),
|
||||
WaitInterval(0.3),
|
||||
LerpPosInterval(self.platform, duration,
|
||||
startPos, startPos=endPos,
|
||||
name='platformBack%s' % fakeId,
|
||||
fluid = 1),
|
||||
name='platformIval%s' % fakeId,
|
||||
)
|
||||
self.moveIval.loop()
|
||||
"""
|
||||
|
||||
def setWalkSpeed(self, forward, jump, reverse, rotate):
|
||||
assert self.debugPrint("setWalkSpeed()")
|
||||
self.avatarControlForwardSpeed=forward
|
||||
|
|
|
|||
|
|
@ -16,22 +16,13 @@
|
|||
#include "dcAtomicField.h"
|
||||
#include "hashGenerator.h"
|
||||
#include "dcindent.h"
|
||||
#include "dcmsgtypes.h"
|
||||
|
||||
#include "dcClassParameter.h"
|
||||
#include <algorithm>
|
||||
|
||||
#ifdef HAVE_PYTHON
|
||||
#include "py_panda.h"
|
||||
#endif
|
||||
|
||||
using std::ostream;
|
||||
using std::ostringstream;
|
||||
using std::string;
|
||||
|
||||
#ifdef WITHIN_PANDA
|
||||
#include "pStatTimer.h"
|
||||
|
||||
#ifndef CPPPARSER
|
||||
PStatCollector DCClass::_update_pcollector("App:Show code:readerPollTask:Update");
|
||||
PStatCollector DCClass::_generate_pcollector("App:Show code:readerPollTask:Generate");
|
||||
|
|
@ -86,10 +77,7 @@ DCClass(DCFile *dc_file, const string &name, bool is_struct, bool bogus_class) :
|
|||
_number = -1;
|
||||
_constructor = nullptr;
|
||||
|
||||
#ifdef HAVE_PYTHON
|
||||
_class_def = nullptr;
|
||||
_owner_class_def = nullptr;
|
||||
#endif
|
||||
_python_class_defs = nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -105,11 +93,6 @@ DCClass::
|
|||
for (fi = _fields.begin(); fi != _fields.end(); ++fi) {
|
||||
delete (*fi);
|
||||
}
|
||||
|
||||
#ifdef HAVE_PYTHON
|
||||
Py_XDECREF(_class_def);
|
||||
Py_XDECREF(_owner_class_def);
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -335,7 +318,7 @@ inherits_from_bogus_class() const {
|
|||
* Write a string representation of this instance to <out>.
|
||||
*/
|
||||
void DCClass::
|
||||
output(ostream &out) const {
|
||||
output(std::ostream &out) const {
|
||||
if (_is_struct) {
|
||||
out << "struct";
|
||||
} else {
|
||||
|
|
@ -346,678 +329,11 @@ output(ostream &out) const {
|
|||
}
|
||||
}
|
||||
|
||||
#ifdef HAVE_PYTHON
|
||||
/**
|
||||
* Returns true if the DCClass object has an associated Python class
|
||||
* definition, false otherwise.
|
||||
*/
|
||||
bool DCClass::
|
||||
has_class_def() const {
|
||||
return (_class_def != nullptr);
|
||||
}
|
||||
#endif // HAVE_PYTHON
|
||||
|
||||
#ifdef HAVE_PYTHON
|
||||
/**
|
||||
* Sets the class object associated with this DistributedClass. This object
|
||||
* will be used to construct new instances of the class.
|
||||
*/
|
||||
void DCClass::
|
||||
set_class_def(PyObject *class_def) {
|
||||
Py_XINCREF(class_def);
|
||||
Py_XDECREF(_class_def);
|
||||
_class_def = class_def;
|
||||
}
|
||||
#endif // HAVE_PYTHON
|
||||
|
||||
#ifdef HAVE_PYTHON
|
||||
/**
|
||||
* Returns the class object that was previously associated with this
|
||||
* DistributedClass. This will return a new reference to the object.
|
||||
*/
|
||||
PyObject *DCClass::
|
||||
get_class_def() const {
|
||||
if (_class_def == nullptr) {
|
||||
Py_INCREF(Py_None);
|
||||
return Py_None;
|
||||
}
|
||||
|
||||
Py_INCREF(_class_def);
|
||||
return _class_def;
|
||||
}
|
||||
#endif // HAVE_PYTHON
|
||||
|
||||
#ifdef HAVE_PYTHON
|
||||
/**
|
||||
* Returns true if the DCClass object has an associated Python owner class
|
||||
* definition, false otherwise.
|
||||
*/
|
||||
bool DCClass::
|
||||
has_owner_class_def() const {
|
||||
return (_owner_class_def != nullptr);
|
||||
}
|
||||
#endif // HAVE_PYTHON
|
||||
|
||||
#ifdef HAVE_PYTHON
|
||||
/**
|
||||
* Sets the owner class object associated with this DistributedClass. This
|
||||
* object will be used to construct new owner instances of the class.
|
||||
*/
|
||||
void DCClass::
|
||||
set_owner_class_def(PyObject *owner_class_def) {
|
||||
Py_XINCREF(owner_class_def);
|
||||
Py_XDECREF(_owner_class_def);
|
||||
_owner_class_def = owner_class_def;
|
||||
}
|
||||
#endif // HAVE_PYTHON
|
||||
|
||||
#ifdef HAVE_PYTHON
|
||||
/**
|
||||
* Returns the owner class object that was previously associated with this
|
||||
* DistributedClass. This will return a new reference to the object.
|
||||
*/
|
||||
PyObject *DCClass::
|
||||
get_owner_class_def() const {
|
||||
if (_owner_class_def == nullptr) {
|
||||
Py_INCREF(Py_None);
|
||||
return Py_None;
|
||||
}
|
||||
|
||||
Py_INCREF(_owner_class_def);
|
||||
return _owner_class_def;
|
||||
}
|
||||
#endif // HAVE_PYTHON
|
||||
|
||||
#ifdef HAVE_PYTHON
|
||||
/**
|
||||
* Extracts the update message out of the packer and applies it to the
|
||||
* indicated object by calling the appropriate method.
|
||||
*/
|
||||
void DCClass::
|
||||
receive_update(PyObject *distobj, DatagramIterator &di) const {
|
||||
#ifdef WITHIN_PANDA
|
||||
PStatTimer timer(((DCClass *)this)->_class_update_pcollector);
|
||||
#endif
|
||||
DCPacker packer;
|
||||
const char *data = (const char *)di.get_datagram().get_data();
|
||||
packer.set_unpack_data(data + di.get_current_index(),
|
||||
di.get_remaining_size(), false);
|
||||
|
||||
int field_id = packer.raw_unpack_uint16();
|
||||
DCField *field = get_field_by_index(field_id);
|
||||
if (field == nullptr) {
|
||||
ostringstream strm;
|
||||
strm
|
||||
<< "Received update for field " << field_id << ", not in class "
|
||||
<< get_name();
|
||||
nassert_raise(strm.str());
|
||||
return;
|
||||
}
|
||||
|
||||
packer.begin_unpack(field);
|
||||
field->receive_update(packer, distobj);
|
||||
packer.end_unpack();
|
||||
|
||||
di.skip_bytes(packer.get_num_unpacked_bytes());
|
||||
|
||||
}
|
||||
#endif // HAVE_PYTHON
|
||||
|
||||
#ifdef HAVE_PYTHON
|
||||
/**
|
||||
* Processes a big datagram that includes all of the "required" fields that
|
||||
* are sent along with a normal "generate with required" message. This is all
|
||||
* of the atomic fields that are marked "broadcast required".
|
||||
*/
|
||||
void DCClass::
|
||||
receive_update_broadcast_required(PyObject *distobj, DatagramIterator &di) const {
|
||||
#ifdef WITHIN_PANDA
|
||||
PStatTimer timer(((DCClass *)this)->_class_update_pcollector);
|
||||
#endif
|
||||
DCPacker packer;
|
||||
const char *data = (const char *)di.get_datagram().get_data();
|
||||
packer.set_unpack_data(data + di.get_current_index(),
|
||||
di.get_remaining_size(), false);
|
||||
|
||||
int num_fields = get_num_inherited_fields();
|
||||
for (int i = 0; i < num_fields && !PyErr_Occurred(); ++i) {
|
||||
DCField *field = get_inherited_field(i);
|
||||
if (field->as_molecular_field() == nullptr &&
|
||||
field->is_required() && field->is_broadcast()) {
|
||||
packer.begin_unpack(field);
|
||||
field->receive_update(packer, distobj);
|
||||
if (!packer.end_unpack()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
di.skip_bytes(packer.get_num_unpacked_bytes());
|
||||
}
|
||||
#endif // HAVE_PYTHON
|
||||
|
||||
#ifdef HAVE_PYTHON
|
||||
/**
|
||||
* Processes a big datagram that includes all of the "required" fields that
|
||||
* are sent along with a normal "generate with required" message. This is all
|
||||
* of the atomic fields that are marked "broadcast ownrecv". Should be used
|
||||
* for 'owner-view' objects.
|
||||
*/
|
||||
void DCClass::
|
||||
receive_update_broadcast_required_owner(PyObject *distobj,
|
||||
DatagramIterator &di) const {
|
||||
#ifdef WITHIN_PANDA
|
||||
PStatTimer timer(((DCClass *)this)->_class_update_pcollector);
|
||||
#endif
|
||||
DCPacker packer;
|
||||
const char *data = (const char *)di.get_datagram().get_data();
|
||||
packer.set_unpack_data(data + di.get_current_index(),
|
||||
di.get_remaining_size(), false);
|
||||
|
||||
int num_fields = get_num_inherited_fields();
|
||||
for (int i = 0; i < num_fields && !PyErr_Occurred(); ++i) {
|
||||
DCField *field = get_inherited_field(i);
|
||||
if (field->as_molecular_field() == nullptr &&
|
||||
field->is_required() && (field->is_ownrecv() || field->is_broadcast())) {
|
||||
packer.begin_unpack(field);
|
||||
field->receive_update(packer, distobj);
|
||||
if (!packer.end_unpack()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
di.skip_bytes(packer.get_num_unpacked_bytes());
|
||||
}
|
||||
#endif // HAVE_PYTHON
|
||||
|
||||
#ifdef HAVE_PYTHON
|
||||
/**
|
||||
* Processes a big datagram that includes all of the "required" fields that
|
||||
* are sent when an avatar is created. This is all of the atomic fields that
|
||||
* are marked "required", whether they are broadcast or not.
|
||||
*/
|
||||
void DCClass::
|
||||
receive_update_all_required(PyObject *distobj, DatagramIterator &di) const {
|
||||
#ifdef WITHIN_PANDA
|
||||
PStatTimer timer(((DCClass *)this)->_class_update_pcollector);
|
||||
#endif
|
||||
DCPacker packer;
|
||||
const char *data = (const char *)di.get_datagram().get_data();
|
||||
packer.set_unpack_data(data + di.get_current_index(),
|
||||
di.get_remaining_size(), false);
|
||||
|
||||
int num_fields = get_num_inherited_fields();
|
||||
for (int i = 0; i < num_fields && !PyErr_Occurred(); ++i) {
|
||||
DCField *field = get_inherited_field(i);
|
||||
if (field->as_molecular_field() == nullptr &&
|
||||
field->is_required()) {
|
||||
packer.begin_unpack(field);
|
||||
field->receive_update(packer, distobj);
|
||||
if (!packer.end_unpack()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
di.skip_bytes(packer.get_num_unpacked_bytes());
|
||||
}
|
||||
#endif // HAVE_PYTHON
|
||||
|
||||
#ifdef HAVE_PYTHON
|
||||
/**
|
||||
* Processes a datagram that lists some additional fields that are broadcast
|
||||
* in one chunk.
|
||||
*/
|
||||
void DCClass::
|
||||
receive_update_other(PyObject *distobj, DatagramIterator &di) const {
|
||||
#ifdef WITHIN_PANDA
|
||||
PStatTimer timer(((DCClass *)this)->_class_update_pcollector);
|
||||
#endif
|
||||
int num_fields = di.get_uint16();
|
||||
for (int i = 0; i < num_fields && !PyErr_Occurred(); ++i) {
|
||||
receive_update(distobj, di);
|
||||
}
|
||||
}
|
||||
#endif // HAVE_PYTHON
|
||||
|
||||
#ifdef HAVE_PYTHON
|
||||
/**
|
||||
* Processes an update for a named field from a packed value blob.
|
||||
*/
|
||||
void DCClass::
|
||||
direct_update(PyObject *distobj, const string &field_name,
|
||||
const vector_uchar &value_blob) {
|
||||
DCField *field = get_field_by_name(field_name);
|
||||
nassertv_always(field != nullptr);
|
||||
|
||||
DCPacker packer;
|
||||
packer.set_unpack_data(value_blob);
|
||||
packer.begin_unpack(field);
|
||||
field->receive_update(packer, distobj);
|
||||
packer.end_unpack();
|
||||
}
|
||||
#endif // HAVE_PYTHON
|
||||
|
||||
#ifdef HAVE_PYTHON
|
||||
/**
|
||||
* Processes an update for a named field from a packed datagram.
|
||||
*/
|
||||
void DCClass::
|
||||
direct_update(PyObject *distobj, const string &field_name,
|
||||
const Datagram &datagram) {
|
||||
DCField *field = get_field_by_name(field_name);
|
||||
nassertv_always(field != nullptr);
|
||||
|
||||
DCPacker packer;
|
||||
packer.set_unpack_data((const char *)datagram.get_data(), datagram.get_length(), false);
|
||||
packer.begin_unpack(field);
|
||||
field->receive_update(packer, distobj);
|
||||
packer.end_unpack();
|
||||
}
|
||||
#endif // HAVE_PYTHON
|
||||
|
||||
#ifdef HAVE_PYTHON
|
||||
/**
|
||||
* Looks up the current value of the indicated field by calling the
|
||||
* appropriate get*() function, then packs that value into the datagram. This
|
||||
* field is presumably either a required field or a specified optional field,
|
||||
* and we are building up a datagram for the generate-with-required message.
|
||||
*
|
||||
* Returns true on success, false on failure.
|
||||
*/
|
||||
bool DCClass::
|
||||
pack_required_field(Datagram &datagram, PyObject *distobj,
|
||||
const DCField *field) const {
|
||||
DCPacker packer;
|
||||
packer.begin_pack(field);
|
||||
if (!pack_required_field(packer, distobj, field)) {
|
||||
return false;
|
||||
}
|
||||
if (!packer.end_pack()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
datagram.append_data(packer.get_data(), packer.get_length());
|
||||
return true;
|
||||
}
|
||||
#endif // HAVE_PYTHON
|
||||
|
||||
#ifdef HAVE_PYTHON
|
||||
/**
|
||||
* Looks up the current value of the indicated field by calling the
|
||||
* appropriate get*() function, then packs that value into the packer. This
|
||||
* field is presumably either a required field or a specified optional field,
|
||||
* and we are building up a datagram for the generate-with-required message.
|
||||
*
|
||||
* Returns true on success, false on failure.
|
||||
*/
|
||||
bool DCClass::
|
||||
pack_required_field(DCPacker &packer, PyObject *distobj,
|
||||
const DCField *field) const {
|
||||
const DCParameter *parameter = field->as_parameter();
|
||||
if (parameter != nullptr) {
|
||||
// This is the easy case: to pack a parameter, we just look on the class
|
||||
// object for the data element.
|
||||
string field_name = field->get_name();
|
||||
|
||||
if (!PyObject_HasAttrString(distobj, (char *)field_name.c_str())) {
|
||||
// If the attribute is not defined, but the field has a default value
|
||||
// specified, quietly pack the default value.
|
||||
if (field->has_default_value()) {
|
||||
packer.pack_default_value();
|
||||
return true;
|
||||
}
|
||||
|
||||
// If there is no default value specified, it's an error.
|
||||
ostringstream strm;
|
||||
strm << "Data element " << field_name
|
||||
<< ", required by dc file for dclass " << get_name()
|
||||
<< ", not defined on object";
|
||||
nassert_raise(strm.str());
|
||||
return false;
|
||||
}
|
||||
PyObject *result =
|
||||
PyObject_GetAttrString(distobj, (char *)field_name.c_str());
|
||||
nassertr(result != nullptr, false);
|
||||
|
||||
// Now pack the value into the datagram.
|
||||
bool pack_ok = parameter->pack_args(packer, result);
|
||||
Py_DECREF(result);
|
||||
|
||||
return pack_ok;
|
||||
}
|
||||
|
||||
if (field->as_molecular_field() != nullptr) {
|
||||
ostringstream strm;
|
||||
strm << "Cannot pack molecular field " << field->get_name()
|
||||
<< " for generate";
|
||||
nassert_raise(strm.str());
|
||||
return false;
|
||||
}
|
||||
|
||||
const DCAtomicField *atom = field->as_atomic_field();
|
||||
nassertr(atom != nullptr, false);
|
||||
|
||||
// We need to get the initial value of this field. There isn't a good,
|
||||
// robust way to get this; presently, we just mangle the "setFoo()" name of
|
||||
// the required field into "getFoo()" and call that.
|
||||
string setter_name = atom->get_name();
|
||||
|
||||
if (setter_name.empty()) {
|
||||
ostringstream strm;
|
||||
strm << "Required field is unnamed!";
|
||||
nassert_raise(strm.str());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (atom->get_num_elements() == 0) {
|
||||
// It sure doesn't make sense to have a required field with no parameters.
|
||||
// What data, exactly, is required?
|
||||
ostringstream strm;
|
||||
strm << "Required field " << setter_name << " has no parameters!";
|
||||
nassert_raise(strm.str());
|
||||
return false;
|
||||
}
|
||||
|
||||
string getter_name = setter_name;
|
||||
if (setter_name.substr(0, 3) == "set") {
|
||||
// If the original method started with "set", we mangle this directly to
|
||||
// "get".
|
||||
getter_name[0] = 'g';
|
||||
|
||||
} else {
|
||||
// Otherwise, we add a "get" prefix, and capitalize the next letter.
|
||||
getter_name = "get" + setter_name;
|
||||
getter_name[3] = toupper(getter_name[3]);
|
||||
}
|
||||
|
||||
// Now we have to look up the getter on the distributed object and call it.
|
||||
if (!PyObject_HasAttrString(distobj, (char *)getter_name.c_str())) {
|
||||
// As above, if there's no getter but the field has a default value
|
||||
// specified, quietly pack the default value.
|
||||
if (field->has_default_value()) {
|
||||
packer.pack_default_value();
|
||||
return true;
|
||||
}
|
||||
|
||||
// Otherwise, with no default value it's an error.
|
||||
ostringstream strm;
|
||||
strm << "Distributed class " << get_name()
|
||||
<< " doesn't have getter named " << getter_name
|
||||
<< " to match required field " << setter_name;
|
||||
nassert_raise(strm.str());
|
||||
return false;
|
||||
}
|
||||
PyObject *func =
|
||||
PyObject_GetAttrString(distobj, (char *)getter_name.c_str());
|
||||
nassertr(func != nullptr, false);
|
||||
|
||||
PyObject *empty_args = PyTuple_New(0);
|
||||
PyObject *result = PyObject_CallObject(func, empty_args);
|
||||
Py_DECREF(empty_args);
|
||||
Py_DECREF(func);
|
||||
if (result == nullptr) {
|
||||
// We don't set this as an exception, since presumably the Python method
|
||||
// itself has already triggered a Python exception.
|
||||
std::cerr << "Error when calling " << getter_name << "\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (atom->get_num_elements() == 1) {
|
||||
// In this case, we expect the getter to return one object, which we wrap
|
||||
// up in a tuple.
|
||||
PyObject *tuple = PyTuple_New(1);
|
||||
PyTuple_SET_ITEM(tuple, 0, result);
|
||||
result = tuple;
|
||||
|
||||
} else {
|
||||
// Otherwise, it had better already be a sequence or tuple of some sort.
|
||||
if (!PySequence_Check(result)) {
|
||||
ostringstream strm;
|
||||
strm << "Since dclass " << get_name() << " method " << setter_name
|
||||
<< " is declared to have multiple parameters, Python function "
|
||||
<< getter_name << " must return a list or tuple.\n";
|
||||
nassert_raise(strm.str());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Now pack the arguments into the datagram.
|
||||
bool pack_ok = atom->pack_args(packer, result);
|
||||
Py_DECREF(result);
|
||||
|
||||
return pack_ok;
|
||||
}
|
||||
#endif // HAVE_PYTHON
|
||||
|
||||
#ifdef HAVE_PYTHON
|
||||
/**
|
||||
* Generates a datagram containing the message necessary to send an update for
|
||||
* the indicated distributed object from the client.
|
||||
*/
|
||||
Datagram DCClass::
|
||||
client_format_update(const string &field_name, DOID_TYPE do_id,
|
||||
PyObject *args) const {
|
||||
DCField *field = get_field_by_name(field_name);
|
||||
if (field == nullptr) {
|
||||
ostringstream strm;
|
||||
strm << "No field named " << field_name << " in class " << get_name()
|
||||
<< "\n";
|
||||
nassert_raise(strm.str());
|
||||
return Datagram();
|
||||
}
|
||||
|
||||
return field->client_format_update(do_id, args);
|
||||
}
|
||||
#endif // HAVE_PYTHON
|
||||
|
||||
#ifdef HAVE_PYTHON
|
||||
/**
|
||||
* Generates a datagram containing the message necessary to send an update for
|
||||
* the indicated distributed object from the AI.
|
||||
*/
|
||||
Datagram DCClass::
|
||||
ai_format_update(const string &field_name, DOID_TYPE do_id,
|
||||
CHANNEL_TYPE to_id, CHANNEL_TYPE from_id, PyObject *args) const {
|
||||
DCField *field = get_field_by_name(field_name);
|
||||
if (field == nullptr) {
|
||||
ostringstream strm;
|
||||
strm << "No field named " << field_name << " in class " << get_name()
|
||||
<< "\n";
|
||||
nassert_raise(strm.str());
|
||||
return Datagram();
|
||||
}
|
||||
|
||||
return field->ai_format_update(do_id, to_id, from_id, args);
|
||||
}
|
||||
#endif // HAVE_PYTHON
|
||||
|
||||
#ifdef HAVE_PYTHON
|
||||
/**
|
||||
* Generates a datagram containing the message necessary to send an update,
|
||||
* using the indicated msg type for the indicated distributed object from the
|
||||
* AI.
|
||||
*/
|
||||
Datagram DCClass::
|
||||
ai_format_update_msg_type(const string &field_name, DOID_TYPE do_id,
|
||||
CHANNEL_TYPE to_id, CHANNEL_TYPE from_id, int msg_type, PyObject *args) const {
|
||||
DCField *field = get_field_by_name(field_name);
|
||||
if (field == nullptr) {
|
||||
ostringstream strm;
|
||||
strm << "No field named " << field_name << " in class " << get_name()
|
||||
<< "\n";
|
||||
nassert_raise(strm.str());
|
||||
return Datagram();
|
||||
}
|
||||
|
||||
return field->ai_format_update_msg_type(do_id, to_id, from_id, msg_type, args);
|
||||
}
|
||||
#endif // HAVE_PYTHON
|
||||
|
||||
#ifdef HAVE_PYTHON
|
||||
/**
|
||||
* Generates a datagram containing the message necessary to generate a new
|
||||
* distributed object from the client. This requires querying the object for
|
||||
* the initial value of its required fields.
|
||||
*
|
||||
* optional_fields is a list of fieldNames to generate in addition to the
|
||||
* normal required fields.
|
||||
*
|
||||
* This method is only called by the CMU implementation.
|
||||
*/
|
||||
Datagram DCClass::
|
||||
client_format_generate_CMU(PyObject *distobj, DOID_TYPE do_id,
|
||||
ZONEID_TYPE zone_id,
|
||||
PyObject *optional_fields) const {
|
||||
DCPacker packer;
|
||||
|
||||
packer.raw_pack_uint16(CLIENT_OBJECT_GENERATE_CMU);
|
||||
|
||||
packer.raw_pack_uint32(zone_id);
|
||||
packer.raw_pack_uint16(_number);
|
||||
packer.raw_pack_uint32(do_id);
|
||||
|
||||
// Specify all of the required fields.
|
||||
int num_fields = get_num_inherited_fields();
|
||||
for (int i = 0; i < num_fields; ++i) {
|
||||
DCField *field = get_inherited_field(i);
|
||||
if (field->is_required() && field->as_molecular_field() == nullptr) {
|
||||
packer.begin_pack(field);
|
||||
if (!pack_required_field(packer, distobj, field)) {
|
||||
return Datagram();
|
||||
}
|
||||
packer.end_pack();
|
||||
}
|
||||
}
|
||||
|
||||
// Also specify the optional fields.
|
||||
int num_optional_fields = 0;
|
||||
if (PyObject_IsTrue(optional_fields)) {
|
||||
num_optional_fields = PySequence_Size(optional_fields);
|
||||
}
|
||||
packer.raw_pack_uint16(num_optional_fields);
|
||||
|
||||
for (int i = 0; i < num_optional_fields; i++) {
|
||||
PyObject *py_field_name = PySequence_GetItem(optional_fields, i);
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
string field_name = PyUnicode_AsUTF8(py_field_name);
|
||||
#else
|
||||
string field_name = PyString_AsString(py_field_name);
|
||||
#endif
|
||||
Py_XDECREF(py_field_name);
|
||||
|
||||
DCField *field = get_field_by_name(field_name);
|
||||
if (field == nullptr) {
|
||||
ostringstream strm;
|
||||
strm << "No field named " << field_name << " in class " << get_name()
|
||||
<< "\n";
|
||||
nassert_raise(strm.str());
|
||||
return Datagram();
|
||||
}
|
||||
packer.raw_pack_uint16(field->get_number());
|
||||
packer.begin_pack(field);
|
||||
if (!pack_required_field(packer, distobj, field)) {
|
||||
return Datagram();
|
||||
}
|
||||
packer.end_pack();
|
||||
}
|
||||
|
||||
return Datagram(packer.get_data(), packer.get_length());
|
||||
}
|
||||
#endif // HAVE_PYTHON
|
||||
|
||||
#ifdef HAVE_PYTHON
|
||||
/**
|
||||
* Generates a datagram containing the message necessary to generate a new
|
||||
* distributed object from the AI. This requires querying the object for the
|
||||
* initial value of its required fields.
|
||||
*
|
||||
* optional_fields is a list of fieldNames to generate in addition to the
|
||||
* normal required fields.
|
||||
*/
|
||||
Datagram DCClass::
|
||||
ai_format_generate(PyObject *distobj, DOID_TYPE do_id,
|
||||
DOID_TYPE parent_id, ZONEID_TYPE zone_id,
|
||||
CHANNEL_TYPE district_channel_id, CHANNEL_TYPE from_channel_id,
|
||||
PyObject *optional_fields) const {
|
||||
DCPacker packer;
|
||||
|
||||
packer.raw_pack_uint8(1);
|
||||
packer.RAW_PACK_CHANNEL(district_channel_id);
|
||||
packer.RAW_PACK_CHANNEL(from_channel_id);
|
||||
// packer.raw_pack_uint8('A');
|
||||
|
||||
bool has_optional_fields = (PyObject_IsTrue(optional_fields) != 0);
|
||||
|
||||
if (has_optional_fields) {
|
||||
packer.raw_pack_uint16(STATESERVER_CREATE_OBJECT_WITH_REQUIRED_OTHER);
|
||||
} else {
|
||||
packer.raw_pack_uint16(STATESERVER_CREATE_OBJECT_WITH_REQUIRED);
|
||||
}
|
||||
|
||||
packer.raw_pack_uint32(do_id);
|
||||
// Parent is a bit overloaded; this parent is not about inheritance, this
|
||||
// one is about the visibility container parent, i.e. the zone parent:
|
||||
packer.raw_pack_uint32(parent_id);
|
||||
packer.raw_pack_uint32(zone_id);
|
||||
packer.raw_pack_uint16(_number);
|
||||
|
||||
// Specify all of the required fields.
|
||||
int num_fields = get_num_inherited_fields();
|
||||
for (int i = 0; i < num_fields; ++i) {
|
||||
DCField *field = get_inherited_field(i);
|
||||
if (field->is_required() && field->as_molecular_field() == nullptr) {
|
||||
packer.begin_pack(field);
|
||||
if (!pack_required_field(packer, distobj, field)) {
|
||||
return Datagram();
|
||||
}
|
||||
packer.end_pack();
|
||||
}
|
||||
}
|
||||
|
||||
// Also specify the optional fields.
|
||||
if (has_optional_fields) {
|
||||
int num_optional_fields = PySequence_Size(optional_fields);
|
||||
packer.raw_pack_uint16(num_optional_fields);
|
||||
|
||||
for (int i = 0; i < num_optional_fields; ++i) {
|
||||
PyObject *py_field_name = PySequence_GetItem(optional_fields, i);
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
string field_name = PyUnicode_AsUTF8(py_field_name);
|
||||
#else
|
||||
string field_name = PyString_AsString(py_field_name);
|
||||
#endif
|
||||
Py_XDECREF(py_field_name);
|
||||
|
||||
DCField *field = get_field_by_name(field_name);
|
||||
if (field == nullptr) {
|
||||
ostringstream strm;
|
||||
strm << "No field named " << field_name << " in class " << get_name()
|
||||
<< "\n";
|
||||
nassert_raise(strm.str());
|
||||
return Datagram();
|
||||
}
|
||||
|
||||
packer.raw_pack_uint16(field->get_number());
|
||||
|
||||
packer.begin_pack(field);
|
||||
if (!pack_required_field(packer, distobj, field)) {
|
||||
return Datagram();
|
||||
}
|
||||
packer.end_pack();
|
||||
}
|
||||
}
|
||||
|
||||
return Datagram(packer.get_data(), packer.get_length());
|
||||
}
|
||||
#endif // HAVE_PYTHON
|
||||
|
||||
/**
|
||||
* Write a string representation of this instance to <out>.
|
||||
*/
|
||||
void DCClass::
|
||||
output(ostream &out, bool brief) const {
|
||||
output(std::ostream &out, bool brief) const {
|
||||
output_instance(out, brief, "", "", "");
|
||||
}
|
||||
|
||||
|
|
@ -1026,7 +342,7 @@ output(ostream &out, bool brief) const {
|
|||
* stream.
|
||||
*/
|
||||
void DCClass::
|
||||
write(ostream &out, bool brief, int indent_level) const {
|
||||
write(std::ostream &out, bool brief, int indent_level) const {
|
||||
indent(out, indent_level);
|
||||
if (_is_struct) {
|
||||
out << "struct";
|
||||
|
|
@ -1086,7 +402,7 @@ write(ostream &out, bool brief, int indent_level) const {
|
|||
* stream.
|
||||
*/
|
||||
void DCClass::
|
||||
output_instance(ostream &out, bool brief, const string &prename,
|
||||
output_instance(std::ostream &out, bool brief, const string &prename,
|
||||
const string &name, const string &postname) const {
|
||||
if (_is_struct) {
|
||||
out << "struct";
|
||||
|
|
|
|||
|
|
@ -17,11 +17,12 @@
|
|||
#include "dcbase.h"
|
||||
#include "dcField.h"
|
||||
#include "dcDeclaration.h"
|
||||
#include "dcPython.h"
|
||||
|
||||
#ifdef WITHIN_PANDA
|
||||
#include "pStatCollector.h"
|
||||
#include "configVariableBool.h"
|
||||
#include "extension.h"
|
||||
#include "datagramIterator.h"
|
||||
|
||||
extern ConfigVariableBool dc_multiple_inheritance;
|
||||
extern ConfigVariableBool dc_virtual_inheritance;
|
||||
|
|
@ -80,44 +81,52 @@ PUBLISHED:
|
|||
|
||||
virtual void output(std::ostream &out) const;
|
||||
|
||||
#ifdef HAVE_PYTHON
|
||||
bool has_class_def() const;
|
||||
void set_class_def(PyObject *class_def);
|
||||
PyObject *get_class_def() const;
|
||||
bool has_owner_class_def() const;
|
||||
void set_owner_class_def(PyObject *owner_class_def);
|
||||
PyObject *get_owner_class_def() const;
|
||||
EXTENSION(bool has_class_def() const);
|
||||
EXTENSION(void set_class_def(PyObject *class_def));
|
||||
EXTENSION(PyObject *get_class_def() const);
|
||||
EXTENSION(bool has_owner_class_def() const);
|
||||
EXTENSION(void set_owner_class_def(PyObject *owner_class_def));
|
||||
EXTENSION(PyObject *get_owner_class_def() const);
|
||||
|
||||
void receive_update(PyObject *distobj, DatagramIterator &di) const;
|
||||
void receive_update_broadcast_required(PyObject *distobj, DatagramIterator &di) const;
|
||||
void receive_update_broadcast_required_owner(PyObject *distobj, DatagramIterator &di) const;
|
||||
void receive_update_all_required(PyObject *distobj, DatagramIterator &di) const;
|
||||
void receive_update_other(PyObject *distobj, DatagramIterator &di) const;
|
||||
EXTENSION(void receive_update(PyObject *distobj, DatagramIterator &di) const);
|
||||
EXTENSION(void receive_update_broadcast_required(PyObject *distobj, DatagramIterator &di) const);
|
||||
EXTENSION(void receive_update_broadcast_required_owner(PyObject *distobj, DatagramIterator &di) const);
|
||||
EXTENSION(void receive_update_all_required(PyObject *distobj, DatagramIterator &di) const);
|
||||
EXTENSION(void receive_update_other(PyObject *distobj, DatagramIterator &di) const);
|
||||
|
||||
void direct_update(PyObject *distobj, const std::string &field_name,
|
||||
const vector_uchar &value_blob);
|
||||
void direct_update(PyObject *distobj, const std::string &field_name,
|
||||
const Datagram &datagram);
|
||||
bool pack_required_field(Datagram &datagram, PyObject *distobj,
|
||||
const DCField *field) const;
|
||||
bool pack_required_field(DCPacker &packer, PyObject *distobj,
|
||||
const DCField *field) const;
|
||||
EXTENSION(void direct_update(PyObject *distobj, const std::string &field_name,
|
||||
const vector_uchar &value_blob));
|
||||
EXTENSION(void direct_update(PyObject *distobj, const std::string &field_name,
|
||||
const Datagram &datagram));
|
||||
EXTENSION(bool pack_required_field(Datagram &datagram, PyObject *distobj,
|
||||
const DCField *field) const);
|
||||
EXTENSION(bool pack_required_field(DCPacker &packer, PyObject *distobj,
|
||||
const DCField *field) const);
|
||||
|
||||
|
||||
|
||||
Datagram client_format_update(const std::string &field_name,
|
||||
DOID_TYPE do_id, PyObject *args) const;
|
||||
Datagram ai_format_update(const std::string &field_name, DOID_TYPE do_id,
|
||||
CHANNEL_TYPE to_id, CHANNEL_TYPE from_id, PyObject *args) const;
|
||||
Datagram ai_format_update_msg_type(const std::string &field_name, DOID_TYPE do_id,
|
||||
CHANNEL_TYPE to_id, CHANNEL_TYPE from_id, int msg_type, PyObject *args) const;
|
||||
Datagram ai_format_generate(PyObject *distobj, DOID_TYPE do_id, ZONEID_TYPE parent_id, ZONEID_TYPE zone_id,
|
||||
CHANNEL_TYPE district_channel_id, CHANNEL_TYPE from_channel_id,
|
||||
PyObject *optional_fields) const;
|
||||
Datagram client_format_generate_CMU(PyObject *distobj, DOID_TYPE do_id,
|
||||
ZONEID_TYPE zone_id, PyObject *optional_fields) const;
|
||||
EXTENSION(Datagram client_format_update(const std::string &field_name,
|
||||
DOID_TYPE do_id, PyObject *args) const);
|
||||
EXTENSION(Datagram ai_format_update(const std::string &field_name,
|
||||
DOID_TYPE do_id,
|
||||
CHANNEL_TYPE to_id, CHANNEL_TYPE from_id,
|
||||
PyObject *args) const);
|
||||
EXTENSION(Datagram ai_format_update_msg_type(const std::string &field_name,
|
||||
DOID_TYPE do_id,
|
||||
CHANNEL_TYPE to_id,
|
||||
CHANNEL_TYPE from_id,
|
||||
int msg_type,
|
||||
PyObject *args) const);
|
||||
EXTENSION(Datagram ai_format_generate(PyObject *distobj, DOID_TYPE do_id,
|
||||
ZONEID_TYPE parent_id,
|
||||
ZONEID_TYPE zone_id,
|
||||
CHANNEL_TYPE district_channel_id,
|
||||
CHANNEL_TYPE from_channel_id,
|
||||
PyObject *optional_fields) const);
|
||||
EXTENSION(Datagram client_format_generate_CMU(PyObject *distobj, DOID_TYPE do_id,
|
||||
ZONEID_TYPE zone_id,
|
||||
PyObject *optional_fields) const);
|
||||
|
||||
#endif
|
||||
|
||||
public:
|
||||
virtual void output(std::ostream &out, bool brief) const;
|
||||
|
|
@ -136,8 +145,8 @@ private:
|
|||
void shadow_inherited_field(const std::string &name);
|
||||
|
||||
#ifdef WITHIN_PANDA
|
||||
PStatCollector _class_update_pcollector;
|
||||
PStatCollector _class_generate_pcollector;
|
||||
mutable PStatCollector _class_update_pcollector;
|
||||
mutable PStatCollector _class_generate_pcollector;
|
||||
static PStatCollector _update_pcollector;
|
||||
static PStatCollector _generate_pcollector;
|
||||
#endif
|
||||
|
|
@ -163,12 +172,17 @@ private:
|
|||
typedef pmap<int, DCField *> FieldsByIndex;
|
||||
FieldsByIndex _fields_by_index;
|
||||
|
||||
#ifdef HAVE_PYTHON
|
||||
PyObject *_class_def;
|
||||
PyObject *_owner_class_def;
|
||||
#endif
|
||||
// See pandaNode.h for an explanation of this trick
|
||||
class PythonClassDefs : public ReferenceCount {
|
||||
public:
|
||||
virtual ~PythonClassDefs() {};
|
||||
};
|
||||
PT(PythonClassDefs) _python_class_defs;
|
||||
|
||||
friend class DCField;
|
||||
#ifdef WITHIN_PANDA
|
||||
friend class Extension<DCClass>;
|
||||
#endif
|
||||
};
|
||||
|
||||
#include "dcClass.I"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,665 @@
|
|||
/**
|
||||
* 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 dcClass_ext.cxx
|
||||
* @author CFSworks
|
||||
* @date 2019-07-03
|
||||
*/
|
||||
|
||||
#include "dcClass_ext.h"
|
||||
#include "dcField_ext.h"
|
||||
#include "dcAtomicField.h"
|
||||
#include "dcPacker.h"
|
||||
#include "dcmsgtypes.h"
|
||||
|
||||
#include "datagram.h"
|
||||
#include "datagramIterator.h"
|
||||
#include "pStatTimer.h"
|
||||
|
||||
#ifdef HAVE_PYTHON
|
||||
|
||||
/**
|
||||
* Returns true if the DCClass object has an associated Python class
|
||||
* definition, false otherwise.
|
||||
*/
|
||||
bool Extension<DCClass>::
|
||||
has_class_def() const {
|
||||
return _this->_python_class_defs != nullptr
|
||||
&& ((PythonClassDefsImpl *)_this->_python_class_defs.p())->_class_def != nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the class object associated with this DistributedClass. This object
|
||||
* will be used to construct new instances of the class.
|
||||
*/
|
||||
void Extension<DCClass>::
|
||||
set_class_def(PyObject *class_def) {
|
||||
PythonClassDefsImpl *defs = do_get_defs();
|
||||
|
||||
Py_XINCREF(class_def);
|
||||
Py_XDECREF(defs->_class_def);
|
||||
defs->_class_def = class_def;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the class object that was previously associated with this
|
||||
* DistributedClass. This will return a new reference to the object.
|
||||
*/
|
||||
PyObject *Extension<DCClass>::
|
||||
get_class_def() const {
|
||||
if (!has_class_def()) {
|
||||
Py_INCREF(Py_None);
|
||||
return Py_None;
|
||||
}
|
||||
|
||||
PythonClassDefsImpl *defs = do_get_defs();
|
||||
Py_INCREF(defs->_class_def);
|
||||
return defs->_class_def;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the DCClass object has an associated Python owner class
|
||||
* definition, false otherwise.
|
||||
*/
|
||||
bool Extension<DCClass>::
|
||||
has_owner_class_def() const {
|
||||
return _this->_python_class_defs != nullptr
|
||||
&& ((PythonClassDefsImpl *)_this->_python_class_defs.p())->_owner_class_def != nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the owner class object associated with this DistributedClass. This
|
||||
* object will be used to construct new owner instances of the class.
|
||||
*/
|
||||
void Extension<DCClass>::
|
||||
set_owner_class_def(PyObject *owner_class_def) {
|
||||
PythonClassDefsImpl *defs = do_get_defs();
|
||||
|
||||
Py_XINCREF(owner_class_def);
|
||||
Py_XDECREF(defs->_owner_class_def);
|
||||
defs->_owner_class_def = owner_class_def;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the owner class object that was previously associated with this
|
||||
* DistributedClass. This will return a new reference to the object.
|
||||
*/
|
||||
PyObject *Extension<DCClass>::
|
||||
get_owner_class_def() const {
|
||||
if (!has_owner_class_def()) {
|
||||
Py_INCREF(Py_None);
|
||||
return Py_None;
|
||||
}
|
||||
|
||||
PythonClassDefsImpl *defs = do_get_defs();
|
||||
Py_INCREF(defs->_owner_class_def);
|
||||
return defs->_owner_class_def;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the update message out of the packer and applies it to the
|
||||
* indicated object by calling the appropriate method.
|
||||
*/
|
||||
void Extension<DCClass>::
|
||||
receive_update(PyObject *distobj, DatagramIterator &di) const {
|
||||
PStatTimer timer(_this->_class_update_pcollector);
|
||||
DCPacker packer;
|
||||
const char *data = (const char *)di.get_datagram().get_data();
|
||||
packer.set_unpack_data(data + di.get_current_index(),
|
||||
di.get_remaining_size(), false);
|
||||
|
||||
int field_id = packer.raw_unpack_uint16();
|
||||
DCField *field = _this->get_field_by_index(field_id);
|
||||
if (field == nullptr) {
|
||||
ostringstream strm;
|
||||
strm
|
||||
<< "Received update for field " << field_id << ", not in class "
|
||||
<< _this->get_name();
|
||||
nassert_raise(strm.str());
|
||||
return;
|
||||
}
|
||||
|
||||
packer.begin_unpack(field);
|
||||
invoke_extension(field).receive_update(packer, distobj);
|
||||
packer.end_unpack();
|
||||
|
||||
di.skip_bytes(packer.get_num_unpacked_bytes());
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes a big datagram that includes all of the "required" fields that
|
||||
* are sent along with a normal "generate with required" message. This is all
|
||||
* of the atomic fields that are marked "broadcast required".
|
||||
*/
|
||||
void Extension<DCClass>::
|
||||
receive_update_broadcast_required(PyObject *distobj, DatagramIterator &di) const {
|
||||
PStatTimer timer(_this->_class_update_pcollector);
|
||||
DCPacker packer;
|
||||
const char *data = (const char *)di.get_datagram().get_data();
|
||||
packer.set_unpack_data(data + di.get_current_index(),
|
||||
di.get_remaining_size(), false);
|
||||
|
||||
int num_fields = _this->get_num_inherited_fields();
|
||||
for (int i = 0; i < num_fields && !PyErr_Occurred(); ++i) {
|
||||
DCField *field = _this->get_inherited_field(i);
|
||||
if (field->as_molecular_field() == nullptr &&
|
||||
field->is_required() && field->is_broadcast()) {
|
||||
packer.begin_unpack(field);
|
||||
invoke_extension(field).receive_update(packer, distobj);
|
||||
if (!packer.end_unpack()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
di.skip_bytes(packer.get_num_unpacked_bytes());
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes a big datagram that includes all of the "required" fields that
|
||||
* are sent along with a normal "generate with required" message. This is all
|
||||
* of the atomic fields that are marked "broadcast ownrecv". Should be used
|
||||
* for 'owner-view' objects.
|
||||
*/
|
||||
void Extension<DCClass>::
|
||||
receive_update_broadcast_required_owner(PyObject *distobj,
|
||||
DatagramIterator &di) const {
|
||||
PStatTimer timer(_this->_class_update_pcollector);
|
||||
DCPacker packer;
|
||||
const char *data = (const char *)di.get_datagram().get_data();
|
||||
packer.set_unpack_data(data + di.get_current_index(),
|
||||
di.get_remaining_size(), false);
|
||||
|
||||
int num_fields = _this->get_num_inherited_fields();
|
||||
for (int i = 0; i < num_fields && !PyErr_Occurred(); ++i) {
|
||||
DCField *field = _this->get_inherited_field(i);
|
||||
if (field->as_molecular_field() == nullptr &&
|
||||
field->is_required() && (field->is_ownrecv() || field->is_broadcast())) {
|
||||
packer.begin_unpack(field);
|
||||
invoke_extension(field).receive_update(packer, distobj);
|
||||
if (!packer.end_unpack()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
di.skip_bytes(packer.get_num_unpacked_bytes());
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes a big datagram that includes all of the "required" fields that
|
||||
* are sent when an avatar is created. This is all of the atomic fields that
|
||||
* are marked "required", whether they are broadcast or not.
|
||||
*/
|
||||
void Extension<DCClass>::
|
||||
receive_update_all_required(PyObject *distobj, DatagramIterator &di) const {
|
||||
PStatTimer timer(_this->_class_update_pcollector);
|
||||
DCPacker packer;
|
||||
const char *data = (const char *)di.get_datagram().get_data();
|
||||
packer.set_unpack_data(data + di.get_current_index(),
|
||||
di.get_remaining_size(), false);
|
||||
|
||||
int num_fields = _this->get_num_inherited_fields();
|
||||
for (int i = 0; i < num_fields && !PyErr_Occurred(); ++i) {
|
||||
DCField *field = _this->get_inherited_field(i);
|
||||
if (field->as_molecular_field() == nullptr &&
|
||||
field->is_required()) {
|
||||
packer.begin_unpack(field);
|
||||
invoke_extension(field).receive_update(packer, distobj);
|
||||
if (!packer.end_unpack()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
di.skip_bytes(packer.get_num_unpacked_bytes());
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes a datagram that lists some additional fields that are broadcast
|
||||
* in one chunk.
|
||||
*/
|
||||
void Extension<DCClass>::
|
||||
receive_update_other(PyObject *distobj, DatagramIterator &di) const {
|
||||
PStatTimer timer(_this->_class_update_pcollector);
|
||||
int num_fields = di.get_uint16();
|
||||
for (int i = 0; i < num_fields && !PyErr_Occurred(); ++i) {
|
||||
receive_update(distobj, di);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes an update for a named field from a packed value blob.
|
||||
*/
|
||||
void Extension<DCClass>::
|
||||
direct_update(PyObject *distobj, const std::string &field_name,
|
||||
const vector_uchar &value_blob) {
|
||||
DCField *field = _this->get_field_by_name(field_name);
|
||||
nassertv_always(field != nullptr);
|
||||
|
||||
DCPacker packer;
|
||||
packer.set_unpack_data(value_blob);
|
||||
packer.begin_unpack(field);
|
||||
invoke_extension(field).receive_update(packer, distobj);
|
||||
packer.end_unpack();
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes an update for a named field from a packed datagram.
|
||||
*/
|
||||
void Extension<DCClass>::
|
||||
direct_update(PyObject *distobj, const std::string &field_name,
|
||||
const Datagram &datagram) {
|
||||
DCField *field = _this->get_field_by_name(field_name);
|
||||
nassertv_always(field != nullptr);
|
||||
|
||||
DCPacker packer;
|
||||
packer.set_unpack_data((const char *)datagram.get_data(), datagram.get_length(), false);
|
||||
packer.begin_unpack(field);
|
||||
invoke_extension(field).receive_update(packer, distobj);
|
||||
packer.end_unpack();
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks up the current value of the indicated field by calling the
|
||||
* appropriate get*() function, then packs that value into the datagram. This
|
||||
* field is presumably either a required field or a specified optional field,
|
||||
* and we are building up a datagram for the generate-with-required message.
|
||||
*
|
||||
* Returns true on success, false on failure.
|
||||
*/
|
||||
bool Extension<DCClass>::
|
||||
pack_required_field(Datagram &datagram, PyObject *distobj,
|
||||
const DCField *field) const {
|
||||
DCPacker packer;
|
||||
packer.begin_pack(field);
|
||||
if (!pack_required_field(packer, distobj, field)) {
|
||||
return false;
|
||||
}
|
||||
if (!packer.end_pack()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
datagram.append_data(packer.get_data(), packer.get_length());
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks up the current value of the indicated field by calling the
|
||||
* appropriate get*() function, then packs that value into the packer. This
|
||||
* field is presumably either a required field or a specified optional field,
|
||||
* and we are building up a datagram for the generate-with-required message.
|
||||
*
|
||||
* Returns true on success, false on failure.
|
||||
*/
|
||||
bool Extension<DCClass>::
|
||||
pack_required_field(DCPacker &packer, PyObject *distobj,
|
||||
const DCField *field) const {
|
||||
using std::ostringstream;
|
||||
|
||||
const DCParameter *parameter = field->as_parameter();
|
||||
if (parameter != nullptr) {
|
||||
// This is the easy case: to pack a parameter, we just look on the class
|
||||
// object for the data element.
|
||||
std::string field_name = field->get_name();
|
||||
|
||||
if (!PyObject_HasAttrString(distobj, (char *)field_name.c_str())) {
|
||||
// If the attribute is not defined, but the field has a default value
|
||||
// specified, quietly pack the default value.
|
||||
if (field->has_default_value()) {
|
||||
packer.pack_default_value();
|
||||
return true;
|
||||
}
|
||||
|
||||
// If there is no default value specified, it's an error.
|
||||
ostringstream strm;
|
||||
strm << "Data element " << field_name
|
||||
<< ", required by dc file for dclass " << _this->get_name()
|
||||
<< ", not defined on object";
|
||||
nassert_raise(strm.str());
|
||||
return false;
|
||||
}
|
||||
PyObject *result =
|
||||
PyObject_GetAttrString(distobj, (char *)field_name.c_str());
|
||||
nassertr(result != nullptr, false);
|
||||
|
||||
// Now pack the value into the datagram.
|
||||
bool pack_ok = invoke_extension((DCField *)parameter).pack_args(packer, result);
|
||||
Py_DECREF(result);
|
||||
|
||||
return pack_ok;
|
||||
}
|
||||
|
||||
if (field->as_molecular_field() != nullptr) {
|
||||
ostringstream strm;
|
||||
strm << "Cannot pack molecular field " << field->get_name()
|
||||
<< " for generate";
|
||||
nassert_raise(strm.str());
|
||||
return false;
|
||||
}
|
||||
|
||||
const DCAtomicField *atom = field->as_atomic_field();
|
||||
nassertr(atom != nullptr, false);
|
||||
|
||||
// We need to get the initial value of this field. There isn't a good,
|
||||
// robust way to get this; presently, we just mangle the "setFoo()" name of
|
||||
// the required field into "getFoo()" and call that.
|
||||
std::string setter_name = atom->get_name();
|
||||
|
||||
if (setter_name.empty()) {
|
||||
ostringstream strm;
|
||||
strm << "Required field is unnamed!";
|
||||
nassert_raise(strm.str());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (atom->get_num_elements() == 0) {
|
||||
// It sure doesn't make sense to have a required field with no parameters.
|
||||
// What data, exactly, is required?
|
||||
ostringstream strm;
|
||||
strm << "Required field " << setter_name << " has no parameters!";
|
||||
nassert_raise(strm.str());
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string getter_name = setter_name;
|
||||
if (setter_name.substr(0, 3) == "set") {
|
||||
// If the original method started with "set", we mangle this directly to
|
||||
// "get".
|
||||
getter_name[0] = 'g';
|
||||
|
||||
} else {
|
||||
// Otherwise, we add a "get" prefix, and capitalize the next letter.
|
||||
getter_name = "get" + setter_name;
|
||||
getter_name[3] = toupper(getter_name[3]);
|
||||
}
|
||||
|
||||
// Now we have to look up the getter on the distributed object and call it.
|
||||
if (!PyObject_HasAttrString(distobj, (char *)getter_name.c_str())) {
|
||||
// As above, if there's no getter but the field has a default value
|
||||
// specified, quietly pack the default value.
|
||||
if (field->has_default_value()) {
|
||||
packer.pack_default_value();
|
||||
return true;
|
||||
}
|
||||
|
||||
// Otherwise, with no default value it's an error.
|
||||
ostringstream strm;
|
||||
strm << "Distributed class " << _this->get_name()
|
||||
<< " doesn't have getter named " << getter_name
|
||||
<< " to match required field " << setter_name;
|
||||
nassert_raise(strm.str());
|
||||
return false;
|
||||
}
|
||||
PyObject *func =
|
||||
PyObject_GetAttrString(distobj, (char *)getter_name.c_str());
|
||||
nassertr(func != nullptr, false);
|
||||
|
||||
PyObject *empty_args = PyTuple_New(0);
|
||||
PyObject *result = PyObject_CallObject(func, empty_args);
|
||||
Py_DECREF(empty_args);
|
||||
Py_DECREF(func);
|
||||
if (result == nullptr) {
|
||||
// We don't set this as an exception, since presumably the Python method
|
||||
// itself has already triggered a Python exception.
|
||||
std::cerr << "Error when calling " << getter_name << "\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (atom->get_num_elements() == 1) {
|
||||
// In this case, we expect the getter to return one object, which we wrap
|
||||
// up in a tuple.
|
||||
PyObject *tuple = PyTuple_New(1);
|
||||
PyTuple_SET_ITEM(tuple, 0, result);
|
||||
result = tuple;
|
||||
|
||||
} else {
|
||||
// Otherwise, it had better already be a sequence or tuple of some sort.
|
||||
if (!PySequence_Check(result)) {
|
||||
ostringstream strm;
|
||||
strm << "Since dclass " << _this->get_name() << " method " << setter_name
|
||||
<< " is declared to have multiple parameters, Python function "
|
||||
<< getter_name << " must return a list or tuple.\n";
|
||||
nassert_raise(strm.str());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Now pack the arguments into the datagram.
|
||||
bool pack_ok = invoke_extension((DCField *)atom).pack_args(packer, result);
|
||||
Py_DECREF(result);
|
||||
|
||||
return pack_ok;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a datagram containing the message necessary to send an update for
|
||||
* the indicated distributed object from the client.
|
||||
*/
|
||||
Datagram Extension<DCClass>::
|
||||
client_format_update(const std::string &field_name, DOID_TYPE do_id,
|
||||
PyObject *args) const {
|
||||
DCField *field = _this->get_field_by_name(field_name);
|
||||
if (field == nullptr) {
|
||||
std::ostringstream strm;
|
||||
strm << "No field named " << field_name << " in class " << _this->get_name()
|
||||
<< "\n";
|
||||
nassert_raise(strm.str());
|
||||
return Datagram();
|
||||
}
|
||||
|
||||
return invoke_extension(field).client_format_update(do_id, args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a datagram containing the message necessary to send an update for
|
||||
* the indicated distributed object from the AI.
|
||||
*/
|
||||
Datagram Extension<DCClass>::
|
||||
ai_format_update(const std::string &field_name, DOID_TYPE do_id,
|
||||
CHANNEL_TYPE to_id, CHANNEL_TYPE from_id, PyObject *args) const {
|
||||
DCField *field = _this->get_field_by_name(field_name);
|
||||
if (field == nullptr) {
|
||||
std::ostringstream strm;
|
||||
strm << "No field named " << field_name << " in class " << _this->get_name()
|
||||
<< "\n";
|
||||
nassert_raise(strm.str());
|
||||
return Datagram();
|
||||
}
|
||||
|
||||
return invoke_extension(field).ai_format_update(do_id, to_id, from_id, args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a datagram containing the message necessary to send an update,
|
||||
* using the indicated msg type for the indicated distributed object from the
|
||||
* AI.
|
||||
*/
|
||||
Datagram Extension<DCClass>::
|
||||
ai_format_update_msg_type(const std::string &field_name, DOID_TYPE do_id,
|
||||
CHANNEL_TYPE to_id, CHANNEL_TYPE from_id,
|
||||
int msg_type, PyObject *args) const {
|
||||
DCField *field = _this->get_field_by_name(field_name);
|
||||
if (field == nullptr) {
|
||||
std::ostringstream strm;
|
||||
strm << "No field named " << field_name << " in class " << _this->get_name()
|
||||
<< "\n";
|
||||
nassert_raise(strm.str());
|
||||
return Datagram();
|
||||
}
|
||||
|
||||
return invoke_extension(field).ai_format_update_msg_type(do_id, to_id, from_id, msg_type, args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a datagram containing the message necessary to generate a new
|
||||
* distributed object from the client. This requires querying the object for
|
||||
* the initial value of its required fields.
|
||||
*
|
||||
* optional_fields is a list of fieldNames to generate in addition to the
|
||||
* normal required fields.
|
||||
*
|
||||
* This method is only called by the CMU implementation.
|
||||
*/
|
||||
Datagram Extension<DCClass>::
|
||||
client_format_generate_CMU(PyObject *distobj, DOID_TYPE do_id,
|
||||
ZONEID_TYPE zone_id,
|
||||
PyObject *optional_fields) const {
|
||||
DCPacker packer;
|
||||
|
||||
packer.raw_pack_uint16(CLIENT_OBJECT_GENERATE_CMU);
|
||||
|
||||
packer.raw_pack_uint32(zone_id);
|
||||
packer.raw_pack_uint16(_this->_number);
|
||||
packer.raw_pack_uint32(do_id);
|
||||
|
||||
// Specify all of the required fields.
|
||||
int num_fields = _this->get_num_inherited_fields();
|
||||
for (int i = 0; i < num_fields; ++i) {
|
||||
DCField *field = _this->get_inherited_field(i);
|
||||
if (field->is_required() && field->as_molecular_field() == nullptr) {
|
||||
packer.begin_pack(field);
|
||||
if (!pack_required_field(packer, distobj, field)) {
|
||||
return Datagram();
|
||||
}
|
||||
packer.end_pack();
|
||||
}
|
||||
}
|
||||
|
||||
// Also specify the optional fields.
|
||||
int num_optional_fields = 0;
|
||||
if (PyObject_IsTrue(optional_fields)) {
|
||||
num_optional_fields = PySequence_Size(optional_fields);
|
||||
}
|
||||
packer.raw_pack_uint16(num_optional_fields);
|
||||
|
||||
for (int i = 0; i < num_optional_fields; i++) {
|
||||
PyObject *py_field_name = PySequence_GetItem(optional_fields, i);
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
std::string field_name = PyUnicode_AsUTF8(py_field_name);
|
||||
#else
|
||||
std::string field_name = PyString_AsString(py_field_name);
|
||||
#endif
|
||||
Py_XDECREF(py_field_name);
|
||||
|
||||
DCField *field = _this->get_field_by_name(field_name);
|
||||
if (field == nullptr) {
|
||||
std::ostringstream strm;
|
||||
strm << "No field named " << field_name << " in class " << _this->get_name()
|
||||
<< "\n";
|
||||
nassert_raise(strm.str());
|
||||
return Datagram();
|
||||
}
|
||||
packer.raw_pack_uint16(field->get_number());
|
||||
packer.begin_pack(field);
|
||||
if (!pack_required_field(packer, distobj, field)) {
|
||||
return Datagram();
|
||||
}
|
||||
packer.end_pack();
|
||||
}
|
||||
|
||||
return Datagram(packer.get_data(), packer.get_length());
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a datagram containing the message necessary to generate a new
|
||||
* distributed object from the AI. This requires querying the object for the
|
||||
* initial value of its required fields.
|
||||
*
|
||||
* optional_fields is a list of fieldNames to generate in addition to the
|
||||
* normal required fields.
|
||||
*/
|
||||
Datagram Extension<DCClass>::
|
||||
ai_format_generate(PyObject *distobj, DOID_TYPE do_id,
|
||||
DOID_TYPE parent_id, ZONEID_TYPE zone_id,
|
||||
CHANNEL_TYPE district_channel_id, CHANNEL_TYPE from_channel_id,
|
||||
PyObject *optional_fields) const {
|
||||
DCPacker packer;
|
||||
|
||||
packer.raw_pack_uint8(1);
|
||||
packer.RAW_PACK_CHANNEL(district_channel_id);
|
||||
packer.RAW_PACK_CHANNEL(from_channel_id);
|
||||
// packer.raw_pack_uint8('A');
|
||||
|
||||
bool has_optional_fields = (PyObject_IsTrue(optional_fields) != 0);
|
||||
|
||||
if (has_optional_fields) {
|
||||
packer.raw_pack_uint16(STATESERVER_CREATE_OBJECT_WITH_REQUIRED_OTHER);
|
||||
} else {
|
||||
packer.raw_pack_uint16(STATESERVER_CREATE_OBJECT_WITH_REQUIRED);
|
||||
}
|
||||
|
||||
packer.raw_pack_uint32(do_id);
|
||||
// Parent is a bit overloaded; this parent is not about inheritance, this
|
||||
// one is about the visibility container parent, i.e. the zone parent:
|
||||
packer.raw_pack_uint32(parent_id);
|
||||
packer.raw_pack_uint32(zone_id);
|
||||
packer.raw_pack_uint16(_this->_number);
|
||||
|
||||
// Specify all of the required fields.
|
||||
int num_fields = _this->get_num_inherited_fields();
|
||||
for (int i = 0; i < num_fields; ++i) {
|
||||
DCField *field = _this->get_inherited_field(i);
|
||||
if (field->is_required() && field->as_molecular_field() == nullptr) {
|
||||
packer.begin_pack(field);
|
||||
if (!pack_required_field(packer, distobj, field)) {
|
||||
return Datagram();
|
||||
}
|
||||
packer.end_pack();
|
||||
}
|
||||
}
|
||||
|
||||
// Also specify the optional fields.
|
||||
if (has_optional_fields) {
|
||||
int num_optional_fields = PySequence_Size(optional_fields);
|
||||
packer.raw_pack_uint16(num_optional_fields);
|
||||
|
||||
for (int i = 0; i < num_optional_fields; ++i) {
|
||||
PyObject *py_field_name = PySequence_GetItem(optional_fields, i);
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
std::string field_name = PyUnicode_AsUTF8(py_field_name);
|
||||
#else
|
||||
std::string field_name = PyString_AsString(py_field_name);
|
||||
#endif
|
||||
Py_XDECREF(py_field_name);
|
||||
|
||||
DCField *field = _this->get_field_by_name(field_name);
|
||||
if (field == nullptr) {
|
||||
std::ostringstream strm;
|
||||
strm << "No field named " << field_name << " in class "
|
||||
<< _this->get_name() << "\n";
|
||||
nassert_raise(strm.str());
|
||||
return Datagram();
|
||||
}
|
||||
|
||||
packer.raw_pack_uint16(field->get_number());
|
||||
|
||||
packer.begin_pack(field);
|
||||
if (!pack_required_field(packer, distobj, field)) {
|
||||
return Datagram();
|
||||
}
|
||||
packer.end_pack();
|
||||
}
|
||||
}
|
||||
|
||||
return Datagram(packer.get_data(), packer.get_length());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the PythonClassDefsImpl object stored on the DCClass object,
|
||||
* creating it if it didn't yet exist.
|
||||
*/
|
||||
Extension<DCClass>::PythonClassDefsImpl *Extension<DCClass>::
|
||||
do_get_defs() const {
|
||||
if (!_this->_python_class_defs) {
|
||||
_this->_python_class_defs = new PythonClassDefsImpl();
|
||||
}
|
||||
return (PythonClassDefsImpl *)_this->_python_class_defs.p();
|
||||
}
|
||||
|
||||
#endif // HAVE_PYTHON
|
||||
|
|
@ -0,0 +1,93 @@
|
|||
/**
|
||||
* 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 dcClass_ext.h
|
||||
* @author CFSworks
|
||||
* @date 2019-07-03
|
||||
*/
|
||||
|
||||
#ifndef DCCLASS_EXT_H
|
||||
#define DCCLASS_EXT_H
|
||||
|
||||
#include "dtoolbase.h"
|
||||
|
||||
#ifdef HAVE_PYTHON
|
||||
|
||||
#include "extension.h"
|
||||
#include "dcClass.h"
|
||||
#include "py_panda.h"
|
||||
|
||||
/**
|
||||
* This class defines the extension methods for DCClass, which are called
|
||||
* instead of any C++ methods with the same prototype.
|
||||
*/
|
||||
template<>
|
||||
class Extension<DCClass> : public ExtensionBase<DCClass> {
|
||||
public:
|
||||
bool has_class_def() const;
|
||||
void set_class_def(PyObject *class_def);
|
||||
PyObject *get_class_def() const;
|
||||
bool has_owner_class_def() const;
|
||||
void set_owner_class_def(PyObject *owner_class_def);
|
||||
PyObject *get_owner_class_def() const;
|
||||
|
||||
void receive_update(PyObject *distobj, DatagramIterator &di) const;
|
||||
void receive_update_broadcast_required(PyObject *distobj, DatagramIterator &di) const;
|
||||
void receive_update_broadcast_required_owner(PyObject *distobj, DatagramIterator &di) const;
|
||||
void receive_update_all_required(PyObject *distobj, DatagramIterator &di) const;
|
||||
void receive_update_other(PyObject *distobj, DatagramIterator &di) const;
|
||||
|
||||
void direct_update(PyObject *distobj, const std::string &field_name,
|
||||
const vector_uchar &value_blob);
|
||||
void direct_update(PyObject *distobj, const std::string &field_name,
|
||||
const Datagram &datagram);
|
||||
bool pack_required_field(Datagram &datagram, PyObject *distobj,
|
||||
const DCField *field) const;
|
||||
bool pack_required_field(DCPacker &packer, PyObject *distobj,
|
||||
const DCField *field) const;
|
||||
|
||||
|
||||
|
||||
Datagram client_format_update(const std::string &field_name,
|
||||
DOID_TYPE do_id, PyObject *args) const;
|
||||
Datagram ai_format_update(const std::string &field_name, DOID_TYPE do_id,
|
||||
CHANNEL_TYPE to_id, CHANNEL_TYPE from_id, PyObject *args) const;
|
||||
Datagram ai_format_update_msg_type(const std::string &field_name, DOID_TYPE do_id,
|
||||
CHANNEL_TYPE to_id, CHANNEL_TYPE from_id, int msg_type, PyObject *args) const;
|
||||
Datagram ai_format_generate(PyObject *distobj, DOID_TYPE do_id,
|
||||
ZONEID_TYPE parent_id, ZONEID_TYPE zone_id,
|
||||
CHANNEL_TYPE district_channel_id,
|
||||
CHANNEL_TYPE from_channel_id,
|
||||
PyObject *optional_fields) const;
|
||||
Datagram client_format_generate_CMU(PyObject *distobj, DOID_TYPE do_id,
|
||||
ZONEID_TYPE zone_id,
|
||||
PyObject *optional_fields) const;
|
||||
|
||||
private:
|
||||
/**
|
||||
* Implementation of DCClass::PythonClassDefs which actually stores the
|
||||
* Python pointers. This needs to be defined here rather than on DCClass
|
||||
* itself, since DCClass cannot include Python.h or call Python functions.
|
||||
*/
|
||||
class PythonClassDefsImpl : public DCClass::PythonClassDefs {
|
||||
public:
|
||||
virtual ~PythonClassDefsImpl() {
|
||||
Py_XDECREF(_class_def);
|
||||
Py_XDECREF(_owner_class_def);
|
||||
}
|
||||
|
||||
PyObject *_class_def = nullptr;
|
||||
PyObject *_owner_class_def = nullptr;
|
||||
};
|
||||
|
||||
PythonClassDefsImpl *do_get_defs() const;
|
||||
};
|
||||
|
||||
#endif // HAVE_PYTHON
|
||||
|
||||
#endif // DCCLASS_EXT_H
|
||||
|
|
@ -18,16 +18,6 @@
|
|||
#include "hashGenerator.h"
|
||||
#include "dcmsgtypes.h"
|
||||
|
||||
#ifdef HAVE_PYTHON
|
||||
#include "py_panda.h"
|
||||
#endif
|
||||
|
||||
#ifdef WITHIN_PANDA
|
||||
#include "pStatTimer.h"
|
||||
#endif
|
||||
|
||||
using std::string;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
|
|
@ -58,7 +48,7 @@ DCField() :
|
|||
*
|
||||
*/
|
||||
DCField::
|
||||
DCField(const string &name, DCClass *dclass) :
|
||||
DCField(const std::string &name, DCClass *dclass) :
|
||||
DCPackerInterface(name),
|
||||
_dclass(dclass)
|
||||
#ifdef WITHIN_PANDA
|
||||
|
|
@ -161,14 +151,14 @@ as_parameter() const {
|
|||
* string formatting it for human consumption. Returns empty string if there
|
||||
* is an error.
|
||||
*/
|
||||
string DCField::
|
||||
std::string DCField::
|
||||
format_data(const vector_uchar &packed_data, bool show_field_names) {
|
||||
DCPacker packer;
|
||||
packer.set_unpack_data(packed_data);
|
||||
packer.begin_unpack(this);
|
||||
string result = packer.unpack_and_format(show_field_names);
|
||||
std::string result = packer.unpack_and_format(show_field_names);
|
||||
if (!packer.end_unpack()) {
|
||||
return string();
|
||||
return std::string();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
|
@ -179,7 +169,7 @@ format_data(const vector_uchar &packed_data, bool show_field_names) {
|
|||
* the corresponding packed data. Returns empty string if there is an error.
|
||||
*/
|
||||
vector_uchar DCField::
|
||||
parse_string(const string &formatted_string) {
|
||||
parse_string(const std::string &formatted_string) {
|
||||
DCPacker packer;
|
||||
packer.begin_pack(this);
|
||||
if (!packer.parse_and_pack(formatted_string)) {
|
||||
|
|
@ -212,254 +202,6 @@ validate_ranges(const vector_uchar &packed_data) const {
|
|||
return (packer.get_num_unpacked_bytes() == packed_data.size());
|
||||
}
|
||||
|
||||
#ifdef HAVE_PYTHON
|
||||
/**
|
||||
* Packs the Python arguments from the indicated tuple into the packer.
|
||||
* Returns true on success, false on failure.
|
||||
*
|
||||
* It is assumed that the packer is currently positioned on this field.
|
||||
*/
|
||||
bool DCField::
|
||||
pack_args(DCPacker &packer, PyObject *sequence) const {
|
||||
nassertr(!packer.had_error(), false);
|
||||
nassertr(packer.get_current_field() == this, false);
|
||||
|
||||
packer.pack_object(sequence);
|
||||
if (!packer.had_error()) {
|
||||
/*
|
||||
cerr << "pack " << get_name() << get_pystr(sequence) << "\n";
|
||||
*/
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!Notify::ptr()->has_assert_failed()) {
|
||||
std::ostringstream strm;
|
||||
PyObject *exc_type = PyExc_Exception;
|
||||
|
||||
if (as_parameter() != nullptr) {
|
||||
// If it's a parameter-type field, the value may or may not be a
|
||||
// sequence.
|
||||
if (packer.had_pack_error()) {
|
||||
strm << "Incorrect arguments to field: " << get_name()
|
||||
<< " = " << get_pystr(sequence);
|
||||
exc_type = PyExc_TypeError;
|
||||
} else {
|
||||
strm << "Value out of range on field: " << get_name()
|
||||
<< " = " << get_pystr(sequence);
|
||||
exc_type = PyExc_ValueError;
|
||||
}
|
||||
|
||||
} else {
|
||||
// If it's a molecular or atomic field, the value should be a sequence.
|
||||
PyObject *tuple = PySequence_Tuple(sequence);
|
||||
if (tuple == nullptr) {
|
||||
strm << "Value for " << get_name() << " not a sequence: " \
|
||||
<< get_pystr(sequence);
|
||||
exc_type = PyExc_TypeError;
|
||||
|
||||
} else {
|
||||
if (packer.had_pack_error()) {
|
||||
strm << "Incorrect arguments to field: " << get_name()
|
||||
<< get_pystr(sequence);
|
||||
exc_type = PyExc_TypeError;
|
||||
} else {
|
||||
strm << "Value out of range on field: " << get_name()
|
||||
<< get_pystr(sequence);
|
||||
exc_type = PyExc_ValueError;
|
||||
}
|
||||
|
||||
Py_DECREF(tuple);
|
||||
}
|
||||
}
|
||||
|
||||
string message = strm.str();
|
||||
PyErr_SetString(exc_type, message.c_str());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
#endif // HAVE_PYTHON
|
||||
|
||||
#ifdef HAVE_PYTHON
|
||||
/**
|
||||
* Unpacks the values from the packer, beginning at the current point in the
|
||||
* unpack_buffer, into a Python tuple and returns the tuple.
|
||||
*
|
||||
* It is assumed that the packer is currently positioned on this field.
|
||||
*/
|
||||
PyObject *DCField::
|
||||
unpack_args(DCPacker &packer) const {
|
||||
nassertr(!packer.had_error(), nullptr);
|
||||
nassertr(packer.get_current_field() == this, nullptr);
|
||||
|
||||
size_t start_byte = packer.get_num_unpacked_bytes();
|
||||
PyObject *object = packer.unpack_object();
|
||||
|
||||
if (!packer.had_error()) {
|
||||
// Successfully unpacked.
|
||||
/*
|
||||
cerr << "recv " << get_name() << get_pystr(object) << "\n";
|
||||
*/
|
||||
|
||||
return object;
|
||||
}
|
||||
|
||||
if (!Notify::ptr()->has_assert_failed()) {
|
||||
std::ostringstream strm;
|
||||
PyObject *exc_type = PyExc_Exception;
|
||||
|
||||
if (packer.had_pack_error()) {
|
||||
strm << "Data error unpacking field ";
|
||||
output(strm, true);
|
||||
size_t length = packer.get_unpack_length() - start_byte;
|
||||
strm << "\nGot data (" << (int)length << " bytes):\n";
|
||||
Datagram dg(packer.get_unpack_data() + start_byte, length);
|
||||
dg.dump_hex(strm);
|
||||
size_t error_byte = packer.get_num_unpacked_bytes() - start_byte;
|
||||
strm << "Error detected on byte " << error_byte
|
||||
<< " (" << std::hex << error_byte << std::dec << " hex)";
|
||||
|
||||
exc_type = PyExc_RuntimeError;
|
||||
} else {
|
||||
strm << "Value outside specified range when unpacking field "
|
||||
<< get_name() << ": " << get_pystr(object);
|
||||
exc_type = PyExc_ValueError;
|
||||
}
|
||||
|
||||
string message = strm.str();
|
||||
PyErr_SetString(exc_type, message.c_str());
|
||||
}
|
||||
|
||||
Py_XDECREF(object);
|
||||
return nullptr;
|
||||
}
|
||||
#endif // HAVE_PYTHON
|
||||
|
||||
#ifdef HAVE_PYTHON
|
||||
/**
|
||||
* Extracts the update message out of the datagram and applies it to the
|
||||
* indicated object by calling the appropriate method.
|
||||
*/
|
||||
void DCField::
|
||||
receive_update(DCPacker &packer, PyObject *distobj) const {
|
||||
if (as_parameter() != nullptr) {
|
||||
// If it's a parameter-type field, just store a new value on the object.
|
||||
PyObject *value = unpack_args(packer);
|
||||
if (value != nullptr) {
|
||||
PyObject_SetAttrString(distobj, (char *)_name.c_str(), value);
|
||||
}
|
||||
Py_DECREF(value);
|
||||
|
||||
} else {
|
||||
// Otherwise, it must be an atomic or molecular field, so call the
|
||||
// corresponding method.
|
||||
|
||||
if (!PyObject_HasAttrString(distobj, (char *)_name.c_str())) {
|
||||
// If there's no Python method to receive this message, don't bother
|
||||
// unpacking it to a Python tuple--just skip past the message.
|
||||
packer.unpack_skip();
|
||||
|
||||
} else {
|
||||
// Otherwise, get a Python tuple from the args and call the Python
|
||||
// method.
|
||||
PyObject *args = unpack_args(packer);
|
||||
|
||||
if (args != nullptr) {
|
||||
PyObject *func = PyObject_GetAttrString(distobj, (char *)_name.c_str());
|
||||
nassertv(func != nullptr);
|
||||
|
||||
PyObject *result;
|
||||
{
|
||||
#ifdef WITHIN_PANDA
|
||||
PStatTimer timer(((DCField *)this)->_field_update_pcollector);
|
||||
#endif
|
||||
result = PyObject_CallObject(func, args);
|
||||
}
|
||||
Py_XDECREF(result);
|
||||
Py_DECREF(func);
|
||||
Py_DECREF(args);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif // HAVE_PYTHON
|
||||
|
||||
#ifdef HAVE_PYTHON
|
||||
/**
|
||||
* Generates a datagram containing the message necessary to send an update for
|
||||
* the indicated distributed object from the client.
|
||||
*/
|
||||
Datagram DCField::
|
||||
client_format_update(DOID_TYPE do_id, PyObject *args) const {
|
||||
DCPacker packer;
|
||||
|
||||
packer.raw_pack_uint16(CLIENT_OBJECT_SET_FIELD);
|
||||
packer.raw_pack_uint32(do_id);
|
||||
packer.raw_pack_uint16(_number);
|
||||
|
||||
packer.begin_pack(this);
|
||||
pack_args(packer, args);
|
||||
if (!packer.end_pack()) {
|
||||
return Datagram();
|
||||
}
|
||||
|
||||
return Datagram(packer.get_data(), packer.get_length());
|
||||
}
|
||||
#endif // HAVE_PYTHON
|
||||
|
||||
#ifdef HAVE_PYTHON
|
||||
/**
|
||||
* Generates a datagram containing the message necessary to send an update for
|
||||
* the indicated distributed object from the AI.
|
||||
*/
|
||||
Datagram DCField::
|
||||
ai_format_update(DOID_TYPE do_id, CHANNEL_TYPE to_id, CHANNEL_TYPE from_id, PyObject *args) const {
|
||||
DCPacker packer;
|
||||
|
||||
packer.raw_pack_uint8(1);
|
||||
packer.RAW_PACK_CHANNEL(to_id);
|
||||
packer.RAW_PACK_CHANNEL(from_id);
|
||||
packer.raw_pack_uint16(STATESERVER_OBJECT_SET_FIELD);
|
||||
packer.raw_pack_uint32(do_id);
|
||||
packer.raw_pack_uint16(_number);
|
||||
|
||||
packer.begin_pack(this);
|
||||
pack_args(packer, args);
|
||||
if (!packer.end_pack()) {
|
||||
return Datagram();
|
||||
}
|
||||
|
||||
return Datagram(packer.get_data(), packer.get_length());
|
||||
}
|
||||
#endif // HAVE_PYTHON
|
||||
|
||||
#ifdef HAVE_PYTHON
|
||||
/**
|
||||
* Generates a datagram containing the message necessary to send an update,
|
||||
* with the msg type, for the indicated distributed object from the AI.
|
||||
*/
|
||||
Datagram DCField::
|
||||
ai_format_update_msg_type(DOID_TYPE do_id, CHANNEL_TYPE to_id, CHANNEL_TYPE from_id, int msg_type, PyObject *args) const {
|
||||
DCPacker packer;
|
||||
|
||||
packer.raw_pack_uint8(1);
|
||||
packer.RAW_PACK_CHANNEL(to_id);
|
||||
packer.RAW_PACK_CHANNEL(from_id);
|
||||
packer.raw_pack_uint16(msg_type);
|
||||
packer.raw_pack_uint32(do_id);
|
||||
packer.raw_pack_uint16(_number);
|
||||
|
||||
packer.begin_pack(this);
|
||||
pack_args(packer, args);
|
||||
if (!packer.end_pack()) {
|
||||
return Datagram();
|
||||
}
|
||||
|
||||
return Datagram(packer.get_data(), packer.get_length());
|
||||
}
|
||||
#endif // HAVE_PYTHON
|
||||
|
||||
|
||||
/**
|
||||
* Accumulates the properties of this field into the hash.
|
||||
*/
|
||||
|
|
@ -499,62 +241,13 @@ pack_default_value(DCPackData &pack_data, bool &) const {
|
|||
* Sets the name of this field.
|
||||
*/
|
||||
void DCField::
|
||||
set_name(const string &name) {
|
||||
set_name(const std::string &name) {
|
||||
DCPackerInterface::set_name(name);
|
||||
if (_dclass != nullptr) {
|
||||
_dclass->_dc_file->mark_inherited_fields_stale();
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef HAVE_PYTHON
|
||||
/**
|
||||
* Returns the string representation of the indicated Python object.
|
||||
*/
|
||||
string DCField::
|
||||
get_pystr(PyObject *value) {
|
||||
if (value == nullptr) {
|
||||
return "(null)";
|
||||
}
|
||||
|
||||
PyObject *str = PyObject_Str(value);
|
||||
if (str != nullptr) {
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
string result = PyUnicode_AsUTF8(str);
|
||||
#else
|
||||
string result = PyString_AsString(str);
|
||||
#endif
|
||||
Py_DECREF(str);
|
||||
return result;
|
||||
}
|
||||
|
||||
PyObject *repr = PyObject_Repr(value);
|
||||
if (repr != nullptr) {
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
string result = PyUnicode_AsUTF8(repr);
|
||||
#else
|
||||
string result = PyString_AsString(repr);
|
||||
#endif
|
||||
Py_DECREF(repr);
|
||||
return result;
|
||||
}
|
||||
|
||||
if (value->ob_type != nullptr) {
|
||||
PyObject *typestr = PyObject_Str((PyObject *)(value->ob_type));
|
||||
if (typestr != nullptr) {
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
string result = PyUnicode_AsUTF8(typestr);
|
||||
#else
|
||||
string result = PyString_AsString(typestr);
|
||||
#endif
|
||||
Py_DECREF(typestr);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
return "(invalid object)";
|
||||
}
|
||||
#endif // HAVE_PYTHON
|
||||
|
||||
/**
|
||||
* Recomputes the default value of the field by repacking it.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -17,10 +17,11 @@
|
|||
#include "dcbase.h"
|
||||
#include "dcPackerInterface.h"
|
||||
#include "dcKeywordList.h"
|
||||
#include "dcPython.h"
|
||||
|
||||
#ifdef WITHIN_PANDA
|
||||
#include "pStatCollector.h"
|
||||
#include "extension.h"
|
||||
#include "datagram.h"
|
||||
#endif
|
||||
|
||||
class DCPacker;
|
||||
|
|
@ -76,18 +77,17 @@ PUBLISHED:
|
|||
INLINE void output(std::ostream &out) const;
|
||||
INLINE void write(std::ostream &out, int indent_level) const;
|
||||
|
||||
#ifdef HAVE_PYTHON
|
||||
bool pack_args(DCPacker &packer, PyObject *sequence) const;
|
||||
PyObject *unpack_args(DCPacker &packer) const;
|
||||
EXTENSION(bool pack_args(DCPacker &packer, PyObject *sequence) const);
|
||||
EXTENSION(PyObject *unpack_args(DCPacker &packer) const);
|
||||
|
||||
void receive_update(DCPacker &packer, PyObject *distobj) const;
|
||||
EXTENSION(void receive_update(DCPacker &packer, PyObject *distobj) const);
|
||||
|
||||
Datagram client_format_update(DOID_TYPE do_id, PyObject *args) const;
|
||||
Datagram ai_format_update(DOID_TYPE do_id, CHANNEL_TYPE to_id, CHANNEL_TYPE from_id,
|
||||
PyObject *args) const;
|
||||
Datagram ai_format_update_msg_type(DOID_TYPE do_id, CHANNEL_TYPE to_id, CHANNEL_TYPE from_id,
|
||||
int msg_type, PyObject *args) const;
|
||||
#endif
|
||||
EXTENSION(Datagram client_format_update(DOID_TYPE do_id, PyObject *args) const);
|
||||
EXTENSION(Datagram ai_format_update(DOID_TYPE do_id, CHANNEL_TYPE to_id,
|
||||
CHANNEL_TYPE from_id, PyObject *args) const);
|
||||
EXTENSION(Datagram ai_format_update_msg_type(DOID_TYPE do_id, CHANNEL_TYPE to_id,
|
||||
CHANNEL_TYPE from_id, int msg_type,
|
||||
PyObject *args) const);
|
||||
|
||||
public:
|
||||
virtual void output(std::ostream &out, bool brief) const=0;
|
||||
|
|
@ -100,10 +100,6 @@ public:
|
|||
INLINE void set_class(DCClass *dclass);
|
||||
INLINE void set_default_value(vector_uchar default_value);
|
||||
|
||||
#ifdef HAVE_PYTHON
|
||||
static std::string get_pystr(PyObject *value);
|
||||
#endif
|
||||
|
||||
protected:
|
||||
void refresh_default_value();
|
||||
|
||||
|
|
@ -119,6 +115,8 @@ private:
|
|||
|
||||
#ifdef WITHIN_PANDA
|
||||
PStatCollector _field_update_pcollector;
|
||||
|
||||
friend class Extension<DCField>;
|
||||
#endif
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,305 @@
|
|||
/**
|
||||
* 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 dcField_ext.cxx
|
||||
* @author CFSworks
|
||||
* @date 2019-07-03
|
||||
*/
|
||||
|
||||
#include "dcField_ext.h"
|
||||
#include "dcPacker_ext.h"
|
||||
#include "dcmsgtypes.h"
|
||||
|
||||
#include "datagram.h"
|
||||
#include "pStatTimer.h"
|
||||
|
||||
#ifdef HAVE_PYTHON
|
||||
|
||||
/**
|
||||
* Packs the Python arguments from the indicated tuple into the packer.
|
||||
* Returns true on success, false on failure.
|
||||
*
|
||||
* It is assumed that the packer is currently positioned on this field.
|
||||
*/
|
||||
bool Extension<DCField>::
|
||||
pack_args(DCPacker &packer, PyObject *sequence) const {
|
||||
nassertr(!packer.had_error(), false);
|
||||
nassertr(packer.get_current_field() == _this, false);
|
||||
|
||||
invoke_extension(&packer).pack_object(sequence);
|
||||
if (!packer.had_error()) {
|
||||
/*
|
||||
cerr << "pack " << _this->get_name() << get_pystr(sequence) << "\n";
|
||||
*/
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!Notify::ptr()->has_assert_failed()) {
|
||||
std::ostringstream strm;
|
||||
PyObject *exc_type = PyExc_Exception;
|
||||
|
||||
if (_this->as_parameter() != nullptr) {
|
||||
// If it's a parameter-type field, the value may or may not be a
|
||||
// sequence.
|
||||
if (packer.had_pack_error()) {
|
||||
strm << "Incorrect arguments to field: " << _this->get_name()
|
||||
<< " = " << get_pystr(sequence);
|
||||
exc_type = PyExc_TypeError;
|
||||
} else {
|
||||
strm << "Value out of range on field: " << _this->get_name()
|
||||
<< " = " << get_pystr(sequence);
|
||||
exc_type = PyExc_ValueError;
|
||||
}
|
||||
|
||||
} else {
|
||||
// If it's a molecular or atomic field, the value should be a sequence.
|
||||
PyObject *tuple = PySequence_Tuple(sequence);
|
||||
if (tuple == nullptr) {
|
||||
strm << "Value for " << _this->get_name() << " not a sequence: " \
|
||||
<< get_pystr(sequence);
|
||||
exc_type = PyExc_TypeError;
|
||||
|
||||
} else {
|
||||
if (packer.had_pack_error()) {
|
||||
strm << "Incorrect arguments to field: " << _this->get_name()
|
||||
<< get_pystr(sequence);
|
||||
exc_type = PyExc_TypeError;
|
||||
} else {
|
||||
strm << "Value out of range on field: " << _this->get_name()
|
||||
<< get_pystr(sequence);
|
||||
exc_type = PyExc_ValueError;
|
||||
}
|
||||
|
||||
Py_DECREF(tuple);
|
||||
}
|
||||
}
|
||||
|
||||
std::string message = strm.str();
|
||||
PyErr_SetString(exc_type, message.c_str());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unpacks the values from the packer, beginning at the current point in the
|
||||
* unpack_buffer, into a Python tuple and returns the tuple.
|
||||
*
|
||||
* It is assumed that the packer is currently positioned on this field.
|
||||
*/
|
||||
PyObject *Extension<DCField>::
|
||||
unpack_args(DCPacker &packer) const {
|
||||
nassertr(!packer.had_error(), nullptr);
|
||||
nassertr(packer.get_current_field() == _this, nullptr);
|
||||
|
||||
size_t start_byte = packer.get_num_unpacked_bytes();
|
||||
PyObject *object = invoke_extension(&packer).unpack_object();
|
||||
|
||||
if (!packer.had_error()) {
|
||||
// Successfully unpacked.
|
||||
/*
|
||||
cerr << "recv " << _this->get_name() << get_pystr(object) << "\n";
|
||||
*/
|
||||
|
||||
return object;
|
||||
}
|
||||
|
||||
if (!Notify::ptr()->has_assert_failed()) {
|
||||
std::ostringstream strm;
|
||||
PyObject *exc_type = PyExc_Exception;
|
||||
|
||||
if (packer.had_pack_error()) {
|
||||
strm << "Data error unpacking field ";
|
||||
_this->output(strm, true);
|
||||
size_t length = packer.get_unpack_length() - start_byte;
|
||||
strm << "\nGot data (" << (int)length << " bytes):\n";
|
||||
Datagram dg(packer.get_unpack_data() + start_byte, length);
|
||||
dg.dump_hex(strm);
|
||||
size_t error_byte = packer.get_num_unpacked_bytes() - start_byte;
|
||||
strm << "Error detected on byte " << error_byte
|
||||
<< " (" << std::hex << error_byte << std::dec << " hex)";
|
||||
|
||||
exc_type = PyExc_RuntimeError;
|
||||
} else {
|
||||
strm << "Value outside specified range when unpacking field "
|
||||
<< _this->get_name() << ": " << get_pystr(object);
|
||||
exc_type = PyExc_ValueError;
|
||||
}
|
||||
|
||||
std::string message = strm.str();
|
||||
PyErr_SetString(exc_type, message.c_str());
|
||||
}
|
||||
|
||||
Py_XDECREF(object);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the update message out of the datagram and applies it to the
|
||||
* indicated object by calling the appropriate method.
|
||||
*/
|
||||
void Extension<DCField>::
|
||||
receive_update(DCPacker &packer, PyObject *distobj) const {
|
||||
if (_this->as_parameter() != nullptr) {
|
||||
// If it's a parameter-type field, just store a new value on the object.
|
||||
PyObject *value = unpack_args(packer);
|
||||
if (value != nullptr) {
|
||||
PyObject_SetAttrString(distobj, (char *)_this->_name.c_str(), value);
|
||||
}
|
||||
Py_DECREF(value);
|
||||
|
||||
} else {
|
||||
// Otherwise, it must be an atomic or molecular field, so call the
|
||||
// corresponding method.
|
||||
|
||||
if (!PyObject_HasAttrString(distobj, (char *)_this->_name.c_str())) {
|
||||
// If there's no Python method to receive this message, don't bother
|
||||
// unpacking it to a Python tuple--just skip past the message.
|
||||
packer.unpack_skip();
|
||||
|
||||
} else {
|
||||
// Otherwise, get a Python tuple from the args and call the Python
|
||||
// method.
|
||||
PyObject *args = unpack_args(packer);
|
||||
|
||||
if (args != nullptr) {
|
||||
PyObject *func = PyObject_GetAttrString(distobj, (char *)_this->_name.c_str());
|
||||
nassertv(func != nullptr);
|
||||
|
||||
PyObject *result;
|
||||
{
|
||||
#ifdef WITHIN_PANDA
|
||||
PStatTimer timer(_this->_field_update_pcollector);
|
||||
#endif
|
||||
result = PyObject_CallObject(func, args);
|
||||
}
|
||||
Py_XDECREF(result);
|
||||
Py_DECREF(func);
|
||||
Py_DECREF(args);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a datagram containing the message necessary to send an update for
|
||||
* the indicated distributed object from the client.
|
||||
*/
|
||||
Datagram Extension<DCField>::
|
||||
client_format_update(DOID_TYPE do_id, PyObject *args) const {
|
||||
DCPacker packer;
|
||||
|
||||
packer.raw_pack_uint16(CLIENT_OBJECT_SET_FIELD);
|
||||
packer.raw_pack_uint32(do_id);
|
||||
packer.raw_pack_uint16(_this->_number);
|
||||
|
||||
packer.begin_pack(_this);
|
||||
pack_args(packer, args);
|
||||
if (!packer.end_pack()) {
|
||||
return Datagram();
|
||||
}
|
||||
|
||||
return Datagram(packer.get_data(), packer.get_length());
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a datagram containing the message necessary to send an update for
|
||||
* the indicated distributed object from the AI.
|
||||
*/
|
||||
Datagram Extension<DCField>::
|
||||
ai_format_update(DOID_TYPE do_id, CHANNEL_TYPE to_id, CHANNEL_TYPE from_id, PyObject *args) const {
|
||||
DCPacker packer;
|
||||
|
||||
packer.raw_pack_uint8(1);
|
||||
packer.RAW_PACK_CHANNEL(to_id);
|
||||
packer.RAW_PACK_CHANNEL(from_id);
|
||||
packer.raw_pack_uint16(STATESERVER_OBJECT_SET_FIELD);
|
||||
packer.raw_pack_uint32(do_id);
|
||||
packer.raw_pack_uint16(_this->_number);
|
||||
|
||||
packer.begin_pack(_this);
|
||||
pack_args(packer, args);
|
||||
if (!packer.end_pack()) {
|
||||
return Datagram();
|
||||
}
|
||||
|
||||
return Datagram(packer.get_data(), packer.get_length());
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a datagram containing the message necessary to send an update,
|
||||
* with the msg type, for the indicated distributed object from the AI.
|
||||
*/
|
||||
Datagram Extension<DCField>::
|
||||
ai_format_update_msg_type(DOID_TYPE do_id, CHANNEL_TYPE to_id, CHANNEL_TYPE from_id, int msg_type, PyObject *args) const {
|
||||
DCPacker packer;
|
||||
|
||||
packer.raw_pack_uint8(1);
|
||||
packer.RAW_PACK_CHANNEL(to_id);
|
||||
packer.RAW_PACK_CHANNEL(from_id);
|
||||
packer.raw_pack_uint16(msg_type);
|
||||
packer.raw_pack_uint32(do_id);
|
||||
packer.raw_pack_uint16(_this->_number);
|
||||
|
||||
packer.begin_pack(_this);
|
||||
pack_args(packer, args);
|
||||
if (!packer.end_pack()) {
|
||||
return Datagram();
|
||||
}
|
||||
|
||||
return Datagram(packer.get_data(), packer.get_length());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the string representation of the indicated Python object.
|
||||
*/
|
||||
std::string Extension<DCField>::
|
||||
get_pystr(PyObject *value) {
|
||||
if (value == nullptr) {
|
||||
return "(null)";
|
||||
}
|
||||
|
||||
PyObject *str = PyObject_Str(value);
|
||||
if (str != nullptr) {
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
std::string result = PyUnicode_AsUTF8(str);
|
||||
#else
|
||||
std::string result = PyString_AsString(str);
|
||||
#endif
|
||||
Py_DECREF(str);
|
||||
return result;
|
||||
}
|
||||
|
||||
PyObject *repr = PyObject_Repr(value);
|
||||
if (repr != nullptr) {
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
std::string result = PyUnicode_AsUTF8(repr);
|
||||
#else
|
||||
std::string result = PyString_AsString(repr);
|
||||
#endif
|
||||
Py_DECREF(repr);
|
||||
return result;
|
||||
}
|
||||
|
||||
if (value->ob_type != nullptr) {
|
||||
PyObject *typestr = PyObject_Str((PyObject *)(value->ob_type));
|
||||
if (typestr != nullptr) {
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
std::string result = PyUnicode_AsUTF8(typestr);
|
||||
#else
|
||||
std::string result = PyString_AsString(typestr);
|
||||
#endif
|
||||
Py_DECREF(typestr);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
return "(invalid object)";
|
||||
}
|
||||
|
||||
#endif // HAVE_PYTHON
|
||||
|
|
@ -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 dcField_ext.h
|
||||
* @author CFSworks
|
||||
* @date 2019-07-03
|
||||
*/
|
||||
|
||||
#ifndef DCFIELD_EXT_H
|
||||
#define DCFIELD_EXT_H
|
||||
|
||||
#include "dtoolbase.h"
|
||||
|
||||
#ifdef HAVE_PYTHON
|
||||
|
||||
#include "extension.h"
|
||||
#include "dcField.h"
|
||||
#include "py_panda.h"
|
||||
|
||||
/**
|
||||
* This class defines the extension methods for DCField, which are called
|
||||
* instead of any C++ methods with the same prototype.
|
||||
*/
|
||||
template<>
|
||||
class Extension<DCField> : public ExtensionBase<DCField> {
|
||||
public:
|
||||
bool pack_args(DCPacker &packer, PyObject *sequence) const;
|
||||
PyObject *unpack_args(DCPacker &packer) const;
|
||||
|
||||
void receive_update(DCPacker &packer, PyObject *distobj) const;
|
||||
|
||||
Datagram client_format_update(DOID_TYPE do_id, PyObject *args) const;
|
||||
Datagram ai_format_update(DOID_TYPE do_id, CHANNEL_TYPE to_id, CHANNEL_TYPE from_id,
|
||||
PyObject *args) const;
|
||||
Datagram ai_format_update_msg_type(DOID_TYPE do_id, CHANNEL_TYPE to_id, CHANNEL_TYPE from_id,
|
||||
int msg_type, PyObject *args) const;
|
||||
|
||||
static std::string get_pystr(PyObject *value);
|
||||
};
|
||||
|
||||
#endif // HAVE_PYTHON
|
||||
|
||||
#endif // DCFIELD_EXT_H
|
||||
|
|
@ -19,10 +19,6 @@
|
|||
#include "dcSwitchParameter.h"
|
||||
#include "dcClass.h"
|
||||
|
||||
#ifdef HAVE_PYTHON
|
||||
#include "py_panda.h"
|
||||
#endif
|
||||
|
||||
using std::istream;
|
||||
using std::istringstream;
|
||||
using std::ostream;
|
||||
|
|
@ -622,335 +618,6 @@ unpack_skip() {
|
|||
}
|
||||
}
|
||||
|
||||
#ifdef HAVE_PYTHON
|
||||
/**
|
||||
* Packs the Python object of whatever type into the packer. Each numeric
|
||||
* object and string object maps to the corresponding pack_value() call; a
|
||||
* tuple or sequence maps to a push() followed by all of the tuple's contents
|
||||
* followed by a pop().
|
||||
*/
|
||||
void DCPacker::
|
||||
pack_object(PyObject *object) {
|
||||
nassertv(_mode == M_pack || _mode == M_repack);
|
||||
DCPackType pack_type = get_pack_type();
|
||||
|
||||
// had to add this for basic 64 and unsigned data to get packed right .. Not
|
||||
// sure if we can just do the rest this way..
|
||||
|
||||
switch(pack_type)
|
||||
{
|
||||
case PT_int64:
|
||||
if(PyLong_Check(object))
|
||||
{
|
||||
pack_int64(PyLong_AsLongLong(object));
|
||||
return;
|
||||
}
|
||||
#if PY_MAJOR_VERSION < 3
|
||||
else if (PyInt_Check(object))
|
||||
{
|
||||
pack_int64(PyInt_AsLong(object));
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
break;
|
||||
case PT_uint64:
|
||||
if(PyLong_Check(object))
|
||||
{
|
||||
pack_uint64(PyLong_AsUnsignedLongLong(object));
|
||||
return;
|
||||
}
|
||||
#if PY_MAJOR_VERSION < 3
|
||||
else if(PyInt_Check(object))
|
||||
{
|
||||
PyObject *obj1 = PyNumber_Long(object);
|
||||
pack_int(PyLong_AsUnsignedLongLong(obj1));
|
||||
Py_DECREF(obj1);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
break;
|
||||
case PT_int:
|
||||
if(PyLong_Check(object))
|
||||
{
|
||||
pack_int(PyLong_AsLong(object));
|
||||
return;
|
||||
}
|
||||
#if PY_MAJOR_VERSION < 3
|
||||
else if (PyInt_Check(object))
|
||||
{
|
||||
pack_int(PyInt_AsLong(object));
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
break;
|
||||
case PT_uint:
|
||||
if(PyLong_Check(object))
|
||||
{
|
||||
pack_uint(PyLong_AsUnsignedLong(object));
|
||||
return;
|
||||
}
|
||||
#if PY_MAJOR_VERSION < 3
|
||||
else if (PyInt_Check(object))
|
||||
{
|
||||
PyObject *obj1 = PyNumber_Long(object);
|
||||
pack_uint(PyLong_AsUnsignedLong(obj1));
|
||||
Py_DECREF(obj1);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (PyLong_Check(object)) {
|
||||
pack_int(PyLong_AsLong(object));
|
||||
#if PY_MAJOR_VERSION < 3
|
||||
} else if (PyInt_Check(object)) {
|
||||
pack_int(PyInt_AS_LONG(object));
|
||||
#endif
|
||||
} else if (PyFloat_Check(object)) {
|
||||
pack_double(PyFloat_AS_DOUBLE(object));
|
||||
} else if (PyLong_Check(object)) {
|
||||
pack_int64(PyLong_AsLongLong(object));
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
} else if (PyUnicode_Check(object)) {
|
||||
const char *buffer;
|
||||
Py_ssize_t length;
|
||||
buffer = PyUnicode_AsUTF8AndSize(object, &length);
|
||||
if (buffer) {
|
||||
pack_string(string(buffer, length));
|
||||
}
|
||||
} else if (PyBytes_Check(object)) {
|
||||
const unsigned char *buffer;
|
||||
Py_ssize_t length;
|
||||
PyBytes_AsStringAndSize(object, (char **)&buffer, &length);
|
||||
if (buffer) {
|
||||
pack_blob(vector_uchar(buffer, buffer + length));
|
||||
}
|
||||
#else
|
||||
} else if (PyString_Check(object) || PyUnicode_Check(object)) {
|
||||
char *buffer;
|
||||
Py_ssize_t length;
|
||||
PyString_AsStringAndSize(object, &buffer, &length);
|
||||
if (buffer) {
|
||||
pack_string(string(buffer, length));
|
||||
}
|
||||
#endif
|
||||
} else {
|
||||
// For some reason, PySequence_Check() is incorrectly reporting that a
|
||||
// class instance is a sequence, even if it doesn't provide __len__, so we
|
||||
// double-check by testing for __len__ explicitly.
|
||||
bool is_sequence =
|
||||
(PySequence_Check(object) != 0) &&
|
||||
(PyObject_HasAttrString(object, "__len__") != 0);
|
||||
bool is_instance = false;
|
||||
|
||||
const DCClass *dclass = nullptr;
|
||||
const DCPackerInterface *current_field = get_current_field();
|
||||
if (current_field != nullptr) {
|
||||
const DCClassParameter *class_param = get_current_field()->as_class_parameter();
|
||||
if (class_param != nullptr) {
|
||||
dclass = class_param->get_class();
|
||||
|
||||
if (dclass->has_class_def()) {
|
||||
PyObject *class_def = dclass->get_class_def();
|
||||
is_instance = (PyObject_IsInstance(object, dclass->get_class_def()) != 0);
|
||||
Py_DECREF(class_def);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If dclass is not NULL, the packer is expecting a class object. There
|
||||
// are then two cases: (1) the user has supplied a matching class object,
|
||||
// or (2) the user has supplied a sequence object. Unfortunately, it may
|
||||
// be difficult to differentiate these two cases, since a class object may
|
||||
// also be a sequence object.
|
||||
|
||||
// The rule to differentiate them is:
|
||||
|
||||
// (1) If the supplied class object is an instance of the expected class
|
||||
// object, it is considered to be a class object.
|
||||
|
||||
// (2) Otherwise, if the supplied class object has a __len__() method
|
||||
// (i.e. PySequence_Check() returns true), then it is considered to be a
|
||||
// sequence.
|
||||
|
||||
// (3) Otherwise, it is considered to be a class object.
|
||||
|
||||
if (dclass != nullptr && (is_instance || !is_sequence)) {
|
||||
// The supplied object is either an instance of the expected class
|
||||
// object, or it is not a sequence--this is case (1) or (3).
|
||||
pack_class_object(dclass, object);
|
||||
} else if (is_sequence) {
|
||||
// The supplied object is not an instance of the expected class object,
|
||||
// but it is a sequence. This is case (2).
|
||||
push();
|
||||
int size = PySequence_Size(object);
|
||||
for (int i = 0; i < size; ++i) {
|
||||
PyObject *element = PySequence_GetItem(object, i);
|
||||
if (element != nullptr) {
|
||||
pack_object(element);
|
||||
Py_DECREF(element);
|
||||
} else {
|
||||
std::cerr << "Unable to extract item " << i << " from sequence.\n";
|
||||
}
|
||||
}
|
||||
pop();
|
||||
} else {
|
||||
// The supplied object is not a sequence, and we weren't expecting a
|
||||
// class parameter. This is none of the above, an error.
|
||||
ostringstream strm;
|
||||
strm << "Don't know how to pack object: "
|
||||
<< DCField::get_pystr(object);
|
||||
nassert_raise(strm.str());
|
||||
_pack_error = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif // HAVE_PYTHON
|
||||
|
||||
#ifdef HAVE_PYTHON
|
||||
/**
|
||||
* Unpacks a Python object of the appropriate type from the stream for the
|
||||
* current field. This may be an integer or a string for a simple field
|
||||
* object; if the current field represents a list of fields it will be a
|
||||
* tuple.
|
||||
*/
|
||||
PyObject *DCPacker::
|
||||
unpack_object() {
|
||||
PyObject *object = nullptr;
|
||||
|
||||
DCPackType pack_type = get_pack_type();
|
||||
|
||||
switch (pack_type) {
|
||||
case PT_invalid:
|
||||
object = Py_None;
|
||||
Py_INCREF(object);
|
||||
unpack_skip();
|
||||
break;
|
||||
|
||||
case PT_double:
|
||||
{
|
||||
double value = unpack_double();
|
||||
object = PyFloat_FromDouble(value);
|
||||
}
|
||||
break;
|
||||
|
||||
case PT_int:
|
||||
{
|
||||
int value = unpack_int();
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
object = PyLong_FromLong(value);
|
||||
#else
|
||||
object = PyInt_FromLong(value);
|
||||
#endif
|
||||
}
|
||||
break;
|
||||
|
||||
case PT_uint:
|
||||
{
|
||||
unsigned int value = unpack_uint();
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
object = PyLong_FromLong(value);
|
||||
#else
|
||||
if (value & 0x80000000) {
|
||||
object = PyLong_FromUnsignedLong(value);
|
||||
} else {
|
||||
object = PyInt_FromLong(value);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
break;
|
||||
|
||||
case PT_int64:
|
||||
{
|
||||
int64_t value = unpack_int64();
|
||||
object = PyLong_FromLongLong(value);
|
||||
}
|
||||
break;
|
||||
|
||||
case PT_uint64:
|
||||
{
|
||||
uint64_t value = unpack_uint64();
|
||||
object = PyLong_FromUnsignedLongLong(value);
|
||||
}
|
||||
break;
|
||||
|
||||
case PT_blob:
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
{
|
||||
string str;
|
||||
unpack_string(str);
|
||||
object = PyBytes_FromStringAndSize(str.data(), str.size());
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
// On Python 2, fall through to below.
|
||||
|
||||
case PT_string:
|
||||
{
|
||||
string str;
|
||||
unpack_string(str);
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
object = PyUnicode_FromStringAndSize(str.data(), str.size());
|
||||
#else
|
||||
object = PyString_FromStringAndSize(str.data(), str.size());
|
||||
#endif
|
||||
}
|
||||
break;
|
||||
|
||||
case PT_class:
|
||||
{
|
||||
const DCClassParameter *class_param = get_current_field()->as_class_parameter();
|
||||
if (class_param != nullptr) {
|
||||
const DCClass *dclass = class_param->get_class();
|
||||
if (dclass->has_class_def()) {
|
||||
// If we know what kind of class object this is and it has a valid
|
||||
// constructor, create the class object instead of just a tuple.
|
||||
object = unpack_class_object(dclass);
|
||||
if (object == nullptr) {
|
||||
std::cerr << "Unable to construct object of class "
|
||||
<< dclass->get_name() << "\n";
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Fall through (if no constructor)
|
||||
|
||||
// If we don't know what kind of class object it is, or it doesn't have a
|
||||
// constructor, fall through and make a tuple.
|
||||
default:
|
||||
{
|
||||
// First, build up a list from the nested objects.
|
||||
object = PyList_New(0);
|
||||
|
||||
push();
|
||||
while (more_nested_fields()) {
|
||||
PyObject *element = unpack_object();
|
||||
PyList_Append(object, element);
|
||||
Py_DECREF(element);
|
||||
}
|
||||
pop();
|
||||
|
||||
if (pack_type != PT_array) {
|
||||
// For these other kinds of objects, we'll convert the list into a
|
||||
// tuple.
|
||||
PyObject *tuple = PyList_AsTuple(object);
|
||||
Py_DECREF(object);
|
||||
object = tuple;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
nassertr(object != nullptr, nullptr);
|
||||
return object;
|
||||
}
|
||||
#endif // HAVE_PYTHON
|
||||
|
||||
|
||||
/**
|
||||
* Parses an object's value according to the DC file syntax (e.g. as a
|
||||
* default value string) and packs it. Returns true on success, false on a
|
||||
|
|
@ -1206,178 +873,3 @@ clear_stack() {
|
|||
_stack = next;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef HAVE_PYTHON
|
||||
/**
|
||||
* Given that the current element is a ClassParameter for a Python class
|
||||
* object, try to extract the appropriate values from the class object and
|
||||
* pack in.
|
||||
*/
|
||||
void DCPacker::
|
||||
pack_class_object(const DCClass *dclass, PyObject *object) {
|
||||
push();
|
||||
while (more_nested_fields() && !_pack_error) {
|
||||
const DCField *field = get_current_field()->as_field();
|
||||
nassertv(field != nullptr);
|
||||
get_class_element(dclass, object, field);
|
||||
}
|
||||
pop();
|
||||
}
|
||||
#endif // HAVE_PYTHON
|
||||
|
||||
#ifdef HAVE_PYTHON
|
||||
/**
|
||||
* Given that the current element is a ClassParameter for a Python class for
|
||||
* which we have a valid constructor, unpack it and fill in its values.
|
||||
*/
|
||||
PyObject *DCPacker::
|
||||
unpack_class_object(const DCClass *dclass) {
|
||||
PyObject *class_def = dclass->get_class_def();
|
||||
nassertr(class_def != nullptr, nullptr);
|
||||
|
||||
PyObject *object = nullptr;
|
||||
|
||||
if (!dclass->has_constructor()) {
|
||||
// If the class uses a default constructor, go ahead and create the Python
|
||||
// object for it now.
|
||||
object = PyObject_CallObject(class_def, nullptr);
|
||||
if (object == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
push();
|
||||
if (object == nullptr && more_nested_fields()) {
|
||||
// The first nested field will be the constructor.
|
||||
const DCField *field = get_current_field()->as_field();
|
||||
nassertr(field != nullptr, object);
|
||||
nassertr(field == dclass->get_constructor(), object);
|
||||
|
||||
set_class_element(class_def, object, field);
|
||||
|
||||
// By now, the object should have been constructed.
|
||||
if (object == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
while (more_nested_fields()) {
|
||||
const DCField *field = get_current_field()->as_field();
|
||||
nassertr(field != nullptr, object);
|
||||
|
||||
set_class_element(class_def, object, field);
|
||||
}
|
||||
pop();
|
||||
|
||||
return object;
|
||||
}
|
||||
#endif // HAVE_PYTHON
|
||||
|
||||
|
||||
#ifdef HAVE_PYTHON
|
||||
/**
|
||||
* Unpacks the current element and stuffs it on the Python class object in
|
||||
* whatever way is appropriate.
|
||||
*/
|
||||
void DCPacker::
|
||||
set_class_element(PyObject *class_def, PyObject *&object,
|
||||
const DCField *field) {
|
||||
string field_name = field->get_name();
|
||||
DCPackType pack_type = get_pack_type();
|
||||
|
||||
if (field_name.empty()) {
|
||||
switch (pack_type) {
|
||||
case PT_class:
|
||||
case PT_switch:
|
||||
// If the field has no name, but it is one of these container objects,
|
||||
// we want to unpack its nested objects directly into the class.
|
||||
push();
|
||||
while (more_nested_fields()) {
|
||||
const DCField *field = get_current_field()->as_field();
|
||||
nassertv(field != nullptr);
|
||||
nassertv(object != nullptr);
|
||||
set_class_element(class_def, object, field);
|
||||
}
|
||||
pop();
|
||||
break;
|
||||
|
||||
default:
|
||||
// Otherwise, we just skip over the field.
|
||||
unpack_skip();
|
||||
}
|
||||
|
||||
} else {
|
||||
// If the field does have a name, we will want to store it on the class,
|
||||
// either by calling a method (for a PT_field pack_type) or by setting a
|
||||
// value (for any other kind of pack_type).
|
||||
|
||||
PyObject *element = unpack_object();
|
||||
|
||||
if (pack_type == PT_field) {
|
||||
if (object == nullptr) {
|
||||
// If the object hasn't been constructed yet, assume this is the
|
||||
// constructor.
|
||||
object = PyObject_CallObject(class_def, element);
|
||||
|
||||
} else {
|
||||
if (PyObject_HasAttrString(object, (char *)field_name.c_str())) {
|
||||
PyObject *func = PyObject_GetAttrString(object, (char *)field_name.c_str());
|
||||
if (func != nullptr) {
|
||||
PyObject *result = PyObject_CallObject(func, element);
|
||||
Py_XDECREF(result);
|
||||
Py_DECREF(func);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
nassertv(object != nullptr);
|
||||
PyObject_SetAttrString(object, (char *)field_name.c_str(), element);
|
||||
}
|
||||
|
||||
Py_DECREF(element);
|
||||
}
|
||||
}
|
||||
#endif // HAVE_PYTHON
|
||||
|
||||
|
||||
#ifdef HAVE_PYTHON
|
||||
/**
|
||||
* Gets the current element from the Python object and packs it.
|
||||
*/
|
||||
void DCPacker::
|
||||
get_class_element(const DCClass *dclass, PyObject *object,
|
||||
const DCField *field) {
|
||||
string field_name = field->get_name();
|
||||
DCPackType pack_type = get_pack_type();
|
||||
|
||||
if (field_name.empty()) {
|
||||
switch (pack_type) {
|
||||
case PT_class:
|
||||
case PT_switch:
|
||||
// If the field has no name, but it is one of these container objects,
|
||||
// we want to get its nested objects directly from the class.
|
||||
push();
|
||||
while (more_nested_fields() && !_pack_error) {
|
||||
const DCField *field = get_current_field()->as_field();
|
||||
nassertv(field != nullptr);
|
||||
get_class_element(dclass, object, field);
|
||||
}
|
||||
pop();
|
||||
break;
|
||||
|
||||
default:
|
||||
// Otherwise, we just pack the default value.
|
||||
pack_default_value();
|
||||
}
|
||||
|
||||
} else {
|
||||
// If the field does have a name, we will want to get it from the class
|
||||
// and pack it. It just so happens that there's already a method that
|
||||
// does this on DCClass.
|
||||
|
||||
if (!dclass->pack_required_field(*this, object, field)) {
|
||||
_pack_error = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif // HAVE_PYTHON
|
||||
|
|
|
|||
|
|
@ -19,7 +19,10 @@
|
|||
#include "dcSubatomicType.h"
|
||||
#include "dcPackData.h"
|
||||
#include "dcPackerCatalog.h"
|
||||
#include "dcPython.h"
|
||||
|
||||
#ifdef WITHIN_PANDA
|
||||
#include "extension.h"
|
||||
#endif
|
||||
|
||||
class DCClass;
|
||||
class DCSwitchParameter;
|
||||
|
|
@ -104,10 +107,8 @@ public:
|
|||
|
||||
PUBLISHED:
|
||||
|
||||
#ifdef HAVE_PYTHON
|
||||
void pack_object(PyObject *object);
|
||||
PyObject *unpack_object();
|
||||
#endif
|
||||
EXTENSION(void pack_object(PyObject *object));
|
||||
EXTENSION(PyObject *unpack_object());
|
||||
|
||||
bool parse_and_pack(const std::string &formatted_object);
|
||||
bool parse_and_pack(std::istream &in);
|
||||
|
|
@ -195,14 +196,12 @@ private:
|
|||
void clear();
|
||||
void clear_stack();
|
||||
|
||||
#ifdef HAVE_PYTHON
|
||||
void pack_class_object(const DCClass *dclass, PyObject *object);
|
||||
PyObject *unpack_class_object(const DCClass *dclass);
|
||||
void set_class_element(PyObject *class_def, PyObject *&object,
|
||||
const DCField *field);
|
||||
void get_class_element(const DCClass *dclass, PyObject *object,
|
||||
const DCField *field);
|
||||
#endif
|
||||
EXTENSION(void pack_class_object(const DCClass *dclass, PyObject *object));
|
||||
EXTENSION(PyObject *unpack_class_object(const DCClass *dclass));
|
||||
EXTENSION(void set_class_element(PyObject *class_def, PyObject *&object,
|
||||
const DCField *field));
|
||||
EXTENSION(void get_class_element(const DCClass *dclass, PyObject *object,
|
||||
const DCField *field));
|
||||
|
||||
private:
|
||||
enum Mode {
|
||||
|
|
@ -223,7 +222,7 @@ private:
|
|||
const DCPackerCatalog *_catalog;
|
||||
const DCPackerCatalog::LiveCatalog *_live_catalog;
|
||||
|
||||
class StackElement {
|
||||
class EXPCL_DIRECT_DCPARSER StackElement {
|
||||
public:
|
||||
// As an optimization, we implement operator new and delete here to
|
||||
// minimize allocation overhead during push() and pop().
|
||||
|
|
@ -258,6 +257,10 @@ private:
|
|||
bool _parse_error;
|
||||
bool _pack_error;
|
||||
bool _range_error;
|
||||
|
||||
#ifdef WITHIN_PANDA
|
||||
friend class Extension<DCPacker>;
|
||||
#endif
|
||||
};
|
||||
|
||||
#include "dcPacker.I"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,508 @@
|
|||
/**
|
||||
* 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 dcPacker_ext.cxx
|
||||
* @author CFSworks
|
||||
* @date 2019-07-03
|
||||
*/
|
||||
|
||||
#include "dcPacker_ext.h"
|
||||
#include "dcClass_ext.h"
|
||||
#include "dcField_ext.h"
|
||||
|
||||
#include "dcClassParameter.h"
|
||||
|
||||
#ifdef HAVE_PYTHON
|
||||
|
||||
/**
|
||||
* Packs the Python object of whatever type into the packer. Each numeric
|
||||
* object and string object maps to the corresponding pack_value() call; a
|
||||
* tuple or sequence maps to a push() followed by all of the tuple's contents
|
||||
* followed by a pop().
|
||||
*/
|
||||
void Extension<DCPacker>::
|
||||
pack_object(PyObject *object) {
|
||||
nassertv(_this->_mode == DCPacker::Mode::M_pack ||
|
||||
_this->_mode == DCPacker::Mode::M_repack);
|
||||
DCPackType pack_type = _this->get_pack_type();
|
||||
|
||||
// had to add this for basic 64 and unsigned data to get packed right .. Not
|
||||
// sure if we can just do the rest this way..
|
||||
|
||||
switch(pack_type) {
|
||||
case PT_int64:
|
||||
if (PyLong_Check(object)) {
|
||||
_this->pack_int64(PyLong_AsLongLong(object));
|
||||
return;
|
||||
}
|
||||
#if PY_MAJOR_VERSION < 3
|
||||
else if (PyInt_Check(object)) {
|
||||
_this->pack_int64(PyInt_AsLong(object));
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
break;
|
||||
|
||||
case PT_uint64:
|
||||
if (PyLong_Check(object)) {
|
||||
_this->pack_uint64(PyLong_AsUnsignedLongLong(object));
|
||||
return;
|
||||
}
|
||||
#if PY_MAJOR_VERSION < 3
|
||||
else if (PyInt_Check(object)) {
|
||||
PyObject *obj1 = PyNumber_Long(object);
|
||||
_this->pack_int(PyLong_AsUnsignedLongLong(obj1));
|
||||
Py_DECREF(obj1);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
break;
|
||||
|
||||
case PT_int:
|
||||
if (PyLong_Check(object)) {
|
||||
_this->pack_int(PyLong_AsLong(object));
|
||||
return;
|
||||
}
|
||||
#if PY_MAJOR_VERSION < 3
|
||||
else if (PyInt_Check(object)) {
|
||||
_this->pack_int(PyInt_AsLong(object));
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
break;
|
||||
|
||||
case PT_uint:
|
||||
if (PyLong_Check(object)) {
|
||||
_this->pack_uint(PyLong_AsUnsignedLong(object));
|
||||
return;
|
||||
}
|
||||
#if PY_MAJOR_VERSION < 3
|
||||
else if (PyInt_Check(object)) {
|
||||
PyObject *obj1 = PyNumber_Long(object);
|
||||
_this->pack_uint(PyLong_AsUnsignedLong(obj1));
|
||||
Py_DECREF(obj1);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (PyLong_Check(object)) {
|
||||
_this->pack_int(PyLong_AsLong(object));
|
||||
#if PY_MAJOR_VERSION < 3
|
||||
} else if (PyInt_Check(object)) {
|
||||
_this->pack_int(PyInt_AS_LONG(object));
|
||||
#endif
|
||||
} else if (PyFloat_Check(object)) {
|
||||
_this->pack_double(PyFloat_AS_DOUBLE(object));
|
||||
} else if (PyLong_Check(object)) {
|
||||
_this->pack_int64(PyLong_AsLongLong(object));
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
} else if (PyUnicode_Check(object)) {
|
||||
const char *buffer;
|
||||
Py_ssize_t length;
|
||||
buffer = PyUnicode_AsUTF8AndSize(object, &length);
|
||||
if (buffer) {
|
||||
_this->pack_string(std::string(buffer, length));
|
||||
}
|
||||
} else if (PyBytes_Check(object)) {
|
||||
const unsigned char *buffer;
|
||||
Py_ssize_t length;
|
||||
PyBytes_AsStringAndSize(object, (char **)&buffer, &length);
|
||||
if (buffer) {
|
||||
_this->pack_blob(vector_uchar(buffer, buffer + length));
|
||||
}
|
||||
#else
|
||||
} else if (PyString_Check(object) || PyUnicode_Check(object)) {
|
||||
char *buffer;
|
||||
Py_ssize_t length;
|
||||
PyString_AsStringAndSize(object, &buffer, &length);
|
||||
if (buffer) {
|
||||
_this->pack_string(std::string(buffer, length));
|
||||
}
|
||||
#endif
|
||||
} else {
|
||||
// For some reason, PySequence_Check() is incorrectly reporting that a
|
||||
// class instance is a sequence, even if it doesn't provide __len__, so we
|
||||
// double-check by testing for __len__ explicitly.
|
||||
bool is_sequence =
|
||||
(PySequence_Check(object) != 0) &&
|
||||
(PyObject_HasAttrString(object, "__len__") != 0);
|
||||
bool is_instance = false;
|
||||
|
||||
const DCClass *dclass = nullptr;
|
||||
const DCPackerInterface *current_field = _this->get_current_field();
|
||||
if (current_field != nullptr) {
|
||||
const DCClassParameter *class_param = _this->get_current_field()->as_class_parameter();
|
||||
if (class_param != nullptr) {
|
||||
dclass = class_param->get_class();
|
||||
|
||||
if (invoke_extension(dclass).has_class_def()) {
|
||||
PyObject *class_def = invoke_extension(dclass).get_class_def();
|
||||
is_instance = (PyObject_IsInstance(object, invoke_extension(dclass).get_class_def()) != 0);
|
||||
Py_DECREF(class_def);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If dclass is not NULL, the packer is expecting a class object. There
|
||||
// are then two cases: (1) the user has supplied a matching class object,
|
||||
// or (2) the user has supplied a sequence object. Unfortunately, it may
|
||||
// be difficult to differentiate these two cases, since a class object may
|
||||
// also be a sequence object.
|
||||
|
||||
// The rule to differentiate them is:
|
||||
|
||||
// (1) If the supplied class object is an instance of the expected class
|
||||
// object, it is considered to be a class object.
|
||||
|
||||
// (2) Otherwise, if the supplied class object has a __len__() method
|
||||
// (i.e. PySequence_Check() returns true), then it is considered to be a
|
||||
// sequence.
|
||||
|
||||
// (3) Otherwise, it is considered to be a class object.
|
||||
|
||||
if (dclass != nullptr && (is_instance || !is_sequence)) {
|
||||
// The supplied object is either an instance of the expected class
|
||||
// object, or it is not a sequence--this is case (1) or (3).
|
||||
pack_class_object(dclass, object);
|
||||
} else if (is_sequence) {
|
||||
// The supplied object is not an instance of the expected class object,
|
||||
// but it is a sequence. This is case (2).
|
||||
_this->push();
|
||||
int size = PySequence_Size(object);
|
||||
for (int i = 0; i < size; ++i) {
|
||||
PyObject *element = PySequence_GetItem(object, i);
|
||||
if (element != nullptr) {
|
||||
pack_object(element);
|
||||
Py_DECREF(element);
|
||||
} else {
|
||||
std::cerr << "Unable to extract item " << i << " from sequence.\n";
|
||||
}
|
||||
}
|
||||
_this->pop();
|
||||
} else {
|
||||
// The supplied object is not a sequence, and we weren't expecting a
|
||||
// class parameter. This is none of the above, an error.
|
||||
std::ostringstream strm;
|
||||
strm << "Don't know how to pack object: "
|
||||
<< Extension<DCField>::get_pystr(object);
|
||||
nassert_raise(strm.str());
|
||||
_this->_pack_error = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unpacks a Python object of the appropriate type from the stream for the
|
||||
* current field. This may be an integer or a string for a simple field
|
||||
* object; if the current field represents a list of fields it will be a
|
||||
* tuple.
|
||||
*/
|
||||
PyObject *Extension<DCPacker>::
|
||||
unpack_object() {
|
||||
PyObject *object = nullptr;
|
||||
|
||||
DCPackType pack_type = _this->get_pack_type();
|
||||
|
||||
switch (pack_type) {
|
||||
case PT_invalid:
|
||||
object = Py_None;
|
||||
Py_INCREF(object);
|
||||
_this->unpack_skip();
|
||||
break;
|
||||
|
||||
case PT_double:
|
||||
{
|
||||
double value = _this->unpack_double();
|
||||
object = PyFloat_FromDouble(value);
|
||||
}
|
||||
break;
|
||||
|
||||
case PT_int:
|
||||
{
|
||||
int value = _this->unpack_int();
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
object = PyLong_FromLong(value);
|
||||
#else
|
||||
object = PyInt_FromLong(value);
|
||||
#endif
|
||||
}
|
||||
break;
|
||||
|
||||
case PT_uint:
|
||||
{
|
||||
unsigned int value = _this->unpack_uint();
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
object = PyLong_FromLong(value);
|
||||
#else
|
||||
if (value & 0x80000000) {
|
||||
object = PyLong_FromUnsignedLong(value);
|
||||
} else {
|
||||
object = PyInt_FromLong(value);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
break;
|
||||
|
||||
case PT_int64:
|
||||
{
|
||||
int64_t value = _this->unpack_int64();
|
||||
object = PyLong_FromLongLong(value);
|
||||
}
|
||||
break;
|
||||
|
||||
case PT_uint64:
|
||||
{
|
||||
uint64_t value = _this->unpack_uint64();
|
||||
object = PyLong_FromUnsignedLongLong(value);
|
||||
}
|
||||
break;
|
||||
|
||||
case PT_blob:
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
{
|
||||
std::string str;
|
||||
_this->unpack_string(str);
|
||||
object = PyBytes_FromStringAndSize(str.data(), str.size());
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
// On Python 2, fall through to below.
|
||||
|
||||
case PT_string:
|
||||
{
|
||||
std::string str;
|
||||
_this->unpack_string(str);
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
object = PyUnicode_FromStringAndSize(str.data(), str.size());
|
||||
#else
|
||||
object = PyString_FromStringAndSize(str.data(), str.size());
|
||||
#endif
|
||||
}
|
||||
break;
|
||||
|
||||
case PT_class:
|
||||
{
|
||||
const DCClassParameter *class_param = _this->get_current_field()->as_class_parameter();
|
||||
if (class_param != nullptr) {
|
||||
const DCClass *dclass = class_param->get_class();
|
||||
if (invoke_extension(dclass).has_class_def()) {
|
||||
// If we know what kind of class object this is and it has a valid
|
||||
// constructor, create the class object instead of just a tuple.
|
||||
object = unpack_class_object(dclass);
|
||||
if (object == nullptr) {
|
||||
std::cerr << "Unable to construct object of class "
|
||||
<< dclass->get_name() << "\n";
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Fall through (if no constructor)
|
||||
|
||||
// If we don't know what kind of class object it is, or it doesn't have a
|
||||
// constructor, fall through and make a tuple.
|
||||
default:
|
||||
{
|
||||
// First, build up a list from the nested objects.
|
||||
object = PyList_New(0);
|
||||
|
||||
_this->push();
|
||||
while (_this->more_nested_fields()) {
|
||||
PyObject *element = unpack_object();
|
||||
PyList_Append(object, element);
|
||||
Py_DECREF(element);
|
||||
}
|
||||
_this->pop();
|
||||
|
||||
if (pack_type != PT_array) {
|
||||
// For these other kinds of objects, we'll convert the list into a
|
||||
// tuple.
|
||||
PyObject *tuple = PyList_AsTuple(object);
|
||||
Py_DECREF(object);
|
||||
object = tuple;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
nassertr(object != nullptr, nullptr);
|
||||
return object;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given that the current element is a ClassParameter for a Python class
|
||||
* object, try to extract the appropriate values from the class object and
|
||||
* pack in.
|
||||
*/
|
||||
void Extension<DCPacker>::
|
||||
pack_class_object(const DCClass *dclass, PyObject *object) {
|
||||
_this->push();
|
||||
while (_this->more_nested_fields() && !_this->_pack_error) {
|
||||
const DCField *field = _this->get_current_field()->as_field();
|
||||
nassertv(field != nullptr);
|
||||
get_class_element(dclass, object, field);
|
||||
}
|
||||
_this->pop();
|
||||
}
|
||||
|
||||
/**
|
||||
* Given that the current element is a ClassParameter for a Python class for
|
||||
* which we have a valid constructor, unpack it and fill in its values.
|
||||
*/
|
||||
PyObject *Extension<DCPacker>::
|
||||
unpack_class_object(const DCClass *dclass) {
|
||||
PyObject *class_def = invoke_extension(dclass).get_class_def();
|
||||
nassertr(class_def != nullptr, nullptr);
|
||||
|
||||
PyObject *object = nullptr;
|
||||
|
||||
if (!dclass->has_constructor()) {
|
||||
// If the class uses a default constructor, go ahead and create the Python
|
||||
// object for it now.
|
||||
object = PyObject_CallObject(class_def, nullptr);
|
||||
if (object == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
_this->push();
|
||||
if (object == nullptr && _this->more_nested_fields()) {
|
||||
// The first nested field will be the constructor.
|
||||
const DCField *field = _this->get_current_field()->as_field();
|
||||
nassertr(field != nullptr, object);
|
||||
nassertr(field == dclass->get_constructor(), object);
|
||||
|
||||
set_class_element(class_def, object, field);
|
||||
|
||||
// By now, the object should have been constructed.
|
||||
if (object == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
while (_this->more_nested_fields()) {
|
||||
const DCField *field = _this->get_current_field()->as_field();
|
||||
nassertr(field != nullptr, object);
|
||||
|
||||
set_class_element(class_def, object, field);
|
||||
}
|
||||
_this->pop();
|
||||
|
||||
return object;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unpacks the current element and stuffs it on the Python class object in
|
||||
* whatever way is appropriate.
|
||||
*/
|
||||
void Extension<DCPacker>::
|
||||
set_class_element(PyObject *class_def, PyObject *&object,
|
||||
const DCField *field) {
|
||||
std::string field_name = field->get_name();
|
||||
DCPackType pack_type = _this->get_pack_type();
|
||||
|
||||
if (field_name.empty()) {
|
||||
switch (pack_type) {
|
||||
case PT_class:
|
||||
case PT_switch:
|
||||
// If the field has no name, but it is one of these container objects,
|
||||
// we want to unpack its nested objects directly into the class.
|
||||
_this->push();
|
||||
while (_this->more_nested_fields()) {
|
||||
const DCField *field = _this->get_current_field()->as_field();
|
||||
nassertv(field != nullptr);
|
||||
nassertv(object != nullptr);
|
||||
set_class_element(class_def, object, field);
|
||||
}
|
||||
_this->pop();
|
||||
break;
|
||||
|
||||
default:
|
||||
// Otherwise, we just skip over the field.
|
||||
_this->unpack_skip();
|
||||
}
|
||||
|
||||
} else {
|
||||
// If the field does have a name, we will want to store it on the class,
|
||||
// either by calling a method (for a PT_field pack_type) or by setting a
|
||||
// value (for any other kind of pack_type).
|
||||
|
||||
PyObject *element = unpack_object();
|
||||
|
||||
if (pack_type == PT_field) {
|
||||
if (object == nullptr) {
|
||||
// If the object hasn't been constructed yet, assume this is the
|
||||
// constructor.
|
||||
object = PyObject_CallObject(class_def, element);
|
||||
|
||||
} else {
|
||||
if (PyObject_HasAttrString(object, (char *)field_name.c_str())) {
|
||||
PyObject *func = PyObject_GetAttrString(object, (char *)field_name.c_str());
|
||||
if (func != nullptr) {
|
||||
PyObject *result = PyObject_CallObject(func, element);
|
||||
Py_XDECREF(result);
|
||||
Py_DECREF(func);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
nassertv(object != nullptr);
|
||||
PyObject_SetAttrString(object, (char *)field_name.c_str(), element);
|
||||
}
|
||||
|
||||
Py_DECREF(element);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current element from the Python object and packs it.
|
||||
*/
|
||||
void Extension<DCPacker>::
|
||||
get_class_element(const DCClass *dclass, PyObject *object,
|
||||
const DCField *field) {
|
||||
std::string field_name = field->get_name();
|
||||
DCPackType pack_type = _this->get_pack_type();
|
||||
|
||||
if (field_name.empty()) {
|
||||
switch (pack_type) {
|
||||
case PT_class:
|
||||
case PT_switch:
|
||||
// If the field has no name, but it is one of these container objects,
|
||||
// we want to get its nested objects directly from the class.
|
||||
_this->push();
|
||||
while (_this->more_nested_fields() && !_this->_pack_error) {
|
||||
const DCField *field = _this->get_current_field()->as_field();
|
||||
nassertv(field != nullptr);
|
||||
get_class_element(dclass, object, field);
|
||||
}
|
||||
_this->pop();
|
||||
break;
|
||||
|
||||
default:
|
||||
// Otherwise, we just pack the default value.
|
||||
_this->pack_default_value();
|
||||
}
|
||||
|
||||
} else {
|
||||
// If the field does have a name, we will want to get it from the class
|
||||
// and pack it. It just so happens that there's already a method that
|
||||
// does this on DCClass.
|
||||
|
||||
if (!invoke_extension(dclass).pack_required_field(*_this, object, field)) {
|
||||
_this->_pack_error = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif // HAVE_PYTHON
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
/**
|
||||
* 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 dcPacker_ext.h
|
||||
* @author CFSworks
|
||||
* @date 2019-07-03
|
||||
*/
|
||||
|
||||
#ifndef DCPACKER_EXT_H
|
||||
#define DCPACKER_EXT_H
|
||||
|
||||
#include "dtoolbase.h"
|
||||
|
||||
#ifdef HAVE_PYTHON
|
||||
|
||||
#include "extension.h"
|
||||
#include "dcPacker.h"
|
||||
#include "py_panda.h"
|
||||
|
||||
/**
|
||||
* This class defines the extension methods for DCPacker, which are called
|
||||
* instead of any C++ methods with the same prototype.
|
||||
*/
|
||||
template<>
|
||||
class Extension<DCPacker> : public ExtensionBase<DCPacker> {
|
||||
public:
|
||||
void pack_object(PyObject *object);
|
||||
PyObject *unpack_object();
|
||||
|
||||
void pack_class_object(const DCClass *dclass, PyObject *object);
|
||||
PyObject *unpack_class_object(const DCClass *dclass);
|
||||
void set_class_element(PyObject *class_def, PyObject *&object,
|
||||
const DCField *field);
|
||||
void get_class_element(const DCClass *dclass, PyObject *object,
|
||||
const DCField *field);
|
||||
};
|
||||
|
||||
#endif // HAVE_PYTHON
|
||||
|
||||
#endif // DCPACKER_EXT_H
|
||||
|
|
@ -1,42 +0,0 @@
|
|||
/**
|
||||
* 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 dcPython.h
|
||||
* @author drose
|
||||
* @date 2004-06-22
|
||||
*/
|
||||
|
||||
#ifndef DCPYTHON_H
|
||||
#define DCPYTHON_H
|
||||
|
||||
// The only purpose of this file is to serve as a common place to put the
|
||||
// nonsense associated with #including <Python.h>.
|
||||
|
||||
#ifdef HAVE_PYTHON
|
||||
|
||||
#define PY_SSIZE_T_CLEAN 1
|
||||
|
||||
#undef _POSIX_C_SOURCE
|
||||
#undef _XOPEN_SOURCE
|
||||
#include <Python.h>
|
||||
|
||||
// Python 2.5 adds Py_ssize_t; earlier versions don't have it.
|
||||
#if PY_VERSION_HEX < 0x02050000 && !defined(PY_SSIZE_T_MIN)
|
||||
typedef int Py_ssize_t;
|
||||
#define PY_SSIZE_T_MAX INT_MAX
|
||||
#define PY_SSIZE_T_MIN INT_MIN
|
||||
#endif
|
||||
|
||||
// Several interfaces in this module that use Python also require these header
|
||||
// files, so we might as well pick them up too.
|
||||
#include "datagram.h"
|
||||
#include "datagramIterator.h"
|
||||
|
||||
#endif // HAVE_PYTHON
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
#include "dcClass_ext.cxx"
|
||||
#include "dcField_ext.cxx"
|
||||
#include "dcPacker_ext.cxx"
|
||||
|
|
@ -1,99 +0,0 @@
|
|||
// Based on Apple sample code at
|
||||
// http://developer.apple.com/internet/webcontent/examples/detectplugins_source.html
|
||||
|
||||
|
||||
// initialize global variables
|
||||
var detectableWithVB = false;
|
||||
var pluginFound = false;
|
||||
|
||||
function goURL(daURL) {
|
||||
// Assume we have Javascript 1.1 functionality.
|
||||
window.location.replace(daURL);
|
||||
return;
|
||||
}
|
||||
|
||||
function redirectCheck(pluginFound, redirectURL, redirectIfFound) {
|
||||
// check for redirection
|
||||
if( redirectURL && ((pluginFound && redirectIfFound) ||
|
||||
(!pluginFound && !redirectIfFound)) ) {
|
||||
// go away
|
||||
goURL(redirectURL);
|
||||
return pluginFound;
|
||||
} else {
|
||||
// stay here and return result of plugin detection
|
||||
return pluginFound;
|
||||
}
|
||||
}
|
||||
|
||||
function canDetectPlugins() {
|
||||
if( detectableWithVB || (navigator.plugins && navigator.plugins.length > 0) ) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function detectPanda3D(redirectURL, redirectIfFound) {
|
||||
pluginFound = detectPlugin('Panda3D');
|
||||
// if not found, try to detect with VisualBasic
|
||||
if(!pluginFound && detectableWithVB) {
|
||||
pluginFound = detectActiveXControl('P3DACTIVEX.P3DActiveXCtrl.1');
|
||||
}
|
||||
// check for redirection
|
||||
return redirectCheck(pluginFound, redirectURL, redirectIfFound);
|
||||
}
|
||||
|
||||
function detectPlugin() {
|
||||
// allow for multiple checks in a single pass
|
||||
var daPlugins = detectPlugin.arguments;
|
||||
// consider pluginFound to be false until proven true
|
||||
var pluginFound = false;
|
||||
// if plugins array is there and not fake
|
||||
if (navigator.plugins && navigator.plugins.length > 0) {
|
||||
var pluginsArrayLength = navigator.plugins.length;
|
||||
// for each plugin...
|
||||
for (pluginsArrayCounter=0; pluginsArrayCounter < pluginsArrayLength; pluginsArrayCounter++ ) {
|
||||
// loop through all desired names and check each against the current plugin name
|
||||
var numFound = 0;
|
||||
for(namesCounter=0; namesCounter < daPlugins.length; namesCounter++) {
|
||||
// if desired plugin name is found in either plugin name or description
|
||||
if( (navigator.plugins[pluginsArrayCounter].name.indexOf(daPlugins[namesCounter]) >= 0) ||
|
||||
(navigator.plugins[pluginsArrayCounter].description.indexOf(daPlugins[namesCounter]) >= 0) ) {
|
||||
// this name was found
|
||||
numFound++;
|
||||
}
|
||||
}
|
||||
// now that we have checked all the required names against this one plugin,
|
||||
// if the number we found matches the total number provided then we were successful
|
||||
if(numFound == daPlugins.length) {
|
||||
pluginFound = true;
|
||||
// if we've found the plugin, we can stop looking through at the rest of the plugins
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return pluginFound;
|
||||
} // detectPlugin
|
||||
|
||||
|
||||
// Here we write out the VBScript block for MSIE Windows
|
||||
if ((navigator.userAgent.indexOf('MSIE') != -1) && (navigator.userAgent.indexOf('Win') != -1)) {
|
||||
document.writeln('<script language="VBscript">');
|
||||
|
||||
document.writeln('\'do a one-time test for a version of VBScript that can handle this code');
|
||||
document.writeln('detectableWithVB = False');
|
||||
document.writeln('If ScriptEngineMajorVersion >= 2 then');
|
||||
document.writeln(' detectableWithVB = True');
|
||||
document.writeln('End If');
|
||||
|
||||
document.writeln('\'this next function will detect most plugins');
|
||||
document.writeln('Function detectActiveXControl(activeXControlName)');
|
||||
document.writeln(' on error resume next');
|
||||
document.writeln(' detectActiveXControl = False');
|
||||
document.writeln(' If detectableWithVB Then');
|
||||
document.writeln(' detectActiveXControl = IsObject(CreateObject(activeXControlName))');
|
||||
document.writeln(' End If');
|
||||
document.writeln('End Function');
|
||||
|
||||
document.writeln('</script>');
|
||||
}
|
||||
|
|
@ -432,7 +432,7 @@ EXTRACT_STATIC = NO
|
|||
# for Java sources.
|
||||
# The default value is: YES.
|
||||
|
||||
EXTRACT_LOCAL_CLASSES = YES
|
||||
EXTRACT_LOCAL_CLASSES = NO
|
||||
|
||||
# This flag is only useful for Objective-C code. When set to YES local methods,
|
||||
# which are defined in the implementation section but not in the interface are
|
||||
|
|
@ -802,6 +802,11 @@ EXCLUDE = dtool/src/parser-inc \
|
|||
panda/src/iphone \
|
||||
panda/src/tinydisplay \
|
||||
panda/src/movies/dr_flac.h \
|
||||
panda/src/windisplay/winDetectDx.h \
|
||||
panda/src/wgldisplay/wglext.h \
|
||||
panda/src/glxdisplay/panda_glxext.h \
|
||||
pandatool/src/gtk-stats \
|
||||
dtool/src/dtoolbase/fakestringstream.h \
|
||||
dtool/src/dtoolbase/pdtoa.cxx \
|
||||
dtool/src/dtoolutil/panda_getopt_long.h \
|
||||
dtool/src/dtoolutil/panda_getopt_impl.h \
|
||||
|
|
@ -840,7 +845,10 @@ EXCLUDE_PATTERNS = */Opt*-*/* \
|
|||
# Note that the wildcards are matched against the file with absolute path, so to
|
||||
# exclude all test directories use the pattern */test/*
|
||||
|
||||
EXCLUDE_SYMBOLS =
|
||||
EXCLUDE_SYMBOLS = InterrogateFunctionWrapper::Parameter \
|
||||
CollisionFloorMesh::TriangleIndices \
|
||||
tagTOUCHINPUT \
|
||||
WINDOW_METRICS
|
||||
|
||||
# The EXAMPLE_PATH tag can be used to specify one or more files or directories
|
||||
# that contain example code fragments that are included (see the \include
|
||||
|
|
@ -1018,7 +1026,7 @@ ALPHABETICAL_INDEX = YES
|
|||
# Minimum value: 1, maximum value: 20, default value: 5.
|
||||
# This tag requires that the tag ALPHABETICAL_INDEX is set to YES.
|
||||
|
||||
COLS_IN_ALPHA_INDEX = 5
|
||||
COLS_IN_ALPHA_INDEX = 3
|
||||
|
||||
# In case all classes in a project start with a common prefix, all classes will
|
||||
# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag
|
||||
|
|
@ -1972,6 +1980,7 @@ INCLUDE_FILE_PATTERNS =
|
|||
|
||||
PREDEFINED = TVOLATILE= \
|
||||
INLINE=inline \
|
||||
ALWAYS_INLINE=inline \
|
||||
PUBLISHED=public \
|
||||
protected=private \
|
||||
INLINE_LINMATH=inline \
|
||||
|
|
@ -1986,7 +1995,8 @@ PREDEFINED = TVOLATILE= \
|
|||
MAKE_SEQ(x)= \
|
||||
MAKE_SEQ_PROPERTY(x)= \
|
||||
MAKE_MAP_PROPERTY(x)= \
|
||||
MAKE_MAP_KEYS_SEQ(x)=
|
||||
MAKE_MAP_KEYS_SEQ(x)= \
|
||||
RETURNS_ALIGNED(x)=
|
||||
|
||||
# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this
|
||||
# tag can be used to specify a list of macro names that should be expanded. The
|
||||
|
|
|
|||
|
|
@ -805,7 +805,7 @@ ALPHABETICAL_INDEX = YES
|
|||
# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns
|
||||
# in which this list will be split (can be a number in the range [1..20])
|
||||
|
||||
COLS_IN_ALPHA_INDEX = 5
|
||||
COLS_IN_ALPHA_INDEX = 2
|
||||
|
||||
# In case all classes in a project start with a common prefix, all
|
||||
# classes will be put under the same header in the alphabetical index.
|
||||
|
|
|
|||
|
|
@ -1,132 +0,0 @@
|
|||
// This script injects the appropriate syntax into the document to
|
||||
// embed Panda3D, either for the ActiveX or Mozilla-based plugin.
|
||||
|
||||
// It is also possible to write browser-independent code by nesting
|
||||
// <object> tags, but this causes problems when you need to reference
|
||||
// the particular object that is actually running (which object is
|
||||
// it?) for scripting purposes.
|
||||
|
||||
// This script writes only a single <object> tag, and it can be
|
||||
// assigned the id you specify, avoiding this ambiguity.
|
||||
|
||||
var isIE = (navigator.appVersion.indexOf("MSIE") != -1) ? true : false;
|
||||
var isWin = (navigator.appVersion.toLowerCase().indexOf("win") != -1) ? true : false;
|
||||
var isOpera = (navigator.userAgent.indexOf("Opera") != -1) ? true : false;
|
||||
|
||||
|
||||
function P3D_Generateobj(objAttrs, params, embedAttrs, imageAttrs)
|
||||
{
|
||||
var str = '';
|
||||
|
||||
if (isIE && isWin && !isOpera)
|
||||
{
|
||||
str += '<object ';
|
||||
for (var i in objAttrs)
|
||||
{
|
||||
str += i + '="' + objAttrs[i] + '" ';
|
||||
}
|
||||
str += '>';
|
||||
for (var i in params)
|
||||
{
|
||||
str += '<param name="' + i + '" value="' + params[i] + '" /> ';
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
str += '<object ';
|
||||
for (var i in embedAttrs)
|
||||
{
|
||||
str += i + '="' + embedAttrs[i] + '" ';
|
||||
}
|
||||
str += '> ';
|
||||
}
|
||||
if (imageAttrs["src"]) {
|
||||
if (imageAttrs["href"]) {
|
||||
str += '<a href="' + imageAttrs["href"] + '">';
|
||||
}
|
||||
str += '<img ';
|
||||
for (var i in imageAttrs) {
|
||||
if (i != "href") {
|
||||
str += i + '="' + imageAttrs[i] + '" ';
|
||||
}
|
||||
}
|
||||
str += '>';
|
||||
if (imageAttrs["href"]) {
|
||||
str += '</a>';
|
||||
}
|
||||
}
|
||||
str += '</object>';
|
||||
|
||||
document.write(str);
|
||||
}
|
||||
|
||||
function P3D_RunContent() {
|
||||
var ret =
|
||||
P3D_GetArgs
|
||||
(arguments, "clsid:924b4927-d3ba-41ea-9f7e-8a89194ab3ac",
|
||||
"application/x-panda3d");
|
||||
P3D_Generateobj(ret.objAttrs, ret.params, ret.embedAttrs, ret.imageAttrs);
|
||||
}
|
||||
|
||||
function P3D_GetArgs(args, classid, mimeType){
|
||||
var ret = new Object();
|
||||
ret.embedAttrs = new Object();
|
||||
ret.params = new Object();
|
||||
ret.objAttrs = new Object();
|
||||
ret.imageAttrs = new Object();
|
||||
|
||||
for (var i = 0; i < args.length; i = i + 2){
|
||||
var currArg = args[i].toLowerCase();
|
||||
|
||||
switch (currArg){
|
||||
case "src":
|
||||
case "data":
|
||||
ret.embedAttrs['data'] = args[i+1];
|
||||
ret.params['data'] = args[i+1];
|
||||
break;
|
||||
|
||||
case "codebase":
|
||||
ret.objAttrs['codebase'] = args[i+1];
|
||||
break;
|
||||
|
||||
case "noplugin_img":
|
||||
ret.imageAttrs["src"] = args[i+1];
|
||||
ret.imageAttrs["border"] = '0';
|
||||
break;
|
||||
|
||||
case "noplugin_href":
|
||||
ret.imageAttrs["href"] = args[i+1];
|
||||
break;
|
||||
|
||||
case "splash_img":
|
||||
ret.embedAttrs[args[i]] = ret.params[args[i]] = args[i+1];
|
||||
if (!ret.imageAttrs["src"]) {
|
||||
ret.imageAttrs["src"] = args[i+1];
|
||||
}
|
||||
break;
|
||||
|
||||
case "width":
|
||||
case "height":
|
||||
ret.imageAttrs[args[i]] = ret.embedAttrs[args[i]] = ret.objAttrs[args[i]] = args[i+1];
|
||||
break;
|
||||
|
||||
case "id":
|
||||
case "align":
|
||||
case "vspace":
|
||||
case "hspace":
|
||||
case "class":
|
||||
case "title":
|
||||
case "accesskey":
|
||||
case "name":
|
||||
case "tabindex":
|
||||
ret.embedAttrs[args[i]] = ret.objAttrs[args[i]] = args[i+1];
|
||||
break;
|
||||
|
||||
default:
|
||||
ret.embedAttrs[args[i]] = ret.params[args[i]] = args[i+1];
|
||||
}
|
||||
}
|
||||
ret.objAttrs["classid"] = classid;
|
||||
if (mimeType) ret.embedAttrs["type"] = mimeType;
|
||||
return ret;
|
||||
}
|
||||
|
|
@ -273,6 +273,7 @@ def processModule(handle, package):
|
|||
if "panda3d." + package == module_name:
|
||||
processType(handle, type)
|
||||
else:
|
||||
typename = interrogate_type_name(type)
|
||||
print("Type %s has no module name" % typename)
|
||||
|
||||
for i_func in range(interrogate_number_of_global_functions()):
|
||||
|
|
@ -283,7 +284,8 @@ def processModule(handle, package):
|
|||
if "panda3d." + package == module_name:
|
||||
processFunction(handle, func)
|
||||
else:
|
||||
print("Type %s has no module name" % typename)
|
||||
funcname = interrogate_function_name(func)
|
||||
print("Function %s has no module name" % funcname)
|
||||
|
||||
print("}", file=handle)
|
||||
|
||||
|
|
|
|||
|
|
@ -56,9 +56,9 @@ import os, sys, parser, symbol, token, re
|
|||
#
|
||||
########################################################################
|
||||
|
||||
SECHEADER = re.compile("^[A-Z][a-z]+\s*:")
|
||||
JUNKHEADER = re.compile("^((Function)|(Access))\s*:")
|
||||
IMPORTSTAR = re.compile("^from\s+([a-zA-Z0-9_.]+)\s+import\s+[*]\s*$")
|
||||
SECHEADER = re.compile("^[A-Z][a-z]+\\s*:")
|
||||
JUNKHEADER = re.compile("^((Function)|(Access))\\s*:")
|
||||
IMPORTSTAR = re.compile("^from\\s+([a-zA-Z0-9_.]+)\\s+import\\s+[*]\\s*$")
|
||||
IDENTIFIER = re.compile("[a-zA-Z0-9_]+")
|
||||
FILEHEADER = re.compile(
|
||||
r"""^// Filename: [a-zA-Z.]+
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
|
||||
from .DirectGlobals import *
|
||||
from panda3d.core import VBase4
|
||||
from direct.task.Task import Task
|
||||
|
||||
# Routines to adjust values
|
||||
def ROUND_TO(value, divisor):
|
||||
|
|
|
|||
|
|
@ -210,15 +210,15 @@ class CompilationEnvironment:
|
|||
if ('MAKEPANDA' in os.environ):
|
||||
self.compileObjExe = 'cl /wd4996 /Fo%(basename)s.obj /nologo /c %(MD)s /Zi /O2 /Ob2 /EHsc /Zm300 /W3 /I"%(pythonIPath)s" %(filename)s'
|
||||
self.compileObjDll = self.compileObjExe
|
||||
self.linkExe = 'link /nologo /MAP:NUL /FIXED:NO /OPT:REF /STACK:4194304 /INCREMENTAL:NO /LIBPATH:"%(python)s\libs" /out:%(basename)s.exe %(basename)s.obj'
|
||||
self.linkDll = 'link /nologo /DLL /MAP:NUL /FIXED:NO /OPT:REF /INCREMENTAL:NO /LIBPATH:"%(python)s\libs" /out:%(basename)s%(dllext)s.pyd %(basename)s.obj'
|
||||
self.linkExe = 'link /nologo /MAP:NUL /FIXED:NO /OPT:REF /STACK:4194304 /INCREMENTAL:NO /LIBPATH:"%(python)s\\libs" /out:%(basename)s.exe %(basename)s.obj'
|
||||
self.linkDll = 'link /nologo /DLL /MAP:NUL /FIXED:NO /OPT:REF /INCREMENTAL:NO /LIBPATH:"%(python)s\\libs" /out:%(basename)s%(dllext)s.pyd %(basename)s.obj'
|
||||
else:
|
||||
os.environ['PATH'] += ';' + self.MSVC + '\\bin' + self.suffix64 + ';' + self.MSVC + '\\Common7\\IDE;' + self.PSDK + '\\bin'
|
||||
|
||||
self.compileObjExe = 'cl /wd4996 /Fo%(basename)s.obj /nologo /c %(MD)s /Zi /O2 /Ob2 /EHsc /Zm300 /W3 /I"%(pythonIPath)s" /I"%(PSDK)s\include" /I"%(MSVC)s\include" %(filename)s'
|
||||
self.compileObjExe = 'cl /wd4996 /Fo%(basename)s.obj /nologo /c %(MD)s /Zi /O2 /Ob2 /EHsc /Zm300 /W3 /I"%(pythonIPath)s" /I"%(PSDK)s\\include" /I"%(MSVC)s\\include" %(filename)s'
|
||||
self.compileObjDll = self.compileObjExe
|
||||
self.linkExe = 'link /nologo /MAP:NUL /FIXED:NO /OPT:REF /STACK:4194304 /INCREMENTAL:NO /LIBPATH:"%(PSDK)s\lib" /LIBPATH:"%(MSVC)s\\lib%(suffix64)s" /LIBPATH:"%(python)s\libs" /out:%(basename)s.exe %(basename)s.obj'
|
||||
self.linkDll = 'link /nologo /DLL /MAP:NUL /FIXED:NO /OPT:REF /INCREMENTAL:NO /LIBPATH:"%(PSDK)s\lib" /LIBPATH:"%(MSVC)s\\lib%(suffix64)s" /LIBPATH:"%(python)s\libs" /out:%(basename)s%(dllext)s.pyd %(basename)s.obj'
|
||||
self.linkExe = 'link /nologo /MAP:NUL /FIXED:NO /OPT:REF /STACK:4194304 /INCREMENTAL:NO /LIBPATH:"%(PSDK)s\\lib" /LIBPATH:"%(MSVC)s\\lib%(suffix64)s" /LIBPATH:"%(python)s\\libs" /out:%(basename)s.exe %(basename)s.obj'
|
||||
self.linkDll = 'link /nologo /DLL /MAP:NUL /FIXED:NO /OPT:REF /INCREMENTAL:NO /LIBPATH:"%(PSDK)s\\lib" /LIBPATH:"%(MSVC)s\\lib%(suffix64)s" /LIBPATH:"%(python)s\\libs" /out:%(basename)s%(dllext)s.pyd %(basename)s.obj'
|
||||
|
||||
elif self.platform.startswith('osx_'):
|
||||
# OSX
|
||||
|
|
@ -800,29 +800,36 @@ class Freezer:
|
|||
self.moduleSuffixes[i] = (suffix[0], 'rb', imp.PY_SOURCE)
|
||||
else:
|
||||
self.moduleSuffixes = [('.py', 'rb', 1), ('.pyc', 'rb', 2)]
|
||||
|
||||
abi_version = '{0}{1}'.format(*sys.version_info)
|
||||
abi_flags = ''
|
||||
if sys.version_info < (3, 8):
|
||||
abi_flags += 'm'
|
||||
|
||||
if 'linux' in self.platform:
|
||||
self.moduleSuffixes += [
|
||||
('.cpython-{0}{1}m-x86_64-linux-gnu.so'.format(*sys.version_info), 'rb', 3),
|
||||
('.cpython-{0}{1}m-i686-linux-gnu.so'.format(*sys.version_info), 'rb', 3),
|
||||
('.cpython-{0}{1}-x86_64-linux-gnu.so'.format(abi_version, abi_flags), 'rb', 3),
|
||||
('.cpython-{0}{1}-i686-linux-gnu.so'.format(abi_version, abi_flags), 'rb', 3),
|
||||
('.abi{0}.so'.format(sys.version_info[0]), 'rb', 3),
|
||||
('.so', 'rb', 3),
|
||||
]
|
||||
elif 'win' in self.platform:
|
||||
# ABI flags are not appended on Windows.
|
||||
self.moduleSuffixes += [
|
||||
('.cp{0}{1}-win_amd64.pyd'.format(*sys.version_info), 'rb', 3),
|
||||
('.cp{0}{1}-win32.pyd'.format(*sys.version_info), 'rb', 3),
|
||||
('.cp{0}-win_amd64.pyd'.format(abi_version), 'rb', 3),
|
||||
('.cp{0}-win32.pyd'.format(abi_version), 'rb', 3),
|
||||
('.pyd', 'rb', 3),
|
||||
]
|
||||
elif 'mac' in self.platform:
|
||||
self.moduleSuffixes += [
|
||||
('.cpython-{0}{1}m-darwin.so'.format(*sys.version_info), 'rb', 3),
|
||||
('.cpython-{0}{1}-darwin.so'.format(abi_version, abi_flags), 'rb', 3),
|
||||
('.abi{0}.so'.format(sys.version_info[0]), 'rb', 3),
|
||||
('.so', 'rb', 3),
|
||||
]
|
||||
else: # FreeBSD et al.
|
||||
self.moduleSuffixes += [
|
||||
('.cpython-{0}{1}m.so'.format(*sys.version_info), 'rb', 3),
|
||||
('.abi{0}.so'.format(*sys.version_info), 'rb', 3),
|
||||
('.cpython-{0}{1}.so'.format(abi_version, abi_flags), 'rb', 3),
|
||||
('.abi{0}.so'.format(sys.version_info[0]), 'rb', 3),
|
||||
('.so', 'rb', 3),
|
||||
]
|
||||
|
||||
|
|
@ -845,7 +852,7 @@ class Freezer:
|
|||
allowChildren is true, the children of the indicated module
|
||||
may still be included."""
|
||||
|
||||
assert self.mf == None
|
||||
assert self.mf is None
|
||||
|
||||
self.modules[moduleName] = self.ModuleDef(
|
||||
moduleName, exclude = True,
|
||||
|
|
@ -879,7 +886,7 @@ class Freezer:
|
|||
print("couldn't import %s" % (moduleName))
|
||||
module = None
|
||||
|
||||
if module != None:
|
||||
if module is not None:
|
||||
for symbol in moduleName.split('.')[1:]:
|
||||
module = getattr(module, symbol)
|
||||
if hasattr(module, '__path__'):
|
||||
|
|
@ -894,7 +901,7 @@ class Freezer:
|
|||
if '.' in baseName:
|
||||
parentName, baseName = moduleName.rsplit('.', 1)
|
||||
path = self.getModulePath(parentName)
|
||||
if path == None:
|
||||
if path is None:
|
||||
return None
|
||||
|
||||
try:
|
||||
|
|
@ -918,7 +925,7 @@ class Freezer:
|
|||
print("couldn't import %s" % (moduleName))
|
||||
module = None
|
||||
|
||||
if module != None:
|
||||
if module is not None:
|
||||
for symbol in moduleName.split('.')[1:]:
|
||||
module = getattr(module, symbol)
|
||||
if hasattr(module, '__all__'):
|
||||
|
|
@ -931,7 +938,7 @@ class Freezer:
|
|||
if '.' in baseName:
|
||||
parentName, baseName = moduleName.rsplit('.', 1)
|
||||
path = self.getModulePath(parentName)
|
||||
if path == None:
|
||||
if path is None:
|
||||
return None
|
||||
|
||||
try:
|
||||
|
|
@ -988,9 +995,9 @@ class Freezer:
|
|||
for parentName, newParentName in parentNames:
|
||||
modules = self.getModuleStar(parentName)
|
||||
|
||||
if modules == None:
|
||||
if modules is None:
|
||||
# It's actually a regular module.
|
||||
mdef[newParentName] = self.ModuleDef(
|
||||
mdefs[newParentName] = self.ModuleDef(
|
||||
parentName, implicit = implicit, guess = guess,
|
||||
fromSource = fromSource, text = text)
|
||||
|
||||
|
|
@ -1024,7 +1031,7 @@ class Freezer:
|
|||
directories within a particular directory.
|
||||
"""
|
||||
|
||||
assert self.mf == None
|
||||
assert self.mf is None
|
||||
|
||||
if not newName:
|
||||
newName = moduleName
|
||||
|
|
@ -1046,7 +1053,7 @@ class Freezer:
|
|||
to done(), you may not add any more modules until you call
|
||||
reset(). """
|
||||
|
||||
assert self.mf == None
|
||||
assert self.mf is None
|
||||
|
||||
# If we are building an exe, we also need to implicitly
|
||||
# bring in Python's startup modules.
|
||||
|
|
|
|||
|
|
@ -13,12 +13,15 @@ import stat
|
|||
import struct
|
||||
import imp
|
||||
import string
|
||||
import time
|
||||
import tempfile
|
||||
|
||||
import setuptools
|
||||
import distutils.log
|
||||
|
||||
from . import FreezeTool
|
||||
from . import pefile
|
||||
from .icon import Icon
|
||||
import panda3d.core as p3d
|
||||
|
||||
|
||||
|
|
@ -30,6 +33,15 @@ if sys.version_info < (3, 0):
|
|||
# Python 3 defines these subtypes of IOError, but Python 2 doesn't.
|
||||
FileNotFoundError = IOError
|
||||
|
||||
# Warn the user. They might be using Python 2 by accident.
|
||||
print("=================================================================")
|
||||
print("WARNING: You are using Python 2, which will soon be discontinued.")
|
||||
print("WARNING: Please use Python 3 for best results and continued")
|
||||
print("WARNING: support after the EOL date of December 31st, 2019.")
|
||||
print("=================================================================")
|
||||
sys.stdout.flush()
|
||||
time.sleep(4.0)
|
||||
|
||||
|
||||
def _parse_list(input):
|
||||
if isinstance(input, basestring):
|
||||
|
|
@ -54,6 +66,8 @@ def _parse_dict(input):
|
|||
|
||||
|
||||
def egg2bam(_build_cmd, srcpath, dstpath):
|
||||
if dstpath.endswith('.gz') or dstpath.endswith('.pz'):
|
||||
dstpath = dstpath[:-3]
|
||||
dstpath = dstpath + '.bam'
|
||||
try:
|
||||
subprocess.check_call([
|
||||
|
|
@ -214,6 +228,7 @@ class build_apps(setuptools.Command):
|
|||
self.exclude_patterns = []
|
||||
self.include_modules = {}
|
||||
self.exclude_modules = {}
|
||||
self.icons = {}
|
||||
self.platforms = [
|
||||
'manylinux1_x86_64',
|
||||
'macosx_10_6_x86_64',
|
||||
|
|
@ -229,7 +244,9 @@ class build_apps(setuptools.Command):
|
|||
self.requirements_path = os.path.join(os.getcwd(), 'requirements.txt')
|
||||
self.use_optimized_wheels = True
|
||||
self.optimized_wheel_index = ''
|
||||
self.pypi_extra_indexes = []
|
||||
self.pypi_extra_indexes = [
|
||||
'https://archive.panda3d.org/thirdparty',
|
||||
]
|
||||
self.file_handlers = {}
|
||||
self.exclude_dependencies = [
|
||||
# Windows
|
||||
|
|
@ -286,6 +303,7 @@ class build_apps(setuptools.Command):
|
|||
key: _parse_list(value)
|
||||
for key, value in _parse_dict(self.exclude_modules).items()
|
||||
}
|
||||
self.icons = _parse_dict(self.icons)
|
||||
self.platforms = _parse_list(self.platforms)
|
||||
self.plugins = _parse_list(self.plugins)
|
||||
self.extra_prc_files = _parse_list(self.extra_prc_files)
|
||||
|
|
@ -345,6 +363,18 @@ class build_apps(setuptools.Command):
|
|||
tmp.update(self.package_data_dirs)
|
||||
self.package_data_dirs = tmp
|
||||
|
||||
self.icon_objects = {}
|
||||
for app, iconpaths in self.icons.items():
|
||||
if not isinstance(iconpaths, list) and not isinstance(iconpaths, tuple):
|
||||
iconpaths = (iconpaths,)
|
||||
|
||||
iconobj = Icon()
|
||||
for iconpath in iconpaths:
|
||||
iconobj.addImage(iconpath)
|
||||
|
||||
iconobj.generateMissingImages()
|
||||
self.icon_objects[app] = iconobj
|
||||
|
||||
def run(self):
|
||||
self.announce('Building platforms: {0}'.format(','.join(self.platforms)), distutils.log.INFO)
|
||||
|
||||
|
|
@ -421,6 +451,22 @@ class build_apps(setuptools.Command):
|
|||
|
||||
return wheelpaths
|
||||
|
||||
def update_pe_resources(self, appname, runtime):
|
||||
"""Update resources (e.g., icons) in windows PE file"""
|
||||
|
||||
icon = self.icon_objects.get(
|
||||
appname,
|
||||
self.icon_objects.get('*', None),
|
||||
)
|
||||
|
||||
if icon is not None:
|
||||
pef = pefile.PEFile()
|
||||
pef.open(runtime, 'r+')
|
||||
pef.add_icon(icon)
|
||||
pef.add_resource_section()
|
||||
pef.write_changes()
|
||||
pef.close()
|
||||
|
||||
def bundle_macos_app(self, builddir):
|
||||
"""Bundle built runtime into a .app for macOS"""
|
||||
|
||||
|
|
@ -462,6 +508,15 @@ class build_apps(setuptools.Command):
|
|||
'CFBundleSignature': '', #TODO
|
||||
'CFBundleExecutable': self.macos_main_app,
|
||||
}
|
||||
|
||||
icon = self.icon_objects.get(
|
||||
self.macos_main_app,
|
||||
self.icon_objects.get('*', None)
|
||||
)
|
||||
if icon is not None:
|
||||
plist['CFBundleIconFile'] = 'iconfile'
|
||||
icon.makeICNS(os.path.join(resdir, 'iconfile.icns'))
|
||||
|
||||
with open(os.path.join(contentsdir, 'Info.plist'), 'wb') as f:
|
||||
if hasattr(plistlib, 'dump'):
|
||||
plistlib.dump(plist, f)
|
||||
|
|
@ -606,6 +661,18 @@ class build_apps(setuptools.Command):
|
|||
stub_path = os.path.join(os.path.dirname(dtool_path), '..', 'bin', stub_name)
|
||||
stub_file = open(stub_path, 'rb')
|
||||
|
||||
# Do we need an icon? On Windows, we need to add this to the stub
|
||||
# before we add the blob.
|
||||
if 'win' in platform:
|
||||
temp_file = tempfile.NamedTemporaryFile(suffix='-icon.exe', delete=False)
|
||||
temp_file.write(stub_file.read())
|
||||
stub_file.close()
|
||||
temp_file.close()
|
||||
self.update_pe_resources(appname, temp_file.name)
|
||||
stub_file = open(temp_file.name, 'rb')
|
||||
else:
|
||||
temp_file = None
|
||||
|
||||
freezer.generateRuntimeFromStub(target_path, stub_file, use_console, {
|
||||
'prc_data': prcexport if self.embed_prc_data else None,
|
||||
'default_prc_dir': self.default_prc_dir,
|
||||
|
|
@ -621,6 +688,9 @@ class build_apps(setuptools.Command):
|
|||
}, self.log_append)
|
||||
stub_file.close()
|
||||
|
||||
if temp_file:
|
||||
os.unlink(temp_file.name)
|
||||
|
||||
# Copy the dependencies.
|
||||
search_path = [builddir]
|
||||
if use_wheels:
|
||||
|
|
@ -827,6 +897,9 @@ class build_apps(setuptools.Command):
|
|||
os.makedirs(dst_dir)
|
||||
|
||||
ext = os.path.splitext(src)[1]
|
||||
# If the file ends with .gz/.pz, we strip this off.
|
||||
if ext in ('.gz', '.pz'):
|
||||
ext = os.path.splitext(src[:-3])[1]
|
||||
if not ext:
|
||||
ext = os.path.basename(src)
|
||||
|
||||
|
|
@ -1346,8 +1419,9 @@ class bdist_apps(setuptools.Command):
|
|||
self.run_command('build_apps')
|
||||
|
||||
platforms = build_cmd.platforms
|
||||
build_base = build_cmd.build_base
|
||||
os.makedirs(self.dist_dir, exist_ok=True)
|
||||
build_base = os.path.abspath(build_cmd.build_base)
|
||||
if not os.path.exists(self.dist_dir):
|
||||
os.makedirs(self.dist_dir)
|
||||
os.chdir(self.dist_dir)
|
||||
|
||||
for platform in platforms:
|
||||
|
|
|
|||
|
|
@ -0,0 +1,269 @@
|
|||
from direct.directnotify.DirectNotifyGlobal import *
|
||||
from panda3d.core import PNMImage, Filename
|
||||
|
||||
class Icon:
|
||||
""" This class is used to create an icon for various platforms. """
|
||||
notify = directNotify.newCategory("Icon")
|
||||
|
||||
def __init__(self):
|
||||
self.images = {}
|
||||
|
||||
def addImage(self, image):
|
||||
""" Adds an image to the icon. Returns False on failure, True on success.
|
||||
Only one image per size can be loaded, and the image size must be square. """
|
||||
|
||||
if not isinstance(image, PNMImage):
|
||||
fn = image
|
||||
if not isinstance(fn, Filename):
|
||||
fn = Filename.fromOsSpecific(fn)
|
||||
|
||||
image = PNMImage()
|
||||
if not image.read(fn):
|
||||
Icon.notify.warning("Image '%s' could not be read" % fn.getBasename())
|
||||
return False
|
||||
|
||||
if image.getXSize() != image.getYSize():
|
||||
Icon.notify.warning("Ignoring image without square size")
|
||||
return False
|
||||
|
||||
self.images[image.getXSize()] = image
|
||||
|
||||
return True
|
||||
|
||||
def generateMissingImages(self):
|
||||
""" Generates image sizes that should be present but aren't by scaling
|
||||
from the next higher size. """
|
||||
|
||||
for required_size in (256, 128, 48, 32, 16):
|
||||
if required_size in self.images:
|
||||
continue
|
||||
|
||||
sizes = sorted(self.images.keys())
|
||||
if required_size * 2 in sizes:
|
||||
from_size = required_size * 2
|
||||
else:
|
||||
from_size = 0
|
||||
for from_size in sizes:
|
||||
if from_size > required_size:
|
||||
break
|
||||
|
||||
if from_size > required_size:
|
||||
Icon.notify.warning("Generating %dx%d icon by scaling down %dx%d image" % (required_size, required_size, from_size, from_size))
|
||||
|
||||
image = PNMImage(required_size, required_size)
|
||||
if self.images[from_size].hasAlpha():
|
||||
image.addAlpha()
|
||||
image.quickFilterFrom(self.images[from_size])
|
||||
self.images[required_size] = image
|
||||
else:
|
||||
Icon.notify.warning("Cannot generate %dx%d icon; no higher resolution image available" % (required_size, required_size))
|
||||
|
||||
def _write_bitmap(self, fp, image, size, bpp):
|
||||
""" Writes the bitmap header and data of an .ico file. """
|
||||
|
||||
fp.write(struct.pack('<IiiHHIIiiII', 40, size, size * 2, 1, bpp, 0, 0, 0, 0, 0, 0))
|
||||
|
||||
# XOR mask
|
||||
if bpp == 24:
|
||||
# Align rows to 4-byte boundary
|
||||
rowalign = b'\0' * (-(size * 3) & 3)
|
||||
for y in xrange(size):
|
||||
for x in xrange(size):
|
||||
r, g, b = image.getXel(x, size - y - 1)
|
||||
fp.write(struct.pack('<BBB', int(b * 255), int(g * 255), int(r * 255)))
|
||||
fp.write(rowalign)
|
||||
|
||||
elif bpp == 32:
|
||||
for y in xrange(size):
|
||||
for x in xrange(size):
|
||||
r, g, b, a = image.getXelA(x, size - y - 1)
|
||||
fp.write(struct.pack('<BBBB', int(b * 255), int(g * 255), int(r * 255), int(a * 255)))
|
||||
|
||||
elif bpp == 8:
|
||||
# We'll have to generate a palette of 256 colors.
|
||||
hist = PNMImage.Histogram()
|
||||
image2 = PNMImage(image)
|
||||
if image2.hasAlpha():
|
||||
image2.premultiplyAlpha()
|
||||
image2.removeAlpha()
|
||||
image2.quantize(256)
|
||||
image2.make_histogram(hist)
|
||||
colors = list(hist.get_pixels())
|
||||
assert len(colors) <= 256
|
||||
|
||||
# Write the palette.
|
||||
i = 0
|
||||
while i < 256 and i < len(colors):
|
||||
r, g, b, a = colors[i]
|
||||
fp.write(struct.pack('<BBBB', b, g, r, 0))
|
||||
i += 1
|
||||
if i < 256:
|
||||
# Fill the rest with zeroes.
|
||||
fp.write(b'\x00' * (4 * (256 - i)))
|
||||
|
||||
# Write indices. Align rows to 4-byte boundary.
|
||||
rowalign = b'\0' * (-size & 3)
|
||||
for y in xrange(size):
|
||||
for x in xrange(size):
|
||||
pixel = image2.get_pixel(x, size - y - 1)
|
||||
index = colors.index(pixel)
|
||||
if index >= 256:
|
||||
# Find closest pixel instead.
|
||||
index = closest_indices[index - 256]
|
||||
fp.write(struct.pack('<B', index))
|
||||
fp.write(rowalign)
|
||||
else:
|
||||
raise ValueError("Invalid bpp %d" % (bpp))
|
||||
|
||||
# Create an AND mask, aligned to 4-byte boundary
|
||||
if image.hasAlpha() and bpp <= 8:
|
||||
rowalign = b'\0' * (-((size + 7) >> 3) & 3)
|
||||
for y in xrange(size):
|
||||
mask = 0
|
||||
num_bits = 7
|
||||
for x in xrange(size):
|
||||
a = image.get_alpha_val(x, size - y - 1)
|
||||
if a <= 1:
|
||||
mask |= (1 << num_bits)
|
||||
num_bits -= 1
|
||||
if num_bits < 0:
|
||||
fp.write(struct.pack('<B', mask))
|
||||
mask = 0
|
||||
num_bits = 7
|
||||
if num_bits < 7:
|
||||
fp.write(struct.pack('<B', mask))
|
||||
fp.write(rowalign)
|
||||
else:
|
||||
andsize = (size + 7) >> 3
|
||||
if andsize % 4 != 0:
|
||||
andsize += 4 - (andsize % 4)
|
||||
fp.write(b'\x00' * (andsize * size))
|
||||
|
||||
def makeICO(self, fn):
|
||||
""" Writes the images to a Windows ICO file. Returns True on success. """
|
||||
|
||||
if not isinstance(fn, Filename):
|
||||
fn = Filename.fromOsSpecific(fn)
|
||||
fn.setBinary()
|
||||
|
||||
# ICO files only support resolutions up to 256x256.
|
||||
count = 0
|
||||
for size in self.images.keys():
|
||||
if size < 256:
|
||||
count += 1
|
||||
if size <= 256:
|
||||
count += 1
|
||||
dataoffs = 6 + count * 16
|
||||
|
||||
ico = open(fn, 'wb')
|
||||
ico.write(struct.pack('<HHH', 0, 1, count))
|
||||
|
||||
# Write 8-bpp image headers for sizes under 256x256.
|
||||
for size, image in self.images.items():
|
||||
if size >= 256:
|
||||
continue
|
||||
ico.write(struct.pack('<BB', size, size))
|
||||
|
||||
# Calculate row sizes
|
||||
xorsize = size
|
||||
if xorsize % 4 != 0:
|
||||
xorsize += 4 - (xorsize % 4)
|
||||
andsize = (size + 7) >> 3
|
||||
if andsize % 4 != 0:
|
||||
andsize += 4 - (andsize % 4)
|
||||
datasize = 40 + 256 * 4 + (xorsize + andsize) * size
|
||||
|
||||
ico.write(struct.pack('<BBHHII', 0, 0, 1, 8, datasize, dataoffs))
|
||||
dataoffs += datasize
|
||||
|
||||
# Write 24/32-bpp image headers.
|
||||
for size, image in self.images.items():
|
||||
if size > 256:
|
||||
continue
|
||||
elif size == 256:
|
||||
ico.write(b'\0\0')
|
||||
else:
|
||||
ico.write(struct.pack('<BB', size, size))
|
||||
|
||||
# Calculate the size so we can write the offset within the file.
|
||||
if image.hasAlpha():
|
||||
bpp = 32
|
||||
xorsize = size * 4
|
||||
else:
|
||||
bpp = 24
|
||||
xorsize = size * 3 + (-(size * 3) & 3)
|
||||
andsize = (size + 7) >> 3
|
||||
if andsize % 4 != 0:
|
||||
andsize += 4 - (andsize % 4)
|
||||
datasize = 40 + (xorsize + andsize) * size
|
||||
|
||||
ico.write(struct.pack('<BBHHII', 0, 0, 1, bpp, datasize, dataoffs))
|
||||
dataoffs += datasize
|
||||
|
||||
# Now write the actual icon bitmap data.
|
||||
for size, image in self.images.items():
|
||||
if size < 256:
|
||||
self._write_bitmap(ico, image, size, 8)
|
||||
|
||||
for size, image in self.images.items():
|
||||
if size <= 256:
|
||||
bpp = 32 if image.hasAlpha() else 24
|
||||
self._write_bitmap(ico, image, size, bpp)
|
||||
|
||||
assert ico.tell() == dataoffs
|
||||
ico.close()
|
||||
|
||||
return True
|
||||
|
||||
def makeICNS(self, fn):
|
||||
""" Writes the images to an Apple ICNS file. Returns True on success. """
|
||||
|
||||
if not isinstance(fn, Filename):
|
||||
fn = Filename.fromOsSpecific(fn)
|
||||
fn.setBinary()
|
||||
|
||||
icns = open(fn, 'wb')
|
||||
icns.write(b'icns\0\0\0\0')
|
||||
|
||||
icon_types = {16: b'is32', 32: b'il32', 48: b'ih32', 128: b'it32'}
|
||||
mask_types = {16: b's8mk', 32: b'l8mk', 48: b'h8mk', 128: b't8mk'}
|
||||
png_types = {256: b'ic08', 512: b'ic09', 1024: b'ic10'}
|
||||
|
||||
pngtype = PNMFileTypeRegistry.getGlobalPtr().getTypeFromExtension("png")
|
||||
|
||||
for size, image in sorted(self.images.items(), key=lambda item:item[0]):
|
||||
if size in png_types and pngtype is not None:
|
||||
stream = StringStream()
|
||||
image.write(stream, "", pngtype)
|
||||
pngdata = stream.data
|
||||
|
||||
icns.write(png_types[size])
|
||||
icns.write(struct.pack('>I', len(pngdata)))
|
||||
icns.write(pngdata)
|
||||
|
||||
elif size in icon_types:
|
||||
# If it has an alpha channel, we write out a mask too.
|
||||
if image.hasAlpha():
|
||||
icns.write(mask_types[size])
|
||||
icns.write(struct.pack('>I', size * size + 8))
|
||||
|
||||
for y in xrange(size):
|
||||
for x in xrange(size):
|
||||
icns.write(struct.pack('<B', int(image.getAlpha(x, y) * 255)))
|
||||
|
||||
icns.write(icon_types[size])
|
||||
icns.write(struct.pack('>I', size * size * 4 + 8))
|
||||
|
||||
for y in xrange(size):
|
||||
for x in xrange(size):
|
||||
r, g, b = image.getXel(x, y)
|
||||
icns.write(struct.pack('>BBBB', 0, int(r * 255), int(g * 255), int(b * 255)))
|
||||
|
||||
length = icns.tell()
|
||||
icns.seek(4)
|
||||
icns.write(struct.pack('>I', length))
|
||||
icns.close()
|
||||
|
||||
return True
|
||||
|
||||
|
||||
|
|
@ -26,6 +26,7 @@
|
|||
|
||||
#ifdef HAVE_PYTHON
|
||||
#include "py_panda.h"
|
||||
#include "dcClass_ext.h"
|
||||
#endif
|
||||
|
||||
using std::endl;
|
||||
|
|
@ -736,7 +737,7 @@ handle_update_field() {
|
|||
// get into trouble if it tried to delete the object from the doId2do
|
||||
// map.
|
||||
Py_INCREF(distobj);
|
||||
dclass->receive_update(distobj, _di);
|
||||
invoke_extension(dclass).receive_update(distobj, _di);
|
||||
Py_DECREF(distobj);
|
||||
|
||||
if (PyErr_Occurred()) {
|
||||
|
|
@ -820,7 +821,7 @@ handle_update_field_owner() {
|
|||
// make a copy of the datagram iterator so that we can use the main
|
||||
// iterator for the non-owner update
|
||||
DatagramIterator _odi(_di);
|
||||
dclass->receive_update(distobjOV, _odi);
|
||||
invoke_extension(dclass).receive_update(distobjOV, _odi);
|
||||
Py_DECREF(distobjOV);
|
||||
|
||||
if (PyErr_Occurred()) {
|
||||
|
|
@ -861,7 +862,7 @@ handle_update_field_owner() {
|
|||
// get into trouble if it tried to delete the object from the doId2do
|
||||
// map.
|
||||
Py_INCREF(distobj);
|
||||
dclass->receive_update(distobj, _di);
|
||||
invoke_extension(dclass).receive_update(distobj, _di);
|
||||
Py_DECREF(distobj);
|
||||
|
||||
if (PyErr_Occurred()) {
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ class SocketStream;
|
|||
* the C++ layer, while server messages that are not understood by the C++
|
||||
* layer are returned up to the Python layer for processing.
|
||||
*/
|
||||
class EXPCL_DIRECT_DISTRIBUTED CConnectionRepository {
|
||||
class CConnectionRepository {
|
||||
PUBLISHED:
|
||||
explicit CConnectionRepository(bool has_owner_view = false,
|
||||
bool threaded_net = false);
|
||||
|
|
|
|||
|
|
@ -18,6 +18,10 @@
|
|||
#include "dcmsgtypes.h"
|
||||
#include "config_distributed.h"
|
||||
|
||||
#ifdef HAVE_PYTHON
|
||||
#include "py_panda.h"
|
||||
#endif
|
||||
|
||||
static const PN_stdfloat smooth_node_epsilon = 0.01;
|
||||
static const double network_time_precision = 100.0; // Matches ClockDelta.py
|
||||
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@
|
|||
#include "nodePath.h"
|
||||
#include "dcbase.h"
|
||||
#include "dcPacker.h"
|
||||
#include "dcPython.h" // to pick up Python.h
|
||||
#include "clockObject.h"
|
||||
|
||||
class DCClass;
|
||||
|
|
@ -28,7 +27,7 @@ class CConnectionRepository;
|
|||
* This class defines some basic methods of DistributedSmoothNodeBase which
|
||||
* have been moved into C++ as a performance optimization.
|
||||
*/
|
||||
class EXPCL_DIRECT_DISTRIBUTED CDistributedSmoothNodeBase {
|
||||
class CDistributedSmoothNodeBase {
|
||||
PUBLISHED:
|
||||
CDistributedSmoothNodeBase();
|
||||
~CDistributedSmoothNodeBase();
|
||||
|
|
|
|||
|
|
@ -23,10 +23,10 @@
|
|||
|
||||
NotifyCategoryDecl(distributed, EXPCL_DIRECT_DISTRIBUTED, EXPTP_DIRECT_DISTRIBUTED);
|
||||
|
||||
extern ConfigVariableInt game_server_timeout_ms;
|
||||
extern ConfigVariableDouble min_lag;
|
||||
extern ConfigVariableDouble max_lag;
|
||||
extern ConfigVariableBool handle_datagrams_internally;
|
||||
extern EXPCL_DIRECT_DISTRIBUTED ConfigVariableInt game_server_timeout_ms;
|
||||
extern EXPCL_DIRECT_DISTRIBUTED ConfigVariableDouble min_lag;
|
||||
extern EXPCL_DIRECT_DISTRIBUTED ConfigVariableDouble max_lag;
|
||||
extern EXPCL_DIRECT_DISTRIBUTED ConfigVariableBool handle_datagrams_internally;
|
||||
|
||||
extern EXPCL_DIRECT_DISTRIBUTED void init_libdistributed();
|
||||
|
||||
|
|
|
|||
|
|
@ -124,7 +124,7 @@ class FilterManager(DirectObject):
|
|||
|
||||
return winx,winy
|
||||
|
||||
def renderSceneInto(self, depthtex=None, colortex=None, auxtex=None, auxbits=0, textures=None):
|
||||
def renderSceneInto(self, depthtex=None, colortex=None, auxtex=None, auxbits=0, textures=None, fbprops=None):
|
||||
|
||||
""" Causes the scene to be rendered into the supplied textures
|
||||
instead of into the original window. Puts a fullscreen quad
|
||||
|
|
@ -185,7 +185,10 @@ class FilterManager(DirectObject):
|
|||
# Choose the size of the offscreen buffer.
|
||||
|
||||
(winx, winy) = self.getScaledSize(1,1,1)
|
||||
buffer = self.createBuffer("filter-base", winx, winy, texgroup)
|
||||
if fbprops is not None:
|
||||
buffer = self.createBuffer("filter-base", winx, winy, texgroup, fbprops=fbprops)
|
||||
else:
|
||||
buffer = self.createBuffer("filter-base", winx, winy, texgroup)
|
||||
|
||||
if (buffer == None):
|
||||
return None
|
||||
|
|
@ -236,7 +239,7 @@ class FilterManager(DirectObject):
|
|||
|
||||
return quad
|
||||
|
||||
def renderQuadInto(self, name="filter-stage", mul=1, div=1, align=1, depthtex=None, colortex=None, auxtex0=None, auxtex1=None):
|
||||
def renderQuadInto(self, name="filter-stage", mul=1, div=1, align=1, depthtex=None, colortex=None, auxtex0=None, auxtex1=None, fbprops=None):
|
||||
|
||||
""" Creates an offscreen buffer for an intermediate
|
||||
computation. Installs a quad into the buffer. Returns
|
||||
|
|
@ -250,7 +253,10 @@ class FilterManager(DirectObject):
|
|||
|
||||
depthbits = bool(depthtex != None)
|
||||
|
||||
buffer = self.createBuffer(name, winx, winy, texgroup, depthbits)
|
||||
if fbprops is not None:
|
||||
buffer = self.createBuffer(name, winx, winy, texgroup, depthbits, fbprops=fbprops)
|
||||
else:
|
||||
buffer = self.createBuffer(name, winx, winy, texgroup, depthbits)
|
||||
|
||||
if (buffer == None):
|
||||
return None
|
||||
|
|
@ -287,7 +293,7 @@ class FilterManager(DirectObject):
|
|||
|
||||
return quad
|
||||
|
||||
def createBuffer(self, name, xsize, ysize, texgroup, depthbits=1):
|
||||
def createBuffer(self, name, xsize, ysize, texgroup, depthbits=1, fbprops=None):
|
||||
""" Low-level buffer creation. Not intended for public use. """
|
||||
|
||||
winprops = WindowProperties()
|
||||
|
|
@ -297,6 +303,9 @@ class FilterManager(DirectObject):
|
|||
props.setRgbColor(1)
|
||||
props.setDepthBits(depthbits)
|
||||
props.setStereo(self.win.isStereo())
|
||||
if fbprops is not None:
|
||||
props.addProperties(fbprops)
|
||||
|
||||
depthtex, colortex, auxtex0, auxtex1 = texgroup
|
||||
if (auxtex0 != None):
|
||||
props.setAuxRgba(1)
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
__all__ = ['findDialog', 'cleanupDialog', 'DirectDialog', 'OkDialog', 'OkCancelDialog', 'YesNoDialog', 'YesNoCancelDialog', 'RetryCancelDialog']
|
||||
|
||||
from panda3d.core import *
|
||||
from direct.showbase import ShowBaseGlobal
|
||||
from . import DirectGuiGlobals as DGG
|
||||
from .DirectFrame import *
|
||||
from .DirectButton import *
|
||||
|
|
@ -207,7 +208,7 @@ class DirectDialog(DirectFrame):
|
|||
image = None
|
||||
# Get size of text/geom without image (for state 0)
|
||||
if image:
|
||||
image.reparentTo(hidden)
|
||||
image.reparentTo(ShowBaseGlobal.hidden)
|
||||
bounds = self.stateNodePath[0].getTightBounds()
|
||||
if image:
|
||||
image.reparentTo(self.stateNodePath[0])
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ text entered using the keyboard."""
|
|||
__all__ = ['DirectEntry']
|
||||
|
||||
from panda3d.core import *
|
||||
from direct.showbase import ShowBaseGlobal
|
||||
from . import DirectGuiGlobals as DGG
|
||||
from .DirectFrame import *
|
||||
from .OnscreenText import OnscreenText
|
||||
|
|
@ -94,7 +95,7 @@ class DirectEntry(DirectFrame):
|
|||
self.onscreenText = self.createcomponent(
|
||||
'text', (), None,
|
||||
OnscreenText,
|
||||
(), parent = hidden,
|
||||
(), parent = ShowBaseGlobal.hidden,
|
||||
# Pass in empty text to avoid extra work, since its really
|
||||
# The PGEntry which will use the TextNode to generate geometry
|
||||
text = '',
|
||||
|
|
@ -215,11 +216,11 @@ class DirectEntry(DirectFrame):
|
|||
self._autoCapitalize()
|
||||
|
||||
def _autoCapitalize(self):
|
||||
name = self.get().decode('utf-8')
|
||||
name = self.guiItem.getWtext()
|
||||
# capitalize each word, allowing for things like McMutton
|
||||
capName = ''
|
||||
capName = u''
|
||||
# track each individual word to detect prefixes like Mc
|
||||
wordSoFar = ''
|
||||
wordSoFar = u''
|
||||
# track whether the previous character was part of a word or not
|
||||
wasNonWordChar = True
|
||||
for i, character in enumerate(name):
|
||||
|
|
@ -228,9 +229,9 @@ class DirectEntry(DirectFrame):
|
|||
# This assumes that string.lower and string.upper will return different
|
||||
# values for all unicode letters.
|
||||
# - Don't count apostrophes as a break between words
|
||||
if character.lower() == character.upper() and character != "'":
|
||||
if character.lower() == character.upper() and character != u"'":
|
||||
# we are between words
|
||||
wordSoFar = ''
|
||||
wordSoFar = u''
|
||||
wasNonWordChar = True
|
||||
else:
|
||||
capitalize = False
|
||||
|
|
@ -254,7 +255,8 @@ class DirectEntry(DirectFrame):
|
|||
wordSoFar += character
|
||||
wasNonWordChar = False
|
||||
capName += character
|
||||
self.enterText(capName.encode('utf-8'))
|
||||
self.guiItem.setWtext(capName)
|
||||
self.guiItem.setCursorPosition(self.guiItem.getNumCharacters())
|
||||
|
||||
def focusOutCommandFunc(self):
|
||||
if self['focusOutCommand']:
|
||||
|
|
|
|||
|
|
@ -29,19 +29,41 @@ class DirectEntryScroll(DirectFrame):
|
|||
# frameSize = (-0.006, 3.2, -0.015, 0.036),
|
||||
# if you need to scale the entry scale it's parent instead
|
||||
|
||||
self.entry = entry
|
||||
self.canvas = NodePath(self.guiItem.getCanvasNode())
|
||||
self.entry.reparentTo(self.canvas)
|
||||
self.canvas.setPos(0,0,0)
|
||||
|
||||
self.entry.bind(DGG.CURSORMOVE,self.cursorMove)
|
||||
self.entry = None
|
||||
if entry is not None:
|
||||
self.entry = entry
|
||||
self.entry.reparentTo(self.canvas)
|
||||
self.entry.bind(DGG.CURSORMOVE, self.cursorMove)
|
||||
|
||||
self.canvas.node().setBounds(OmniBoundingVolume())
|
||||
self.canvas.node().setFinal(1)
|
||||
self.resetCanvas()
|
||||
|
||||
def setEntry(self, entry):
|
||||
"""
|
||||
Sets a DirectEntry element for this scroll frame. A DirectEntryScroll
|
||||
can only hold one entry at a time, so make sure to not call this
|
||||
function twice or call clearEntry before to make sure no entry
|
||||
is already set.
|
||||
"""
|
||||
assert self.entry is None, "An entry was already set for this DirectEntryScroll element"
|
||||
self.entry = entry
|
||||
self.entry.reparentTo(self.canvas)
|
||||
|
||||
self.entry.bind(DGG.CURSORMOVE, self.cursorMove)
|
||||
|
||||
def clearEntry(self):
|
||||
"""
|
||||
detaches and unbinds the entry from the scroll frame and its
|
||||
events. You'll be responsible for destroying it.
|
||||
"""
|
||||
if self.entry is None: return
|
||||
self.entry.unbind(DGG.CURSORMOVE)
|
||||
self.entry.detachNode()
|
||||
self.entry = None
|
||||
|
||||
def cursorMove(self, cursorX, cursorY):
|
||||
cursorX = self.entry.guiItem.getCursorX() * self.entry['text_scale'][0]
|
||||
|
|
|
|||
|
|
@ -634,7 +634,7 @@ class DirectGuiBase(DirectObject.DirectObject):
|
|||
"""
|
||||
# Need to tack on gui item specific id
|
||||
gEvent = event + self.guiId
|
||||
if ShowBase.config.GetBool('debug-directgui-msgs', False):
|
||||
if ShowBaseGlobal.config.GetBool('debug-directgui-msgs', False):
|
||||
from direct.showbase.PythonUtil import StackTrace
|
||||
print(gEvent)
|
||||
print(StackTrace())
|
||||
|
|
@ -663,7 +663,7 @@ class DirectGuiWidget(DirectGuiBase, NodePath):
|
|||
# Determine the default initial state for inactive (or
|
||||
# unclickable) components. If we are in edit mode, these are
|
||||
# actually clickable by default.
|
||||
guiEdit = ShowBase.config.GetBool('direct-gui-edit', False)
|
||||
guiEdit = ShowBaseGlobal.config.GetBool('direct-gui-edit', False)
|
||||
if guiEdit:
|
||||
inactiveInitState = DGG.NORMAL
|
||||
else:
|
||||
|
|
@ -729,7 +729,7 @@ class DirectGuiWidget(DirectGuiBase, NodePath):
|
|||
guiObjectCollector.addLevel(1)
|
||||
guiObjectCollector.flushLevel()
|
||||
# track gui items by guiId for tracking down leaks
|
||||
if ShowBase.config.GetBool('track-gui-items', False):
|
||||
if ShowBaseGlobal.config.GetBool('track-gui-items', False):
|
||||
if not hasattr(ShowBase, 'guiItems'):
|
||||
ShowBase.guiItems = {}
|
||||
if self.guiId in ShowBase.guiItems:
|
||||
|
|
|
|||
|
|
@ -22,10 +22,12 @@ class DirectOptionMenu(DirectButton):
|
|||
# List of items to display on the popup menu
|
||||
('items', [], self.setItems),
|
||||
# Initial item to display on menu button
|
||||
# Can be an interger index or the same string as the button
|
||||
# Can be an integer index or the same string as the button
|
||||
('initialitem', None, DGG.INITOPT),
|
||||
# Amount of padding to place around popup button indicator
|
||||
('popupMarkerBorder', (.1, .1), None),
|
||||
# The initial position of the popup marker
|
||||
('popupMarker_pos', None, None),
|
||||
# Background color to use to highlight popup menu items
|
||||
('highlightColor', (.5, .5, .5, 1), None),
|
||||
# Extra scale to use on highlight popup menu items
|
||||
|
|
@ -50,6 +52,8 @@ class DirectOptionMenu(DirectButton):
|
|||
frameSize = (-0.5, 0.5, -0.2, 0.2),
|
||||
scale = 0.4,
|
||||
relief = DGG.RAISED)
|
||||
# Record any user specified popup marker position
|
||||
self.initPopupMarkerPos = self['popupMarker_pos']
|
||||
# This needs to popup the menu too
|
||||
self.popupMarker.bind(DGG.B1PRESS, self.showPopupMenu)
|
||||
# Check if item is highlighted on release and select it if it is
|
||||
|
|
@ -73,6 +77,7 @@ class DirectOptionMenu(DirectButton):
|
|||
state = 'normal')
|
||||
# Make sure this is on top of all the other widgets
|
||||
self.cancelFrame.setBin('gui-popup', 0)
|
||||
self.cancelFrame.node().setBounds(OmniBoundingVolume())
|
||||
self.cancelFrame.bind(DGG.B1PRESS, self.hidePopupMenu)
|
||||
# Default action on press is to show popup menu
|
||||
self.bind(DGG.B1PRESS, self.showPopupMenu)
|
||||
|
|
@ -168,8 +173,13 @@ class DirectOptionMenu(DirectButton):
|
|||
else:
|
||||
# Or base it upon largest item
|
||||
bounds = [self.minX, self.maxX, self.minZ, self.maxZ]
|
||||
pm.setPos(bounds[1] + pmw/2.0, 0,
|
||||
bounds[2] + (bounds[3] - bounds[2])/2.0)
|
||||
if self.initPopupMarkerPos:
|
||||
# Use specified position
|
||||
pmPos = list(self.initPopupMarkerPos)
|
||||
else:
|
||||
# Or base the position on the frame size.
|
||||
pmPos = [bounds[1] + pmw/2.0, 0, bounds[2] + (bounds[3] - bounds[2])/2.0]
|
||||
pm.setPos(pmPos[0], pmPos[1], pmPos[2])
|
||||
# Adjust popup menu button to fit all items (or use user specified
|
||||
# frame size
|
||||
bounds[1] += pmw
|
||||
|
|
@ -184,6 +194,12 @@ class DirectOptionMenu(DirectButton):
|
|||
Adjust popup position if default position puts it outside of
|
||||
visible screen region
|
||||
"""
|
||||
|
||||
# Needed attributes (such as minZ) won't be set unless the user has specified
|
||||
# items to display. Let's assert that we've given items to work with.
|
||||
items = self['items']
|
||||
assert items and len(items) > 0, 'Cannot show an empty popup menu! You must add items!'
|
||||
|
||||
# Show the menu
|
||||
self.popupMenu.show()
|
||||
# Make sure its at the right scale
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ class DirectScrolledFrame(DirectFrame):
|
|||
('canvasSize', (-1, 1, -1, 1), self.setCanvasSize),
|
||||
('manageScrollBars', 1, self.setManageScrollBars),
|
||||
('autoHideScrollBars', 1, self.setAutoHideScrollBars),
|
||||
('scrollBarWidth', 0.08, None),
|
||||
('scrollBarWidth', 0.08, self.setScrollBarWidth),
|
||||
('borderWidth', (0.01, 0.01), self.setBorderWidth),
|
||||
)
|
||||
|
||||
|
|
@ -72,6 +72,11 @@ class DirectScrolledFrame(DirectFrame):
|
|||
# Call option initialization functions
|
||||
self.initialiseoptions(DirectScrolledFrame)
|
||||
|
||||
def setScrollBarWidth(self):
|
||||
w = self['scrollBarWidth']
|
||||
self.verticalScroll["frameSize"] = (-w / 2.0, w / 2.0, -1, 1)
|
||||
self.horizontalScroll["frameSize"] = (-1, 1, -w / 2.0, w / 2.0)
|
||||
|
||||
def setCanvasSize(self):
|
||||
f = self['canvasSize']
|
||||
self.guiItem.setVirtualFrame(f[0], f[1], f[2], f[3])
|
||||
|
|
@ -100,8 +105,10 @@ class DirectScrolledFrame(DirectFrame):
|
|||
simpleChildGui = self.guiDict.get(parts[-1])
|
||||
if simpleChildGui:
|
||||
simpleChildGui.destroy()
|
||||
self.verticalScroll.destroy()
|
||||
self.horizontalScroll.destroy()
|
||||
del self.verticalScroll
|
||||
del self.horizontalScroll
|
||||
if self.verticalScroll:
|
||||
self.verticalScroll.destroy()
|
||||
if self.horizontalScroll:
|
||||
self.horizontalScroll.destroy()
|
||||
self.verticalScroll = None
|
||||
self.horizontalScroll = None
|
||||
DirectFrame.destroy(self)
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
__all__ = ['DirectScrolledListItem', 'DirectScrolledList']
|
||||
|
||||
from panda3d.core import *
|
||||
from direct.showbase import ShowBaseGlobal
|
||||
from . import DirectGuiGlobals as DGG
|
||||
from direct.directnotify import DirectNotifyGlobal
|
||||
from direct.task.Task import Task
|
||||
|
|
@ -369,7 +370,7 @@ class DirectScrolledList(DirectFrame):
|
|||
del self.currentSelected
|
||||
self["items"].remove(item)
|
||||
if type(item) != type(''):
|
||||
item.reparentTo(hidden)
|
||||
item.reparentTo(ShowBaseGlobal.hidden)
|
||||
self.refresh()
|
||||
return 1
|
||||
else:
|
||||
|
|
@ -387,7 +388,7 @@ class DirectScrolledList(DirectFrame):
|
|||
item.destroy()
|
||||
self["items"].remove(item)
|
||||
if type(item) != type(''):
|
||||
item.reparentTo(hidden)
|
||||
item.reparentTo(ShowBaseGlobal.hidden)
|
||||
self.refresh()
|
||||
return 1
|
||||
else:
|
||||
|
|
@ -410,7 +411,7 @@ class DirectScrolledList(DirectFrame):
|
|||
self["items"].remove(item)
|
||||
if type(item) != type(''):
|
||||
#RAU possible leak here, let's try to do the right thing
|
||||
#item.reparentTo(hidden)
|
||||
#item.reparentTo(ShowBaseGlobal.hidden)
|
||||
item.removeNode()
|
||||
retval = 1
|
||||
|
||||
|
|
@ -435,7 +436,7 @@ class DirectScrolledList(DirectFrame):
|
|||
self["items"].remove(item)
|
||||
if type(item) != type(''):
|
||||
#RAU possible leak here, let's try to do the right thing
|
||||
#item.reparentTo(hidden)
|
||||
#item.reparentTo(ShowBaseGlobal.hidden)
|
||||
item.removeNode()
|
||||
retval = 1
|
||||
if (refresh):
|
||||
|
|
|
|||
|
|
@ -27,8 +27,7 @@ class SoundInterval(Interval.Interval):
|
|||
# than explicitly restarting the sound every time around. This
|
||||
# prevents a skip in the sound at every repetition (the gap in
|
||||
# the sound is caused by the delay between the end of the sound
|
||||
# and the next taskMgr cycle). There still seems to be a skip
|
||||
# in Miles when looping MP3s. =(
|
||||
# and the next taskMgr cycle).
|
||||
# RAU 03/01/07 add listenerNode in case we don't want to
|
||||
# use base.camera as the listener, node must not be None
|
||||
def __init__(self, sound, loop = 0, duration = 0.0, name = None,
|
||||
|
|
@ -62,23 +61,6 @@ class SoundInterval(Interval.Interval):
|
|||
#if (duration == 0):
|
||||
# self.notify.warning('zero length duration!')
|
||||
|
||||
# MPG - hack for Miles bug
|
||||
#duration += 1.5
|
||||
|
||||
# DCR - hack for Miles bug - adding 1.5 seconds caused
|
||||
# problems for MG_neg_buzzer.wav
|
||||
|
||||
# DCR - what this is all about: Miles is under-reporting the
|
||||
# length of MP3 files, and they're getting cut off too early.
|
||||
# This is a temporary hack. We could:
|
||||
# - hack Miles to fix its MP3 length calculation
|
||||
# - complain louder about this to RAD
|
||||
# - precompute MP3 durations and store them in a table
|
||||
|
||||
# drose - ok, I've put in a lower-level workaround in the
|
||||
# MilesAudioManager. This is no longer necessary up here,
|
||||
# where it pollutes SoundInterval for everyone.
|
||||
#duration += min(duration * 2.4, 1.5)
|
||||
|
||||
# Generate unique name if necessary
|
||||
if (name == None):
|
||||
|
|
|
|||
|
|
@ -106,6 +106,8 @@ set_end_hpr(const LQuaternion &quat) {
|
|||
* if either set_end_quat() or set_end_hpr() is also called. This parameter
|
||||
* is optional; if unspecified, the value will be taken from the node's actual
|
||||
* rotation at the time the lerp is performed.
|
||||
*
|
||||
* The given quaternion needs to be normalized.
|
||||
*/
|
||||
INLINE void CLerpNodePathInterval::
|
||||
set_start_quat(const LQuaternion &quat) {
|
||||
|
|
@ -143,6 +145,8 @@ set_end_quat(const LVecBase3 &hpr) {
|
|||
* This replaces a previous call to set_end_hpr(). If neither set_end_quat()
|
||||
* nor set_end_hpr() is called, the node's rotation will not be affected by
|
||||
* the lerp.
|
||||
*
|
||||
* The given quaternion needs to be normalized.
|
||||
*/
|
||||
INLINE void CLerpNodePathInterval::
|
||||
set_end_quat(const LQuaternion &quat) {
|
||||
|
|
|
|||
|
|
@ -174,14 +174,14 @@ priv_step(double t) {
|
|||
setup_slerp();
|
||||
|
||||
} else if ((_flags & F_bake_in_start) != 0) {
|
||||
set_start_quat(transform->get_quat());
|
||||
set_start_quat(transform->get_norm_quat());
|
||||
setup_slerp();
|
||||
|
||||
} else {
|
||||
if (_prev_d == 1.0) {
|
||||
_start_quat = _end_quat;
|
||||
} else {
|
||||
LQuaternion prev_value = transform->get_quat();
|
||||
LQuaternion prev_value = transform->get_norm_quat();
|
||||
_start_quat = (prev_value - _prev_d * _end_quat) / (1.0 - _prev_d);
|
||||
}
|
||||
setup_slerp();
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
from pandac.PandaModules import *
|
||||
from panda3d.core import *
|
||||
from . import ObjectGlobals as OG
|
||||
|
||||
class ActionMgr:
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
This is the module for curve edit
|
||||
"""
|
||||
|
||||
from pandac.PandaModules import *
|
||||
from panda3d.core import *
|
||||
from direct.wxwidgets.WxPandaShell import *
|
||||
from direct.showbase.DirectObject import *
|
||||
from direct.directtools.DirectSelection import SelectionRay
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ class FileMgr:
|
|||
def saveToFile(self, fileName):
|
||||
try:
|
||||
f = open(fileName, 'w')
|
||||
f.write("from pandac.PandaModules import *\n")
|
||||
f.write("from panda3d.core import *\n")
|
||||
f.write("\nif hasattr(base, 'le'):\n")
|
||||
f.write(" objectMgr = base.le.objectMgr\n")
|
||||
f.write(" animMgr = base.le.animMgr\n")
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
Defines Layer UI
|
||||
"""
|
||||
import wx
|
||||
from pandac.PandaModules import *
|
||||
from panda3d.core import *
|
||||
|
||||
from . import ObjectGlobals as OG
|
||||
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ class LevelEditor(LevelEditorBase):
|
|||
|
||||
# Populating uderlined data-structures
|
||||
self.ui = LevelEditorUI(self)
|
||||
self.ui.SetCursor(wx.StockCursor(wx.CURSOR_WAIT))
|
||||
self.ui.SetCursor(wx.Cursor(wx.CURSOR_WAIT))
|
||||
self.objectPalette.populate()
|
||||
self.protoPalette.populate()
|
||||
|
||||
|
|
@ -42,4 +42,4 @@ class LevelEditor(LevelEditorBase):
|
|||
# When you define your own LevelEditor class inheriting LevelEditorBase
|
||||
# you should call self.initialize() at the end of __init__() function
|
||||
self.initialize()
|
||||
self.ui.SetCursor(wx.StockCursor(wx.CURSOR_ARROW))
|
||||
self.ui.SetCursor(wx.Cursor(wx.CURSOR_ARROW))
|
||||
|
|
|
|||
|
|
@ -305,7 +305,7 @@ class LevelEditorBase(DirectObject):
|
|||
if self.settingsFile is None:
|
||||
return
|
||||
|
||||
self.ui.SetCursor(wx.StockCursor(wx.CURSOR_WAIT))
|
||||
self.ui.SetCursor(wx.Cursor(wx.CURSOR_WAIT))
|
||||
try:
|
||||
f = open(self.settingsFile, 'r')
|
||||
configLines = f.readlines()
|
||||
|
|
@ -342,7 +342,7 @@ class LevelEditorBase(DirectObject):
|
|||
self.ui.updateMenu()
|
||||
except:
|
||||
pass
|
||||
self.ui.SetCursor(wx.StockCursor(wx.CURSOR_ARROW))
|
||||
self.ui.SetCursor(wx.Cursor(wx.CURSOR_ARROW))
|
||||
|
||||
def convertMaya(self, modelname, callBack, obj=None, isAnim=False):
|
||||
if obj and isAnim:
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
from . import LevelEditor
|
||||
from direct.leveleditor import LevelEditor
|
||||
|
||||
if __name__ == '__main__':
|
||||
base.le = LevelEditor.LevelEditor()
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
## import os
|
||||
## from wx.lib.agw import fourwaysplitter as FWS
|
||||
|
||||
from pandac.PandaModules import *
|
||||
from panda3d.core import *
|
||||
from direct.wxwidgets.WxPandaShell import *
|
||||
from direct.directtools.DirectSelection import SelectionRay
|
||||
|
||||
|
|
@ -478,7 +478,7 @@ class LevelEditorUIBase(WxPandaShell):
|
|||
self.editor.reset()
|
||||
|
||||
def onOpen(self, evt=None):
|
||||
dialog = wx.FileDialog(None, "Choose a file", os.getcwd(), "", "*.py", wx.OPEN)
|
||||
dialog = wx.FileDialog(None, "Choose a file", os.getcwd(), "", "*.py", style = wx.FD_OPEN | wx.FD_FILE_MUST_EXIST)
|
||||
if dialog.ShowModal() == wx.ID_OK:
|
||||
self.editor.load(dialog.GetPath())
|
||||
self.editor.setTitleWithFilename(dialog.GetPath())
|
||||
|
|
@ -492,7 +492,7 @@ class LevelEditorUIBase(WxPandaShell):
|
|||
self.editor.save()
|
||||
|
||||
def onSaveAs(self, evt):
|
||||
dialog = wx.FileDialog(None, "Choose a file", os.getcwd(), "", "*.py", wx.SAVE)
|
||||
dialog = wx.FileDialog(None, "Choose a file", os.getcwd(), "", "*.py", style = wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT)
|
||||
result = True
|
||||
if dialog.ShowModal() == wx.ID_OK:
|
||||
self.editor.saveAs(dialog.GetPath())
|
||||
|
|
@ -503,7 +503,7 @@ class LevelEditorUIBase(WxPandaShell):
|
|||
return result
|
||||
|
||||
def onExportToMaya(self, evt):
|
||||
dialog = wx.FileDialog(None, "Choose a file", os.getcwd(), "", "*.mb", wx.SAVE)
|
||||
dialog = wx.FileDialog(None, "Choose a file", os.getcwd(), "", "*.mb", style = wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT)
|
||||
if dialog.ShowModal() == wx.ID_OK:
|
||||
self.editor.exportToMaya(dialog.GetPath())
|
||||
dialog.Destroy()
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import os, time, copy
|
|||
|
||||
from direct.task import Task
|
||||
from direct.actor.Actor import Actor
|
||||
from pandac.PandaModules import *
|
||||
from panda3d.core import *
|
||||
from .ActionMgr import *
|
||||
from . import ObjectGlobals as OG
|
||||
|
||||
|
|
|
|||
|
|
@ -69,4 +69,4 @@ class ObjectPaletteUI(wx.Panel):
|
|||
return cmp(index1, index2)
|
||||
|
||||
def getSelected(self):
|
||||
return self.tree.GetItemPyData(self.tree.GetSelection())
|
||||
return self.tree.GetItemData(self.tree.GetSelection())
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ from wx.lib.embeddedimage import PyEmbeddedImage
|
|||
from wx.lib.scrolledpanel import ScrolledPanel
|
||||
from wx.lib.agw.cubecolourdialog import *
|
||||
from direct.wxwidgets.WxSlider import *
|
||||
from pandac.PandaModules import *
|
||||
from panda3d.core import *
|
||||
from . import ObjectGlobals as OG
|
||||
from . import AnimGlobals as AG
|
||||
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ class PaletteTreeCtrl(wx.TreeCtrl):
|
|||
roots.append(key)
|
||||
for root in roots:
|
||||
newItem = self.AppendItem(parentItem, root)
|
||||
self.SetItemPyData(newItem, root)
|
||||
self.SetItemData(newItem, root)
|
||||
rootItems.append(newItem)
|
||||
itemKeys.remove(root)
|
||||
for rootItem in rootItems:
|
||||
|
|
@ -108,7 +108,7 @@ class PaletteTreeCtrl(wx.TreeCtrl):
|
|||
item, cookie = self.GetFirstChild(parent)
|
||||
while item:
|
||||
itemName = self.GetItemText(item)
|
||||
itemData = self.GetItemPyData(item)
|
||||
itemData = self.GetItemData(item)
|
||||
|
||||
newItem = self.AppendItem(newParent, itemName)
|
||||
self.SetItemPyData(newItem, itemData)
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ Defines ProtoObjs List UI
|
|||
import wx
|
||||
import os
|
||||
|
||||
from pandac.PandaModules import *
|
||||
from panda3d.core import *
|
||||
from .ProtoObjs import *
|
||||
|
||||
class ProtoDropTarget(wx.PyDropTarget):
|
||||
|
|
|
|||
|
|
@ -3,13 +3,13 @@ Defines ProtoPalette tree UI
|
|||
"""
|
||||
import wx
|
||||
import os
|
||||
from pandac.PandaModules import *
|
||||
from panda3d.core import *
|
||||
from .PaletteTreeCtrl import *
|
||||
|
||||
class UniversalDropTarget(wx.PyDropTarget):
|
||||
class UniversalDropTarget(wx.DropTarget):
|
||||
"""Implements drop target functionality to receive files, bitmaps and text"""
|
||||
def __init__(self, editor):
|
||||
wx.PyDropTarget.__init__(self)
|
||||
wx.DropTarget.__init__(self)
|
||||
self.editor = editor
|
||||
self.do = wx.DataObjectComposite() # the dataobject that gets filled with the appropriate data
|
||||
self.filedo = wx.FileDataObject()
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
Defines Scene Graph tree UI Base
|
||||
"""
|
||||
import wx
|
||||
from pandac.PandaModules import *
|
||||
from panda3d.core import *
|
||||
from .ActionMgr import *
|
||||
|
||||
from . import ObjectGlobals as OG
|
||||
|
|
@ -26,7 +26,7 @@ class SceneGraphUIBase(wx.Panel):
|
|||
size=wx.DefaultSize, style=wx.TR_MULTIPLE|wx.TR_DEFAULT_STYLE,
|
||||
validator=wx.DefaultValidator, name="treeCtrl")
|
||||
self.root = self.tree.AddRoot('render')
|
||||
self.tree.SetItemPyData(self.root, "render")
|
||||
self.tree.SetItemData(self.root, "render")
|
||||
|
||||
self.shouldShowPandaObjChildren = False
|
||||
|
||||
|
|
@ -61,7 +61,7 @@ class SceneGraphUIBase(wx.Panel):
|
|||
self.tree.Delete(item)
|
||||
|
||||
def traversePandaObjects(self, parent, objNodePath):
|
||||
itemId = self.tree.GetItemPyData(parent)
|
||||
itemId = self.tree.GetItemData(parent)
|
||||
i = 0
|
||||
for child in objNodePath.getChildren():
|
||||
if child.hasTag('OBJRoot'):
|
||||
|
|
@ -78,7 +78,7 @@ class SceneGraphUIBase(wx.Panel):
|
|||
|
||||
def addPandaObjectChildren(self, parent):
|
||||
# first, find Panda Object's NodePath of the item
|
||||
itemId = self.tree.GetItemPyData(parent)
|
||||
itemId = self.tree.GetItemData(parent)
|
||||
if itemId == "render":
|
||||
return
|
||||
obj = self.editor.objectMgr.findObjectById(itemId)
|
||||
|
|
@ -96,7 +96,7 @@ class SceneGraphUIBase(wx.Panel):
|
|||
|
||||
def removePandaObjectChildren(self, parent):
|
||||
# first, find Panda Object's NodePath of the item
|
||||
itemId = self.tree.GetItemPyData(parent)
|
||||
itemId = self.tree.GetItemData(parent)
|
||||
if itemId == "render":
|
||||
return
|
||||
obj = self.editor.objectMgr.findObjectById(itemId)
|
||||
|
|
@ -142,14 +142,14 @@ class SceneGraphUIBase(wx.Panel):
|
|||
|
||||
def traverse(self, parent, itemId):
|
||||
# prevent from traversing into self
|
||||
if itemId == self.tree.GetItemPyData(parent):
|
||||
if itemId == self.tree.GetItemData(parent):
|
||||
return None
|
||||
|
||||
# main loop - serching for an item with an itemId
|
||||
item, cookie = self.tree.GetFirstChild(parent)
|
||||
while item:
|
||||
# if the item was found - return it
|
||||
if itemId == self.tree.GetItemPyData(item):
|
||||
if itemId == self.tree.GetItemData(item):
|
||||
return item
|
||||
|
||||
# the tem was not found - checking if it has children
|
||||
|
|
@ -168,7 +168,7 @@ class SceneGraphUIBase(wx.Panel):
|
|||
item, cookie = self.tree.GetFirstChild(parent)
|
||||
while item:
|
||||
data = self.tree.GetItemText(item)
|
||||
itemId = self.tree.GetItemPyData(item)
|
||||
itemId = self.tree.GetItemData(item)
|
||||
newItem = self.tree.AppendItem(newParent, data)
|
||||
self.tree.SetItemPyData(newItem, itemId)
|
||||
|
||||
|
|
@ -186,13 +186,13 @@ class SceneGraphUIBase(wx.Panel):
|
|||
def reParent(self, oldParent, newParent, child):
|
||||
if newParent is None:
|
||||
newParent = self.root
|
||||
itemId = self.tree.GetItemPyData(oldParent)
|
||||
itemId = self.tree.GetItemData(oldParent)
|
||||
newItem = self.tree.AppendItem(newParent, child)
|
||||
self.tree.SetItemPyData(newItem, itemId)
|
||||
self.reParentTree(oldParent, newItem)
|
||||
|
||||
obj = self.editor.objectMgr.findObjectById(itemId)
|
||||
itemId = self.tree.GetItemPyData(newParent)
|
||||
itemId = self.tree.GetItemData(newParent)
|
||||
if itemId != "render":
|
||||
newParentObj = self.editor.objectMgr.findObjectById(itemId)
|
||||
self.reParentData(newParentObj[OG.OBJ_NP], obj[OG.OBJ_NP])
|
||||
|
|
@ -207,7 +207,7 @@ class SceneGraphUIBase(wx.Panel):
|
|||
self.addPandaObjectChildren(newpParent)
|
||||
|
||||
def isChildOrGrandChild(self, parent, child):
|
||||
childId = self.tree.GetItemPyData(child)
|
||||
childId = self.tree.GetItemData(child)
|
||||
return self.traverse(parent, childId)
|
||||
|
||||
def changeHierarchy(self, data, x, y):
|
||||
|
|
@ -226,7 +226,7 @@ class SceneGraphUIBase(wx.Panel):
|
|||
return
|
||||
|
||||
# undo function setup...
|
||||
action = ActionChangeHierarchy(self.editor, self.tree.GetItemPyData(self.tree.GetItemParent(item)), self.tree.GetItemPyData(item), self.tree.GetItemPyData(dragToItem), data)
|
||||
action = ActionChangeHierarchy(self.editor, self.tree.GetItemData(self.tree.GetItemParent(item)), self.tree.GetItemData(item), self.tree.GetItemData(dragToItem), data)
|
||||
self.editor.actionMgr.push(action)
|
||||
action()
|
||||
|
||||
|
|
@ -283,7 +283,7 @@ class SceneGraphUIBase(wx.Panel):
|
|||
def onSelected(self, event):
|
||||
item = event.GetItem();
|
||||
if item:
|
||||
itemId = self.tree.GetItemPyData(item)
|
||||
itemId = self.tree.GetItemData(item)
|
||||
if itemId:
|
||||
obj = self.editor.objectMgr.findObjectById(itemId);
|
||||
if obj:
|
||||
|
|
@ -313,7 +313,7 @@ class SceneGraphUIBase(wx.Panel):
|
|||
if not item.IsOk():
|
||||
return
|
||||
self.currItem = item
|
||||
itemId = self.tree.GetItemPyData(item)
|
||||
itemId = self.tree.GetItemData(item)
|
||||
if not itemId:
|
||||
return
|
||||
self.currObj = self.editor.objectMgr.findObjectById(itemId);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
from pandac.PandaModules import *
|
||||
from panda3d.core import *
|
||||
|
||||
if hasattr(base, 'le'):
|
||||
objectMgr = base.le.objectMgr
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,96 +0,0 @@
|
|||
__all__ = ["DWBPackageInstaller"]
|
||||
|
||||
from direct.p3d.PackageInstaller import PackageInstaller
|
||||
from direct.gui.DirectWaitBar import DirectWaitBar
|
||||
from direct.gui import DirectGuiGlobals as DGG
|
||||
|
||||
class DWBPackageInstaller(DirectWaitBar, PackageInstaller):
|
||||
""" This class presents a PackageInstaller that also inherits from
|
||||
DirectWaitBar, so it updates its own GUI as it downloads.
|
||||
|
||||
Specify perPackage = True to make the progress bar reset for each
|
||||
package, or False (the default) to show one continuous progress
|
||||
bar for all packages.
|
||||
|
||||
Specify updateText = True (the default) to update the text label
|
||||
with the name of the package or False to leave it up to you to set
|
||||
it.
|
||||
|
||||
You can specify a callback function with finished = func; this
|
||||
function will be called, with one boolean parameter, when the
|
||||
download has completed. The parameter will be true on success, or
|
||||
false on failure.
|
||||
"""
|
||||
|
||||
def __init__(self, appRunner, parent = None, **kw):
|
||||
PackageInstaller.__init__(self, appRunner)
|
||||
|
||||
optiondefs = (
|
||||
('borderWidth', (0.01, 0.01), None),
|
||||
('relief', DGG.SUNKEN, self.setRelief),
|
||||
('range', 1, self.setRange),
|
||||
('barBorderWidth', (0.01, 0.01), self.setBarBorderWidth),
|
||||
('barColor', (0.424, 0.647, 0.878, 1), self.setBarColor),
|
||||
('barRelief', DGG.RAISED, self.setBarRelief),
|
||||
('text', 'Starting', self.setText),
|
||||
('text_pos', (0, -0.025), None),
|
||||
('text_scale', 0.1, None),
|
||||
('perPackage', False, None),
|
||||
('updateText', True, None),
|
||||
('finished', None, None),
|
||||
)
|
||||
self.defineoptions(kw, optiondefs)
|
||||
DirectWaitBar.__init__(self, parent, **kw)
|
||||
self.initialiseoptions(DWBPackageInstaller)
|
||||
self.updateBarStyle()
|
||||
|
||||
# Hidden by default until the download begins.
|
||||
self.hide()
|
||||
|
||||
def cleanup(self):
|
||||
PackageInstaller.cleanup(self)
|
||||
DirectWaitBar.destroy(self)
|
||||
|
||||
def destroy(self):
|
||||
PackageInstaller.cleanup(self)
|
||||
DirectWaitBar.destroy(self)
|
||||
|
||||
def packageStarted(self, package):
|
||||
""" This callback is made for each package between
|
||||
downloadStarted() and downloadFinished() to indicate the start
|
||||
of a new package. """
|
||||
if self['updateText']:
|
||||
self['text'] = 'Installing %s' % (package.getFormattedName())
|
||||
self.show()
|
||||
|
||||
def packageProgress(self, package, progress):
|
||||
""" This callback is made repeatedly between packageStarted()
|
||||
and packageFinished() to update the current progress on the
|
||||
indicated package only. The progress value ranges from 0
|
||||
(beginning) to 1 (complete). """
|
||||
|
||||
if self['perPackage']:
|
||||
self['value'] = progress * self['range']
|
||||
|
||||
def downloadProgress(self, overallProgress):
|
||||
""" This callback is made repeatedly between downloadStarted()
|
||||
and downloadFinished() to update the current progress through
|
||||
all packages. The progress value ranges from 0 (beginning) to
|
||||
1 (complete). """
|
||||
|
||||
if not self['perPackage']:
|
||||
self['value'] = overallProgress * self['range']
|
||||
|
||||
def downloadFinished(self, success):
|
||||
""" This callback is made when all of the packages have been
|
||||
downloaded and installed (or there has been some failure). If
|
||||
all packages where successfully installed, success is True.
|
||||
|
||||
If there were no packages that required downloading, this
|
||||
callback will be made immediately, *without* a corresponding
|
||||
call to downloadStarted(). """
|
||||
|
||||
self.hide()
|
||||
|
||||
if self['finished']:
|
||||
self['finished'](success)
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,246 +0,0 @@
|
|||
__all__ = ["FileSpec"]
|
||||
|
||||
import os
|
||||
import time
|
||||
from panda3d.core import Filename, HashVal, VirtualFileSystem
|
||||
|
||||
class FileSpec:
|
||||
""" This class represents a disk file whose hash and size
|
||||
etc. were read from an xml file. This class provides methods to
|
||||
verify whether the file on disk matches the version demanded by
|
||||
the xml. """
|
||||
|
||||
def __init__(self):
|
||||
self.actualFile = None
|
||||
self.filename = None
|
||||
self.size = 0
|
||||
self.timestamp = 0
|
||||
self.hash = None
|
||||
|
||||
def fromFile(self, packageDir, filename, pathname = None, st = None):
|
||||
""" Reads the file information from the indicated file. If st
|
||||
is supplied, it is the result of os.stat on the filename. """
|
||||
|
||||
vfs = VirtualFileSystem.getGlobalPtr()
|
||||
|
||||
filename = Filename(filename)
|
||||
if pathname is None:
|
||||
pathname = Filename(packageDir, filename)
|
||||
|
||||
self.filename = str(filename)
|
||||
self.basename = filename.getBasename()
|
||||
|
||||
if st is None:
|
||||
st = os.stat(pathname.toOsSpecific())
|
||||
self.size = st.st_size
|
||||
self.timestamp = int(st.st_mtime)
|
||||
|
||||
self.readHash(pathname)
|
||||
|
||||
def readHash(self, pathname):
|
||||
""" Reads the hash only from the indicated pathname. """
|
||||
|
||||
hv = HashVal()
|
||||
hv.hashFile(pathname)
|
||||
self.hash = hv.asHex()
|
||||
|
||||
|
||||
def loadXml(self, xelement):
|
||||
""" Reads the file information from the indicated XML
|
||||
element. """
|
||||
|
||||
self.filename = xelement.Attribute('filename')
|
||||
self.basename = None
|
||||
if self.filename:
|
||||
self.basename = Filename(self.filename).getBasename()
|
||||
|
||||
size = xelement.Attribute('size')
|
||||
try:
|
||||
self.size = int(size)
|
||||
except:
|
||||
self.size = 0
|
||||
|
||||
timestamp = xelement.Attribute('timestamp')
|
||||
try:
|
||||
self.timestamp = int(timestamp)
|
||||
except:
|
||||
self.timestamp = 0
|
||||
|
||||
self.hash = xelement.Attribute('hash')
|
||||
|
||||
def storeXml(self, xelement):
|
||||
""" Adds the file information to the indicated XML
|
||||
element. """
|
||||
|
||||
if self.filename:
|
||||
xelement.SetAttribute('filename', self.filename)
|
||||
if self.size:
|
||||
xelement.SetAttribute('size', str(self.size))
|
||||
if self.timestamp:
|
||||
xelement.SetAttribute('timestamp', str(int(self.timestamp)))
|
||||
if self.hash:
|
||||
xelement.SetAttribute('hash', self.hash)
|
||||
|
||||
def storeMiniXml(self, xelement):
|
||||
""" Adds the just the "mini" file information--size and
|
||||
hash--to the indicated XML element. """
|
||||
|
||||
if self.size:
|
||||
xelement.SetAttribute('size', str(self.size))
|
||||
if self.hash:
|
||||
xelement.SetAttribute('hash', self.hash)
|
||||
|
||||
def quickVerify(self, packageDir = None, pathname = None,
|
||||
notify = None, correctSelf = False):
|
||||
""" Performs a quick test to ensure the file has not been
|
||||
modified. This test is vulnerable to people maliciously
|
||||
attempting to fool the program (by setting datestamps etc.).
|
||||
|
||||
if correctSelf is True, then any discrepency is corrected by
|
||||
updating the appropriate fields internally, making the
|
||||
assumption that the file on disk is the authoritative version.
|
||||
|
||||
Returns true if it is intact, false if it is incorrect. If
|
||||
correctSelf is true, raises OSError if the self-update is
|
||||
impossible (for instance, because the file does not exist)."""
|
||||
|
||||
if not pathname:
|
||||
pathname = Filename(packageDir, self.filename)
|
||||
try:
|
||||
st = os.stat(pathname.toOsSpecific())
|
||||
except OSError:
|
||||
# If the file is missing, the file fails.
|
||||
if notify:
|
||||
notify.debug("file not found: %s" % (pathname))
|
||||
if correctSelf:
|
||||
raise
|
||||
return False
|
||||
|
||||
if st.st_size != self.size:
|
||||
# If the size is wrong, the file fails.
|
||||
if notify:
|
||||
notify.debug("size wrong: %s" % (pathname))
|
||||
if correctSelf:
|
||||
self.__correctHash(packageDir, pathname, st, notify)
|
||||
return False
|
||||
|
||||
if int(st.st_mtime) == self.timestamp:
|
||||
# If the size is right and the timestamp is right, the
|
||||
# file passes.
|
||||
if notify:
|
||||
notify.debug("file ok: %s" % (pathname))
|
||||
return True
|
||||
|
||||
if notify:
|
||||
notify.debug("modification time wrong: %s" % (pathname))
|
||||
|
||||
# If the size is right but the timestamp is wrong, the file
|
||||
# soft-fails. We follow this up with a hash check.
|
||||
if not self.checkHash(packageDir, pathname, st):
|
||||
# Hard fail, the hash is wrong.
|
||||
if notify:
|
||||
notify.debug("hash check wrong: %s" % (pathname))
|
||||
notify.debug(" found %s, expected %s" % (self.actualFile.hash, self.hash))
|
||||
if correctSelf:
|
||||
self.__correctHash(packageDir, pathname, st, notify)
|
||||
return False
|
||||
|
||||
if notify:
|
||||
notify.debug("hash check ok: %s" % (pathname))
|
||||
|
||||
# The hash is OK after all. Change the file's timestamp back
|
||||
# to what we expect it to be, so we can quick-verify it
|
||||
# successfully next time.
|
||||
if correctSelf:
|
||||
# Or update our own timestamp.
|
||||
self.__correctTimestamp(pathname, st, notify)
|
||||
return False
|
||||
else:
|
||||
self.__updateTimestamp(pathname, st)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def fullVerify(self, packageDir = None, pathname = None, notify = None):
|
||||
""" Performs a more thorough test to ensure the file has not
|
||||
been modified. This test is less vulnerable to malicious
|
||||
attacks, since it reads and verifies the entire file.
|
||||
|
||||
Returns true if it is intact, false if it needs to be
|
||||
redownloaded. """
|
||||
|
||||
if not pathname:
|
||||
pathname = Filename(packageDir, self.filename)
|
||||
try:
|
||||
st = os.stat(pathname.toOsSpecific())
|
||||
except OSError:
|
||||
# If the file is missing, the file fails.
|
||||
if notify:
|
||||
notify.debug("file not found: %s" % (pathname))
|
||||
return False
|
||||
|
||||
if st.st_size != self.size:
|
||||
# If the size is wrong, the file fails;
|
||||
if notify:
|
||||
notify.debug("size wrong: %s" % (pathname))
|
||||
return False
|
||||
|
||||
if not self.checkHash(packageDir, pathname, st):
|
||||
# Hard fail, the hash is wrong.
|
||||
if notify:
|
||||
notify.debug("hash check wrong: %s" % (pathname))
|
||||
notify.debug(" found %s, expected %s" % (self.actualFile.hash, self.hash))
|
||||
return False
|
||||
|
||||
if notify:
|
||||
notify.debug("hash check ok: %s" % (pathname))
|
||||
|
||||
# The hash is OK. If the timestamp is wrong, change it back
|
||||
# to what we expect it to be, so we can quick-verify it
|
||||
# successfully next time.
|
||||
if int(st.st_mtime) != self.timestamp:
|
||||
self.__updateTimestamp(pathname, st)
|
||||
|
||||
return True
|
||||
|
||||
def __updateTimestamp(self, pathname, st):
|
||||
# On Windows, we have to change the file to read-write before
|
||||
# we can successfully update its timestamp.
|
||||
try:
|
||||
os.chmod(pathname.toOsSpecific(), 0o755)
|
||||
os.utime(pathname.toOsSpecific(), (st.st_atime, self.timestamp))
|
||||
os.chmod(pathname.toOsSpecific(), 0o555)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
def __correctTimestamp(self, pathname, st, notify):
|
||||
""" Corrects the internal timestamp to match the one on
|
||||
disk. """
|
||||
if notify:
|
||||
notify.info("Correcting timestamp of %s to %d (%s)" % (
|
||||
self.filename, st.st_mtime, time.asctime(time.localtime(st.st_mtime))))
|
||||
self.timestamp = int(st.st_mtime)
|
||||
|
||||
def checkHash(self, packageDir, pathname, st):
|
||||
""" Returns true if the file has the expected md5 hash, false
|
||||
otherwise. As a side effect, stores a FileSpec corresponding
|
||||
to the on-disk file in self.actualFile. """
|
||||
|
||||
fileSpec = FileSpec()
|
||||
fileSpec.fromFile(packageDir, self.filename,
|
||||
pathname = pathname, st = st)
|
||||
self.actualFile = fileSpec
|
||||
|
||||
return (fileSpec.hash == self.hash)
|
||||
|
||||
def __correctHash(self, packageDir, pathname, st, notify):
|
||||
""" Corrects the internal hash to match the one on disk. """
|
||||
if not self.actualFile:
|
||||
self.checkHash(packageDir, pathname, st)
|
||||
|
||||
if notify:
|
||||
notify.info("Correcting hash %s to %s" % (
|
||||
self.filename, self.actualFile.hash))
|
||||
self.hash = self.actualFile.hash
|
||||
self.size = self.actualFile.size
|
||||
self.timestamp = self.actualFile.timestamp
|
||||
|
|
@ -1,751 +0,0 @@
|
|||
__all__ = ["HostInfo"]
|
||||
|
||||
from panda3d.core import HashVal, Filename, PandaSystem, DocumentSpec, Ramfile
|
||||
from panda3d.core import HTTPChannel, ConfigVariableInt
|
||||
from panda3d import core
|
||||
from direct.p3d.PackageInfo import PackageInfo
|
||||
from direct.p3d.FileSpec import FileSpec
|
||||
from direct.directnotify.DirectNotifyGlobal import directNotify
|
||||
import time
|
||||
|
||||
class HostInfo:
|
||||
""" This class represents a particular download host serving up
|
||||
Panda3D packages. It is the Python equivalent of the P3DHost
|
||||
class in the core API. """
|
||||
|
||||
notify = directNotify.newCategory("HostInfo")
|
||||
|
||||
def __init__(self, hostUrl, appRunner = None, hostDir = None,
|
||||
rootDir = None, asMirror = False, perPlatform = None):
|
||||
|
||||
""" You must specify either an appRunner or a hostDir to the
|
||||
HostInfo constructor.
|
||||
|
||||
If you pass asMirror = True, it means that this HostInfo
|
||||
object is to be used to populate a "mirror" folder, a
|
||||
duplicate (or subset) of the contents hosted by a server.
|
||||
This means when you use this HostInfo to download packages, it
|
||||
will only download the compressed archive file and leave it
|
||||
there. At the moment, mirror folders do not download old
|
||||
patch files from the server.
|
||||
|
||||
If you pass perPlatform = True, then files are unpacked into a
|
||||
platform-specific directory, which is appropriate when you
|
||||
might be downloading multiple platforms. The default is
|
||||
perPlatform = False, which means all files are unpacked into
|
||||
the host directory directly, without an intervening
|
||||
platform-specific directory name. If asMirror is True, then
|
||||
the default is perPlatform = True.
|
||||
|
||||
Note that perPlatform is also restricted by the individual
|
||||
package's specification. """
|
||||
|
||||
self.__setHostUrl(hostUrl)
|
||||
self.appRunner = appRunner
|
||||
self.rootDir = rootDir
|
||||
if rootDir is None and appRunner:
|
||||
self.rootDir = appRunner.rootDir
|
||||
|
||||
if hostDir and not isinstance(hostDir, Filename):
|
||||
hostDir = Filename.fromOsSpecific(hostDir)
|
||||
|
||||
self.hostDir = hostDir
|
||||
self.asMirror = asMirror
|
||||
self.perPlatform = perPlatform
|
||||
if perPlatform is None:
|
||||
self.perPlatform = asMirror
|
||||
|
||||
# Initially false, this is set true when the contents file is
|
||||
# successfully read.
|
||||
self.hasContentsFile = False
|
||||
|
||||
# This is the time value at which the current contents file is
|
||||
# no longer valid.
|
||||
self.contentsExpiration = 0
|
||||
|
||||
# Contains the md5 hash of the original contents.xml file.
|
||||
self.contentsSpec = FileSpec()
|
||||
|
||||
# descriptiveName will be filled in later, when the
|
||||
# contents file is read.
|
||||
self.descriptiveName = None
|
||||
|
||||
# A list of known mirrors for this host, all URL's guaranteed
|
||||
# to end with a slash.
|
||||
self.mirrors = []
|
||||
|
||||
# A map of keyword -> altHost URL's. An altHost is different
|
||||
# than a mirror; an altHost is an alternate URL to download a
|
||||
# different (e.g. testing) version of this host's contents.
|
||||
# It is rarely used.
|
||||
self.altHosts = {}
|
||||
|
||||
# This is a dictionary of packages by (name, version). It
|
||||
# will be filled in when the contents file is read.
|
||||
self.packages = {}
|
||||
|
||||
if self.appRunner and self.appRunner.verifyContents != self.appRunner.P3DVCForce:
|
||||
# Attempt to pre-read the existing contents.xml; maybe it
|
||||
# will be current enough for our purposes.
|
||||
self.readContentsFile()
|
||||
|
||||
def __setHostUrl(self, hostUrl):
|
||||
""" Assigns self.hostUrl, and related values. """
|
||||
self.hostUrl = hostUrl
|
||||
|
||||
if not self.hostUrl:
|
||||
# A special case: the URL will be set later.
|
||||
self.hostUrlPrefix = None
|
||||
self.downloadUrlPrefix = None
|
||||
else:
|
||||
# hostUrlPrefix is the host URL, but it is guaranteed to end
|
||||
# with a slash.
|
||||
self.hostUrlPrefix = hostUrl
|
||||
if self.hostUrlPrefix[-1] != '/':
|
||||
self.hostUrlPrefix += '/'
|
||||
|
||||
# downloadUrlPrefix is the URL prefix that should be used for
|
||||
# everything other than the contents.xml file. It might be
|
||||
# the same as hostUrlPrefix, but in the case of an
|
||||
# https-protected hostUrl, it will be the cleartext channel.
|
||||
self.downloadUrlPrefix = self.hostUrlPrefix
|
||||
|
||||
def freshenFile(self, http, fileSpec, localPathname):
|
||||
""" Ensures that the localPathname is the most current version
|
||||
of the file defined by fileSpec, as offered by host. If not,
|
||||
it downloads a new version on-the-spot. Returns true on
|
||||
success, false on failure. """
|
||||
|
||||
if fileSpec.quickVerify(pathname = localPathname):
|
||||
# It's good, keep it.
|
||||
return True
|
||||
|
||||
# It's stale, get a new one.
|
||||
doc = None
|
||||
if self.appRunner and self.appRunner.superMirrorUrl:
|
||||
# Use the "super mirror" first.
|
||||
url = core.URLSpec(self.appRunner.superMirrorUrl + fileSpec.filename)
|
||||
self.notify.info("Freshening %s" % (url))
|
||||
doc = http.getDocument(url)
|
||||
|
||||
if not doc or not doc.isValid():
|
||||
# Failing the super mirror, contact the actual host.
|
||||
url = core.URLSpec(self.hostUrlPrefix + fileSpec.filename)
|
||||
self.notify.info("Freshening %s" % (url))
|
||||
doc = http.getDocument(url)
|
||||
if not doc.isValid():
|
||||
return False
|
||||
|
||||
file = Filename.temporary('', 'p3d_')
|
||||
if not doc.downloadToFile(file):
|
||||
# Failed to download.
|
||||
file.unlink()
|
||||
return False
|
||||
|
||||
# Successfully downloaded!
|
||||
localPathname.makeDir()
|
||||
if not file.renameTo(localPathname):
|
||||
# Couldn't move it into place.
|
||||
file.unlink()
|
||||
return False
|
||||
|
||||
if not fileSpec.fullVerify(pathname = localPathname, notify = self.notify):
|
||||
# No good after download.
|
||||
self.notify.info("%s is still no good after downloading." % (url))
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def downloadContentsFile(self, http, redownload = False,
|
||||
hashVal = None):
|
||||
""" Downloads the contents.xml file for this particular host,
|
||||
synchronously, and then reads it. Returns true on success,
|
||||
false on failure. If hashVal is not None, it should be a
|
||||
HashVal object, which will be filled with the hash from the
|
||||
new contents.xml file."""
|
||||
|
||||
if self.hasCurrentContentsFile():
|
||||
# We've already got one.
|
||||
return True
|
||||
|
||||
if self.appRunner and self.appRunner.verifyContents == self.appRunner.P3DVCNever:
|
||||
# Not allowed to.
|
||||
return False
|
||||
|
||||
rf = None
|
||||
if http:
|
||||
if not redownload and self.appRunner and self.appRunner.superMirrorUrl:
|
||||
# We start with the "super mirror", if it's defined.
|
||||
url = self.appRunner.superMirrorUrl + 'contents.xml'
|
||||
request = DocumentSpec(url)
|
||||
self.notify.info("Downloading contents file %s" % (request))
|
||||
|
||||
rf = Ramfile()
|
||||
channel = http.makeChannel(False)
|
||||
channel.getDocument(request)
|
||||
if not channel.downloadToRam(rf):
|
||||
self.notify.warning("Unable to download %s" % (url))
|
||||
rf = None
|
||||
|
||||
if not rf:
|
||||
# Then go to the main host, if our super mirror let us
|
||||
# down.
|
||||
|
||||
url = self.hostUrlPrefix + 'contents.xml'
|
||||
# Append a uniquifying query string to the URL to force the
|
||||
# download to go all the way through any caches. We use the
|
||||
# time in seconds; that's unique enough.
|
||||
url += '?' + str(int(time.time()))
|
||||
|
||||
# We might as well explicitly request the cache to be disabled
|
||||
# too, since we have an interface for that via HTTPChannel.
|
||||
request = DocumentSpec(url)
|
||||
request.setCacheControl(DocumentSpec.CCNoCache)
|
||||
|
||||
self.notify.info("Downloading contents file %s" % (request))
|
||||
statusCode = None
|
||||
statusString = ''
|
||||
for attempt in range(int(ConfigVariableInt('contents-xml-dl-attempts', 3))):
|
||||
if attempt > 0:
|
||||
self.notify.info("Retrying (%s)..."%(attempt,))
|
||||
rf = Ramfile()
|
||||
channel = http.makeChannel(False)
|
||||
channel.getDocument(request)
|
||||
if channel.downloadToRam(rf):
|
||||
self.notify.info("Successfully downloaded %s" % (url,))
|
||||
break
|
||||
else:
|
||||
rf = None
|
||||
statusCode = channel.getStatusCode()
|
||||
statusString = channel.getStatusString()
|
||||
self.notify.warning("Could not contact download server at %s" % (url,))
|
||||
self.notify.warning("Status code = %s %s" % (statusCode, statusString))
|
||||
|
||||
if not rf:
|
||||
self.notify.warning("Unable to download %s" % (url,))
|
||||
try:
|
||||
# Something screwed up.
|
||||
if statusCode == HTTPChannel.SCDownloadOpenError or \
|
||||
statusCode == HTTPChannel.SCDownloadWriteError:
|
||||
launcher.setPandaErrorCode(2)
|
||||
elif statusCode == 404:
|
||||
# 404 not found
|
||||
launcher.setPandaErrorCode(5)
|
||||
elif statusCode < 100:
|
||||
# statusCode < 100 implies the connection attempt itself
|
||||
# failed. This is usually due to firewall software
|
||||
# interfering. Apparently some firewall software might
|
||||
# allow the first connection and disallow subsequent
|
||||
# connections; how strange.
|
||||
launcher.setPandaErrorCode(4)
|
||||
else:
|
||||
# There are other kinds of failures, but these will
|
||||
# generally have been caught already by the first test; so
|
||||
# if we get here there may be some bigger problem. Just
|
||||
# give the generic "big problem" message.
|
||||
launcher.setPandaErrorCode(6)
|
||||
except NameError as e:
|
||||
# no launcher
|
||||
pass
|
||||
except AttributeError as e:
|
||||
self.notify.warning("%s" % (str(e),))
|
||||
pass
|
||||
return False
|
||||
|
||||
tempFilename = Filename.temporary('', 'p3d_', '.xml')
|
||||
if rf:
|
||||
f = open(tempFilename.toOsSpecific(), 'wb')
|
||||
f.write(rf.getData())
|
||||
f.close()
|
||||
if hashVal:
|
||||
hashVal.hashString(rf.getData())
|
||||
|
||||
if not self.readContentsFile(tempFilename, freshDownload = True):
|
||||
self.notify.warning("Failure reading %s" % (url))
|
||||
tempFilename.unlink()
|
||||
return False
|
||||
|
||||
tempFilename.unlink()
|
||||
return True
|
||||
|
||||
# Couldn't download the file. Maybe we should look for a
|
||||
# previously-downloaded copy already on disk?
|
||||
return False
|
||||
|
||||
def redownloadContentsFile(self, http):
|
||||
""" Downloads a new contents.xml file in case it has changed.
|
||||
Returns true if the file has indeed changed, false if it has
|
||||
not. """
|
||||
assert self.hasContentsFile
|
||||
|
||||
if self.appRunner and self.appRunner.verifyContents == self.appRunner.P3DVCNever:
|
||||
# Not allowed to.
|
||||
return False
|
||||
|
||||
url = self.hostUrlPrefix + 'contents.xml'
|
||||
self.notify.info("Redownloading %s" % (url))
|
||||
|
||||
# Get the hash of the original file.
|
||||
assert self.hostDir
|
||||
hv1 = HashVal()
|
||||
if self.contentsSpec.hash:
|
||||
hv1.setFromHex(self.contentsSpec.hash)
|
||||
else:
|
||||
filename = Filename(self.hostDir, 'contents.xml')
|
||||
hv1.hashFile(filename)
|
||||
|
||||
# Now download it again.
|
||||
self.hasContentsFile = False
|
||||
hv2 = HashVal()
|
||||
if not self.downloadContentsFile(http, redownload = True,
|
||||
hashVal = hv2):
|
||||
return False
|
||||
|
||||
if hv2 == HashVal():
|
||||
self.notify.info("%s didn't actually redownload." % (url))
|
||||
return False
|
||||
elif hv1 != hv2:
|
||||
self.notify.info("%s has changed." % (url))
|
||||
return True
|
||||
else:
|
||||
self.notify.info("%s has not changed." % (url))
|
||||
return False
|
||||
|
||||
def hasCurrentContentsFile(self):
|
||||
""" Returns true if a contents.xml file has been successfully
|
||||
read for this host and is still current, false otherwise. """
|
||||
if not self.appRunner \
|
||||
or self.appRunner.verifyContents == self.appRunner.P3DVCNone \
|
||||
or self.appRunner.verifyContents == self.appRunner.P3DVCNever:
|
||||
# If we're not asking to verify contents, then
|
||||
# contents.xml files never expires.
|
||||
return self.hasContentsFile
|
||||
|
||||
now = int(time.time())
|
||||
return now < self.contentsExpiration and self.hasContentsFile
|
||||
|
||||
def readContentsFile(self, tempFilename = None, freshDownload = False):
|
||||
""" Reads the contents.xml file for this particular host, once
|
||||
it has been downloaded into the indicated temporary file.
|
||||
Returns true on success, false if the contents file is not
|
||||
already on disk or is unreadable.
|
||||
|
||||
If tempFilename is specified, it is the filename read, and it
|
||||
is copied the file into the standard location if it's not
|
||||
there already. If tempFilename is not specified, the standard
|
||||
filename is read if it is known. """
|
||||
|
||||
if not hasattr(core, 'TiXmlDocument'):
|
||||
return False
|
||||
|
||||
if not tempFilename:
|
||||
if self.hostDir:
|
||||
# If the filename is not specified, we can infer it
|
||||
# if we already know our hostDir
|
||||
hostDir = self.hostDir
|
||||
else:
|
||||
# Otherwise, we have to guess the hostDir.
|
||||
hostDir = self.__determineHostDir(None, self.hostUrl)
|
||||
|
||||
tempFilename = Filename(hostDir, 'contents.xml')
|
||||
|
||||
doc = core.TiXmlDocument(tempFilename.toOsSpecific())
|
||||
if not doc.LoadFile():
|
||||
return False
|
||||
|
||||
xcontents = doc.FirstChildElement('contents')
|
||||
if not xcontents:
|
||||
return False
|
||||
|
||||
maxAge = xcontents.Attribute('max_age')
|
||||
if maxAge:
|
||||
try:
|
||||
maxAge = int(maxAge)
|
||||
except:
|
||||
maxAge = None
|
||||
if maxAge is None:
|
||||
# Default max_age if unspecified (see p3d_plugin.h).
|
||||
from direct.p3d.AppRunner import AppRunner
|
||||
maxAge = AppRunner.P3D_CONTENTS_DEFAULT_MAX_AGE
|
||||
|
||||
# Get the latest possible expiration time, based on the max_age
|
||||
# indication. Any expiration time later than this is in error.
|
||||
now = int(time.time())
|
||||
self.contentsExpiration = now + maxAge
|
||||
|
||||
if freshDownload:
|
||||
self.contentsSpec.readHash(tempFilename)
|
||||
|
||||
# Update the XML with the new download information.
|
||||
xorig = xcontents.FirstChildElement('orig')
|
||||
while xorig:
|
||||
xcontents.RemoveChild(xorig)
|
||||
xorig = xcontents.FirstChildElement('orig')
|
||||
|
||||
xorig = core.TiXmlElement('orig')
|
||||
self.contentsSpec.storeXml(xorig)
|
||||
xorig.SetAttribute('expiration', str(self.contentsExpiration))
|
||||
|
||||
xcontents.InsertEndChild(xorig)
|
||||
|
||||
else:
|
||||
# Read the download hash and expiration time from the XML.
|
||||
expiration = None
|
||||
xorig = xcontents.FirstChildElement('orig')
|
||||
if xorig:
|
||||
self.contentsSpec.loadXml(xorig)
|
||||
expiration = xorig.Attribute('expiration')
|
||||
if expiration:
|
||||
try:
|
||||
expiration = int(expiration)
|
||||
except:
|
||||
expiration = None
|
||||
if not self.contentsSpec.hash:
|
||||
self.contentsSpec.readHash(tempFilename)
|
||||
|
||||
if expiration is not None:
|
||||
self.contentsExpiration = min(self.contentsExpiration, expiration)
|
||||
|
||||
# Look for our own entry in the hosts table.
|
||||
if self.hostUrl:
|
||||
self.__findHostXml(xcontents)
|
||||
else:
|
||||
assert self.hostDir
|
||||
self.__findHostXmlForHostDir(xcontents)
|
||||
|
||||
if self.rootDir and not self.hostDir:
|
||||
self.hostDir = self.__determineHostDir(None, self.hostUrl)
|
||||
|
||||
# Get the list of packages available for download and/or import.
|
||||
xpackage = xcontents.FirstChildElement('package')
|
||||
while xpackage:
|
||||
name = xpackage.Attribute('name')
|
||||
platform = xpackage.Attribute('platform')
|
||||
version = xpackage.Attribute('version')
|
||||
try:
|
||||
solo = int(xpackage.Attribute('solo') or '')
|
||||
except ValueError:
|
||||
solo = False
|
||||
try:
|
||||
perPlatform = int(xpackage.Attribute('per_platform') or '')
|
||||
except ValueError:
|
||||
perPlatform = False
|
||||
|
||||
package = self.__makePackage(name, platform, version, solo, perPlatform)
|
||||
package.descFile = FileSpec()
|
||||
package.descFile.loadXml(xpackage)
|
||||
package.setupFilenames()
|
||||
|
||||
package.importDescFile = None
|
||||
ximport = xpackage.FirstChildElement('import')
|
||||
if ximport:
|
||||
package.importDescFile = FileSpec()
|
||||
package.importDescFile.loadXml(ximport)
|
||||
|
||||
xpackage = xpackage.NextSiblingElement('package')
|
||||
|
||||
self.hasContentsFile = True
|
||||
|
||||
# Now save the contents.xml file into the standard location.
|
||||
if self.appRunner and self.appRunner.verifyContents != self.appRunner.P3DVCNever:
|
||||
assert self.hostDir
|
||||
filename = Filename(self.hostDir, 'contents.xml')
|
||||
filename.makeDir()
|
||||
if freshDownload:
|
||||
doc.SaveFile(filename.toOsSpecific())
|
||||
else:
|
||||
if filename != tempFilename:
|
||||
tempFilename.copyTo(filename)
|
||||
|
||||
return True
|
||||
|
||||
def __findHostXml(self, xcontents):
|
||||
""" Looks for the <host> or <alt_host> entry in the
|
||||
contents.xml that corresponds to the URL that we actually
|
||||
downloaded from. """
|
||||
|
||||
xhost = xcontents.FirstChildElement('host')
|
||||
while xhost:
|
||||
url = xhost.Attribute('url')
|
||||
if url == self.hostUrl:
|
||||
self.readHostXml(xhost)
|
||||
return
|
||||
|
||||
xalthost = xhost.FirstChildElement('alt_host')
|
||||
while xalthost:
|
||||
url = xalthost.Attribute('url')
|
||||
if url == self.hostUrl:
|
||||
self.readHostXml(xalthost)
|
||||
return
|
||||
xalthost = xalthost.NextSiblingElement('alt_host')
|
||||
|
||||
xhost = xhost.NextSiblingElement('host')
|
||||
|
||||
def __findHostXmlForHostDir(self, xcontents):
|
||||
""" Looks for the <host> or <alt_host> entry in the
|
||||
contents.xml that corresponds to the host dir that we read the
|
||||
contents.xml from. This is used when reading a contents.xml
|
||||
file found on disk, as opposed to downloading it from a
|
||||
site. """
|
||||
|
||||
xhost = xcontents.FirstChildElement('host')
|
||||
while xhost:
|
||||
url = xhost.Attribute('url')
|
||||
hostDirBasename = xhost.Attribute('host_dir')
|
||||
hostDir = self.__determineHostDir(hostDirBasename, url)
|
||||
if hostDir == self.hostDir:
|
||||
self.__setHostUrl(url)
|
||||
self.readHostXml(xhost)
|
||||
return
|
||||
|
||||
xalthost = xhost.FirstChildElement('alt_host')
|
||||
while xalthost:
|
||||
url = xalthost.Attribute('url')
|
||||
hostDirBasename = xalthost.Attribute('host_dir')
|
||||
hostDir = self.__determineHostDir(hostDirBasename, url)
|
||||
if hostDir == self.hostDir:
|
||||
self.__setHostUrl(url)
|
||||
self.readHostXml(xalthost)
|
||||
return
|
||||
xalthost = xalthost.NextSiblingElement('alt_host')
|
||||
|
||||
xhost = xhost.NextSiblingElement('host')
|
||||
|
||||
def readHostXml(self, xhost):
|
||||
""" Reads a <host> or <alt_host> entry and applies the data to
|
||||
this object. """
|
||||
|
||||
descriptiveName = xhost.Attribute('descriptive_name')
|
||||
if descriptiveName and not self.descriptiveName:
|
||||
self.descriptiveName = descriptiveName
|
||||
|
||||
hostDirBasename = xhost.Attribute('host_dir')
|
||||
if self.rootDir and not self.hostDir:
|
||||
self.hostDir = self.__determineHostDir(hostDirBasename, self.hostUrl)
|
||||
|
||||
# Get the "download" URL, which is the source from which we
|
||||
# download everything other than the contents.xml file.
|
||||
downloadUrl = xhost.Attribute('download_url')
|
||||
if downloadUrl:
|
||||
self.downloadUrlPrefix = downloadUrl
|
||||
if self.downloadUrlPrefix[-1] != '/':
|
||||
self.downloadUrlPrefix += '/'
|
||||
else:
|
||||
self.downloadUrlPrefix = self.hostUrlPrefix
|
||||
|
||||
xmirror = xhost.FirstChildElement('mirror')
|
||||
while xmirror:
|
||||
url = xmirror.Attribute('url')
|
||||
if url:
|
||||
if url[-1] != '/':
|
||||
url += '/'
|
||||
if url not in self.mirrors:
|
||||
self.mirrors.append(url)
|
||||
xmirror = xmirror.NextSiblingElement('mirror')
|
||||
|
||||
xalthost = xhost.FirstChildElement('alt_host')
|
||||
while xalthost:
|
||||
keyword = xalthost.Attribute('keyword')
|
||||
url = xalthost.Attribute('url')
|
||||
if url and keyword:
|
||||
self.altHosts[keyword] = url
|
||||
xalthost = xalthost.NextSiblingElement('alt_host')
|
||||
|
||||
def __makePackage(self, name, platform, version, solo, perPlatform):
|
||||
""" Creates a new PackageInfo entry for the given name,
|
||||
version, and platform. If there is already a matching
|
||||
PackageInfo, returns it. """
|
||||
|
||||
if not platform:
|
||||
platform = None
|
||||
|
||||
platforms = self.packages.setdefault((name, version or ""), {})
|
||||
package = platforms.get("", None)
|
||||
if not package:
|
||||
package = PackageInfo(self, name, version, platform = platform,
|
||||
solo = solo, asMirror = self.asMirror,
|
||||
perPlatform = perPlatform)
|
||||
platforms[platform or ""] = package
|
||||
|
||||
return package
|
||||
|
||||
def getPackage(self, name, version, platform = None):
|
||||
""" Returns a PackageInfo that matches the indicated name and
|
||||
version and the indicated platform or the current runtime
|
||||
platform, if one is provided by this host, or None if not. """
|
||||
|
||||
assert self.hasContentsFile
|
||||
platforms = self.packages.get((name, version or ""), {})
|
||||
|
||||
if platform:
|
||||
# In this case, we are looking for a specific platform
|
||||
# only.
|
||||
return platforms.get(platform, None)
|
||||
|
||||
# We are looking for one matching the current runtime
|
||||
# platform. First, look for a package matching the current
|
||||
# platform exactly.
|
||||
package = platforms.get(PandaSystem.getPlatform(), None)
|
||||
|
||||
# If not found, look for one matching no particular platform.
|
||||
if not package:
|
||||
package = platforms.get("", None)
|
||||
|
||||
return package
|
||||
|
||||
def getPackages(self, name = None, platform = None):
|
||||
""" Returns a list of PackageInfo objects that match the
|
||||
indicated name and/or platform, with no particular regards to
|
||||
version. If name is None, all packages are returned. """
|
||||
|
||||
assert self.hasContentsFile
|
||||
|
||||
packages = []
|
||||
for (pn, version), platforms in self.packages.items():
|
||||
if name and pn != name:
|
||||
continue
|
||||
|
||||
if not platform:
|
||||
for p2 in platforms:
|
||||
package = self.getPackage(pn, version, platform = p2)
|
||||
if package:
|
||||
packages.append(package)
|
||||
else:
|
||||
package = self.getPackage(pn, version, platform = platform)
|
||||
if package:
|
||||
packages.append(package)
|
||||
|
||||
return packages
|
||||
|
||||
def getAllPackages(self, includeAllPlatforms = False):
|
||||
""" Returns a list of all available packages provided by this
|
||||
host. """
|
||||
|
||||
result = []
|
||||
|
||||
items = sorted(self.packages.items())
|
||||
for key, platforms in items:
|
||||
if self.perPlatform or includeAllPlatforms:
|
||||
# If we maintain a different answer per platform,
|
||||
# return all of them.
|
||||
pitems = sorted(platforms.items())
|
||||
for pkey, package in pitems:
|
||||
result.append(package)
|
||||
else:
|
||||
# If we maintain a host for the current platform
|
||||
# only (e.g. a client copy), then return only the
|
||||
# current platform, or no particular platform.
|
||||
package = platforms.get(PandaSystem.getPlatform(), None)
|
||||
if not package:
|
||||
package = platforms.get("", None)
|
||||
|
||||
if package:
|
||||
result.append(package)
|
||||
|
||||
return result
|
||||
|
||||
def deletePackages(self, packages):
|
||||
""" Removes all of the indicated packages from the disk,
|
||||
uninstalling them and deleting all of their files. The
|
||||
packages parameter must be a list of one or more PackageInfo
|
||||
objects, for instance as returned by getPackage(). Returns
|
||||
the list of packages that were NOT found. """
|
||||
|
||||
packages = packages[:]
|
||||
|
||||
for key, platforms in list(self.packages.items()):
|
||||
for platform, package in list(platforms.items()):
|
||||
if package in packages:
|
||||
self.__deletePackageFiles(package)
|
||||
del platforms[platform]
|
||||
packages.remove(package)
|
||||
|
||||
if not platforms:
|
||||
# If we've removed all the platforms for a given
|
||||
# package, remove the key from the toplevel map.
|
||||
del self.packages[key]
|
||||
|
||||
return packages
|
||||
|
||||
def __deletePackageFiles(self, package):
|
||||
""" Called by deletePackage(), this actually removes the files
|
||||
for the indicated package. """
|
||||
|
||||
if self.appRunner:
|
||||
self.notify.info("Deleting package %s: %s" % (package.packageName, package.getPackageDir()))
|
||||
self.appRunner.rmtree(package.getPackageDir())
|
||||
|
||||
self.appRunner.sendRequest('forget_package', self.hostUrl, package.packageName, package.packageVersion or '')
|
||||
|
||||
def __determineHostDir(self, hostDirBasename, hostUrl):
|
||||
""" Hashes the host URL into a (mostly) unique directory
|
||||
string, which will be the root of the host's install tree.
|
||||
Returns the resulting path, as a Filename.
|
||||
|
||||
This code is duplicated in C++, in
|
||||
P3DHost::determine_host_dir(). """
|
||||
|
||||
if hostDirBasename:
|
||||
# If the contents.xml specified a host_dir parameter, use
|
||||
# it.
|
||||
hostDir = str(self.rootDir) + '/hosts'
|
||||
for component in hostDirBasename.split('/'):
|
||||
if component:
|
||||
if component[0] == '.':
|
||||
# Forbid ".foo" or "..".
|
||||
component = 'x' + component
|
||||
hostDir += '/'
|
||||
hostDir += component
|
||||
return Filename(hostDir)
|
||||
|
||||
hostDir = 'hosts/'
|
||||
|
||||
# Look for a server name in the URL. Including this string in the
|
||||
# directory name makes it friendlier for people browsing the
|
||||
# directory.
|
||||
|
||||
# We could use URLSpec, but we do it by hand instead, to make
|
||||
# it more likely that our hash code will exactly match the
|
||||
# similar logic in P3DHost.
|
||||
p = hostUrl.find('://')
|
||||
hostname = ''
|
||||
if p != -1:
|
||||
start = p + 3
|
||||
end = hostUrl.find('/', start)
|
||||
# Now start .. end is something like "username@host:port".
|
||||
|
||||
at = hostUrl.find('@', start)
|
||||
if at != -1 and at < end:
|
||||
start = at + 1
|
||||
|
||||
colon = hostUrl.find(':', start)
|
||||
if colon != -1 and colon < end:
|
||||
end = colon
|
||||
|
||||
# Now start .. end is just the hostname.
|
||||
hostname = hostUrl[start : end]
|
||||
|
||||
# Now build a hash string of the whole URL. We'll use MD5 to
|
||||
# get a pretty good hash, with a minimum chance of collision.
|
||||
# Even if there is a hash collision, though, it's not the end
|
||||
# of the world; it just means that both hosts will dump their
|
||||
# packages into the same directory, and they'll fight over the
|
||||
# toplevel contents.xml file. Assuming they use different
|
||||
# version numbers (which should be safe since they have the
|
||||
# same hostname), there will be minimal redownloading.
|
||||
|
||||
hashSize = 16
|
||||
keepHash = hashSize
|
||||
if hostname:
|
||||
hostDir += hostname + '_'
|
||||
|
||||
# If we successfully got a hostname, we don't really need the
|
||||
# full hash. We'll keep half of it.
|
||||
keepHash = keepHash // 2
|
||||
|
||||
md = HashVal()
|
||||
md.hashString(hostUrl)
|
||||
hostDir += md.asHex()[:keepHash * 2]
|
||||
|
||||
hostDir = Filename(self.rootDir, hostDir)
|
||||
return hostDir
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
__all__ = ["InstalledHostData"]
|
||||
|
||||
from panda3d.core import URLSpec
|
||||
|
||||
class InstalledHostData:
|
||||
""" A list of instances of this class is returned by
|
||||
AppRunner.scanInstalledPackages(). Each of these corresponds to a
|
||||
particular host that has provided packages that have been
|
||||
installed on the local client. """
|
||||
|
||||
def __init__(self, host, dirnode):
|
||||
self.host = host
|
||||
self.pathname = dirnode.pathname
|
||||
self.totalSize = dirnode.getTotalSize()
|
||||
self.packages = []
|
||||
|
||||
if self.host:
|
||||
self.hostUrl = self.host.hostUrl
|
||||
self.descriptiveName = self.host.descriptiveName
|
||||
if not self.descriptiveName:
|
||||
self.descriptiveName = URLSpec(self.hostUrl).getServer()
|
||||
else:
|
||||
self.hostUrl = 'unknown'
|
||||
self.descriptiveName = 'unknown'
|
||||
|
|
@ -1,30 +0,0 @@
|
|||
__all__ = ["InstalledPackageData"]
|
||||
|
||||
class InstalledPackageData:
|
||||
""" A list of instances of this class is maintained by
|
||||
InstalledHostData (which is in turn returned by
|
||||
AppRunner.scanInstalledPackages()). Each of these corresponds to
|
||||
a particular package that has been installed on the local
|
||||
client. """
|
||||
|
||||
def __init__(self, package, dirnode):
|
||||
self.package = package
|
||||
self.pathname = dirnode.pathname
|
||||
self.totalSize = dirnode.getTotalSize()
|
||||
self.lastUse = None
|
||||
|
||||
if self.package:
|
||||
self.displayName = self.package.getFormattedName()
|
||||
xusage = self.package.getUsage()
|
||||
|
||||
if xusage:
|
||||
lastUse = xusage.Attribute('last_use')
|
||||
try:
|
||||
lastUse = int(lastUse or '')
|
||||
except ValueError:
|
||||
lastUse = None
|
||||
self.lastUse = lastUse
|
||||
|
||||
else:
|
||||
self.displayName = dirnode.pathname.getBasename()
|
||||
|
||||
|
|
@ -1,298 +0,0 @@
|
|||
""" This module defines some simple classes and instances which are
|
||||
useful when writing code that integrates with JavaScript, especially
|
||||
code that runs in a browser via the web plugin. """
|
||||
|
||||
__all__ = ["UndefinedObject", "Undefined", "ConcreteStruct", "BrowserObject", "MethodWrapper"]
|
||||
|
||||
class UndefinedObject:
|
||||
""" This is a special object that is returned by the browser to
|
||||
represent an "undefined" or "void" value, typically the value for
|
||||
an uninitialized variable or undefined property. It has no
|
||||
attributes, similar to None, but it is a slightly different
|
||||
concept in JavaScript. """
|
||||
|
||||
def __bool__(self):
|
||||
return False
|
||||
|
||||
__nonzero__ = __bool__ # Python 2
|
||||
|
||||
def __str__(self):
|
||||
return "Undefined"
|
||||
|
||||
# In fact, we normally always return this precise instance of the
|
||||
# UndefinedObject.
|
||||
Undefined = UndefinedObject()
|
||||
|
||||
class ConcreteStruct:
|
||||
""" Python objects that inherit from this class are passed to
|
||||
JavaScript as a concrete struct: a mapping from string -> value,
|
||||
with no methods, passed by value. This can be more optimal than
|
||||
traditional Python objects which are passed by reference,
|
||||
especially for small objects which might be repeatedly referenced
|
||||
on the JavaScript side. """
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def getConcreteProperties(self):
|
||||
""" Returns a list of 2-tuples of the (key, value) pairs that
|
||||
are to be passed to the concrete instance. By default, this
|
||||
returns all properties of the object. You can override this
|
||||
to restrict the set of properties that are uploaded. """
|
||||
|
||||
return list(self.__dict__.items())
|
||||
|
||||
class BrowserObject:
|
||||
""" This class provides the Python wrapper around some object that
|
||||
actually exists in the plugin host's namespace, e.g. a JavaScript
|
||||
or DOM object. """
|
||||
|
||||
def __init__(self, runner, objectId):
|
||||
self.__dict__['_BrowserObject__runner'] = runner
|
||||
self.__dict__['_BrowserObject__objectId'] = objectId
|
||||
|
||||
# This element is filled in by __getattr__; it connects
|
||||
# the object to its parent.
|
||||
self.__dict__['_BrowserObject__childObject'] = (None, None)
|
||||
|
||||
# This is a cache of method names to MethodWrapper objects in
|
||||
# the parent object.
|
||||
self.__dict__['_BrowserObject__methods'] = {}
|
||||
|
||||
def __del__(self):
|
||||
# When the BrowserObject destructs, tell the parent process it
|
||||
# doesn't need to keep around its corresponding P3D_object any
|
||||
# more.
|
||||
self.__runner.dropObject(self.__objectId)
|
||||
|
||||
def __cacheMethod(self, methodName):
|
||||
""" Stores a pointer to the named method on this object, so
|
||||
that the next time __getattr__ is called, it can retrieve the
|
||||
method wrapper without having to query the browser. This
|
||||
cache assumes that callable methods don't generally come and
|
||||
go on and object.
|
||||
|
||||
The return value is the MethodWrapper object. """
|
||||
|
||||
method = self.__methods.get(methodName, None)
|
||||
if method is None:
|
||||
method = MethodWrapper(self.__runner, self, methodName)
|
||||
self.__methods[methodName] = method
|
||||
return method
|
||||
|
||||
def __str__(self):
|
||||
return self.toString()
|
||||
|
||||
def __bool__(self):
|
||||
return True
|
||||
|
||||
__nonzero__ = __bool__ # Python 2
|
||||
|
||||
def __call__(self, *args, **kw):
|
||||
needsResponse = True
|
||||
if 'needsResponse' in kw:
|
||||
needsResponse = kw['needsResponse']
|
||||
del kw['needsResponse']
|
||||
if kw:
|
||||
raise ArgumentError('Keyword arguments not supported')
|
||||
|
||||
try:
|
||||
parentObj, attribName = self.__childObject
|
||||
if parentObj:
|
||||
# Call it as a method.
|
||||
if parentObj is self.__runner.dom and attribName == 'alert':
|
||||
# As a special hack, we don't wait for the return
|
||||
# value from the alert() call, since this is a
|
||||
# blocking call, and waiting for this could cause
|
||||
# problems.
|
||||
needsResponse = False
|
||||
|
||||
if parentObj is self.__runner.dom and attribName == 'eval' and len(args) == 1 and isinstance(args[0], str):
|
||||
# As another special hack, we make dom.eval() a
|
||||
# special case, and map it directly into an eval()
|
||||
# call. If the string begins with 'void ', we further
|
||||
# assume we're not waiting for a response.
|
||||
if args[0].startswith('void '):
|
||||
needsResponse = False
|
||||
result = self.__runner.scriptRequest('eval', parentObj, value = args[0], needsResponse = needsResponse)
|
||||
else:
|
||||
# This is a normal method call.
|
||||
try:
|
||||
result = self.__runner.scriptRequest('call', parentObj, propertyName = attribName, value = args, needsResponse = needsResponse)
|
||||
except EnvironmentError:
|
||||
# Problem on the call. Maybe no such method?
|
||||
raise AttributeError
|
||||
|
||||
# Hey, the method call appears to have succeeded.
|
||||
# Cache the method object on the parent so we won't
|
||||
# have to look up the method wrapper again next time.
|
||||
parentObj.__cacheMethod(attribName)
|
||||
|
||||
else:
|
||||
# Call it as a plain function.
|
||||
result = self.__runner.scriptRequest('call', self, value = args, needsResponse = needsResponse)
|
||||
except EnvironmentError:
|
||||
# Some odd problem on the call.
|
||||
raise TypeError
|
||||
|
||||
return result
|
||||
|
||||
def __getattr__(self, name):
|
||||
""" Remaps attempts to query an attribute, as in obj.attr,
|
||||
into the appropriate calls to query the actual browser object
|
||||
under the hood. """
|
||||
|
||||
# First check to see if there's a cached method wrapper from a
|
||||
# previous call.
|
||||
method = self.__methods.get(name, None)
|
||||
if method:
|
||||
return method
|
||||
|
||||
# No cache. Go query the browser for the desired value.
|
||||
try:
|
||||
value = self.__runner.scriptRequest('get_property', self,
|
||||
propertyName = name)
|
||||
except EnvironmentError:
|
||||
# Failed to retrieve the attribute. But maybe there's a
|
||||
# method instead?
|
||||
if self.__runner.scriptRequest('has_method', self, propertyName = name):
|
||||
# Yes, so create a method wrapper for it.
|
||||
return self.__cacheMethod(name)
|
||||
|
||||
raise AttributeError(name)
|
||||
|
||||
if isinstance(value, BrowserObject):
|
||||
# Fill in the parent object association, so __call__ can
|
||||
# properly call a method. (Javascript needs to know the
|
||||
# method container at the time of the call, and doesn't
|
||||
# store it on the function object.)
|
||||
value.__dict__['_BrowserObject__childObject'] = (self, name)
|
||||
|
||||
return value
|
||||
|
||||
def __setattr__(self, name, value):
|
||||
if name in self.__dict__:
|
||||
self.__dict__[name] = value
|
||||
return
|
||||
|
||||
result = self.__runner.scriptRequest('set_property', self,
|
||||
propertyName = name,
|
||||
value = value)
|
||||
if not result:
|
||||
raise AttributeError(name)
|
||||
|
||||
def __delattr__(self, name):
|
||||
if name in self.__dict__:
|
||||
del self.__dict__[name]
|
||||
return
|
||||
|
||||
result = self.__runner.scriptRequest('del_property', self,
|
||||
propertyName = name)
|
||||
if not result:
|
||||
raise AttributeError(name)
|
||||
|
||||
def __getitem__(self, key):
|
||||
""" Remaps attempts to query an attribute, as in obj['attr'],
|
||||
into the appropriate calls to query the actual browser object
|
||||
under the hood. Following the JavaScript convention, we treat
|
||||
obj['attr'] almost the same as obj.attr. """
|
||||
|
||||
try:
|
||||
value = self.__runner.scriptRequest('get_property', self,
|
||||
propertyName = str(key))
|
||||
except EnvironmentError:
|
||||
# Failed to retrieve the property. We return IndexError
|
||||
# for numeric keys so we can properly support Python's
|
||||
# iterators, but we return KeyError for string keys to
|
||||
# emulate mapping objects.
|
||||
if isinstance(key, str):
|
||||
raise KeyError(key)
|
||||
else:
|
||||
raise IndexError(key)
|
||||
|
||||
return value
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
result = self.__runner.scriptRequest('set_property', self,
|
||||
propertyName = str(key),
|
||||
value = value)
|
||||
if not result:
|
||||
if isinstance(key, str):
|
||||
raise KeyError(key)
|
||||
else:
|
||||
raise IndexError(key)
|
||||
|
||||
def __delitem__(self, key):
|
||||
result = self.__runner.scriptRequest('del_property', self,
|
||||
propertyName = str(key))
|
||||
if not result:
|
||||
if isinstance(key, str):
|
||||
raise KeyError(key)
|
||||
else:
|
||||
raise IndexError(key)
|
||||
|
||||
class MethodWrapper:
|
||||
""" This is a Python wrapper around a property of a BrowserObject
|
||||
that doesn't appear to be a first-class object in the Python
|
||||
sense, but is nonetheless a callable method. """
|
||||
|
||||
def __init__(self, runner, parentObj, objectId):
|
||||
self.__dict__['_MethodWrapper__runner'] = runner
|
||||
self.__dict__['_MethodWrapper__childObject'] = (parentObj, objectId)
|
||||
|
||||
def __str__(self):
|
||||
parentObj, attribName = self.__childObject
|
||||
return "%s.%s" % (parentObj, attribName)
|
||||
|
||||
def __bool__(self):
|
||||
return True
|
||||
|
||||
__nonzero__ = __bool__ # Python 2
|
||||
|
||||
def __call__(self, *args, **kw):
|
||||
needsResponse = True
|
||||
if 'needsResponse' in kw:
|
||||
needsResponse = kw['needsResponse']
|
||||
del kw['needsResponse']
|
||||
if kw:
|
||||
raise ArgumentError('Keyword arguments not supported')
|
||||
|
||||
try:
|
||||
parentObj, attribName = self.__childObject
|
||||
# Call it as a method.
|
||||
if parentObj is self.__runner.dom and attribName == 'alert':
|
||||
# As a special hack, we don't wait for the return
|
||||
# value from the alert() call, since this is a
|
||||
# blocking call, and waiting for this could cause
|
||||
# problems.
|
||||
needsResponse = False
|
||||
|
||||
if parentObj is self.__runner.dom and attribName == 'eval' and len(args) == 1 and isinstance(args[0], str):
|
||||
# As another special hack, we make dom.eval() a
|
||||
# special case, and map it directly into an eval()
|
||||
# call. If the string begins with 'void ', we further
|
||||
# assume we're not waiting for a response.
|
||||
if args[0].startswith('void '):
|
||||
needsResponse = False
|
||||
result = self.__runner.scriptRequest('eval', parentObj, value = args[0], needsResponse = needsResponse)
|
||||
else:
|
||||
# This is a normal method call.
|
||||
try:
|
||||
result = self.__runner.scriptRequest('call', parentObj, propertyName = attribName, value = args, needsResponse = needsResponse)
|
||||
except EnvironmentError:
|
||||
# Problem on the call. Maybe no such method?
|
||||
raise AttributeError
|
||||
|
||||
except EnvironmentError:
|
||||
# Some odd problem on the call.
|
||||
raise TypeError
|
||||
|
||||
return result
|
||||
|
||||
def __setattr__(self, name, value):
|
||||
""" setattr will generally fail on method objects. """
|
||||
raise AttributeError(name)
|
||||
|
||||
def __delattr__(self, name):
|
||||
""" delattr will generally fail on method objects. """
|
||||
raise AttributeError(name)
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,640 +0,0 @@
|
|||
__all__ = ["PackageInstaller"]
|
||||
|
||||
from direct.showbase.DirectObject import DirectObject
|
||||
from direct.stdpy.threading import Lock, RLock
|
||||
from direct.showbase.MessengerGlobal import messenger
|
||||
from direct.task.TaskManagerGlobal import taskMgr
|
||||
from direct.p3d.PackageInfo import PackageInfo
|
||||
from panda3d.core import TPLow, PStatCollector
|
||||
from direct.directnotify.DirectNotifyGlobal import directNotify
|
||||
|
||||
class PackageInstaller(DirectObject):
|
||||
|
||||
""" This class is used in a p3d runtime environment to manage the
|
||||
asynchronous download and installation of packages. If you just
|
||||
want to install a package synchronously, see
|
||||
appRunner.installPackage() for a simpler interface.
|
||||
|
||||
To use this class, you should subclass from it and override any of
|
||||
the six callback methods: downloadStarted(), packageStarted(),
|
||||
packageProgress(), downloadProgress(), packageFinished(),
|
||||
downloadFinished().
|
||||
|
||||
Also see DWBPackageInstaller, which does exactly this, to add a
|
||||
DirectWaitBar GUI.
|
||||
|
||||
"""
|
||||
|
||||
notify = directNotify.newCategory("PackageInstaller")
|
||||
|
||||
globalLock = Lock()
|
||||
nextUniqueId = 1
|
||||
|
||||
# This is a chain of state values progressing forward in time.
|
||||
S_initial = 0 # addPackage() calls are being made
|
||||
S_ready = 1 # donePackages() has been called
|
||||
S_started = 2 # download has started
|
||||
S_done = 3 # download is over
|
||||
|
||||
class PendingPackage:
|
||||
""" This class describes a package added to the installer for
|
||||
download. """
|
||||
|
||||
notify = directNotify.newCategory("PendingPackage")
|
||||
|
||||
def __init__(self, packageName, version, host):
|
||||
self.packageName = packageName
|
||||
self.version = version
|
||||
self.host = host
|
||||
|
||||
# This will be filled in properly by checkDescFile() or
|
||||
# getDescFile(); in the meantime, set a placeholder.
|
||||
self.package = PackageInfo(host, packageName, version)
|
||||
|
||||
# Set true when the package has finished downloading,
|
||||
# either successfully or unsuccessfully.
|
||||
self.done = False
|
||||
|
||||
# Set true or false when self.done has been set.
|
||||
self.success = False
|
||||
|
||||
# Set true when the packageFinished() callback has been
|
||||
# delivered.
|
||||
self.notified = False
|
||||
|
||||
# These are used to ensure the callbacks only get
|
||||
# delivered once for a particular package.
|
||||
self.calledPackageStarted = False
|
||||
self.calledPackageFinished = False
|
||||
|
||||
# This is the amount of stuff we have to process to
|
||||
# install this package, and the amount of stuff we have
|
||||
# processed so far. "Stuff" includes bytes downloaded,
|
||||
# bytes uncompressed, and bytes extracted; and each of
|
||||
# which is weighted differently into one grand total. So,
|
||||
# the total doesn't really represent bytes; it's a
|
||||
# unitless number, which means something only as a ratio
|
||||
# to other packages. Filled in by checkDescFile() or
|
||||
# getDescFile().
|
||||
self.downloadEffort = 0
|
||||
|
||||
# Similar, but this is the theoretical effort if the
|
||||
# package were already downloaded.
|
||||
self.prevDownloadedEffort = 0
|
||||
|
||||
def __cmp__(self, pp):
|
||||
""" Python comparision function. This makes all
|
||||
PendingPackages withe same (packageName, version, host)
|
||||
combination be deemed equivalent. """
|
||||
return cmp((self.packageName, self.version, self.host),
|
||||
(pp.packageName, pp.version, pp.host))
|
||||
|
||||
def getProgress(self):
|
||||
""" Returns the download progress of this package in the
|
||||
range 0..1. """
|
||||
|
||||
return self.package.downloadProgress
|
||||
|
||||
def checkDescFile(self):
|
||||
""" Returns true if the desc file is already downloaded
|
||||
and good, or false if it needs to be downloaded. """
|
||||
|
||||
if not self.host.hasCurrentContentsFile():
|
||||
# If the contents file isn't ready yet, we can't check
|
||||
# the desc file yet.
|
||||
return False
|
||||
|
||||
# All right, get the package info now.
|
||||
package = self.host.getPackage(self.packageName, self.version)
|
||||
if not package:
|
||||
self.notify.warning("Package %s %s not known on %s" % (
|
||||
self.packageName, self.version, self.host.hostUrl))
|
||||
return False
|
||||
|
||||
self.package = package
|
||||
self.package.checkStatus()
|
||||
|
||||
if not self.package.hasDescFile:
|
||||
return False
|
||||
|
||||
self.downloadEffort = self.package.getDownloadEffort()
|
||||
self.prevDownloadEffort = 0
|
||||
if self.downloadEffort == 0:
|
||||
self.prevDownloadedEffort = self.package.getPrevDownloadedEffort()
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def getDescFile(self, http):
|
||||
""" Synchronously downloads the desc files required for
|
||||
the package. """
|
||||
|
||||
if not self.host.downloadContentsFile(http):
|
||||
return False
|
||||
|
||||
# All right, get the package info now.
|
||||
package = self.host.getPackage(self.packageName, self.version)
|
||||
if not package:
|
||||
self.notify.warning("Package %s %s not known on %s" % (
|
||||
self.packageName, self.version, self.host.hostUrl))
|
||||
return False
|
||||
|
||||
self.package = package
|
||||
if not self.package.downloadDescFile(http):
|
||||
return False
|
||||
|
||||
self.package.checkStatus()
|
||||
self.downloadEffort = self.package.getDownloadEffort()
|
||||
self.prevDownloadEffort = 0
|
||||
if self.downloadEffort == 0:
|
||||
self.prevDownloadedEffort = self.package.getPrevDownloadedEffort()
|
||||
|
||||
return True
|
||||
|
||||
def __init__(self, appRunner, taskChain = 'default'):
|
||||
self.globalLock.acquire()
|
||||
try:
|
||||
self.uniqueId = PackageInstaller.nextUniqueId
|
||||
PackageInstaller.nextUniqueId += 1
|
||||
finally:
|
||||
self.globalLock.release()
|
||||
|
||||
self.appRunner = appRunner
|
||||
self.taskChain = taskChain
|
||||
|
||||
# If we're to be running on an asynchronous task chain, and
|
||||
# the task chain hasn't yet been set up already, create the
|
||||
# default parameters now.
|
||||
if taskChain != 'default' and not taskMgr.hasTaskChain(self.taskChain):
|
||||
taskMgr.setupTaskChain(self.taskChain, numThreads = 1,
|
||||
threadPriority = TPLow)
|
||||
|
||||
self.callbackLock = Lock()
|
||||
self.calledDownloadStarted = False
|
||||
self.calledDownloadFinished = False
|
||||
|
||||
# A list of all packages that have been added to the
|
||||
# installer.
|
||||
self.packageLock = RLock()
|
||||
self.packages = []
|
||||
self.state = self.S_initial
|
||||
|
||||
# A list of packages that are waiting for their desc files.
|
||||
self.needsDescFile = []
|
||||
self.descFileTask = None
|
||||
|
||||
# A list of packages that are waiting to be downloaded and
|
||||
# installed.
|
||||
self.needsDownload = []
|
||||
self.downloadTask = None
|
||||
|
||||
# A list of packages that were already done at the time they
|
||||
# were passed to addPackage().
|
||||
self.earlyDone = []
|
||||
|
||||
# A list of packages that have been successfully installed, or
|
||||
# packages that have failed.
|
||||
self.done = []
|
||||
self.failed = []
|
||||
|
||||
# This task is spawned on the default task chain, to update
|
||||
# the status during the download.
|
||||
self.progressTask = None
|
||||
|
||||
self.accept('PackageInstaller-%s-allHaveDesc' % self.uniqueId,
|
||||
self.__allHaveDesc)
|
||||
self.accept('PackageInstaller-%s-packageStarted' % self.uniqueId,
|
||||
self.__packageStarted)
|
||||
self.accept('PackageInstaller-%s-packageDone' % self.uniqueId,
|
||||
self.__packageDone)
|
||||
|
||||
def destroy(self):
|
||||
""" Interrupts all pending downloads. No further callbacks
|
||||
will be made. """
|
||||
self.cleanup()
|
||||
|
||||
def cleanup(self):
|
||||
""" Interrupts all pending downloads. No further callbacks
|
||||
will be made. """
|
||||
|
||||
self.packageLock.acquire()
|
||||
try:
|
||||
if self.descFileTask:
|
||||
taskMgr.remove(self.descFileTask)
|
||||
self.descFileTask = None
|
||||
if self.downloadTask:
|
||||
taskMgr.remove(self.downloadTask)
|
||||
self.downloadTask = None
|
||||
finally:
|
||||
self.packageLock.release()
|
||||
|
||||
if self.progressTask:
|
||||
taskMgr.remove(self.progressTask)
|
||||
self.progressTask = None
|
||||
|
||||
self.ignoreAll()
|
||||
|
||||
def addPackage(self, packageName, version = None, hostUrl = None):
|
||||
""" Adds the named package to the list of packages to be
|
||||
downloaded. Call donePackages() to finish the list. """
|
||||
|
||||
if self.state != self.S_initial:
|
||||
raise ValueError('addPackage called after donePackages')
|
||||
|
||||
host = self.appRunner.getHostWithAlt(hostUrl)
|
||||
pp = self.PendingPackage(packageName, version, host)
|
||||
|
||||
self.packageLock.acquire()
|
||||
try:
|
||||
self.__internalAddPackage(pp)
|
||||
finally:
|
||||
self.packageLock.release()
|
||||
|
||||
def __internalAddPackage(self, pp):
|
||||
""" Adds the indicated "pending package" to the appropriate
|
||||
list(s) for downloading and installing. Assumes packageLock
|
||||
is already held."""
|
||||
|
||||
if pp in self.packages:
|
||||
# Already added.
|
||||
return
|
||||
|
||||
self.packages.append(pp)
|
||||
|
||||
# We always add the package to needsDescFile, even if we
|
||||
# already have its desc file; this guarantees that packages
|
||||
# are downloaded in the order they are added.
|
||||
self.needsDescFile.append(pp)
|
||||
if not self.descFileTask:
|
||||
self.descFileTask = taskMgr.add(
|
||||
self.__getDescFileTask, 'getDescFile',
|
||||
taskChain = self.taskChain)
|
||||
|
||||
def donePackages(self):
|
||||
""" After calling addPackage() for each package to be
|
||||
installed, call donePackages() to mark the end of the list.
|
||||
This is necessary to determine what the complete set of
|
||||
packages is (and therefore how large the total download size
|
||||
is). None of the low-level callbacks will be made before this
|
||||
call. """
|
||||
|
||||
if self.state != self.S_initial:
|
||||
# We've already been here.
|
||||
return
|
||||
|
||||
# Throw the messages for packages that were already done
|
||||
# before we started.
|
||||
for pp in self.earlyDone:
|
||||
self.__donePackage(pp, True)
|
||||
self.earlyDone = []
|
||||
|
||||
self.packageLock.acquire()
|
||||
try:
|
||||
if self.state != self.S_initial:
|
||||
return
|
||||
self.state = self.S_ready
|
||||
if not self.needsDescFile:
|
||||
# All package desc files are already available; so begin.
|
||||
self.__prepareToStart()
|
||||
finally:
|
||||
self.packageLock.release()
|
||||
|
||||
if not self.packages:
|
||||
# Trivial no-op.
|
||||
self.__callDownloadFinished(True)
|
||||
|
||||
def downloadStarted(self):
|
||||
""" This callback is made at some point after donePackages()
|
||||
is called; at the time of this callback, the total download
|
||||
size is known, and we can sensibly report progress through the
|
||||
whole. """
|
||||
|
||||
self.notify.info("downloadStarted")
|
||||
|
||||
def packageStarted(self, package):
|
||||
""" This callback is made for each package between
|
||||
downloadStarted() and downloadFinished() to indicate the start
|
||||
of a new package. """
|
||||
|
||||
self.notify.debug("packageStarted: %s" % (package.packageName))
|
||||
|
||||
def packageProgress(self, package, progress):
|
||||
""" This callback is made repeatedly between packageStarted()
|
||||
and packageFinished() to update the current progress on the
|
||||
indicated package only. The progress value ranges from 0
|
||||
(beginning) to 1 (complete). """
|
||||
|
||||
self.notify.debug("packageProgress: %s %s" % (package.packageName, progress))
|
||||
|
||||
def downloadProgress(self, overallProgress):
|
||||
""" This callback is made repeatedly between downloadStarted()
|
||||
and downloadFinished() to update the current progress through
|
||||
all packages. The progress value ranges from 0 (beginning) to
|
||||
1 (complete). """
|
||||
|
||||
self.notify.debug("downloadProgress: %s" % (overallProgress))
|
||||
|
||||
def packageFinished(self, package, success):
|
||||
""" This callback is made for each package between
|
||||
downloadStarted() and downloadFinished() to indicate that a
|
||||
package has finished downloading. If success is true, there
|
||||
were no problems and the package is now installed.
|
||||
|
||||
If this package did not require downloading (because it was
|
||||
already downloaded), this callback will be made immediately,
|
||||
*without* a corresponding call to packageStarted(), and may
|
||||
even be made before downloadStarted(). """
|
||||
|
||||
self.notify.info("packageFinished: %s %s" % (package.packageName, success))
|
||||
|
||||
def downloadFinished(self, success):
|
||||
""" This callback is made when all of the packages have been
|
||||
downloaded and installed (or there has been some failure). If
|
||||
all packages where successfully installed, success is True.
|
||||
|
||||
If there were no packages that required downloading, this
|
||||
callback will be made immediately, *without* a corresponding
|
||||
call to downloadStarted(). """
|
||||
|
||||
self.notify.info("downloadFinished: %s" % (success))
|
||||
|
||||
def __prepareToStart(self):
|
||||
""" This is called internally when transitioning from S_ready
|
||||
to S_started. It sets up whatever initial values are
|
||||
needed. Assumes self.packageLock is held. Returns False if
|
||||
there were no packages to download, and the state was
|
||||
therefore transitioned immediately to S_done. """
|
||||
|
||||
if not self.needsDownload:
|
||||
self.state = self.S_done
|
||||
return False
|
||||
|
||||
self.state = self.S_started
|
||||
|
||||
assert not self.downloadTask
|
||||
self.downloadTask = taskMgr.add(
|
||||
self.__downloadPackageTask, 'downloadPackage',
|
||||
taskChain = self.taskChain)
|
||||
|
||||
assert not self.progressTask
|
||||
self.progressTask = taskMgr.add(
|
||||
self.__progressTask, 'packageProgress')
|
||||
|
||||
return True
|
||||
|
||||
def __allHaveDesc(self):
|
||||
""" This method is called internally when all of the pending
|
||||
packages have their desc info. """
|
||||
working = True
|
||||
|
||||
self.packageLock.acquire()
|
||||
try:
|
||||
if self.state == self.S_ready:
|
||||
# We've already called donePackages(), so move on now.
|
||||
working = self.__prepareToStart()
|
||||
finally:
|
||||
self.packageLock.release()
|
||||
|
||||
if not working:
|
||||
self.__callDownloadFinished(True)
|
||||
|
||||
def __packageStarted(self, pp):
|
||||
""" This method is called when a single package is beginning
|
||||
to download. """
|
||||
|
||||
self.__callDownloadStarted()
|
||||
self.__callPackageStarted(pp)
|
||||
|
||||
def __packageDone(self, pp):
|
||||
""" This method is called when a single package has been
|
||||
downloaded and installed, or has failed. """
|
||||
|
||||
self.__callPackageFinished(pp, pp.success)
|
||||
pp.notified = True
|
||||
|
||||
# See if there are more packages to go.
|
||||
success = True
|
||||
allDone = True
|
||||
self.packageLock.acquire()
|
||||
try:
|
||||
for pp in self.packages:
|
||||
if pp.notified:
|
||||
success = success and pp.success
|
||||
else:
|
||||
allDone = False
|
||||
finally:
|
||||
self.packageLock.release()
|
||||
|
||||
if allDone:
|
||||
self.__callDownloadFinished(success)
|
||||
|
||||
def __callPackageStarted(self, pp):
|
||||
""" Calls the packageStarted() callback for a particular
|
||||
package if it has not already been called, being careful to
|
||||
avoid race conditions. """
|
||||
|
||||
self.callbackLock.acquire()
|
||||
try:
|
||||
if not pp.calledPackageStarted:
|
||||
self.packageStarted(pp.package)
|
||||
self.packageProgress(pp.package, 0)
|
||||
pp.calledPackageStarted = True
|
||||
finally:
|
||||
self.callbackLock.release()
|
||||
|
||||
def __callPackageFinished(self, pp, success):
|
||||
""" Calls the packageFinished() callback for a paricular
|
||||
package if it has not already been called, being careful to
|
||||
avoid race conditions. """
|
||||
|
||||
self.callbackLock.acquire()
|
||||
try:
|
||||
if not pp.calledPackageFinished:
|
||||
if success:
|
||||
self.packageProgress(pp.package, 1)
|
||||
self.packageFinished(pp.package, success)
|
||||
pp.calledPackageFinished = True
|
||||
finally:
|
||||
self.callbackLock.release()
|
||||
|
||||
def __callDownloadStarted(self):
|
||||
""" Calls the downloadStarted() callback if it has not already
|
||||
been called, being careful to avoid race conditions. """
|
||||
|
||||
self.callbackLock.acquire()
|
||||
try:
|
||||
if not self.calledDownloadStarted:
|
||||
self.downloadStarted()
|
||||
self.downloadProgress(0)
|
||||
self.calledDownloadStarted = True
|
||||
finally:
|
||||
self.callbackLock.release()
|
||||
|
||||
def __callDownloadFinished(self, success):
|
||||
""" Calls the downloadFinished() callback if it has not
|
||||
already been called, being careful to avoid race
|
||||
conditions. """
|
||||
|
||||
self.callbackLock.acquire()
|
||||
try:
|
||||
if not self.calledDownloadFinished:
|
||||
if success:
|
||||
self.downloadProgress(1)
|
||||
self.downloadFinished(success)
|
||||
self.calledDownloadFinished = True
|
||||
finally:
|
||||
self.callbackLock.release()
|
||||
|
||||
def __getDescFileTask(self, task):
|
||||
|
||||
""" This task runs on the aysynchronous task chain; each pass,
|
||||
it extracts one package from self.needsDescFile and downloads
|
||||
its desc file. On success, it adds the package to
|
||||
self.needsDownload. """
|
||||
|
||||
self.packageLock.acquire()
|
||||
try:
|
||||
# If we've finished all of the packages that need desc
|
||||
# files, stop the task.
|
||||
if not self.needsDescFile:
|
||||
self.descFileTask = None
|
||||
|
||||
eventName = 'PackageInstaller-%s-allHaveDesc' % self.uniqueId
|
||||
messenger.send(eventName, taskChain = 'default')
|
||||
|
||||
return task.done
|
||||
pp = self.needsDescFile[0]
|
||||
del self.needsDescFile[0]
|
||||
finally:
|
||||
self.packageLock.release()
|
||||
|
||||
# Now serve this one package.
|
||||
if not pp.checkDescFile():
|
||||
if not pp.getDescFile(self.appRunner.http):
|
||||
self.__donePackage(pp, False)
|
||||
return task.cont
|
||||
|
||||
# This package is now ready to be downloaded. We always add
|
||||
# it to needsDownload, even if it's already downloaded, to
|
||||
# guarantee ordering of packages.
|
||||
|
||||
self.packageLock.acquire()
|
||||
try:
|
||||
# Also add any packages required by this one.
|
||||
for packageName, version, host in pp.package.requires:
|
||||
pp2 = self.PendingPackage(packageName, version, host)
|
||||
self.__internalAddPackage(pp2)
|
||||
self.needsDownload.append(pp)
|
||||
finally:
|
||||
self.packageLock.release()
|
||||
|
||||
return task.cont
|
||||
|
||||
def __downloadPackageTask(self, task):
|
||||
|
||||
""" This task runs on the aysynchronous task chain; each pass,
|
||||
it extracts one package from self.needsDownload and downloads
|
||||
it. """
|
||||
|
||||
while True:
|
||||
self.packageLock.acquire()
|
||||
try:
|
||||
# If we're done downloading, stop the task.
|
||||
if self.state == self.S_done or not self.needsDownload:
|
||||
self.downloadTask = None
|
||||
self.packageLock.release()
|
||||
yield task.done; return
|
||||
|
||||
assert self.state == self.S_started
|
||||
pp = self.needsDownload[0]
|
||||
del self.needsDownload[0]
|
||||
except:
|
||||
self.packageLock.release()
|
||||
raise
|
||||
self.packageLock.release()
|
||||
|
||||
# Now serve this one package.
|
||||
eventName = 'PackageInstaller-%s-packageStarted' % self.uniqueId
|
||||
messenger.send(eventName, [pp], taskChain = 'default')
|
||||
|
||||
if not pp.package.hasPackage:
|
||||
for token in pp.package.downloadPackageGenerator(self.appRunner.http):
|
||||
if token == pp.package.stepContinue:
|
||||
yield task.cont
|
||||
else:
|
||||
break
|
||||
|
||||
if token != pp.package.stepComplete:
|
||||
pc = PStatCollector(':App:PackageInstaller:donePackage:%s' % (pp.package.packageName))
|
||||
pc.start()
|
||||
self.__donePackage(pp, False)
|
||||
pc.stop()
|
||||
yield task.cont
|
||||
continue
|
||||
|
||||
# Successfully downloaded and installed.
|
||||
pc = PStatCollector(':App:PackageInstaller:donePackage:%s' % (pp.package.packageName))
|
||||
pc.start()
|
||||
self.__donePackage(pp, True)
|
||||
pc.stop()
|
||||
|
||||
# Continue the loop without yielding, so we pick up the
|
||||
# next package within this same frame.
|
||||
|
||||
def __donePackage(self, pp, success):
|
||||
""" Marks the indicated package as done, either successfully
|
||||
or otherwise. """
|
||||
assert not pp.done
|
||||
|
||||
if success:
|
||||
pc = PStatCollector(':App:PackageInstaller:install:%s' % (pp.package.packageName))
|
||||
pc.start()
|
||||
pp.package.installPackage(self.appRunner)
|
||||
pc.stop()
|
||||
|
||||
self.packageLock.acquire()
|
||||
try:
|
||||
pp.done = True
|
||||
pp.success = success
|
||||
if success:
|
||||
self.done.append(pp)
|
||||
else:
|
||||
self.failed.append(pp)
|
||||
finally:
|
||||
self.packageLock.release()
|
||||
|
||||
eventName = 'PackageInstaller-%s-packageDone' % self.uniqueId
|
||||
messenger.send(eventName, [pp], taskChain = 'default')
|
||||
|
||||
def __progressTask(self, task):
|
||||
self.callbackLock.acquire()
|
||||
try:
|
||||
if not self.calledDownloadStarted:
|
||||
# We haven't yet officially started the download.
|
||||
return task.cont
|
||||
|
||||
if self.calledDownloadFinished:
|
||||
# We've officially ended the download.
|
||||
self.progressTask = None
|
||||
return task.done
|
||||
|
||||
downloadEffort = 0
|
||||
currentDownloadSize = 0
|
||||
for pp in self.packages:
|
||||
downloadEffort += pp.downloadEffort + pp.prevDownloadedEffort
|
||||
packageProgress = pp.getProgress()
|
||||
currentDownloadSize += pp.downloadEffort * packageProgress + pp.prevDownloadedEffort
|
||||
if pp.calledPackageStarted and not pp.calledPackageFinished:
|
||||
self.packageProgress(pp.package, packageProgress)
|
||||
|
||||
if not downloadEffort:
|
||||
progress = 1
|
||||
else:
|
||||
progress = float(currentDownloadSize) / float(downloadEffort)
|
||||
self.downloadProgress(progress)
|
||||
|
||||
finally:
|
||||
self.callbackLock.release()
|
||||
|
||||
return task.cont
|
||||
|
||||
|
|
@ -1,306 +0,0 @@
|
|||
__all__ = ["PackageMerger", "PackageMergerError"]
|
||||
|
||||
from direct.p3d.FileSpec import FileSpec
|
||||
from direct.p3d.SeqValue import SeqValue
|
||||
from direct.directnotify.DirectNotifyGlobal import *
|
||||
from panda3d.core import *
|
||||
import shutil
|
||||
import os
|
||||
|
||||
class PackageMergerError(Exception):
|
||||
pass
|
||||
|
||||
class PackageMerger:
|
||||
""" This class will combine two or more separately-built stage
|
||||
directories, the output of Packager.py or the ppackage tool, into
|
||||
a single output directory. It assumes that the clocks on all
|
||||
hosts are in sync, so that the file across all builds with the
|
||||
most recent timestamp (indicated in the contents.xml file) is
|
||||
always the most current version of the file. """
|
||||
|
||||
notify = directNotify.newCategory("PackageMerger")
|
||||
|
||||
class PackageEntry:
|
||||
""" This corresponds to a <package> entry in the contents.xml
|
||||
file. """
|
||||
|
||||
def __init__(self, xpackage, sourceDir):
|
||||
self.sourceDir = sourceDir
|
||||
self.loadXml(xpackage)
|
||||
|
||||
def getKey(self):
|
||||
""" Returns a tuple used for sorting the PackageEntry
|
||||
objects uniquely per package. """
|
||||
return (self.packageName, self.platform, self.version)
|
||||
|
||||
def isNewer(self, other):
|
||||
return self.descFile.timestamp > other.descFile.timestamp
|
||||
|
||||
def loadXml(self, xpackage):
|
||||
self.packageName = xpackage.Attribute('name')
|
||||
self.platform = xpackage.Attribute('platform')
|
||||
self.version = xpackage.Attribute('version')
|
||||
solo = xpackage.Attribute('solo')
|
||||
self.solo = int(solo or '0')
|
||||
perPlatform = xpackage.Attribute('per_platform')
|
||||
self.perPlatform = int(perPlatform or '0')
|
||||
|
||||
self.descFile = FileSpec()
|
||||
self.descFile.loadXml(xpackage)
|
||||
|
||||
self.validatePackageContents()
|
||||
|
||||
self.descFile.quickVerify(packageDir = self.sourceDir, notify = PackageMerger.notify, correctSelf = True)
|
||||
|
||||
self.packageSeq = SeqValue()
|
||||
self.packageSeq.loadXml(xpackage, 'seq')
|
||||
self.packageSetVer = SeqValue()
|
||||
self.packageSetVer.loadXml(xpackage, 'set_ver')
|
||||
|
||||
self.importDescFile = None
|
||||
ximport = xpackage.FirstChildElement('import')
|
||||
if ximport:
|
||||
self.importDescFile = FileSpec()
|
||||
self.importDescFile.loadXml(ximport)
|
||||
self.importDescFile.quickVerify(packageDir = self.sourceDir, notify = PackageMerger.notify, correctSelf = True)
|
||||
|
||||
def makeXml(self):
|
||||
""" Returns a new TiXmlElement. """
|
||||
xpackage = TiXmlElement('package')
|
||||
xpackage.SetAttribute('name', self.packageName)
|
||||
if self.platform:
|
||||
xpackage.SetAttribute('platform', self.platform)
|
||||
if self.version:
|
||||
xpackage.SetAttribute('version', self.version)
|
||||
if self.solo:
|
||||
xpackage.SetAttribute('solo', '1')
|
||||
if self.perPlatform:
|
||||
xpackage.SetAttribute('per_platform', '1')
|
||||
|
||||
self.descFile.storeXml(xpackage)
|
||||
self.packageSeq.storeXml(xpackage, 'seq')
|
||||
self.packageSetVer.storeXml(xpackage, 'set_ver')
|
||||
|
||||
if self.importDescFile:
|
||||
ximport = TiXmlElement('import')
|
||||
self.importDescFile.storeXml(ximport)
|
||||
xpackage.InsertEndChild(ximport)
|
||||
|
||||
return xpackage
|
||||
|
||||
def validatePackageContents(self):
|
||||
""" Validates the contents of the package directory itself
|
||||
against the expected hashes and timestamps. Updates
|
||||
hashes and timestamps where needed. """
|
||||
|
||||
if self.solo:
|
||||
return
|
||||
|
||||
needsChange = False
|
||||
packageDescFullpath = Filename(self.sourceDir, self.descFile.filename)
|
||||
packageDir = Filename(packageDescFullpath.getDirname())
|
||||
doc = TiXmlDocument(packageDescFullpath.toOsSpecific())
|
||||
if not doc.LoadFile():
|
||||
message = "Could not read XML file: %s" % (self.descFile.filename)
|
||||
raise OSError(message)
|
||||
|
||||
xpackage = doc.FirstChildElement('package')
|
||||
if not xpackage:
|
||||
message = "No package definition: %s" % (self.descFile.filename)
|
||||
raise OSError(message)
|
||||
|
||||
xcompressed = xpackage.FirstChildElement('compressed_archive')
|
||||
if xcompressed:
|
||||
spec = FileSpec()
|
||||
spec.loadXml(xcompressed)
|
||||
if not spec.quickVerify(packageDir = packageDir, notify = PackageMerger.notify, correctSelf = True):
|
||||
spec.storeXml(xcompressed)
|
||||
needsChange = True
|
||||
|
||||
xpatch = xpackage.FirstChildElement('patch')
|
||||
while xpatch:
|
||||
spec = FileSpec()
|
||||
spec.loadXml(xpatch)
|
||||
if not spec.quickVerify(packageDir = packageDir, notify = PackageMerger.notify, correctSelf = True):
|
||||
spec.storeXml(xpatch)
|
||||
needsChange = True
|
||||
|
||||
xpatch = xpatch.NextSiblingElement('patch')
|
||||
|
||||
if needsChange:
|
||||
PackageMerger.notify.info("Rewriting %s" % (self.descFile.filename))
|
||||
doc.SaveFile()
|
||||
self.descFile.quickVerify(packageDir = self.sourceDir, notify = PackageMerger.notify, correctSelf = True)
|
||||
|
||||
# PackageMerger constructor
|
||||
def __init__(self, installDir):
|
||||
self.installDir = installDir
|
||||
self.xhost = None
|
||||
self.contents = {}
|
||||
self.maxAge = None
|
||||
self.contentsSeq = SeqValue()
|
||||
|
||||
# We allow the first one to fail quietly.
|
||||
self.__readContentsFile(self.installDir, None)
|
||||
|
||||
def __readContentsFile(self, sourceDir, packageNames):
|
||||
""" Reads the contents.xml file from the indicated sourceDir,
|
||||
and updates the internal set of packages appropriately. """
|
||||
|
||||
assert sourceDir != None, "No source directory was specified!"
|
||||
contentsFilename = Filename(sourceDir, 'contents.xml')
|
||||
doc = TiXmlDocument(contentsFilename.toOsSpecific())
|
||||
if not doc.LoadFile():
|
||||
# Couldn't read file.
|
||||
return False
|
||||
|
||||
xcontents = doc.FirstChildElement('contents')
|
||||
if xcontents:
|
||||
maxAge = xcontents.Attribute('max_age')
|
||||
if maxAge:
|
||||
maxAge = int(maxAge)
|
||||
if self.maxAge is None:
|
||||
self.maxAge = maxAge
|
||||
else:
|
||||
self.maxAge = min(self.maxAge, maxAge)
|
||||
|
||||
contentsSeq = SeqValue()
|
||||
if contentsSeq.loadXml(xcontents):
|
||||
self.contentsSeq = max(self.contentsSeq, contentsSeq)
|
||||
|
||||
xhost = xcontents.FirstChildElement('host')
|
||||
if xhost:
|
||||
self.xhost = xhost.Clone()
|
||||
|
||||
xpackage = xcontents.FirstChildElement('package')
|
||||
while xpackage:
|
||||
pe = self.PackageEntry(xpackage, sourceDir)
|
||||
|
||||
# Filter out any packages not listed in
|
||||
# packageNames (unless packageNames is None,
|
||||
# in which case don't filter anything).
|
||||
if packageNames is None or pe.packageName in packageNames:
|
||||
other = self.contents.get(pe.getKey(), None)
|
||||
if not other or pe.isNewer(other):
|
||||
# Store this package in the resulting output.
|
||||
self.contents[pe.getKey()] = pe
|
||||
|
||||
xpackage = xpackage.NextSiblingElement('package')
|
||||
|
||||
self.contentsDoc = doc
|
||||
|
||||
return True
|
||||
|
||||
def __writeContentsFile(self):
|
||||
""" Writes the contents.xml file at the end of processing. """
|
||||
|
||||
filename = Filename(self.installDir, 'contents.xml')
|
||||
doc = TiXmlDocument(filename.toOsSpecific())
|
||||
decl = TiXmlDeclaration("1.0", "utf-8", "")
|
||||
doc.InsertEndChild(decl)
|
||||
|
||||
xcontents = TiXmlElement('contents')
|
||||
if self.xhost:
|
||||
xcontents.InsertEndChild(self.xhost)
|
||||
|
||||
if self.maxAge is not None:
|
||||
xcontents.SetAttribute('max_age', str(self.maxAge))
|
||||
self.contentsSeq.storeXml(xcontents)
|
||||
|
||||
contents = list(self.contents.items())
|
||||
contents.sort()
|
||||
for key, pe in contents:
|
||||
xpackage = pe.makeXml()
|
||||
xcontents.InsertEndChild(xpackage)
|
||||
|
||||
doc.InsertEndChild(xcontents)
|
||||
doc.SaveFile()
|
||||
|
||||
def __copySubdirectory(self, pe):
|
||||
""" Copies the subdirectory referenced in the indicated
|
||||
PackageEntry object into the installDir, replacing the
|
||||
contents of any similarly-named subdirectory already
|
||||
there. """
|
||||
|
||||
dirname = Filename(pe.descFile.filename).getDirname()
|
||||
self.notify.info("copying %s" % (dirname))
|
||||
sourceDirname = Filename(pe.sourceDir, dirname)
|
||||
targetDirname = Filename(self.installDir, dirname)
|
||||
|
||||
self.__rCopyTree(sourceDirname, targetDirname)
|
||||
|
||||
def __rCopyTree(self, sourceFilename, targetFilename):
|
||||
""" Recursively copies the contents of sourceDirname onto
|
||||
targetDirname. This behaves like shutil.copytree, but it does
|
||||
not remove pre-existing subdirectories. """
|
||||
|
||||
if targetFilename.exists():
|
||||
if not targetFilename.isDirectory():
|
||||
# Delete any regular files in the way.
|
||||
targetFilename.unlink()
|
||||
|
||||
elif not sourceFilename.isDirectory():
|
||||
# If the source file is a regular file, but the target
|
||||
# file is a directory, completely remove the target
|
||||
# file.
|
||||
shutil.rmtree(targetFilename.toOsSpecific())
|
||||
|
||||
else:
|
||||
# Both the source file and target file are
|
||||
# directories.
|
||||
|
||||
# We have to clean out the target directory first.
|
||||
# Instead of using shutil.rmtree(), remove the files in
|
||||
# this directory one at a time, so we don't inadvertently
|
||||
# clean out subdirectories too.
|
||||
files = os.listdir(targetFilename.toOsSpecific())
|
||||
for file in files:
|
||||
f = Filename(targetFilename, file)
|
||||
if f.isRegularFile():
|
||||
f.unlink()
|
||||
|
||||
if sourceFilename.isDirectory():
|
||||
# Recursively copying a directory.
|
||||
Filename(targetFilename, '').makeDir()
|
||||
files = os.listdir(sourceFilename.toOsSpecific())
|
||||
for file in files:
|
||||
self.__rCopyTree(Filename(sourceFilename, file),
|
||||
Filename(targetFilename, file))
|
||||
else:
|
||||
# Copying a regular file.
|
||||
sourceFilename.copyTo(targetFilename)
|
||||
|
||||
# Also try to copy the timestamp, but don't fuss too much
|
||||
# if it doesn't work.
|
||||
try:
|
||||
st = os.stat(sourceFilename.toOsSpecific())
|
||||
os.utime(targetFilename.toOsSpecific(), (st.st_atime, st.st_mtime))
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
def merge(self, sourceDir, packageNames = None):
|
||||
""" Adds the contents of the indicated source directory into
|
||||
the current pool. If packageNames is not None, it is a list
|
||||
of package names that we wish to include from the source;
|
||||
packages not named in this list will be unchanged. """
|
||||
|
||||
if not self.__readContentsFile(sourceDir, packageNames):
|
||||
message = "Couldn't read %s" % (sourceDir)
|
||||
raise PackageMergerError(message)
|
||||
|
||||
def close(self):
|
||||
""" Finalizes the results of all of the previous calls to
|
||||
merge(), writes the new contents.xml file, and copies in all
|
||||
of the new contents. """
|
||||
|
||||
dirname = Filename(self.installDir, '')
|
||||
dirname.makeDir()
|
||||
|
||||
for pe in self.contents.values():
|
||||
if pe.sourceDir != self.installDir:
|
||||
# Here's a new subdirectory we have to copy in.
|
||||
self.__copySubdirectory(pe)
|
||||
|
||||
self.contentsSeq += 1
|
||||
self.__writeContentsFile()
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,814 +0,0 @@
|
|||
__all__ = ["PatchMaker"]
|
||||
|
||||
from direct.p3d.FileSpec import FileSpec
|
||||
from direct.p3d.SeqValue import SeqValue
|
||||
from panda3d.core import *
|
||||
import copy
|
||||
|
||||
class PatchMaker:
|
||||
""" This class will operate on an existing package install
|
||||
directory, as generated by the Packager, and create patchfiles
|
||||
between versions as needed. It is also used at runtime, to apply
|
||||
the downloaded patches. """
|
||||
|
||||
class PackageVersion:
|
||||
""" A specific patch version of a package. This is not just
|
||||
the package's "version" string; it also corresponds to the
|
||||
particular patch version, which increments independently of
|
||||
the "version". """
|
||||
|
||||
def __init__(self, packageName, platform, version, hostUrl, file):
|
||||
self.packageName = packageName
|
||||
self.platform = platform
|
||||
self.version = version
|
||||
self.hostUrl = hostUrl
|
||||
self.file = file
|
||||
self.printName = None
|
||||
|
||||
# The Package object that produces this version, if this
|
||||
# is the current, base, or top form, respectively.
|
||||
self.packageCurrent = None
|
||||
self.packageBase = None
|
||||
self.packageTop = None
|
||||
|
||||
# A list of patchfiles that can produce this version.
|
||||
self.fromPatches = []
|
||||
|
||||
# A list of patchfiles that can start from this version.
|
||||
self.toPatches = []
|
||||
|
||||
# A temporary file for re-creating the archive file for
|
||||
# this version.
|
||||
self.tempFile = None
|
||||
|
||||
def cleanup(self):
|
||||
if self.tempFile:
|
||||
self.tempFile.unlink()
|
||||
|
||||
def getPatchChain(self, startPv, alreadyVisited = []):
|
||||
""" Returns a list of patches that, when applied in
|
||||
sequence to the indicated PackageVersion object, will
|
||||
produce this PackageVersion object. Returns None if no
|
||||
chain can be found. """
|
||||
|
||||
if self is startPv:
|
||||
# We're already here. A zero-length patch chain is
|
||||
# therefore the answer.
|
||||
return []
|
||||
if self in alreadyVisited:
|
||||
# We've already been here; this is a loop. Avoid
|
||||
# infinite recursion.
|
||||
return None
|
||||
|
||||
alreadyVisited = alreadyVisited[:]
|
||||
alreadyVisited.append(self)
|
||||
|
||||
bestPatchChain = None
|
||||
for patchfile in self.fromPatches:
|
||||
fromPv = patchfile.fromPv
|
||||
patchChain = fromPv.getPatchChain(startPv, alreadyVisited)
|
||||
if patchChain is not None:
|
||||
# There's a path through this patchfile.
|
||||
patchChain = patchChain + [patchfile]
|
||||
if bestPatchChain is None or len(patchChain) < len(bestPatchChain):
|
||||
bestPatchChain = patchChain
|
||||
|
||||
# Return the shortest path found, or None if there were no
|
||||
# paths found.
|
||||
return bestPatchChain
|
||||
|
||||
def getRecreateFilePlan(self, alreadyVisited = []):
|
||||
""" Returns the tuple (startFile, startPv, plan),
|
||||
describing how to recreate the archive file for this
|
||||
version. startFile and startPv is the Filename and
|
||||
packageVersion of the file to start with, and plan is a
|
||||
list of tuples (patchfile, pv), listing the patches to
|
||||
apply in sequence, and the packageVersion object
|
||||
associated with each patch. Returns (None, None, None) if
|
||||
there is no way to recreate this archive file. """
|
||||
|
||||
if self.tempFile:
|
||||
return (self.tempFile, self, [])
|
||||
|
||||
if self in alreadyVisited:
|
||||
# We've already been here; this is a loop. Avoid
|
||||
# infinite recursion.
|
||||
return (None, None, None)
|
||||
|
||||
alreadyVisited = alreadyVisited[:]
|
||||
alreadyVisited.append(self)
|
||||
|
||||
if self.packageCurrent:
|
||||
# This PackageVersion instance represents the current
|
||||
# version of some package.
|
||||
package = self.packageCurrent
|
||||
return (Filename(package.packageDir, package.compressedFilename), self, [])
|
||||
|
||||
if self.packageBase:
|
||||
# This PackageVersion instance represents the base
|
||||
# (oldest) version of some package.
|
||||
package = self.packageBase
|
||||
return (Filename(package.packageDir, package.baseFile.filename + '.pz'), self, [])
|
||||
|
||||
# We'll need to re-create the file.
|
||||
bestPlan = None
|
||||
bestStartFile = None
|
||||
bestStartPv = None
|
||||
for patchfile in self.fromPatches:
|
||||
fromPv = patchfile.fromPv
|
||||
startFile, startPv, plan = fromPv.getRecreateFilePlan(alreadyVisited)
|
||||
if plan is not None:
|
||||
# There's a path through this patchfile.
|
||||
plan = plan + [(patchfile, self)]
|
||||
if bestPlan is None or len(plan) < len(bestPlan):
|
||||
bestPlan = plan
|
||||
bestStartFile = startFile
|
||||
bestStartPv = startPv
|
||||
|
||||
# Return the shortest path found, or None if there were no
|
||||
# paths found.
|
||||
return (bestStartFile, bestStartPv, bestPlan)
|
||||
|
||||
def getFile(self):
|
||||
""" Returns the Filename of the archive file associated
|
||||
with this version. If the file doesn't actually exist on
|
||||
disk, a temporary file will be created. Returns None if
|
||||
the file can't be recreated. """
|
||||
|
||||
startFile, startPv, plan = self.getRecreateFilePlan()
|
||||
|
||||
if startFile.getExtension() in ('pz', 'gz'):
|
||||
# If the starting file is compressed, we have to
|
||||
# decompress it first.
|
||||
assert startPv.tempFile is None
|
||||
startPv.tempFile = Filename.temporary('', 'patch_')
|
||||
if not decompressFile(startFile, startPv.tempFile):
|
||||
# Failure trying to decompress the file.
|
||||
return None
|
||||
startFile = startPv.tempFile
|
||||
|
||||
if not plan:
|
||||
# If plan is a zero-length list, we're already
|
||||
# here--return startFile. If plan is None, there's no
|
||||
# solution, and startFile is None. In either case, we
|
||||
# can return startFile.
|
||||
return startFile
|
||||
|
||||
# If plan is a non-empty list, we have to walk the list to
|
||||
# apply the patch plan.
|
||||
prevFile = startFile
|
||||
for patchfile, pv in plan:
|
||||
fromPv = patchfile.fromPv
|
||||
patchFilename = Filename(patchfile.package.packageDir, patchfile.file.filename)
|
||||
result = self.applyPatch(prevFile, patchFilename)
|
||||
if not result:
|
||||
# Failure trying to re-create the file.
|
||||
return None
|
||||
|
||||
pv.tempFile = result
|
||||
prevFile = result
|
||||
|
||||
# Successfully patched.
|
||||
assert pv is self and prevFile is self.tempFile
|
||||
return prevFile
|
||||
|
||||
def applyPatch(self, origFile, patchFilename):
|
||||
""" Applies the named patch to the indicated original
|
||||
file, storing the results in a temporary file, and returns
|
||||
that temporary Filename. Returns None on failure. """
|
||||
|
||||
result = Filename.temporary('', 'patch_')
|
||||
p = Patchfile()
|
||||
if not p.apply(patchFilename, origFile, result):
|
||||
print("Internal patching failed: %s" % (patchFilename))
|
||||
return None
|
||||
|
||||
return result
|
||||
|
||||
def getNext(self, package):
|
||||
""" Gets the next patch in the chain towards this
|
||||
package. """
|
||||
for patch in self.toPatches:
|
||||
if patch.packageName == package.packageName and \
|
||||
patch.platform == package.platform and \
|
||||
patch.version == package.version and \
|
||||
patch.hostUrl == package.hostUrl:
|
||||
return patch.toPv
|
||||
|
||||
return None
|
||||
|
||||
class Patchfile:
|
||||
""" A single patchfile for a package. """
|
||||
|
||||
def __init__(self, package):
|
||||
self.package = package
|
||||
self.packageName = package.packageName
|
||||
self.platform = package.platform
|
||||
self.version = package.version
|
||||
self.hostUrl = None
|
||||
|
||||
# FileSpec for the patchfile itself
|
||||
self.file = None
|
||||
|
||||
# FileSpec for the package file that the patch is applied to
|
||||
self.sourceFile = None
|
||||
|
||||
# FileSpec for the package file that the patch generates
|
||||
self.targetFile = None
|
||||
|
||||
# The PackageVersion corresponding to our sourceFile
|
||||
self.fromPv = None
|
||||
|
||||
# The PackageVersion corresponding to our targetFile
|
||||
self.toPv = None
|
||||
|
||||
def getSourceKey(self):
|
||||
""" Returns the key for locating the package that this
|
||||
patchfile can be applied to. """
|
||||
return (self.packageName, self.platform, self.version, self.hostUrl, self.sourceFile)
|
||||
|
||||
def getTargetKey(self):
|
||||
""" Returns the key for locating the package that this
|
||||
patchfile will generate. """
|
||||
return (self.packageName, self.platform, self.version, self.hostUrl, self.targetFile)
|
||||
|
||||
def fromFile(self, packageDir, patchFilename, sourceFile, targetFile):
|
||||
""" Creates the data structures from an existing patchfile
|
||||
on disk. """
|
||||
|
||||
self.file = FileSpec()
|
||||
self.file.fromFile(packageDir, patchFilename)
|
||||
self.sourceFile = sourceFile
|
||||
self.targetFile = targetFile
|
||||
|
||||
def loadXml(self, xpatch):
|
||||
""" Reads the data structures from an xml file. """
|
||||
|
||||
self.packageName = xpatch.Attribute('name') or self.packageName
|
||||
self.platform = xpatch.Attribute('platform') or self.platform
|
||||
self.version = xpatch.Attribute('version') or self.version
|
||||
self.hostUrl = xpatch.Attribute('host') or self.hostUrl
|
||||
|
||||
self.file = FileSpec()
|
||||
self.file.loadXml(xpatch)
|
||||
|
||||
xsource = xpatch.FirstChildElement('source')
|
||||
if xsource:
|
||||
self.sourceFile = FileSpec()
|
||||
self.sourceFile.loadXml(xsource)
|
||||
|
||||
xtarget = xpatch.FirstChildElement('target')
|
||||
if xtarget:
|
||||
self.targetFile = FileSpec()
|
||||
self.targetFile.loadXml(xtarget)
|
||||
|
||||
def makeXml(self, package):
|
||||
xpatch = TiXmlElement('patch')
|
||||
|
||||
if self.packageName != package.packageName:
|
||||
xpatch.SetAttribute('name', self.packageName)
|
||||
if self.platform != package.platform:
|
||||
xpatch.SetAttribute('platform', self.platform)
|
||||
if self.version != package.version:
|
||||
xpatch.SetAttribute('version', self.version)
|
||||
if self.hostUrl != package.hostUrl:
|
||||
xpatch.SetAttribute('host', self.hostUrl)
|
||||
|
||||
self.file.storeXml(xpatch)
|
||||
|
||||
xsource = TiXmlElement('source')
|
||||
self.sourceFile.storeMiniXml(xsource)
|
||||
xpatch.InsertEndChild(xsource)
|
||||
|
||||
xtarget = TiXmlElement('target')
|
||||
self.targetFile.storeMiniXml(xtarget)
|
||||
xpatch.InsertEndChild(xtarget)
|
||||
|
||||
return xpatch
|
||||
|
||||
class Package:
|
||||
""" This is a particular package. This contains all of the
|
||||
information needed to reconstruct the package's desc file. """
|
||||
|
||||
def __init__(self, packageDesc, patchMaker, xpackage = None):
|
||||
self.packageDir = Filename(patchMaker.installDir, packageDesc.getDirname())
|
||||
self.packageDesc = packageDesc
|
||||
self.patchMaker = patchMaker
|
||||
self.contentsDocPackage = xpackage
|
||||
self.patchVersion = 1
|
||||
self.currentPv = None
|
||||
self.basePv = None
|
||||
self.topPv = None
|
||||
|
||||
self.packageName = None
|
||||
self.platform = None
|
||||
self.version = None
|
||||
self.hostUrl = None
|
||||
self.currentFile = None
|
||||
self.baseFile = None
|
||||
|
||||
self.doc = None
|
||||
self.anyChanges = False
|
||||
self.patches = []
|
||||
|
||||
def getCurrentKey(self):
|
||||
""" Returns the key to locate the current version of this
|
||||
package. """
|
||||
|
||||
return (self.packageName, self.platform, self.version, self.hostUrl, self.currentFile)
|
||||
|
||||
def getBaseKey(self):
|
||||
""" Returns the key to locate the "base" or oldest version
|
||||
of this package. """
|
||||
|
||||
return (self.packageName, self.platform, self.version, self.hostUrl, self.baseFile)
|
||||
|
||||
def getTopKey(self):
|
||||
""" Returns the key to locate the "top" or newest version
|
||||
of this package. """
|
||||
|
||||
return (self.packageName, self.platform, self.version, self.hostUrl, self.topFile)
|
||||
|
||||
def getGenericKey(self, fileSpec):
|
||||
""" Returns the key that has the indicated hash. """
|
||||
return (self.packageName, self.platform, self.version, self.hostUrl, fileSpec)
|
||||
|
||||
def readDescFile(self, doProcessing = False):
|
||||
""" Reads the existing package.xml file and stores it in
|
||||
this class for later rewriting. if doProcessing is true,
|
||||
it may massage the file and the directory contents in
|
||||
preparation for building patches. Returns true on
|
||||
success, false on failure. """
|
||||
|
||||
self.anyChanges = False
|
||||
|
||||
packageDescFullpath = Filename(self.patchMaker.installDir, self.packageDesc)
|
||||
self.doc = TiXmlDocument(packageDescFullpath.toOsSpecific())
|
||||
if not self.doc.LoadFile():
|
||||
print("Couldn't read %s" % (packageDescFullpath))
|
||||
return False
|
||||
|
||||
xpackage = self.doc.FirstChildElement('package')
|
||||
if not xpackage:
|
||||
return False
|
||||
self.packageName = xpackage.Attribute('name')
|
||||
self.platform = xpackage.Attribute('platform')
|
||||
self.version = xpackage.Attribute('version')
|
||||
|
||||
# All packages we defined in-line are assigned to the
|
||||
# "none" host. TODO: support patching from packages on
|
||||
# other hosts, which means we'll need to fill in a value
|
||||
# here for those hosts.
|
||||
self.hostUrl = None
|
||||
|
||||
self.currentFile = None
|
||||
self.baseFile = None
|
||||
self.topFile = None
|
||||
self.compressedFilename = None
|
||||
compressedFile = None
|
||||
|
||||
# Assume there are changes for this version, until we
|
||||
# discover that there aren't.
|
||||
isNewVersion = True
|
||||
|
||||
# Get the actual current version.
|
||||
xarchive = xpackage.FirstChildElement('uncompressed_archive')
|
||||
if xarchive:
|
||||
self.currentFile = FileSpec()
|
||||
self.currentFile.loadXml(xarchive)
|
||||
|
||||
# Get the top_version--the top (newest) of the patch
|
||||
# chain.
|
||||
xarchive = xpackage.FirstChildElement('top_version')
|
||||
if xarchive:
|
||||
self.topFile = FileSpec()
|
||||
self.topFile.loadXml(xarchive)
|
||||
|
||||
if self.topFile.hash == self.currentFile.hash:
|
||||
# No new version this pass.
|
||||
isNewVersion = False
|
||||
else:
|
||||
# There's a new version this pass. Update it.
|
||||
self.anyChanges = True
|
||||
|
||||
else:
|
||||
# If there isn't a top_version yet, we have to make
|
||||
# one, by duplicating the currentFile.
|
||||
self.topFile = copy.copy(self.currentFile)
|
||||
self.anyChanges = True
|
||||
|
||||
# Get the current patch version. If we have a
|
||||
# patch_version attribute, it refers to this particular
|
||||
# instance of the file, and that is the current patch
|
||||
# version number. If we only have a last_patch_version
|
||||
# attribute, it means a patch has not yet been built for
|
||||
# this particular instance, and that number is the
|
||||
# previous version's patch version number.
|
||||
patchVersion = xpackage.Attribute('patch_version')
|
||||
if patchVersion:
|
||||
self.patchVersion = int(patchVersion)
|
||||
else:
|
||||
patchVersion = xpackage.Attribute('last_patch_version')
|
||||
if patchVersion:
|
||||
self.patchVersion = int(patchVersion)
|
||||
if isNewVersion:
|
||||
self.patchVersion += 1
|
||||
self.anyChanges = True
|
||||
|
||||
# Put the patchVersion in the compressed filename, for
|
||||
# cache-busting. This means when the version changes, its
|
||||
# URL will also change, guaranteeing that users will
|
||||
# download the latest version, and not some stale cache
|
||||
# file.
|
||||
xcompressed = xpackage.FirstChildElement('compressed_archive')
|
||||
if xcompressed:
|
||||
compressedFile = FileSpec()
|
||||
compressedFile.loadXml(xcompressed)
|
||||
|
||||
oldCompressedFilename = compressedFile.filename
|
||||
self.compressedFilename = oldCompressedFilename
|
||||
|
||||
if doProcessing:
|
||||
newCompressedFilename = '%s.%s.pz' % (self.currentFile.filename, self.patchVersion)
|
||||
if newCompressedFilename != oldCompressedFilename:
|
||||
oldCompressedPathname = Filename(self.packageDir, oldCompressedFilename)
|
||||
newCompressedPathname = Filename(self.packageDir, newCompressedFilename)
|
||||
if oldCompressedPathname.renameTo(newCompressedPathname):
|
||||
compressedFile.fromFile(self.packageDir, newCompressedFilename)
|
||||
compressedFile.storeXml(xcompressed)
|
||||
|
||||
self.compressedFilename = newCompressedFilename
|
||||
self.anyChanges = True
|
||||
|
||||
# Get the base_version--the bottom (oldest) of the patch
|
||||
# chain.
|
||||
xarchive = xpackage.FirstChildElement('base_version')
|
||||
if xarchive:
|
||||
self.baseFile = FileSpec()
|
||||
self.baseFile.loadXml(xarchive)
|
||||
else:
|
||||
# If there isn't a base_version yet, we have to make
|
||||
# one, by duplicating the currentFile.
|
||||
self.baseFile = copy.copy(self.currentFile)
|
||||
|
||||
# Note that the we only store the compressed version
|
||||
# of base_filename on disk, but we store the md5 of
|
||||
# the uncompressed version in the xml file. To
|
||||
# emphasize this, we name it without the .pz extension
|
||||
# in the xml file, even though the compressed file on
|
||||
# disk actually has a .pz extension.
|
||||
self.baseFile.filename += '.base'
|
||||
|
||||
# Also duplicate the (compressed) file itself.
|
||||
if doProcessing and self.compressedFilename:
|
||||
fromPathname = Filename(self.packageDir, self.compressedFilename)
|
||||
toPathname = Filename(self.packageDir, self.baseFile.filename + '.pz')
|
||||
fromPathname.copyTo(toPathname)
|
||||
self.anyChanges = True
|
||||
|
||||
self.patches = []
|
||||
xpatch = xpackage.FirstChildElement('patch')
|
||||
while xpatch:
|
||||
patchfile = PatchMaker.Patchfile(self)
|
||||
patchfile.loadXml(xpatch)
|
||||
self.patches.append(patchfile)
|
||||
xpatch = xpatch.NextSiblingElement('patch')
|
||||
|
||||
return True
|
||||
|
||||
def writeDescFile(self):
|
||||
""" Rewrites the desc file with the new patch
|
||||
information. """
|
||||
|
||||
if not self.anyChanges:
|
||||
# No need to rewrite.
|
||||
return
|
||||
|
||||
xpackage = self.doc.FirstChildElement('package')
|
||||
if not xpackage:
|
||||
return
|
||||
|
||||
packageSeq = SeqValue()
|
||||
packageSeq.loadXml(xpackage, 'seq')
|
||||
packageSeq += 1
|
||||
packageSeq.storeXml(xpackage, 'seq')
|
||||
|
||||
# Remove all of the old patch entries from the desc file
|
||||
# we read earlier.
|
||||
xremove = []
|
||||
for value in ['base_version', 'top_version', 'patch']:
|
||||
xpatch = xpackage.FirstChildElement(value)
|
||||
while xpatch:
|
||||
xremove.append(xpatch)
|
||||
xpatch = xpatch.NextSiblingElement(value)
|
||||
|
||||
for xelement in xremove:
|
||||
xpackage.RemoveChild(xelement)
|
||||
|
||||
xpackage.RemoveAttribute('last_patch_version')
|
||||
|
||||
# Now replace them with the current patch information.
|
||||
xpackage.SetAttribute('patch_version', str(self.patchVersion))
|
||||
|
||||
xarchive = TiXmlElement('base_version')
|
||||
self.baseFile.storeXml(xarchive)
|
||||
xpackage.InsertEndChild(xarchive)
|
||||
|
||||
# The current version is now the top version.
|
||||
xarchive = TiXmlElement('top_version')
|
||||
self.currentFile.storeXml(xarchive)
|
||||
xpackage.InsertEndChild(xarchive)
|
||||
|
||||
for patchfile in self.patches:
|
||||
xpatch = patchfile.makeXml(self)
|
||||
xpackage.InsertEndChild(xpatch)
|
||||
|
||||
self.doc.SaveFile()
|
||||
|
||||
# Also copy the seq to the import desc file, for
|
||||
# documentation purposes.
|
||||
|
||||
importDescFilename = str(self.packageDesc)[:-3] + 'import.xml'
|
||||
importDescFullpath = Filename(self.patchMaker.installDir, importDescFilename)
|
||||
doc = TiXmlDocument(importDescFullpath.toOsSpecific())
|
||||
if doc.LoadFile():
|
||||
xpackage = doc.FirstChildElement('package')
|
||||
if xpackage:
|
||||
packageSeq.storeXml(xpackage, 'seq')
|
||||
doc.SaveFile()
|
||||
else:
|
||||
print("Couldn't read %s" % (importDescFullpath))
|
||||
|
||||
if self.contentsDocPackage:
|
||||
# Now that we've rewritten the xml file, we have to
|
||||
# change the contents.xml file that references it to
|
||||
# indicate the new file hash.
|
||||
fileSpec = FileSpec()
|
||||
fileSpec.fromFile(self.patchMaker.installDir, self.packageDesc)
|
||||
fileSpec.storeXml(self.contentsDocPackage)
|
||||
|
||||
# Also important to update the import.xml hash.
|
||||
ximport = self.contentsDocPackage.FirstChildElement('import')
|
||||
if ximport:
|
||||
fileSpec = FileSpec()
|
||||
fileSpec.fromFile(self.patchMaker.installDir, importDescFilename)
|
||||
fileSpec.storeXml(ximport)
|
||||
|
||||
# Also copy the package seq value into the
|
||||
# contents.xml file, mainly for documentation purposes
|
||||
# (the authoritative seq value is within the desc
|
||||
# file).
|
||||
packageSeq.storeXml(self.contentsDocPackage, 'seq')
|
||||
|
||||
|
||||
# PatchMaker constructor.
|
||||
def __init__(self, installDir):
|
||||
self.installDir = installDir
|
||||
self.packageVersions = {}
|
||||
self.packages = []
|
||||
|
||||
def buildPatches(self, packageNames = None):
|
||||
""" Makes the patches required in a particular directory
|
||||
structure on disk. If packageNames is None, this makes
|
||||
patches for all packages; otherwise, it should be a list of
|
||||
package name strings, limiting the set of packages that are
|
||||
processed. """
|
||||
|
||||
if not self.readContentsFile():
|
||||
return False
|
||||
self.buildPatchChains()
|
||||
if packageNames is None:
|
||||
self.processAllPackages()
|
||||
else:
|
||||
self.processSomePackages(packageNames)
|
||||
|
||||
self.writeContentsFile()
|
||||
self.cleanup()
|
||||
return True
|
||||
|
||||
def cleanup(self):
|
||||
""" Should be called on exit to remove temporary files and
|
||||
such created during processing. """
|
||||
|
||||
for pv in self.packageVersions.values():
|
||||
pv.cleanup()
|
||||
|
||||
def getPatchChainToCurrent(self, descFilename, fileSpec):
|
||||
""" Reads the package defined in the indicated desc file, and
|
||||
constructs a patch chain from the version represented by
|
||||
fileSpec to the current version of this package, if possible.
|
||||
Returns the patch chain if successful, or None otherwise. """
|
||||
|
||||
package = self.readPackageDescFile(descFilename)
|
||||
if not package:
|
||||
return None
|
||||
|
||||
self.buildPatchChains()
|
||||
fromPv = self.getPackageVersion(package.getGenericKey(fileSpec))
|
||||
toPv = package.currentPv
|
||||
|
||||
patchChain = None
|
||||
if toPv and fromPv:
|
||||
patchChain = toPv.getPatchChain(fromPv)
|
||||
|
||||
return patchChain
|
||||
|
||||
def readPackageDescFile(self, descFilename):
|
||||
""" Reads a desc file associated with a particular package,
|
||||
and adds the package to self.packages. Returns the Package
|
||||
object, or None on failure. """
|
||||
|
||||
package = self.Package(Filename(descFilename), self)
|
||||
if not package.readDescFile(doProcessing = False):
|
||||
return None
|
||||
|
||||
self.packages.append(package)
|
||||
return package
|
||||
|
||||
def readContentsFile(self):
|
||||
""" Reads the contents.xml file at the beginning of
|
||||
processing. """
|
||||
|
||||
contentsFilename = Filename(self.installDir, 'contents.xml')
|
||||
doc = TiXmlDocument(contentsFilename.toOsSpecific())
|
||||
if not doc.LoadFile():
|
||||
# Couldn't read file.
|
||||
print("couldn't read %s" % (contentsFilename))
|
||||
return False
|
||||
|
||||
xcontents = doc.FirstChildElement('contents')
|
||||
if xcontents:
|
||||
contentsSeq = SeqValue()
|
||||
contentsSeq.loadXml(xcontents)
|
||||
contentsSeq += 1
|
||||
contentsSeq.storeXml(xcontents)
|
||||
|
||||
xpackage = xcontents.FirstChildElement('package')
|
||||
while xpackage:
|
||||
solo = xpackage.Attribute('solo')
|
||||
solo = int(solo or '0')
|
||||
filename = xpackage.Attribute('filename')
|
||||
if filename and not solo:
|
||||
filename = Filename(filename)
|
||||
package = self.Package(filename, self, xpackage)
|
||||
package.readDescFile(doProcessing = True)
|
||||
self.packages.append(package)
|
||||
|
||||
xpackage = xpackage.NextSiblingElement('package')
|
||||
|
||||
self.contentsDoc = doc
|
||||
|
||||
return True
|
||||
|
||||
def writeContentsFile(self):
|
||||
""" Writes the contents.xml file at the end of processing. """
|
||||
|
||||
# We also have to write the desc file for all packages that
|
||||
# might need it, because we might have changed some of them on
|
||||
# read.
|
||||
for package in self.packages:
|
||||
package.writeDescFile()
|
||||
|
||||
# The above writeDescFile() call should also update each
|
||||
# package's element within the contents.xml document, so all
|
||||
# we have to do now is write out the document.
|
||||
self.contentsDoc.SaveFile()
|
||||
|
||||
def getPackageVersion(self, key):
|
||||
""" Returns a shared PackageVersion object for the indicated
|
||||
key. """
|
||||
|
||||
packageName, platform, version, hostUrl, file = key
|
||||
|
||||
# We actually key on the hash, not the FileSpec itself.
|
||||
k = (packageName, platform, version, hostUrl, file.hash)
|
||||
pv = self.packageVersions.get(k, None)
|
||||
if not pv:
|
||||
pv = self.PackageVersion(*key)
|
||||
self.packageVersions[k] = pv
|
||||
return pv
|
||||
|
||||
def buildPatchChains(self):
|
||||
""" Builds up the chains of PackageVersions and the patchfiles
|
||||
that connect them. """
|
||||
|
||||
self.patchFilenames = {}
|
||||
|
||||
for package in self.packages:
|
||||
if not package.baseFile:
|
||||
# This package doesn't have any versions yet.
|
||||
continue
|
||||
|
||||
currentPv = self.getPackageVersion(package.getCurrentKey())
|
||||
package.currentPv = currentPv
|
||||
currentPv.packageCurrent = package
|
||||
currentPv.printName = package.currentFile.filename
|
||||
|
||||
basePv = self.getPackageVersion(package.getBaseKey())
|
||||
package.basePv = basePv
|
||||
basePv.packageBase = package
|
||||
basePv.printName = package.baseFile.filename
|
||||
|
||||
topPv = self.getPackageVersion(package.getTopKey())
|
||||
package.topPv = topPv
|
||||
topPv.packageTop = package
|
||||
|
||||
for patchfile in package.patches:
|
||||
self.recordPatchfile(patchfile)
|
||||
|
||||
def recordPatchfile(self, patchfile):
|
||||
""" Adds the indicated patchfile to the patch chains. """
|
||||
self.patchFilenames[patchfile.file.filename] = patchfile
|
||||
|
||||
fromPv = self.getPackageVersion(patchfile.getSourceKey())
|
||||
patchfile.fromPv = fromPv
|
||||
fromPv.toPatches.append(patchfile)
|
||||
|
||||
toPv = self.getPackageVersion(patchfile.getTargetKey())
|
||||
patchfile.toPv = toPv
|
||||
toPv.fromPatches.append(patchfile)
|
||||
toPv.printName = patchfile.file.filename
|
||||
|
||||
def processSomePackages(self, packageNames):
|
||||
""" Builds missing patches only for the named packages. """
|
||||
|
||||
remainingNames = packageNames[:]
|
||||
for package in self.packages:
|
||||
if package.packageName in packageNames:
|
||||
self.processPackage(package)
|
||||
if package.packageName in remainingNames:
|
||||
remainingNames.remove(package.packageName)
|
||||
|
||||
if remainingNames:
|
||||
print("Unknown packages: %s" % (remainingNames,))
|
||||
|
||||
def processAllPackages(self):
|
||||
""" Walks through the list of packages, and builds missing
|
||||
patches for each one. """
|
||||
|
||||
for package in self.packages:
|
||||
self.processPackage(package)
|
||||
|
||||
def processPackage(self, package):
|
||||
""" Builds missing patches for the indicated package. """
|
||||
|
||||
if not package.baseFile:
|
||||
# No versions.
|
||||
return
|
||||
|
||||
# What's the current version on the top of the tree?
|
||||
topPv = package.topPv
|
||||
currentPv = package.currentPv
|
||||
|
||||
if topPv != currentPv:
|
||||
# They're different, so build a new patch.
|
||||
filename = Filename(package.currentFile.filename + '.%s.patch' % (package.patchVersion))
|
||||
assert filename not in self.patchFilenames
|
||||
if not self.buildPatch(topPv, currentPv, package, filename):
|
||||
raise Exception("Couldn't build patch.")
|
||||
|
||||
def buildPatch(self, v1, v2, package, patchFilename):
|
||||
""" Builds a patch from PackageVersion v1 to PackageVersion
|
||||
v2, and stores it in patchFilename.pz. Returns true on
|
||||
success, false on failure."""
|
||||
|
||||
pathname = Filename(package.packageDir, patchFilename)
|
||||
if not self.buildPatchFile(v1.getFile(), v2.getFile(), pathname,
|
||||
v1.printName, v2.printName):
|
||||
return False
|
||||
|
||||
compressedPathname = Filename(pathname + '.pz')
|
||||
compressedPathname.unlink()
|
||||
if not compressFile(pathname, compressedPathname, 9):
|
||||
raise Exception("Couldn't compress patch.")
|
||||
pathname.unlink()
|
||||
|
||||
patchfile = self.Patchfile(package)
|
||||
patchfile.fromFile(package.packageDir, patchFilename + '.pz',
|
||||
v1.file, v2.file)
|
||||
package.patches.append(patchfile)
|
||||
package.anyChanges = True
|
||||
|
||||
self.recordPatchfile(patchfile)
|
||||
|
||||
return True
|
||||
|
||||
def buildPatchFile(self, origFilename, newFilename, patchFilename,
|
||||
printOrigName, printNewName):
|
||||
""" Creates a patch file from origFilename to newFilename,
|
||||
storing the result in patchFilename. Returns true on success,
|
||||
false on failure. """
|
||||
|
||||
if not origFilename.exists():
|
||||
# No original version to patch from.
|
||||
return False
|
||||
|
||||
print("Building patch from %s to %s" % (printOrigName, printNewName))
|
||||
patchFilename.unlink()
|
||||
p = Patchfile() # The C++ class
|
||||
if p.build(origFilename, newFilename, patchFilename):
|
||||
return True
|
||||
|
||||
# Unable to build a patch for some reason.
|
||||
patchFilename.unlink()
|
||||
return False
|
||||
|
|
@ -1,95 +0,0 @@
|
|||
__all__ = ["ScanDirectoryNode"]
|
||||
|
||||
from panda3d.core import VirtualFileSystem, VirtualFileMountSystem, Filename, TiXmlDocument
|
||||
vfs = VirtualFileSystem.getGlobalPtr()
|
||||
|
||||
class ScanDirectoryNode:
|
||||
""" This class is used to scan a list of files on disk. """
|
||||
|
||||
def __init__(self, pathname, ignoreUsageXml = False):
|
||||
self.pathname = pathname
|
||||
self.filenames = []
|
||||
self.fileSize = 0
|
||||
self.nested = []
|
||||
self.nestedSize = 0
|
||||
|
||||
xusage = None
|
||||
if not ignoreUsageXml:
|
||||
# Look for a usage.xml file in this directory. If we find
|
||||
# one, we read it for the file size and then stop here, as
|
||||
# an optimization.
|
||||
usageFilename = Filename(pathname, 'usage.xml')
|
||||
doc = TiXmlDocument(usageFilename.toOsSpecific())
|
||||
if doc.LoadFile():
|
||||
xusage = doc.FirstChildElement('usage')
|
||||
if xusage:
|
||||
diskSpace = xusage.Attribute('disk_space')
|
||||
try:
|
||||
diskSpace = int(diskSpace or '')
|
||||
except ValueError:
|
||||
diskSpace = None
|
||||
if diskSpace is not None:
|
||||
self.fileSize = diskSpace
|
||||
return
|
||||
|
||||
files = vfs.scanDirectory(self.pathname)
|
||||
if files is None:
|
||||
files = []
|
||||
for vfile in files:
|
||||
if hasattr(vfile, 'getMount'):
|
||||
if not isinstance(vfile.getMount(), VirtualFileMountSystem):
|
||||
# Not a real file; ignore it.
|
||||
continue
|
||||
|
||||
if vfile.isDirectory():
|
||||
# A nested directory.
|
||||
subdir = ScanDirectoryNode(vfile.getFilename(), ignoreUsageXml = ignoreUsageXml)
|
||||
self.nested.append(subdir)
|
||||
self.nestedSize += subdir.getTotalSize()
|
||||
|
||||
elif vfile.isRegularFile():
|
||||
# A nested file.
|
||||
self.filenames.append(vfile.getFilename())
|
||||
self.fileSize += vfile.getFileSize()
|
||||
|
||||
else:
|
||||
# Some other wacky file thing.
|
||||
self.filenames.append(vfile.getFilename())
|
||||
|
||||
if xusage:
|
||||
# Now update the usage.xml file with the newly-determined
|
||||
# disk space.
|
||||
xusage.SetAttribute('disk_space', str(self.getTotalSize()))
|
||||
tfile = Filename.temporary(str(pathname), '.xml')
|
||||
if doc.SaveFile(tfile.toOsSpecific()):
|
||||
tfile.renameTo(usageFilename)
|
||||
|
||||
def getTotalSize(self):
|
||||
return self.nestedSize + self.fileSize
|
||||
|
||||
def extractSubdir(self, pathname):
|
||||
""" Finds the ScanDirectoryNode within this node that
|
||||
corresponds to the indicated full pathname. If it is found,
|
||||
removes it from its parent, and returns it. If it is not
|
||||
found, returns None. """
|
||||
|
||||
# We could be a little smarter here, but why bother. Just
|
||||
# recursively search all children.
|
||||
for subdir in self.nested:
|
||||
if subdir.pathname == pathname:
|
||||
self.nested.remove(subdir)
|
||||
self.nestedSize -= subdir.getTotalSize()
|
||||
return subdir
|
||||
|
||||
result = subdir.extractSubdir(pathname)
|
||||
if result:
|
||||
self.nestedSize -= result.getTotalSize()
|
||||
if subdir.getTotalSize() == 0:
|
||||
# No other files in the subdirectory that contains
|
||||
# this package; remove it too.
|
||||
self.nested.remove(subdir)
|
||||
return result
|
||||
|
||||
return None
|
||||
|
||||
|
||||
|
|
@ -1,90 +0,0 @@
|
|||
__all__ = ["SeqValue"]
|
||||
|
||||
class SeqValue:
|
||||
|
||||
""" This represents a sequence value read from a contents.xml
|
||||
file, either from the <contents> or the <package> section. It's
|
||||
represented as series of dotted integers in the xml file, and
|
||||
stored internally as a tuple of integers.
|
||||
|
||||
It may be incremented, which increments only the last integer in
|
||||
the series; or it may be compared with another SeqValue, which
|
||||
compares all of the integers componentwise. """
|
||||
|
||||
def __init__(self, value = None):
|
||||
self.value = ()
|
||||
if value is not None:
|
||||
self.set(value)
|
||||
|
||||
def set(self, value):
|
||||
""" Sets the seq from the indicated value of unspecified
|
||||
type. """
|
||||
if isinstance(value, tuple):
|
||||
self.setFromTuple(value)
|
||||
elif isinstance(value, str):
|
||||
self.setFromString(value)
|
||||
else:
|
||||
raise TypeError('Invalid sequence type: %s' % (value,))
|
||||
|
||||
def setFromTuple(self, value):
|
||||
""" Sets the seq from the indicated tuple of integers. """
|
||||
assert isinstance(value, tuple)
|
||||
self.value = value
|
||||
|
||||
def setFromString(self, value):
|
||||
""" Sets the seq from the indicated string of dot-separated
|
||||
integers. Raises ValueError on error. """
|
||||
assert isinstance(value, str)
|
||||
|
||||
self.value = ()
|
||||
if value:
|
||||
value = value.split('.')
|
||||
value = map(int, value)
|
||||
self.value = tuple(value)
|
||||
|
||||
def loadXml(self, xelement, attribute = 'seq'):
|
||||
""" Reads the seq from the indicated XML element. Returns
|
||||
true if loaded, false if not given or if there was an
|
||||
error. """
|
||||
|
||||
self.value = ()
|
||||
value = xelement.Attribute(attribute)
|
||||
if value:
|
||||
try:
|
||||
self.setFromString(value)
|
||||
except ValueError:
|
||||
return False
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def storeXml(self, xelement, attribute = 'seq'):
|
||||
""" Adds the seq to the indicated XML element. """
|
||||
if self.value:
|
||||
value = '.'.join(map(str, self.value))
|
||||
xelement.SetAttribute(attribute, value)
|
||||
|
||||
def __add__(self, inc):
|
||||
""" Increments the seq value, returning the new value. """
|
||||
if not self.value:
|
||||
value = (1,)
|
||||
else:
|
||||
value = self.value[:-1] + (self.value[-1] + inc,)
|
||||
return SeqValue(value)
|
||||
|
||||
def __cmp__(self, other):
|
||||
""" Compares to another seq value. """
|
||||
return cmp(self.value, other.value)
|
||||
|
||||
def __lt__(self, other):
|
||||
return self.value < other.value
|
||||
|
||||
def __gt__(self, other):
|
||||
return self.value > other.value
|
||||
|
||||
def __bool__(self):
|
||||
return bool(self.value)
|
||||
|
||||
def __str__(self):
|
||||
return 'SeqValue%s' % (repr(self.value))
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
"""
|
||||
This package provides the Python interface to functionality relating to
|
||||
the Panda3D Runtime environment.
|
||||
"""
|
||||
|
|
@ -1,124 +0,0 @@
|
|||
from panda3d.core import getModelPath, Filename, ConfigVariableFilename, DSearchPath, ExecutionEnvironment, PandaSystem
|
||||
|
||||
# This file defines a number of standard "packages" that correspond to
|
||||
# a Panda3D distribution. These packages are built by passing this
|
||||
# file to the ppackage utility, either as a packaged application, or
|
||||
# as the module direct.p3d.ppackage.
|
||||
|
||||
# The packages in this file define the "Core API". This is the second
|
||||
# installed piece of the three-part plugin system (the plugin, the
|
||||
# core API, Panda3D).
|
||||
|
||||
# These packages are downloaded directly by the plugin, from the host
|
||||
# specified by the value of PANDA_PACKAGE_HOST_URL compiled into the
|
||||
# plugin. Thus, these packages are inextricably tied to the
|
||||
# particular plugin they have been built with. They do not have to be
|
||||
# present on a server that hosts a version of Panda3D for download,
|
||||
# just on the server that hosts the plugin itself. These packages do
|
||||
# not need to be updated with each new version of Panda3D.
|
||||
|
||||
# Also see panda3d.pdef.
|
||||
|
||||
class coreapi(solo):
|
||||
# The special "coreapi" package. As a "solo", this is just a
|
||||
# single .dll (or dylib, or whatever).
|
||||
setVer(PandaSystem.getP3dCoreapiVersionString())
|
||||
file('p3d_plugin.dll')
|
||||
|
||||
class images(package):
|
||||
# The default startup images are stored as their own package.
|
||||
names = ['download', 'failed', 'play_click', 'play_ready', 'play_rollover',
|
||||
'auth_click', 'auth_ready', 'auth_rollover']
|
||||
configDict = {}
|
||||
|
||||
# Construct a search path to look for the images.
|
||||
search = DSearchPath()
|
||||
|
||||
# First on the path: an explicit $PLUGIN_IMAGES env var.
|
||||
if ExecutionEnvironment.hasEnvironmentVariable('PLUGIN_IMAGES'):
|
||||
search.appendDirectory(Filename.expandFrom('$PLUGIN_IMAGES'))
|
||||
|
||||
# Next on the path: the models/plugin_images directory within the
|
||||
# current directory.
|
||||
search.appendDirectory('models/plugin_images')
|
||||
|
||||
# Finally on the path: models/plugin_images within the model
|
||||
# search path.
|
||||
for dir in getModelPath().getDirectories():
|
||||
search.appendDirectory(Filename(dir, 'plugin_images'))
|
||||
|
||||
for name in names:
|
||||
# Look for a png image first.
|
||||
basename = '%s.png' % (name)
|
||||
filename = Filename(basename)
|
||||
found = filename.resolveFilename(search)
|
||||
if not found:
|
||||
# Then try a jpeg image.
|
||||
basename = '%s.jpg' % (name)
|
||||
filename = Filename(basename)
|
||||
found = filename.resolveFilename(search)
|
||||
|
||||
if found:
|
||||
# Add the image file to the package
|
||||
file(filename, newName = basename, extract = True)
|
||||
|
||||
# And set the config variable to reference it.
|
||||
token = '%s_img' % (name)
|
||||
configDict[token] = basename
|
||||
else:
|
||||
print("Could not locate %s" % (filename))
|
||||
|
||||
# Also make a few special cases. We use the same default image
|
||||
# for many of the states.
|
||||
download = configDict.get('download_img', None)
|
||||
if download:
|
||||
configDict['ready_img'] = download
|
||||
configDict['unauth_img'] = download
|
||||
configDict['launch_img'] = download
|
||||
configDict['active_img'] = download
|
||||
|
||||
config(**configDict)
|
||||
|
||||
class certlist(package):
|
||||
# This package holds any certificates that should be considered
|
||||
# pre-approved by the plugin vendor. If you build and host your
|
||||
# own version of the Panda3D plugin, you can add your own
|
||||
# certificates to this list. The certificates in this package
|
||||
# will be assumed to have been approved by the user when he/she
|
||||
# installed the plugin.
|
||||
|
||||
# They should be PEM-encoded and their filenames must end in the
|
||||
# ".pem" or ".crt" extension, and they should be added with the
|
||||
# extract = True flag so they will be extracted to disk.
|
||||
pass
|
||||
|
||||
|
||||
class p3dcert(package):
|
||||
# This special application, used to pop up a dialog to prompt the
|
||||
# user to accept or deny unknown applications, is its own package.
|
||||
config(display_name = "Authorization Dialog")
|
||||
|
||||
if platform.startswith('osx'):
|
||||
# On Mac, we package up a P3DCert.app bundle. This includes
|
||||
# specifications in the plist file to avoid creating a dock
|
||||
# icon and stuff.
|
||||
|
||||
# Find p3dcert.plist in the direct source tree.
|
||||
import direct
|
||||
plist = Filename(direct.__path__[0], 'plugin/p3dcert.plist')
|
||||
makeBundle('P3DCert.app', plist, executable = 'p3dcert')
|
||||
|
||||
else:
|
||||
# Anywhere else, we just ship the executable file p3dcert.exe.
|
||||
file('p3dcert.exe', required = True)
|
||||
|
||||
|
||||
class p3dembed(package):
|
||||
# This contains the p3dembed executable, that is used by
|
||||
# pdeploy to generate a self-extracting .p3d executable.
|
||||
|
||||
config(platform_specific = True)
|
||||
file('p3dembed.exe', required = True)
|
||||
|
||||
if platform.startswith('win'):
|
||||
file('p3dembedw.exe', required = True)
|
||||
|
|
@ -1,127 +0,0 @@
|
|||
/* Filename: p3dWrapper.c
|
||||
* Created by: rdb (16Jan10)
|
||||
*
|
||||
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
*
|
||||
* 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."
|
||||
*
|
||||
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
||||
/* p3dWrapper is a small wrapper executable that locates a .p3d file
|
||||
in the same directory as this executable file, with the same name
|
||||
(except .p3d instead of .exe of course). It is only meant to be
|
||||
used on Windows system, it is not needed on Unix-like systems. */
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <windows.h>
|
||||
#include <process.h>
|
||||
#include <assert.h>
|
||||
#include <malloc.h>
|
||||
|
||||
#define BUFFER_SIZE 1024
|
||||
|
||||
/* It makes sense to use "App Paths\panda3d.exe". However, Microsoft
|
||||
decided in their infinite wisdom to disable Redirection for that
|
||||
key from Windows 7 onward, so we can't rely on it producing a
|
||||
result appropriate to the right architecture when both the 32-bit
|
||||
and 64-bit versions of the runtime are installed. Beh. */
|
||||
|
||||
#define UNINST_KEY "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\Panda3D Game Engine"
|
||||
|
||||
int main (int argc, char* argv[]) {
|
||||
int i;
|
||||
char buffer[BUFFER_SIZE];
|
||||
char* p3dfile;
|
||||
char* runtime = NULL;
|
||||
DWORD size;
|
||||
STARTUPINFO si;
|
||||
PROCESS_INFORMATION pi;
|
||||
char *cmd;
|
||||
char *newcmd;
|
||||
HKEY hKey = 0;
|
||||
char buf[1024] = {0};
|
||||
DWORD dwType = REG_SZ;
|
||||
DWORD dwBufSize = sizeof(buf);
|
||||
size = GetModuleFileName(NULL, buffer, BUFFER_SIZE);
|
||||
assert (size > 0);
|
||||
|
||||
/* Chop off the .exe and replace it by .p3d. */
|
||||
p3dfile = (char*) _alloca(size + 1);
|
||||
memcpy(p3dfile, buffer, size);
|
||||
p3dfile[size] = 0;
|
||||
memcpy(p3dfile + size - 3, "p3d", 3);
|
||||
|
||||
/* Find the location of panda3d.exe using the registry path. */
|
||||
#ifdef _WIN64
|
||||
/* If we're on 64-bit Windows, try the 64-bit registry first. */
|
||||
if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, UNINST_KEY, 0, KEY_QUERY_VALUE | KEY_WOW64_64KEY, &hKey) == ERROR_SUCCESS) {
|
||||
if (RegQueryValueEx(hKey, "DisplayIcon", 0, &dwType, (BYTE*) buf, &dwBufSize) == ERROR_SUCCESS) {
|
||||
char *slash = strrchr(buf, '\\');
|
||||
if (slash != NULL) {
|
||||
strcpy(slash, "\\panda3d.exe");
|
||||
runtime = buf;
|
||||
}
|
||||
}
|
||||
RegCloseKey(hKey);
|
||||
}
|
||||
#endif
|
||||
|
||||
/* On 32-bit Windows, or no 64-bit Runtime installed. Try 32-bit registry. */
|
||||
if (runtime == NULL) {
|
||||
if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, UNINST_KEY, 0, KEY_QUERY_VALUE | KEY_WOW64_32KEY, &hKey) == ERROR_SUCCESS) {
|
||||
if (RegQueryValueEx(hKey, "DisplayIcon", 0, &dwType, (BYTE*) buf, &dwBufSize) == ERROR_SUCCESS) {
|
||||
char *slash = strrchr(buf, '\\');
|
||||
if (slash != NULL) {
|
||||
strcpy(slash, "\\panda3d.exe");
|
||||
runtime = buf;
|
||||
}
|
||||
}
|
||||
RegCloseKey(hKey);
|
||||
}
|
||||
}
|
||||
|
||||
/* Backward compatibility: Runtime 1.0.4 and below looked for the below key, even though the
|
||||
above keys should work fine, but let's just be certain.
|
||||
Find the Panda3D applet\DefaultIcon key and extract the path to the runtime from there. */
|
||||
if (runtime == NULL) {
|
||||
if (RegOpenKey(HKEY_CLASSES_ROOT, "Panda3D applet\\DefaultIcon", &hKey) == ERROR_SUCCESS) {
|
||||
if (RegQueryValueEx(hKey, 0, 0, &dwType, (BYTE*) buf, &dwBufSize) == ERROR_SUCCESS) {
|
||||
char *slash = strrchr(buf, '\\');
|
||||
if (slash != NULL) {
|
||||
strcpy(slash, "\\panda3d.exe");
|
||||
runtime = buf;
|
||||
}
|
||||
} else {
|
||||
fprintf(stderr, "Failed to read registry key. Try reinstalling the Panda3D Runtime.\n");
|
||||
return 1;
|
||||
}
|
||||
RegCloseKey(hKey);
|
||||
} else {
|
||||
fprintf(stderr, "The Panda3D Runtime does not appear to be installed!\n");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (runtime == NULL) {
|
||||
fprintf(stderr, "Failed to find panda3d.exe in registry. Try reinstalling the Panda3D Runtime.\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Build the command-line and run panda3d.exe. */
|
||||
cmd = GetCommandLine();
|
||||
newcmd = (char*) _alloca(strlen(runtime) + strlen(p3dfile) + strlen(cmd) - strlen (argv[0]) + 7);
|
||||
sprintf(newcmd, "\"%s\" \"%s\" %s", runtime, p3dfile, cmd + strlen(argv[0]));
|
||||
memset(&si, 0, sizeof(si));
|
||||
si.cb = sizeof(STARTUPINFO);
|
||||
if (CreateProcess(runtime, newcmd, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi)) {
|
||||
WaitForSingleObject(pi.hProcess, INFINITE);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -1,241 +0,0 @@
|
|||
#! /usr/bin/env python
|
||||
|
||||
usageText = """
|
||||
|
||||
This command will pack a Panda application, consisting of a directory
|
||||
tree of .py files and models, into a p3d file for convenient
|
||||
distribution. The resulting p3d file can be run by the Panda3D
|
||||
runtime executable, or by the Panda3D web browser plugin.
|
||||
|
||||
This command will build p3d files that reference Panda3D %s,
|
||||
from host %s .
|
||||
|
||||
Also see ppackage, a more powerful (but more complex) tool that can
|
||||
also be used to build p3d applications, using a pdef description file.
|
||||
|
||||
Usage:
|
||||
|
||||
%s [opts] -o app.p3d
|
||||
|
||||
Options:
|
||||
|
||||
-o app.p3d
|
||||
Specify the name of the p3d file to generate. This is required.
|
||||
|
||||
-d application_root
|
||||
Specify the root directory of the application source; this is a
|
||||
directory tree that contains all of your .py files and models.
|
||||
If this is omitted, the default is the current directory.
|
||||
|
||||
-m main.py
|
||||
Names the Python file that begins the application. This should
|
||||
be a file within the root directory. If this is omitted, the
|
||||
default is a file named "main.py", or if there is only one Python
|
||||
file present, it is used. If this file contains a function
|
||||
called main(), that function will be called after importing it
|
||||
(this is preferable to having the module start itself immediately
|
||||
upon importing).
|
||||
|
||||
-S file.crt[,chain.crt[,file.key[,\"password\"]]]
|
||||
Signs the resulting p3d with the indicated certificate. You may
|
||||
specify the signing certificate, the optional authorization
|
||||
chain, and the private key in three different files, or they may
|
||||
all be combined in the first file. If the private key is
|
||||
encrypted, the password will be required to decrypt it.
|
||||
|
||||
-e ext
|
||||
Adds a new extension to be processed as a generic, compressible
|
||||
file type. Do not include the leading dot. Files matching this
|
||||
extension found within the root directory will be automatically
|
||||
added to the p3d file, in compressed form. This option may be
|
||||
repeated as necessary.
|
||||
|
||||
-n ext
|
||||
Adds a new extension to be processed as a noncompressible file
|
||||
type. Files matching this extension will be added to the p3d
|
||||
file, in their original, uncompressed form. You should use this
|
||||
instead of -e for files that are uncompressible by their nature
|
||||
(e.g. mpg files). This option may be repeated as necessary.
|
||||
|
||||
-x ext
|
||||
Marks files with the given extensions of needing to be physically
|
||||
extracted to disk before they can be loaded. This is used for
|
||||
file types that cannot be loaded via the virtual file system,
|
||||
such as .ico files on Windows.
|
||||
This option is currently only implemented when deploying the
|
||||
application with pdeploy.
|
||||
|
||||
-p python_lib_dir
|
||||
Adds a directory to search for additional Python modules. You
|
||||
can use this to add your system's Python path, to allow packp3d
|
||||
to find any system modules not included in the standard Panda3D
|
||||
release, but your version of Python must match this one (%s).
|
||||
This option may be repeated to add multiple directories.
|
||||
|
||||
-c config=value
|
||||
Sets the indicated config flag in the application. This option
|
||||
may be repeated as necessary.
|
||||
|
||||
-r package[,version[,hostURL]]
|
||||
Names an additional package that this application requires at
|
||||
startup time. The default package is 'panda3d'; you may repeat
|
||||
this option to indicate dependencies on additional packages.
|
||||
|
||||
-s search_dir
|
||||
Additional directories to search for previously-built packages.
|
||||
This option may be repeated as necessary. These directories may
|
||||
also be specified with the pdef-path Config.prc variable.
|
||||
|
||||
-D
|
||||
Sets the allow_python_dev flag in the application. This enables
|
||||
additional runtime debug operations, particularly the -i option
|
||||
to the panda3d command, which enables a live Python prompt within
|
||||
the application's environment. Setting this flag may be useful
|
||||
to develop an application initially, but should not be set on an
|
||||
application intended for deployment.
|
||||
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
import getopt
|
||||
import glob
|
||||
from direct.p3d import Packager
|
||||
from panda3d.core import *
|
||||
|
||||
# Temp hack for debugging.
|
||||
#from direct.p3d.AppRunner import dummyAppRunner; dummyAppRunner()
|
||||
|
||||
class ArgumentError(Exception):
|
||||
pass
|
||||
|
||||
def makePackedApp(args):
|
||||
opts, args = getopt.getopt(args, 'o:d:m:S:e:n:x:p:c:r:s:Dh')
|
||||
|
||||
packager = Packager.Packager()
|
||||
|
||||
appFilename = None
|
||||
root = Filename('.')
|
||||
main = None
|
||||
configFlags = []
|
||||
requires = []
|
||||
allowPythonDev = False
|
||||
|
||||
for option, value in opts:
|
||||
if option == '-o':
|
||||
appFilename = Filename.fromOsSpecific(value)
|
||||
elif option == '-d':
|
||||
root = Filename.fromOsSpecific(value)
|
||||
elif option == '-m':
|
||||
main = value
|
||||
elif option == '-S':
|
||||
tokens = value.split(',')
|
||||
while len(tokens) < 4:
|
||||
tokens.append('')
|
||||
certificate, chain, pkey, password = tokens[:4]
|
||||
packager.signParams.append((Filename.fromOsSpecific(certificate),
|
||||
Filename.fromOsSpecific(chain),
|
||||
Filename.fromOsSpecific(pkey),
|
||||
Filename.fromOsSpecific(password)))
|
||||
elif option == '-e':
|
||||
packager.binaryExtensions.append(value)
|
||||
elif option == '-n':
|
||||
packager.uncompressibleExtensions.append(value)
|
||||
elif option == '-x':
|
||||
packager.extractExtensions.append(value)
|
||||
elif option == '-p':
|
||||
sys.path.append(value)
|
||||
elif option == '-c':
|
||||
configFlags.append(value.split('=', 1))
|
||||
elif option == '-r':
|
||||
tokens = value.split(',')
|
||||
while len(tokens) < 3:
|
||||
tokens.append('')
|
||||
name, version, host = tokens[:3]
|
||||
requires.append((name, version, host))
|
||||
elif option == '-s':
|
||||
packager.installSearch.append(Filename.fromOsSpecific(value))
|
||||
elif option == '-D':
|
||||
allowPythonDev = True
|
||||
elif option == '-h':
|
||||
print(usageText % (
|
||||
PandaSystem.getPackageVersionString(),
|
||||
PandaSystem.getPackageHostUrl(),
|
||||
os.path.split(sys.argv[0])[1],
|
||||
'%s.%s' % (sys.version_info[0], sys.version_info[1])))
|
||||
sys.exit(0)
|
||||
|
||||
if not appFilename:
|
||||
raise ArgumentError("No target app specified. Use:\n %s -o app.p3d\nUse -h to get more usage information." % (os.path.split(sys.argv[0])[1]))
|
||||
|
||||
if args:
|
||||
raise ArgumentError("Extra arguments on command line.")
|
||||
|
||||
if appFilename.getExtension() != 'p3d':
|
||||
raise ArgumentError('Application filename must end in ".p3d".')
|
||||
|
||||
appDir = Filename(appFilename.getDirname())
|
||||
if not appDir:
|
||||
appDir = Filename('.')
|
||||
appBase = appFilename.getBasenameWoExtension()
|
||||
|
||||
if main:
|
||||
main = Filename.fromOsSpecific(main)
|
||||
main.makeAbsolute(root)
|
||||
else:
|
||||
main = Filename(root, 'main.py')
|
||||
if not main.exists():
|
||||
main = glob.glob(os.path.join(root.toOsSpecific(), '*.py'))
|
||||
if len(main) == 0:
|
||||
raise ArgumentError('No Python files in root directory.')
|
||||
elif len(main) > 1:
|
||||
raise ArgumentError('Multiple Python files in root directory; specify the main application with -m "main".')
|
||||
|
||||
main = Filename.fromOsSpecific(os.path.split(main[0])[1])
|
||||
main.makeAbsolute(root)
|
||||
|
||||
packager.installDir = appDir
|
||||
packager.allowPythonDev = allowPythonDev
|
||||
|
||||
# Put the root directory on the front of the model-path, so that
|
||||
# any texture references in egg or bam files that reference
|
||||
# textures from the top of the root directory will be properly
|
||||
# resolved.
|
||||
getModelPath().prependDirectory(root)
|
||||
|
||||
try:
|
||||
packager.setup()
|
||||
packager.beginPackage(appBase, p3dApplication = True)
|
||||
|
||||
# Pre-require panda3d, to give a less-confusing error message
|
||||
# if one of our requirements pulls in a wrong version of
|
||||
# panda3d.
|
||||
if 'panda3d' not in [t[0] for t in requires]:
|
||||
packager.do_require('panda3d')
|
||||
|
||||
for name, version, host in requires:
|
||||
packager.do_require(name, version = version, host = host)
|
||||
|
||||
if configFlags:
|
||||
packager.do_config(**dict(configFlags))
|
||||
|
||||
packager.do_dir(root)
|
||||
packager.do_main(main)
|
||||
packager.endPackage()
|
||||
packager.close()
|
||||
|
||||
except Packager.PackagerError:
|
||||
# Just print the error message and exit gracefully.
|
||||
inst = sys.exc_info()[1]
|
||||
print(inst.args[0])
|
||||
sys.exit(1)
|
||||
|
||||
try:
|
||||
makePackedApp(sys.argv[1:])
|
||||
except ArgumentError as e:
|
||||
print(e.args[0])
|
||||
sys.exit(1)
|
||||
|
||||
# An explicit call to exit() is required to exit the program, when
|
||||
# this module is packaged in a p3d file.
|
||||
sys.exit(0)
|
||||
|
|
@ -1,518 +0,0 @@
|
|||
import sys
|
||||
from panda3d.core import Filename, PandaSystem, getModelPath
|
||||
|
||||
# This file defines a number of standard "packages" that correspond to
|
||||
# a Panda3D distribution. These packages are built by passing this
|
||||
# file to the ppackage utility, either as a packaged application, or
|
||||
# as the module direct.p3d.ppackage.
|
||||
|
||||
# The packages in this file define Panda3D itself. This is the third
|
||||
# installed piece of the three-part plugin system (the plugin, the
|
||||
# core API, Panda3D).
|
||||
|
||||
# When needed, these packages are downloaded by the core API, from the
|
||||
# host URL specified in a given p3d file, and not from any hardcoded
|
||||
# URL. Thus, any custom version of Panda3D may be hosted on any
|
||||
# server in the world, and any version of the plugin can download it.
|
||||
|
||||
# Also see coreapi.pdef.
|
||||
|
||||
class panda3d(package):
|
||||
# The main Panda3D package. Contains Python and most of the graphics
|
||||
# code in Panda3D.
|
||||
|
||||
config(display_name = "Panda3D")
|
||||
|
||||
# First, add the minimum startup files for a Panda3D package.
|
||||
# These are files that the Panda3D runtime will explicitly look
|
||||
# for by name in order to get itself bootstrapped.
|
||||
setupPanda3D()
|
||||
|
||||
# These are Python modules that are needed by most Panda3D
|
||||
# applications. It doesn't matter too much if we miss one or two
|
||||
# here, since any module imported by any of this code will
|
||||
# automatically be included as well, and we end up with a pretty
|
||||
# complete list that way.
|
||||
module('direct.directbase.DirectStart',
|
||||
# Don't want to include all files in direct.p3d, because
|
||||
# that picks up the runtime scripts too, which are their
|
||||
# own p3d files below.
|
||||
'direct.p3d.AppRunner',
|
||||
'direct.p3d.DWBPackageInstaller',
|
||||
'direct.actor.*',
|
||||
'direct.controls.*',
|
||||
'direct.directdevices.*',
|
||||
'direct.directnotify.*',
|
||||
'direct.directtools.*',
|
||||
'direct.directutil.*',
|
||||
'direct.distributed.*',
|
||||
'direct.filter.*',
|
||||
'direct.fsm.*',
|
||||
'direct.gui.*',
|
||||
'direct.interval.*',
|
||||
'direct.particles.*',
|
||||
'direct.showbase.*',
|
||||
'direct.showutil.*',
|
||||
'direct.stdpy.*',
|
||||
'direct.task.*')
|
||||
|
||||
# Keep these modules for backward compatibility.
|
||||
module('pandac.PandaModules', 'pandac.extension_native_helpers')
|
||||
|
||||
module('panda3d.core',
|
||||
'panda3d.direct',
|
||||
'panda3d.fx',
|
||||
'panda3d.physics')
|
||||
|
||||
# Include various standard Python encodings. The rest is in morepy.
|
||||
module('encodings', 'encodings.aliases', 'encodings.undefined',
|
||||
'encodings.utf_8', 'encodings.ascii', 'encodings.mbcs',
|
||||
'encodings.latin_1', 'io')
|
||||
if sys.version_info < (3, 0):
|
||||
module('encodings.string_escape')
|
||||
|
||||
# pandac.PandaModules pulls in other Panda3D libs automatically.
|
||||
# Exclude these Panda3D libs; they are big and many applications don't
|
||||
# use them. We define them as separate, optional packages, below,
|
||||
# except for skel - this is useless in a shipped game anyways.
|
||||
excludeModule('panda3d.egg')
|
||||
excludeModule('panda3d.ode')
|
||||
excludeModule('panda3d.bullet')
|
||||
excludeModule('panda3d.vision')
|
||||
excludeModule('panda3d.skel')
|
||||
excludeModule('panda3d.physx')
|
||||
excludeModule('panda3d.ai')
|
||||
excludeModule('panda3d.vrpn')
|
||||
|
||||
excludeModule('libpandaegg')
|
||||
excludeModule('libpandaode')
|
||||
excludeModule('libpandabullet')
|
||||
excludeModule('libp3vision')
|
||||
excludeModule('libpandaskel')
|
||||
excludeModule('libpandaphysx')
|
||||
excludeModule('libpandaai')
|
||||
excludeModule('libp3vrpn')
|
||||
|
||||
# Exclude these GUI toolkits; they're big, and many applications don't
|
||||
# use them. We define them as separate, optional packages, below.
|
||||
excludeModule('wx',
|
||||
'direct.showbase.WxGlobal')
|
||||
|
||||
excludeModule('Tkinter', 'tkinter', 'Pmw', 'tkinter.simpledialog',
|
||||
'direct.showbase.TkGlobal',
|
||||
'direct.tkpanels', 'direct.tkwidgets',
|
||||
'tkCommonDialog', 'tkMessageBox', 'tkSimpleDialog')
|
||||
|
||||
excludeModule('MySQLdb', 'MySQLdb.connections', 'MySQLdb.constants',
|
||||
'MySQLdb.converters', '_mysql')
|
||||
|
||||
# Some code in distributed conditionally imports these.
|
||||
excludeModule('otp.ai', 'otp.ai.AIZoneData')
|
||||
|
||||
# Most of the core Panda3D DLL's will be included implicitly due to
|
||||
# being referenced by the above Python code. Here we name a few more
|
||||
# that are also needed, but aren't referenced by any code. Again,
|
||||
# note that the .dll extension is automatically replaced with the
|
||||
# platform-specific extension for an executable.
|
||||
file('libpandagl.dll', 'libp3tinydisplay.dll')
|
||||
if platform.startswith('win'):
|
||||
file('libpandadx9.dll')
|
||||
|
||||
# A basic config file is needed to lay some some fundamental runtime
|
||||
# variables.
|
||||
if platform.startswith('win'):
|
||||
auxDisplays = """
|
||||
aux-display pandagl
|
||||
aux-display pandadx9
|
||||
aux-display p3tinydisplay
|
||||
"""
|
||||
else:
|
||||
auxDisplays = """
|
||||
aux-display pandagl
|
||||
aux-display p3tinydisplay
|
||||
"""
|
||||
|
||||
file('Config.prc', extract = True, text = """
|
||||
plugin-path $PANDA3D_ROOT
|
||||
default-model-extension .bam
|
||||
cull-bin gui-popup 60 unsorted
|
||||
""" + auxDisplays)
|
||||
|
||||
class morepy(package):
|
||||
# Additional Python modules provided by the standard Python
|
||||
# library. Include this package to get the full suite of standard
|
||||
# Python functionality.
|
||||
|
||||
config(display_name = "Python standard library")
|
||||
require('panda3d')
|
||||
|
||||
module('string', 're', 'struct', 'difflib',
|
||||
'textwrap', 'codecs', 'unicodedata', 'stringprep', 'datetime',
|
||||
'calendar', 'collections', 'heapq', 'bisect', 'array',
|
||||
'sched', 'queue', 'weakref', 'types', 'copy', 'pprint',
|
||||
'numbers', 'math', 'cmath', 'decimal', 'fractions',
|
||||
'random', 'itertools', 'functools', 'operator', 'os.path',
|
||||
'fileinput', 'filecmp', 'tempfile', 'glob', 'fnmatch', 'linecache',
|
||||
'shutil', 'macpath', 'pickle', 'shelve', 'marshal', 'zlib',
|
||||
'gzip', 'bz2', 'zipfile', 'tarfile', 'csv', 'netrc',
|
||||
'xdrlib', 'plistlib', 'hashlib', 'hmac', 'os',
|
||||
'time', 'optparse', 'getopt', 'logging', 'logging.*')
|
||||
module('getpass', 'curses', 'curses.textpad', 'curses.wrapper',
|
||||
'curses.ascii', 'curses.panel', 'platform', 'errno',
|
||||
'ctypes', 'select', 'threading', 'dummy_threading', 'dummy_thread',
|
||||
'multiprocessing', 'mmap', 'readline', 'rlcompleter', 'subprocess',
|
||||
'socket', 'ssl', 'signal', 'asyncore', 'asynchat', 'email', 'json',
|
||||
'mailcap', 'mailbox', 'mimetypes', 'base64', 'binhex', 'binascii',
|
||||
'quopri', 'uu', 'xml.parsers.expat', 'xml.dom', 'xml.dom.minidom',
|
||||
'xml.dom.pulldom', 'xml.sax', 'xml.sax.handler',
|
||||
'xml.sax.saxutils', 'xml.sax.xmlreader',
|
||||
'xml.etree.ElementTree', 'webbrowser', 'cgi', 'cgitb')
|
||||
module('wsgiref', 'ftplib', 'poplib', 'imaplib', 'nntplib', 'smtplib',
|
||||
'smtpd', 'telnetlib', 'uuid', 'audioop', 'aifc', 'sunau',
|
||||
'wave', 'chunk', 'colorsys', 'imghdr', 'sndhdr',
|
||||
'ossaudiodev', 'gettext', 'locale', 'cmd', 'shlex',
|
||||
'pydoc', 'doctest', 'unittest', 'test',
|
||||
'bdb', 'pdb', 'timeit', 'trace', 'sys')
|
||||
module('warnings', 'contextlib', 'abc',
|
||||
'atexit', 'traceback', '__future__', 'gc', 'inspect',
|
||||
'site', 'fpectl', 'code', 'codeop', 'zipimport', 'pkgutil',
|
||||
'modulefinder', 'runpy', 'parser', 'symtable', 'symbol',
|
||||
'token', 'keyword', 'tokenize', 'tabnanny', 'pyclbr',
|
||||
'py_compile', 'compileall', 'dis', 'pickletools',
|
||||
'distutils', 'msilib', 'msvcrt', 'winsound', 'posix', 'pwd', 'spwd',
|
||||
'grp', 'crypt', 'termios', 'tty', 'pty', 'fcntl', 'pipes',
|
||||
'resource', 'nis', 'syslog', 'ast')
|
||||
|
||||
# Handle module name differences between Python 2 and Python 3 (see PEP3108)
|
||||
if sys.version_info[0] < 3:
|
||||
# Deprecated modules that were removed in Python 3
|
||||
module('posixfile', 'rfc822', 'mimetools','MimeWriter', 'mimify',
|
||||
'multifile', 'sets', 'md5', 'sha', 'imp', 'formatter')
|
||||
# Mac-specific modules that were removed
|
||||
module('autoGIL', 'ColorPicker', 'EasyDialogs', 'findertools',
|
||||
'FrameWork', 'ic', 'MacOS', 'macostools')
|
||||
# Removed because they were hardly used
|
||||
module('imputil', 'mutex', 'user', 'new')
|
||||
# Removed because they were obsolete
|
||||
module('Bastion', 'rexec', 'commands', 'dircache', 'dl', 'fpformat',
|
||||
'htmllib', 'imageop', 'mhlib', 'popen2', 'sgmllib', 'stat',
|
||||
'statvfs', 'thread', 'UserDict', 'UserList', 'UserString',
|
||||
'future_builtins', 'hotshot', 'bsddb')
|
||||
# Renamed to fix PEP8 violations
|
||||
module('_winreg', 'ConfigParser', 'copy_reg', 'SocketServer')
|
||||
# C and Python implementations of the same interface were merged
|
||||
module('cPickle', 'cStringIO', 'StringIO')
|
||||
# Renamed because of poorly chosen names
|
||||
module('repr', 'test.test_support', '__builtins__')
|
||||
# Modules that got grouped under packages
|
||||
module('anydbm', 'whichdb', 'dbm', 'dumbdbm', 'gdbm', 'dbhash')
|
||||
module('HTMLParser', 'htmlentitydefs')
|
||||
module('BaseHTTPServer', 'SimpleHTTPServer', 'cookielib', 'Cookie',
|
||||
'CGIHTTPServer', 'httplib')
|
||||
module('urllib', 'urllib2', 'urlparse', 'robotparser')
|
||||
module('xmlrpclib', 'SimpleXMLRPCServer', 'DocXMLRPCServer')
|
||||
else:
|
||||
# Renamed to fix PEP8 violations
|
||||
module('winreg', 'configparser', 'copyreg', 'socketserver')
|
||||
# Renamed because of poorly chosen names
|
||||
module('reprlib', 'test.support', 'builtins')
|
||||
# Modules that got grouped under packages
|
||||
module('dbm')
|
||||
module('html')
|
||||
module('http')
|
||||
module('urllib')
|
||||
module('xmlrpc')
|
||||
|
||||
# To add the multitude of standard Python string encodings.
|
||||
module('encodings', 'encodings.*')
|
||||
|
||||
class models(package):
|
||||
# The standard models package. This is the contents of the
|
||||
# "models" directory that ships with Panda; it includes a few
|
||||
# default fonts, and some silly little sample models like smiley
|
||||
# and teapot.
|
||||
config(display_name = "Standard models")
|
||||
|
||||
# We assign it the same version as the panda3d package. This
|
||||
# would be assigned by default if we had a requirement on panda3d,
|
||||
# but there's no real reason to declare that requirement.
|
||||
config(version = PandaSystem.getPackageVersionString())
|
||||
|
||||
# Look for cmss12.egg on the model-path. Wherever this is found,
|
||||
# we assume this is the models directory.
|
||||
pathname = getModelPath().findFile('cmss12.egg')
|
||||
if pathname:
|
||||
dir(pathname.getDirname(), newDir = 'models')
|
||||
|
||||
# Some people are used to loading the models without models/ prefix.
|
||||
file('models.prc', extract = True, text = "model-path $MODELS_ROOT/models")
|
||||
|
||||
|
||||
class fmod(package):
|
||||
# This package includes the FMod audio library. This is
|
||||
# full-featured and robust, but it is closed-source and the
|
||||
# licensing is cost-free only for non-commercial products.
|
||||
|
||||
config(display_name = "FMod audio library")
|
||||
require('panda3d')
|
||||
|
||||
file('libp3fmod_audio.dll', required = True)
|
||||
|
||||
file('fmod.prc', extract = True, text = """
|
||||
plugin-path $FMOD_ROOT
|
||||
audio-library-name p3fmod_audio
|
||||
""")
|
||||
|
||||
class openal(package):
|
||||
# This package includes the OpenAL audio libraries. This is free
|
||||
# in both senses, but there are some technical issues, especially
|
||||
# on certain platforms.
|
||||
|
||||
config(display_name = "OpenAL audio library")
|
||||
require('panda3d')
|
||||
|
||||
file('libp3openal_audio.dll', required = True)
|
||||
file('OpenAL32.dll')
|
||||
file('soft_oal.dll')
|
||||
file('wrap_oal.dll')
|
||||
|
||||
file('openal.prc', extract = True, text = """
|
||||
plugin-path $OPENAL_ROOT
|
||||
audio-library-name p3openal_audio
|
||||
""")
|
||||
|
||||
class audio(package):
|
||||
# This package includes the best audio library for the given
|
||||
# platform, assuming a non-commercial application.
|
||||
require('panda3d')
|
||||
|
||||
# OpenAL seems to work pretty well universally now.
|
||||
require('openal')
|
||||
|
||||
class ffmpeg(package):
|
||||
# This package includes the ffmpeg libraries and integration.
|
||||
# It's a bit big, and is restricted by patents and the restrictive
|
||||
# GPLv3 license, which is why it is included as a separate package.
|
||||
|
||||
config(display_name = "FFMpeg multimedia decoder")
|
||||
require('panda3d')
|
||||
|
||||
file('libp3ffmpeg.dll', required = True)
|
||||
|
||||
file('ffmpeg.prc', extract = True, text = """
|
||||
plugin-path $FFMPEG_ROOT
|
||||
load-audio-type * p3ffmpeg
|
||||
load-video-type * p3ffmpeg
|
||||
""")
|
||||
|
||||
class egg(package):
|
||||
# This package contains the code for reading and operating on egg
|
||||
# files and other model files. Since the Packager automatically
|
||||
# converts egg files to bam files, this is not needed for most
|
||||
# Panda3D applications.
|
||||
|
||||
config(display_name = "Panda3D egg loader")
|
||||
require('panda3d')
|
||||
|
||||
module('panda3d.egg', required = True)
|
||||
file('libpandaegg.dll', required = True)
|
||||
file('libp3ptloader.dll', required = True)
|
||||
|
||||
file('egg.prc', extract = True, text = """
|
||||
plugin-path $EGG_ROOT
|
||||
load-file-type egg pandaegg
|
||||
load-file-type p3ptloader
|
||||
|
||||
# These are excerpted from the default Confauto.prc file.
|
||||
egg-object-type-portal <Scalar> portal { 1 }
|
||||
egg-object-type-polylight <Scalar> polylight { 1 }
|
||||
egg-object-type-seq24 <Switch> { 1 } <Scalar> fps { 24 }
|
||||
egg-object-type-seq12 <Switch> { 1 } <Scalar> fps { 12 }
|
||||
egg-object-type-indexed <Scalar> indexed { 1 }
|
||||
egg-object-type-seq10 <Switch> { 1 } <Scalar> fps { 10 }
|
||||
egg-object-type-seq8 <Switch> { 1 } <Scalar> fps { 8 }
|
||||
egg-object-type-seq6 <Switch> { 1 } <Scalar> fps { 6 }
|
||||
egg-object-type-seq4 <Switch> { 1 } <Scalar> fps { 4 }
|
||||
egg-object-type-seq2 <Switch> { 1 } <Scalar> fps { 2 }
|
||||
|
||||
egg-object-type-binary <Scalar> alpha { binary }
|
||||
egg-object-type-dual <Scalar> alpha { dual }
|
||||
egg-object-type-glass <Scalar> alpha { blend_no_occlude }
|
||||
|
||||
egg-object-type-model <Model> { 1 }
|
||||
egg-object-type-dcs <DCS> { 1 }
|
||||
egg-object-type-notouch <DCS> { no_touch }
|
||||
|
||||
egg-object-type-barrier <Collide> { Polyset descend }
|
||||
egg-object-type-sphere <Collide> { Sphere descend }
|
||||
egg-object-type-invsphere <Collide> { InvSphere descend }
|
||||
egg-object-type-tube <Collide> { Tube descend }
|
||||
egg-object-type-trigger <Collide> { Polyset descend intangible }
|
||||
egg-object-type-trigger-sphere <Collide> { Sphere descend intangible }
|
||||
egg-object-type-floor <Collide> { Polyset descend level }
|
||||
egg-object-type-dupefloor <Collide> { Polyset keep descend level }
|
||||
egg-object-type-bubble <Collide> { Sphere keep descend }
|
||||
egg-object-type-ghost <Scalar> collide-mask { 0 }
|
||||
egg-object-type-glow <Scalar> blend { add }
|
||||
egg-object-type-direct-widget <Scalar> collide-mask { 0x80000000 } <Collide> { Polyset descend }
|
||||
""")
|
||||
|
||||
class ode(package):
|
||||
# This package contains the code for the ODE integration.
|
||||
# As not every application uses the ODE layers, and to cut down
|
||||
# the download size, it is provided as separate package.
|
||||
|
||||
config(display_name = "Panda3D Open Dynamics Engine integration")
|
||||
require('panda3d')
|
||||
|
||||
module('panda3d.ode', required = True)
|
||||
file('libpandaode.dll', required = True)
|
||||
|
||||
class bullet(package):
|
||||
# This package contains the code for the Bullet integration.
|
||||
# As not every application uses the Bullet layers, and to cut down
|
||||
# the download size, it is provided as separate package.
|
||||
|
||||
config(display_name = "Panda3D Bullet integration")
|
||||
require('panda3d')
|
||||
|
||||
module('panda3d.bullet', required = True)
|
||||
file('libpandabullet.dll', required = True)
|
||||
|
||||
class physx(package):
|
||||
# This package contains the code for the NVIDIA PhysX integration.
|
||||
# As not every application uses the NVIDIA PhysX layers, and to cut down
|
||||
# the download size, it is provided as separate package.
|
||||
|
||||
config(display_name = "Panda3D PhysX integration")
|
||||
require('panda3d')
|
||||
|
||||
module('panda3d.physx', required = True)
|
||||
file('libpandaphysx.dll', required = True)
|
||||
file('physxcudart_20.dll')
|
||||
file('PhysXDevice.dll')
|
||||
|
||||
class ai(package):
|
||||
# This package contains the PandAI library for pathfinding
|
||||
# and seek/flee behaviors. As some games may not need this
|
||||
# functionality, it is provided as separate package to save
|
||||
# on package download size.
|
||||
|
||||
config(display_name = "Panda3D AI modules")
|
||||
require('panda3d')
|
||||
|
||||
module('panda3d.ai', required = True)
|
||||
file('libpandaai.dll', required = True)
|
||||
|
||||
class vision(package):
|
||||
# This package contains the code for webcam support, augmented
|
||||
# reality and computer vision. As many games will not need this
|
||||
# functionality, it is provided as separate package to save
|
||||
# on package download size.
|
||||
|
||||
config(display_name = "Panda3D vision modules")
|
||||
require('panda3d')
|
||||
|
||||
module('panda3d.vision', required = True)
|
||||
file('libp3vision.dll', required = True)
|
||||
|
||||
class rocket(package):
|
||||
# This package contains the code for libRocket integration.
|
||||
# As not every application uses libRocket GUI, and to cut down
|
||||
# the download size, it is provided as separate package.
|
||||
|
||||
config(display_name = "Panda3D libRocket support")
|
||||
require('panda3d')
|
||||
|
||||
file('rocket.py', extract = True, text = """
|
||||
from _rocketcore import *
|
||||
try:
|
||||
from _rocketcontrols import *
|
||||
except ImportError:
|
||||
pass
|
||||
""")
|
||||
|
||||
module('panda3d.rocket', '_rocketcore', required = True)
|
||||
module('_rocketcontrols')
|
||||
file('libp3rocket.dll', required = True)
|
||||
|
||||
class vrpn(package):
|
||||
# This package contains the code for VRPN integration.
|
||||
# Most applications don't use this, which is why it's separate.
|
||||
|
||||
config(display_name = "Panda3D VRPN support")
|
||||
require('panda3d')
|
||||
|
||||
module('panda3d.vrpn', required = True)
|
||||
file('libp3vrpn.dll', required = True)
|
||||
|
||||
|
||||
class packp3d(p3d):
|
||||
# This application is a command-line convenience for building a p3d
|
||||
# application out of a directory hierarchy on disk. We build it here
|
||||
# into its own p3d application, to allow end-users to easily build p3d
|
||||
# applications using the appropriate version of Python and Panda for
|
||||
# the targeted runtime.
|
||||
|
||||
config(display_name = "Panda3D Application Packer",
|
||||
hidden = True, platform_specific = False,
|
||||
keep_user_env = True)
|
||||
require('panda3d', 'egg')
|
||||
|
||||
mainModule('direct.p3d.packp3d')
|
||||
|
||||
file('packp3d.prc', extract = True, text = "preload-textures false")
|
||||
|
||||
|
||||
class ppackage(p3d):
|
||||
# As above, a packaging utility. This is the fully-general ppackage
|
||||
# utility, which reads pdef files (like this one!) and creates one or
|
||||
# more packages or p3d applications.
|
||||
|
||||
config(display_name = "Panda3D General Package Utility",
|
||||
hidden = True, platform_specific = False,
|
||||
keep_user_env = True)
|
||||
require('panda3d', 'egg')
|
||||
|
||||
mainModule('direct.p3d.ppackage')
|
||||
|
||||
file('ppackage.prc', extract = True, text = "preload-textures false")
|
||||
|
||||
|
||||
class ppatcher(p3d):
|
||||
# A handy utility to go along with ppackage. This builds
|
||||
# patchfiles as needed in the directory structure created by
|
||||
# ppackage.
|
||||
|
||||
config(display_name = "Panda3D Patch Maker",
|
||||
hidden = True, platform_specific = False,
|
||||
keep_user_env = True)
|
||||
require('panda3d')
|
||||
|
||||
mainModule('direct.p3d.ppatcher')
|
||||
|
||||
|
||||
class pmerge(p3d):
|
||||
# Another handy utility to go along with ppackage. This
|
||||
# merges multiple directory structures as created by
|
||||
# ppackage into a single one.
|
||||
|
||||
config(display_name = "Panda3D Package Merger",
|
||||
hidden = True, platform_specific = False,
|
||||
keep_user_env = True)
|
||||
require('panda3d')
|
||||
|
||||
mainModule('direct.p3d.pmerge')
|
||||
|
||||
|
||||
class pdeploy(p3d):
|
||||
# This utility can distribute a game in the form of
|
||||
# a standalone executable or a graphical installer.
|
||||
|
||||
config(display_name = "Panda3D Deployment Tool",
|
||||
hidden = True, platform_specific = False,
|
||||
keep_user_env = True)
|
||||
require('panda3d', 'morepy')
|
||||
|
||||
mainModule('direct.p3d.pdeploy')
|
||||
|
||||
|
|
@ -1,359 +0,0 @@
|
|||
#! /usr/bin/env python
|
||||
|
||||
usageText = """
|
||||
|
||||
This command will help you to distribute your Panda application,
|
||||
consisting of a .p3d package, into a standalone executable, graphical
|
||||
installer or an HTML webpage. It will attempt to create packages
|
||||
for every platform, if possible.
|
||||
Note that pdeploy requires an internet connection to run.
|
||||
|
||||
When used with the 'installer' option, it is strongly advisable
|
||||
to specify most if not all of the descriptive information that can
|
||||
be passed on the command-line.
|
||||
|
||||
Usage:
|
||||
|
||||
%(prog)s [opts] app.p3d standalone|installer|html
|
||||
|
||||
Modes:
|
||||
|
||||
standalone
|
||||
A standalone executable will be created that embeds the given
|
||||
p3d file. The resulting executable will require an
|
||||
internet connection in order to run properly.
|
||||
|
||||
installer
|
||||
In this mode, installable packages will be created for as many
|
||||
platforms as possible. To create Windows installers on
|
||||
non-Windows platforms, you need to have the "makensis" utility
|
||||
on your system PATH environment variable.
|
||||
|
||||
html
|
||||
An HTML webpage will be generated that can be used to view
|
||||
the provided p3d file in a browser.
|
||||
|
||||
Options:
|
||||
|
||||
-n your_app
|
||||
Short, lowercase name of the application or game. May only
|
||||
contain alphanumeric characters, underscore or dash. This
|
||||
name will also define the output file(s) of the process.
|
||||
If omitted, the basename of the p3d file is used.
|
||||
|
||||
-N "Your Application"
|
||||
Full name of the application or game. This is the
|
||||
name that will be displayed to the end-user.
|
||||
The 'display_name' config is used by default. If it
|
||||
is missing, the short name is used.
|
||||
|
||||
-v version_number
|
||||
This should define the version number of your application
|
||||
or game. In some deploy modes, this argument is required.
|
||||
This should only contain alphanumeric characters, dots and
|
||||
dashes, as otherwise the result of the deployment may be
|
||||
invalid on some platforms.
|
||||
|
||||
-o output_dir
|
||||
Indicates the directory where the output will be stored.
|
||||
Within this directory, subdirectories will be created
|
||||
for every platform, unless -t is provided.
|
||||
If omitted, the current working directory is assumed.
|
||||
|
||||
-t token=value
|
||||
Defines a web token or parameter to pass to the application.
|
||||
Use this to configure how the application will be run.
|
||||
You can pass as many -t options as you need. Some examples of
|
||||
useful token names are width, height, log_basename, auto_start,
|
||||
hidden and console_environment.
|
||||
|
||||
-P platform
|
||||
If this option is provided, it should specify a comma-
|
||||
separated list of platforms that the p3d package will be
|
||||
deployed for. If omitted, it will be built for all platforms.
|
||||
This option may be specified multiple times.
|
||||
Examples of valid platforms are win_i386, linux_amd64 and osx_ppc.
|
||||
|
||||
-c
|
||||
If this option is provided, any -P options are ignored and
|
||||
the p3d package is only deployed for the current platform.
|
||||
Furthermore, no per-platform subdirectories will be created
|
||||
inside the output dirctory.
|
||||
|
||||
-s
|
||||
This option only has effect in 'installer' mode. If it is
|
||||
provided, the resulting installers will be fully self-contained,
|
||||
will not require an internet connection to run, and start up
|
||||
much faster. Note that pdeploy will also take a very long time
|
||||
to finish when -s is provided.
|
||||
If it is omitted, pdeploy will finish much quicker, and the
|
||||
resulting installers will be smaller, but they will require
|
||||
an internet connection for the first run, and the load time
|
||||
will be considerably longer.
|
||||
|
||||
-l "License Name"
|
||||
Specifies the name of the software license that the game
|
||||
or application is licensed under.
|
||||
Only relevant when generating a graphical installer.
|
||||
|
||||
-L licensefile.txt
|
||||
This should point to a file that contains the full text
|
||||
describing the software license that the game or application
|
||||
is licensed under.
|
||||
Only relevant when generating a graphical installer.
|
||||
|
||||
-O
|
||||
Specify this option when generating a graphical installer to omit
|
||||
the default checkboxes for "run this program" and "install a
|
||||
desktop shortcut" on completion.
|
||||
|
||||
-a com.your_company
|
||||
Short identifier of the author of the application. The default
|
||||
is "org.panda3d", but you will most likely want to change
|
||||
it to your own name or that of your organization or company.
|
||||
Only relevant when generating a graphical installer.
|
||||
|
||||
-A "Your Company"
|
||||
Full name of the author of the application. The default is
|
||||
determined from the GECOS information of the current user if
|
||||
available; if not, your username is used.
|
||||
Only relevant when generating a graphical installer.
|
||||
|
||||
-e "you@your_company.com"
|
||||
E-mail address of the maintainer of the application. The default
|
||||
is username@hostname.
|
||||
Only relevant when generating a graphical installer.
|
||||
|
||||
-i "path/icon32.png" -i "path/icon48.png" -i "path/icon128.png"
|
||||
This option should be repeated several times with different square
|
||||
image sizes. These images will then be combined to form an icon
|
||||
file that will be used to represent the installed application.
|
||||
To support all platforms, it is recommended to supply images of
|
||||
the sizes 16x16, 32x32, 48x48, 128x128, 256x256, and 512x512.
|
||||
The larger icon sizes can safely be omitted if you cannot
|
||||
provide images in that resolution.
|
||||
It is recommended to use .png images for correct transparency.
|
||||
If no images are provided, no icon will be generated.
|
||||
Only relevant when generating a graphical installer.
|
||||
|
||||
-h
|
||||
Display this help
|
||||
|
||||
"""
|
||||
|
||||
DEPLOY_MODES = ["standalone", "installer", "html"]
|
||||
|
||||
import sys
|
||||
import os
|
||||
import getopt
|
||||
from direct.p3d.DeploymentTools import Standalone, Installer, Icon
|
||||
from panda3d.core import Filename, PandaSystem
|
||||
|
||||
def usage(code, msg = ''):
|
||||
if not msg:
|
||||
sys.stderr.write(usageText % {'prog' : os.path.split(sys.argv[0])[1]})
|
||||
sys.stderr.write(msg + '\n')
|
||||
sys.exit(code)
|
||||
|
||||
shortname = ""
|
||||
fullname = ""
|
||||
version = ""
|
||||
outputDir = Filename("./")
|
||||
tokens = {}
|
||||
platforms = []
|
||||
currentPlatform = False
|
||||
licensename = ""
|
||||
licensefile = Filename()
|
||||
authorid = ""
|
||||
authorname = ""
|
||||
authoremail = ""
|
||||
iconFiles = []
|
||||
includeRequires = False
|
||||
omitDefaultCheckboxes = False
|
||||
|
||||
try:
|
||||
opts, args = getopt.getopt(sys.argv[1:], 'n:N:v:o:t:P:csOl:L:a:A:e:i:h')
|
||||
except getopt.error as msg:
|
||||
usage(1, msg or 'Invalid option')
|
||||
|
||||
for opt, arg in opts:
|
||||
if opt == '-n':
|
||||
shortname = arg.strip()
|
||||
elif opt == '-N':
|
||||
fullname = arg.strip()
|
||||
elif opt == '-v':
|
||||
version = arg.strip()
|
||||
elif opt == '-o':
|
||||
outputDir = Filename.fromOsSpecific(arg)
|
||||
elif opt == '-t':
|
||||
token = arg.strip().split("=", 1)
|
||||
tokens[token[0]] = token[1]
|
||||
elif opt == '-P':
|
||||
platforms.append(arg)
|
||||
elif opt == '-c':
|
||||
currentPlatform = True
|
||||
elif opt == '-s':
|
||||
includeRequires = True
|
||||
elif opt == '-O':
|
||||
omitDefaultCheckboxes = True
|
||||
elif opt == '-l':
|
||||
licensename = arg.strip()
|
||||
elif opt == '-L':
|
||||
licensefile = Filename.fromOsSpecific(arg)
|
||||
elif opt == '-a':
|
||||
authorid = arg.strip()
|
||||
elif opt == '-A':
|
||||
authorname = arg.strip()
|
||||
elif opt == '-e':
|
||||
authoremail = arg.strip()
|
||||
elif opt == '-i':
|
||||
iconFiles.append(Filename.fromOsSpecific(arg))
|
||||
|
||||
elif opt == '-h':
|
||||
usage(0)
|
||||
else:
|
||||
msg = 'illegal option: ' + flag
|
||||
print(msg)
|
||||
sys.exit(1, msg)
|
||||
|
||||
if not args or len(args) != 2:
|
||||
msg = 'Wrong number of arguments, received %s, expected 2' % (
|
||||
len(args or []))
|
||||
usage(1, msg)
|
||||
|
||||
appFilename = Filename.fromOsSpecific(args[0])
|
||||
if appFilename.getExtension().lower() != 'p3d':
|
||||
print('Application filename must end in ".p3d".')
|
||||
sys.exit(1)
|
||||
deploy_mode = args[1].lower()
|
||||
|
||||
if not appFilename.exists():
|
||||
print('Application filename does not exist!')
|
||||
sys.exit(1)
|
||||
|
||||
if shortname == '':
|
||||
shortname = appFilename.getBasenameWoExtension()
|
||||
|
||||
if shortname.lower() != shortname or ' ' in shortname:
|
||||
print('\nProvided short name should be lowercase, and may not contain spaces!\n')
|
||||
|
||||
if version == '' and deploy_mode == 'installer':
|
||||
print('\nA version number is required in "installer" mode.\n')
|
||||
sys.exit(1)
|
||||
|
||||
if not outputDir:
|
||||
print('\nYou must name the output directory with the -o parameter.\n')
|
||||
sys.exit(1)
|
||||
if not outputDir.exists():
|
||||
print('\nThe specified output directory does not exist!\n')
|
||||
sys.exit(1)
|
||||
elif not outputDir.isDirectory():
|
||||
print('\nThe specified output directory is a file!\n')
|
||||
sys.exit(1)
|
||||
|
||||
if deploy_mode == 'standalone':
|
||||
s = Standalone(appFilename, tokens)
|
||||
s.basename = shortname
|
||||
|
||||
if currentPlatform:
|
||||
platform = PandaSystem.getPlatform()
|
||||
if platform.startswith("win"):
|
||||
s.build(Filename(outputDir, shortname + ".exe"), platform)
|
||||
else:
|
||||
s.build(Filename(outputDir, shortname), platform)
|
||||
elif len(platforms) == 0:
|
||||
s.buildAll(outputDir)
|
||||
else:
|
||||
for platform in platforms:
|
||||
if platform.startswith("win"):
|
||||
s.build(Filename(outputDir, platform + "/" + shortname + ".exe"), platform)
|
||||
else:
|
||||
s.build(Filename(outputDir, platform + "/" + shortname), platform)
|
||||
|
||||
elif deploy_mode == 'installer':
|
||||
if includeRequires:
|
||||
tokens["verify_contents"] = "never"
|
||||
i = Installer(appFilename, shortname, fullname, version, tokens = tokens)
|
||||
i.includeRequires = includeRequires
|
||||
if omitDefaultCheckboxes:
|
||||
i.offerRun = False
|
||||
i.offerDesktopShortcut = False
|
||||
i.licensename = licensename
|
||||
i.licensefile = licensefile
|
||||
if authorid:
|
||||
i.authorid = authorid
|
||||
if authorname:
|
||||
i.authorname = authorname
|
||||
if authoremail:
|
||||
i.authoremail = authoremail
|
||||
if not authorname or not authoremail or not authorid:
|
||||
print("Using author \"%s\" <%s> with ID %s" % \
|
||||
(i.authorname, i.authoremail, i.authorid))
|
||||
|
||||
# Add the supplied icon images
|
||||
if len(iconFiles) > 0:
|
||||
failed = False
|
||||
i.icon = Icon()
|
||||
for iconFile in iconFiles:
|
||||
if not i.icon.addImage(iconFile):
|
||||
print('\nFailed to add icon image "%s"!\n' % iconFile)
|
||||
failed = True
|
||||
if failed:
|
||||
sys.exit(1)
|
||||
|
||||
# Now build for the requested platforms.
|
||||
if currentPlatform:
|
||||
platform = PandaSystem.getPlatform()
|
||||
if platform.startswith("win"):
|
||||
i.build(outputDir, platform)
|
||||
else:
|
||||
i.build(outputDir, platform)
|
||||
elif len(platforms) == 0:
|
||||
i.buildAll(outputDir)
|
||||
else:
|
||||
for platform in platforms:
|
||||
output = Filename(outputDir, platform + "/")
|
||||
output.makeDir()
|
||||
i.build(output, platform)
|
||||
|
||||
del i
|
||||
|
||||
elif deploy_mode == 'html':
|
||||
w, h = tokens.get("width", 800), tokens.get("height", 600)
|
||||
if "data" not in tokens:
|
||||
tokens["data"] = appFilename.getBasename()
|
||||
|
||||
print("Creating %s.html..." % shortname)
|
||||
html = open(Filename(outputDir, shortname + ".html").toOsSpecific(), "w")
|
||||
html.write("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n")
|
||||
html.write("<html xmlns=\"http://www.w3.org/1999/xhtml\">\n")
|
||||
html.write(" <head>\n")
|
||||
html.write(" <title>%s</title>\n" % fullname)
|
||||
html.write(" <meta http-equiv=\"Content-type\" content=\"text/html;charset=UTF-8\" />\n")
|
||||
if authorname:
|
||||
html.write(" <meta name=\"Author\" content=\"%s\" />\n" % authorname.replace('"', '\\"'))
|
||||
html.write(" </head>\n")
|
||||
html.write(" <body>\n")
|
||||
html.write(" <object")
|
||||
for key, value in tokens.items():
|
||||
html.write(" %s=\"%s\"" % (key, value.replace('"', '\\"')))
|
||||
if "width" not in tokens:
|
||||
html.write(" width=\"%s\"" % w)
|
||||
if "height" not in tokens:
|
||||
html.write(" height=\"%s\"" % h)
|
||||
html.write(" type=\"application/x-panda3d\">")
|
||||
html.write(" <object width=\"%s\" height=\"%s\" classid=\"CLSID:924B4927-D3BA-41EA-9F7E-8A89194AB3AC\">\n" % (w, h))
|
||||
for key, value in tokens.items():
|
||||
html.write(" <param name=\"%s\" value=\"%s\" />\n" % (key, value.replace('"', '\\"')))
|
||||
html.write(" </object>\n")
|
||||
html.write(" </object>\n")
|
||||
html.write(" </body>\n")
|
||||
html.write("</html>\n")
|
||||
html.close()
|
||||
else:
|
||||
usage(1, 'Invalid deployment mode!')
|
||||
|
||||
# An explicit call to exit() is required to exit the program, when
|
||||
# this module is packaged in a p3d file.
|
||||
sys.exit(0)
|
||||
|
||||
|
|
@ -1,106 +0,0 @@
|
|||
#! /usr/bin/env python
|
||||
|
||||
usageText = """
|
||||
|
||||
This script can be used to merge together the contents of two or more
|
||||
separately-built stage directories, built independently via ppackage,
|
||||
or via Packager.py. This script also verifies the hash, file size,
|
||||
and timestamp values in the stage directory as it runs, so it can be
|
||||
run on a single standalone directory just to perform this validation.
|
||||
|
||||
This script is actually a wrapper around Panda's PackageMerger.py.
|
||||
|
||||
Usage:
|
||||
|
||||
%(prog)s [opts] [inputdir1 .. inputdirN]
|
||||
|
||||
Parameters:
|
||||
|
||||
inputdir1 .. inputdirN
|
||||
Specify the full path to the input directories you wish to merge.
|
||||
These are the directories specified by -i on the previous
|
||||
invocations of ppackage. The order is mostly unimportant.
|
||||
|
||||
Options:
|
||||
|
||||
-i install_dir
|
||||
The full path to the final install directory. This may also
|
||||
contain some pre-existing contents; if so, it is merged with all
|
||||
of the input directories as well. The contents of this directory
|
||||
are checked for self-consistency with regards to hashes and
|
||||
timestamps.
|
||||
|
||||
-p packageName[,packageName...]
|
||||
Specifies one or more particular packages by name that are to be
|
||||
included from the input directories. Any packages not in this
|
||||
list are left unchanged in the install directory, even if there
|
||||
is a newer version in one of the input directories. If no
|
||||
packages are named, all packages are involved. This option may
|
||||
be repeated.
|
||||
|
||||
-h
|
||||
Display this help
|
||||
|
||||
"""
|
||||
|
||||
import sys
|
||||
import getopt
|
||||
import os
|
||||
|
||||
from direct.p3d import PackageMerger
|
||||
from panda3d.core import Filename
|
||||
|
||||
def usage(code, msg = ''):
|
||||
sys.stderr.write(usageText % {'prog' : os.path.split(sys.argv[0])[1]})
|
||||
sys.stderr.write(msg + '\n')
|
||||
sys.exit(code)
|
||||
|
||||
try:
|
||||
opts, args = getopt.getopt(sys.argv[1:], 'i:p:h')
|
||||
except getopt.error as msg:
|
||||
usage(1, msg)
|
||||
|
||||
installDir = None
|
||||
packageNames = []
|
||||
for opt, arg in opts:
|
||||
if opt == '-i':
|
||||
installDir = Filename.fromOsSpecific(arg)
|
||||
elif opt == '-p':
|
||||
packageNames += arg.split(',')
|
||||
|
||||
elif opt == '-h':
|
||||
usage(0)
|
||||
else:
|
||||
print('illegal option: ' + arg)
|
||||
sys.exit(1)
|
||||
|
||||
if not packageNames:
|
||||
# No package names means allow all packages.
|
||||
packageNames = None
|
||||
|
||||
inputDirs = []
|
||||
for arg in args:
|
||||
inputDirs.append(Filename.fromOsSpecific(arg))
|
||||
|
||||
# It's now legal to have no input files if you only want to verify
|
||||
# timestamps and hashes.
|
||||
## if not inputDirs:
|
||||
## print "no input directories specified."
|
||||
## sys.exit(1)
|
||||
|
||||
try:
|
||||
pm = PackageMerger.PackageMerger(installDir)
|
||||
for dir in inputDirs:
|
||||
pm.merge(dir, packageNames = packageNames)
|
||||
pm.close()
|
||||
|
||||
except PackageMerger.PackageMergerError:
|
||||
# Just print the error message and exit gracefully.
|
||||
inst = sys.exc_info()[1]
|
||||
print(inst.args[0])
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
# An explicit call to exit() is required to exit the program, when
|
||||
# this module is packaged in a p3d file.
|
||||
sys.exit(0)
|
||||
|
|
@ -1,267 +0,0 @@
|
|||
#! /usr/bin/env python
|
||||
|
||||
usageText = """
|
||||
This script can be used to produce a Panda3D downloadable "package",
|
||||
which may contain arbitrary files--for instance, Python code, bam
|
||||
files, and/or compiled DLL's--and which may be downloaded by
|
||||
application code to extend an application at runtime.
|
||||
|
||||
In addition to packages, this script can also be used to build
|
||||
standalone p3d applications, that is, packaged Python code in a p3d
|
||||
file, for execution by the Panda3D plugin or runtime. (But also see
|
||||
packp3d, which is designed to be a simpler interface for building
|
||||
applications.)
|
||||
|
||||
This command will build p3d files that reference Panda3D %(version)s,
|
||||
from host %(host)s .
|
||||
|
||||
This script is actually a wrapper around Panda's Packager.py, which
|
||||
can be invoked directly by Python code that requires a programmatic
|
||||
interface to building packages.
|
||||
|
||||
Usage:
|
||||
|
||||
%(prog)s [opts] package.pdef [packageName1 .. packageNameN]
|
||||
|
||||
Parameters:
|
||||
|
||||
package.pdef
|
||||
The config file that describes the contents of the package file(s)
|
||||
to be built, in excruciating detail. See the Panda3D manual for
|
||||
the syntax of this file.
|
||||
|
||||
packageName1 .. packageNameN
|
||||
Specify the names of the package(s) you wish to build out of the
|
||||
package.pdef file. This allows you to build only a subset of the
|
||||
packages defined in this file. If you omit these parameters, all
|
||||
packages are built, and packages that cannot be built are silently
|
||||
ignored.
|
||||
|
||||
Options:
|
||||
|
||||
-i install_dir
|
||||
The full path to a local directory to copy the
|
||||
ready-to-be-published files into. This directory structure may
|
||||
contain multiple different packages from multiple different
|
||||
invocations of this script. It is the user's responsibility to
|
||||
copy this directory structure to a server, which will have the
|
||||
URL specified by the packager.setHost() call appearing within the
|
||||
pdef file.
|
||||
|
||||
-p
|
||||
Automatically build patches against previous versions after
|
||||
generating the results. Patches are difference files that users
|
||||
can download when updating a package, in lieu of redownloading
|
||||
the whole package; this happens automatically if patches are
|
||||
present. You should generally build patches when you are
|
||||
committing to a final, public-facing release. Patches are
|
||||
usually a good idea, but generating a patch for each internal
|
||||
test build may needlessly generate a lot of small, inefficient
|
||||
patch files instead of a few larger ones. You can also generate
|
||||
patches after the fact, by running ppatcher on the install
|
||||
directory.
|
||||
|
||||
-s search_dir
|
||||
Additional directories to search for previously-built packages.
|
||||
This option may be repeated as necessary. These directories may
|
||||
also be specified with the pdef-path Config.prc variable.
|
||||
|
||||
-S file.crt[,chain.crt[,file.key[,\"password\"]]]
|
||||
Signs any resulting p3d file(s) with the indicated certificate.
|
||||
You may specify the signing certificate, the optional
|
||||
authorization chain, and the private key in three different
|
||||
files, or they may all be combined in the first file. If the
|
||||
private key is encrypted, the password will be required to
|
||||
decrypt it.
|
||||
|
||||
-D
|
||||
Sets the allow_python_dev flag in any applications built with
|
||||
this command. This enables additional runtime debug operations,
|
||||
particularly the -i option to the panda3d command, which enables
|
||||
a live Python prompt within the application's environment.
|
||||
Setting this flag may be useful to develop an application
|
||||
initially, but should not be set on an application intended for
|
||||
deployment.
|
||||
|
||||
-N
|
||||
If this option is set, Packager will not try to compile any Python
|
||||
files to .pyc or .pyo, instead storing the original source files.
|
||||
|
||||
-u
|
||||
On the Mac OSX platform, this means that Panda was built with
|
||||
universal binaries, and the package should be built that way as
|
||||
well (that is, a version of the the package should be created for
|
||||
each supported architecture). On other platforms, this option
|
||||
does nothing. This is therefore safe to apply in all cases, if
|
||||
you wish to take advantage of universal binaries. This is
|
||||
equivalent to "-P osx_i386 -P osx_amd64" on Mac platforms.
|
||||
|
||||
-P platform
|
||||
Specify the platform to masquerade as. The default is whatever
|
||||
platform Panda has been built for. You can use this on Mac OSX
|
||||
in order to build packages for an alternate architecture if you
|
||||
have built Panda with universal binaries; you may repeat this
|
||||
option for each architecture you wish to support. For other
|
||||
platforms, it is probably a mistake to set this. However, see
|
||||
the option -u.
|
||||
|
||||
-R sysroot
|
||||
Specify the sysroot that these files were compiled against. This
|
||||
will shadow the system shared libraries, so that alternate
|
||||
versions are used instead of the system versions. If any program
|
||||
references a library, say /usr/lib/libfoo.so, and
|
||||
/sysroot/usr/lib/libfoo.so exists instead, that file will be used
|
||||
instead of the system library. This is particularly useful for
|
||||
cross-compilation. At the moment, this is supported only on OSX.
|
||||
|
||||
-H
|
||||
Treats a packager.setHost() call in the pdef file as if it were
|
||||
merely a call to packager.addHost(). This allows producing a
|
||||
package for an alternate host than its normally configured host,
|
||||
which is sometimes useful in development.
|
||||
|
||||
-a suffix
|
||||
Appends the given suffix to the p3d filename, before the extension.
|
||||
This is useful when the same packages are compiled several times
|
||||
but using different settings, and you want to mark those packages
|
||||
as such. This only applies to .p3d packages, not to other types
|
||||
of packages!
|
||||
|
||||
-v
|
||||
Emit a warning for any file not recognized by the dir() command
|
||||
(indicating there may be a need for addExtensions(...)).
|
||||
|
||||
-h
|
||||
Display this help
|
||||
"""
|
||||
|
||||
import sys
|
||||
import getopt
|
||||
import os
|
||||
|
||||
from direct.p3d import Packager
|
||||
from panda3d.core import *
|
||||
|
||||
def usage(code, msg = ''):
|
||||
sys.stderr.write(usageText % {
|
||||
'version' : PandaSystem.getPackageVersionString(),
|
||||
'host' : PandaSystem.getPackageHostUrl(),
|
||||
'prog' : os.path.split(sys.argv[0])[1]
|
||||
})
|
||||
sys.stderr.write(msg + '\n')
|
||||
sys.exit(code)
|
||||
|
||||
installDir = None
|
||||
buildPatches = False
|
||||
installSearch = []
|
||||
signParams = []
|
||||
allowPythonDev = False
|
||||
storePythonSource = False
|
||||
universalBinaries = False
|
||||
systemRoot = None
|
||||
ignoreSetHost = False
|
||||
verbosePrint = False
|
||||
p3dSuffix = ''
|
||||
platforms = []
|
||||
|
||||
try:
|
||||
opts, args = getopt.getopt(sys.argv[1:], 'i:ps:S:DNuP:R:Ha:hv')
|
||||
except getopt.error as msg:
|
||||
usage(1, msg)
|
||||
|
||||
for opt, arg in opts:
|
||||
if opt == '-i':
|
||||
installDir = Filename.fromOsSpecific(arg)
|
||||
elif opt == '-p':
|
||||
buildPatches = True
|
||||
elif opt == '-s':
|
||||
installSearch.append(Filename.fromOsSpecific(arg))
|
||||
elif opt == '-S':
|
||||
tokens = arg.split(',')
|
||||
while len(tokens) < 4:
|
||||
tokens.append('')
|
||||
certificate, chain, pkey, password = tokens[:4]
|
||||
signParams.append((Filename.fromOsSpecific(certificate),
|
||||
Filename.fromOsSpecific(chain),
|
||||
Filename.fromOsSpecific(pkey),
|
||||
Filename.fromOsSpecific(password)))
|
||||
elif opt == '-D':
|
||||
allowPythonDev = True
|
||||
elif opt == '-N':
|
||||
storePythonSource = True
|
||||
elif opt == '-u':
|
||||
universalBinaries = True
|
||||
elif opt == '-P':
|
||||
platforms.append(arg)
|
||||
elif opt == '-R':
|
||||
systemRoot = arg
|
||||
elif opt == '-H':
|
||||
ignoreSetHost = True
|
||||
elif opt == '-a':
|
||||
p3dSuffix = arg
|
||||
|
||||
elif opt == '-v':
|
||||
verbosePrint = True
|
||||
|
||||
elif opt == '-h':
|
||||
usage(0)
|
||||
else:
|
||||
print('illegal option: ' + arg)
|
||||
sys.exit(1)
|
||||
|
||||
if not args:
|
||||
usage(0)
|
||||
|
||||
packageDef = Filename.fromOsSpecific(args[0])
|
||||
packageNames = None
|
||||
if len(args) > 1:
|
||||
packageNames = args[1:]
|
||||
|
||||
# Add the directory containing the pdef file itself to sys.path, to
|
||||
# help the Packager locate modules where a pathname isn't specified.
|
||||
dirname = packageDef.getDirname()
|
||||
if dirname:
|
||||
sys.path.append(Filename(dirname).toOsSpecific())
|
||||
else:
|
||||
sys.path.append('.')
|
||||
|
||||
if universalBinaries:
|
||||
if platforms:
|
||||
print('\nYou may not specify both -u and -P.\n')
|
||||
sys.exit(1)
|
||||
if PandaSystem.getPlatform().startswith('osx_'):
|
||||
platforms = ['osx_i386', 'osx_amd64']
|
||||
|
||||
if not platforms:
|
||||
platforms = [PandaSystem.getPlatform()]
|
||||
|
||||
for platform in platforms:
|
||||
packager = Packager.Packager(platform = platform)
|
||||
packager.installDir = installDir
|
||||
packager.installSearch = installSearch + packager.installSearch
|
||||
if installDir is not None:
|
||||
packager.installSearch = [installDir] + packager.installSearch
|
||||
packager.signParams = signParams
|
||||
packager.allowPythonDev = allowPythonDev
|
||||
packager.storePythonSource = storePythonSource
|
||||
packager.systemRoot = systemRoot
|
||||
packager.ignoreSetHost = ignoreSetHost
|
||||
packager.verbosePrint = verbosePrint
|
||||
packager.p3dSuffix = p3dSuffix
|
||||
|
||||
try:
|
||||
packager.setup()
|
||||
packages = packager.readPackageDef(packageDef, packageNames = packageNames)
|
||||
packager.close()
|
||||
if buildPatches:
|
||||
packager.buildPatches(packages)
|
||||
|
||||
except Packager.PackagerError:
|
||||
# Just print the error message and exit gracefully.
|
||||
inst = sys.exc_info()[1]
|
||||
print(inst.args[0])
|
||||
sys.exit(1)
|
||||
|
||||
# An explicit call to exit() is required to exit the program, when
|
||||
# this module is packaged in a p3d file.
|
||||
sys.exit(0)
|
||||
|
|
@ -1,101 +0,0 @@
|
|||
#! /usr/bin/env python
|
||||
|
||||
usageText = """
|
||||
|
||||
This script generates the patches required to support incremental
|
||||
download of Panda3D packages. It can be run as a post-process on a
|
||||
directory hierarchy created by ppackage; it will examine the directory
|
||||
hierarchy, and create any patches that appear to be missing.
|
||||
|
||||
You may run ppackage on the same directory hierarchy as many times as
|
||||
you like, without creating patches. You may then download and test
|
||||
the resulting files--users connecting to the tree without fresh
|
||||
patches will be forced to download the entire file, instead of making
|
||||
an incremental download, but the entire process will work otherwise.
|
||||
When you are satisfied that all of the files are ready to be released,
|
||||
you may run ppackage on the directory hierarchy to generate the
|
||||
required patches.
|
||||
|
||||
Generating the patches just before final release is a good idea to
|
||||
limit the number of trivially small patches that are created. Each
|
||||
time this script is run, a patch is created from the previous version,
|
||||
and these patches daisy-chain together to define a complete update
|
||||
sequence. If you run this script on internal releases, you will
|
||||
generate a long chain of small patches that your users must download;
|
||||
this is pointless if there is no possibility of anyone having
|
||||
downloaded one of the intervening versions.
|
||||
|
||||
You can also generate patches with the -p option to ppackage, but that
|
||||
only generates patches for the specific packages built by that
|
||||
invocation of ppackage. If you use the ppatcher script instead, it
|
||||
will generate patches for all packages (or the set of packages that
|
||||
you name specifically).
|
||||
|
||||
This script is actually a wrapper around Panda's PatchMaker.py.
|
||||
|
||||
Usage:
|
||||
|
||||
%(prog)s [opts] [packageName1 .. packageNameN]
|
||||
|
||||
Parameters:
|
||||
|
||||
packageName1 .. packageNameN
|
||||
Specify the names of the package(s) you wish to generate patches
|
||||
for. This allows you to build patches for only a subset of the
|
||||
packages found in the tree. If you omit these parameters, patches
|
||||
are built for all packages that require them.
|
||||
|
||||
Options:
|
||||
|
||||
-i install_dir
|
||||
The full path to the install directory. This should be the same
|
||||
directory named by the -i parameter to ppackage.
|
||||
|
||||
-h
|
||||
Display this help
|
||||
|
||||
"""
|
||||
|
||||
import sys
|
||||
import getopt
|
||||
import os
|
||||
|
||||
from direct.p3d.PatchMaker import PatchMaker
|
||||
from panda3d.core import Filename
|
||||
|
||||
def usage(code, msg = ''):
|
||||
sys.stderr.write(usageText % {'prog' : os.path.split(sys.argv[0])[1]})
|
||||
sys.stderr.write(msg + '\n')
|
||||
sys.exit(code)
|
||||
|
||||
try:
|
||||
opts, args = getopt.getopt(sys.argv[1:], 'i:h')
|
||||
except getopt.error as msg:
|
||||
usage(1, msg)
|
||||
|
||||
installDir = None
|
||||
for opt, arg in opts:
|
||||
if opt == '-i':
|
||||
installDir = Filename.fromOsSpecific(arg)
|
||||
|
||||
elif opt == '-h':
|
||||
usage(0)
|
||||
else:
|
||||
print('illegal option: ' + arg)
|
||||
sys.exit(1)
|
||||
|
||||
packageNames = args
|
||||
|
||||
if not installDir:
|
||||
installDir = Filename('install')
|
||||
|
||||
if not packageNames:
|
||||
# "None" means all packages.
|
||||
packageNames = None
|
||||
|
||||
pm = PatchMaker(installDir)
|
||||
pm.buildPatches(packageNames = packageNames)
|
||||
|
||||
# An explicit call to exit() is required to exit the program, when
|
||||
# this module is packaged in a p3d file.
|
||||
sys.exit(0)
|
||||
|
|
@ -1,79 +0,0 @@
|
|||
#! /usr/bin/env python
|
||||
|
||||
"""
|
||||
|
||||
This tool will invoke the AppRunner to execute a packaged p3d
|
||||
application. It requires that that the current Panda3D and Python
|
||||
versions match the version expected by the application.
|
||||
|
||||
Normally, you do not need to use this tool; instead, use the provided
|
||||
standalone panda3d executable to invoke any p3d application. Using
|
||||
panda3d will guarantee that the correct versions of Panda3D and Python
|
||||
are used to run the application. However, there may be occasions when
|
||||
it is useful to use this tool to run the application with the current
|
||||
build instead of with its advertised version requirements.
|
||||
|
||||
Usage:
|
||||
|
||||
runp3d.py app.p3d [args]
|
||||
|
||||
The command-line arguments following the application name are passed
|
||||
into the application unchanged.
|
||||
|
||||
See pack3d.p3d for an application that generates these p3d files.
|
||||
|
||||
"""
|
||||
|
||||
import sys
|
||||
import getopt
|
||||
from .AppRunner import AppRunner, ArgumentError
|
||||
from direct.task.TaskManagerGlobal import taskMgr
|
||||
from panda3d.core import Filename
|
||||
|
||||
def parseSysArgs():
|
||||
""" Handles sys.argv, if there are any local arguments, and
|
||||
returns a new argv suitable for passing into the
|
||||
application. """
|
||||
|
||||
# We prefix a "+" sign, following the GNU convention, to tell
|
||||
# getopt not to parse options following the first non-option
|
||||
# parameter.
|
||||
opts, args = getopt.getopt(sys.argv[1:], '+h')
|
||||
|
||||
for option, value in opts:
|
||||
if option == '-h':
|
||||
print(__doc__)
|
||||
sys.exit(1)
|
||||
|
||||
if not args or not args[0]:
|
||||
raise ArgumentError("No Panda app specified. Use:\nrunp3d.py app.p3d")
|
||||
|
||||
arg0 = args[0]
|
||||
p3dFilename = Filename.fromOsSpecific(arg0)
|
||||
if p3dFilename.exists():
|
||||
p3dFilename.makeAbsolute()
|
||||
arg0 = p3dFilename.toOsSpecific()
|
||||
|
||||
return [arg0] + args[1:]
|
||||
|
||||
def runPackedApp(pathname):
|
||||
runner = AppRunner()
|
||||
runner.gotWindow = True
|
||||
try:
|
||||
runner.setP3DFilename(pathname, tokens = [], argv = [],
|
||||
instanceId = 0, interactiveConsole = False)
|
||||
except ArgumentError as e:
|
||||
print(e.args[0])
|
||||
sys.exit(1)
|
||||
|
||||
if __name__ == '__main__':
|
||||
runner = AppRunner()
|
||||
runner.gotWindow = True
|
||||
try:
|
||||
argv = parseSysArgs()
|
||||
runner.setP3DFilename(argv[0], tokens = [], argv = argv,
|
||||
instanceId = 0, interactiveConsole = False)
|
||||
except ArgumentError as e:
|
||||
print(e.args[0])
|
||||
sys.exit(1)
|
||||
taskMgr.run()
|
||||
|
|
@ -1,167 +0,0 @@
|
|||
# This file defines a number of standard "packages" that correspond to
|
||||
# a Panda3D distribution. These packages are built by passing this
|
||||
# file to the ppackage utility, either as a packaged application, or
|
||||
# as the module direct.p3d.ppackage.
|
||||
|
||||
# The packages in this file define the thirdparty
|
||||
# packages that could be used by a .p3d archive.
|
||||
|
||||
# When needed, these packages are downloaded by the core API, from the
|
||||
# host URL specified in a given p3d file, and not from any hardcoded
|
||||
# URL. Thus, any custom version of Panda3D may be hosted on any
|
||||
# server in the world, and any version of the plugin can download it.
|
||||
|
||||
# Also see panda3d.pdef.
|
||||
|
||||
from panda3d.core import Filename
|
||||
import sys
|
||||
|
||||
class wx(package):
|
||||
config(display_name = "wxPython GUI Toolkit")
|
||||
#config(gui_app = True)
|
||||
require('panda3d')
|
||||
|
||||
module('direct.showbase.WxGlobal')
|
||||
module('direct.wxwidgets')
|
||||
module('direct.wxwidgets.*')
|
||||
module('wx', required = True)
|
||||
module('wx.*')
|
||||
|
||||
class tk(package):
|
||||
config(display_name = "Tk GUI Toolkit")
|
||||
#config(gui_app = True)
|
||||
require('panda3d')
|
||||
|
||||
if sys.version_info >= (3, 0):
|
||||
module('tkinter', '_tkinter', required = True)
|
||||
module('tkinter.colorchooser', 'tkinter.commondialog',
|
||||
'tkinter.constants', 'tkinter.dialog', 'tkinter.dnd',
|
||||
'tkinter.filedialog', 'tkinter.font', 'tkinter.messagebox',
|
||||
'tkinter.scrolledtext', 'tkinter.simpledialog', 'tkinter.tix',
|
||||
'tkinter.ttk')
|
||||
else:
|
||||
module('Tkinter', '_tkinter', required = True)
|
||||
module('Tkconstants', 'Tkdnd', 'Tix', 'ScrolledText', 'turtle',
|
||||
'tkColorChooser', 'tkCommonDialog', 'tkFileDialog',
|
||||
'tkFont', 'tkMessageBox', 'tkSimpleDialog')
|
||||
|
||||
module('direct.showbase.TkGlobal',
|
||||
'direct.tkpanels',
|
||||
'direct.tkwidgets')
|
||||
|
||||
try:
|
||||
if sys.version_info >= (3, 0):
|
||||
from tkinter import Tcl
|
||||
else:
|
||||
from Tkinter import Tcl
|
||||
tcl = Tcl()
|
||||
dir = Filename.fromOsSpecific(tcl.eval("info library"))
|
||||
ver = tcl.eval("info tclversion")
|
||||
|
||||
file(Filename(dir, "*.tcl"), newDir = 'lib/tcl' + ver, extract = True)
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
try:
|
||||
import Pmw
|
||||
except ImportError:
|
||||
Pmw = None
|
||||
|
||||
if Pmw is not None:
|
||||
Pmw = sys.modules['_Pmw']
|
||||
sys.modules['Pmw'] = Pmw
|
||||
sys.modules['Pmw'].__name__ = 'Pmw'
|
||||
module('Pmw', 'Pmw.Pmw_1_3.lib.*')
|
||||
path = Filename.fromOsSpecific(Pmw.__path__[0])
|
||||
file(Filename(path, 'Pmw_1_3/lib/Pmw.def'), newDir = 'Pmw/Pmw_1_3/lib')
|
||||
|
||||
class sqlite(package):
|
||||
config(display_name = "SQLite Interface")
|
||||
require('panda3d')
|
||||
|
||||
module('sqlite', 'sqlite3')
|
||||
|
||||
class numpy(package):
|
||||
config(display_name = "NumPy")
|
||||
require('panda3d')
|
||||
|
||||
module('numpy', required = True)
|
||||
|
||||
class pygame(package):
|
||||
config(display_name = "PyGame")
|
||||
require('panda3d', 'numpy')
|
||||
|
||||
module('pygame', required = True)
|
||||
module('pygame.*', 'pygame._view')
|
||||
|
||||
class twisted(package):
|
||||
config(display_name = "Twisted")
|
||||
require('panda3d')
|
||||
|
||||
module('twisted', 'twisted._version', required = True)
|
||||
module('twisted.application', 'twisted.conch', 'twisted.cred',
|
||||
'twisted.enterprise', 'twisted.internet', 'twisted.lore',
|
||||
'twisted.mail', 'twisted.manhole', 'twisted.names',
|
||||
'twisted.news', 'twisted.pair', 'twisted.persisted',
|
||||
'twisted.plugin', 'twisted.plugins', 'twisted.python',
|
||||
'twisted.runner', 'twisted.scripts', 'twisted.spread',
|
||||
'twisted.tap', 'twisted.trial', 'twisted.vfs',
|
||||
'twisted.web', 'twisted.web2', 'twisted.words')
|
||||
|
||||
module('twisted.*', 'twisted.*.*')
|
||||
|
||||
class pil(package):
|
||||
config(display_name = "Python Imaging Library")
|
||||
require('panda3d')
|
||||
|
||||
module('Image', required = True)
|
||||
module('PIL', 'ArgImagePlugin', 'BdfFontFile',
|
||||
'BmpImagePlugin', 'BufrStubImagePlugin', 'ContainerIO',
|
||||
'CurImagePlugin', 'DcxImagePlugin', 'EpsImagePlugin',
|
||||
'ExifTags', 'FitsStubImagePlugin', 'FliImagePlugin',
|
||||
'FontFile', 'FpxImagePlugin', 'GbrImagePlugin', 'GdImageFile',
|
||||
'GifImagePlugin', 'GimpGradientFile', 'GimpPaletteFile',
|
||||
'GribStubImagePlugin', 'Hdf5StubImagePlugin',
|
||||
'IcnsImagePlugin', 'IcoImagePlugin', 'ImImagePlugin',
|
||||
'ImageChops', 'ImageCms', 'ImageColor', 'ImageDraw',
|
||||
'ImageDraw2', 'ImageEnhance', 'ImageFile', 'ImageFileIO',
|
||||
'ImageFilter', 'ImageFont', 'ImageGL', 'ImageGrab',
|
||||
'ImageMath', 'ImageMode', 'ImageOps', 'ImagePalette',
|
||||
'ImagePath', 'ImageQt', 'ImageSequence', 'ImageShow',
|
||||
'ImageStat', 'ImageTk', 'ImageTransform', 'ImageWin',
|
||||
'ImtImagePlugin', 'IptcImagePlugin', 'JpegImagePlugin',
|
||||
'McIdasImagePlugin', 'MicImagePlugin', 'MpegImagePlugin',
|
||||
'MspImagePlugin', 'OleFileIO', 'PSDraw', 'PaletteFile',
|
||||
'PalmImagePlugin', 'PcdImagePlugin', 'PcfFontFile',
|
||||
'PcxImagePlugin', 'PdfImagePlugin', 'PixarImagePlugin',
|
||||
'PngImagePlugin', 'PpmImagePlugin', 'PsdImagePlugin',
|
||||
'SgiImagePlugin', 'SpiderImagePlugin', 'SunImagePlugin',
|
||||
'TarIO', 'TgaImagePlugin', 'TiffImagePlugin', 'TiffTags',
|
||||
'WalImageFile', 'WmfImagePlugin', 'XVThumbImagePlugin',
|
||||
'XbmImagePlugin', 'XpmImagePlugin', '_imaging',
|
||||
'_imagingmath', '_imagingtk')
|
||||
|
||||
class pyopengl(package):
|
||||
config(display_name = "PyOpenGL")
|
||||
require('panda3d', 'numpy')
|
||||
|
||||
module('OpenGL', 'OpenGL.GL', required = True)
|
||||
module('OpenGL.GLU', 'OpenGL.GLUT', 'OpenGL.GLE', 'OpenGL.GLX')
|
||||
|
||||
class httplib2(package):
|
||||
config(display_name = "httplib2", platform_specific = False)
|
||||
require('panda3d')
|
||||
|
||||
module('httplib2', required = True)
|
||||
|
||||
class box2d(package):
|
||||
config(display_name = "Box2D")
|
||||
require('panda3d')
|
||||
|
||||
module('Box2D', required = True)
|
||||
|
||||
class pyglet(package):
|
||||
config(display_name = "pyglet", platform_specific = False)
|
||||
require('panda3d', 'morepy')
|
||||
|
||||
module('pyglet', required = True)
|
||||
|
|
@ -7,6 +7,12 @@ from . import Particles
|
|||
from . import ForceGroup
|
||||
|
||||
from direct.directnotify import DirectNotifyGlobal
|
||||
import sys
|
||||
|
||||
|
||||
if sys.version_info < (3, 0):
|
||||
FileNotFoundError = IOError
|
||||
|
||||
|
||||
class ParticleEffect(NodePath):
|
||||
notify = DirectNotifyGlobal.directNotify.newCategory('ParticleEffect')
|
||||
|
|
@ -163,49 +169,51 @@ class ParticleEffect(NodePath):
|
|||
|
||||
def saveConfig(self, filename):
|
||||
filename = Filename(filename)
|
||||
f = open(filename.toOsSpecific(), 'wb')
|
||||
# Add a blank line
|
||||
f.write('\n')
|
||||
with open(filename.toOsSpecific(), 'w') as f:
|
||||
# Add a blank line
|
||||
f.write('\n')
|
||||
|
||||
# Make sure we start with a clean slate
|
||||
f.write('self.reset()\n')
|
||||
# Make sure we start with a clean slate
|
||||
f.write('self.reset()\n')
|
||||
|
||||
pos = self.getPos()
|
||||
hpr = self.getHpr()
|
||||
scale = self.getScale()
|
||||
f.write('self.setPos(%0.3f, %0.3f, %0.3f)\n' %
|
||||
(pos[0], pos[1], pos[2]))
|
||||
f.write('self.setHpr(%0.3f, %0.3f, %0.3f)\n' %
|
||||
(hpr[0], hpr[1], hpr[2]))
|
||||
f.write('self.setScale(%0.3f, %0.3f, %0.3f)\n' %
|
||||
(scale[0], scale[1], scale[2]))
|
||||
pos = self.getPos()
|
||||
hpr = self.getHpr()
|
||||
scale = self.getScale()
|
||||
f.write('self.setPos(%0.3f, %0.3f, %0.3f)\n' %
|
||||
(pos[0], pos[1], pos[2]))
|
||||
f.write('self.setHpr(%0.3f, %0.3f, %0.3f)\n' %
|
||||
(hpr[0], hpr[1], hpr[2]))
|
||||
f.write('self.setScale(%0.3f, %0.3f, %0.3f)\n' %
|
||||
(scale[0], scale[1], scale[2]))
|
||||
|
||||
# Save all the particles to file
|
||||
num = 0
|
||||
for p in list(self.particlesDict.values()):
|
||||
target = 'p%d' % num
|
||||
num = num + 1
|
||||
f.write(target + ' = Particles.Particles(\'%s\')\n' % p.getName())
|
||||
p.printParams(f, target)
|
||||
f.write('self.addParticles(%s)\n' % target)
|
||||
# Save all the particles to file
|
||||
num = 0
|
||||
for p in list(self.particlesDict.values()):
|
||||
target = 'p%d' % num
|
||||
num = num + 1
|
||||
f.write(target + ' = Particles.Particles(\'%s\')\n' % p.getName())
|
||||
p.printParams(f, target)
|
||||
f.write('self.addParticles(%s)\n' % target)
|
||||
|
||||
# Save all the forces to file
|
||||
num = 0
|
||||
for fg in list(self.forceGroupDict.values()):
|
||||
target = 'f%d' % num
|
||||
num = num + 1
|
||||
f.write(target + ' = ForceGroup.ForceGroup(\'%s\')\n' % \
|
||||
fg.getName())
|
||||
fg.printParams(f, target)
|
||||
f.write('self.addForceGroup(%s)\n' % target)
|
||||
|
||||
# Close the file
|
||||
f.close()
|
||||
# Save all the forces to file
|
||||
num = 0
|
||||
for fg in list(self.forceGroupDict.values()):
|
||||
target = 'f%d' % num
|
||||
num = num + 1
|
||||
f.write(target + ' = ForceGroup.ForceGroup(\'%s\')\n' % \
|
||||
fg.getName())
|
||||
fg.printParams(f, target)
|
||||
f.write('self.addForceGroup(%s)\n' % target)
|
||||
|
||||
def loadConfig(self, filename):
|
||||
data = vfs.readFile(filename, 1)
|
||||
data = data.replace(b'\r', b'')
|
||||
fn = Filename(filename)
|
||||
vfs = VirtualFileSystem.getGlobalPtr()
|
||||
try:
|
||||
if not vfs.resolveFilename(fn, getModelPath().value) and not fn.isRegularFile():
|
||||
raise FileNotFoundError("could not find particle file: %s" % (filename))
|
||||
|
||||
data = vfs.readFile(fn, True)
|
||||
data = data.replace(b'\r', b'')
|
||||
exec(data)
|
||||
except:
|
||||
self.notify.warning('loadConfig: failed to load particle file: '+ repr(filename))
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ from panda3d.physics import RingEmitter
|
|||
from panda3d.physics import SphereSurfaceEmitter
|
||||
from panda3d.physics import SphereVolumeEmitter
|
||||
from panda3d.physics import TangentRingEmitter
|
||||
from panda3d.physics import SpriteAnim
|
||||
|
||||
from . import SpriteParticleRendererExt
|
||||
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue