2 Copyright (C) 2001-2006, William Joseph.
5 This file is part of GtkRadiant.
7 GtkRadiant is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 2 of the License, or
10 (at your option) any later version.
12 GtkRadiant is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
17 You should have received a copy of the GNU General Public License
18 along with GtkRadiant; if not, write to the Free Software
19 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
22 #include "referencecache.h"
23 #include "globaldefs.h"
25 #include "debugging/debugging.h"
27 #include "iscenegraph.h"
28 #include "iselection.h"
32 MapModules &ReferenceAPI_getMapModules();
36 ModelModules &ReferenceAPI_getModelModules();
38 #include "ifilesystem.h"
40 #include "ifiletypes.h"
41 #include "ireference.h"
43 #include "qerplugin.h"
47 #include "container/cache.h"
48 #include "container/hashfunc.h"
50 #include "stream/textfilestream.h"
51 #include "nullmodel.h"
53 #include "stream/stringstream.h"
55 #include "moduleobserver.h"
56 #include "moduleobservers.h"
58 #include "mainframe.h"
60 #include "filetypes.h"
63 bool References_Saved();
67 Map_SetModified(g_map, !References_Saved());
71 EntityCreator *g_entityCreator = 0;
73 bool MapResource_loadFile(const MapFormat &format, scene::Node &root, const char *filename)
75 globalOutputStream() << "Open file " << filename << " for read...";
76 TextFileInputStream file(filename);
78 globalOutputStream() << "success\n";
79 ScopeDisableScreenUpdates disableScreenUpdates(path_get_filename_start(filename), "Loading Map");
80 ASSERT_NOTNULL(g_entityCreator);
81 format.readGraph(root, file, *g_entityCreator);
84 globalErrorStream() << "failure\n";
89 NodeSmartReference MapResource_load(const MapFormat &format, const char *path, const char *name)
91 NodeSmartReference root(NewMapRoot(name));
93 StringOutputStream fullpath(256);
94 fullpath << path << name;
96 if (path_is_absolute(fullpath.c_str())) {
97 MapResource_loadFile(format, root, fullpath.c_str());
99 globalErrorStream() << "map path is not fully qualified: " << makeQuoted(fullpath.c_str()) << "\n";
105 bool MapResource_saveFile(const MapFormat &format, scene::Node &root, GraphTraversalFunc traverse, const char *filename)
107 //ASSERT_MESSAGE(path_is_absolute(filename), "MapResource_saveFile: path is not absolute: " << makeQuoted(filename));
108 globalOutputStream() << "Open file " << filename << " for write...";
109 TextFileOutputStream file(filename);
110 if (!file.failed()) {
111 globalOutputStream() << "success\n";
112 ScopeDisableScreenUpdates disableScreenUpdates(path_get_filename_start(filename), "Saving Map");
113 format.writeGraph(root, traverse, file);
117 globalErrorStream() << "failure\n";
121 bool file_saveBackup(const char *path)
123 if (file_writeable(path)) {
124 StringOutputStream backup(256);
125 backup << StringRange(path, path_get_extension(path)) << "bak";
127 return (!file_exists(backup.c_str()) || file_remove(backup.c_str())) // remove backup
128 && file_move(path, backup.c_str()); // rename current to backup
131 globalErrorStream() << "map path is not writeable: " << makeQuoted(path) << "\n";
135 bool MapResource_save(const MapFormat &format, scene::Node &root, const char *path, const char *name)
137 StringOutputStream fullpath(256);
138 fullpath << path << name;
140 if (path_is_absolute(fullpath.c_str())) {
141 if (!file_exists(fullpath.c_str()) || file_saveBackup(fullpath.c_str())) {
142 return MapResource_saveFile(format, root, Map_Traverse, fullpath.c_str());
145 globalErrorStream() << "failed to save a backup map file: " << makeQuoted(fullpath.c_str()) << "\n";
149 globalErrorStream() << "map path is not fully qualified: " << makeQuoted(fullpath.c_str()) << "\n";
154 NodeSmartReference g_nullNode(NewNullNode());
155 NodeSmartReference g_nullModel(g_nullNode);
158 class NullModelLoader : public ModelLoader {
160 scene::Node &loadModel(ArchiveFile &file)
167 NullModelLoader g_NullModelLoader;
171 /// \brief Returns the model loader for the model \p type or 0 if the model \p type has no loader module
172 ModelLoader *ModelLoader_forType(const char *type)
174 const char *moduleName = findModuleName(&GlobalFiletypes(), ModelLoader::Name(), type);
175 if (string_not_empty(moduleName)) {
176 ModelLoader *table = ReferenceAPI_getModelModules().findModule(moduleName);
180 globalErrorStream() << "ERROR: Model type incorrectly registered: \"" << moduleName << "\"\n";
181 return &g_NullModelLoader;
187 NodeSmartReference ModelResource_load(ModelLoader *loader, const char *name)
189 ScopeDisableScreenUpdates disableScreenUpdates(path_get_filename_start(name), "Loading Model");
191 NodeSmartReference model(g_nullModel);
194 ArchiveFile *file = GlobalFileSystem().openFile(name);
197 globalOutputStream() << "Loaded Model: \"" << name << "\"\n";
198 model = loader->loadModel(*file);
201 globalErrorStream() << "Model load failed: \"" << name << "\"\n";
205 model.get().m_isRoot = true;
211 inline hash_t path_hash(const char *path, hash_t previous = 0)
214 return string_hash_nocase( path, previous );
216 return string_hash(path, previous);
221 bool operator()(const CopiedString &path, const CopiedString &other) const
223 return path_equal(path.c_str(), other.c_str());
228 typedef hash_t hash_type;
230 hash_type operator()(const CopiedString &path) const
232 return path_hash(path.c_str());
236 typedef std::pair<CopiedString, CopiedString> ModelKey;
238 struct ModelKeyEqual {
239 bool operator()(const ModelKey &key, const ModelKey &other) const
241 return path_equal(key.first.c_str(), other.first.c_str()) &&
242 path_equal(key.second.c_str(), other.second.c_str());
246 struct ModelKeyHash {
247 typedef hash_t hash_type;
249 hash_type operator()(const ModelKey &key) const
251 return hash_combine(path_hash(key.first.c_str()), path_hash(key.second.c_str()));
255 typedef HashTable<ModelKey, NodeSmartReference, ModelKeyHash, ModelKeyEqual> ModelCache;
256 ModelCache g_modelCache;
257 bool g_modelCache_enabled = true;
259 ModelCache::iterator ModelCache_find(const char *path, const char *name)
261 if (g_modelCache_enabled) {
262 return g_modelCache.find(ModelKey(path, name));
264 return g_modelCache.end();
267 ModelCache::iterator ModelCache_insert(const char *path, const char *name, scene::Node &node)
269 if (g_modelCache_enabled) {
270 return g_modelCache.insert(ModelKey(path, name), NodeSmartReference(node));
272 return g_modelCache.insert(ModelKey("", ""), g_nullModel);
275 void ModelCache_flush(const char *path, const char *name)
277 ModelCache::iterator i = g_modelCache.find(ModelKey(path, name));
278 if (i != g_modelCache.end()) {
279 //ASSERT_MESSAGE((*i).value.getCount() == 0, "resource flushed while still in use: " << (*i).key.first.c_str() << (*i).key.second.c_str());
280 g_modelCache.erase(i);
284 void ModelCache_clear()
286 g_modelCache_enabled = false;
287 g_modelCache.clear();
288 g_modelCache_enabled = true;
291 NodeSmartReference Model_load(ModelLoader *loader, const char *path, const char *name, const char *type)
294 return ModelResource_load(loader, name);
296 const char *moduleName = findModuleName(&GlobalFiletypes(), MapFormat::Name(), type);
297 if (string_not_empty(moduleName)) {
298 const MapFormat *format = ReferenceAPI_getMapModules().findModule(moduleName);
300 return MapResource_load(*format, path, name);
302 globalErrorStream() << "ERROR: Map type incorrectly registered: \"" << moduleName << "\"\n";
306 if (string_not_empty(type)) {
307 globalErrorStream() << "Model type not supported: \"" << name << "\"\n";
315 bool g_realised = false;
317 // name may be absolute or relative
318 const char *rootPath(const char *name)
320 return GlobalFileSystem().findRoot(
321 path_is_absolute(name)
323 : GlobalFileSystem().findFile(name)
328 struct ModelResource : public Resource {
329 NodeSmartReference m_model;
330 const CopiedString m_originalName;
334 ModelLoader *m_loader;
335 ModuleObservers m_observers;
336 std::time_t m_modified;
337 std::size_t m_unrealised;
339 ModelResource(const CopiedString &name) :
340 m_model(g_nullModel),
341 m_originalName(name),
342 m_type(path_get_extension(name.c_str())),
347 m_loader = ModelLoader_forType(m_type.c_str());
359 ASSERT_MESSAGE(!realised(), "ModelResource::~ModelResource: resource reference still realised: "
360 << makeQuoted(m_name.c_str()));
364 ModelResource(const ModelResource &);
367 ModelResource &operator=(const ModelResource &);
369 void setModel(const NodeSmartReference &model)
376 m_model = g_nullModel;
381 if (g_modelCache_enabled) {
383 ModelCache::iterator i = ModelCache_find(m_path.c_str(), m_name.c_str());
384 if (i == g_modelCache.end()) {
385 i = ModelCache_insert(
388 Model_load(m_loader, m_path.c_str(), m_name.c_str(), m_type.c_str())
392 setModel((*i).value);
394 setModel(Model_load(m_loader, m_path.c_str(), m_name.c_str(), m_type.c_str()));
407 ASSERT_MESSAGE(realised(), "resource not realised");
408 if (m_model == g_nullModel) {
412 return m_model != g_nullModel;
418 const char *moduleName = findModuleName(GetFileTypeRegistry(), MapFormat::Name(), m_type.c_str());
419 if (string_not_empty(moduleName)) {
420 const MapFormat *format = ReferenceAPI_getMapModules().findModule(moduleName);
421 if (format != 0 && MapResource_save(*format, m_model.get(), m_path.c_str(), m_name.c_str())) {
433 ModelCache_flush(m_path.c_str(), m_name.c_str());
437 scene::Node *getNode()
439 //if(m_model != g_nullModel)
441 return m_model.get_pointer();
446 void setNode(scene::Node *node)
448 ModelCache::iterator i = ModelCache_find(m_path.c_str(), m_name.c_str());
449 if (i != g_modelCache.end()) {
450 (*i).value = NodeSmartReference(*node);
452 setModel(NodeSmartReference(*node));
457 void attach(ModuleObserver &observer)
462 m_observers.attach(observer);
465 void detach(ModuleObserver &observer)
468 observer.unrealise();
470 m_observers.detach(observer);
475 return m_unrealised == 0;
480 ASSERT_MESSAGE(m_unrealised != 0, "ModelResource::realise: already realised");
481 if (--m_unrealised == 0) {
482 m_path = rootPath(m_originalName.c_str());
483 m_name = path_make_relative(m_originalName.c_str(), m_path.c_str());
485 //globalOutputStream() << "ModelResource::realise: " << m_path.c_str() << m_name.c_str() << "\n";
487 m_observers.realise();
493 if (++m_unrealised == 1) {
494 m_observers.unrealise();
496 //globalOutputStream() << "ModelResource::unrealise: " << m_path.c_str() << m_name.c_str() << "\n";
503 return Node_getMapFile(m_model) != 0;
508 MapFile *map = Node_getMapFile(m_model);
510 map->setChangedCallback(makeCallbackF(MapChanged));
514 std::time_t modified() const
516 StringOutputStream fullpath(256);
517 fullpath << m_path.c_str() << m_name.c_str();
518 return file_modified(fullpath.c_str());
523 m_modified = modified();
524 MapFile *map = Node_getMapFile(m_model);
530 bool mapSaved() const
532 MapFile *map = Node_getMapFile(m_model);
534 return m_modified == modified() && map->saved();
539 bool isModified() const
541 return ((!string_empty(m_path.c_str()) // had or has an absolute path
542 && m_modified != modified()) // AND disk timestamp changed
543 || !path_equal(rootPath(m_originalName.c_str()), m_path.c_str())); // OR absolute vfs-root changed
556 class HashtableReferenceCache : public ReferenceCache, public ModuleObserver {
557 typedef HashedCache<CopiedString, ModelResource, PathHash, PathEqual> ModelReferences;
558 ModelReferences m_references;
559 std::size_t m_unrealised;
561 class ModelReferencesSnapshot {
562 ModelReferences &m_references;
563 typedef std::list<ModelReferences::iterator> Iterators;
564 Iterators m_iterators;
566 typedef Iterators::iterator iterator;
568 ModelReferencesSnapshot(ModelReferences &references) : m_references(references)
570 for (ModelReferences::iterator i = m_references.begin(); i != m_references.end(); ++i) {
571 m_references.capture(i);
572 m_iterators.push_back(i);
576 ~ModelReferencesSnapshot()
578 for (Iterators::iterator i = m_iterators.begin(); i != m_iterators.end(); ++i) {
579 m_references.release(*i);
585 return m_iterators.begin();
590 return m_iterators.end();
596 typedef ModelReferences::iterator iterator;
598 HashtableReferenceCache() : m_unrealised(1)
604 return m_references.begin();
609 return m_references.end();
614 m_references.clear();
617 Resource *capture(const char *path)
619 //globalOutputStream() << "capture: \"" << path << "\"\n";
620 return m_references.capture(CopiedString(path)).get();
623 void release(const char *path)
625 m_references.release(CopiedString(path));
626 //globalOutputStream() << "release: \"" << path << "\"\n";
629 void setEntityCreator(EntityCreator &entityCreator)
631 g_entityCreator = &entityCreator;
634 bool realised() const
636 return m_unrealised == 0;
641 ASSERT_MESSAGE(m_unrealised != 0, "HashtableReferenceCache::realise: already realised");
642 if (--m_unrealised == 0) {
646 ModelReferencesSnapshot snapshot(m_references);
647 for (ModelReferencesSnapshot::iterator i = snapshot.begin(); i != snapshot.end(); ++i) {
648 ModelReferences::value_type &value = *(*i);
649 if (value.value.count() != 1) {
650 value.value.get()->realise();
659 if (++m_unrealised == 1) {
663 ModelReferencesSnapshot snapshot(m_references);
664 for (ModelReferencesSnapshot::iterator i = snapshot.begin(); i != snapshot.end(); ++i) {
665 ModelReferences::value_type &value = *(*i);
666 if (value.value.count() != 1) {
667 value.value.get()->unrealise();
678 ModelReferencesSnapshot snapshot(m_references);
679 for (ModelReferencesSnapshot::iterator i = snapshot.begin(); i != snapshot.end(); ++i) {
680 ModelResource *resource = (*(*i)).value.get();
681 if (!resource->isMap()) {
689 HashtableReferenceCache g_referenceCache;
693 class ResourceVisitor
696 virtual void visit( const char* name, const char* path, const
700 void SaveReferences()
702 ScopeDisableScreenUpdates disableScreenUpdates("Processing...", "Saving Map");
703 for (HashtableReferenceCache::iterator i = g_referenceCache.begin(); i != g_referenceCache.end(); ++i) {
709 bool References_Saved()
711 for (HashtableReferenceCache::iterator i = g_referenceCache.begin(); i != g_referenceCache.end(); ++i) {
712 scene::Node *node = (*i).value->getNode();
714 MapFile *map = Node_getMapFile(*node);
715 if (map != 0 && !map->saved()) {
723 void RefreshReferences()
725 ScopeDisableScreenUpdates disableScreenUpdates("Processing...", "Refreshing Models");
726 g_referenceCache.refresh();
730 void FlushReferences()
734 g_referenceCache.clear();
737 ReferenceCache &GetReferenceCache()
739 return g_referenceCache;
743 #include "modulesystem/modulesmap.h"
744 #include "modulesystem/singletonmodule.h"
745 #include "modulesystem/moduleregistry.h"
747 class ReferenceDependencies :
748 public GlobalRadiantModuleRef,
749 public GlobalFileSystemModuleRef,
750 public GlobalFiletypesModuleRef {
751 ModelModulesRef m_model_modules;
752 MapModulesRef m_map_modules;
754 ReferenceDependencies() :
755 m_model_modules(GlobalRadiant().getRequiredGameDescriptionKeyValue("modeltypes")),
756 m_map_modules(GlobalRadiant().getRequiredGameDescriptionKeyValue("maptypes"))
760 ModelModules &getModelModules()
762 return m_model_modules.get();
765 MapModules &getMapModules()
767 return m_map_modules.get();
771 class ReferenceAPI : public TypeSystemRef {
772 ReferenceCache *m_reference;
774 typedef ReferenceCache Type;
776 STRING_CONSTANT(Name, "*");
780 g_nullModel = NewNullModel();
782 GlobalFileSystem().attach(g_referenceCache);
784 m_reference = &GetReferenceCache();
789 GlobalFileSystem().detach(g_referenceCache);
791 g_nullModel = g_nullNode;
794 ReferenceCache *getTable()
800 typedef SingletonModule<ReferenceAPI, ReferenceDependencies> ReferenceModule;
801 typedef Static<ReferenceModule> StaticReferenceModule;
802 StaticRegisterModule staticRegisterReference(StaticReferenceModule::instance());
804 ModelModules &ReferenceAPI_getModelModules()
806 return StaticReferenceModule::instance().getDependencies().getModelModules();
809 MapModules &ReferenceAPI_getMapModules()
811 return StaticReferenceModule::instance().getDependencies().getMapModules();