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