519 lines
17 KiB
C++
519 lines
17 KiB
C++
/**
|
|
* 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 loader.cxx
|
|
* @author mike
|
|
* @date 1997-01-09
|
|
*/
|
|
|
|
#include "loader.h"
|
|
#include "loaderFileType.h"
|
|
#include "loaderFileTypeRegistry.h"
|
|
#include "config_pgraph.h"
|
|
#include "modelPool.h"
|
|
#include "modelLoadRequest.h"
|
|
#include "modelSaveRequest.h"
|
|
#include "config_express.h"
|
|
#include "config_util.h"
|
|
#include "virtualFileSystem.h"
|
|
#include "filename.h"
|
|
#include "load_dso.h"
|
|
#include "string_utils.h"
|
|
#include "bamCache.h"
|
|
#include "bamCacheRecord.h"
|
|
#include "sceneGraphReducer.h"
|
|
#include "renderState.h"
|
|
#include "bamFile.h"
|
|
#include "configVariableInt.h"
|
|
#include "configVariableEnum.h"
|
|
|
|
bool Loader::_file_types_loaded = false;
|
|
PT(Loader) Loader::_global_ptr;
|
|
TypeHandle Loader::_type_handle;
|
|
|
|
/**
|
|
*
|
|
*/
|
|
Loader::
|
|
Loader(const string &name) :
|
|
Namable(name)
|
|
{
|
|
_task_manager = AsyncTaskManager::get_global_ptr();
|
|
_task_chain = name;
|
|
|
|
if (_task_manager->find_task_chain(_task_chain) == NULL) {
|
|
PT(AsyncTaskChain) chain = _task_manager->make_task_chain(_task_chain);
|
|
|
|
ConfigVariableInt loader_num_threads
|
|
("loader-num-threads", 1,
|
|
PRC_DESC("The number of threads that will be started by the Loader class "
|
|
"to load models asynchronously. These threads will only be "
|
|
"started if the asynchronous interface is used, and if threading "
|
|
"support is compiled into Panda. The default is one thread, "
|
|
"which allows models to be loaded one at a time in a single "
|
|
"asychronous thread. You can set this higher, particularly if "
|
|
"you have many CPU's available, to allow loading multiple models "
|
|
"simultaneously."));
|
|
chain->set_num_threads(loader_num_threads);
|
|
|
|
ConfigVariableEnum<ThreadPriority> loader_thread_priority
|
|
("loader-thread-priority", TP_low,
|
|
PRC_DESC("The default thread priority to assign to the threads created "
|
|
"for asynchronous loading. The default is 'low'; you may "
|
|
"also specify 'normal', 'high', or 'urgent'."));
|
|
chain->set_thread_priority(loader_thread_priority);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns a new AsyncTask object suitable for adding to load_async() to start
|
|
* an asynchronous model load.
|
|
*/
|
|
PT(AsyncTask) Loader::
|
|
make_async_request(const Filename &filename, const LoaderOptions &options) {
|
|
return new ModelLoadRequest(string("model:")+filename.get_basename(),
|
|
filename, options, this);
|
|
}
|
|
|
|
/**
|
|
* Returns a new AsyncTask object suitable for adding to save_async() to start
|
|
* an asynchronous model save.
|
|
*/
|
|
PT(AsyncTask) Loader::
|
|
make_async_save_request(const Filename &filename, const LoaderOptions &options,
|
|
PandaNode *node) {
|
|
return new ModelSaveRequest(string("model_save:")+filename.get_basename(),
|
|
filename, options, node, this);
|
|
}
|
|
|
|
/**
|
|
* Attempts to read a bam file from the indicated stream and return the scene
|
|
* graph defined there.
|
|
*/
|
|
PT(PandaNode) Loader::
|
|
load_bam_stream(istream &in) {
|
|
BamFile bam_file;
|
|
if (!bam_file.open_read(in)) {
|
|
return NULL;
|
|
}
|
|
|
|
return bam_file.read_node();
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
void Loader::
|
|
output(ostream &out) const {
|
|
out << get_type() << " " << get_name();
|
|
|
|
int num_tasks = _task_manager->make_task_chain(_task_chain)->get_num_tasks();
|
|
if (num_tasks != 0) {
|
|
out << " (" << num_tasks << " models pending)";
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Loads a single scene graph file, if possible. Returns the Node that is the
|
|
* root of the file, or NULL if the file cannot be loaded.
|
|
*
|
|
* If search is true, the file is searched for along the model path;
|
|
* otherwise, only the exact filename is loaded.
|
|
*/
|
|
PT(PandaNode) Loader::
|
|
load_file(const Filename &filename, const LoaderOptions &options) const {
|
|
Filename this_filename(filename);
|
|
LoaderOptions this_options(options);
|
|
|
|
bool report_errors = (this_options.get_flags() & LoaderOptions::LF_report_errors) != 0;
|
|
|
|
string extension = this_filename.get_extension();
|
|
if (extension.empty()) {
|
|
// If the filename has no filename extension, append the default extension
|
|
// specified in the Config file.
|
|
this_filename = this_filename.get_fullpath() + default_model_extension.get_value();
|
|
extension = this_filename.get_extension();
|
|
}
|
|
|
|
bool compressed = false;
|
|
#ifdef HAVE_ZLIB
|
|
if (extension == "pz" || extension == "gz") {
|
|
compressed = true;
|
|
extension = Filename(this_filename.get_basename_wo_extension()).get_extension();
|
|
}
|
|
#endif // HAVE_ZLIB
|
|
|
|
if (extension.empty()) {
|
|
if (report_errors) {
|
|
loader_cat.error()
|
|
<< "Cannot load " << this_filename
|
|
<< " without filename extension. Loading of model filenames with an "
|
|
"implicit extension is deprecated in Panda3D. Please "
|
|
"correct the filename reference. If necessary, you may put the "
|
|
"line \"default-model-extension .bam\" or \"default-model-extension .egg\" "
|
|
"in your Config.prc to globally assume a particular model "
|
|
"filename extension.\n";
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
LoaderFileTypeRegistry *reg = LoaderFileTypeRegistry::get_global_ptr();
|
|
LoaderFileType *requested_type =
|
|
reg->get_type_from_extension(extension);
|
|
if (requested_type == (LoaderFileType *)NULL) {
|
|
if (report_errors) {
|
|
loader_cat.error()
|
|
<< "Extension of file " << this_filename
|
|
<< " is unrecognized; cannot load.\n";
|
|
loader_cat.error(false)
|
|
<< "Currently known scene file types are:\n";
|
|
reg->write(loader_cat.error(false), 2);
|
|
}
|
|
return NULL;
|
|
} else if (!requested_type->supports_load()) {
|
|
if (report_errors) {
|
|
loader_cat.error()
|
|
<< requested_type->get_name() << " file type (."
|
|
<< extension << ") does not support loading.\n";
|
|
}
|
|
return NULL;
|
|
} else if (compressed && !requested_type->supports_compressed()) {
|
|
if (report_errors) {
|
|
loader_cat.error()
|
|
<< requested_type->get_name() << " file type (."
|
|
<< extension << ") does not support in-line compression.\n";
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
bool search = (this_options.get_flags() & LoaderOptions::LF_search) != 0;
|
|
if (!filename.is_local()) {
|
|
// If we have a global filename, we don't search the model path.
|
|
search = false;
|
|
}
|
|
|
|
// Now that we've decided whether to search for the file, don't try to
|
|
// search again.
|
|
this_options.set_flags(this_options.get_flags() & ~LoaderOptions::LF_search);
|
|
|
|
VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr();
|
|
|
|
if (search) {
|
|
// Look for the file along the model path.
|
|
const ConfigVariableSearchPath &model_path = get_model_path();
|
|
int num_dirs = model_path.get_num_directories();
|
|
for (int i = 0; i < num_dirs; ++i) {
|
|
Filename pathname(model_path.get_directory(i), this_filename);
|
|
PT(PandaNode) result = try_load_file(pathname, this_options,
|
|
requested_type);
|
|
if (result != (PandaNode *)NULL) {
|
|
return result;
|
|
}
|
|
}
|
|
|
|
if (report_errors) {
|
|
bool any_exist = false;
|
|
for (int i = 0; i < num_dirs; ++i) {
|
|
Filename pathname(model_path.get_directory(i), this_filename);
|
|
if (vfs->exists(pathname)) {
|
|
any_exist = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (any_exist) {
|
|
loader_cat.error()
|
|
<< "Couldn't load file " << this_filename
|
|
<< ": all matching files on model path invalid "
|
|
<< "(the model path is currently: \"" << get_model_path() << "\")\n";
|
|
} else {
|
|
loader_cat.error()
|
|
<< "Couldn't load file " << this_filename
|
|
<< ": not found on model path "
|
|
<< "(currently: \"" << get_model_path() << "\")\n";
|
|
}
|
|
}
|
|
|
|
} else {
|
|
// Look for the file only where it is.
|
|
VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr();
|
|
PT(PandaNode) result = try_load_file(this_filename, this_options, requested_type);
|
|
if (result != (PandaNode *)NULL) {
|
|
return result;
|
|
}
|
|
if (report_errors) {
|
|
if (vfs->exists(this_filename)) {
|
|
loader_cat.error()
|
|
<< "Couldn't load file " << this_filename << ": invalid.\n";
|
|
} else {
|
|
loader_cat.error()
|
|
<< "Couldn't load file " << this_filename << ": does not exist.\n";
|
|
}
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* The implementatin of load_file(), this tries a single possible file without
|
|
* searching further along the path.
|
|
*/
|
|
PT(PandaNode) Loader::
|
|
try_load_file(const Filename &pathname, const LoaderOptions &options,
|
|
LoaderFileType *requested_type) const {
|
|
BamCache *cache = BamCache::get_global_ptr();
|
|
|
|
bool allow_ram_cache = requested_type->get_allow_ram_cache(options);
|
|
|
|
if (allow_ram_cache) {
|
|
// If we're allowing a RAM cache, use the ModelPool to load the file.
|
|
PT(PandaNode) node = ModelPool::get_model(pathname, true);
|
|
if (node != (PandaNode *)NULL) {
|
|
if ((options.get_flags() & LoaderOptions::LF_allow_instance) == 0) {
|
|
if (loader_cat.is_debug()) {
|
|
loader_cat.debug()
|
|
<< "Model " << pathname << " found in ModelPool.\n";
|
|
}
|
|
// But return a deep copy of the shared model.
|
|
node = node->copy_subgraph();
|
|
}
|
|
return node;
|
|
}
|
|
}
|
|
|
|
bool report_errors = ((options.get_flags() & LoaderOptions::LF_report_errors) != 0 || loader_cat.is_debug());
|
|
|
|
PT(BamCacheRecord) record;
|
|
if (cache->get_cache_models() && requested_type->get_allow_disk_cache(options)) {
|
|
// See if the model can be found in the on-disk cache, if it is active.
|
|
record = cache->lookup(pathname, "bam");
|
|
if (record != (BamCacheRecord *)NULL) {
|
|
if (record->has_data()) {
|
|
if (report_errors) {
|
|
loader_cat.info()
|
|
<< "Model " << pathname << " found in disk cache.\n";
|
|
}
|
|
PT(PandaNode) result = DCAST(PandaNode, record->get_data());
|
|
|
|
if (premunge_data) {
|
|
SceneGraphReducer sgr;
|
|
sgr.premunge(result, RenderState::make_empty());
|
|
}
|
|
|
|
if (result->is_of_type(ModelRoot::get_class_type())) {
|
|
ModelRoot *model_root = DCAST(ModelRoot, result.p());
|
|
model_root->set_fullpath(pathname);
|
|
model_root->set_timestamp(record->get_source_timestamp());
|
|
|
|
if (allow_ram_cache) {
|
|
// Store the loaded model in the RAM cache, and make sure we
|
|
// return a copy so that this node can be modified independently
|
|
// from the RAM cached version.
|
|
ModelPool::add_model(pathname, model_root);
|
|
if ((options.get_flags() & LoaderOptions::LF_allow_instance) == 0) {
|
|
return model_root->copy_subgraph();
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
}
|
|
|
|
if (loader_cat.is_debug()) {
|
|
loader_cat.debug()
|
|
<< "Model " << pathname << " not found in cache.\n";
|
|
}
|
|
}
|
|
|
|
bool cache_only = (options.get_flags() & LoaderOptions::LF_cache_only) != 0;
|
|
if (!cache_only) {
|
|
// Load the model from disk.
|
|
PT(PandaNode) result = requested_type->load_file(pathname, options, record);
|
|
if (result != (PandaNode *)NULL) {
|
|
if (record != (BamCacheRecord *)NULL) {
|
|
// Store the loaded model in the model cache.
|
|
record->set_data(result);
|
|
cache->store(record);
|
|
}
|
|
|
|
if (premunge_data) {
|
|
SceneGraphReducer sgr;
|
|
sgr.premunge(result, RenderState::make_empty());
|
|
}
|
|
|
|
if (allow_ram_cache && result->is_of_type(ModelRoot::get_class_type())) {
|
|
// Store the loaded model in the RAM cache, and make sure we return a
|
|
// copy so that this node can be modified independently from the RAM
|
|
// cached version.
|
|
ModelPool::add_model(pathname, DCAST(ModelRoot, result.p()));
|
|
if ((options.get_flags() & LoaderOptions::LF_allow_instance) == 0) {
|
|
result = result->copy_subgraph();
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* Saves a scene graph to a single file, if possible. The file type written
|
|
* is implicit in the filename extension.
|
|
*/
|
|
bool Loader::
|
|
save_file(const Filename &filename, const LoaderOptions &options,
|
|
PandaNode *node) const {
|
|
Filename this_filename(filename);
|
|
LoaderOptions this_options(options);
|
|
|
|
bool report_errors = (this_options.get_flags() & LoaderOptions::LF_report_errors) != 0;
|
|
|
|
string extension = this_filename.get_extension();
|
|
if (extension.empty()) {
|
|
// If the filename has no filename extension, append the default extension
|
|
// specified in the Config file.
|
|
this_filename = this_filename.get_fullpath() + default_model_extension.get_value();
|
|
extension = this_filename.get_extension();
|
|
}
|
|
|
|
bool compressed = false;
|
|
#ifdef HAVE_ZLIB
|
|
if (extension == "pz" || extension == "gz") {
|
|
compressed = true;
|
|
extension = Filename(this_filename.get_basename_wo_extension()).get_extension();
|
|
}
|
|
#endif // HAVE_ZLIB
|
|
|
|
if (extension.empty()) {
|
|
if (report_errors) {
|
|
loader_cat.error()
|
|
<< "Cannot save " << this_filename
|
|
<< " without filename extension.\n";
|
|
}
|
|
return false;
|
|
}
|
|
|
|
LoaderFileTypeRegistry *reg = LoaderFileTypeRegistry::get_global_ptr();
|
|
LoaderFileType *requested_type =
|
|
reg->get_type_from_extension(extension);
|
|
|
|
if (requested_type == (LoaderFileType *)NULL) {
|
|
if (report_errors) {
|
|
loader_cat.error()
|
|
<< "Extension of file " << this_filename
|
|
<< " is unrecognized; cannot save.\n";
|
|
loader_cat.error(false)
|
|
<< "Currently known scene file types are:\n";
|
|
reg->write(loader_cat.error(false), 2);
|
|
}
|
|
return false;
|
|
|
|
} else if (!requested_type->supports_save()) {
|
|
if (report_errors) {
|
|
loader_cat.error()
|
|
<< requested_type->get_name() << " file type (."
|
|
<< extension << ") does not support saving.\n";
|
|
}
|
|
return false;
|
|
|
|
} else if (compressed && !requested_type->supports_compressed()) {
|
|
if (report_errors) {
|
|
loader_cat.error()
|
|
<< requested_type->get_name() << " file type (."
|
|
<< extension << ") does not support in-line compression.\n";
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool result = try_save_file(this_filename, this_options, node, requested_type);
|
|
if (!result) {
|
|
if (report_errors) {
|
|
loader_cat.error()
|
|
<< "Couldn't save file " << this_filename << ".\n";
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* The implementation of save_file(), this tries to write a specific file
|
|
* type.
|
|
*/
|
|
bool Loader::
|
|
try_save_file(const Filename &pathname, const LoaderOptions &options,
|
|
PandaNode *node, LoaderFileType *requested_type) const {
|
|
return requested_type->save_file(pathname, options, node);
|
|
}
|
|
|
|
/**
|
|
* Loads up all of the dynamic libraries named in a load-file-type Configure
|
|
* variable. Presumably this will make the various file types available for
|
|
* runtime loading.
|
|
*/
|
|
void Loader::
|
|
load_file_types() {
|
|
if (!_file_types_loaded) {
|
|
int num_unique_values = load_file_type.get_num_unique_values();
|
|
|
|
for (int i = 0; i < num_unique_values; i++) {
|
|
string param = load_file_type.get_unique_value(i);
|
|
|
|
vector_string words;
|
|
extract_words(param, words);
|
|
|
|
if (words.size() == 1) {
|
|
// Exactly one word: load the named library immediately.
|
|
string name = words[0];
|
|
Filename dlname = Filename::dso_filename("lib" + name + ".so");
|
|
loader_cat.info()
|
|
<< "loading file type module: " << name << endl;
|
|
void *tmp = load_dso(get_plugin_path().get_value(), dlname);
|
|
if (tmp == (void *)NULL) {
|
|
loader_cat.warning()
|
|
<< "Unable to load " << dlname.to_os_specific()
|
|
<< ": " << load_dso_error() << endl;
|
|
} else if (loader_cat.is_debug()) {
|
|
loader_cat.debug()
|
|
<< "done loading file type module: " << name << endl;
|
|
}
|
|
|
|
} else if (words.size() > 1) {
|
|
// Multiple words: the first n words are filename extensions, and the
|
|
// last word is the name of the library to load should any of those
|
|
// filename extensions be encountered.
|
|
LoaderFileTypeRegistry *registry = LoaderFileTypeRegistry::get_global_ptr();
|
|
size_t num_extensions = words.size() - 1;
|
|
string library_name = words[num_extensions];
|
|
|
|
for (size_t i = 0; i < num_extensions; i++) {
|
|
string extension = words[i];
|
|
if (extension[0] == '.') {
|
|
extension = extension.substr(1);
|
|
}
|
|
|
|
registry->register_deferred_type(extension, library_name);
|
|
}
|
|
}
|
|
}
|
|
|
|
_file_types_loaded = true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Called once per application to create the global loader object.
|
|
*/
|
|
void Loader::
|
|
make_global_ptr() {
|
|
nassertv(_global_ptr == (Loader *)NULL);
|
|
|
|
_global_ptr = new Loader("loader");
|
|
}
|