]> de.git.xonotic.org Git - xonotic/netradiant.git/blob - radiant/referencecache.cpp
Merge branch 'NateEag-master-patch-12920' into 'master'
[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         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         globalOutputStream() << "Open file " << filename << " for read...";
75         TextFileInputStream file( filename );
76         if ( !file.failed() ) {
77                 globalOutputStream() << "success\n";
78                 ScopeDisableScreenUpdates disableScreenUpdates( path_get_filename_start( filename ), "Loading Map" );
79                 ASSERT_NOTNULL( g_entityCreator );
80                 format.readGraph( root, file, *g_entityCreator );
81                 return true;
82         }
83         else
84         {
85                 globalErrorStream() << "failure\n";
86                 return false;
87         }
88 }
89
90 NodeSmartReference MapResource_load( const MapFormat& format, const char* path, const char* name ){
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         }
99         else
100         {
101                 globalErrorStream() << "map path is not fully qualified: " << makeQuoted( fullpath.c_str() ) << "\n";
102         }
103
104         return root;
105 }
106
107 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 );
115                 return true;
116         }
117
118         globalErrorStream() << "failure\n";
119         return false;
120 }
121
122 bool file_saveBackup( const char* path ){
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         StringOutputStream fullpath( 256 );
137         fullpath << path << name;
138
139         if ( path_is_absolute( fullpath.c_str() ) ) {
140                 if ( !file_exists( fullpath.c_str() ) || file_saveBackup( fullpath.c_str() ) ) {
141                         return MapResource_saveFile( format, root, Map_Traverse, fullpath.c_str() );
142                 }
143
144                 globalErrorStream() << "failed to save a backup map file: " << makeQuoted( fullpath.c_str() ) << "\n";
145                 return false;
146         }
147
148         globalErrorStream() << "map path is not fully qualified: " << makeQuoted( fullpath.c_str() ) << "\n";
149         return false;
150 }
151
152 namespace
153 {
154 NodeSmartReference g_nullNode( NewNullNode() );
155 NodeSmartReference g_nullModel( g_nullNode );
156 }
157
158 class NullModelLoader : public ModelLoader
159 {
160 public:
161 scene::Node& loadModel( ArchiveFile& file ){
162         return g_nullModel;
163 }
164 };
165
166 namespace
167 {
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         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                 }
180                 else
181                 {
182                         globalErrorStream() << "ERROR: Model type incorrectly registered: \"" << moduleName << "\"\n";
183                         return &g_NullModelLoader;
184                 }
185         }
186         return 0;
187 }
188
189 NodeSmartReference ModelResource_load( ModelLoader* loader, const char* name ){
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                 }
202                 else
203                 {
204                         globalErrorStream() << "Model load failed: \"" << name << "\"\n";
205                 }
206         }
207
208         model.get().m_isRoot = true;
209
210         return model;
211 }
212
213
214 inline hash_t path_hash( const char* path, hash_t previous = 0 ){
215 #if GDEF_OS_WINDOWS
216         return string_hash_nocase( path, previous );
217 #else // UNIX
218         return string_hash( path, previous );
219 #endif
220 }
221
222 struct PathEqual
223 {
224         bool operator()( const CopiedString& path, const CopiedString& other ) const {
225                 return path_equal( path.c_str(), other.c_str() );
226         }
227 };
228
229 struct PathHash
230 {
231         typedef hash_t hash_type;
232
233         hash_type operator()( const CopiedString& path ) const {
234                 return path_hash( path.c_str() );
235         }
236 };
237
238 typedef std::pair<CopiedString, CopiedString> ModelKey;
239
240 struct ModelKeyEqual
241 {
242         bool operator()( const ModelKey& key, const ModelKey& other ) const {
243                 return path_equal( key.first.c_str(), other.first.c_str() ) && path_equal( key.second.c_str(), other.second.c_str() );
244         }
245 };
246
247 struct ModelKeyHash
248 {
249         typedef hash_t hash_type;
250
251         hash_type operator()( const ModelKey& key ) const {
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         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         if ( g_modelCache_enabled ) {
269                 return g_modelCache.insert( ModelKey( path, name ), NodeSmartReference( node ) );
270         }
271         return g_modelCache.insert( ModelKey( "", "" ), g_nullModel );
272 }
273
274 void ModelCache_flush( const char* path, const char* name ){
275         ModelCache::iterator i = g_modelCache.find( ModelKey( path, name ) );
276         if ( i != g_modelCache.end() ) {
277                 //ASSERT_MESSAGE((*i).value.getCount() == 0, "resource flushed while still in use: " << (*i).key.first.c_str() << (*i).key.second.c_str());
278                 g_modelCache.erase( i );
279         }
280 }
281
282 void ModelCache_clear(){
283         g_modelCache_enabled = false;
284         g_modelCache.clear();
285         g_modelCache_enabled = true;
286 }
287
288 NodeSmartReference Model_load( ModelLoader* loader, const char* path, const char* name, const char* type ){
289         if ( loader != 0 ) {
290                 return ModelResource_load( loader, name );
291         }
292         else
293         {
294                 const char* moduleName = findModuleName( &GlobalFiletypes(), MapFormat::Name(), type );
295                 if ( string_not_empty( moduleName ) ) {
296                         const MapFormat* format = ReferenceAPI_getMapModules().findModule( moduleName );
297                         if ( format != 0 ) {
298                                 return MapResource_load( *format, path, name );
299                         }
300                         else
301                         {
302                                 globalErrorStream() << "ERROR: Map type incorrectly registered: \"" << moduleName << "\"\n";
303                                 return g_nullModel;
304                         }
305                 }
306                 else
307                 {
308                         if ( string_not_empty( type ) ) {
309                                 globalErrorStream() << "Model type not supported: \"" << name << "\"\n";
310                         }
311                         return g_nullModel;
312                 }
313         }
314 }
315
316 namespace
317 {
318 bool g_realised = false;
319
320 // name may be absolute or relative
321 const char* rootPath( const char* name ){
322         return GlobalFileSystem().findRoot(
323                            path_is_absolute( name )
324                            ? name
325                            : GlobalFileSystem().findFile( name )
326                            );
327 }
328 }
329
330 struct ModelResource : public Resource
331 {
332         NodeSmartReference m_model;
333         const CopiedString m_originalName;
334         CopiedString m_path;
335         CopiedString m_name;
336         CopiedString m_type;
337         ModelLoader* m_loader;
338         ModuleObservers m_observers;
339         std::time_t m_modified;
340         std::size_t m_unrealised;
341
342         ModelResource( const CopiedString& name ) :
343                 m_model( g_nullModel ),
344                 m_originalName( name ),
345                 m_type( path_get_extension( name.c_str() ) ),
346                 m_loader( 0 ),
347                 m_modified( 0 ),
348                 m_unrealised( 1 ){
349                 m_loader = ModelLoader_forType( m_type.c_str() );
350
351                 if ( g_realised ) {
352                         realise();
353                 }
354         }
355
356         ~ModelResource(){
357                 if ( realised() ) {
358                         unrealise();
359                 }
360                 ASSERT_MESSAGE( !realised(), "ModelResource::~ModelResource: resource reference still realised: " << 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                 m_model = model;
371         }
372
373         void clearModel(){
374                 m_model = g_nullModel;
375         }
376
377         void loadCached(){
378                 if ( g_modelCache_enabled ) {
379                         // cache lookup
380                         ModelCache::iterator i = ModelCache_find( m_path.c_str(), m_name.c_str() );
381                         if ( i == g_modelCache.end() ) {
382                                 i = ModelCache_insert(
383                                         m_path.c_str(),
384                                         m_name.c_str(),
385                                         Model_load( m_loader, m_path.c_str(), m_name.c_str(), m_type.c_str() )
386                                         );
387                         }
388
389                         setModel( ( *i ).value );
390                 }
391                 else
392                 {
393                         setModel( Model_load( m_loader, m_path.c_str(), m_name.c_str(), m_type.c_str() ) );
394                 }
395         }
396
397         void loadModel(){
398                 loadCached();
399                 connectMap();
400                 mapSave();
401         }
402
403         bool load(){
404                 ASSERT_MESSAGE( realised(), "resource not realised" );
405                 if ( m_model == g_nullModel ) {
406                         loadModel();
407                 }
408
409                 return m_model != g_nullModel;
410         }
411
412         bool save(){
413                 if ( !mapSaved() ) {
414                         const char* moduleName = findModuleName( GetFileTypeRegistry(), MapFormat::Name(), m_type.c_str() );
415                         if ( string_not_empty( moduleName ) ) {
416                                 const MapFormat* format = ReferenceAPI_getMapModules().findModule( moduleName );
417                                 if ( format != 0 && MapResource_save( *format, m_model.get(), m_path.c_str(), m_name.c_str() ) ) {
418                                         mapSave();
419                                         return true;
420                                 }
421                         }
422                 }
423                 return false;
424         }
425
426         void flush(){
427                 if ( realised() ) {
428                         ModelCache_flush( m_path.c_str(), m_name.c_str() );
429                 }
430         }
431
432         scene::Node* getNode(){
433                 //if(m_model != g_nullModel)
434                 {
435                         return m_model.get_pointer();
436                 }
437                 //return 0;
438         }
439
440         void setNode( scene::Node* node ){
441                 ModelCache::iterator i = ModelCache_find( m_path.c_str(), m_name.c_str() );
442                 if ( i != g_modelCache.end() ) {
443                         ( *i ).value = NodeSmartReference( *node );
444                 }
445                 setModel( NodeSmartReference( *node ) );
446
447                 connectMap();
448         }
449
450         void attach( ModuleObserver& observer ){
451                 if ( realised() ) {
452                         observer.realise();
453                 }
454                 m_observers.attach( observer );
455         }
456
457         void detach( ModuleObserver& observer ){
458                 if ( realised() ) {
459                         observer.unrealise();
460                 }
461                 m_observers.detach( observer );
462         }
463
464         bool realised(){
465                 return m_unrealised == 0;
466         }
467
468         void realise(){
469                 ASSERT_MESSAGE( m_unrealised != 0, "ModelResource::realise: already realised" );
470                 if ( --m_unrealised == 0 ) {
471                         m_path = rootPath( m_originalName.c_str() );
472                         m_name = path_make_relative( m_originalName.c_str(), m_path.c_str() );
473
474                         //globalOutputStream() << "ModelResource::realise: " << m_path.c_str() << m_name.c_str() << "\n";
475
476                         m_observers.realise();
477                 }
478         }
479
480         void unrealise(){
481                 if ( ++m_unrealised == 1 ) {
482                         m_observers.unrealise();
483
484                         //globalOutputStream() << "ModelResource::unrealise: " << m_path.c_str() << m_name.c_str() << "\n";
485                         clearModel();
486                 }
487         }
488
489         bool isMap() const {
490                 return Node_getMapFile( m_model ) != 0;
491         }
492
493         void connectMap(){
494                 MapFile* map = Node_getMapFile( m_model );
495                 if ( map != 0 ) {
496                         map->setChangedCallback( makeCallbackF(MapChanged) );
497                 }
498         }
499
500         std::time_t modified() const {
501                 StringOutputStream fullpath( 256 );
502                 fullpath << m_path.c_str() << m_name.c_str();
503                 return file_modified( fullpath.c_str() );
504         }
505
506         void mapSave(){
507                 m_modified = modified();
508                 MapFile* map = Node_getMapFile( m_model );
509                 if ( map != 0 ) {
510                         map->save();
511                 }
512         }
513
514         bool mapSaved() const {
515                 MapFile* map = Node_getMapFile( m_model );
516                 if ( map != 0 ) {
517                         return m_modified == modified() && map->saved();
518                 }
519                 return true;
520         }
521
522         bool isModified() const {
523                 return ( ( !string_empty( m_path.c_str() ) // had or has an absolute path
524                                    && m_modified != modified() ) // AND disk timestamp changed
525                                  || !path_equal( rootPath( m_originalName.c_str() ), m_path.c_str() ) ); // OR absolute vfs-root changed
526         }
527
528         void refresh(){
529                 if ( isModified() ) {
530                         flush();
531                         unrealise();
532                         realise();
533                 }
534         }
535 };
536
537 class HashtableReferenceCache : public ReferenceCache, public ModuleObserver
538 {
539 typedef HashedCache<CopiedString, ModelResource, PathHash, PathEqual> ModelReferences;
540 ModelReferences m_references;
541 std::size_t m_unrealised;
542
543 class ModelReferencesSnapshot
544 {
545 ModelReferences& m_references;
546 typedef std::list<ModelReferences::iterator> Iterators;
547 Iterators m_iterators;
548 public:
549 typedef Iterators::iterator iterator;
550
551 ModelReferencesSnapshot( ModelReferences& references ) : m_references( references ){
552         for ( ModelReferences::iterator i = m_references.begin(); i != m_references.end(); ++i )
553         {
554                 m_references.capture( i );
555                 m_iterators.push_back( i );
556         }
557 }
558
559 ~ModelReferencesSnapshot(){
560         for ( Iterators::iterator i = m_iterators.begin(); i != m_iterators.end(); ++i )
561         {
562                 m_references.release( *i );
563         }
564 }
565
566 iterator begin(){
567         return m_iterators.begin();
568 }
569
570 iterator end(){
571         return m_iterators.end();
572 }
573 };
574
575 public:
576
577 typedef ModelReferences::iterator iterator;
578
579 HashtableReferenceCache() : m_unrealised( 1 ){
580 }
581
582 iterator begin(){
583         return m_references.begin();
584 }
585
586 iterator end(){
587         return m_references.end();
588 }
589
590 void clear(){
591         m_references.clear();
592 }
593
594 Resource* capture( const char* path ){
595         //globalOutputStream() << "capture: \"" << path << "\"\n";
596         return m_references.capture( CopiedString( path ) ).get();
597 }
598
599 void release( const char* path ){
600         m_references.release( CopiedString( path ) );
601         //globalOutputStream() << "release: \"" << path << "\"\n";
602 }
603
604 void setEntityCreator( EntityCreator& entityCreator ){
605         g_entityCreator = &entityCreator;
606 }
607
608 bool realised() const {
609         return m_unrealised == 0;
610 }
611
612 void realise(){
613         ASSERT_MESSAGE( m_unrealised != 0, "HashtableReferenceCache::realise: already realised" );
614         if ( --m_unrealised == 0 ) {
615                 g_realised = true;
616
617                 {
618                         ModelReferencesSnapshot snapshot( m_references );
619                         for ( ModelReferencesSnapshot::iterator i = snapshot.begin(); i != snapshot.end(); ++i )
620                         {
621                                 ModelReferences::value_type& value = *( *i );
622                                 if ( value.value.count() != 1 ) {
623                                         value.value.get()->realise();
624                                 }
625                         }
626                 }
627         }
628 }
629
630 void unrealise(){
631         if ( ++m_unrealised == 1 ) {
632                 g_realised = false;
633
634                 {
635                         ModelReferencesSnapshot snapshot( m_references );
636                         for ( ModelReferencesSnapshot::iterator i = snapshot.begin(); i != snapshot.end(); ++i )
637                         {
638                                 ModelReferences::value_type& value = *( *i );
639                                 if ( value.value.count() != 1 ) {
640                                         value.value.get()->unrealise();
641                                 }
642                         }
643                 }
644
645                 ModelCache_clear();
646         }
647 }
648
649 void refresh(){
650         ModelReferencesSnapshot snapshot( m_references );
651         for ( ModelReferencesSnapshot::iterator i = snapshot.begin(); i != snapshot.end(); ++i )
652         {
653                 ModelResource* resource = ( *( *i ) ).value.get();
654                 if ( !resource->isMap() ) {
655                         resource->refresh();
656                 }
657         }
658 }
659 };
660
661 namespace
662 {
663 HashtableReferenceCache g_referenceCache;
664 }
665
666 #if 0
667 class ResourceVisitor
668 {
669 public:
670 virtual void visit( const char* name, const char* path, const
671                                         };
672 #endif
673
674 void SaveReferences(){
675         ScopeDisableScreenUpdates disableScreenUpdates( "Processing...", "Saving Map" );
676         for ( HashtableReferenceCache::iterator i = g_referenceCache.begin(); i != g_referenceCache.end(); ++i )
677         {
678                 ( *i ).value->save();
679         }
680         MapChanged();
681 }
682
683 bool References_Saved(){
684         for ( HashtableReferenceCache::iterator i = g_referenceCache.begin(); i != g_referenceCache.end(); ++i )
685         {
686                 scene::Node* node = ( *i ).value->getNode();
687                 if ( node != 0 ) {
688                         MapFile* map = Node_getMapFile( *node );
689                         if ( map != 0 && !map->saved() ) {
690                                 return false;
691                         }
692                 }
693         }
694         return true;
695 }
696
697 void RefreshReferences(){
698         ScopeDisableScreenUpdates disableScreenUpdates( "Processing...", "Refreshing Models" );
699         g_referenceCache.refresh();
700 }
701
702
703 void FlushReferences(){
704         ModelCache_clear();
705
706         g_referenceCache.clear();
707 }
708
709 ReferenceCache& GetReferenceCache(){
710         return g_referenceCache;
711 }
712
713
714 #include "modulesystem/modulesmap.h"
715 #include "modulesystem/singletonmodule.h"
716 #include "modulesystem/moduleregistry.h"
717
718 class ReferenceDependencies :
719         public GlobalRadiantModuleRef,
720         public GlobalFileSystemModuleRef,
721         public GlobalFiletypesModuleRef
722 {
723 ModelModulesRef m_model_modules;
724 MapModulesRef m_map_modules;
725 public:
726 ReferenceDependencies() :
727         m_model_modules( GlobalRadiant().getRequiredGameDescriptionKeyValue( "modeltypes" ) ),
728         m_map_modules( GlobalRadiant().getRequiredGameDescriptionKeyValue( "maptypes" ) )
729 {
730 }
731
732 ModelModules& getModelModules(){
733         return m_model_modules.get();
734 }
735
736 MapModules& getMapModules(){
737         return m_map_modules.get();
738 }
739 };
740
741 class ReferenceAPI : public TypeSystemRef
742 {
743 ReferenceCache* m_reference;
744 public:
745 typedef ReferenceCache Type;
746
747 STRING_CONSTANT( Name, "*" );
748
749 ReferenceAPI(){
750         g_nullModel = NewNullModel();
751
752         GlobalFileSystem().attach( g_referenceCache );
753
754         m_reference = &GetReferenceCache();
755 }
756
757 ~ReferenceAPI(){
758         GlobalFileSystem().detach( g_referenceCache );
759
760         g_nullModel = g_nullNode;
761 }
762
763 ReferenceCache* getTable(){
764         return m_reference;
765 }
766 };
767
768 typedef SingletonModule<ReferenceAPI, ReferenceDependencies> ReferenceModule;
769 typedef Static<ReferenceModule> StaticReferenceModule;
770 StaticRegisterModule staticRegisterReference( StaticReferenceModule::instance() );
771
772 ModelModules& ReferenceAPI_getModelModules(){
773         return StaticReferenceModule::instance().getDependencies().getModelModules();
774 }
775
776 MapModules& ReferenceAPI_getMapModules(){
777         return StaticReferenceModule::instance().getDependencies().getMapModules( );
778 }