per_platform flag, giving better compatibility with old p3d files

This commit is contained in:
David Rose 2012-09-28 20:26:22 +00:00
parent 526159f961
commit a737ebcc90
19 changed files with 247 additions and 89 deletions

View File

@ -104,6 +104,7 @@ class AppRunner(DirectObject):
self.interactiveConsole = False
self.initialAppImport = False
self.trueFileIO = False
self.respectPerPlatform = None
self.verifyContents = self.P3DVCNone
@ -493,7 +494,7 @@ class AppRunner(DirectObject):
hostData = InstalledHostData(host, dirnode)
if host:
for package in host.getAllPackages():
for package in host.getAllPackages(includeAllPlatforms = True):
packageDir = package.getPackageDir()
if not packageDir.exists():
continue
@ -781,7 +782,7 @@ class AppRunner(DirectObject):
self.deferredEvals = []
def setInstanceInfo(self, rootDir, logDirectory, superMirrorUrl,
verifyContents, main):
verifyContents, main, respectPerPlatform):
""" Called by the browser to set some global information about
the instance. """
@ -807,6 +808,9 @@ class AppRunner(DirectObject):
if main is not None:
self.main = main
self.respectPerPlatform = respectPerPlatform
#self.notify.info("respectPerPlatform = %s" % (self.respectPerPlatform))
# Now that we have rootDir, we can read the config file.
self.readConfigXml()

View File

