395 lines
14 KiB
C++
395 lines
14 KiB
C++
// Filename: loader.cxx
|
|
// Created by: mike (09Jan97)
|
|
//
|
|
////////////////////////////////////////////////////////////////////
|
|
//
|
|
// 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 "loader.h"
|
|
#include "loaderFileType.h"
|
|
#include "loaderFileTypeRegistry.h"
|
|
#include "config_pgraph.h"
|
|
#include "modelPool.h"
|
|
#include "modelLoadRequest.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;
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: Loader::Constructor
|
|
// Access: Published
|
|
// Description:
|
|
////////////////////////////////////////////////////////////////////
|
|
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);
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: Loader::make_async_request
|
|
// Access: Published
|
|
// Description: 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);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: Loader::load_bam_stream
|
|
// Access: Published
|
|
// Description: 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();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: Loader::output
|
|
// Access: Published, Virtual
|
|
// Description:
|
|
////////////////////////////////////////////////////////////////////
|
|
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)";
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: Loader::load_file
|
|
// Access: Private
|
|
// Description: 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 pz_file = false;
|
|
#ifdef HAVE_ZLIB
|
|
if (extension == "pz") {
|
|
pz_file = 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 (pz_file && !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;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: Loader::try_load_file
|
|
// Access: Private
|
|
// Description: 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 cache_only = (options.get_flags() & LoaderOptions::LF_cache_only) != 0;
|
|
|
|
if (requested_type->get_allow_ram_cache(options)) {
|
|
// If we're allowing a RAM cache, use the ModelPool to load the
|
|
// file.
|
|
if (!cache_only || ModelPool::has_model(pathname)) {
|
|
PT(PandaNode) node = ModelPool::load_model(pathname, options);
|
|
if (node != (PandaNode *)NULL &&
|
|
(options.get_flags() & LoaderOptions::LF_allow_instance) == 0) {
|
|
// 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;
|
|
|
|
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());
|
|
}
|
|
return result;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!cache_only) {
|
|
PT(PandaNode) result = requested_type->load_file(pathname, options, record);
|
|
if (result != (PandaNode *)NULL){
|
|
if (record != (BamCacheRecord *)NULL) {
|
|
record->set_data(result, result);
|
|
cache->store(record);
|
|
}
|
|
|
|
if (premunge_data) {
|
|
SceneGraphReducer sgr;
|
|
sgr.premunge(result, RenderState::make_empty());
|
|
}
|
|
return result;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: Loader::load_file_types
|
|
// Access: Private, Static
|
|
// Description: 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 (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;
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: Loader::make_global_ptr
|
|
// Access: Private, Static
|
|
// Description: 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");
|
|
}
|
|
|