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"
62 extern bool g_writeMapComments;
64 bool References_Saved();
68 Map_SetModified(g_map, !References_Saved());
72 EntityCreator *g_entityCreator = 0;
74 bool MapResource_loadFile(const MapFormat &format, scene::Node &root, const char *filename)
76 globalOutputStream() << "Open file " << filename << " for read...";
77 TextFileInputStream file(filename);
79 globalOutputStream() << "success\n";
80 ScopeDisableScreenUpdates disableScreenUpdates(path_get_filename_start(filename), "Loading Map");
81 ASSERT_NOTNULL(g_entityCreator);
82 format.readGraph(root, file, *g_entityCreator);
85 globalErrorStream() << "failure\n";
90 NodeSmartReference MapResource_load(const MapFormat &format, const char *path, const char *name)
92 NodeSmartReference root(NewMapRoot(name));
94 StringOutputStream fullpath(256);
95 fullpath << path << name;
97 if (path_is_absolute(fullpath.c_str())) {
98 MapResource_loadFile(format, root, fullpath.c_str());
100 globalErrorStream() << "map path is not fully qualified: " << makeQuoted(fullpath.c_str()) << "\n";
106 bool MapResource_saveFile(const MapFormat &format, scene::Node &root, GraphTraversalFunc traverse, const char *filename)
108 //ASSERT_MESSAGE(path_is_absolute(filename), "MapResource_saveFile: path is not absolute: " << makeQuoted(filename));
109 globalOutputStream() << "Open file " << filename << " for write...";
110 TextFileOutputStream file(filename);
111 if (!file.failed()) {
112 globalOutputStream() << "success\n";
113 ScopeDisableScreenUpdates disableScreenUpdates(path_get_filename_start(filename), "Saving Map");
114 format.writeGraph(root, traverse, file, g_writeMapComments);
118 globalErrorStream() << "failure\n";
122 bool file_saveBackup(const char *path)
124 if (file_writeable(path)) {
125 StringOutputStream backup(256);
126 backup << StringRange(path, path_get_extension(path)) << "bak";
128 return (!file_exists(backup.c_str()) || file_remove(backup.c_str())) // remove backup
129 && file_move(path, backup.c_str()); // rename current to backup
132 globalErrorStream() << "map path is not writeable: " << makeQuoted(path) << "\n";
136 bool MapResource_save(const MapFormat &format, scene::Node &root, const char *path, const char *name)
138 StringOutputStream fullpath(256);
139 fullpath << path << name;
141 if (path_is_absolute(fullpath.c_str())) {
142 if (!file_exists(fullpath.c_str()) || file_saveBackup(fullpath.c_str())) {
143 return MapResource_saveFile(format, root, Map_Traverse, fullpath.c_str());
146 globalErrorStream() << "failed to save a backup map file: " << makeQuoted(fullpath.c_str()) << "\n";
150 globalErrorStream() << "map path is not fully qualified: " << makeQuoted(fullpath.c_str()) << "\n";
155 NodeSmartReference g_nullNode(NewNullNode());
156 NodeSmartReference g_nullModel(g_nullNode);
159 class NullModelLoader : public ModelLoader {
161 scene::Node &loadModel(ArchiveFile &file)
168 NullModelLoader g_NullModelLoader;
172 /// \brief Returns the model loader for the model \p type or 0 if the model \p type has no loader module
173 ModelLoader *ModelLoader_forType(const char *type)
175 const char *moduleName = findModuleName(&GlobalFiletypes(), ModelLoader::Name(), type);
176 if (string_not_empty(moduleName)) {
177 ModelLoader *table = ReferenceAPI_getModelModules().findModule(moduleName);
181 globalErrorStream() << "ERROR: Model type incorrectly registered: \"" << moduleName << "\"\n";
182 return &g_NullModelLoader;
188 NodeSmartReference ModelResource_load(ModelLoader *loader, const char *name)
190 ScopeDisableScreenUpdates disableScreenUpdates(path_get_filename_start(name), "Loading Model");
192 NodeSmartReference model(g_nullModel);
195 ArchiveFile *file = GlobalFileSystem().openFile(name);
198 globalOutputStream() << "Loaded Model: \"" << name << "\"\n";
199 model = loader->loadModel(*file);
202 globalErrorStream() << "Model load failed: \"" << name << "\"\n";
206 model.get().m_isRoot = true;
212 inline hash_t path_hash(const char *path, hash_t previous = 0)
215 return string_hash_nocase( path, previous );
217 return string_hash(path, previous);
222 bool operator()(const CopiedString &path, const CopiedString &other) const
224 return path_equal(path.c_str(), other.c_str());
229 typedef hash_t hash_type;
231 hash_type operator()(const CopiedString &path) const
233 return path_hash(path.c_str());
237 typedef std::pair<CopiedString, CopiedString> ModelKey;
239 struct ModelKeyEqual {
240 bool operator()(const ModelKey &key, const ModelKey &other) const
242 return path_equal(key.first.c_str(), other.first.c_str()) &&
243 path_equal(key.second.c_str(), other.second.c_str());
247 struct ModelKeyHash {
248 typedef hash_t hash_type;
250 hash_type operator()(const ModelKey &key) const
252 return hash_combine(path_hash(key.first.c_str()), path_hash(key.second.c_str()));
256 typedef HashTable<ModelKey, NodeSmartReference, ModelKeyHash, ModelKeyEqual> ModelCache;
257 ModelCache g_modelCache;
258 bool g_modelCache_enabled = true;
260 ModelCache::iterator ModelCache_find(const char *path, const char *name)
262 if (g_modelCache_enabled) {
263 return g_modelCache.find(ModelKey(path, name));
265 return g_modelCache.end();
268 ModelCache::iterator ModelCache_insert(const char *path, const char *name, scene::Node &node)
270 if (g_modelCache_enabled) {
271 return g_modelCache.insert(ModelKey(path, name), NodeSmartReference(node));
273 return g_modelCache.insert(ModelKey("", ""), g_nullModel);
276 void ModelCache_flush(const char *path, const char *name)
278 ModelCache::iterator i = g_modelCache.find(ModelKey(path, name));
279 if (i != g_modelCache.end()) {
280 //ASSERT_MESSAGE((*i).value.getCount() == 0, "resource flushed while still in use: " << (*i).key.first.c_str() << (*i).key.second.c_str());
281 g_modelCache.erase(i);
285 void ModelCache_clear()
287 g_modelCache_enabled = false;
288 g_modelCache.clear();
289 g_modelCache_enabled = true;
292 NodeSmartReference Model_load(ModelLoader *loader, const char *path, const char *name, const char *type)
295 return ModelResource_load(loader, name);
297 const char *moduleName = findModuleName(&GlobalFiletypes(), MapFormat::Name(), type);
298 if (string_not_empty(moduleName)) {
299 const MapFormat *format = ReferenceAPI_getMapModules().findModule(moduleName);
301 return MapResource_load(*format, path, name);
303 globalErrorStream() << "ERROR: Map type incorrectly registered: \"" << moduleName << "\"\n";
307 if (string_not_empty(type)) {
308 globalErrorStream() << "Model type not supported: \"" << name << "\"\n";
316 bool g_realised = false;
318 // name may be absolute or relative
319 const char *rootPath(const char *name)
321 return GlobalFileSystem().findRoot(
322 path_is_absolute(name)
324 : GlobalFileSystem().findFile(name)
329 struct ModelResource : public Resource {
330 NodeSmartReference m_model;
331 const CopiedString m_originalName;
335 ModelLoader *m_loader;
336 ModuleObservers m_observers;
337 std::time_t m_modified;
338 std::size_t m_unrealised;
340 ModelResource(const CopiedString &name) :
341 m_model(g_nullModel),
342 m_originalName(name),
343 m_type(path_get_extension(name.c_str())),
348 m_loader = ModelLoader_forType(m_type.c_str());
360 ASSERT_MESSAGE(!realised(), "ModelResource::~ModelResource: resource reference still realised: "
361 << makeQuoted(m_name.c_str()));
365 ModelResource(const ModelResource &);
368 ModelResource &operator=(const ModelResource &);
370 void setModel(const NodeSmartReference &model)
377 m_model = g_nullModel;
382 if (g_modelCache_enabled) {
384 ModelCache::iterator i = ModelCache_find(m_path.c_str(), m_name.c_str());
385 if (i == g_modelCache.end()) {
386 i = ModelCache_insert(
389 Model_load(m_loader, m_path.c_str(), m_name.c_str(), m_type.c_str())
393 setModel((*i).value);
395 setModel(Model_load(m_loader, m_path.c_str(), m_name.c_str(), m_type.c_str()));
408 ASSERT_MESSAGE(realised(), "resource not realised");
409 if (m_model == g_nullModel) {
413 return m_model != g_nullModel;
419 const char *moduleName = findModuleName(GetFileTypeRegistry(), MapFormat::Name(), m_type.c_str());
420 if (string_not_empty(moduleName)) {
421 const MapFormat *format = ReferenceAPI_getMapModules().findModule(moduleName);
422 if (format != 0 && MapResource_save(*format, m_model.get(), m_path.c_str(), m_name.c_str())) {
434 ModelCache_flush(m_path.c_str(), m_name.c_str());
438 scene::Node *getNode()
440 //if(m_model != g_nullModel)
442 return m_model.get_pointer();
447 void setNode(scene::Node *node)
449 ModelCache::iterator i = ModelCache_find(m_path.c_str(), m_name.c_str());
450 if (i != g_modelCache.end()) {
451 (*i).value = NodeSmartReference(*node);
453 setModel(NodeSmartReference(*node));
458 void attach(ModuleObserver &observer)
463 m_observers.attach(observer);
466 void detach(ModuleObserver &observer)
469 observer.unrealise();
471 m_observers.detach(observer);
476 return m_unrealised == 0;
481 ASSERT_MESSAGE(m_unrealised != 0, "ModelResource::realise: already realised");
482 if (--m_unrealised == 0) {
483 m_path = rootPath(m_originalName.c_str());
484 m_name = path_make_relative(m_originalName.c_str(), m_path.c_str());
486 //globalOutputStream() << "ModelResource::realise: " << m_path.c_str() << m_name.c_str() << "\n";
488 m_observers.realise();
494 if (++m_unrealised == 1) {
495 m_observers.unrealise();
497 //globalOutputStream() << "ModelResource::unrealise: " << m_path.c_str() << m_name.c_str() << "\n";
504 return Node_getMapFile(m_model) != 0;
509 MapFile *map = Node_getMapFile(m_model);
511 map->setChangedCallback(makeCallbackF(MapChanged));
515 std::time_t modified() const
517 StringOutputStream fullpath(256);
518 fullpath << m_path.c_str() << m_name.c_str();
519 return file_modified(fullpath.c_str());
524 m_modified = modified();
525 MapFile *map = Node_getMapFile(m_model);
531 bool mapSaved() const
533 MapFile *map = Node_getMapFile(m_model);
535 return m_modified == modified() && map->saved();
540 bool isModified() const
542 return ((!string_empty(m_path.c_str()) // had or has an absolute path
543 && m_modified != modified()) // AND disk timestamp changed
544 || !path_equal(rootPath(m_originalName.c_str()), m_path.c_str())); // OR absolute vfs-root changed
557 class HashtableReferenceCache : public ReferenceCache, public ModuleObserver {
558 typedef HashedCache<CopiedString, ModelResource, PathHash, PathEqual> ModelReferences;
559 ModelReferences m_references;
560 std::size_t m_unrealised;
562 class ModelReferencesSnapshot {
563 ModelReferences &m_references;
564 typedef std::list<ModelReferences::iterator> Iterators;
565 Iterators m_iterators;
567 typedef Iterators::iterator iterator;
569 ModelReferencesSnapshot(ModelReferences &references) : m_references(references)
571 for (ModelReferences::iterator i = m_references.begin(); i != m_references.end(); ++i) {
572 m_references.capture(i);
573 m_iterators.push_back(i);
577 ~ModelReferencesSnapshot()
579 for (Iterators::iterator i = m_iterators.begin(); i != m_iterators.end(); ++i) {
580 m_references.release(*i);
586 return m_iterators.begin();
591 return m_iterators.end();
597 typedef ModelReferences::iterator iterator;
599 HashtableReferenceCache() : m_unrealised(1)
605 return m_references.begin();
610 return m_references.end();
615 m_references.clear();
618 Resource *capture(const char *path)
620 //globalOutputStream() << "capture: \"" << path << "\"\n";
621 return m_references.capture(CopiedString(path)).get();
624 void release(const char *path)
626 m_references.release(CopiedString(path));
627 //globalOutputStream() << "release: \"" << path << "\"\n";
630 void setEntityCreator(EntityCreator &entityCreator)
632 g_entityCreator = &entityCreator;
635 bool realised() const
637 return m_unrealised == 0;
642 ASSERT_MESSAGE(m_unrealised != 0, "HashtableReferenceCache::realise: already realised");
643 if (--m_unrealised == 0) {
647 ModelReferencesSnapshot snapshot(m_references);
648 for (ModelReferencesSnapshot::iterator i = snapshot.begin(); i != snapshot.end(); ++i) {
649 ModelReferences::value_type &value = *(*i);
650 if (value.value.count() != 1) {
651 value.value.get()->realise();
660 if (++m_unrealised == 1) {
664 ModelReferencesSnapshot snapshot(m_references);
665 for (ModelReferencesSnapshot::iterator i = snapshot.begin(); i != snapshot.end(); ++i) {
666 ModelReferences::value_type &value = *(*i);
667 if (value.value.count() != 1) {
668 value.value.get()->unrealise();
679 ModelReferencesSnapshot snapshot(m_references);
680 for (ModelReferencesSnapshot::iterator i = snapshot.begin(); i != snapshot.end(); ++i) {
681 ModelResource *resource = (*(*i)).value.get();
682 if (!resource->isMap()) {
690 HashtableReferenceCache g_referenceCache;
694 class ResourceVisitor
697 virtual void visit( const char* name, const char* path, const
701 void SaveReferences()
703 ScopeDisableScreenUpdates disableScreenUpdates("Processing...", "Saving Map");
704 for (HashtableReferenceCache::iterator i = g_referenceCache.begin(); i != g_referenceCache.end(); ++i) {
710 bool References_Saved()
712 for (HashtableReferenceCache::iterator i = g_referenceCache.begin(); i != g_referenceCache.end(); ++i) {
713 scene::Node *node = (*i).value->getNode();
715 MapFile *map = Node_getMapFile(*node);
716 if (map != 0 && !map->saved()) {
724 void RefreshReferences()
726 ScopeDisableScreenUpdates disableScreenUpdates("Processing...", "Refreshing Models");
727 g_referenceCache.refresh();
731 void FlushReferences()
735 g_referenceCache.clear();
738 ReferenceCache &GetReferenceCache()
740 return g_referenceCache;
744 #include "modulesystem/modulesmap.h"
745 #include "modulesystem/singletonmodule.h"
746 #include "modulesystem/moduleregistry.h"
748 class ReferenceDependencies :
749 public GlobalRadiantModuleRef,
750 public GlobalFileSystemModuleRef,
751 public GlobalFiletypesModuleRef {
752 ModelModulesRef m_model_modules;
753 MapModulesRef m_map_modules;
755 ReferenceDependencies() :
756 m_model_modules(GlobalRadiant().getRequiredGameDescriptionKeyValue("modeltypes")),
757 m_map_modules(GlobalRadiant().getRequiredGameDescriptionKeyValue("maptypes"))
761 ModelModules &getModelModules()
763 return m_model_modules.get();
766 MapModules &getMapModules()
768 return m_map_modules.get();
772 class ReferenceAPI : public TypeSystemRef {
773 ReferenceCache *m_reference;
775 typedef ReferenceCache Type;
777 STRING_CONSTANT(Name, "*");
781 g_nullModel = NewNullModel();
783 GlobalFileSystem().attach(g_referenceCache);
785 m_reference = &GetReferenceCache();
790 GlobalFileSystem().detach(g_referenceCache);
792 g_nullModel = g_nullNode;
795 ReferenceCache *getTable()
801 typedef SingletonModule<ReferenceAPI, ReferenceDependencies> ReferenceModule;
802 typedef Static<ReferenceModule> StaticReferenceModule;
803 StaticRegisterModule staticRegisterReference(StaticReferenceModule::instance());
805 ModelModules &ReferenceAPI_getModelModules()
807 return StaticReferenceModule::instance().getDependencies().getModelModules();
810 MapModules &ReferenceAPI_getMapModules()
812 return StaticReferenceModule::instance().getDependencies().getMapModules();