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