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