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