]> icculus.org git repositories - divverent/netradiant.git/blob - radiant/referencecache.cpp
make Ctrl-Tab center the views in the XYZ view
[divverent/netradiant.git] / radiant / referencecache.cpp
1 /*
2 Copyright (C) 2001-2006, William Joseph.
3 All Rights Reserved.
4
5 This file is part of GtkRadiant.
6
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.
11
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.
16
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
20 */
21
22 #include "referencecache.h"
23
24 #include "debugging/debugging.h"
25
26 #include "iscenegraph.h"
27 #include "iselection.h"
28 #include "iundo.h"
29 #include "imap.h"
30 MapModules& ReferenceAPI_getMapModules();
31 #include "imodel.h"
32 ModelModules& ReferenceAPI_getModelModules();
33 #include "ifilesystem.h"
34 #include "iarchive.h"
35 #include "ifiletypes.h"
36 #include "ireference.h"
37 #include "ientity.h"
38 #include "qerplugin.h"
39
40 #include <list>
41
42 #include "container/cache.h"
43 #include "container/hashfunc.h"
44 #include "os/path.h"
45 #include "stream/textfilestream.h"
46 #include "nullmodel.h"
47 #include "maplib.h"
48 #include "stream/stringstream.h"
49 #include "os/file.h"
50 #include "moduleobserver.h"
51 #include "moduleobservers.h"
52
53 #include "mainframe.h"
54 #include "map.h"
55 #include "filetypes.h"
56
57
58 bool References_Saved();
59
60 void MapChanged()
61 {
62   Map_SetModified(g_map, !References_Saved());
63 }
64
65
66 EntityCreator* g_entityCreator = 0;
67
68 bool MapResource_loadFile(const MapFormat& format, scene::Node& root, const char* filename)
69 {
70   globalOutputStream() << "Open file " << filename << " for read...";
71   TextFileInputStream file(filename);
72   if(!file.failed())
73   {
74     globalOutputStream() << "success\n";
75     ScopeDisableScreenUpdates disableScreenUpdates(path_get_filename_start(filename), "Loading Map");
76     ASSERT_NOTNULL(g_entityCreator);
77     format.readGraph(root, file, *g_entityCreator);
78     return true;
79   }
80   else
81   {
82     globalErrorStream() << "failure\n";
83     return false;
84   }
85 }
86
87 NodeSmartReference MapResource_load(const MapFormat& format, const char* path, const char* name)
88 {
89   NodeSmartReference root(NewMapRoot(name));
90
91   StringOutputStream fullpath(256);
92   fullpath << path << name;
93
94   if(path_is_absolute(fullpath.c_str()))
95   {
96     MapResource_loadFile(format, root, fullpath.c_str());
97   }
98   else
99   {
100     globalErrorStream() << "map path is not fully qualified: " << makeQuoted(fullpath.c_str()) << "\n";
101   }
102
103   return root;
104 }
105
106 bool MapResource_saveFile(const MapFormat& format, scene::Node& root, GraphTraversalFunc traverse, const char* filename)
107 {
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   {
113     globalOutputStream() << "success\n";
114     ScopeDisableScreenUpdates disableScreenUpdates(path_get_filename_start(filename), "Saving Map");
115     format.writeGraph(root, traverse, file);
116     return true;
117   }
118
119   globalErrorStream() << "failure\n";
120   return false;
121 }
122
123 bool file_saveBackup(const char* path)
124 {
125   if(file_writeable(path))
126   {
127           StringOutputStream backup(256);
128     backup << StringRange(path, path_get_extension(path)) << "bak";
129
130     return (!file_exists(backup.c_str()) || file_remove(backup.c_str())) // remove backup
131       && file_move(path, backup.c_str()); // rename current to backup
132   }
133
134   globalErrorStream() << "map path is not writeable: " << makeQuoted(path) << "\n";
135   return false;
136 }
137
138 bool MapResource_save(const MapFormat& format, scene::Node& root, const char* path, const char* name)
139 {
140   StringOutputStream fullpath(256);
141   fullpath << path << name;
142
143   if(path_is_absolute(fullpath.c_str()))
144   {
145     if(!file_exists(fullpath.c_str()) || file_saveBackup(fullpath.c_str()))
146     {
147       return MapResource_saveFile(format, root, Map_Traverse, fullpath.c_str());
148     }
149
150     globalErrorStream() << "failed to save a backup map file: " << makeQuoted(fullpath.c_str()) << "\n";
151     return false;
152   }
153
154   globalErrorStream() << "map path is not fully qualified: " << makeQuoted(fullpath.c_str()) << "\n";
155   return false;
156 }
157
158 namespace
159 {
160   NodeSmartReference g_nullNode(NewNullNode());
161   NodeSmartReference g_nullModel(g_nullNode);
162 }
163
164 class NullModelLoader : public ModelLoader
165 {
166 public:
167   scene::Node& loadModel(ArchiveFile& file)
168   {
169     return g_nullModel;
170   }
171 };
172
173 namespace
174 {
175   NullModelLoader g_NullModelLoader;
176 }
177
178
179 /// \brief Returns the model loader for the model \p type or 0 if the model \p type has no loader module
180 ModelLoader* ModelLoader_forType(const char* type)
181 {
182   const char* moduleName = findModuleName(&GlobalFiletypes(), ModelLoader::Name(), type);
183   if(string_not_empty(moduleName))
184   {
185     ModelLoader* table = ReferenceAPI_getModelModules().findModule(moduleName);
186     if(table != 0)
187     {
188       return table;
189     }
190     else
191     {
192       globalErrorStream() << "ERROR: Model type incorrectly registered: \"" << moduleName << "\"\n";
193       return &g_NullModelLoader;
194     }
195   }
196   return 0;
197 }
198
199 NodeSmartReference ModelResource_load(ModelLoader* loader, const char* name)
200 {
201   ScopeDisableScreenUpdates disableScreenUpdates(path_get_filename_start(name), "Loading Model");
202
203   NodeSmartReference model(g_nullModel);
204
205   {
206     ArchiveFile* file = GlobalFileSystem().openFile(name);
207
208     if(file != 0)
209     {
210       globalOutputStream() << "Loaded Model: \"" << name << "\"\n";
211       model = loader->loadModel(*file);
212       file->release();
213     }
214     else
215     {
216       globalErrorStream() << "Model load failed: \"" << name << "\"\n";
217     }
218   }
219
220   model.get().m_isRoot = true;
221
222   return model;
223 }
224
225
226 inline hash_t path_hash(const char* path, hash_t previous = 0)
227 {
228 #if defined(WIN32)
229   return string_hash_nocase(path, previous);
230 #else // UNIX
231   return string_hash(path, previous);
232 #endif
233 }
234
235 struct PathEqual
236 {
237   bool operator()(const CopiedString& path, const CopiedString& other) const
238   {
239     return path_equal(path.c_str(), other.c_str());
240   }
241 };
242
243 struct PathHash
244 {
245   typedef hash_t hash_type;
246   hash_type operator()(const CopiedString& path) const
247   {
248     return path_hash(path.c_str());
249   }
250 };
251
252 typedef std::pair<CopiedString, CopiedString> ModelKey;
253
254 struct ModelKeyEqual
255 {
256   bool operator()(const ModelKey& key, const ModelKey& other) const
257   {
258     return path_equal(key.first.c_str(), other.first.c_str()) && path_equal(key.second.c_str(), other.second.c_str());
259   }
260 };
261
262 struct ModelKeyHash
263 {
264   typedef hash_t hash_type;
265   hash_type operator()(const ModelKey& key) const
266   {
267     return hash_combine(path_hash(key.first.c_str()), path_hash(key.second.c_str()));
268   }
269 };
270
271 typedef HashTable<ModelKey, NodeSmartReference, ModelKeyHash, ModelKeyEqual> ModelCache;
272 ModelCache g_modelCache;
273 bool g_modelCache_enabled = true;
274
275 ModelCache::iterator ModelCache_find(const char* path, const char* name)
276 {
277   if(g_modelCache_enabled)
278   {
279     return g_modelCache.find(ModelKey(path, name));
280   }
281   return g_modelCache.end();
282 }
283
284 ModelCache::iterator ModelCache_insert(const char* path, const char* name, scene::Node& node)
285 {
286   if(g_modelCache_enabled)
287   {
288     return g_modelCache.insert(ModelKey(path, name), NodeSmartReference(node));
289   }
290   return g_modelCache.insert(ModelKey("", ""), g_nullModel);
291 }
292
293 void ModelCache_flush(const char* path, const char* name)
294 {
295   ModelCache::iterator i = g_modelCache.find(ModelKey(path, name));
296   if(i != g_modelCache.end())
297   {
298     //ASSERT_MESSAGE((*i).value.getCount() == 0, "resource flushed while still in use: " << (*i).key.first.c_str() << (*i).key.second.c_str());
299     g_modelCache.erase(i);
300   }
301 }
302
303 void ModelCache_clear()
304 {
305   g_modelCache_enabled = false;
306   g_modelCache.clear();
307   g_modelCache_enabled = true;
308 }
309
310 NodeSmartReference Model_load(ModelLoader* loader, const char* path, const char* name, const char* type)
311 {
312   if(loader != 0)
313   {
314     return ModelResource_load(loader, name);
315   }
316   else
317   {
318     const char* moduleName = findModuleName(&GlobalFiletypes(), MapFormat::Name(), type);
319     if(string_not_empty(moduleName))
320     {
321       const MapFormat* format = ReferenceAPI_getMapModules().findModule(moduleName);
322       if(format != 0)
323       {
324         return MapResource_load(*format, path, name);
325       }
326       else
327       {
328         globalErrorStream() << "ERROR: Map type incorrectly registered: \"" << moduleName << "\"\n";
329         return g_nullModel;
330       }
331     }
332     else
333     {
334       if(string_not_empty(type))
335       {
336         globalErrorStream() << "Model type not supported: \"" << name << "\"\n";
337       }
338       return g_nullModel;
339     }
340   }
341 }
342
343 namespace
344 {
345   bool g_realised = false;
346
347   // name may be absolute or relative
348   const char* rootPath(const char* name)
349   {
350     return GlobalFileSystem().findRoot(
351       path_is_absolute(name)
352         ? name
353         : GlobalFileSystem().findFile(name)
354     );
355   }
356 }
357
358 struct ModelResource : public Resource
359 {
360   NodeSmartReference m_model;
361   const CopiedString m_originalName;
362   CopiedString m_path;
363   CopiedString m_name;
364   CopiedString m_type;
365   ModelLoader* m_loader;
366   ModuleObservers m_observers;
367   std::time_t m_modified;
368   std::size_t m_unrealised;
369
370   ModelResource(const CopiedString& name) :
371     m_model(g_nullModel),
372     m_originalName(name),
373     m_type(path_get_extension(name.c_str())),
374     m_loader(0),
375     m_modified(0),
376     m_unrealised(1)
377   {
378     m_loader = ModelLoader_forType(m_type.c_str());
379
380     if(g_realised)
381     {
382       realise();
383     }
384   }
385   ~ModelResource()
386   {
387     if(realised())
388     {
389       unrealise();
390     }
391     ASSERT_MESSAGE(!realised(), "ModelResource::~ModelResource: resource reference still realised: " << makeQuoted(m_name.c_str()));
392   }
393   // NOT COPYABLE
394   ModelResource(const ModelResource&);
395   // NOT ASSIGNABLE
396   ModelResource& operator=(const ModelResource&);
397
398   void setModel(const NodeSmartReference& model)
399   {
400     m_model = model;
401   }
402   void clearModel()
403   {
404     m_model = g_nullModel;
405   }
406
407   void loadCached()
408   {
409     if(g_modelCache_enabled)
410     {
411       // cache lookup
412       ModelCache::iterator i = ModelCache_find(m_path.c_str(), m_name.c_str());
413       if(i == g_modelCache.end())
414       {
415         i = ModelCache_insert(
416           m_path.c_str(),
417           m_name.c_str(),
418           Model_load(m_loader, m_path.c_str(), m_name.c_str(), m_type.c_str())
419         );
420       }
421
422       setModel((*i).value);
423     }
424     else
425     {
426       setModel(Model_load(m_loader, m_path.c_str(), m_name.c_str(), m_type.c_str()));
427     }
428   }
429
430   void loadModel()
431   {
432     loadCached();
433     connectMap();
434     mapSave();
435   }
436
437   bool load()
438   {
439     ASSERT_MESSAGE(realised(), "resource not realised");
440     if(m_model == g_nullModel)
441     {
442       loadModel();
443     }
444
445     return m_model != g_nullModel;
446   }
447   bool save()
448   {
449     if(!mapSaved())
450     {
451       const char* moduleName = findModuleName(GetFileTypeRegistry(), MapFormat::Name(), m_type.c_str());
452       if(string_not_empty(moduleName))
453       {
454         const MapFormat* format = ReferenceAPI_getMapModules().findModule(moduleName);
455         if(format != 0 && MapResource_save(*format, m_model.get(), m_path.c_str(), m_name.c_str()))
456         {
457           mapSave();
458           return true;
459         }
460       }
461     }
462     return false;
463   }
464   void flush()
465   {
466     if(realised())
467     {
468       ModelCache_flush(m_path.c_str(), m_name.c_str());
469     }
470   }
471   scene::Node* getNode()
472   {
473     //if(m_model != g_nullModel)
474     {
475       return m_model.get_pointer();
476     }
477     //return 0;
478   }
479   void setNode(scene::Node* node)
480   {
481     ModelCache::iterator i = ModelCache_find(m_path.c_str(), m_name.c_str());
482     if(i != g_modelCache.end())
483     {
484       (*i).value = NodeSmartReference(*node);
485     }
486     setModel(NodeSmartReference(*node));
487
488     connectMap();
489   }
490   void attach(ModuleObserver& observer)
491   {
492     if(realised())
493     {
494       observer.realise();
495     }
496     m_observers.attach(observer);
497   }
498   void detach(ModuleObserver& observer)
499   {
500     if(realised())
501     {
502       observer.unrealise();
503     }
504     m_observers.detach(observer);
505   }
506   bool realised()
507   {
508     return m_unrealised == 0;
509   }
510   void realise()
511   {
512     ASSERT_MESSAGE(m_unrealised != 0, "ModelResource::realise: already realised");
513     if(--m_unrealised == 0)
514     {
515       m_path = rootPath(m_originalName.c_str());
516       m_name = path_make_relative(m_originalName.c_str(), m_path.c_str());
517
518       //globalOutputStream() << "ModelResource::realise: " << m_path.c_str() << m_name.c_str() << "\n";
519
520       m_observers.realise();
521     }
522   }
523   void unrealise()
524   {
525     if(++m_unrealised == 1)
526     {
527       m_observers.unrealise();
528
529       //globalOutputStream() << "ModelResource::unrealise: " << m_path.c_str() << m_name.c_str() << "\n";
530       clearModel();
531     }
532   }
533   bool isMap() const
534   {
535     return Node_getMapFile(m_model) != 0;
536   }
537   void connectMap()
538   {
539     MapFile* map = Node_getMapFile(m_model);
540     if(map != 0)
541     {
542       map->setChangedCallback(FreeCaller<MapChanged>());
543     }
544   }
545   std::time_t modified() const
546   {
547     StringOutputStream fullpath(256);
548     fullpath << m_path.c_str() << m_name.c_str();
549     return file_modified(fullpath.c_str());
550   }
551   void mapSave()
552   {
553     m_modified = modified();
554     MapFile* map = Node_getMapFile(m_model);
555     if(map != 0)
556     {
557       map->save();
558     }
559   }
560   bool mapSaved() const
561   {
562     MapFile* map = Node_getMapFile(m_model);
563     if(map != 0)
564     {
565       return m_modified == modified() && map->saved();
566     }
567     return true;
568   }
569   bool isModified() const
570   {
571     return ((!string_empty(m_path.c_str()) // had or has an absolute path
572         && m_modified != modified()) // AND disk timestamp changed
573       || !path_equal(rootPath(m_originalName.c_str()), m_path.c_str())); // OR absolute vfs-root changed
574   }
575   void refresh()
576   {
577     if(isModified())
578     {
579       flush();
580       unrealise();
581       realise();
582     }
583   }
584 };
585
586 class HashtableReferenceCache : public ReferenceCache, public ModuleObserver
587 {
588   typedef HashedCache<CopiedString, ModelResource, PathHash, PathEqual> ModelReferences;
589   ModelReferences m_references;
590   std::size_t m_unrealised;
591
592   class ModelReferencesSnapshot
593   {
594     ModelReferences& m_references;
595     typedef std::list<ModelReferences::iterator> Iterators;
596     Iterators m_iterators;
597   public:
598     typedef Iterators::iterator iterator;
599     ModelReferencesSnapshot(ModelReferences& references) : m_references(references)
600     {
601       for(ModelReferences::iterator i = m_references.begin(); i != m_references.end(); ++i)
602       {
603         m_references.capture(i);
604         m_iterators.push_back(i);
605       }
606     }
607     ~ModelReferencesSnapshot()
608     {
609       for(Iterators::iterator i = m_iterators.begin(); i != m_iterators.end(); ++i)
610       {
611         m_references.release(*i);
612       }
613     }
614     iterator begin()
615     {
616       return m_iterators.begin();
617     }
618     iterator end()
619     {
620       return m_iterators.end();
621     }
622   };
623
624 public:
625
626   typedef ModelReferences::iterator iterator;
627
628   HashtableReferenceCache() : m_unrealised(1)
629   {
630   }
631
632   iterator begin()
633   {
634     return m_references.begin();
635   }
636   iterator end()
637   {
638     return m_references.end();
639   }
640
641   void clear()
642   {
643     m_references.clear();
644   }
645
646   Resource* capture(const char* path)
647   {
648     //globalOutputStream() << "capture: \"" << path << "\"\n";
649     return m_references.capture(CopiedString(path)).get();
650   }
651   void release(const char* path)
652   {
653     m_references.release(CopiedString(path));
654     //globalOutputStream() << "release: \"" << path << "\"\n";
655   }
656
657   void setEntityCreator(EntityCreator& entityCreator)
658   {
659     g_entityCreator = &entityCreator;
660   }
661
662   bool realised() const
663   {
664     return m_unrealised == 0;
665   }
666   void realise()
667   {
668     ASSERT_MESSAGE(m_unrealised != 0, "HashtableReferenceCache::realise: already realised");
669     if(--m_unrealised == 0)
670     {
671       g_realised = true;
672
673       {
674         ModelReferencesSnapshot snapshot(m_references);
675         for(ModelReferencesSnapshot::iterator i = snapshot.begin(); i != snapshot.end(); ++i)
676         {
677           ModelReferences::value_type& value = *(*i);
678           if(value.value.count() != 1)
679           {
680             value.value.get()->realise();
681           }
682         }
683       }
684     }
685   }
686   void unrealise()
687   {
688     if(++m_unrealised == 1)
689     {
690       g_realised = false;
691
692       {
693         ModelReferencesSnapshot snapshot(m_references);
694         for(ModelReferencesSnapshot::iterator i = snapshot.begin(); i != snapshot.end(); ++i)
695         {
696           ModelReferences::value_type& value = *(*i);
697           if(value.value.count() != 1)
698           {
699             value.value.get()->unrealise();
700           }
701         }
702       }
703
704       ModelCache_clear();
705     }
706   }
707   void refresh()
708   {
709     ModelReferencesSnapshot snapshot(m_references);
710     for(ModelReferencesSnapshot::iterator i = snapshot.begin(); i != snapshot.end(); ++i)
711     {
712       ModelResource* resource = (*(*i)).value.get();
713       if(!resource->isMap())
714       {
715         resource->refresh();
716       }
717     }
718   }
719 };
720
721 namespace
722 {
723   HashtableReferenceCache g_referenceCache;
724 }
725
726 #if 0
727 class ResourceVisitor
728 {
729 public:
730   virtual void visit(const char* name, const char* path, const
731 };
732 #endif
733
734 void SaveReferences()
735 {
736   ScopeDisableScreenUpdates disableScreenUpdates("Processing...", "Saving Map");
737   for(HashtableReferenceCache::iterator i = g_referenceCache.begin(); i != g_referenceCache.end(); ++i)
738   {
739     (*i).value->save();
740   }
741   MapChanged();
742 }
743
744 bool References_Saved()
745 {
746   for(HashtableReferenceCache::iterator i = g_referenceCache.begin(); i != g_referenceCache.end(); ++i)
747   {
748     scene::Node* node = (*i).value->getNode();
749     if(node != 0)
750     {
751       MapFile* map = Node_getMapFile(*node);
752       if(map != 0 && !map->saved())
753       {
754         return false;
755       }
756     }
757   }
758   return true;
759 }
760
761 void RefreshReferences()
762 {
763   ScopeDisableScreenUpdates disableScreenUpdates("Processing...", "Refreshing Models");
764   g_referenceCache.refresh();
765 }
766
767
768 void FlushReferences()
769 {
770   ModelCache_clear();
771
772   g_referenceCache.clear();
773 }
774
775 ReferenceCache& GetReferenceCache()
776 {
777   return g_referenceCache;
778 }
779
780
781 #include "modulesystem/modulesmap.h"
782 #include "modulesystem/singletonmodule.h"
783 #include "modulesystem/moduleregistry.h"
784
785 class ReferenceDependencies :
786   public GlobalRadiantModuleRef,
787   public GlobalFileSystemModuleRef,
788   public GlobalFiletypesModuleRef
789 {
790   ModelModulesRef m_model_modules;
791   MapModulesRef m_map_modules;
792 public:
793   ReferenceDependencies() :
794     m_model_modules(GlobalRadiant().getRequiredGameDescriptionKeyValue("modeltypes")),
795     m_map_modules(GlobalRadiant().getRequiredGameDescriptionKeyValue("maptypes"))
796   {
797   }
798   ModelModules& getModelModules()
799   {
800     return m_model_modules.get();
801   }
802   MapModules& getMapModules()
803   {
804     return m_map_modules.get();
805   }
806 };
807
808 class ReferenceAPI : public TypeSystemRef
809 {
810   ReferenceCache* m_reference;
811 public:
812   typedef ReferenceCache Type;
813   STRING_CONSTANT(Name, "*");
814
815   ReferenceAPI()
816   {
817     g_nullModel = NewNullModel();
818
819     GlobalFileSystem().attach(g_referenceCache);
820
821     m_reference = &GetReferenceCache();
822   }
823   ~ReferenceAPI()
824   {
825     GlobalFileSystem().detach(g_referenceCache);
826
827     g_nullModel = g_nullNode;
828   }
829   ReferenceCache* getTable()
830   {
831     return m_reference;
832   }
833 };
834
835 typedef SingletonModule<ReferenceAPI, ReferenceDependencies> ReferenceModule;
836 typedef Static<ReferenceModule> StaticReferenceModule;
837 StaticRegisterModule staticRegisterReference(StaticReferenceModule::instance());
838
839 ModelModules& ReferenceAPI_getModelModules()
840 {
841   return StaticReferenceModule::instance().getDependencies().getModelModules();
842 }
843 MapModules& ReferenceAPI_getMapModules()
844 {
845   return StaticReferenceModule::instance().getDependencies().getMapModules();
846 }