netradiant-custom/radiant/referencecache.cpp
Garux df02774ff5 tweak StringOutputStream use
auto str = StringOutputStream()(bla) use form was not doing copy elision or move, but copy
2024-01-29 16:54:08 +06:00

735 lines
19 KiB
C++

/*
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 "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 <list>
#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"
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 ) );
const auto fullpath = StringStream( path, name );
if ( path_is_absolute( fullpath ) ) {
MapResource_loadFile( format, root, fullpath );
}
else
{
globalErrorStream() << "map path is not fully qualified: " << makeQuoted( fullpath ) << '\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 );
return true;
}
globalErrorStream() << "failure\n";
return false;
}
bool file_saveBackup( const char* path ){
const auto backup = StringStream( PathExtensionless( path ), ".bak" );
if ( file_move( path, backup ) ) {
return true;
}
if ( !file_exists( path ) ) {
return true; // nothing to move, no wonder it failed
}
globalErrorStream() << "map path (or backup path) is not writable: " << makeQuoted( path ) << '\n';
return false;
}
bool MapResource_save( const MapFormat& format, scene::Node& root, const char* path, const char* name ){
const auto fullpath = StringStream( path, name );
if ( path_is_absolute( fullpath ) ) {
// 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.
if ( file_is_symlink( fullpath ) || file_saveBackup( fullpath ) ) {
return MapResource_saveFile( format, root, Map_Traverse, fullpath );
}
globalErrorStream() << "failed to save map file: " << makeQuoted( fullpath ) << '\n';
return false;
}
globalErrorStream() << "map path is not fully qualified: " << makeQuoted( fullpath ) << '\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 defined( WIN32 )
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<CopiedString, CopiedString> 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<ModelKey, NodeSmartReference, ModelKeyHash, ModelKeyEqual> 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 ) );
}
// NOT COPYABLE
ModelResource( const ModelResource& ) = delete;
// NOT ASSIGNABLE
ModelResource& operator=( const ModelResource& ) = delete;
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( FreeCaller<MapChanged>() );
}
}
std::time_t modified() const {
return file_modified( StringStream( m_path, m_name ) );
}
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<CopiedString, ModelResource, PathHash, PathEqual> ModelReferences;
ModelReferences m_references;
std::size_t m_unrealised;
class ModelReferencesSnapshot
{
ModelReferences& m_references;
typedef std::list<ModelReferences::iterator> 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<ReferenceAPI, ReferenceDependencies> ReferenceModule;
typedef Static<ReferenceModule> StaticReferenceModule;
StaticRegisterModule staticRegisterReference( StaticReferenceModule::instance() );
ModelModules& ReferenceAPI_getModelModules(){
return StaticReferenceModule::instance().getDependencies().getModelModules();
}
MapModules& ReferenceAPI_getMapModules(){
return StaticReferenceModule::instance().getDependencies().getMapModules( );
}