/* Copyright (C) 2001-2006, William Joseph. All Rights Reserved. This file is part of GtkRadiant. GtkRadiant is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. GtkRadiant is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with GtkRadiant; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include "referencecache.h" #include "globaldefs.h" #include "debugging/debugging.h" #include "iscenegraph.h" #include "iselection.h" #include "iundo.h" #include "imap.h" MapModules& ReferenceAPI_getMapModules(); #include "imodel.h" ModelModules& ReferenceAPI_getModelModules(); #include "ifilesystem.h" #include "iarchive.h" #include "ifiletypes.h" #include "ireference.h" #include "ientity.h" #include "qerplugin.h" #include #include "container/cache.h" #include "container/hashfunc.h" #include "os/path.h" #include "stream/textfilestream.h" #include "nullmodel.h" #include "maplib.h" #include "stream/stringstream.h" #include "os/file.h" #include "moduleobserver.h" #include "moduleobservers.h" #include "mainframe.h" #include "map.h" #include "filetypes.h" extern bool g_writeMapComments; bool References_Saved(); void MapChanged(){ Map_SetModified( g_map, !References_Saved() ); } EntityCreator* g_entityCreator = 0; bool MapResource_loadFile( const MapFormat& format, scene::Node& root, const char* filename ){ globalOutputStream() << "Open file " << filename << " for read..."; TextFileInputStream file( filename ); if ( !file.failed() ) { globalOutputStream() << "success\n"; ScopeDisableScreenUpdates disableScreenUpdates( path_get_filename_start( filename ), "Loading Map" ); ASSERT_NOTNULL( g_entityCreator ); format.readGraph( root, file, *g_entityCreator ); return true; } else { globalErrorStream() << "failure\n"; return false; } } NodeSmartReference MapResource_load( const MapFormat& format, const char* path, const char* name ){ NodeSmartReference root( NewMapRoot( name ) ); StringOutputStream fullpath( 256 ); fullpath << path << name; if ( path_is_absolute( fullpath.c_str() ) ) { MapResource_loadFile( format, root, fullpath.c_str() ); } else { globalErrorStream() << "map path is not fully qualified: " << makeQuoted( fullpath.c_str() ) << "\n"; } return root; } bool MapResource_saveFile( const MapFormat& format, scene::Node& root, GraphTraversalFunc traverse, const char* filename ){ //ASSERT_MESSAGE(path_is_absolute(filename), "MapResource_saveFile: path is not absolute: " << makeQuoted(filename)); globalOutputStream() << "Open file " << filename << " for write..."; TextFileOutputStream file( filename ); if ( !file.failed() ) { globalOutputStream() << "success\n"; ScopeDisableScreenUpdates disableScreenUpdates( path_get_filename_start( filename ), "Saving Map" ); format.writeGraph( root, traverse, file, g_writeMapComments ); return true; } globalErrorStream() << "failure\n"; return false; } bool file_saveBackup( const char* path ){ if ( file_writeable( path ) ) { StringOutputStream backup( 256 ); backup << StringRange( path, path_get_extension( path ) ) << "bak"; #if GDEF_OS_WINDOWS // NT symlinks are not supported yet. return ( !file_exists( backup.c_str() ) || file_remove( backup.c_str() ) ) // remove backup && file_move( path, backup.c_str() ); // rename current to backup #else // POSIX symlinks are supported. return file_move( path, backup.c_str() ); // rename current to backup #endif } globalErrorStream() << "map path is not writeable: " << makeQuoted( path ) << "\n"; return false; } bool MapResource_save( const MapFormat& format, scene::Node& root, const char* path, const char* name ){ StringOutputStream fullpath( 256 ); fullpath << path << name; if ( path_is_absolute( fullpath.c_str() ) ) { #if GDEF_OS_WINDOWS // NT symlinks are not supported yet. if ( !file_exists( fullpath.c_str() ) || file_saveBackup( fullpath.c_str() ) ) { #else // POSIX symlinks are supported. /* We don't want a backup + rename operation if the .map file is * a symlink. Otherwise we'll break the user's careful symlink setup. * Just overwrite the original file. Assume the user has versioning. */ bool make_backup; struct stat st; if ( lstat(fullpath.c_str(), &st) == 0 ) { make_backup = true; // file exists if ( (st.st_mode & S_IFMT) == S_IFLNK ) { make_backup = false; // .. but it is a symlink } } else { make_backup = false; // nothing to move } if ( !make_backup || file_saveBackup( fullpath.c_str() ) ) { #endif return MapResource_saveFile( format, root, Map_Traverse, fullpath.c_str() ); } globalErrorStream() << "failed to save a backup map file: " << makeQuoted( fullpath.c_str() ) << "\n"; return false; } globalErrorStream() << "map path is not fully qualified: " << makeQuoted( fullpath.c_str() ) << "\n"; return false; } namespace { NodeSmartReference g_nullNode( NewNullNode() ); NodeSmartReference g_nullModel( g_nullNode ); } class NullModelLoader : public ModelLoader { public: scene::Node& loadModel( ArchiveFile& file ){ return g_nullModel; } }; namespace { NullModelLoader g_NullModelLoader; } /// \brief Returns the model loader for the model \p type or 0 if the model \p type has no loader module ModelLoader* ModelLoader_forType( const char* type ){ const char* moduleName = findModuleName( &GlobalFiletypes(), ModelLoader::Name(), type ); if ( string_not_empty( moduleName ) ) { ModelLoader* table = ReferenceAPI_getModelModules().findModule( moduleName ); if ( table != 0 ) { return table; } else { globalErrorStream() << "ERROR: Model type incorrectly registered: \"" << moduleName << "\"\n"; return &g_NullModelLoader; } } return 0; } NodeSmartReference ModelResource_load( ModelLoader* loader, const char* name ){ ScopeDisableScreenUpdates disableScreenUpdates( path_get_filename_start( name ), "Loading Model" ); NodeSmartReference model( g_nullModel ); { ArchiveFile* file = GlobalFileSystem().openFile( name ); if ( file != 0 ) { globalOutputStream() << "Loaded Model: \"" << name << "\"\n"; model = loader->loadModel( *file ); file->release(); } else { globalErrorStream() << "Model load failed: \"" << name << "\"\n"; } } model.get().m_isRoot = true; return model; } inline hash_t path_hash( const char* path, hash_t previous = 0 ){ #if GDEF_OS_WINDOWS return string_hash_nocase( path, previous ); #else // UNIX return string_hash( path, previous ); #endif } struct PathEqual { bool operator()( const CopiedString& path, const CopiedString& other ) const { return path_equal( path.c_str(), other.c_str() ); } }; struct PathHash { typedef hash_t hash_type; hash_type operator()( const CopiedString& path ) const { return path_hash( path.c_str() ); } }; typedef std::pair ModelKey; struct ModelKeyEqual { bool operator()( const ModelKey& key, const ModelKey& other ) const { return path_equal( key.first.c_str(), other.first.c_str() ) && path_equal( key.second.c_str(), other.second.c_str() ); } }; struct ModelKeyHash { typedef hash_t hash_type; hash_type operator()( const ModelKey& key ) const { return hash_combine( path_hash( key.first.c_str() ), path_hash( key.second.c_str() ) ); } }; typedef HashTable ModelCache; ModelCache g_modelCache; bool g_modelCache_enabled = true; ModelCache::iterator ModelCache_find( const char* path, const char* name ){ if ( g_modelCache_enabled ) { return g_modelCache.find( ModelKey( path, name ) ); } return g_modelCache.end(); } ModelCache::iterator ModelCache_insert( const char* path, const char* name, scene::Node& node ){ if ( g_modelCache_enabled ) { return g_modelCache.insert( ModelKey( path, name ), NodeSmartReference( node ) ); } return g_modelCache.insert( ModelKey( "", "" ), g_nullModel ); } void ModelCache_flush( const char* path, const char* name ){ ModelCache::iterator i = g_modelCache.find( ModelKey( path, name ) ); if ( i != g_modelCache.end() ) { //ASSERT_MESSAGE((*i).value.getCount() == 0, "resource flushed while still in use: " << (*i).key.first.c_str() << (*i).key.second.c_str()); g_modelCache.erase( i ); } } void ModelCache_clear(){ g_modelCache_enabled = false; g_modelCache.clear(); g_modelCache_enabled = true; } NodeSmartReference Model_load( ModelLoader* loader, const char* path, const char* name, const char* type ){ if ( loader != 0 ) { return ModelResource_load( loader, name ); } else { const char* moduleName = findModuleName( &GlobalFiletypes(), MapFormat::Name(), type ); if ( string_not_empty( moduleName ) ) { const MapFormat* format = ReferenceAPI_getMapModules().findModule( moduleName ); if ( format != 0 ) { return MapResource_load( *format, path, name ); } else { globalErrorStream() << "ERROR: Map type incorrectly registered: \"" << moduleName << "\"\n"; return g_nullModel; } } else { if ( string_not_empty( type ) ) { globalErrorStream() << "Model type not supported: \"" << name << "\"\n"; } return g_nullModel; } } } namespace { bool g_realised = false; // name may be absolute or relative const char* rootPath( const char* name ){ return GlobalFileSystem().findRoot( path_is_absolute( name ) ? name : GlobalFileSystem().findFile( name ) ); } } struct ModelResource : public Resource { NodeSmartReference m_model; const CopiedString m_originalName; CopiedString m_path; CopiedString m_name; CopiedString m_type; ModelLoader* m_loader; ModuleObservers m_observers; std::time_t m_modified; std::size_t m_unrealised; ModelResource( const CopiedString& name ) : m_model( g_nullModel ), m_originalName( name ), m_type( path_get_extension( name.c_str() ) ), m_loader( 0 ), m_modified( 0 ), m_unrealised( 1 ){ m_loader = ModelLoader_forType( m_type.c_str() ); if ( g_realised ) { realise(); } } ~ModelResource(){ if ( realised() ) { unrealise(); } ASSERT_MESSAGE( !realised(), "ModelResource::~ModelResource: resource reference still realised: " << makeQuoted( m_name.c_str() ) ); } // NOT COPYABLE ModelResource( const ModelResource& ); // NOT ASSIGNABLE ModelResource& operator=( const ModelResource& ); void setModel( const NodeSmartReference& model ){ m_model = model; } void clearModel(){ m_model = g_nullModel; } void loadCached(){ if ( g_modelCache_enabled ) { // cache lookup ModelCache::iterator i = ModelCache_find( m_path.c_str(), m_name.c_str() ); if ( i == g_modelCache.end() ) { i = ModelCache_insert( m_path.c_str(), m_name.c_str(), Model_load( m_loader, m_path.c_str(), m_name.c_str(), m_type.c_str() ) ); } setModel( ( *i ).value ); } else { setModel( Model_load( m_loader, m_path.c_str(), m_name.c_str(), m_type.c_str() ) ); } } void loadModel(){ loadCached(); connectMap(); mapSave(); } bool load(){ ASSERT_MESSAGE( realised(), "resource not realised" ); if ( m_model == g_nullModel ) { loadModel(); } return m_model != g_nullModel; } bool save(){ if ( !mapSaved() ) { const char* moduleName = findModuleName( GetFileTypeRegistry(), MapFormat::Name(), m_type.c_str() ); if ( string_not_empty( moduleName ) ) { const MapFormat* format = ReferenceAPI_getMapModules().findModule( moduleName ); if ( format != 0 && MapResource_save( *format, m_model.get(), m_path.c_str(), m_name.c_str() ) ) { mapSave(); return true; } } } return false; } void flush(){ if ( realised() ) { ModelCache_flush( m_path.c_str(), m_name.c_str() ); } } scene::Node* getNode(){ //if(m_model != g_nullModel) { return m_model.get_pointer(); } //return 0; } void setNode( scene::Node* node ){ ModelCache::iterator i = ModelCache_find( m_path.c_str(), m_name.c_str() ); if ( i != g_modelCache.end() ) { ( *i ).value = NodeSmartReference( *node ); } setModel( NodeSmartReference( *node ) ); connectMap(); } void attach( ModuleObserver& observer ){ if ( realised() ) { observer.realise(); } m_observers.attach( observer ); } void detach( ModuleObserver& observer ){ if ( realised() ) { observer.unrealise(); } m_observers.detach( observer ); } bool realised(){ return m_unrealised == 0; } void realise(){ ASSERT_MESSAGE( m_unrealised != 0, "ModelResource::realise: already realised" ); if ( --m_unrealised == 0 ) { m_path = rootPath( m_originalName.c_str() ); m_name = path_make_relative( m_originalName.c_str(), m_path.c_str() ); //globalOutputStream() << "ModelResource::realise: " << m_path.c_str() << m_name.c_str() << "\n"; m_observers.realise(); } } void unrealise(){ if ( ++m_unrealised == 1 ) { m_observers.unrealise(); //globalOutputStream() << "ModelResource::unrealise: " << m_path.c_str() << m_name.c_str() << "\n"; clearModel(); } } bool isMap() const { return Node_getMapFile( m_model ) != 0; } void connectMap(){ MapFile* map = Node_getMapFile( m_model ); if ( map != 0 ) { map->setChangedCallback( makeCallbackF(MapChanged) ); } } std::time_t modified() const { StringOutputStream fullpath( 256 ); fullpath << m_path.c_str() << m_name.c_str(); return file_modified( fullpath.c_str() ); } void mapSave(){ m_modified = modified(); MapFile* map = Node_getMapFile( m_model ); if ( map != 0 ) { map->save(); } } bool mapSaved() const { MapFile* map = Node_getMapFile( m_model ); if ( map != 0 ) { return m_modified == modified() && map->saved(); } return true; } bool isModified() const { return ( ( !string_empty( m_path.c_str() ) // had or has an absolute path && m_modified != modified() ) // AND disk timestamp changed || !path_equal( rootPath( m_originalName.c_str() ), m_path.c_str() ) ); // OR absolute vfs-root changed } void refresh(){ if ( isModified() ) { flush(); unrealise(); realise(); } } }; class HashtableReferenceCache : public ReferenceCache, public ModuleObserver { typedef HashedCache ModelReferences; ModelReferences m_references; std::size_t m_unrealised; class ModelReferencesSnapshot { ModelReferences& m_references; typedef std::list Iterators; Iterators m_iterators; public: typedef Iterators::iterator iterator; ModelReferencesSnapshot( ModelReferences& references ) : m_references( references ){ for ( ModelReferences::iterator i = m_references.begin(); i != m_references.end(); ++i ) { m_references.capture( i ); m_iterators.push_back( i ); } } ~ModelReferencesSnapshot(){ for ( Iterators::iterator i = m_iterators.begin(); i != m_iterators.end(); ++i ) { m_references.release( *i ); } } iterator begin(){ return m_iterators.begin(); } iterator end(){ return m_iterators.end(); } }; public: typedef ModelReferences::iterator iterator; HashtableReferenceCache() : m_unrealised( 1 ){ } iterator begin(){ return m_references.begin(); } iterator end(){ return m_references.end(); } void clear(){ m_references.clear(); } Resource* capture( const char* path ){ //globalOutputStream() << "capture: \"" << path << "\"\n"; return m_references.capture( CopiedString( path ) ).get(); } void release( const char* path ){ m_references.release( CopiedString( path ) ); //globalOutputStream() << "release: \"" << path << "\"\n"; } void setEntityCreator( EntityCreator& entityCreator ){ g_entityCreator = &entityCreator; } bool realised() const { return m_unrealised == 0; } void realise(){ ASSERT_MESSAGE( m_unrealised != 0, "HashtableReferenceCache::realise: already realised" ); if ( --m_unrealised == 0 ) { g_realised = true; { ModelReferencesSnapshot snapshot( m_references ); for ( ModelReferencesSnapshot::iterator i = snapshot.begin(); i != snapshot.end(); ++i ) { ModelReferences::value_type& value = *( *i ); if ( value.value.count() != 1 ) { value.value.get()->realise(); } } } } } void unrealise(){ if ( ++m_unrealised == 1 ) { g_realised = false; { ModelReferencesSnapshot snapshot( m_references ); for ( ModelReferencesSnapshot::iterator i = snapshot.begin(); i != snapshot.end(); ++i ) { ModelReferences::value_type& value = *( *i ); if ( value.value.count() != 1 ) { value.value.get()->unrealise(); } } } ModelCache_clear(); } } void refresh(){ ModelReferencesSnapshot snapshot( m_references ); for ( ModelReferencesSnapshot::iterator i = snapshot.begin(); i != snapshot.end(); ++i ) { ModelResource* resource = ( *( *i ) ).value.get(); if ( !resource->isMap() ) { resource->refresh(); } } } }; namespace { HashtableReferenceCache g_referenceCache; } #if 0 class ResourceVisitor { public: virtual void visit( const char* name, const char* path, const }; #endif void SaveReferences(){ ScopeDisableScreenUpdates disableScreenUpdates( "Processing...", "Saving Map" ); for ( HashtableReferenceCache::iterator i = g_referenceCache.begin(); i != g_referenceCache.end(); ++i ) { ( *i ).value->save(); } MapChanged(); } bool References_Saved(){ for ( HashtableReferenceCache::iterator i = g_referenceCache.begin(); i != g_referenceCache.end(); ++i ) { scene::Node* node = ( *i ).value->getNode(); if ( node != 0 ) { MapFile* map = Node_getMapFile( *node ); if ( map != 0 && !map->saved() ) { return false; } } } return true; } void RefreshReferences(){ ScopeDisableScreenUpdates disableScreenUpdates( "Processing...", "Refreshing Models" ); g_referenceCache.refresh(); } void FlushReferences(){ ModelCache_clear(); g_referenceCache.clear(); } ReferenceCache& GetReferenceCache(){ return g_referenceCache; } #include "modulesystem/modulesmap.h" #include "modulesystem/singletonmodule.h" #include "modulesystem/moduleregistry.h" class ReferenceDependencies : public GlobalRadiantModuleRef, public GlobalFileSystemModuleRef, public GlobalFiletypesModuleRef { ModelModulesRef m_model_modules; MapModulesRef m_map_modules; public: ReferenceDependencies() : m_model_modules( GlobalRadiant().getRequiredGameDescriptionKeyValue( "modeltypes" ) ), m_map_modules( GlobalRadiant().getRequiredGameDescriptionKeyValue( "maptypes" ) ) { } ModelModules& getModelModules(){ return m_model_modules.get(); } MapModules& getMapModules(){ return m_map_modules.get(); } }; class ReferenceAPI : public TypeSystemRef { ReferenceCache* m_reference; public: typedef ReferenceCache Type; STRING_CONSTANT( Name, "*" ); ReferenceAPI(){ g_nullModel = NewNullModel(); GlobalFileSystem().attach( g_referenceCache ); m_reference = &GetReferenceCache(); } ~ReferenceAPI(){ GlobalFileSystem().detach( g_referenceCache ); g_nullModel = g_nullNode; } ReferenceCache* getTable(){ return m_reference; } }; typedef SingletonModule ReferenceModule; typedef Static StaticReferenceModule; StaticRegisterModule staticRegisterReference( StaticReferenceModule::instance() ); ModelModules& ReferenceAPI_getModelModules(){ return StaticReferenceModule::instance().getDependencies().getModelModules(); } MapModules& ReferenceAPI_getMapModules(){ return StaticReferenceModule::instance().getDependencies().getMapModules( ); }