mirror of
https://github.com/panda3d/panda3d.git
synced 2025-10-04 10:54:24 -04:00
support deferred loading of loader file types
This commit is contained in:
parent
e435834ec8
commit
25a599e3c9
@ -31,9 +31,10 @@
|
|||||||
#include "circBuffer.h"
|
#include "circBuffer.h"
|
||||||
#include "filename.h"
|
#include "filename.h"
|
||||||
#include "load_dso.h"
|
#include "load_dso.h"
|
||||||
|
#include "string_utils.h"
|
||||||
#include "plist.h"
|
#include "plist.h"
|
||||||
#include "pvector.h"
|
#include "pvector.h"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
|
||||||
|
|
||||||
@ -310,17 +311,49 @@ load_file_types() {
|
|||||||
nassertv(load_file_type != (Config::ConfigTable::Symbol *)NULL);
|
nassertv(load_file_type != (Config::ConfigTable::Symbol *)NULL);
|
||||||
|
|
||||||
if (!_file_types_loaded) {
|
if (!_file_types_loaded) {
|
||||||
|
|
||||||
|
// When we use GetAll(), we might inadvertently read duplicate
|
||||||
|
// lines. Filter them out with a set.
|
||||||
|
pset<string> already_read;
|
||||||
|
|
||||||
Config::ConfigTable::Symbol::iterator ti;
|
Config::ConfigTable::Symbol::iterator ti;
|
||||||
for (ti = load_file_type->begin(); ti != load_file_type->end(); ++ti) {
|
for (ti = load_file_type->begin(); ti != load_file_type->end(); ++ti) {
|
||||||
Filename dlname = Filename::dso_filename("lib" + (*ti).Val() + ".so");
|
string param = (*ti).Val();
|
||||||
loader_cat.info()
|
if (already_read.insert(param).second) {
|
||||||
<< "loading file type module: " << dlname.to_os_specific() << endl;
|
vector_string words;
|
||||||
void *tmp = load_dso(dlname);
|
extract_words(param, words);
|
||||||
if (tmp == (void *)NULL) {
|
|
||||||
loader_cat.info()
|
if (words.size() == 1) {
|
||||||
<< "Unable to load: " << load_dso_error() << endl;
|
// Exactly one word: load the named library immediately.
|
||||||
|
Filename dlname = Filename::dso_filename("lib" + words[0] + ".so");
|
||||||
|
loader_cat.info()
|
||||||
|
<< "loading file type module: " << dlname.to_os_specific() << endl;
|
||||||
|
void *tmp = load_dso(dlname);
|
||||||
|
if (tmp == (void *)NULL) {
|
||||||
|
loader_cat.info()
|
||||||
|
<< "Unable to load: " << 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_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;
|
_file_types_loaded = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -38,6 +38,18 @@ LoaderFileType::
|
|||||||
~LoaderFileType() {
|
~LoaderFileType() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////
|
||||||
|
// Function: LoaderFileType::get_additional_extensions
|
||||||
|
// Access: Public, Virtual
|
||||||
|
// Description: Returns a space-separated list of extension, in
|
||||||
|
// addition to the one returned by get_extension(), that
|
||||||
|
// are recognized by this loader.
|
||||||
|
////////////////////////////////////////////////////////////////////
|
||||||
|
string LoaderFileType::
|
||||||
|
get_additional_extensions() const {
|
||||||
|
return string();
|
||||||
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////
|
||||||
// Function: LoaderFileType::load_file
|
// Function: LoaderFileType::load_file
|
||||||
// Access: Public, Virtual
|
// Access: Public, Virtual
|
||||||
|
@ -43,6 +43,7 @@ public:
|
|||||||
|
|
||||||
virtual string get_name() const=0;
|
virtual string get_name() const=0;
|
||||||
virtual string get_extension() const=0;
|
virtual string get_extension() const=0;
|
||||||
|
virtual string get_additional_extensions() const;
|
||||||
|
|
||||||
virtual PT(PandaNode) load_file(const Filename &path, bool report_errors) const;
|
virtual PT(PandaNode) load_file(const Filename &path, bool report_errors) const;
|
||||||
|
|
||||||
|
@ -20,8 +20,8 @@
|
|||||||
#include "loaderFileType.h"
|
#include "loaderFileType.h"
|
||||||
#include "config_pgraph.h"
|
#include "config_pgraph.h"
|
||||||
|
|
||||||
#include <string_utils.h>
|
#include "string_utils.h"
|
||||||
#include <indent.h>
|
#include "indent.h"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
|
||||||
@ -88,11 +88,41 @@ get_type(int n) const {
|
|||||||
// the extension matches no known file types.
|
// the extension matches no known file types.
|
||||||
////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////
|
||||||
LoaderFileType *LoaderFileTypeRegistry::
|
LoaderFileType *LoaderFileTypeRegistry::
|
||||||
get_type_from_extension(const string &extension) const {
|
get_type_from_extension(const string &extension) {
|
||||||
|
string dcextension = downcase(extension);
|
||||||
|
|
||||||
Extensions::const_iterator ei;
|
Extensions::const_iterator ei;
|
||||||
ei = _extensions.find(downcase(extension));
|
ei = _extensions.find(dcextension);
|
||||||
if (ei == _extensions.end()) {
|
if (ei == _extensions.end()) {
|
||||||
// Nothing matches that extension.
|
// Nothing matches that extension. Do we have a deferred type?
|
||||||
|
|
||||||
|
DeferredTypes::iterator di;
|
||||||
|
di = _deferred_types.find(dcextension);
|
||||||
|
if (di != _deferred_types.end()) {
|
||||||
|
// We do! Try to load the deferred library on-the-fly. Note
|
||||||
|
// that this is a race condition if we support threaded loading;
|
||||||
|
// this whole function needs to be protected from multiple
|
||||||
|
// entry.
|
||||||
|
Filename dlname = Filename::dso_filename("lib" + (*di).second + ".so");
|
||||||
|
_deferred_types.erase(di);
|
||||||
|
|
||||||
|
loader_cat->info()
|
||||||
|
<< "loading file type module: " << dlname.to_os_specific() << endl;
|
||||||
|
void *tmp = load_dso(dlname);
|
||||||
|
if (tmp == (void *)NULL) {
|
||||||
|
loader_cat->info()
|
||||||
|
<< "Unable to load: " << load_dso_error() << endl;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now try again to find the LoaderFileType.
|
||||||
|
ei = _extensions.find(dcextension);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ei == _extensions.end()) {
|
||||||
|
// Nothing matches that extension, even after we've checked for a
|
||||||
|
// deferred type description.
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -119,6 +149,16 @@ write_types(ostream &out, int indent_level) const {
|
|||||||
<< " ." << type->get_extension() << "\n";
|
<< " ." << type->get_extension() << "\n";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!_deferred_types.empty()) {
|
||||||
|
indent(out, indent_level) << "Also available:";
|
||||||
|
DeferredTypes::const_iterator di;
|
||||||
|
for (di = _deferred_types.begin(); di != _deferred_types.end(); ++di) {
|
||||||
|
const string &extension = (*di).first;
|
||||||
|
out << " ." << extension;
|
||||||
|
}
|
||||||
|
out << "\n";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////
|
||||||
@ -130,7 +170,7 @@ void LoaderFileTypeRegistry::
|
|||||||
register_type(LoaderFileType *type) {
|
register_type(LoaderFileType *type) {
|
||||||
// Make sure we haven't already registered this type.
|
// Make sure we haven't already registered this type.
|
||||||
if (find(_types.begin(), _types.end(), type) != _types.end()) {
|
if (find(_types.begin(), _types.end(), type) != _types.end()) {
|
||||||
loader_cat.warning()
|
loader_cat->info()
|
||||||
<< "Attempt to register LoaderFileType " << type->get_name()
|
<< "Attempt to register LoaderFileType " << type->get_name()
|
||||||
<< " (" << type->get_type() << ") more than once.\n";
|
<< " (" << type->get_type() << ") more than once.\n";
|
||||||
return;
|
return;
|
||||||
@ -138,14 +178,77 @@ register_type(LoaderFileType *type) {
|
|||||||
|
|
||||||
_types.push_back(type);
|
_types.push_back(type);
|
||||||
|
|
||||||
string extension = downcase(type->get_extension());
|
record_extension(type->get_extension(), type);
|
||||||
Extensions::const_iterator ei;
|
|
||||||
ei = _extensions.find(extension);
|
vector_string words;
|
||||||
if (ei != _extensions.end()) {
|
extract_words(type->get_additional_extensions(), words);
|
||||||
loader_cat.warning()
|
vector_string::const_iterator wi;
|
||||||
<< "Multiple LoaderFileTypes registered that use the extension "
|
for (wi = words.begin(); wi != words.end(); ++wi) {
|
||||||
<< extension << "\n";
|
record_extension(*wi, type);
|
||||||
} else {
|
|
||||||
_extensions.insert(Extensions::value_type(extension, type));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////
|
||||||
|
// Function: LoaderFileTypeRegistry::register_deferred_type
|
||||||
|
// Access: Public
|
||||||
|
// Description: Records a type associated with a particular extension
|
||||||
|
// to be loaded in the future. The named library will
|
||||||
|
// be dynamically loaded the first time files of this
|
||||||
|
// extension are loaded; presumably this library will
|
||||||
|
// call register_type() when it initializes, thus making
|
||||||
|
// the extension loadable.
|
||||||
|
////////////////////////////////////////////////////////////////////
|
||||||
|
void LoaderFileTypeRegistry::
|
||||||
|
register_deferred_type(const string &extension, const string &library) {
|
||||||
|
string dcextension = downcase(extension);
|
||||||
|
|
||||||
|
Extensions::const_iterator ei;
|
||||||
|
ei = _extensions.find(dcextension);
|
||||||
|
if (ei != _extensions.end()) {
|
||||||
|
// We already have a loader for this type; no need to register
|
||||||
|
// another one.
|
||||||
|
loader_cat->info()
|
||||||
|
<< "Attempt to register loader library " << library
|
||||||
|
<< " (" << dcextension << ") when extension is already known.\n";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
DeferredTypes::const_iterator di;
|
||||||
|
di = _deferred_types.find(dcextension);
|
||||||
|
if (di != _deferred_types.end()) {
|
||||||
|
if ((*di).second == library) {
|
||||||
|
loader_cat->info()
|
||||||
|
<< "Attempt to register loader library " << library
|
||||||
|
<< " (" << dcextension << ") more than once.\n";
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
loader_cat->info()
|
||||||
|
<< "Multiple libraries registered that use the extension "
|
||||||
|
<< dcextension << "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_deferred_types[dcextension] = library;
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////
|
||||||
|
// Function: LoaderFileTypeRegistry::record_extension
|
||||||
|
// Access: Private
|
||||||
|
// Description: Records a filename extension recognized by a loader
|
||||||
|
// file type.
|
||||||
|
////////////////////////////////////////////////////////////////////
|
||||||
|
void LoaderFileTypeRegistry::
|
||||||
|
record_extension(const string &extension, LoaderFileType *type) {
|
||||||
|
string dcextension = downcase(extension);
|
||||||
|
Extensions::const_iterator ei;
|
||||||
|
ei = _extensions.find(dcextension);
|
||||||
|
if (ei != _extensions.end()) {
|
||||||
|
loader_cat->info()
|
||||||
|
<< "Multiple LoaderFileTypes registered that use the extension "
|
||||||
|
<< dcextension << "\n";
|
||||||
|
} else {
|
||||||
|
_extensions.insert(Extensions::value_type(dcextension, type));
|
||||||
|
}
|
||||||
|
|
||||||
|
_deferred_types.erase(dcextension);
|
||||||
|
}
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
#ifndef LOADERFILETYPEREGISTRY_H
|
#ifndef LOADERFILETYPEREGISTRY_H
|
||||||
#define LOADERFILETYPEREGISTRY_H
|
#define LOADERFILETYPEREGISTRY_H
|
||||||
|
|
||||||
#include <pandabase.h>
|
#include "pandabase.h"
|
||||||
|
|
||||||
#include "pvector.h"
|
#include "pvector.h"
|
||||||
#include "pmap.h"
|
#include "pmap.h"
|
||||||
@ -44,11 +44,15 @@ public:
|
|||||||
int get_num_types() const;
|
int get_num_types() const;
|
||||||
LoaderFileType *get_type(int n) const;
|
LoaderFileType *get_type(int n) const;
|
||||||
|
|
||||||
LoaderFileType *get_type_from_extension(const string &extension) const;
|
LoaderFileType *get_type_from_extension(const string &extension);
|
||||||
|
|
||||||
void write_types(ostream &out, int indent_level = 0) const;
|
void write_types(ostream &out, int indent_level = 0) const;
|
||||||
|
|
||||||
void register_type(LoaderFileType *type);
|
void register_type(LoaderFileType *type);
|
||||||
|
void register_deferred_type(const string &extension, const string &library);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void record_extension(const string &extension, LoaderFileType *type);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
typedef pvector<LoaderFileType *> Types;
|
typedef pvector<LoaderFileType *> Types;
|
||||||
@ -57,6 +61,9 @@ private:
|
|||||||
typedef pmap<string, LoaderFileType *> Extensions;
|
typedef pmap<string, LoaderFileType *> Extensions;
|
||||||
Extensions _extensions;
|
Extensions _extensions;
|
||||||
|
|
||||||
|
typedef pmap<string, string> DeferredTypes;
|
||||||
|
DeferredTypes _deferred_types;
|
||||||
|
|
||||||
static LoaderFileTypeRegistry *_global_ptr;
|
static LoaderFileTypeRegistry *_global_ptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -90,6 +90,18 @@ set_egg_data(EggData *egg_data, bool owns_egg_data) {
|
|||||||
_owns_egg_data = owns_egg_data;
|
_owns_egg_data = owns_egg_data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////
|
||||||
|
// Function: SomethingToEggConverter::get_additional_extensions
|
||||||
|
// Access: Public, Virtual
|
||||||
|
// Description: Returns a space-separated list of extension, in
|
||||||
|
// addition to the one returned by get_extension(), that
|
||||||
|
// are recognized by this converter.
|
||||||
|
////////////////////////////////////////////////////////////////////
|
||||||
|
string SomethingToEggConverter::
|
||||||
|
get_additional_extensions() const {
|
||||||
|
return string();
|
||||||
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////
|
||||||
// Function: SomethingToEggConverter::handle_external_reference
|
// Function: SomethingToEggConverter::handle_external_reference
|
||||||
// Access: Public
|
// Access: Public
|
||||||
|
@ -101,6 +101,7 @@ public:
|
|||||||
|
|
||||||
virtual string get_name() const=0;
|
virtual string get_name() const=0;
|
||||||
virtual string get_extension() const=0;
|
virtual string get_extension() const=0;
|
||||||
|
virtual string get_additional_extensions() const;
|
||||||
|
|
||||||
virtual bool convert_file(const Filename &filename)=0;
|
virtual bool convert_file(const Filename &filename)=0;
|
||||||
|
|
||||||
|
@ -151,6 +151,18 @@ get_extension() const {
|
|||||||
return "mb";
|
return "mb";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////
|
||||||
|
// Function: MayaToEggConverter::get_additional_extensions
|
||||||
|
// Access: Public, Virtual
|
||||||
|
// Description: Returns a space-separated list of extension, in
|
||||||
|
// addition to the one returned by get_extension(), that
|
||||||
|
// are recognized by this converter.
|
||||||
|
////////////////////////////////////////////////////////////////////
|
||||||
|
string MayaToEggConverter::
|
||||||
|
get_additional_extensions() const {
|
||||||
|
return "ma";
|
||||||
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////
|
||||||
// Function: MayaToEggConverter::convert_file
|
// Function: MayaToEggConverter::convert_file
|
||||||
// Access: Public, Virtual
|
// Access: Public, Virtual
|
||||||
|
@ -70,6 +70,7 @@ public:
|
|||||||
|
|
||||||
virtual string get_name() const;
|
virtual string get_name() const;
|
||||||
virtual string get_extension() const;
|
virtual string get_extension() const;
|
||||||
|
virtual string get_additional_extensions() const;
|
||||||
|
|
||||||
virtual bool convert_file(const Filename &filename);
|
virtual bool convert_file(const Filename &filename);
|
||||||
bool convert_maya(bool from_selection);
|
bool convert_maya(bool from_selection);
|
||||||
|
@ -94,4 +94,21 @@
|
|||||||
mayaPview.cxx mayaPview.h
|
mayaPview.cxx mayaPview.h
|
||||||
|
|
||||||
#end lib_target
|
#end lib_target
|
||||||
|
|
||||||
|
#begin lib_target
|
||||||
|
#define USE_PACKAGES maya
|
||||||
|
#define TARGET mayaloader
|
||||||
|
#define BUILDING_DLL BUILDING_MISC
|
||||||
|
#define LOCAL_LIBS \
|
||||||
|
mayaegg ptloader converter pandatoolbase
|
||||||
|
#define OTHER_LIBS \
|
||||||
|
egg2pg:c builder:c egg:c pandaegg:m \
|
||||||
|
mathutil:c linmath:c putil:c panda:m \
|
||||||
|
express:c pandaexpress:m \
|
||||||
|
dtoolconfig dtool \
|
||||||
|
$[if $[UNIX_PLATFORM],OpenMayalib]
|
||||||
|
|
||||||
|
#define SOURCES \
|
||||||
|
config_mayaloader.cxx
|
||||||
|
|
||||||
|
#end lib_target
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
#define TARGET ptloader
|
#define TARGET ptloader
|
||||||
#define BUILDING_DLL BUILDING_PTLOADER
|
#define BUILDING_DLL BUILDING_PTLOADER
|
||||||
#define LOCAL_LIBS \
|
#define LOCAL_LIBS \
|
||||||
fltegg flt lwoegg lwo mayaegg converter pandatoolbase
|
fltegg flt lwoegg lwo converter pandatoolbase
|
||||||
#define OTHER_LIBS \
|
#define OTHER_LIBS \
|
||||||
egg2pg:c builder:c egg:c pandaegg:m \
|
egg2pg:c builder:c egg:c pandaegg:m \
|
||||||
mathutil:c linmath:c putil:c panda:m \
|
mathutil:c linmath:c putil:c panda:m \
|
||||||
@ -11,10 +11,6 @@
|
|||||||
#define UNIX_SYS_LIBS \
|
#define UNIX_SYS_LIBS \
|
||||||
m
|
m
|
||||||
|
|
||||||
// If we've got Maya, link in the Maya libraries.
|
|
||||||
// On second thought, don't. It takes forever to load.
|
|
||||||
//#define USE_PACKAGES maya
|
|
||||||
|
|
||||||
#define SOURCES \
|
#define SOURCES \
|
||||||
config_ptloader.cxx config_ptloader.h \
|
config_ptloader.cxx config_ptloader.h \
|
||||||
loaderFileTypePandatool.cxx loaderFileTypePandatool.h
|
loaderFileTypePandatool.cxx loaderFileTypePandatool.h
|
||||||
|
@ -19,9 +19,9 @@
|
|||||||
#ifndef CONFIG_PTLOADER_H
|
#ifndef CONFIG_PTLOADER_H
|
||||||
#define CONFIG_PTLOADER_H
|
#define CONFIG_PTLOADER_H
|
||||||
|
|
||||||
#include <pandatoolbase.h>
|
#include "pandatoolbase.h"
|
||||||
|
|
||||||
#include <dconfig.h>
|
#include "dconfig.h"
|
||||||
|
|
||||||
ConfigureDecl(config_ptloader, EXPCL_PTLOADER, EXPTP_PTLOADER);
|
ConfigureDecl(config_ptloader, EXPCL_PTLOADER, EXPTP_PTLOADER);
|
||||||
|
|
||||||
|
@ -66,6 +66,18 @@ get_extension() const {
|
|||||||
return _converter->get_extension();
|
return _converter->get_extension();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////
|
||||||
|
// Function: LoaderFileTypePandatool::get_additional_extensions
|
||||||
|
// Access: Public, Virtual
|
||||||
|
// Description: Returns a space-separated list of extension, in
|
||||||
|
// addition to the one returned by get_extension(), that
|
||||||
|
// are recognized by this converter.
|
||||||
|
////////////////////////////////////////////////////////////////////
|
||||||
|
string LoaderFileTypePandatool::
|
||||||
|
get_additional_extensions() const {
|
||||||
|
return _converter->get_additional_extensions();
|
||||||
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////
|
||||||
// Function: LoaderFileTypePandatool::resolve_filename
|
// Function: LoaderFileTypePandatool::resolve_filename
|
||||||
// Access: Public, Virtual
|
// Access: Public, Virtual
|
||||||
|
@ -39,6 +39,7 @@ public:
|
|||||||
|
|
||||||
virtual string get_name() const;
|
virtual string get_name() const;
|
||||||
virtual string get_extension() const;
|
virtual string get_extension() const;
|
||||||
|
virtual string get_additional_extensions() const;
|
||||||
|
|
||||||
virtual void resolve_filename(Filename &path) const;
|
virtual void resolve_filename(Filename &path) const;
|
||||||
virtual PT(PandaNode) load_file(const Filename &path, bool report_errors) const;
|
virtual PT(PandaNode) load_file(const Filename &path, bool report_errors) const;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user