vmap/radiant/referencecache.cpp
Marco Hladik 976821e604 Map_StartPosition: Don't just pick some random entity, just default to the
center since this happens in our games anyway.
Address the constant spam of unnecessary prints to console. We do not need
to know that a material we loaded succeeded - focus on the warnings and
errors.
2021-06-04 12:21:39 +02:00

811 lines
21 KiB
C++

/*
Copyright (C) 2001-2006, William Joseph.
All Rights Reserved.
This file is part of GtkRadiant.
GtkRadiant is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
GtkRadiant is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with GtkRadiant; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "referencecache.h"
#include "globaldefs.h"
#include "debugging/debugging.h"
#include "iscenegraph.h"
#include "iselection.h"
#include "iundo.h"
#include "imap.h"
MapModules &ReferenceAPI_getMapModules();
#include "imodel.h"
ModelModules &ReferenceAPI_getModelModules();
#include "ifilesystem.h"
#include "iarchive.h"
#include "ifiletypes.h"
#include "ireference.h"
#include "ientity.h"
#include "qerplugin.h"
#include <list>
#include "container/cache.h"
#include "container/hashfunc.h"
#include "os/path.h"
#include "stream/textfilestream.h"
#include "nullmodel.h"
#include "maplib.h"
#include "stream/stringstream.h"
#include "os/file.h"
#include "moduleobserver.h"
#include "moduleobservers.h"
#include "mainframe.h"
#include "map.h"
#include "filetypes.h"
bool References_Saved();
void MapChanged()
{
Map_SetModified(g_map, !References_Saved());
}
EntityCreator *g_entityCreator = 0;
bool MapResource_loadFile(const MapFormat &format, scene::Node &root, const char *filename)
{
globalOutputStream() << "Open file " << filename << " for read...";
TextFileInputStream file(filename);
if (!file.failed()) {
globalOutputStream() << "success\n";
ScopeDisableScreenUpdates disableScreenUpdates(path_get_filename_start(filename), "Loading Map");
ASSERT_NOTNULL(g_entityCreator);
format.readGraph(root, file, *g_entityCreator);
return true;
} else {
globalErrorStream() << "failure\n";
return false;
}
}
NodeSmartReference MapResource_load(const MapFormat &format, const char *path, const char *name)
{
NodeSmartReference root(NewMapRoot(name));
StringOutputStream fullpath(256);
fullpath << path << name;
if (path_is_absolute(fullpath.c_str())) {
MapResource_loadFile(format, root, fullpath.c_str());
} else {
globalErrorStream() << "map path is not fully qualified: " << makeQuoted(fullpath.c_str()) << "\n";
}
return root;
}
bool MapResource_saveFile(const MapFormat &format, scene::Node &root, GraphTraversalFunc traverse, const char *filename)
{
//ASSERT_MESSAGE(path_is_absolute(filename), "MapResource_saveFile: path is not absolute: " << makeQuoted(filename));
globalOutputStream() << "Open file " << filename << " for write...";
TextFileOutputStream file(filename);
if (!file.failed()) {
globalOutputStream() << "success\n";
ScopeDisableScreenUpdates disableScreenUpdates(path_get_filename_start(filename), "Saving Map");
format.writeGraph(root, traverse, file);
return true;
}
globalErrorStream() << "failure\n";
return false;
}
bool file_saveBackup(const char *path)
{
if (file_writeable(path)) {
StringOutputStream backup(256);
backup << StringRange(path, path_get_extension(path)) << "bak";
return (!file_exists(backup.c_str()) || file_remove(backup.c_str())) // remove backup
&& file_move(path, backup.c_str()); // rename current to backup
}
globalErrorStream() << "map path is not writeable: " << makeQuoted(path) << "\n";
return false;
}
bool MapResource_save(const MapFormat &format, scene::Node &root, const char *path, const char *name)
{
StringOutputStream fullpath(256);
fullpath << path << name;
if (path_is_absolute(fullpath.c_str())) {
if (!file_exists(fullpath.c_str()) || file_saveBackup(fullpath.c_str())) {
return MapResource_saveFile(format, root, Map_Traverse, fullpath.c_str());
}
globalErrorStream() << "failed to save a backup map file: " << makeQuoted(fullpath.c_str()) << "\n";
return false;
}
globalErrorStream() << "map path is not fully qualified: " << makeQuoted(fullpath.c_str()) << "\n";
return false;
}
namespace {
NodeSmartReference g_nullNode(NewNullNode());
NodeSmartReference g_nullModel(g_nullNode);
}
class NullModelLoader : public ModelLoader {
public:
scene::Node &loadModel(ArchiveFile &file)
{
return g_nullModel;
}
};
namespace {
NullModelLoader g_NullModelLoader;
}
/// \brief Returns the model loader for the model \p type or 0 if the model \p type has no loader module
ModelLoader *ModelLoader_forType(const char *type)
{
const char *moduleName = findModuleName(&GlobalFiletypes(), ModelLoader::Name(), type);
if (string_not_empty(moduleName)) {
ModelLoader *table = ReferenceAPI_getModelModules().findModule(moduleName);
if (table != 0) {
return table;
} else {
globalErrorStream() << "ERROR: Model type incorrectly registered: \"" << moduleName << "\"\n";
return &g_NullModelLoader;
}
}
return 0;
}
NodeSmartReference ModelResource_load(ModelLoader *loader, const char *name)
{
ScopeDisableScreenUpdates disableScreenUpdates(path_get_filename_start(name), "Loading Model");
NodeSmartReference model(g_nullModel);
ArchiveFile *file = GlobalFileSystem().openFile(name);
if (file != 0) {
// Only output failures, we don't need to spam the console with successes
//globalOutputStream() << "Loaded Model: \"" << name << "\"\n";
model = loader->loadModel(*file);
file->release();
} else {
globalErrorStream() << "Model load failed: \"" << name << "\"\n";
}
model.get().m_isRoot = true;
return model;
}
inline hash_t path_hash(const char *path, hash_t previous = 0)
{
#if GDEF_OS_WINDOWS
return string_hash_nocase( path, previous );
#else // UNIX
return string_hash(path, previous);
#endif
}
struct PathEqual {
bool operator()(const CopiedString &path, const CopiedString &other) const
{
return path_equal(path.c_str(), other.c_str());
}
};
struct PathHash {
typedef hash_t hash_type;
hash_type operator()(const CopiedString &path) const
{
return path_hash(path.c_str());
}
};
typedef std::pair<CopiedString, CopiedString> ModelKey;
struct ModelKeyEqual {
bool operator()(const ModelKey &key, const ModelKey &other) const
{
return path_equal(key.first.c_str(), other.first.c_str()) &&
path_equal(key.second.c_str(), other.second.c_str());
}
};
struct ModelKeyHash {
typedef hash_t hash_type;
hash_type operator()(const ModelKey &key) const
{
return hash_combine(path_hash(key.first.c_str()), path_hash(key.second.c_str()));
}
};
typedef HashTable<ModelKey, NodeSmartReference, ModelKeyHash, ModelKeyEqual> ModelCache;
ModelCache g_modelCache;
bool g_modelCache_enabled = true;
ModelCache::iterator ModelCache_find(const char *path, const char *name)
{
if (g_modelCache_enabled) {
return g_modelCache.find(ModelKey(path, name));
}
return g_modelCache.end();
}
ModelCache::iterator ModelCache_insert(const char *path, const char *name, scene::Node &node)
{
if (g_modelCache_enabled) {
return g_modelCache.insert(ModelKey(path, name), NodeSmartReference(node));
}
return g_modelCache.insert(ModelKey("", ""), g_nullModel);
}
void ModelCache_flush(const char *path, const char *name)
{
ModelCache::iterator i = g_modelCache.find(ModelKey(path, name));
if (i != g_modelCache.end()) {
//ASSERT_MESSAGE((*i).value.getCount() == 0, "resource flushed while still in use: " << (*i).key.first.c_str() << (*i).key.second.c_str());
g_modelCache.erase(i);
}
}
void ModelCache_clear()
{
g_modelCache_enabled = false;
g_modelCache.clear();
g_modelCache_enabled = true;
}
NodeSmartReference Model_load(ModelLoader *loader, const char *path, const char *name, const char *type)
{
if (loader != 0) {
return ModelResource_load(loader, name);
} else {
const char *moduleName = findModuleName(&GlobalFiletypes(), MapFormat::Name(), type);
if (string_not_empty(moduleName)) {
const MapFormat *format = ReferenceAPI_getMapModules().findModule(moduleName);
if (format != 0) {
return MapResource_load(*format, path, name);
} else {
globalErrorStream() << "ERROR: Map type incorrectly registered: \"" << moduleName << "\"\n";
return g_nullModel;
}
} else {
if (string_not_empty(type)) {
globalErrorStream() << "Model type not supported: \"" << name << "\"\n";
}
return g_nullModel;
}
}
}
namespace {
bool g_realised = false;
// name may be absolute or relative
const char *rootPath(const char *name)
{
return GlobalFileSystem().findRoot(
path_is_absolute(name)
? name
: GlobalFileSystem().findFile(name)
);
}
}
struct ModelResource : public Resource {
NodeSmartReference m_model;
const CopiedString m_originalName;
CopiedString m_path;
CopiedString m_name;
CopiedString m_type;
ModelLoader *m_loader;
ModuleObservers m_observers;
std::time_t m_modified;
std::size_t m_unrealised;
ModelResource(const CopiedString &name) :
m_model(g_nullModel),
m_originalName(name),
m_type(path_get_extension(name.c_str())),
m_loader(0),
m_modified(0),
m_unrealised(1)
{
m_loader = ModelLoader_forType(m_type.c_str());
if (g_realised) {
realise();
}
}
~ModelResource()
{
if (realised()) {
unrealise();
}
ASSERT_MESSAGE(!realised(), "ModelResource::~ModelResource: resource reference still realised: "
<< makeQuoted(m_name.c_str()));
}
// NOT COPYABLE
ModelResource(const ModelResource &);
// NOT ASSIGNABLE
ModelResource &operator=(const ModelResource &);
void setModel(const NodeSmartReference &model)
{
m_model = model;
}
void clearModel()
{
m_model = g_nullModel;
}
void loadCached()
{
if (g_modelCache_enabled) {
// cache lookup
ModelCache::iterator i = ModelCache_find(m_path.c_str(), m_name.c_str());
if (i == g_modelCache.end()) {
i = ModelCache_insert(
m_path.c_str(),
m_name.c_str(),
Model_load(m_loader, m_path.c_str(), m_name.c_str(), m_type.c_str())
);
}
setModel((*i).value);
} else {
setModel(Model_load(m_loader, m_path.c_str(), m_name.c_str(), m_type.c_str()));
}
}
void loadModel()
{
loadCached();
connectMap();
mapSave();
}
bool load()
{
ASSERT_MESSAGE(realised(), "resource not realised");
if (m_model == g_nullModel) {
loadModel();
}
return m_model != g_nullModel;
}
bool save()
{
if (!mapSaved()) {
const char *moduleName = findModuleName(GetFileTypeRegistry(), MapFormat::Name(), m_type.c_str());
if (string_not_empty(moduleName)) {
const MapFormat *format = ReferenceAPI_getMapModules().findModule(moduleName);
if (format != 0 && MapResource_save(*format, m_model.get(), m_path.c_str(), m_name.c_str())) {
mapSave();
return true;
}
}
}
return false;
}
void flush()
{
if (realised()) {
ModelCache_flush(m_path.c_str(), m_name.c_str());
}
}
scene::Node *getNode()
{
//if(m_model != g_nullModel)
{
return m_model.get_pointer();
}
//return 0;
}
void setNode(scene::Node *node)
{
ModelCache::iterator i = ModelCache_find(m_path.c_str(), m_name.c_str());
if (i != g_modelCache.end()) {
(*i).value = NodeSmartReference(*node);
}
setModel(NodeSmartReference(*node));
connectMap();
}
void attach(ModuleObserver &observer)
{
if (realised()) {
observer.realise();
}
m_observers.attach(observer);
}
void detach(ModuleObserver &observer)
{
if (realised()) {
observer.unrealise();
}
m_observers.detach(observer);
}
bool realised()
{
return m_unrealised == 0;
}
void realise()
{
ASSERT_MESSAGE(m_unrealised != 0, "ModelResource::realise: already realised");
if (--m_unrealised == 0) {
m_path = rootPath(m_originalName.c_str());
m_name = path_make_relative(m_originalName.c_str(), m_path.c_str());
//globalOutputStream() << "ModelResource::realise: " << m_path.c_str() << m_name.c_str() << "\n";
m_observers.realise();
}
}
void unrealise()
{
if (++m_unrealised == 1) {
m_observers.unrealise();
//globalOutputStream() << "ModelResource::unrealise: " << m_path.c_str() << m_name.c_str() << "\n";
clearModel();
}
}
bool isMap() const
{
return Node_getMapFile(m_model) != 0;
}
void connectMap()
{
MapFile *map = Node_getMapFile(m_model);
if (map != 0) {
map->setChangedCallback(makeCallbackF(MapChanged));
}
}
std::time_t modified() const
{
StringOutputStream fullpath(256);
fullpath << m_path.c_str() << m_name.c_str();
return file_modified(fullpath.c_str());
}
void mapSave()
{
m_modified = modified();
MapFile *map = Node_getMapFile(m_model);
if (map != 0) {
map->save();
}
}
bool mapSaved() const
{
MapFile *map = Node_getMapFile(m_model);
if (map != 0) {
return m_modified == modified() && map->saved();
}
return true;
}
bool isModified() const
{
return ((!string_empty(m_path.c_str()) // had or has an absolute path
&& m_modified != modified()) // AND disk timestamp changed
|| !path_equal(rootPath(m_originalName.c_str()), m_path.c_str())); // OR absolute vfs-root changed
}
void refresh()
{
if (isModified()) {
flush();
unrealise();
realise();
}
}
};
class HashtableReferenceCache : public ReferenceCache, public ModuleObserver {
typedef HashedCache<CopiedString, ModelResource, PathHash, PathEqual> ModelReferences;
ModelReferences m_references;
std::size_t m_unrealised;
class ModelReferencesSnapshot {
ModelReferences &m_references;
typedef std::list<ModelReferences::iterator> Iterators;
Iterators m_iterators;
public:
typedef Iterators::iterator iterator;
ModelReferencesSnapshot(ModelReferences &references) : m_references(references)
{
for (ModelReferences::iterator i = m_references.begin(); i != m_references.end(); ++i) {
m_references.capture(i);
m_iterators.push_back(i);
}
}
~ModelReferencesSnapshot()
{
for (Iterators::iterator i = m_iterators.begin(); i != m_iterators.end(); ++i) {
m_references.release(*i);
}
}
iterator begin()
{
return m_iterators.begin();
}
iterator end()
{
return m_iterators.end();
}
};
public:
typedef ModelReferences::iterator iterator;
HashtableReferenceCache() : m_unrealised(1)
{
}
iterator begin()
{
return m_references.begin();
}
iterator end()
{
return m_references.end();
}
void clear()
{
m_references.clear();
}
Resource *capture(const char *path)
{
//globalOutputStream() << "capture: \"" << path << "\"\n";
return m_references.capture(CopiedString(path)).get();
}
void release(const char *path)
{
m_references.release(CopiedString(path));
//globalOutputStream() << "release: \"" << path << "\"\n";
}
void setEntityCreator(EntityCreator &entityCreator)
{
g_entityCreator = &entityCreator;
}
bool realised() const
{
return m_unrealised == 0;
}
void realise()
{
ASSERT_MESSAGE(m_unrealised != 0, "HashtableReferenceCache::realise: already realised");
if (--m_unrealised == 0) {
g_realised = true;
{
ModelReferencesSnapshot snapshot(m_references);
for (ModelReferencesSnapshot::iterator i = snapshot.begin(); i != snapshot.end(); ++i) {
ModelReferences::value_type &value = *(*i);
if (value.value.count() != 1) {
value.value.get()->realise();
}
}
}
}
}
void unrealise()
{
if (++m_unrealised == 1) {
g_realised = false;
{
ModelReferencesSnapshot snapshot(m_references);
for (ModelReferencesSnapshot::iterator i = snapshot.begin(); i != snapshot.end(); ++i) {
ModelReferences::value_type &value = *(*i);
if (value.value.count() != 1) {
value.value.get()->unrealise();
}
}
}
ModelCache_clear();
}
}
void refresh()
{
ModelReferencesSnapshot snapshot(m_references);
for (ModelReferencesSnapshot::iterator i = snapshot.begin(); i != snapshot.end(); ++i) {
ModelResource *resource = (*(*i)).value.get();
if (!resource->isMap()) {
resource->refresh();
}
}
}
};
namespace {
HashtableReferenceCache g_referenceCache;
}
#if 0
class ResourceVisitor
{
public:
virtual void visit( const char* name, const char* path, const
};
#endif
void SaveReferences()
{
ScopeDisableScreenUpdates disableScreenUpdates("Processing...", "Saving Map");
for (HashtableReferenceCache::iterator i = g_referenceCache.begin(); i != g_referenceCache.end(); ++i) {
(*i).value->save();
}
MapChanged();
}
bool References_Saved()
{
for (HashtableReferenceCache::iterator i = g_referenceCache.begin(); i != g_referenceCache.end(); ++i) {
scene::Node *node = (*i).value->getNode();
if (node != 0) {
MapFile *map = Node_getMapFile(*node);
if (map != 0 && !map->saved()) {
return false;
}
}
}
return true;
}
void RefreshReferences()
{
ScopeDisableScreenUpdates disableScreenUpdates("Processing...", "Refreshing Models");
g_referenceCache.refresh();
}
void FlushReferences()
{
ModelCache_clear();
g_referenceCache.clear();
}
ReferenceCache &GetReferenceCache()
{
return g_referenceCache;
}
#include "modulesystem/modulesmap.h"
#include "modulesystem/singletonmodule.h"
#include "modulesystem/moduleregistry.h"
class ReferenceDependencies :
public GlobalRadiantModuleRef,
public GlobalFileSystemModuleRef,
public GlobalFiletypesModuleRef {
ModelModulesRef m_model_modules;
MapModulesRef m_map_modules;
public:
ReferenceDependencies() :
m_model_modules(GlobalRadiant().getRequiredGameDescriptionKeyValue("modeltypes")),
m_map_modules(GlobalRadiant().getRequiredGameDescriptionKeyValue("maptypes"))
{
}
ModelModules &getModelModules()
{
return m_model_modules.get();
}
MapModules &getMapModules()
{
return m_map_modules.get();
}
};
class ReferenceAPI : public TypeSystemRef {
ReferenceCache *m_reference;
public:
typedef ReferenceCache Type;
STRING_CONSTANT(Name, "*");
ReferenceAPI()
{
g_nullModel = NewNullModel();
GlobalFileSystem().attach(g_referenceCache);
m_reference = &GetReferenceCache();
}
~ReferenceAPI()
{
GlobalFileSystem().detach(g_referenceCache);
g_nullModel = g_nullNode;
}
ReferenceCache *getTable()
{
return m_reference;
}
};
typedef SingletonModule<ReferenceAPI, ReferenceDependencies> ReferenceModule;
typedef Static<ReferenceModule> StaticReferenceModule;
StaticRegisterModule staticRegisterReference(StaticReferenceModule::instance());
ModelModules &ReferenceAPI_getModelModules()
{
return StaticReferenceModule::instance().getDependencies().getModelModules();
}
MapModules &ReferenceAPI_getMapModules()
{
return StaticReferenceModule::instance().getDependencies().getMapModules();
}