Panda3D
Loading...
Searching...
No Matches
loader.cxx
Go to the documentation of this file.
1/**
2 * PANDA 3D SOFTWARE
3 * Copyright (c) Carnegie Mellon University. All rights reserved.
4 *
5 * All use of this software is subject to the terms of the revised BSD
6 * license. You should have received a copy of this license along
7 * with this source code in a file named "LICENSE."
8 *
9 * @file loader.cxx
10 * @author mike
11 * @date 1997-01-09
12 */
13
14#include "loader.h"
15#include "loaderFileType.h"
17#include "config_pgraph.h"
18#include "modelPool.h"
19#include "modelLoadRequest.h"
20#include "modelSaveRequest.h"
21#include "config_express.h"
22#include "config_putil.h"
23#include "virtualFileSystem.h"
24#include "filename.h"
25#include "load_dso.h"
26#include "string_utils.h"
27#include "bamCache.h"
28#include "bamCacheRecord.h"
29#include "sceneGraphReducer.h"
30#include "renderState.h"
31#include "bamFile.h"
32#include "configVariableInt.h"
33#include "configVariableEnum.h"
34
35using std::string;
36
37bool Loader::_file_types_loaded = false;
38PT(Loader) Loader::_global_ptr;
39TypeHandle Loader::_type_handle;
40
41/**
42 *
43 */
44Loader::
45Loader(const string &name) :
46 Namable(name)
47{
48 _task_manager = AsyncTaskManager::get_global_ptr();
49 _task_chain = name;
50
51 if (_task_manager->find_task_chain(_task_chain) == nullptr) {
52 PT(AsyncTaskChain) chain = _task_manager->make_task_chain(_task_chain);
53
54 ConfigVariableInt loader_num_threads
55 ("loader-num-threads", 1,
56 PRC_DESC("The number of threads that will be started by the Loader class "
57 "to load models asynchronously. These threads will only be "
58 "started if the asynchronous interface is used, and if threading "
59 "support is compiled into Panda. The default is one thread, "
60 "which allows models to be loaded one at a time in a single "
61 "asychronous thread. You can set this higher, particularly if "
62 "you have many CPU's available, to allow loading multiple models "
63 "simultaneously."));
64 chain->set_num_threads(loader_num_threads);
65
66 ConfigVariableEnum<ThreadPriority> loader_thread_priority
67 ("loader-thread-priority", TP_low,
68 PRC_DESC("The default thread priority to assign to the threads created "
69 "for asynchronous loading. The default is 'low'; you may "
70 "also specify 'normal', 'high', or 'urgent'."));
71 chain->set_thread_priority(loader_thread_priority);
72 }
73}
74
75/**
76 * Returns a new AsyncTask object suitable for adding to load_async() to start
77 * an asynchronous model load.
78 */
79PT(AsyncTask) Loader::
80make_async_request(const Filename &filename, const LoaderOptions &options) {
81 return new ModelLoadRequest(string("model:")+filename.get_basename(),
82 filename, options, this);
83}
84
85/**
86 * Returns a new AsyncTask object suitable for adding to save_async() to start
87 * an asynchronous model save.
88 */
89PT(AsyncTask) Loader::
90make_async_save_request(const Filename &filename, const LoaderOptions &options,
91 PandaNode *node) {
92 return new ModelSaveRequest(string("model_save:")+filename.get_basename(),
93 filename, options, node, this);
94}
95
96/**
97 * Attempts to read a bam file from the indicated stream and return the scene
98 * graph defined there.
99 */
100PT(PandaNode) Loader::
101load_bam_stream(std::istream &in) {
102 BamFile bam_file;
103 if (!bam_file.open_read(in)) {
104 return nullptr;
105 }
106
107 return bam_file.read_node();
108}
109
110/**
111 *
112 */
113void Loader::
114output(std::ostream &out) const {
115 out << get_type() << " " << get_name();
116
117 int num_tasks = _task_manager->make_task_chain(_task_chain)->get_num_tasks();
118 if (num_tasks != 0) {
119 out << " (" << num_tasks << " models pending)";
120 }
121}
122
123/**
124 * Loads a single scene graph file, if possible. Returns the Node that is the
125 * root of the file, or NULL if the file cannot be loaded.
126 *
127 * If search is true, the file is searched for along the model path;
128 * otherwise, only the exact filename is loaded.
129 */
130PT(PandaNode) Loader::
131load_file(const Filename &filename, const LoaderOptions &options) const {
132 Filename this_filename(filename);
133 LoaderOptions this_options(options);
134
135 bool report_errors = (this_options.get_flags() & LoaderOptions::LF_report_errors) != 0;
136
137 string extension = this_filename.get_extension();
138 if (extension.empty()) {
139 // If the filename has no filename extension, append the default extension
140 // specified in the Config file.
141 this_filename = this_filename.get_fullpath() + default_model_extension.get_value();
142 extension = this_filename.get_extension();
143 }
144
145 bool compressed = false;
146#ifdef HAVE_ZLIB
147 if (extension == "pz" || extension == "gz") {
148 compressed = true;
149 extension = Filename(this_filename.get_basename_wo_extension()).get_extension();
150 }
151#endif // HAVE_ZLIB
152
153 if (extension.empty()) {
154 if (report_errors) {
155 loader_cat.error()
156 << "Cannot load " << this_filename
157 << " without filename extension. Loading of model filenames with an "
158 "implicit extension is deprecated in Panda3D. Please "
159 "correct the filename reference. If necessary, you may put the "
160 "line \"default-model-extension .bam\" or \"default-model-extension .egg\" "
161 "in your Config.prc to globally assume a particular model "
162 "filename extension.\n";
163 }
164 return nullptr;
165 }
166
168 LoaderFileType *requested_type = reg->get_type_from_extension(extension);
169 // requested_type may be nullptr at this point, because there's still a
170 // chance we can load it via the model-cache or by adding .bam to the end.
171 if (requested_type != nullptr) {
172 if (!requested_type->supports_load()) {
173 if (report_errors) {
174 loader_cat.error()
175 << requested_type->get_name() << " file type (."
176 << extension << ") does not support loading.\n";
177 }
178 return nullptr;
179 } else if (compressed && !requested_type->supports_compressed()) {
180 if (report_errors) {
181 loader_cat.error()
182 << requested_type->get_name() << " file type (."
183 << extension << ") does not support in-line compression.\n";
184 }
185 return nullptr;
186 }
187 }
188
189 bool search = (this_options.get_flags() & LoaderOptions::LF_search) != 0;
190 if (!filename.is_local()) {
191 // If we have a global filename, we don't search the model path.
192 search = false;
193 }
194
195 // Now that we've decided whether to search for the file, don't try to
196 // search again.
197 this_options.set_flags(this_options.get_flags() & ~LoaderOptions::LF_search);
198
200
201 if (search) {
202 // Look for the file along the model path.
203 DSearchPath model_path(get_model_path());
204 int num_dirs = model_path.get_num_directories();
205 for (int i = 0; i < num_dirs; ++i) {
206 Filename pathname(model_path.get_directory(i), this_filename);
207 PT(PandaNode) result = try_load_file(pathname, this_options,
208 requested_type);
209 if (result != nullptr) {
210 return result;
211 }
212 }
213
214 if (report_errors) {
215 bool any_exist = false;
216 for (int i = 0; i < num_dirs; ++i) {
217 Filename pathname(model_path.get_directory(i), this_filename);
218 if (vfs->exists(pathname)) {
219 any_exist = true;
220 break;
221 }
222 }
223
224 if (any_exist) {
225 if (requested_type == nullptr) {
226 loader_cat.error()
227 << "Extension of file " << this_filename
228 << " is unrecognized; cannot load.\n";
229 loader_cat.error(false)
230 << "Currently known scene file types are:\n";
231 reg->write(loader_cat.error(false), 2);
232 }
233 loader_cat.error()
234 << "Couldn't load file " << this_filename
235 << ": all matching files on model path invalid "
236 << "(the model path is currently: \"" << get_model_path() << "\")\n";
237 } else {
238 loader_cat.error()
239 << "Couldn't load file " << this_filename
240 << ": not found on model path "
241 << "(currently: \"" << get_model_path() << "\")\n";
242 }
243 }
244
245 } else {
246 // Look for the file only where it is.
248 PT(PandaNode) result = try_load_file(this_filename, this_options, requested_type);
249 if (result != nullptr) {
250 return result;
251 }
252 if (report_errors) {
253 if (vfs->exists(this_filename)) {
254 if (requested_type == nullptr) {
255 loader_cat.error()
256 << "Extension of file " << this_filename
257 << " is unrecognized; cannot load.\n";
258 loader_cat.error(false)
259 << "Currently known scene file types are:\n";
260 reg->write(loader_cat.error(false), 2);
261 }
262 loader_cat.error()
263 << "Couldn't load file " << this_filename << ": invalid.\n";
264 } else {
265 loader_cat.error()
266 << "Couldn't load file " << this_filename << ": does not exist.\n";
267 }
268 }
269 }
270 return nullptr;
271}
272
273/**
274 * The implementatin of load_file(), this tries a single possible file without
275 * searching further along the path.
276 */
277PT(PandaNode) Loader::
278try_load_file(const Filename &pathname, const LoaderOptions &options,
279 LoaderFileType *requested_type) const {
281
282 bool allow_ram_cache =
283 ((options.get_flags() & LoaderOptions::LF_no_ram_cache) == 0);
284
285 if (allow_ram_cache) {
286 // If we're allowing a RAM cache, use the ModelPool to load the file.
287 PT(PandaNode) node = ModelPool::get_model(pathname, true);
288 if (node != nullptr) {
289 if ((options.get_flags() & LoaderOptions::LF_allow_instance) == 0) {
290 if (loader_cat.is_debug()) {
291 loader_cat.debug()
292 << "Model " << pathname << " found in ModelPool.\n";
293 }
294 // But return a deep copy of the shared model.
295 node = NodePath(node).copy_to(NodePath()).node();
296 }
297 return node;
298 }
299 }
300
301 bool report_errors = ((options.get_flags() & LoaderOptions::LF_report_errors) != 0 || loader_cat.is_debug());
302
303 PT(BamCacheRecord) record;
304 if (cache->get_cache_models() &&
305 (options.get_flags() & LoaderOptions::LF_no_disk_cache) == 0) {
306 // See if the model can be found in the on-disk cache, if it is active.
307 record = cache->lookup(pathname, "bam");
308 if (record != nullptr) {
309 if (record->has_data()) {
310 if (report_errors) {
311 loader_cat.info()
312 << "Model " << pathname << " found in disk cache.\n";
313 }
314 PT(PandaNode) result = DCAST(PandaNode, record->get_data());
315
316 if (premunge_data) {
318 sgr.premunge(result, RenderState::make_empty());
319 }
320
321 if (result->is_of_type(ModelRoot::get_class_type())) {
322 ModelRoot *model_root = DCAST(ModelRoot, result.p());
323 model_root->set_fullpath(pathname);
324 model_root->set_timestamp(record->get_source_timestamp());
325
326 if (allow_ram_cache) {
327 // Store the loaded model in the RAM cache, and make sure we
328 // return a copy so that this node can be modified independently
329 // from the RAM cached version.
330 ModelPool::add_model(pathname, model_root);
331 if ((options.get_flags() & LoaderOptions::LF_allow_instance) == 0) {
332 return NodePath(model_root).copy_to(NodePath()).node();
333 }
334 }
335 }
336 return result;
337 }
338 }
339
340 if (loader_cat.is_debug()) {
341 loader_cat.debug()
342 << "Model " << pathname << " not found in cache.\n";
343 }
344 }
345
346 if (options.get_flags() & LoaderOptions::LF_cache_only) {
347 // We're not allowed to read from disk.
348 return nullptr;
349 }
350
351 // Load the model from disk.
352 PT(PandaNode) result;
353 if (requested_type != nullptr) {
354 result = requested_type->load_file(pathname, options, record);
355 }
356 if (result != nullptr) {
357 if (record != nullptr) {
358 // Store the loaded model in the model cache.
359 record->set_data(result);
360 cache->store(record);
361 }
362 } else {
363 // Do we have the same filename, but with .bam appended to the end?
364 string extension = pathname.get_extension();
365 Filename pathname_bam = pathname;
366 if (extension == "pz" || extension == "gz") {
367 // Strip .pz/.gz, so that model.egg.pz -> model.egg.bam
368 extension = pathname_bam.get_extension();
369 pathname_bam = pathname_bam.get_fullpath_wo_extension();
370 }
371 if (extension == "bam") {
372 // Don't try to load .bam.bam files, that is just silly.
373 return nullptr;
374 }
375 pathname_bam += ".bam";
376
377 BamFile bam_file;
378 if (!bam_file.open_read(pathname_bam, report_errors)) {
379 return nullptr;
380 }
381
382 bam_file.get_reader()->set_loader_options(options);
383 result = bam_file.read_node(report_errors);
384
385 nassertr_always(result != nullptr && result->is_of_type(ModelRoot::get_class_type()), nullptr);
386 // We don't bother with the model-cache here, since this .bam file is
387 // already effectively a cached version of the original model.
388 }
389
390 if (premunge_data) {
392 sgr.premunge(result, RenderState::make_empty());
393 }
394
395 if (allow_ram_cache && result->is_of_type(ModelRoot::get_class_type())) {
396 // Store the loaded model in the RAM cache, and make sure we return a
397 // copy so that this node can be modified independently from the RAM
398 // cached version.
399 ModelPool::add_model(pathname, DCAST(ModelRoot, result.p()));
400 if ((options.get_flags() & LoaderOptions::LF_allow_instance) == 0) {
401 result = NodePath(result).copy_to(NodePath()).node();
402 }
403 }
404
405 return result;
406}
407
408/**
409 * Saves a scene graph to a single file, if possible. The file type written
410 * is implicit in the filename extension.
411 */
412bool Loader::
413save_file(const Filename &filename, const LoaderOptions &options,
414 PandaNode *node) const {
415 Filename this_filename(filename);
416 LoaderOptions this_options(options);
417
418 bool report_errors = (this_options.get_flags() & LoaderOptions::LF_report_errors) != 0;
419
420 string extension = this_filename.get_extension();
421 if (extension.empty()) {
422 // If the filename has no filename extension, append the default extension
423 // specified in the Config file.
424 this_filename = this_filename.get_fullpath() + default_model_extension.get_value();
425 extension = this_filename.get_extension();
426 }
427
428 bool compressed = false;
429#ifdef HAVE_ZLIB
430 if (extension == "pz" || extension == "gz") {
431 compressed = true;
432 extension = Filename(this_filename.get_basename_wo_extension()).get_extension();
433 }
434#endif // HAVE_ZLIB
435
436 if (extension.empty()) {
437 if (report_errors) {
438 loader_cat.error()
439 << "Cannot save " << this_filename
440 << " without filename extension.\n";
441 }
442 return false;
443 }
444
446 LoaderFileType *requested_type =
447 reg->get_type_from_extension(extension);
448
449 if (requested_type == nullptr) {
450 if (report_errors) {
451 loader_cat.error()
452 << "Extension of file " << this_filename
453 << " is unrecognized; cannot save.\n";
454 loader_cat.error(false)
455 << "Currently known scene file types are:\n";
456 reg->write(loader_cat.error(false), 2);
457 }
458 return false;
459
460 } else if (!requested_type->supports_save()) {
461 if (report_errors) {
462 loader_cat.error()
463 << requested_type->get_name() << " file type (."
464 << extension << ") does not support saving.\n";
465 }
466 return false;
467
468 } else if (compressed && !requested_type->supports_compressed()) {
469 if (report_errors) {
470 loader_cat.error()
471 << requested_type->get_name() << " file type (."
472 << extension << ") does not support in-line compression.\n";
473 }
474 return false;
475 }
476
477 bool result = try_save_file(this_filename, this_options, node, requested_type);
478 if (!result) {
479 if (report_errors) {
480 loader_cat.error()
481 << "Couldn't save file " << this_filename << ".\n";
482 }
483 }
484
485 return result;
486}
487
488/**
489 * The implementation of save_file(), this tries to write a specific file
490 * type.
491 */
492bool Loader::
493try_save_file(const Filename &pathname, const LoaderOptions &options,
494 PandaNode *node, LoaderFileType *requested_type) const {
495 return requested_type->save_file(pathname, options, node);
496}
497
498/**
499 * Loads up all of the dynamic libraries named in a load-file-type Configure
500 * variable. Presumably this will make the various file types available for
501 * runtime loading.
502 */
503void Loader::
504load_file_types() {
505 if (!_file_types_loaded) {
506 int num_unique_values = load_file_type.get_num_unique_values();
507
508 for (int i = 0; i < num_unique_values; i++) {
509 string param = load_file_type.get_unique_value(i);
510
511 vector_string words;
512 extract_words(param, words);
513
514 if (words.size() == 1) {
515 // Exactly one word: load the named library immediately.
516 string name = words[0];
517 Filename dlname = Filename::dso_filename("lib" + name + ".so");
518 loader_cat.info()
519 << "loading file type module: " << name << std::endl;
520 void *tmp = load_dso(get_plugin_path().get_value(), dlname);
521 if (tmp == nullptr) {
522 loader_cat.warning()
523 << "Unable to load " << dlname.to_os_specific()
524 << ": " << load_dso_error() << std::endl;
525 } else if (loader_cat.is_debug()) {
526 loader_cat.debug()
527 << "done loading file type module: " << name << std::endl;
528 }
529
530 } else if (words.size() > 1) {
531 // Multiple words: the first n words are filename extensions, and the
532 // last word is the name of the library to load should any of those
533 // filename extensions be encountered.
535 size_t num_extensions = words.size() - 1;
536 string library_name = words[num_extensions];
537
538 for (size_t i = 0; i < num_extensions; i++) {
539 string extension = words[i];
540 if (extension[0] == '.') {
541 extension = extension.substr(1);
542 }
543
544 registry->register_deferred_type(extension, library_name);
545 }
546 }
547 }
548
549 _file_types_loaded = true;
550 }
551}
552
553/**
554 * Called once per application to create the global loader object.
555 */
556void Loader::
557make_global_ptr() {
558 nassertv(_global_ptr == nullptr);
559
560 _global_ptr = new Loader("loader");
561}
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
The AsyncTaskChain is a subset of the AsyncTaskManager.
static AsyncTaskManager * get_global_ptr()
Returns a pointer to the global AsyncTaskManager.
This class represents a concrete task performed by an AsyncManager.
Definition asyncTask.h:32
An instance of this class is written to the front of a Bam or Txo file to make the file a cached inst...
get_data
Returns a pointer to the data stored in the record, or NULL if there is no data.
set_data
Stores a new data object on the record.
This class maintains a cache of Bam and/or Txo objects generated from model files and texture images ...
Definition bamCache.h:42
get_cache_models
Returns whether model files (e.g.
Definition bamCache.h:89
bool store(BamCacheRecord *record)
Flushes a cache entry to disk.
Definition bamCache.cxx:194
static BamCache * get_global_ptr()
Returns a pointer to the global BamCache object, which is used automatically by the ModelPool and Tex...
Definition bamCache.I:223
The principle public interface to reading and writing Bam disk files.
Definition bamFile.h:41
bool open_read(const Filename &bam_filename, bool report_errors=true)
Attempts to open the indicated filename for reading.
Definition bamFile.cxx:51
get_reader
Returns the BamReader in charge of performing the read operations.
Definition bamFile.h:85
This class specializes ConfigVariable as an enumerated type.
This is a convenience class to specialize ConfigVariable as an integer type.
std::string get_unique_value(size_t n) const
Returns the nth unique value of the variable.
size_t get_num_unique_values() const
Returns the number of unique values in the variable.
get_value
Returns the variable's value.
This class stores a list of directories that can be searched, in order, to locate a particular file.
Definition dSearchPath.h:28
The name of a file, such as a texture file or an Egg file.
Definition filename.h:44
std::string get_basename() const
Returns the basename part of the filename.
Definition filename.I:367
std::string get_fullpath_wo_extension() const
Returns the full filename–directory and basename parts–except for the extension.
Definition filename.I:377
std::string to_os_specific() const
Converts the filename from our generic Unix-like convention (forward slashes starting with the root a...
std::string get_extension() const
Returns the file extension.
Definition filename.I:400
bool is_local() const
Returns true if the filename is local, e.g.
Definition filename.I:549
This class maintains the set of all known LoaderFileTypes in the universe.
void write(std::ostream &out, int indent_level=0) const
Writes a list of supported file types to the indicated output stream, one per line.
static LoaderFileTypeRegistry * get_global_ptr()
Returns a pointer to the global LoaderFileTypeRegistry object.
LoaderFileType * get_type_from_extension(const std::string &extension)
Determines the type of the file based on the indicated extension (without a leading dot).
void register_deferred_type(const std::string &extension, const std::string &library)
Records a type associated with a particular extension to be loaded in the future.
This is the base class for a family of scene-graph file types that the Loader supports.
virtual bool supports_compressed() const
Returns true if this file type can transparently load compressed files (with a .pz or ....
virtual bool supports_save() const
Returns true if the file type can be used to save files, and save_file() is supported.
virtual bool supports_load() const
Returns true if the file type can be used to load files, and load_file() is supported.
Specifies parameters that may be passed to the loader.
A convenient class for loading models from disk, in bam or egg format (or any of a number of other fo...
Definition loader.h:42
A class object that manages a single asynchronous model load request.
static void add_model(const Filename &filename, ModelRoot *model)
Adds the indicated already-loaded model to the pool.
Definition modelPool.I:69
static ModelRoot * get_model(const Filename &filename, bool verify)
Returns the model that has already been previously loaded, or NULL otherwise.
Definition modelPool.I:46
A node of this type is created automatically at the root of each model file that is loaded.
Definition modelRoot.h:27
set_fullpath
Sets the full pathname of the model represented by this node, as found on disk.
Definition modelRoot.h:37
set_timestamp
Sets the timestamp of the file on disk that was read for this model.
Definition modelRoot.h:41
A class object that manages a single asynchronous model save request.
A base class for all things which can have a name.
Definition namable.h:26
NodePath is the fundamental system for disambiguating instances, and also provides a higher-level int...
Definition nodePath.h:159
NodePath copy_to(const NodePath &other, int sort=0, Thread *current_thread=Thread::get_current_thread()) const
Functions like instance_to(), except a deep copy is made of the referenced node and all of its descen...
Definition nodePath.cxx:538
PandaNode * node() const
Returns the referenced node of the path.
Definition nodePath.I:227
A basic node of the scene graph or data graph.
Definition pandaNode.h:65
An interface for simplifying ("flattening") scene graphs by eliminating unneeded nodes and collapsing...
void premunge(PandaNode *root, const RenderState *initial_state)
Walks the scene graph rooted at this node and below, and uses the indicated GSG to premunge every Geo...
TypeHandle is the identifier used to differentiate C++ class types.
Definition typeHandle.h:81
A hierarchy of directories and files that appears to be one continuous file system,...
bool exists(const Filename &filename) const
Convenience function; returns true if the named file exists in the virtual file system hierarchy.
static VirtualFileSystem * get_global_ptr()
Returns the default global VirtualFileSystem.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
int extract_words(const string &str, vector_string &words)
Divides the string into a number of words according to whitespace.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.