2 Copyright (C) 2001-2006, William Joseph.
5 This file is part of GtkRadiant.
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.
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.
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
22 #include "referencecache.h"
23 #include "globaldefs.h"
25 #include "debugging/debugging.h"
27 #include "iscenegraph.h"
28 #include "iselection.h"
32 MapModules& ReferenceAPI_getMapModules();
36 ModelModules& ReferenceAPI_getModelModules();
38 #include "ifilesystem.h"
40 #include "ifiletypes.h"
41 #include "ireference.h"
43 #include "qerplugin.h"
47 #include "container/cache.h"
48 #include "container/hashfunc.h"
50 #include "stream/textfilestream.h"
51 #include "nullmodel.h"
53 #include "stream/stringstream.h"
55 #include "moduleobserver.h"
56 #include "moduleobservers.h"
58 #include "mainframe.h"
60 #include "filetypes.h"
62 extern bool g_writeMapComments;
64 bool References_Saved();
67 Map_SetModified( g_map, !References_Saved() );
71 EntityCreator* g_entityCreator = 0;
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 );
85 globalErrorStream() << "failure\n";
90 NodeSmartReference MapResource_load( const MapFormat& format, const char* path, const char* name ){
91 NodeSmartReference root( NewMapRoot( name ) );
93 StringOutputStream fullpath( 256 );
94 fullpath << path << name;
96 if ( path_is_absolute( fullpath.c_str() ) ) {
97 MapResource_loadFile( format, root, fullpath.c_str() );
101 globalErrorStream() << "map path is not fully qualified: " << makeQuoted( fullpath.c_str() ) << "\n";
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 );
118 globalErrorStream() << "failure\n";
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";
128 // NT symlinks are not supported yet.
129 return ( !file_exists( backup.c_str() ) || file_remove( backup.c_str() ) ) // remove backup
130 && file_move( path, backup.c_str() ); // rename current to backup
132 // POSIX symlinks are supported.
133 return file_move( path, backup.c_str() ); // rename current to backup
137 globalErrorStream() << "map path is not writeable: " << makeQuoted( path ) << "\n";
141 bool MapResource_save( const MapFormat& format, scene::Node& root, const char* path, const char* name ){
142 StringOutputStream fullpath( 256 );
143 fullpath << path << name;
145 if ( path_is_absolute( fullpath.c_str() ) ) {
147 // NT symlinks are not supported yet.
148 if ( !file_exists( fullpath.c_str() ) || file_saveBackup( fullpath.c_str() ) ) {
150 // POSIX symlinks are supported.
151 /* We don't want a backup + rename operation if the .map file is
152 * a symlink. Otherwise we'll break the user's careful symlink setup.
153 * Just overwrite the original file. Assume the user has versioning. */
156 if ( lstat(fullpath.c_str(), &st) == 0 ) {
157 make_backup = true; // file exists
158 if ( (st.st_mode & S_IFMT) == S_IFLNK ) {
159 make_backup = false; // .. but it is a symlink
162 make_backup = false; // nothing to move
165 if ( !make_backup || file_saveBackup( fullpath.c_str() ) ) {
167 return MapResource_saveFile( format, root, Map_Traverse, fullpath.c_str() );
170 globalErrorStream() << "failed to save a backup map file: " << makeQuoted( fullpath.c_str() ) << "\n";
174 globalErrorStream() << "map path is not fully qualified: " << makeQuoted( fullpath.c_str() ) << "\n";
180 NodeSmartReference g_nullNode( NewNullNode() );
181 NodeSmartReference g_nullModel( g_nullNode );
184 class NullModelLoader : public ModelLoader
187 scene::Node& loadModel( ArchiveFile& file ){
194 NullModelLoader g_NullModelLoader;
198 /// \brief Returns the model loader for the model \p type or 0 if the model \p type has no loader module
199 ModelLoader* ModelLoader_forType( const char* type ){
200 const char* moduleName = findModuleName( &GlobalFiletypes(), ModelLoader::Name(), type );
201 if ( string_not_empty( moduleName ) ) {
202 ModelLoader* table = ReferenceAPI_getModelModules().findModule( moduleName );
208 globalErrorStream() << "ERROR: Model type incorrectly registered: \"" << moduleName << "\"\n";
209 return &g_NullModelLoader;
215 NodeSmartReference ModelResource_load( ModelLoader* loader, const char* name ){
216 ScopeDisableScreenUpdates disableScreenUpdates( path_get_filename_start( name ), "Loading Model" );
218 NodeSmartReference model( g_nullModel );
221 ArchiveFile* file = GlobalFileSystem().openFile( name );
224 globalOutputStream() << "Loaded Model: \"" << name << "\"\n";
225 model = loader->loadModel( *file );
230 globalErrorStream() << "Model load failed: \"" << name << "\"\n";
234 model.get().m_isRoot = true;
240 inline hash_t path_hash( const char* path, hash_t previous = 0 ){
242 return string_hash_nocase( path, previous );
244 return string_hash( path, previous );
250 bool operator()( const CopiedString& path, const CopiedString& other ) const {
251 return path_equal( path.c_str(), other.c_str() );
257 typedef hash_t hash_type;
259 hash_type operator()( const CopiedString& path ) const {
260 return path_hash( path.c_str() );
264 typedef std::pair<CopiedString, CopiedString> ModelKey;
268 bool operator()( const ModelKey& key, const ModelKey& other ) const {
269 return path_equal( key.first.c_str(), other.first.c_str() ) && path_equal( key.second.c_str(), other.second.c_str() );
275 typedef hash_t hash_type;
277 hash_type operator()( const ModelKey& key ) const {
278 return hash_combine( path_hash( key.first.c_str() ), path_hash( key.second.c_str() ) );
282 typedef HashTable<ModelKey, NodeSmartReference, ModelKeyHash, ModelKeyEqual> ModelCache;
283 ModelCache g_modelCache;
284 bool g_modelCache_enabled = true;
286 ModelCache::iterator ModelCache_find( const char* path, const char* name ){
287 if ( g_modelCache_enabled ) {
288 return g_modelCache.find( ModelKey( path, name ) );
290 return g_modelCache.end();
293 ModelCache::iterator ModelCache_insert( const char* path, const char* name, scene::Node& node ){
294 if ( g_modelCache_enabled ) {
295 return g_modelCache.insert( ModelKey( path, name ), NodeSmartReference( node ) );
297 return g_modelCache.insert( ModelKey( "", "" ), g_nullModel );
300 void ModelCache_flush( const char* path, const char* name ){
301 ModelCache::iterator i = g_modelCache.find( ModelKey( path, name ) );
302 if ( i != g_modelCache.end() ) {
303 //ASSERT_MESSAGE((*i).value.getCount() == 0, "resource flushed while still in use: " << (*i).key.first.c_str() << (*i).key.second.c_str());
304 g_modelCache.erase( i );
308 void ModelCache_clear(){
309 g_modelCache_enabled = false;
310 g_modelCache.clear();
311 g_modelCache_enabled = true;
314 NodeSmartReference Model_load( ModelLoader* loader, const char* path, const char* name, const char* type ){
316 return ModelResource_load( loader, name );
320 const char* moduleName = findModuleName( &GlobalFiletypes(), MapFormat::Name(), type );
321 if ( string_not_empty( moduleName ) ) {
322 const MapFormat* format = ReferenceAPI_getMapModules().findModule( moduleName );
324 return MapResource_load( *format, path, name );
328 globalErrorStream() << "ERROR: Map type incorrectly registered: \"" << moduleName << "\"\n";
334 if ( string_not_empty( type ) ) {
335 globalErrorStream() << "Model type not supported: \"" << name << "\"\n";
344 bool g_realised = false;
346 // name may be absolute or relative
347 const char* rootPath( const char* name ){
348 return GlobalFileSystem().findRoot(
349 path_is_absolute( name )
351 : GlobalFileSystem().findFile( name )
356 struct ModelResource : public Resource
358 NodeSmartReference m_model;
359 const CopiedString m_originalName;
363 ModelLoader* m_loader;
364 ModuleObservers m_observers;
365 std::time_t m_modified;
366 std::size_t m_unrealised;
368 ModelResource( const CopiedString& name ) :
369 m_model( g_nullModel ),
370 m_originalName( name ),
371 m_type( path_get_extension( name.c_str() ) ),
375 m_loader = ModelLoader_forType( m_type.c_str() );
386 ASSERT_MESSAGE( !realised(), "ModelResource::~ModelResource: resource reference still realised: " << makeQuoted( m_name.c_str() ) );
390 ModelResource( const ModelResource& );
393 ModelResource& operator=( const ModelResource& );
395 void setModel( const NodeSmartReference& model ){
400 m_model = g_nullModel;
404 if ( g_modelCache_enabled ) {
406 ModelCache::iterator i = ModelCache_find( m_path.c_str(), m_name.c_str() );
407 if ( i == g_modelCache.end() ) {
408 i = ModelCache_insert(
411 Model_load( m_loader, m_path.c_str(), m_name.c_str(), m_type.c_str() )
415 setModel( ( *i ).value );
419 setModel( Model_load( m_loader, m_path.c_str(), m_name.c_str(), m_type.c_str() ) );
430 ASSERT_MESSAGE( realised(), "resource not realised" );
431 if ( m_model == g_nullModel ) {
435 return m_model != g_nullModel;
440 const char* moduleName = findModuleName( GetFileTypeRegistry(), MapFormat::Name(), m_type.c_str() );
441 if ( string_not_empty( moduleName ) ) {
442 const MapFormat* format = ReferenceAPI_getMapModules().findModule( moduleName );
443 if ( format != 0 && MapResource_save( *format, m_model.get(), m_path.c_str(), m_name.c_str() ) ) {
454 ModelCache_flush( m_path.c_str(), m_name.c_str() );
458 scene::Node* getNode(){
459 //if(m_model != g_nullModel)
461 return m_model.get_pointer();
466 void setNode( scene::Node* node ){
467 ModelCache::iterator i = ModelCache_find( m_path.c_str(), m_name.c_str() );
468 if ( i != g_modelCache.end() ) {
469 ( *i ).value = NodeSmartReference( *node );
471 setModel( NodeSmartReference( *node ) );
476 void attach( ModuleObserver& observer ){
480 m_observers.attach( observer );
483 void detach( ModuleObserver& observer ){
485 observer.unrealise();
487 m_observers.detach( observer );
491 return m_unrealised == 0;
495 ASSERT_MESSAGE( m_unrealised != 0, "ModelResource::realise: already realised" );
496 if ( --m_unrealised == 0 ) {
497 m_path = rootPath( m_originalName.c_str() );
498 m_name = path_make_relative( m_originalName.c_str(), m_path.c_str() );
500 //globalOutputStream() << "ModelResource::realise: " << m_path.c_str() << m_name.c_str() << "\n";
502 m_observers.realise();
507 if ( ++m_unrealised == 1 ) {
508 m_observers.unrealise();
510 //globalOutputStream() << "ModelResource::unrealise: " << m_path.c_str() << m_name.c_str() << "\n";
516 return Node_getMapFile( m_model ) != 0;
520 MapFile* map = Node_getMapFile( m_model );
522 map->setChangedCallback( makeCallbackF(MapChanged) );
526 std::time_t modified() const {
527 StringOutputStream fullpath( 256 );
528 fullpath << m_path.c_str() << m_name.c_str();
529 return file_modified( fullpath.c_str() );
533 m_modified = modified();
534 MapFile* map = Node_getMapFile( m_model );
540 bool mapSaved() const {
541 MapFile* map = Node_getMapFile( m_model );
543 return m_modified == modified() && map->saved();
548 bool isModified() const {
549 return ( ( !string_empty( m_path.c_str() ) // had or has an absolute path
550 && m_modified != modified() ) // AND disk timestamp changed
551 || !path_equal( rootPath( m_originalName.c_str() ), m_path.c_str() ) ); // OR absolute vfs-root changed
555 if ( isModified() ) {
563 class HashtableReferenceCache : public ReferenceCache, public ModuleObserver
565 typedef HashedCache<CopiedString, ModelResource, PathHash, PathEqual> ModelReferences;
566 ModelReferences m_references;
567 std::size_t m_unrealised;
569 class ModelReferencesSnapshot
571 ModelReferences& m_references;
572 typedef std::list<ModelReferences::iterator> Iterators;
573 Iterators m_iterators;
575 typedef Iterators::iterator iterator;
577 ModelReferencesSnapshot( ModelReferences& references ) : m_references( references ){
578 for ( ModelReferences::iterator i = m_references.begin(); i != m_references.end(); ++i )
580 m_references.capture( i );
581 m_iterators.push_back( i );
585 ~ModelReferencesSnapshot(){
586 for ( Iterators::iterator i = m_iterators.begin(); i != m_iterators.end(); ++i )
588 m_references.release( *i );
593 return m_iterators.begin();
597 return m_iterators.end();
603 typedef ModelReferences::iterator iterator;
605 HashtableReferenceCache() : m_unrealised( 1 ){
609 return m_references.begin();
613 return m_references.end();
617 m_references.clear();
620 Resource* capture( const char* path ){
621 //globalOutputStream() << "capture: \"" << path << "\"\n";
622 return m_references.capture( CopiedString( path ) ).get();
625 void release( const char* path ){
626 m_references.release( CopiedString( path ) );
627 //globalOutputStream() << "release: \"" << path << "\"\n";
630 void setEntityCreator( EntityCreator& entityCreator ){
631 g_entityCreator = &entityCreator;
634 bool realised() const {
635 return m_unrealised == 0;
639 ASSERT_MESSAGE( m_unrealised != 0, "HashtableReferenceCache::realise: already realised" );
640 if ( --m_unrealised == 0 ) {
644 ModelReferencesSnapshot snapshot( m_references );
645 for ( ModelReferencesSnapshot::iterator i = snapshot.begin(); i != snapshot.end(); ++i )
647 ModelReferences::value_type& value = *( *i );
648 if ( value.value.count() != 1 ) {
649 value.value.get()->realise();
657 if ( ++m_unrealised == 1 ) {
661 ModelReferencesSnapshot snapshot( m_references );
662 for ( ModelReferencesSnapshot::iterator i = snapshot.begin(); i != snapshot.end(); ++i )
664 ModelReferences::value_type& value = *( *i );
665 if ( value.value.count() != 1 ) {
666 value.value.get()->unrealise();
676 ModelReferencesSnapshot snapshot( m_references );
677 for ( ModelReferencesSnapshot::iterator i = snapshot.begin(); i != snapshot.end(); ++i )
679 ModelResource* resource = ( *( *i ) ).value.get();
680 if ( !resource->isMap() ) {
689 HashtableReferenceCache g_referenceCache;
693 class ResourceVisitor
696 virtual void visit( const char* name, const char* path, const
700 void SaveReferences(){
701 ScopeDisableScreenUpdates disableScreenUpdates( "Processing...", "Saving Map" );
702 for ( HashtableReferenceCache::iterator i = g_referenceCache.begin(); i != g_referenceCache.end(); ++i )
704 ( *i ).value->save();
709 bool References_Saved(){
710 for ( HashtableReferenceCache::iterator i = g_referenceCache.begin(); i != g_referenceCache.end(); ++i )
712 scene::Node* node = ( *i ).value->getNode();
714 MapFile* map = Node_getMapFile( *node );
715 if ( map != 0 && !map->saved() ) {
723 void RefreshReferences(){
724 ScopeDisableScreenUpdates disableScreenUpdates( "Processing...", "Refreshing Models" );
725 g_referenceCache.refresh();
729 void FlushReferences(){
732 g_referenceCache.clear();
735 ReferenceCache& GetReferenceCache(){
736 return g_referenceCache;
740 #include "modulesystem/modulesmap.h"
741 #include "modulesystem/singletonmodule.h"
742 #include "modulesystem/moduleregistry.h"
744 class ReferenceDependencies :
745 public GlobalRadiantModuleRef,
746 public GlobalFileSystemModuleRef,
747 public GlobalFiletypesModuleRef
749 ModelModulesRef m_model_modules;
750 MapModulesRef m_map_modules;
752 ReferenceDependencies() :
753 m_model_modules( GlobalRadiant().getRequiredGameDescriptionKeyValue( "modeltypes" ) ),
754 m_map_modules( GlobalRadiant().getRequiredGameDescriptionKeyValue( "maptypes" ) )
758 ModelModules& getModelModules(){
759 return m_model_modules.get();
762 MapModules& getMapModules(){
763 return m_map_modules.get();
767 class ReferenceAPI : public TypeSystemRef
769 ReferenceCache* m_reference;
771 typedef ReferenceCache Type;
773 STRING_CONSTANT( Name, "*" );
776 g_nullModel = NewNullModel();
778 GlobalFileSystem().attach( g_referenceCache );
780 m_reference = &GetReferenceCache();
784 GlobalFileSystem().detach( g_referenceCache );
786 g_nullModel = g_nullNode;
789 ReferenceCache* getTable(){
794 typedef SingletonModule<ReferenceAPI, ReferenceDependencies> ReferenceModule;
795 typedef Static<ReferenceModule> StaticReferenceModule;
796 StaticRegisterModule staticRegisterReference( StaticReferenceModule::instance() );
798 ModelModules& ReferenceAPI_getModelModules(){
799 return StaticReferenceModule::instance().getDependencies().getModelModules();
802 MapModules& ReferenceAPI_getMapModules(){
803 return StaticReferenceModule::instance().getDependencies().getMapModules( );