@ -28,9 +28,16 @@ class HostInfo:
there. At the moment, mirror folders do not download old
patch files from the server.
perPlatform remains for historical reasons, but it is ignored.
Nowadays, all files are always unpacked into platform-specific
directories, even on the client. """
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. """
assert appRunner or rootDir or hostDir
@ -45,7 +52,9 @@ class HostInfo:
self.hostDir = hostDir
self.asMirror = asMirror
self.perPlatform = True
self.perPlatform = perPlatform
if perPlatform is None:
self.perPlatform = asMirror
# Initially false, this is set true when the contents file is
# successfully read.
@ -372,8 +381,12 @@ class HostInfo:
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)
package = self.__makePackage(name, platform, version, solo, perPlatform)
package.descFile = FileSpec()
package.descFile.loadXml(xpackage)
package.setupFilenames()
@ -493,7 +506,7 @@ class HostInfo:
self.altHosts[keyword] = url
xalthost = xalthost.NextSiblingElement('alt_host')
def __makePackage(self, name, platform, version, solo):
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. """
@ -507,7 +520,8 @@ class HostInfo:
package = platforms.get(platform, None)
if not package:
package = PackageInfo(self, name, version, platform = platform,
solo = solo, asMirror = self.asMirror)
solo = solo, asMirror = self.asMirror,
perPlatform = perPlatform)
platforms[platform] = package
return package
@ -560,7 +574,7 @@ class HostInfo:
return packages
def getAllPackages(self):
def getAllPackages(self, includeAllPlatforms = False):
""" Returns a list of all available packages provided by this
host. """
@ -569,7 +583,7 @@ class HostInfo:
items = self.packages.items()
items.sort()
for key, platforms in items:
if self.perPlatform:
if self.perPlatform or includeAllPlatforms:
# If we maintain a different answer per platform,
# return all of them.
pitems = platforms.items()

View File

@ -78,13 +78,14 @@ class PackageInfo:
return min(float(self.bytesDone) / float(self.bytesNeeded), 1)
def __init__(self, host, packageName, packageVersion, platform = None,
solo = False, asMirror = False):
solo = False, asMirror = False, perPlatform = False):
self.host = host
self.packageName = packageName
self.packageVersion = packageVersion
self.platform = platform
self.solo = solo
self.asMirror = asMirror
self.perPlatform = perPlatform
# This will be active while we are in the middle of a download
# cycle.
@ -144,12 +145,23 @@ class PackageInfo:
self.packageDir = Filename(self.packageDir, self.packageVersion)
if self.host.perPlatform:
# The server directory contains the platform name,
# though the client directory normally doesn't (unless
# perPlatform is set true).
# If we're running on a special host that wants us to
# include the platform, we include it.
includePlatform = True
elif self.perPlatform and self.host.appRunner.respectPerPlatform:
# Otherwise, if our package spec wants us to include
# the platform (and our plugin knows about this), then
# we also include it.
includePlatform = True
else:
# Otherwise, we must be running legacy code
# somewhere--either an old package or an old
# plugin--and we therefore shouldn't include the
# platform in the directory hierarchy.
includePlatform = False
if self.platform:
self.packageDir = Filename(self.packageDir, self.platform)
if includePlatform and self.platform:
self.packageDir = Filename(self.packageDir, self.platform)
return self.packageDir
@ -346,6 +358,13 @@ class PackageInfo:
except ValueError:
self.patchVersion = None
try:
perPlatform = int(xpackage.Attribute('per_platform') or '')
except ValueError:
perPlatform = False
if perPlatform != self.perPlatform:
self.notify.warning("per_platform disagreement on package %s" % (self.packageName))
self.displayName = None
xconfig = xpackage.FirstChildElement('config')
if xconfig:

View File

@ -41,6 +41,8 @@ class PackageMerger:
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)
@ -71,6 +73,8 @@ class PackageMerger:
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')

View File

@ -187,12 +187,13 @@ class Packager:
objects uniquely per package. """
return (self.packageName, self.platform, self.version)
def fromFile(self, packageName, platform, version, solo,
def fromFile(self, packageName, platform, version, solo, perPlatform,
installDir, descFilename, importDescFilename):
self.packageName = packageName
self.platform = platform
self.version = version
self.solo = solo
self.perPlatform = perPlatform
self.descFile = FileSpec()
self.descFile.fromFile(installDir, descFilename)
@ -208,6 +209,8 @@ class Packager:
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.packageSeq = SeqValue()
self.packageSeq.loadXml(xpackage, 'seq')
@ -235,6 +238,8 @@ class Packager:
xpackage.SetAttribute('version', self.version)
if self.solo:
xpackage.SetAttribute('solo', '1')
if self.perPlatform:
xpackage.SetAttribute('per_platform', '1')
self.packageSeq.storeXml(xpackage, 'seq')
self.packageSetVer.storeXml(xpackage, 'set_ver')
@ -322,6 +327,9 @@ class Packager:
# platform-specific.
self.platform = None
# This is always true on modern packages.
self.perPlatform = True
# The arch string, though, is pre-loaded from the system
# arch string, so we can sensibly call otool.
self.arch = self.packager.arch
@ -722,6 +730,7 @@ class Packager:
else:
self.readDescFile()
self.packageSeq += 1
self.perPlatform = True # always true on modern packages.
self.compressMultifile()
self.writeDescFile()
self.writeImportDescFile()
@ -734,7 +743,7 @@ class Packager:
# Replace or add the entry in the contents.
pe = Packager.PackageEntry()
pe.fromFile(self.packageName, self.platform, self.version,
False, self.packager.installDir,
False, self.perPlatform, self.packager.installDir,
self.packageDesc, self.packageImportDesc)
pe.packageSeq = self.packageSeq
pe.packageSetVer = self.packageSetVer
@ -753,6 +762,7 @@ class Packager:
kinds of similar "solo" packages as well. """
self.considerPlatform()
self.perPlatform = False # Not true on "solo" packages.
packageDir = self.packageName
if self.platform:
@ -798,7 +808,7 @@ class Packager:
# Replace or add the entry in the contents.
pe = Packager.PackageEntry()
pe.fromFile(self.packageName, self.platform, self.version,
True, self.packager.installDir,
True, self.perPlatform, self.packager.installDir,
Filename(packageDir, file.newName), None)
peOrig = self.packager.contents.get(pe.getKey(), None)
if peOrig:
@ -1509,6 +1519,9 @@ class Packager:
if not xpackage:
return
perPlatform = xpackage.Attribute('per_platform')
self.perPlatform = int(perPlatform or '0')
self.packageSeq.loadXml(xpackage, 'seq')
self.packageSetVer.loadXml(xpackage, 'set_ver')
@ -1554,6 +1567,8 @@ class Packager:
xpackage.SetAttribute('platform', self.platform)
if self.version:
xpackage.SetAttribute('version', self.version)
if self.perPlatform:
xpackage.SetAttribute('per_platform', '1')
if self.patchVersion:
xpackage.SetAttribute('last_patch_version', self.patchVersion)

View File

@ -70,6 +70,7 @@
p3dX11SplashWindow.h p3dX11SplashWindow.I \
p3dWindowParams.h p3dWindowParams.I \
plugin_get_x11.h \
xml_helpers.h \
run_p3dpython.h
#define COREAPI_INCLUDED_SOURCES \
@ -104,7 +105,8 @@
p3dUndefinedObject.cxx \
p3dWinSplashWindow.cxx \
p3dX11SplashWindow.cxx \
p3dWindowParams.cxx
p3dWindowParams.cxx \
xml_helpers.cxx
#begin lib_target

View File

@ -89,28 +89,38 @@ set_p3d_url(const string &p3d_url) {
////////////////////////////////////////////////////////////////////
// Function: P3DFileParams::set_tokens
// Access: Public
// Description: Specifies the tokens associated with the instance.
// Description: Replaces all the tokens associated with the instance.
////////////////////////////////////////////////////////////////////
void P3DFileParams::
set_tokens(const P3D_token tokens[], size_t num_tokens) {
_tokens.clear();
for (size_t i = 0; i < num_tokens; ++i) {
Token token;
if (tokens[i]._keyword != NULL) {
// Make the token lowercase, since HTML is case-insensitive but
// we're not.
for (const char *p = tokens[i]._keyword; *p; ++p) {
token._keyword += tolower(*p);
}
}
if (tokens[i]._value != NULL) {
token._value = tokens[i]._value;
}
_tokens.push_back(token);
set_token(tokens[i]._keyword, tokens[i]._value);
}
}
////////////////////////////////////////////////////////////////////
// Function: P3DFileParams::set_token
// Access: Public
// Description: Sets an individual token value.
////////////////////////////////////////////////////////////////////
void P3DFileParams::
set_token(const char *keyword, const char *value) {
Token token;
if (keyword != NULL) {
// Make the token lowercase, since HTML is case-insensitive but
// we're not.
for (const char *p = keyword; *p; ++p) {
token._keyword += tolower(*p);
}
}
if (value != NULL) {
token._value = value;
}
_tokens.push_back(token);
}
////////////////////////////////////////////////////////////////////
// Function: P3DFileParams::set_args
// Access: Public

View File

@ -34,6 +34,7 @@ public:
void set_p3d_offset(const int &p3d_offset);
void set_p3d_url(const string &p3d_url);
void set_tokens(const P3D_token tokens[], size_t num_tokens);
void set_token(const char *keyword, const char *value);
void set_args(int argc, const char *argv[]);
inline const string &get_p3d_filename() const;

View File

@ -17,6 +17,7 @@
#include "p3dPackage.h"
#include "mkdir_complete.h"
#include "wstring_encode.h"
#include "xml_helpers.h"
#include "openssl/md5.h"
#include <algorithm>
@ -445,12 +446,15 @@ get_package(const string &package_name, const string &package_version,
// Function: P3DHost::choose_suitable_platform
// Access: Public
// Description: Chooses the most appropriate platform for the
// indicated package (presumably the "panda3d" package),
// based on what this hardware supports and what is
// actually available.
// indicated package based on what this hardware
// supports and what is actually available. Also fills
// in per_platform, which is a boolean value indicating
// whether the directory structure contains the platform
// directory or not.
////////////////////////////////////////////////////////////////////
bool P3DHost::
choose_suitable_platform(string &selected_platform,
bool &per_platform,
const string &package_name,
const string &package_version,
const string &package_platform) {
@ -482,6 +486,7 @@ choose_suitable_platform(string &selected_platform,
package_version == version) {
// Here's the matching package definition.
selected_platform = platform;
per_platform = parse_bool_attrib(xpackage, "per_platform", false);
return true;
}
@ -490,7 +495,7 @@ choose_suitable_platform(string &selected_platform,
}
}
// Now, we look for an exact match for the current platform.
// Now, we look for an exact match for the expected platform.
xpackage = _xcontents->FirstChildElement("package");
while (xpackage != NULL) {
const char *name = xpackage->Attribute("name");
@ -505,13 +510,15 @@ choose_suitable_platform(string &selected_platform,
package_version == version) {
// Here's the matching package definition.
selected_platform = platform;
per_platform = parse_bool_attrib(xpackage, "per_platform", false);
return true;
}
xpackage = xpackage->NextSiblingElement("package");
}
// Look again, this time looking for a non-platform-specific version.
// Look one more time, this time looking for a non-platform-specific
// version.
xpackage = _xcontents->FirstChildElement("package");
while (xpackage != NULL) {
const char *name = xpackage->Attribute("name");
@ -528,6 +535,7 @@ choose_suitable_platform(string &selected_platform,
*platform == '\0' &&
package_version == version) {
selected_platform = platform;
per_platform = parse_bool_attrib(xpackage, "per_platform", false);
return true;
}
@ -561,8 +569,9 @@ get_package_desc_file(FileSpec &desc_file, // out
P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
// Scan the contents data for the indicated package. First, we look
// for a platform-specific version.
// Scan the contents data for the indicated package. We expect to
// match the platform precisely, because we previously called
// choose_suitable_platform().
TiXmlElement *xpackage = _xcontents->FirstChildElement("package");
while (xpackage != NULL) {
const char *name = xpackage->Attribute("name");
@ -593,40 +602,6 @@ get_package_desc_file(FileSpec &desc_file, // out
xpackage = xpackage->NextSiblingElement("package");
}
// Look again, this time looking for a non-platform-specific version.
xpackage = _xcontents->FirstChildElement("package");
while (xpackage != NULL) {
const char *name = xpackage->Attribute("name");
const char *platform = xpackage->Attribute("platform");
const char *version = xpackage->Attribute("version");
const char *seq = xpackage->Attribute("seq");
const char *solo = xpackage->Attribute("solo");
if (platform == NULL) {
platform = "";
}
if (version == NULL) {
version = "";
}
if (seq == NULL) {
seq = "";
}
if (name != NULL &&
package_name == name &&
*platform == '\0' &&
package_version == version) {
// Here's the matching package definition.
desc_file.load_xml(xpackage);
package_seq = seq;
package_solo = false;
if (solo != NULL) {
package_solo = (atoi(solo) != 0);
}
return true;
}
xpackage = xpackage->NextSiblingElement("package");
}
// Couldn't find the named package.
return false;
}

View File

@ -58,6 +58,7 @@ public:
const string &package_seq,
const string &alt_host = "");
bool choose_suitable_platform(string &selected_platform,
bool &per_platform,
const string &package_name,
const string &package_version,
const string &package_platform);

View File

@ -1366,10 +1366,15 @@ make_xml() {
xinstance->SetAttribute("log_directory", inst_mgr->get_log_directory());
xinstance->SetAttribute("verify_contents", (int)inst_mgr->get_verify_contents());
// Tell the Panda process that it was started by a plugin that knows
// about the new per_platform flag.
xinstance->SetAttribute("respect_per_platform", 1);
if (!inst_mgr->get_super_mirror().empty()) {
xinstance->SetAttribute("super_mirror", inst_mgr->get_super_mirror());
}
TiXmlElement *xfparams = _fparams.make_xml();
xinstance->LinkEndChild(xfparams);

View File

@ -300,6 +300,12 @@ initialize(int api_version, const string &contents_filename,
}
}
nout << "Supported platforms:";
for (size_t pi = 0; pi < _supported_platforms.size(); ++pi) {
nout << " " << _supported_platforms[pi];
}
nout << "\n";
return true;
}

View File

@ -674,7 +674,7 @@ read_log_file(const string &log_pathname,
log.seekg(0, ios::beg);
log.read(buffer, full_bytes);
streamsize read_bytes = log.gcount();
assert(read_bytes < buffer_bytes);
assert(read_bytes < (streamsize)buffer_bytes);
buffer[read_bytes] = '\0';
log_data << "== PandaLog-" << "Full Start";
log_data << " " << "(" << log_leafname << ")" << "\n";
@ -688,7 +688,7 @@ read_log_file(const string &log_pathname,
log.seekg(0, ios::beg);
log.read(buffer, head_bytes);
streamsize read_bytes = log.gcount();
assert(read_bytes < buffer_bytes);
assert(read_bytes < (streamsize)buffer_bytes);
buffer[read_bytes] = '\0';
log_data << "== PandaLog-" << "Head Start";
log_data << " " << "(" << log_leafname << ")" << "\n";
@ -708,7 +708,7 @@ read_log_file(const string &log_pathname,
log.seekg(file_size - tail_bytes, ios::beg);
log.read(buffer, tail_bytes);
streamsize read_bytes = log.gcount();
assert(read_bytes < buffer_bytes);
assert(read_bytes < (streamsize)buffer_bytes);
buffer[read_bytes] = '\0';
log_data << "== PandaLog-" << "Tail Start";
log_data << " " << "(" << log_leafname << ")" << "\n";

View File

@ -52,10 +52,8 @@ P3DPackage(P3DHost *host, const string &package_name,
_package_platform(package_platform),
_alt_host(alt_host)
{
_package_fullname = _package_name;
if (!_package_version.empty()) {
_package_fullname += string(".") + _package_version;
}
set_fullname();
_per_platform = false;
_patch_version = 0;
// This is set true if the package is a "solo", i.e. a single
@ -638,24 +636,28 @@ host_got_contents_file() {
// provided.
assert(_alt_host.empty());
string new_platform;
if (_host->choose_suitable_platform(new_platform, _package_name, _package_version, _package_platform)) {
if (_host->choose_suitable_platform(new_platform, _per_platform,
_package_name, _package_version, _package_platform)) {
if (new_platform != _package_platform) {
nout << "Migrating " << get_package_name() << " from platform \""
<< _package_platform << "\" to platform \""
<< new_platform << "\"\n";
_package_platform = new_platform;
set_fullname();
}
} else {
nout << "Couldn't find a platform for " << get_package_name() << ".\n";
}
nout << "_per_platform for " << get_package_name() << " = " << _per_platform << "\n";
// Now that we have a valid host and platform, we can define the
// _package_dir.
_package_dir = _host->get_host_dir() + string("/") + _package_name;
if (!_package_version.empty()) {
_package_dir += string("/") + _package_version;
}
if (!_package_platform.empty()) {
if (_per_platform && !_package_platform.empty()) {
_package_dir += string("/") + _package_platform;
}
@ -686,7 +688,7 @@ download_desc_file() {
_package_name, _package_version,
_package_platform)) {
nout << "Couldn't find package " << _package_fullname
<< "/" << _package_platform << " in contents file.\n";
<< " in contents file.\n";
redownload_contents_file(NULL);
return;
}
@ -702,8 +704,8 @@ download_desc_file() {
}
// The desc file might have a different path on the host server than
// it has locally, because we strip out the platform directory
// locally.
// it has locally, because we might strip out the platform directory
// locally (according to _per_platform).
FileSpec local_desc_file = _desc_file;
local_desc_file.set_filename(_desc_file_basename);
_desc_file_pathname = local_desc_file.get_pathname(_package_dir);
@ -791,6 +793,16 @@ got_desc_file(TiXmlDocument *doc, bool freshly_downloaded) {
report_done(false);
return;
}
bool per_platform = parse_bool_attrib(xpackage, "per_platform", false);
if (per_platform != _per_platform) {
nout << "Warning! per_platform disagreement for " << get_package_name()
<< "!\n";
// We don't do anything with this warning--the original value for
// _per_platform we got from the contents.xml file has to apply,
// because we're already committed to the _package_dir we're
// using.
}
xpackage->Attribute("patch_version", &_patch_version);
@ -1458,6 +1470,23 @@ instance_terminating(P3DInstance *instance) {
return true;
}
////////////////////////////////////////////////////////////////////
// Function: P3DPackage::set_fullname
// Access: Private
// Description: Assigns _package_fullname to the appropriate
// combination of name, version, and platform.
////////////////////////////////////////////////////////////////////
void P3DPackage::
set_fullname() {
_package_fullname = _package_name;
if (!_package_version.empty()) {
_package_fullname += string(".") + _package_version;
}
if (!_package_platform.empty()) {
_package_fullname += string(".") + _package_platform;
}
}
////////////////////////////////////////////////////////////////////
// Function: P3DPackage::Download::Constructor
// Access: Public

View File

@ -241,6 +241,7 @@ private:
bool is_extractable(FileSpec &file, const string &filename) const;
bool instance_terminating(P3DInstance *instance);
void set_fullname();
public:
class RequiredPackage {
@ -264,6 +265,7 @@ private:
string _package_name;
string _package_version;
string _package_platform;
bool _per_platform;
int _patch_version;
string _alt_host;
bool _package_solo;

View File

@ -1252,9 +1252,12 @@ set_instance_info(P3DCInstance *inst, TiXmlElement *xinstance) {
Py_INCREF(main);
}
int respect_per_platform = 0;
xinstance->Attribute("respect_per_platform", &respect_per_platform);
PyObject *result = PyObject_CallMethod
(_runner, (char *)"setInstanceInfo", (char *)"sssiO", root_dir,
log_directory, super_mirror, verify_contents, main);
(_runner, (char *)"setInstanceInfo", (char *)"sssiOi", root_dir,
log_directory, super_mirror, verify_contents, main, respect_per_platform);
Py_DECREF(main);
if (result == NULL) {

View File

@ -30,3 +30,4 @@
#include "p3dWinSplashWindow.cxx"
#include "p3dX11SplashWindow.cxx"
#include "p3dWindowParams.cxx"
#include "xml_helpers.cxx"

View File

@ -0,0 +1,43 @@
// Filename: xml_helpers.cxx
// Created by: drose (28Sep12)
//
////////////////////////////////////////////////////////////////////
//
// 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."
//
////////////////////////////////////////////////////////////////////
#include "p3d_plugin_common.h"
#include "xml_helpers.h"
////////////////////////////////////////////////////////////////////
// Function: parse_bool_attrib
// Description: Examines the indicated attrib from the XML attrib and
// returns its true or false value. Returns
// default_value if the attrib is not present or is
// empty.
////////////////////////////////////////////////////////////////////
bool
parse_bool_attrib(TiXmlElement *xelem, const string &attrib,
bool default_value) {
const char *value = xelem->Attribute(attrib.c_str());
if (value == NULL || *value == '\0') {
return default_value;
}
char *endptr;
int result = strtol(value, &endptr, 10);
if (*endptr == '\0') {
// A valid integer.
return (result != 0);
}
// An invalid integer.
return default_value;
}

24
direct/src/plugin/xml_helpers.h Executable file
View File

@ -0,0 +1,24 @@
// Filename: xml_helpers.h
// Created by: drose (28Sep12)
//
////////////////////////////////////////////////////////////////////
//
// 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."
//
////////////////////////////////////////////////////////////////////
#ifndef XML_HELPERS_H
#define XML_HELPERS_H
#include "get_tinyxml.h"
bool parse_bool_attrib(TiXmlElement *xelem, const string &attrib,
bool default_value);
#endif