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