//====== Copyright © 1996-2004, Valve Corporation, All rights reserved. ======= // // Purpose: // //============================================================================= #include "datamodel/idatamodel.h" #include "datamodel/dmattributevar.h" #include "datamodel/dmelementfactoryhelper.h" #include "datamodel.h" #include "dependencygraph.h" #include "dmattributeinternal.h" #include "dmserializerkeyvalues.h" #include "dmserializerkeyvalues2.h" #include "dmserializerbinary.h" #include "undomanager.h" #include "clipboardmanager.h" #include "DmElementFramework.h" #include "vstdlib/iprocessutils.h" #include "tier0/dbg.h" #include "tier1/utlvector.h" #include "tier1/utlqueue.h" #include "tier1/utlbuffer.h" #include "tier1/fmtstr.h" #include "tier2/utlstreambuffer.h" #include "tier2/fileutils.h" #include // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" //----------------------------------------------------------------------------- // Forward declarations //----------------------------------------------------------------------------- class CUtlBuffer; class IDmEditMessage; class KeyValues; #define UNNAMED_ELEMENT_NAME "unnamed" //----------------------------------------------------------------------------- // Class factory for the default element //----------------------------------------------------------------------------- class CDmElementFactoryDefault : public IDmElementFactory { public: // Creation, destruction virtual CDmElement* Create( DmElementHandle_t handle, const char *pElementType, const char *pElementName, DmFileId_t fileid, const DmObjectId_t &id ) { return new CDmElement( handle, pElementType, id, pElementName, fileid ); } virtual void Destroy( DmElementHandle_t hElement ) { if ( hElement != DMELEMENT_HANDLE_INVALID ) { CDmElement *pElement = g_pDataModel->GetElement( hElement ); delete static_cast( pElement ); } } virtual void AddOnElementCreatedCallback( IDmeElementCreated *callback ) {}; virtual void RemoveOnElementCreatedCallback( IDmeElementCreated *callback ) {}; virtual void OnElementCreated( CDmElement* pElement ) {}; }; static CDmElementFactoryDefault s_DefaultElementFactory; //----------------------------------------------------------------------------- // Singleton instance //----------------------------------------------------------------------------- static CDataModel g_DataModel; CDataModel *g_pDataModelImp = &g_DataModel; IDataModel *g_pDataModel = &g_DataModel; //----------------------------------------------------------------------------- // Constructor, destructor //----------------------------------------------------------------------------- CDataModel::CDataModel() : m_elementIds( 4096 ), m_unloadedIdElementMap( 16, 0, 0, ElementIdHandlePair_t::Compare, ElementIdHandlePair_t::HashKey ) { m_pDefaultFactory = &s_DefaultElementFactory; m_bUnableToSetDefaultFactory = false; m_bOnlyCreateUntypedElements = false; m_bUnableToCreateOnlyUntypedElements = false; m_pKeyvaluesCallbackInterface = NULL; m_nElementsAllocatedSoFar = 0; m_nMaxNumberOfElements = 0; m_bIsUnserializing = false; m_bDeleteOrphanedElements = false; } CDataModel::~CDataModel() { m_UndoMgr.WipeUndo(); if ( GetAllocatedElementCount() > 0 ) { Warning( "Leaking %i elements\n", GetAllocatedElementCount() ); } } //----------------------------------------------------------------------------- // Methods of IAppSystem //----------------------------------------------------------------------------- bool CDataModel::Connect( CreateInterfaceFn factory ) { if ( !BaseClass::Connect( factory ) ) return false; if ( !factory( FILESYSTEM_INTERFACE_VERSION, NULL ) ) { Warning( "DataModel needs the file system to function" ); return false; } return true; } void *CDataModel::QueryInterface( const char *pInterfaceName ) { if ( !V_strcmp( pInterfaceName, VDATAMODEL_INTERFACE_VERSION ) ) return (IDataModel*)this; return NULL; } //----------------------------------------------------------------------------- // Purpose: // Input : *databasePath - // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- InitReturnVal_t CDataModel::Init( ) { InitReturnVal_t nRetVal = BaseClass::Init(); if ( nRetVal != INIT_OK ) return nRetVal; InstallKeyValuesSerializer( this ); InstallKeyValues2Serializer( this ); InstallBinarySerializer( this ); m_UndoMgr.SetUndoDepth( 256 ); return INIT_OK; } //#define _ELEMENT_HISTOGRAM_ #ifdef _ELEMENT_HISTOGRAM_ CUtlMap< CUtlSymbolLarge, int > g_typeHistogram( 0, 100, DefLessFunc( CUtlSymbolLarge ) ); #endif _ELEMENT_HISTOGRAM_ //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CDataModel::Shutdown() { #ifdef _ELEMENT_HISTOGRAM_ Msg( "element type histogram for %d elements allocated so far:\n", GetElementsAllocatedSoFar() ); for ( int i = g_typeHistogram.FirstInorder(); g_typeHistogram.IsValidIndex( i ); i = g_typeHistogram.NextInorder( i ) ) { Msg( "%d\t%s\n", g_typeHistogram.Element( i ), GetString( g_typeHistogram.Key( i ) ) ); } Msg( "\n" ); #endif _ELEMENT_HISTOGRAM_ int c = GetAllocatedElementCount(); if ( c > 0 ) { Warning( "CDataModel: %i elements left in memory!!!\n", c ); } m_Factories.Purge(); m_Serializers.Purge(); m_UndoMgr.Shutdown(); BaseClass::Shutdown(); } //----------------------------------------------------------------------------- // Sets the undo context size //----------------------------------------------------------------------------- void CDataModel::SetUndoDepth( int nSize ) { m_UndoMgr.SetUndoDepth( nSize ); } //----------------------------------------------------------------------------- // force creation of untyped elements, ignoring type //----------------------------------------------------------------------------- void CDataModel::OnlyCreateUntypedElements( bool bEnable ) { if ( m_bUnableToCreateOnlyUntypedElements ) { Assert( 0 ); return; } m_bOnlyCreateUntypedElements = bEnable; } int CDataModel::GetElementsAllocatedSoFar() { return m_nElementsAllocatedSoFar; } int CDataModel::GetMaxNumberOfElements() { return m_nMaxNumberOfElements; } int CDataModel::GetAllocatedAttributeCount() { return ::GetAllocatedAttributeCount(); } //----------------------------------------------------------------------------- // Returns the total number of elements allocated at the moment //----------------------------------------------------------------------------- int CDataModel::GetAllocatedElementCount() { return ( int )m_Handles.GetValidHandleCount(); } DmElementHandle_t CDataModel::FirstAllocatedElement() { int nHandles = ( int )m_Handles.GetHandleCount(); for ( int i = 0; i < nHandles; ++i ) { DmElementHandle_t hElement = ( DmElementHandle_t )m_Handles.GetHandleFromIndex( i ); if ( CDmElement *pElement = GetElement( hElement ) ) return hElement; } return DMELEMENT_HANDLE_INVALID; } DmElementHandle_t CDataModel::NextAllocatedElement( DmElementHandle_t hElement ) { int nHandles = ( int )m_Handles.GetHandleCount(); for ( int i = m_Handles.GetIndexFromHandle( hElement ) + 1; i < nHandles; ++i ) { DmElementHandle_t hElement = ( DmElementHandle_t )m_Handles.GetHandleFromIndex( i ); if ( CDmElement *pElement = GetElement( hElement ) ) return hElement; } return DMELEMENT_HANDLE_INVALID; } //----------------------------------------------------------------------------- // estimate memory overhead //----------------------------------------------------------------------------- int CDataModel::EstimateMemoryOverhead() const { int nHandlesOverhead = sizeof( int ) + sizeof( CDmElement* ); // m_Handles int nElementIdsOverhead = sizeof( DmElementHandle_t ); // this also has a 80k static overhead, since hash tables can't grow return nHandlesOverhead + nElementIdsOverhead; } static bool HandleCompare( const DmElementHandle_t & a, const DmElementHandle_t &b ) { return a == b; } static unsigned int HandleHash( const DmElementHandle_t &h ) { return (unsigned int)h; } int CDataModel::EstimateMemoryUsage( DmElementHandle_t hElement, TraversalDepth_t depth ) { CUtlHash< DmElementHandle_t > visited( 1024, 0, 0, HandleCompare, HandleHash ); CDmElement *pElement = m_Handles.GetHandle( hElement ); if ( !pElement ) return 0; return CDmeElementAccessor::EstimateMemoryUsage( pElement, visited, depth, NULL ); } //----------------------------------------------------------------------------- // Displays stats for datamodel //----------------------------------------------------------------------------- struct DmMemoryInfo_t { int m_nCount; int m_nSize; int m_pCategories[ MEMORY_CATEGORY_COUNT ]; }; struct DmMemorySortInfo_t { int m_nIndex; int m_nTotalSize; }; int DmMemorySortFunc( const void * lhs, const void * rhs ) { DmMemorySortInfo_t &info1 = *(DmMemorySortInfo_t*)lhs; DmMemorySortInfo_t &info2 = *(DmMemorySortInfo_t*)rhs; return info1.m_nTotalSize - info2.m_nTotalSize; } // this method assumes that all elements that should be traversed have a parity of !bParity static void GatherElements_R( CDmElement *pElement, CUtlVector< DmElementHandle_t > &list, CUtlHash< DmElementHandle_t > &visited ) { DmElementHandle_t h = pElement->GetHandle(); if ( visited.Find( h ) != visited.InvalidHandle() ) return; visited.Insert( h ); list.AddToTail( h ); for ( const CDmAttribute *pAttr = pElement->FirstAttribute(); pAttr != NULL; pAttr = pAttr->NextAttribute() ) { if ( pAttr->GetType() == AT_ELEMENT ) { CDmElement *pChild = pAttr->GetValueElement(); if ( !pChild ) continue; GatherElements_R( pChild, list, visited ); } else if ( pAttr->GetType() == AT_ELEMENT_ARRAY ) { const CDmrElementArrayConst<> elementArrayAttr( pAttr ); int nChildren = elementArrayAttr.Count(); for ( int i = 0; i < nChildren; ++i ) { CDmElement *pChild = elementArrayAttr[ i ]; if ( !pChild ) continue; GatherElements_R( pChild, list, visited ); } } } } void CDataModel::BuildHistogramForHandles( CUtlMap< CUtlSymbolLarge, DmMemoryInfo_t > &typeHistogram, CUtlVector< DmElementHandle_t > &handles ) { CUtlHash< DmElementHandle_t > visited( 1024, 0, 0, HandleCompare, HandleHash ); int c = handles.Count(); for ( int i = 0; i < c; ++i ) { DmElementHandle_t h = handles[ i ]; if ( h == DMELEMENT_HANDLE_INVALID ) continue; CDmElement *pElement = m_Handles.GetHandle( h ); if ( !pElement ) continue; unsigned short j = typeHistogram.Find( pElement->GetType() ); if ( !typeHistogram.IsValidIndex( j ) ) { j = typeHistogram.Insert( pElement->GetType() ); typeHistogram[j].m_nCount = 0; typeHistogram[j].m_nSize = 0; memset( typeHistogram[j].m_pCategories, 0, sizeof(typeHistogram[j].m_pCategories) ); } int nMemory = CDmeElementAccessor::EstimateMemoryUsage( pElement, visited, TD_NONE, typeHistogram[j].m_pCategories ); ++typeHistogram[j].m_nCount; typeHistogram[j].m_nSize += nMemory; } } void CDataModel::DisplayMemoryStats( DmElementHandle_t hElement /*= DMELEMENT_HANDLE_INVALID*/ ) { CUtlMap< CUtlSymbolLarge, DmMemoryInfo_t > typeHistogram( 0, 100, DefLessFunc( CUtlSymbolLarge ) ); CUtlHash< DmElementHandle_t > visited( 1024, 0, 0, HandleCompare, HandleHash ); CUtlVector< DmElementHandle_t > handles; if ( hElement == DMELEMENT_HANDLE_INVALID ) { int c = (int)m_Handles.GetHandleCount(); for ( int i = 0; i < c; ++i ) { DmElementHandle_t h = (DmElementHandle_t)m_Handles.GetHandleFromIndex( i ); if ( !m_Handles.IsHandleValid( h ) ) continue; handles.AddToTail( h ); } } else { // Do a recursive build if ( !m_Handles.IsHandleValid( hElement ) ) { Msg( "CDataModel::DisplayMemoryStats with invalid handle %u\n", (int)hElement ); return; } CDmElement *pElement = m_Handles.GetHandle( hElement ); if( pElement ) { GatherElements_R( pElement, handles, visited ); } } BuildHistogramForHandles( typeHistogram, handles ); // Sort DmMemorySortInfo_t* pSortInfo = (DmMemorySortInfo_t*)_alloca( typeHistogram.Count() * sizeof(DmMemorySortInfo_t) ); int nCount = 0; for ( int i = typeHistogram.FirstInorder(); typeHistogram.IsValidIndex( i ); i = typeHistogram.NextInorder( i ) ) { pSortInfo[nCount].m_nIndex = i; pSortInfo[nCount].m_nTotalSize = typeHistogram.Element( i ).m_nSize; ++nCount; } qsort( pSortInfo, nCount, sizeof(DmMemorySortInfo_t), DmMemorySortFunc ); int pTotals[ MEMORY_CATEGORY_COUNT ] = { 0 }; int nLastTwoSize = 0; int nTotalSize = 0; int nTotalCount = 0; int nTotalData = 0; ConMsg( "Dm Memory usage: type\t\t\t\tcount\ttotalsize\twastage %%\touter\t\tinner\t\tdatamodel\trefs\t\ttree\t\tatts\t\tdata\t(att count)\n" ); for ( int i = 0; i < nCount; ++i ) { const DmMemoryInfo_t& info = typeHistogram.Element( pSortInfo[i].m_nIndex ); float flPercentOverhead = 1.0f - ( ( info.m_nSize != 0 ) ? ( (float)info.m_pCategories[MEMORY_CATEGORY_ATTRIBUTE_DATA] / (float)info.m_nSize ) : 0.0f ); flPercentOverhead *= 100.0f; ConMsg( "%-40s\t%6d\t%9d\t\t%5.2f", typeHistogram.Key( pSortInfo[i].m_nIndex ).String(), info.m_nCount, info.m_nSize, flPercentOverhead ); int nTotal = 0; for ( int j = 0; j < MEMORY_CATEGORY_COUNT; ++j ) { ConColorMsg( Color( 255, 192, 0, 255 ), "\t%8d", info.m_pCategories[j] ); if ( j != MEMORY_CATEGORY_ATTRIBUTE_COUNT ) { nTotal += info.m_pCategories[j]; } pTotals[j] += info.m_pCategories[j]; } ConMsg( "\n" ); Assert( nTotal == info.m_nSize ); nTotalSize += info.m_nSize; nTotalCount += info.m_nCount; nTotalData += info.m_pCategories[MEMORY_CATEGORY_ATTRIBUTE_DATA]; if ( i >= nCount - 2 ) { nLastTwoSize += info.m_nSize; } } ConMsg( "\n" ); ConMsg( "%-40s\t%6d\t%9d\t\t%5.2f", "Totals", nTotalCount, nTotalSize, 100.0f * ( 1.0f - (float)nTotalData / (float)nTotalSize ) ); for ( int j = 0; j < MEMORY_CATEGORY_COUNT; ++j ) { ConColorMsg( Color( 255, 192, 0, 255 ), "\t%8d", pTotals[j] ); } ConMsg( "\n" ); float flPercent = 0.0f; if ( nTotalSize > 0 ) { flPercent = 100.0 *((float)nLastTwoSize/(float)nTotalSize); } ConMsg( "Last two rows as percentage of total size %f %%:\n", flPercent ); uint64 symbolBytes = m_SymbolTable.GetMemoryUsage(); int symbolCount = m_SymbolTable.GetNumStrings(); // Estimate string table usage ConMsg( "Symbol table: %d entries %I64u bytes\n", symbolCount, symbolBytes ); } void IncrementSymbolRefCount( CUtlSymbolLarge symbol, CUtlMap< CUtlSymbolLarge, int, int > &refCountMap ) { int nIndex = refCountMap.Find( symbol ); if ( nIndex == refCountMap.InvalidIndex() ) { nIndex = refCountMap.Insert( symbol ); refCountMap[ nIndex ] = 1; } else { refCountMap[ nIndex ]++; } } int CompareSymbolsAcending( const CUtlSymbolLarge *symA, const CUtlSymbolLarge *symB ) { return V_stricmp( symA->String(), symB->String() ); } // Dump the symbol table to the console void CDataModel::DumpSymbolTable() { uint64 symbolBytes = m_SymbolTable.GetMemoryUsage(); int symbolCount = m_SymbolTable.GetNumStrings(); // Estimate string table usage ConMsg( "Datamodel symbol table: %d entries %I64u bytes\n", symbolCount, symbolBytes ); CUtlMap< CUtlSymbolLarge, int, int > symbolRefCountMap( 0, symbolCount, DefLessFunc( CUtlSymbolLarge ) ); int nNumElements = (int)m_Handles.GetHandleCount(); for ( int iElement = 0; iElement < nNumElements; ++iElement ) { CDmElement *pElement = m_Handles.GetHandle( iElement ); if ( pElement == NULL ) continue; IncrementSymbolRefCount( pElement->GetType(), symbolRefCountMap ); for ( CDmAttribute *pAttr = pElement->FirstAttribute(); pAttr; pAttr = pAttr->NextAttribute() ) { IncrementSymbolRefCount( pAttr->GetNameSymbol(), symbolRefCountMap ); if ( pAttr->GetType() == AT_STRING ) { IncrementSymbolRefCount( pAttr->GetValue< CUtlSymbolLarge >(), symbolRefCountMap ); } } } CUtlBuffer buf( 0, symbolBytes + ( symbolCount * 32 ), CUtlBuffer::TEXT_BUFFER ); buf.Printf( "Datamodel symbol table: %d entries %I64u bytes\n", symbolCount, symbolBytes ); CUtlVector< CUtlSymbolLarge > elements; elements.EnsureCount( symbolCount ); m_SymbolTable.GetElements( 0, symbolCount, elements.Base() ); elements.Sort( CompareSymbolsAcending ); buf.Printf( "Index Address References String\n" ); for ( int i = 0; i < symbolCount; ++i ) { CUtlSymbolLarge symbol = elements[ i ]; int nIndex = symbolRefCountMap.Find( symbol ); int nRefCount = ( nIndex != symbolRefCountMap.InvalidIndex() ) ? symbolRefCountMap[ nIndex ] : 0; const char *pString = symbol.String(); buf.Printf( "%05d, 0x%p, %5d, '%s'\n", i, pString, nRefCount, pString ); } int nFileIndex = 0; int nMaxFileIndex = 1000; const char baseFilename[] = "DatamodelSymbols"; CUtlString filename; char directory[ 256 ]; g_pFullFileSystem->GetCurrentDirectory( directory, sizeof( directory ) ); while ( nFileIndex < nMaxFileIndex ) { filename = CFmtStr( "%s\\%s%i.txt", directory, baseFilename, nFileIndex ); if ( !g_pFullFileSystem->FileExists( filename ) ) break; ++nFileIndex; } if ( nFileIndex < nMaxFileIndex ) { g_pFullFileSystem->WriteFile( filename, NULL, buf ); Msg( "Wrote symbol stats to file: %s", filename.Get() ); } } //----------------------------------------------------------------------------- // Global symbol table for the datamodel system //----------------------------------------------------------------------------- CUtlSymbolLarge CDataModel::GetSymbol( const char *pString ) { return m_SymbolTable.AddString( pString ); } //----------------------------------------------------------------------------- // file format methods //----------------------------------------------------------------------------- const char* CDataModel::GetFormatExtension( const char *pFormatName ) { IDmFormatUpdater *pUpdater = FindFormatUpdater( pFormatName ); Assert( pUpdater ); if ( !pUpdater ) return NULL; return pUpdater->GetExtension(); } const char* CDataModel::GetFormatDescription( const char *pFormatName ) { IDmFormatUpdater *pUpdater = FindFormatUpdater( pFormatName ); Assert( pUpdater ); if ( !pUpdater ) return NULL; return pUpdater->GetDescription(); } int CDataModel::GetFormatCount() const { return m_FormatUpdaters.Count(); } const char* CDataModel::GetFormatName( int i ) const { IDmFormatUpdater *pUpdater = m_FormatUpdaters[ i ]; if ( !pUpdater ) return NULL; return pUpdater->GetName(); } const char *CDataModel::GetDefaultEncoding( const char *pFormatName ) { IDmFormatUpdater *pUpdater = FindFormatUpdater( pFormatName ); if ( !pUpdater ) return NULL; return pUpdater->GetDefaultEncoding(); } //----------------------------------------------------------------------------- // Adds various serializers //----------------------------------------------------------------------------- void CDataModel::AddSerializer( IDmSerializer *pSerializer ) { Assert( Q_strlen( pSerializer->GetName() ) <= DMX_MAX_FORMAT_NAME_MAX_LENGTH ); if ( FindSerializer( pSerializer->GetName() ) ) { Warning("Attempted to add two serializers with the same file encoding (%s)!\n", pSerializer->GetName() ); return; } m_Serializers.AddToTail( pSerializer ); } void CDataModel::AddLegacyUpdater( IDmLegacyUpdater *pUpdater ) { Assert( Q_strlen( pUpdater->GetName() ) <= DMX_MAX_FORMAT_NAME_MAX_LENGTH ); if ( FindLegacyUpdater( pUpdater->GetName() ) ) { Warning( "Attempted to add two legacy updaters with the same file format (%s)!\n", pUpdater->GetName() ); return; } m_LegacyUpdaters.AddToTail( pUpdater ); } void CDataModel::AddFormatUpdater( IDmFormatUpdater *pUpdater ) { Assert( Q_strlen( pUpdater->GetName() ) <= DMX_MAX_FORMAT_NAME_MAX_LENGTH ); if ( FindFormatUpdater( pUpdater->GetName() ) ) { Warning( "Attempted to add two format updaters with the same file format (%s)!\n", pUpdater->GetName() ); return; } m_FormatUpdaters.AddToTail( pUpdater ); } //----------------------------------------------------------------------------- // encoding-related methods //----------------------------------------------------------------------------- int CDataModel::GetEncodingCount() const { return m_Serializers.Count(); } const char *CDataModel::GetEncodingName( int i ) const { return m_Serializers[ i ]->GetName(); } bool CDataModel::IsEncodingBinary( const char *pEncodingName ) const { IDmSerializer *pSerializer = FindSerializer( pEncodingName ); if ( !pSerializer ) { Warning("Serialize: File encoding %s is undefined!\n", pEncodingName ); return false; } return pSerializer->IsBinaryFormat(); } bool CDataModel::DoesEncodingStoreVersionInFile( const char *pEncodingName ) const { IDmSerializer *pSerializer = FindSerializer( pEncodingName ); if ( !pSerializer ) { Warning("Serialize: File encoding %s is undefined!\n", pEncodingName ); return false; } return pSerializer->StoresVersionInFile(); } IDmSerializer* CDataModel::FindSerializer( const char *pEncodingName ) const { int nSerializers = m_Serializers.Count(); for ( int i = 0; i < nSerializers; ++i ) { IDmSerializer *pSerializer = m_Serializers[ i ]; Assert( pSerializer ); if ( !pSerializer ) continue; if ( !V_strcmp( pEncodingName, pSerializer->GetName() ) ) return pSerializer; } return NULL; } IDmLegacyUpdater* CDataModel::FindLegacyUpdater( const char *pLegacyFormatName ) const { int nUpdaters = m_LegacyUpdaters.Count(); for ( int i = 0; i < nUpdaters; ++i ) { IDmLegacyUpdater *pUpdater = m_LegacyUpdaters[ i ]; Assert( pUpdater ); if ( !pUpdater ) continue; if ( !V_strcmp( pLegacyFormatName, pUpdater->GetName() ) ) return pUpdater; } return NULL; } IDmFormatUpdater* CDataModel::FindFormatUpdater( const char *pFormatName ) const { int nUpdaters = m_FormatUpdaters.Count(); for ( int i = 0; i < nUpdaters; ++i ) { IDmFormatUpdater *pUpdater = m_FormatUpdaters[ i ]; Assert( pUpdater ); if ( !pUpdater ) continue; if ( !V_strcmp( pFormatName, pUpdater->GetName() ) ) return pUpdater; } return NULL; } //----------------------------------------------------------------------------- // Purpose: Sets the name of the DME element to create in keyvalues serialization //----------------------------------------------------------------------------- void CDataModel::SetKeyValuesElementCallback( IElementForKeyValueCallback *pCallbackInterface ) { m_pKeyvaluesCallbackInterface = pCallbackInterface; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- const char *CDataModel::GetKeyValuesElementName( const char *pszKeyName, int iNestingLevel ) { if ( m_pKeyvaluesCallbackInterface ) return m_pKeyvaluesCallbackInterface->GetElementForKeyValue( pszKeyName, iNestingLevel ); return NULL; } //----------------------------------------------------------------------------- // For serialization, set the delimiter rules //----------------------------------------------------------------------------- void CDataModel::SetSerializationDelimiter( CUtlCharConversion *pConv ) { ::SetSerializationDelimiter( pConv ); } void CDataModel::SetSerializationArrayDelimiter( const char *pDelimiter ) { ::SetSerializationArrayDelimiter( pDelimiter ); } bool CDataModel::SaveToFile( char const *pFileName, char const *pPathID, const char *pEncodingName, const char *pFormatName, CDmElement *pRoot ) { // NOTE: This guarantees full path names for pathids char pFullPath[ MAX_PATH ]; if ( !GenerateFullPath( pFileName, pPathID, pFullPath, sizeof( pFullPath ) ) ) { Warning( "CDataModel: Unable to generate full path for file %s\n", pFileName ); return false; } if ( g_pFullFileSystem->FileExists( pFullPath, pPathID ) ) { if ( !g_pFullFileSystem->IsFileWritable( pFullPath, pPathID ) ) { Warning( "CDataModel: Unable to overwrite readonly file %s\n", pFullPath ); return false; } } if ( !pEncodingName ) { pEncodingName = GetDefaultEncoding( pFormatName ); } bool bIsBinary = IsEncodingBinary( pEncodingName ); if ( bIsBinary ) { CUtlStreamBuffer buf( pFullPath, pPathID, 0, true ); if ( !buf.IsValid() ) { Warning( "CDataModel: Unable to open file \"%s\"\n", pFullPath ); return false; } bool bOk = Serialize( buf, pEncodingName, pFormatName, pRoot->GetHandle() ); if ( !bOk ) return false; } else { CUtlBuffer buf( 0, 0, CUtlBuffer::TEXT_BUFFER ); bool bOk = Serialize( buf, pEncodingName, pFormatName, pRoot->GetHandle() ); if ( !bOk ) return false; if ( !g_pFullFileSystem->WriteFile( pFullPath, pPathID, buf ) ) { Warning( "CDataModel: Unable to open file \"%s\"\n", pFullPath ); return false; } } DmFileId_t fileid = GetFileId( pFullPath ); if ( fileid != DMFILEID_INVALID ) { SetFileModificationUTCTime( fileid, GetCurrentUTCTime() ); } return true; } DmFileId_t CDataModel::RestoreFromFile( char const *pFileName, char const *pPathID, const char *pEncodingHint, CDmElement **ppRoot, DmConflictResolution_t idConflictResolution /*= CR_DELETE_NEW*/, DmxHeader_t *pHeaderOut /*= NULL*/ ) { // NOTE: This guarantees full path names for pathids char pFullPath[ MAX_PATH ]; if ( !GenerateFullPath( pFileName, pPathID, pFullPath, sizeof( pFullPath ) ) ) { Warning( "CDataModel: Unable to generate full path for file %s\n", pFileName ); return DMFILEID_INVALID; } char *pTemp = (char*)_alloca( DMX_MAX_HEADER_LENGTH + 1 ); CUtlBuffer typeBuf( pTemp, DMX_MAX_HEADER_LENGTH ); if ( !g_pFullFileSystem->ReadFile( pFullPath, pPathID, typeBuf, DMX_MAX_HEADER_LENGTH ) ) { Warning( "CDataModel: Unable to open file %s\n", pFullPath ); return DMFILEID_INVALID; } DmxHeader_t _header; DmxHeader_t *pHeader = pHeaderOut ? pHeaderOut : &_header; bool bSuccess = ReadDMXHeader( typeBuf, pHeader ); if ( !bSuccess ) { if ( !pEncodingHint ) { Warning( "CDataModel: Unable to determine DMX encoding for file %s\n", pFullPath ); return DMFILEID_INVALID; } if ( !IsValidNonDMXFormat( pEncodingHint ) ) { Warning( "CDataModel: Invalid DMX encoding hint '%s' for file %s\n", pEncodingHint, pFullPath ); return DMFILEID_INVALID; } // non-dmx file importers don't have versions or formats, just encodings V_strncpy( pHeader->encodingName, pEncodingHint, sizeof( pHeader->encodingName ) ); pHeader->formatName[0] = 0; } DmElementHandle_t hRootElement; bool bIsBinary = IsEncodingBinary( pHeader->encodingName ); if ( bIsBinary ) { CUtlStreamBuffer buf( pFullPath, pPathID, CUtlBuffer::READ_ONLY ); if ( !buf.IsValid() ) { Warning( "CDataModel: Unable to open file '%s'\n", pFullPath ); return DMFILEID_INVALID; } if ( !Unserialize( buf, pHeader->encodingName, pHeader->formatName, pEncodingHint, pFullPath, idConflictResolution, hRootElement ) ) return DMFILEID_INVALID; } else { CUtlBuffer buf( 0, 0, CUtlBuffer::READ_ONLY | CUtlBuffer::TEXT_BUFFER ); if ( !g_pFullFileSystem->ReadFile( pFullPath, pPathID, buf ) ) { Warning( "CDataModel: Unable to open file '%s'\n", pFullPath ); return DMFILEID_INVALID; } if ( !Unserialize( buf, pHeader->encodingName, pHeader->formatName, pEncodingHint, pFullPath, idConflictResolution, hRootElement ) ) return DMFILEID_INVALID; } *ppRoot = g_pDataModel->GetElement( hRootElement ); DmFileId_t fileid = g_pDataModel->GetFileId( pFullPath ); Assert( fileid != DMFILEID_INVALID ); if ( fileid != DMFILEID_INVALID ) { SetFileModificationUTCTime( fileid, g_pFullFileSystem->GetFileTime( pFullPath ) ); } return fileid; } //----------------------------------------------------------------------------- // Is this a DMX file format? //----------------------------------------------------------------------------- bool CDataModel::IsDMXFormat( CUtlBuffer &buf ) const { DmxHeader_t header; bool bSuccess = ReadDMXHeader( buf, &header ); buf.SeekGet( CUtlBuffer::SEEK_HEAD, 0 ); return bSuccess; } //----------------------------------------------------------------------------- // Serialization of a element tree into a utlbuffer //----------------------------------------------------------------------------- bool CDataModel::Serialize( CUtlBuffer &outBuf, const char *pEncodingName, const char *pFormatName, DmElementHandle_t hRoot ) { if ( !pEncodingName ) { pEncodingName = outBuf.IsText() ? "keyvalues2" : "binary"; } // Find a serializer appropriate for the file format. IDmSerializer *pSerializer = FindSerializer( pEncodingName ); if ( !pSerializer ) { Warning("Serialize: File encoding '%s' is undefined!\n", pEncodingName ); return false; } // Ensure the utlbuffer is in the appropriate format (binary/text) bool bIsText = outBuf.IsText(); bool bIsCRLF = outBuf.ContainsCRLF(); CUtlBuffer outTextBuffer( 0, 0, CUtlBuffer::TEXT_BUFFER ); CUtlBuffer *pActualOutBuf = &outBuf; if ( pSerializer->IsBinaryFormat() ) { if ( outBuf.IsText() ) { if ( !outBuf.ContainsCRLF() ) { Warning( "Serialize: Format %s expects to be written to a binary format, but the buffer is a text-format buffer\n", pFormatName ); return false; } outBuf.SetBufferType( false, false ); } } else { // If we want text, but the binbuf is binary; recast it to a text buffer w/ CRLF if ( !outBuf.IsText() ) { outBuf.SetBufferType( true, true ); } if ( outBuf.ContainsCRLF() ) { // If we want text, but the binbuf expects CRLF, then we must do a conversion pass pActualOutBuf = &outTextBuffer; } } if ( pSerializer->StoresVersionInFile() ) { // Write the format name into the file using XML format so that // 3rd-party XML readers can read the file without fail pActualOutBuf->Printf( "%s encoding %s %d format %s %d %s\n", DMX_VERSION_STARTING_TOKEN, pEncodingName, pSerializer->GetCurrentVersion(), pFormatName, GetCurrentFormatVersion( pFormatName ), DMX_VERSION_ENDING_TOKEN ); } // Now write the file using the appropriate format CDmElement *pRoot = GetElement( hRoot ); bool bOk = pSerializer->Serialize( *pActualOutBuf, pRoot ); if ( bOk ) { if ( pActualOutBuf == &outTextBuffer ) { outTextBuffer.ConvertCRLF( outBuf ); } } outBuf.SetBufferType( bIsText, bIsCRLF ); return bOk; } //----------------------------------------------------------------------------- // Read the header, return the version (or false if it's not a DMX file) //----------------------------------------------------------------------------- bool CDataModel::ReadDMXHeader( CUtlBuffer &inBuf, DmxHeader_t *pHeader ) const { Assert( pHeader ); if ( !pHeader ) return false; // Make the buffer capable of being read as text bool bIsText = inBuf.IsText(); bool bHasCRLF = inBuf.ContainsCRLF(); inBuf.SetBufferType( true, !bIsText || bHasCRLF ); char headerStr[ DMX_MAX_HEADER_LENGTH ]; bool bOk = inBuf.ParseToken( DMX_VERSION_STARTING_TOKEN, DMX_VERSION_ENDING_TOKEN, headerStr, sizeof( headerStr ) ); if ( bOk ) { #ifdef _WIN32 int nAssigned = sscanf_s( headerStr, "encoding %s %d format %s %d\n", pHeader->encodingName, DMX_MAX_FORMAT_NAME_MAX_LENGTH, &( pHeader->nEncodingVersion ), pHeader->formatName, DMX_MAX_FORMAT_NAME_MAX_LENGTH, &( pHeader->nFormatVersion ) ); #else int nAssigned = sscanf( headerStr, "encoding %s %d format %s %d\n", pHeader->encodingName, &( pHeader->nEncodingVersion ), pHeader->formatName, &( pHeader->nFormatVersion ) ); #endif bOk = nAssigned == 4; } if ( !bOk ) { inBuf.SeekGet( CUtlBuffer::SEEK_HEAD, 0 ); bOk = inBuf.ParseToken( DMX_LEGACY_VERSION_STARTING_TOKEN, DMX_LEGACY_VERSION_ENDING_TOKEN, pHeader->formatName, DMX_MAX_FORMAT_NAME_MAX_LENGTH ); if ( bOk ) { const char *pEncoding = GetEncodingFromLegacyFormat( pHeader->formatName ); if ( pEncoding ) { V_strncpy( pHeader->encodingName, pEncoding, DMX_MAX_FORMAT_NAME_MAX_LENGTH ); pHeader->nEncodingVersion = 0; // the first encoding version pHeader->nFormatVersion = -1; // this value is ignored for legacy formats } else { bOk = false; } } } inBuf.SetBufferType( bIsText, bHasCRLF ); return bOk; } const char *CDataModel::GetEncodingFromLegacyFormat( const char *pLegacyFormatName ) const { if ( StringHasPrefixCaseSensitive( pLegacyFormatName, "binary_v" ) ) return "binary"; if ( StringHasPrefixCaseSensitive( pLegacyFormatName, "sfm_v" ) ) return "binary"; if ( StringHasPrefixCaseSensitive( pLegacyFormatName, "keyvalues2_v" ) ) return "keyvalues2"; if ( StringHasPrefixCaseSensitive( pLegacyFormatName, "keyvalues2_flat_v" ) ) return "keyvalues2_flat"; return NULL; } bool CDataModel::IsLegacyFormat( const char *pFormatName ) const { return GetEncodingFromLegacyFormat( pFormatName ) != NULL; } bool CDataModel::IsValidNonDMXFormat( const char *pEncodingName ) const { IDmSerializer *pSerializer = FindSerializer( pEncodingName ); return pSerializer && !pSerializer->StoresVersionInFile(); } // used to skip auto-creation of child elements during unserialization bool CDataModel::IsUnserializing() { return m_bIsUnserializing; } int CDataModel::GetCurrentFormatVersion( const char *pFormatName ) { IDmFormatUpdater *pUpdater = FindFormatUpdater( pFormatName ); if ( !pUpdater ) return -1; // invalid version # return pUpdater->GetCurrentVersion(); } //----------------------------------------------------------------------------- // Unserializes, returns the root of the unserialized tree in ppRoot //----------------------------------------------------------------------------- bool CDataModel::Unserialize( CUtlBuffer &inBuf, const char *pEncodingName, const char *pRequiredFormat, const char *pUnused, const char *pFileName, DmConflictResolution_t idConflictResolution, DmElementHandle_t &hRoot ) { ClearUndo(); CDisableUndoScopeGuard sg; if ( !pEncodingName ) { pEncodingName = inBuf.IsText() ? "keyvalues2" : "binary"; } // Find a serializer appropriate for the file format. IDmSerializer *pSerializer = FindSerializer( pEncodingName ); if ( !pSerializer ) { Warning( "Unerialize: DMX file encoding %s is undefined!\n", pEncodingName ); return false; } g_pMemAlloc->heapchk(); DmxHeader_t header; bool bStoresVersionInFile = pSerializer->StoresVersionInFile(); bool bIsCurrentVersion = false; // for formats that don't store a format, files are currently always *not* // at the current version since they need to be converted to dmx const char *pDestFormatName; if ( bStoresVersionInFile ) { bool bOk = ReadDMXHeader( inBuf, &header ); if ( !bOk ) { Warning( "Unserialize: unable to read DMX header!\n" ); return false; } int nCurrentFormatVersion = GetCurrentFormatVersion( header.formatName ); if ( header.nFormatVersion > nCurrentFormatVersion ) { Warning( "Unserialize: file format newer than executable's!\n" ); return false; } bIsCurrentVersion = !IsLegacyFormat( header.formatName ) && nCurrentFormatVersion == header.nFormatVersion; pDestFormatName = IsLegacyFormat( header.formatName ) ? GENERIC_DMX_FORMAT : header.formatName; } else { pDestFormatName = pSerializer->GetImportedFormat(); Q_strncpy( header.formatName, pDestFormatName, sizeof(header.formatName) ); header.nFormatVersion = pSerializer->GetImportedVersion(); header.nEncodingVersion = pSerializer->GetCurrentVersion(); Q_strncpy( header.encodingName, pSerializer->GetName(), sizeof(header.encodingName) ); } // if we're not in dmxconvert, and we're not at the latest version, call dmxconvert and unserialize from the converted file if ( !m_bOnlyCreateUntypedElements && !bIsCurrentVersion ) { char path[ MAX_PATH ]; V_ExtractFilePath( pFileName, path, sizeof( path ) ); if ( !V_IsAbsolutePath( path ) ) { g_pFullFileSystem->GetCurrentDirectory( path, sizeof( path ) ); } bool bFileFound = g_pFullFileSystem->FileExists( pFileName ); if ( !bFileFound ) { char *pTempFileName = ( char* )stackalloc( MAX_PATH ); // NOTE - stackalloc frees on function return, not scope return V_ComposeFileName( path, "_temp_input_file_.dmx", pTempFileName, MAX_PATH ); pFileName = pTempFileName; g_pFullFileSystem->WriteFile( pFileName, NULL, inBuf ); } char tempFileName[ MAX_PATH ]; V_ComposeFileName( path, "_temp_conversion_file_.dmx", tempFileName, sizeof( tempFileName ) ); V_RemoveDotSlashes( tempFileName ); const char *pDestEncodingName = "binary"; char cmdline[ 2 * MAX_PATH + 256 ]; V_snprintf( cmdline, sizeof( cmdline ), "dmxconvert -allowdebug -i \"%s\" -ie %s -o \"%s\" -oe %s -of %s", pFileName, header.encodingName, tempFileName, pDestEncodingName, pDestFormatName ); IProcess *pProcess = g_pProcessUtils->StartProcess( cmdline, 0 ); if ( !pProcess ) { Warning( "Unerialize: Unable to start dmxconvert process\n" ); return false; } pProcess->WaitUntilComplete(); pProcess->Release(); bool bSuccess; { if ( !inBuf.IsText() ) { CUtlStreamBuffer buf( tempFileName, NULL, CUtlBuffer::READ_ONLY ); if ( !buf.IsValid() ) { Warning( "Unerialize: Unable to open temp file \"%s\"\n", tempFileName ); return false; } // yes, this passes in pFileName, even though it read from tempFileName - pFileName is only used for marking debug messages and setting fileid bSuccess = Unserialize( buf, pDestEncodingName, pDestFormatName, pDestFormatName, pFileName, idConflictResolution, hRoot ); } else { CUtlBuffer buf( 0, 0, CUtlBuffer::READ_ONLY ); if ( !g_pFullFileSystem->ReadFile( tempFileName, NULL, buf ) ) { Warning( "Unerialize: Unable to open temp file \"%s\"\n", tempFileName ); return false; } // yes, this passes in pFileName, even though it read from tempFileName - pFileName is only used for marking debug messages and setting fileid bSuccess = Unserialize( buf, pDestEncodingName, pDestFormatName, pDestFormatName, pFileName, idConflictResolution, hRoot ); } } if ( !bFileFound ) { g_pFullFileSystem->RemoveFile( pFileName ); } g_pFullFileSystem->RemoveFile( tempFileName ); return bSuccess; } // advance the buffer the the end of the header if ( bStoresVersionInFile ) { if ( V_strcmp( pEncodingName, header.encodingName ) != 0 ) return false; if ( pRequiredFormat && pRequiredFormat[0] ) { if ( V_strcmp( pRequiredFormat, header.formatName ) != 0 ) { Warning( "Unexpected format '%s' reading file '%s' - expected format '%s'\n", pRequiredFormat, pFileName, header.formatName ); } } if ( pSerializer->IsBinaryFormat() ) { // For binary formats, we gotta keep reading until we hit the string terminator // that occurred after the version line. while( inBuf.GetChar() != 0 ) { if ( !inBuf.IsValid() ) break; } } } m_bIsUnserializing = true; DmFileId_t fileid = FindOrCreateFileId( pFileName ); // Now read the file using the appropriate format CDmElement *pRoot; bool bOk = pSerializer->Unserialize( inBuf, pEncodingName, header.nEncodingVersion, header.formatName, header.nFormatVersion, fileid, idConflictResolution, &pRoot ); if ( bOk ) { hRoot = pRoot ? pRoot->GetHandle() : DMELEMENT_HANDLE_INVALID; SetFileModificationUTCTime( fileid, 0 ); SetFileFormat( fileid, header.formatName ); SetFileRoot( fileid, hRoot ); } else { RemoveFileId( fileid ); } m_bIsUnserializing = false; return bOk; } bool CDataModel::UpdateUnserializedElements( const char *pSourceFormatName, int nSourceFormatVersion, DmFileId_t fileid, DmConflictResolution_t idConflictResolution, CDmElement **ppRoot ) { if ( IsLegacyFormat( pSourceFormatName ) ) { IDmLegacyUpdater *pLegacyUpdater = FindLegacyUpdater( pSourceFormatName ); if ( pLegacyUpdater ) { if ( !pLegacyUpdater->Update( ppRoot ) ) return false; } // if there's no legacy updater found, then this is already the latest legacy format pSourceFormatName = GENERIC_DMX_FORMAT; } IDmFormatUpdater *pFormatUpdater = FindFormatUpdater( pSourceFormatName ); if ( !pFormatUpdater ) return true; // Used to return false, if no format updater exists, simply means a format updater hasn't been required yet return pFormatUpdater->Update( ppRoot, nSourceFormatVersion ); } //----------------------------------------------------------------------------- // file id reference methods //----------------------------------------------------------------------------- int CDataModel::NumFileIds() { return m_openFiles.GetHandleCount(); } DmFileId_t CDataModel::GetFileId( int i ) { Assert( i >= 0 && i < ( int )m_openFiles.GetHandleCount() ); if ( i < 0 || i >= ( int )m_openFiles.GetHandleCount() ) return DMFILEID_INVALID; return ( DmFileId_t )m_openFiles.GetHandleFromIndex( i ); } void CleanupFileName( char *pOutFileName, size_t nOutLen, const char *pInFileName ) { char fixedFileName[ MAX_PATH ]; V_FixupPathName( fixedFileName, sizeof( fixedFileName ), pInFileName ); if ( !V_IsAbsolutePath( fixedFileName ) ) { char currentDir[ MAX_PATH ]; g_pFullFileSystem->GetCurrentDirectory( currentDir, sizeof( currentDir ) ); V_MakeAbsolutePath( pOutFileName, nOutLen, fixedFileName, currentDir ); } else { V_strncpy( pOutFileName, fixedFileName, nOutLen ); } } DmFileId_t CDataModel::FindOrCreateFileId( const char *pFileName ) { Assert( pFileName && *pFileName ); if ( !pFileName || !*pFileName ) return DMFILEID_INVALID; char fixedFileName[ MAX_PATH ]; CleanupFileName( fixedFileName, sizeof( fixedFileName ), pFileName ); DmFileId_t fileid = GetFileId( fixedFileName ); if ( fileid != DMFILEID_INVALID ) { // Assert( IsFileLoaded( fileid ) ); MarkFileLoaded( fileid ); // this is sort of a hack, but I'm planning a rewrite phase on all this anyways - joe return fileid; } fileid = ( DmFileId_t )m_openFiles.AddHandle(); m_openFiles.SetHandle( fileid, new FileElementSet_t( GetSymbol( fixedFileName ) ) ); return fileid; } void CDataModel::RemoveFileId( DmFileId_t fileid ) { FileElementSet_t *fes = m_openFiles.GetHandle( fileid ); Assert( fes || fileid == DMFILEID_INVALID ); if ( !fes ) return; if ( fes->m_bLoaded ) { UnloadFile( fileid, true ); } delete fes; m_openFiles.RemoveHandle( fileid ); } DmFileId_t CDataModel::GetFileId( const char *pFileName ) { char fixedFileName[ MAX_PATH ]; CleanupFileName( fixedFileName, sizeof( fixedFileName ), pFileName ); CUtlSymbolLarge filenameSym = m_SymbolTable.Find( fixedFileName ); if ( filenameSym == UTL_INVAL_SYMBOL_LARGE ) return DMFILEID_INVALID; int nFiles = m_openFiles.GetHandleCount(); for ( int i = 0; i < nFiles; ++i ) { DmFileId_t fileid = ( DmFileId_t )m_openFiles.GetHandleFromIndex( i ); FileElementSet_t *fes = m_openFiles.GetHandle( fileid ); Assert( fes || !m_openFiles.IsHandleValid( fileid ) ); if ( fes && fes->m_filename == filenameSym ) return fileid; } return DMFILEID_INVALID; } const char *CDataModel::GetFileName( DmFileId_t fileid ) { FileElementSet_t *fes = m_openFiles.GetHandle( fileid ); Assert( fes || fileid == DMFILEID_INVALID ); return fes ? fes->m_filename.String() : NULL; } void CDataModel::SetFileName( DmFileId_t fileid, const char *pFileName ) { FileElementSet_t *fes = m_openFiles.GetHandle( fileid ); Assert( fes ); if ( !fes ) return; char pFixedFileName[ MAX_PATH ]; CleanupFileName( pFixedFileName, sizeof( pFixedFileName ), pFileName ); fes->m_filename = GetSymbol( pFixedFileName ); } const char *CDataModel::GetFileFormat( DmFileId_t fileid ) { FileElementSet_t *fes = m_openFiles.GetHandle( fileid ); Assert( fes || fileid == DMFILEID_INVALID ); return fes ? fes->m_format.String() : NULL; } void CDataModel::SetFileFormat( DmFileId_t fileid, const char *pFormat ) { FileElementSet_t *fes = m_openFiles.GetHandle( fileid ); Assert( fes ); if ( !fes ) return; fes->m_format = GetSymbol( pFormat ); } DmElementHandle_t CDataModel::GetFileRoot( DmFileId_t fileid ) { FileElementSet_t *fes = m_openFiles.GetHandle( fileid ); Assert( fes || fileid == DMFILEID_INVALID ); return fes ? (DmElementHandle_t)fes->m_hRoot : DMELEMENT_HANDLE_INVALID; } void CDataModel::SetFileRoot( DmFileId_t fileid, DmElementHandle_t hRoot ) { FileElementSet_t *fes = m_openFiles.GetHandle( fileid ); Assert( fes ); if ( !fes ) return; if ( fes->m_hRoot == hRoot ) return; fes->m_hRoot = hRoot; } long CDataModel::GetFileModificationUTCTime( DmFileId_t fileid ) { FileElementSet_t *fes = m_openFiles.GetHandle( fileid ); Assert( fes || fileid == DMFILEID_INVALID ); return fes ? fes->m_fileModificationTime : 0; } void CDataModel::SetFileModificationUTCTime( DmFileId_t fileid, long fileModificationTime ) { FileElementSet_t *fes = m_openFiles.GetHandle( fileid ); Assert( fes || fileid == DMFILEID_INVALID ); if ( !fes ) return; #ifdef _DEBUG char oldFileTime[ 256 ]; char newFileTime[ 256 ]; UTCTimeToString( oldFileTime, sizeof( oldFileTime ), fes->m_fileModificationTime ); UTCTimeToString( newFileTime, sizeof( newFileTime ), fileModificationTime ); Msg( "Setting file modification time for '%s' from %d: %s to %d: %s\n", fes->m_filename.String(), fes->m_fileModificationTime, oldFileTime, fileModificationTime, newFileTime ); #endif // _DEBUG fes->m_fileModificationTime = fileModificationTime; } long CDataModel::GetCurrentUTCTime() { return _time32( NULL ); } void CDataModel::UTCTimeToString( char *pString, int maxChars, long fileTime ) { return g_pFullFileSystem->FileTimeToString( pString, maxChars, fileTime ); } bool CDataModel::IsFileLoaded( DmFileId_t fileid ) { FileElementSet_t *fes = m_openFiles.GetHandle( fileid ); Assert( fes || fileid == DMFILEID_INVALID ); return fes ? fes->m_bLoaded : false; } void CDataModel::UnloadFile( DmFileId_t fileid, bool bDeleteElements ) { ClearUndo(); CDisableUndoScopeGuard sg; int nHandles = ( int )m_Handles.GetHandleCount(); for ( int i = 0; i < nHandles; ++i ) { DmElementHandle_t hElement = ( DmElementHandle_t )m_Handles.GetHandleFromIndex( i ); if ( hElement == DMELEMENT_HANDLE_INVALID ) continue; CDmElement *pElement = GetElement( hElement ); if ( !pElement || pElement->GetFileId() != fileid ) continue; DeleteElement( hElement, bDeleteElements ? HR_ALWAYS : HR_IF_NOT_REFERENCED ); } FileElementSet_t *fes = m_openFiles.GetHandle( fileid ); if ( fes ) { fes->m_bLoaded = false; } } void CDataModel::UnloadFile( DmFileId_t fileid ) { UnloadFile( fileid, false ); } void CDataModel::MarkFileLoaded( DmFileId_t fileid ) { FileElementSet_t *fes = m_openFiles.GetHandle( fileid ); Assert( fes ); if ( !fes ) return; fes->m_bLoaded = true; } int CDataModel::NumElementsInFile( DmFileId_t fileid ) { FileElementSet_t *fes = m_openFiles.GetHandle( fileid ); Assert( fes ); if ( !fes ) return 0; return fes->m_nElements; } //----------------------------------------------------------------------------- // file id reference methods not in IDataModel //----------------------------------------------------------------------------- void CDataModel::RemoveElementFromFile( DmElementHandle_t hElement, DmFileId_t fileid ) { if ( fileid == DMFILEID_INVALID ) return; FileElementSet_t *fes = m_openFiles.GetHandle( fileid ); Assert( fes ); if ( !fes ) return; --fes->m_nElements; } void CDataModel::AddElementToFile( DmElementHandle_t hElement, DmFileId_t fileid ) { if ( fileid == DMFILEID_INVALID ) return; FileElementSet_t *fes = m_openFiles.GetHandle( fileid ); Assert( fes ); if ( !fes ) return; ++fes->m_nElements; } // search id->handle table (both loaded and unloaded) for id, and if not found, create a new handle, map it to the id and return it DmElementHandle_t CDataModel::FindOrCreateElementHandle( const DmObjectId_t &id ) { UtlHashHandle_t h = m_elementIds.Find( id ); if ( h != m_elementIds.InvalidHandle() ) return m_elementIds[ h ]; h = m_unloadedIdElementMap.Find( ElementIdHandlePair_t( id ) ); // TODO - consider optimizing find to take just an id if ( h != m_unloadedIdElementMap.InvalidHandle() ) return m_unloadedIdElementMap[ h ].m_ref.m_hElement; DmElementHandle_t hElement = AcquireElementHandle(); m_unloadedIdElementMap.Insert( ElementIdHandlePair_t( id, DmElementReference_t( hElement ) ) ); MarkHandleInvalid( hElement ); return hElement; } // changes an element's id and associated mappings - generally during unserialization DmElementHandle_t CDataModel::ChangeElementId( DmElementHandle_t hElement, const DmObjectId_t &oldId, const DmObjectId_t &newId ) { UtlHashHandle_t oldHash = m_elementIds.Find( oldId ); Assert( oldHash != m_elementIds.InvalidHandle() ); if ( oldHash == m_elementIds.InvalidHandle() ) return hElement; Assert( m_elementIds[ oldHash ] == hElement ); // can't change an element's id once it has attributes or handles linked to it CDmElement *pElement = GetElement( hElement ); Assert( pElement ); if ( !pElement ) return DMELEMENT_HANDLE_INVALID; Assert( !CDmeElementAccessor::GetReference( pElement )->IsWeaklyReferenced() ); UtlHashHandle_t newHash = m_elementIds.Find( newId ); if ( newHash != m_elementIds.InvalidHandle() ) return DMELEMENT_HANDLE_INVALID; // can't change an element's id to the id of an existing element // remove old element entry m_elementIds.Remove( oldHash ); // change the element id CDmeElementAccessor::SetId( pElement, newId ); newHash = m_unloadedIdElementMap.Find( ElementIdHandlePair_t( newId ) ); if ( newHash == m_unloadedIdElementMap.InvalidHandle() ) { // the newId has never been seen before - keep the element handle the same and rehash into the id->handle map m_elementIds.Insert( hElement ); return hElement; } // else, the newId is being referenced by some other element // change element to use newId and the associated handle from an element reference DmElementReference_t &newRef = m_unloadedIdElementMap[ newHash ].m_ref; DmElementHandle_t newHandle = newRef.m_hElement; Assert( newHandle != hElement ); // no two ids should have the same handle Assert( !m_Handles.IsHandleValid( newHandle ) ); // unloaded elements shouldn't have valid handles m_Handles.SetHandle( newHandle, GetElement( hElement ) ); CDmeElementAccessor::ChangeHandle( pElement, newHandle ); CDmeElementAccessor::SetReference( pElement, newRef ); ReleaseElementHandle( hElement ); // move new element entry from the unloaded map to the loaded map m_elementIds.Insert( newHandle ); m_unloadedIdElementMap.Remove( newHash ); return newHandle; } DmElementReference_t *CDataModel::FindElementReference( DmElementHandle_t hElement, DmObjectId_t **ppId /* = NULL */ ) { if ( ppId ) { *ppId = NULL; } CDmElement* pElement = GetElement( hElement ); if ( pElement ) return CDmeElementAccessor::GetReference( pElement ); for ( UtlHashHandle_t h = m_unloadedIdElementMap.GetFirstHandle(); h != m_unloadedIdElementMap.InvalidHandle(); h = m_unloadedIdElementMap.GetNextHandle( h ) ) { DmElementReference_t &ref = m_unloadedIdElementMap[ h ].m_ref; if ( ref.m_hElement == hElement ) { if ( ppId ) { *ppId = &m_unloadedIdElementMap[ h ].m_id; } return &ref; } } return NULL; } void CDataModel::DontAutoDelete( DmElementHandle_t hElement ) { // this artificially adds a strong reference to the element, so it won't ever get unref'ed to 0 // the only ways for this element to go away are explicit deletion, or file unload OnElementReferenceAdded( hElement, HT_STRONG ); } void CDataModel::OnElementReferenceAdded( DmElementHandle_t hElement, CDmAttribute *pAttribute ) { Assert( pAttribute ); if ( !pAttribute ) return; if ( hElement == DMELEMENT_HANDLE_INVALID ) return; DmElementReference_t *pRef = FindElementReference( hElement ); if ( !pRef ) return; // Msg( "OnElementReferenceAdded: %s 0x%x '%s' referenced by %s 0x%x '%s'\n", // GetElementType( hElement ).String(), hElement, GetElementName( hElement ), // pAttribute->GetOwner()->GetTypeString(), pAttribute->GetOwner()->GetHandle(), pAttribute->GetName() ); if ( pRef->m_bHasEverBeenReferenced && !pRef->IsStronglyReferenced() ) { CDmElement *pElement = GetElement( hElement ); if ( !pElement || pElement->GetFileId() != DMFILEID_INVALID ) { m_bDeleteOrphanedElements = true; } } pRef->m_bHasEverBeenReferenced = true; pRef->AddAttribute( pAttribute ); } void CDataModel::OnElementReferenceAdded( DmElementHandle_t hElement, HandleType_t handleType ) { if ( hElement == DMELEMENT_HANDLE_INVALID ) return; DmElementReference_t *pRef = FindElementReference( hElement ); if ( !pRef ) return; // if ( handleType != HT_WEAK ) // { // int nCount = handleType == HT_STRONG ? pRef->m_nStrongHandleCount : pRef->m_nUndoHandleCount; // Msg( "OnElementReferenceAdded: %s 0x%x \"%s\" referenced by %s handle\n", // GetElementType( hElement ).String(), hElement, GetElementName( hElement ), // handleType == HT_STRONG ? "strong" : "undo" ); // } if ( handleType != HT_WEAK ) { bool bUpdate = handleType == HT_STRONG ? pRef->m_bHasEverBeenReferenced && !pRef->IsStronglyReferenced() : !pRef->IsReferencedByUndo(); if ( bUpdate ) { CDmElement *pElement = GetElement( hElement ); if ( !pElement || pElement->GetFileId() != DMFILEID_INVALID ) { m_bDeleteOrphanedElements = true; } } pRef->m_bHasEverBeenReferenced = true; } switch ( handleType ) { case HT_WEAK: ++pRef->m_nWeakHandleCount; break; case HT_STRONG: ++pRef->m_nStrongHandleCount; break; case HT_UNDO: ++pRef->m_nUndoHandleCount; break; } } void CDataModel::OnElementReferenceRemoved( DmElementHandle_t hElement, CDmAttribute *pAttribute ) { MEM_ALLOC_CREDIT(); Assert( pAttribute ); if ( !pAttribute ) return; if ( hElement == DMELEMENT_HANDLE_INVALID ) return; DmElementReference_t *pRef = FindElementReference( hElement ); if ( !pRef ) return; pRef->RemoveAttribute( pAttribute ); // Msg( "OnElementReferenceRemoved: %s 0x%x '%s' referenced by %s 0x%x '%s'\n", // GetElementType( hElement ).String(), hElement, GetElementName( hElement ), // pAttribute->GetOwner()->GetTypeString(), pAttribute->GetOwner()->GetHandle(), pAttribute->GetName() ); if ( !pRef->IsStronglyReferenced() ) { CDmElement *pElement = GetElement( hElement ); if ( !pElement || pElement->GetFileId() != DMFILEID_INVALID ) { m_bDeleteOrphanedElements = true; } } } void CDataModel::OnElementReferenceRemoved( DmElementHandle_t hElement, HandleType_t handleType ) { MEM_ALLOC_CREDIT(); if ( hElement == DMELEMENT_HANDLE_INVALID ) return; DmElementReference_t *pRef = FindElementReference( hElement ); if ( !pRef ) return; switch ( handleType ) { case HT_WEAK: --pRef->m_nWeakHandleCount; break; case HT_STRONG: --pRef->m_nStrongHandleCount; break; case HT_UNDO: --pRef->m_nUndoHandleCount; break; } // if ( handleType != HT_WEAK ) // { // int nCount = handleType == HT_STRONG ? pRef->m_nStrongHandleCount : pRef->m_nUndoHandleCount; // Msg( "OnElementReferenceRemoved: %s 0x%x \"%s\" referenced by %s handle\n", // GetElementType( hElement ).String(), hElement, GetElementName( hElement ), // handleType == HT_STRONG ? "strong" : "undo" ); // } if ( handleType != HT_WEAK ) { bool bUpdate = handleType == HT_STRONG ? !pRef->IsStronglyReferenced() : !pRef->IsReferencedByUndo(); if ( bUpdate ) { CDmElement *pElement = GetElement( hElement ); if ( !pElement || pElement->GetFileId() != DMFILEID_INVALID ) { m_bDeleteOrphanedElements = true; } } } } void CDataModel::RemoveUnreferencedElements() { CDisableUndoScopeGuard sg; if ( m_bDeleteOrphanedElements ) { FindAndDeleteOrphanedElements(); m_bDeleteOrphanedElements = false; } } void UpdateReferenceToElementsNotInUndoWithParityBitSet( CDmAttribute *pAttr, CDmElement *pChild, int nParityBit, bool bDetach ) { if ( !pAttr || !pChild ) return; if ( pChild->IsOnlyInUndo() || !pChild->GetParity( nParityBit ) ) return; DmElementReference_t *pRef = CDmeElementAccessor::GetReference( pChild ); if ( bDetach ) { // Msg( "removing " ); pRef->RemoveAttribute( pAttr ); } else { Assert( !CDmeElementAccessor::GetReference( pChild )->FindAttribute( pAttr ) ); // Msg( "adding " ); pRef->AddAttribute( pAttr ); } // CDmElement *pOwner = pAttr->GetOwner(); // Msg( "reference to %s 0x%x '%s' from %s 0x%x '%s' %s\n", // pChild->GetTypeString(), pChild->GetHandle(), pChild->GetName(), // pOwner->GetTypeString(), pOwner->GetHandle(), pOwner->GetName(), // pAttr->GetName() ); } void UpdateReferencesToElementsNotInUndoWithParityBitSet( CDmElement *pElement, int nParityBit, bool bDetach ) { for ( CDmAttribute *pAttr = pElement->FirstAttribute(); pAttr; pAttr = pAttr->NextAttribute() ) { DmAttributeType_t type = pAttr->GetType(); if ( type == AT_ELEMENT ) { UpdateReferenceToElementsNotInUndoWithParityBitSet( pAttr, pAttr->GetValueElement< CDmElement >(), nParityBit, bDetach ); } else if ( type == AT_ELEMENT_ARRAY ) { const CDmrElementArrayConst<> elementArray( pAttr ); int nChildren = elementArray.Count(); for ( int i = 0; i < nChildren; ++i ) { UpdateReferenceToElementsNotInUndoWithParityBitSet( pAttr, elementArray[ i ], nParityBit, bDetach ); } } } } void CDataModel::FindAndDeleteOrphanedElements() { // mark & sweep algorithm for elements enum { BIT_STRONG = 0, BIT_UNDO = 1, }; // clear accessible flag from all elements int nHandles = ( int )m_Handles.GetHandleCount(); for ( int i = 0; i < nHandles; ++i ) { DmElementHandle_t hElement = ( DmElementHandle_t )m_Handles.GetHandleFromIndex( i ); CDmElement *pElement = m_Handles.GetHandle( hElement, false ); // walk through invalidated handles as well (such as deleted elements) if ( !pElement ) continue; DmFileId_t fileid = pElement->GetFileId(); if ( fileid == DMFILEID_INVALID ) continue; pElement->SetParity( false, BIT_STRONG ); pElement->SetParity( false, BIT_UNDO ); } // mark elements accessible from undo system for ( int i = 0; i < nHandles; ++i ) { DmElementHandle_t hElement = ( DmElementHandle_t )m_Handles.GetHandleFromIndex( i ); CDmElement *pElement = m_Handles.GetHandle( hElement, false ); // walk through invalidated handles as well (such as deleted elements) if ( !pElement ) continue; DmFileId_t fileid = pElement->GetFileId(); if ( fileid == DMFILEID_INVALID ) continue; if ( CDmeElementAccessor::GetReference( pElement )->m_nUndoHandleCount == 0 ) continue; pElement->SetParity( true, TD_ALL, BIT_UNDO ); } // mark elements accessible from file roots int nFiles = NumFileIds(); for ( int i = 0; i < nFiles; ++i ) { DmFileId_t fileid = GetFileId( i ); if ( fileid == DMFILEID_INVALID ) continue; DmElementHandle_t hRoot = GetFileRoot( fileid ); CDmElement *pRoot = GetElement( hRoot ); if ( !pRoot ) continue; pRoot->SetParity( true, TD_ALL, BIT_STRONG ); } // mark elements accessible from counted handles for ( DmElementHandle_t hElement = FirstAllocatedElement(); hElement != DMELEMENT_HANDLE_INVALID; hElement = NextAllocatedElement( hElement ) ) { CDmElement *pElement = GetElement( hElement ); if ( !pElement ) continue; DmFileId_t fileid = pElement->GetFileId(); if ( fileid == DMFILEID_INVALID ) continue; // an element is deleted when its strong handle count goes to zero, but if it never leaves zero, it shouldn't be deleted DmElementReference_t *pRef = CDmeElementAccessor::GetReference( pElement ); if ( pRef->m_bHasEverBeenReferenced && pRef->m_nStrongHandleCount == 0 ) continue; pElement->SetParity( true, TD_ALL, BIT_STRONG ); } // BIT_STRONG, IsOnlyInUndo() => being moved out of undo // BIT_STRONG, !IsOnlyInUndo() => remaining out of undo // BIT_UNDO, IsOnlyInUndo() => remaining in undo // BIT_UNDO, !IsOnlyInUndo() => being moved into undo // update element references for ( DmElementHandle_t hElement = FirstAllocatedElement(); hElement != DMELEMENT_HANDLE_INVALID; hElement = NextAllocatedElement( hElement ) ) { CDmElement *pElement = GetElement( hElement ); if ( !pElement ) continue; DmFileId_t fileid = pElement->GetFileId(); if ( fileid == DMFILEID_INVALID ) continue; if ( pElement->GetParity( BIT_STRONG ) ) { if ( pElement->IsOnlyInUndo() ) { // this element is leaving the undo system - add references to elements that aren't in undo (and weren't before) UpdateReferencesToElementsNotInUndoWithParityBitSet( pElement, BIT_STRONG, false ); } } else if ( pElement->GetParity( BIT_UNDO ) ) { if ( !pElement->IsOnlyInUndo() ) { // this element is entering the undo system - remove references to elements that aren't in undo (and weren't before) UpdateReferencesToElementsNotInUndoWithParityBitSet( pElement, BIT_STRONG, true ); } } } // delete elements that aren't accessible for ( DmElementHandle_t hElement = FirstAllocatedElement(); hElement != DMELEMENT_HANDLE_INVALID; hElement = NextAllocatedElement( hElement ) ) { CDmElement *pElement = GetElement( hElement ); if ( !pElement ) continue; DmFileId_t fileid = pElement->GetFileId(); if ( fileid == DMFILEID_INVALID ) continue; // Msg( "%s 0x%08x '%s': %s %s - %s\n", // pElement->GetTypeString(), hElement, pElement->GetName(), // pElement->GetParity( BIT_STRONG ) ? "strong" : "!strong", // pElement->GetParity( BIT_UNDO ) ? "undo" : "!undo", // pElement->IsOnlyInUndo() ? "only in undo" : "not only in undo" ); if ( pElement->GetParity( BIT_STRONG ) ) { if ( pElement->IsOnlyInUndo() ) { // Msg( "adopting %s 0x%08x '%s'\n", pElement->GetTypeString(), hElement, pElement->GetName() ); pElement->SetOnlyInUndo( false ); CDmeElementAccessor::OnAdoptedFromUndo( pElement ); } } else if ( pElement->GetParity( BIT_UNDO ) ) { if ( !pElement->IsOnlyInUndo() ) { // Msg( "orphaning %s 0x%08x '%s'\n", pElement->GetTypeString(), hElement, pElement->GetName() ); pElement->SetOnlyInUndo( true ); CDmeElementAccessor::OnOrphanedToUndo( pElement ); } } else { // Msg( "deleting %s 0x%08x '%s'\n", pElement->GetTypeString(), hElement, pElement->GetName() ); DeleteElement( hElement ); } } } DmElementHandle_t CDataModel::FindElement( const DmObjectId_t &id ) { UtlHashHandle_t h = m_elementIds.Find( id ); if ( h == m_elementIds.InvalidHandle() ) return DMELEMENT_HANDLE_INVALID; return m_elementIds[ h ]; } void CDataModel::GetExistingElements( CElementIdHash &hash ) const { DMX_PROFILE_SCOPE( GetExistingElements_Copy ); hash = m_elementIds; } DmAttributeReferenceIterator_t CDataModel::FirstAttributeReferencingElement( DmElementHandle_t hElement ) { DmElementReference_t *pRef = FindElementReference( hElement ); if ( !pRef || pRef->m_attributes.m_hAttribute == DMATTRIBUTE_HANDLE_INVALID ) return DMATTRIBUTE_REFERENCE_ITERATOR_INVALID; return ( DmAttributeReferenceIterator_t )( intp )&pRef->m_attributes; } DmAttributeReferenceIterator_t CDataModel::NextAttributeReferencingElement( DmAttributeReferenceIterator_t hAttrIter ) { DmAttributeList_t *pList = ( DmAttributeList_t* )hAttrIter; if ( !pList ) return DMATTRIBUTE_REFERENCE_ITERATOR_INVALID; return ( DmAttributeReferenceIterator_t )( intp )pList->m_pNext; } CDmAttribute *CDataModel::GetAttribute( DmAttributeReferenceIterator_t hAttrIter ) { DmAttributeList_t *pList = ( DmAttributeList_t* )hAttrIter; if ( !pList ) return NULL; return GetAttribute( pList->m_hAttribute ); } //----------------------------------------------------------------------------- // Purpose: // Input : buf - // Output : IDmElementInternal //----------------------------------------------------------------------------- CDmElement *CDataModel::Unserialize( CUtlBuffer& buf ) { return NULL; } //----------------------------------------------------------------------------- // Purpose: // Input : *element - // buf - //----------------------------------------------------------------------------- void CDataModel::Serialize( CDmElement *element, CUtlBuffer& buf ) { } //----------------------------------------------------------------------------- // Sets a factory to use if the element type can't be found //----------------------------------------------------------------------------- void CDataModel::SetDefaultElementFactory( IDmElementFactory *pFactory ) { if ( m_bUnableToSetDefaultFactory ) { Assert( 0 ); return; } m_pDefaultFactory = pFactory ? pFactory : &s_DefaultElementFactory; } //----------------------------------------------------------------------------- // Purpose: // Input : *elementName - // factory - //----------------------------------------------------------------------------- void CDataModel::AddElementFactory( CDmElementFactoryHelper *pFactoryHelper ) { Assert( pFactoryHelper ); const char *pClassName = pFactoryHelper->GetClassname(); int idx = m_Factories.Find( pClassName ); if ( idx == m_Factories.InvalidIndex() ) { m_Factories.Insert( pClassName, pFactoryHelper ); } else { // Override the factory? m_Factories[idx] = pFactoryHelper; Warning( "Factory for element type '%s' already exists\n", pClassName ); } } CDmElementFactoryHelper *CDataModel::GetElementFactoryHelper( const char *pElementType ) { int idx = m_Factories.Find( pElementType ); if ( idx == m_Factories.InvalidIndex() ) return NULL; return m_Factories[ idx ]; } bool CDataModel::HasElementFactory( const char *pElementType ) const { int idx = m_Factories.Find( pElementType ); return ( idx != m_Factories.InvalidIndex() ); } int CDataModel::GetFirstFactory() const { return m_Factories.First(); } int CDataModel::GetNextFactory( int index ) const { return m_Factories.Next( index ); } bool CDataModel::IsValidFactory( int index ) const { return m_Factories.IsValidIndex( index ); } char const *CDataModel::GetFactoryName( int index ) const { return m_Factories.GetElementName( index ); } //----------------------------------------------------------------------------- // Purpose: Creates a scene object //----------------------------------------------------------------------------- DmElementHandle_t CDataModel::CreateElement( CUtlSymbolLarge typeSymbol, const char *pElementName, DmFileId_t fileid, const DmObjectId_t *pObjectID ) { return CreateElement( typeSymbol.String(), pElementName, fileid, pObjectID ); } DmElementHandle_t CDataModel::CreateElement( const char *pElementType, const char *pElementName, DmFileId_t fileid, const DmObjectId_t *pObjectID ) { DMX_PROFILE_SCOPE( CDataModel_CreateElement1 ); Assert( !pObjectID || m_elementIds.Find( *pObjectID ) == m_elementIds.InvalidHandle() ); UtlHashHandle_t h = pObjectID ? m_unloadedIdElementMap.Find( ElementIdHandlePair_t( *pObjectID ) ) : m_unloadedIdElementMap.InvalidHandle(); if ( h != m_unloadedIdElementMap.InvalidHandle() ) { CDmElement *pElement = CreateElement( m_unloadedIdElementMap[ h ].m_ref, pElementType, pElementName, fileid, pObjectID ); if ( pElement ) { m_unloadedIdElementMap.Remove( h ); return pElement->GetHandle(); } } else { DmElementHandle_t hElement = AcquireElementHandle(); CDmElement *pElement = CreateElement( DmElementReference_t( hElement ), pElementType, pElementName, fileid, pObjectID ); if ( pElement ) return pElement->GetHandle(); ReleaseElementHandle( hElement ); } return DMELEMENT_HANDLE_INVALID; } class CUndoCreateElement : public CUndoElement { typedef CUndoElement BaseClass; public: CUndoCreateElement() : BaseClass( "CUndoCreateElement" ), m_bKill( false ), m_hElement() { } ~CUndoCreateElement() { if ( m_bKill ) { g_pDataModelImp->MarkHandleValid( m_hElement ); g_pDataModelImp->DeleteElement( m_hElement ); } } void SetElement( DmElementHandle_t hElement ) { Assert( GetElement( hElement ) && GetElement( hElement )->GetFileId() != DMFILEID_INVALID ); m_hElement = hElement; // this has to be delayed so that the element's ref count can be incremented } virtual void Undo() { m_bKill = true; g_pDataModelImp->MarkHandleInvalid( m_hElement ); } virtual void Redo() { m_bKill = false; g_pDataModelImp->MarkHandleValid( m_hElement ); } private: CDmeUndoHandle m_hElement; bool m_bKill; }; //----------------------------------------------------------------------------- // CreateElement references the attribute list passed in via ref, so don't edit or purge ref's attribute list afterwards // this is kosher because the ref either is created on the fly and has no attributes, or is being removed from m_unloadedIdElementMap //----------------------------------------------------------------------------- CDmElement* CDataModel::CreateElement( const DmElementReference_t &ref, const char *pElementType, const char *pElementName, DmFileId_t fileid, const DmObjectId_t *pObjectID ) { DMX_PROFILE_SCOPE( CDataModel_CreateElement2 ); // Msg( "Creating %s 0x%x '%s' in file \"%s\" - %d elements loaded\n", pElementType, ref.m_hElement, pElementName ? pElementName : "", GetFileName( fileid ), m_elementIds.Count() ); MEM_ALLOC_CREDIT(); DmPhase_t phase = g_pDmElementFramework->GetPhase(); if ( phase != PH_EDIT ) { Assert( 0 ); return NULL; } // Create a new id if we weren't given one to use DmObjectId_t newId; if ( !pObjectID ) { CreateUniqueId( &newId ); pObjectID = &newId; } if ( !pElementName ) { pElementName = UNNAMED_ELEMENT_NAME; } IDmElementFactory *pFactory = NULL; if ( m_bOnlyCreateUntypedElements ) { // As soon as we create something from the default factory, // we can no longer change the default factory m_bUnableToSetDefaultFactory = true; pFactory = m_pDefaultFactory; } else { DMX_PROFILE_SCOPE( CreateElement_FindFactory ); int idx = m_Factories.Find( pElementType ); if ( idx == m_Factories.InvalidIndex() ) { if ( !m_pDefaultFactory ) { Warning( "Unable to create unknown element %s!\n", pElementType ); return NULL; } m_bUnableToSetDefaultFactory = true; pFactory = m_pDefaultFactory; } else { m_bUnableToCreateOnlyUntypedElements = true; pFactory = m_Factories[ idx ]->GetFactory(); } } Assert( pFactory ); // Create an undo element CUndoCreateElement *pUndo = NULL; { if ( fileid != DMFILEID_INVALID && g_pDataModel->IsUndoEnabled() ) // elements not in any file don't participate in undo { DMX_PROFILE_SCOPE( CreateElement_Undo ); pUndo = new CUndoCreateElement(); g_pDataModel->AddUndoElement( pUndo ); } } CDisableUndoScopeGuard sg; CDmElement *pElement = NULL; { DMX_PROFILE_SCOPE( CreateElement_pFactoryCreate ); pElement = pFactory->Create( ref.m_hElement, pElementType, pElementName, fileid, *pObjectID ); } if ( pElement ) { DMX_PROFILE_SCOPE( CreateElement_PerformConstruction ); ++m_nElementsAllocatedSoFar; m_nMaxNumberOfElements = MAX( m_nMaxNumberOfElements, GetAllocatedElementCount() ); CDmeElementAccessor::SetReference( pElement, ref ); m_Handles.SetHandle( ref.m_hElement, pElement ); m_elementIds.Insert( ref.m_hElement ); CDmeElementAccessor::PerformConstruction( pElement ); CDmeElementAccessor::EnableOnChangedCallbacks( pElement ); if ( pUndo ) { pUndo->SetElement( ref.m_hElement ); } NotifyState( NOTIFY_CHANGE_TOPOLOGICAL ); // Notify any supplied callbacks of this element's creation pFactory->OnElementCreated( pElement ); #ifdef _ELEMENT_HISTOGRAM_ CUtlSymbolLarge typeSym = GetSymbol( pElementType ); short i = g_typeHistogram.Find( typeSym ); if ( g_typeHistogram.IsValidIndex( i ) ) { ++g_typeHistogram[ i ]; } else { g_typeHistogram.Insert( typeSym, 1 ); } #endif _ELEMENT_HISTOGRAM_ } return pElement; } void CDataModel::UpdateReferenceToElements( CDmAttribute *pAttr, CDmElement *pChild, bool bDetach ) { if ( !pAttr || !pChild ) return; if ( bDetach ) { // Msg( "removing " ); OnElementReferenceRemoved( pChild->GetHandle(), pAttr ); } else { Assert( !CDmeElementAccessor::GetReference( pChild )->FindAttribute( pAttr ) ); // Msg( "adding " ); OnElementReferenceAdded( pChild->GetHandle(), pAttr ); } // CDmElement *pOwner = pAttr->GetOwner(); // Msg( "reference to %s 0x%x '%s' from %s 0x%x '%s' %s\n", // pChild->GetTypeString(), pChild->GetHandle(), pChild->GetName(), // pOwner->GetTypeString(), pOwner->GetHandle(), pOwner->GetName(), // pAttr->GetName() ); } void CDataModel::UpdateReferencesToElements( CDmElement *pElement, bool bDetach ) { for ( CDmAttribute *pAttr = pElement->FirstAttribute(); pAttr; pAttr = pAttr->NextAttribute() ) { DmAttributeType_t type = pAttr->GetType(); if ( type == AT_ELEMENT ) { UpdateReferenceToElements( pAttr, pAttr->GetValueElement< CDmElement >(), bDetach ); } else if ( type == AT_ELEMENT_ARRAY ) { const CDmrElementArrayConst<> elementArray( pAttr ); int nChildren = elementArray.Count(); for ( int i = 0; i < nChildren; ++i ) { UpdateReferenceToElements( pAttr, elementArray[ i ], bDetach ); } } } } class CUndoDestroyElement : public CUndoElement { typedef CUndoElement BaseClass; public: CUndoDestroyElement( DmElementHandle_t hElement ) : BaseClass( "CUndoDestroyElement" ), m_bKill( true ), m_hElement( hElement ) { CDmElement *pElement = GetElement( hElement ); Assert( pElement && pElement->GetFileId() != DMFILEID_INVALID ); g_pDataModelImp->UpdateReferencesToElements( pElement, true ); pElement->SetOnlyInUndo( true ); CDmeElementAccessor::OnOrphanedToUndo( pElement ); g_pDataModelImp->MarkHandleInvalid( m_hElement ); } ~CUndoDestroyElement() { if ( m_bKill ) { g_pDataModelImp->MarkHandleValid( m_hElement ); g_pDataModelImp->DeleteElement( m_hElement ); } } virtual void Undo() { m_bKill = false; g_pDataModelImp->MarkHandleValid( m_hElement ); CDmElement *pElement = GetElement( m_hElement ); pElement->SetOnlyInUndo( false ); CDmeElementAccessor::OnAdoptedFromUndo( pElement ); g_pDataModelImp->UpdateReferencesToElements( pElement, false ); } virtual void Redo() { m_bKill = true; CDmElement *pElement = GetElement( m_hElement ); g_pDataModelImp->UpdateReferencesToElements( pElement, true ); pElement->SetOnlyInUndo( true ); CDmeElementAccessor::OnOrphanedToUndo( pElement ); g_pDataModelImp->MarkHandleInvalid( m_hElement ); } private: CDmeUndoHandle m_hElement; bool m_bKill; }; //----------------------------------------------------------------------------- // Purpose: Destroys a scene object //----------------------------------------------------------------------------- void CDataModel::DestroyElement( DmElementHandle_t hElement ) { DmPhase_t phase = g_pDmElementFramework->GetPhase(); if ( phase != PH_EDIT && phase != PH_EDIT_APPLY ) // need to allow edit_apply to delete elements, so that cascading deletes can occur in one phase { Assert( 0 ); return; } if ( hElement == DMELEMENT_HANDLE_INVALID ) return; CDmElement *pElement = m_Handles.GetHandle( hElement ); if ( pElement == NULL ) return; // Create an undo element if ( UndoEnabledForElement( pElement ) ) { m_bDeleteOrphanedElements = true; CUndoDestroyElement *pUndo = new CUndoDestroyElement( hElement ); g_pDataModel->AddUndoElement( pUndo ); NotifyState( NOTIFY_CHANGE_TOPOLOGICAL ); return; // if undo is enabled, just toss this onto the undo stack, rather than actually destroying it } DeleteElement( hElement ); } void CDataModel::DeleteElement( DmElementHandle_t hElement, DmHandleReleasePolicy hrp /* = HR_ALWAYS */ ) { DmPhase_t phase = g_pDmElementFramework->GetPhase(); if ( phase != PH_EDIT && phase != PH_EDIT_APPLY) { Assert( 0 ); return; } if ( hElement == DMELEMENT_HANDLE_INVALID ) return; CDmElement *pElement = m_Handles.GetHandle( hElement ); if ( pElement == NULL ) return; // In order for DestroyElement to work, then, we need to cache off the element type // because that's stored in an attribute const char *pElementType = pElement->GetTypeString(); Assert( pElementType ); // Msg( "Deleting %s element 0x%x \'%s\' in file \"%s\" - %d elements loaded\n", pElementType, hElement, pElement->GetName(), GetFileName( pElement->GetFileId() ), m_elementIds.Count() ); UtlHashHandle_t h = m_elementIds.Find( pElement->GetId() ); Assert( h != m_elementIds.InvalidHandle() ); if ( h != m_elementIds.InvalidHandle() ) { m_elementIds.Remove( h ); } DmElementReference_t *pRef = CDmeElementAccessor::GetReference( pElement ); bool bReleaseHandle = hrp == HR_ALWAYS || ( hrp == HR_IF_NOT_REFERENCED && !pRef->IsWeaklyReferenced() ); if ( !bReleaseHandle ) { m_unloadedIdElementMap.Insert( ElementIdHandlePair_t( GetElementId( hElement ), *pRef ) ); } IDmElementFactory *pFactory = NULL; if ( m_bOnlyCreateUntypedElements ) { pFactory = m_pDefaultFactory; } else { int idx = m_Factories.Find( pElementType ); pFactory = idx == m_Factories.InvalidIndex() ? m_pDefaultFactory : m_Factories[ idx ]->GetFactory(); } CDmeElementAccessor::PerformDestruction( pElement ); // NOTE: Attribute destruction has to happen before the containing object is destroyed // because the inline optimization will crash otherwise, and after PerformDestruction // or else PerformDestruction will crash CDmeElementAccessor::Purge( pElement ); pFactory->Destroy( hElement ); if ( bReleaseHandle ) { ReleaseElementHandle( hElement ); } else { MarkHandleInvalid( hElement ); } } //----------------------------------------------------------------------------- // handle-related methods //----------------------------------------------------------------------------- DmElementHandle_t CDataModel::AcquireElementHandle() { NotifyState( NOTIFY_CHANGE_TOPOLOGICAL ); return ( DmElementHandle_t )m_Handles.AddHandle(); } void CDataModel::ReleaseElementHandle( DmElementHandle_t hElement ) { m_Handles.RemoveHandle( hElement ); NotifyState( NOTIFY_CHANGE_TOPOLOGICAL ); } void CDataModel::MarkHandleInvalid( DmElementHandle_t hElement ) { m_Handles.MarkHandleInvalid( hElement ); NotifyState( NOTIFY_CHANGE_TOPOLOGICAL ); } void CDataModel::MarkHandleValid( DmElementHandle_t hElement ) { m_Handles.MarkHandleValid( hElement ); NotifyState( NOTIFY_CHANGE_TOPOLOGICAL ); } void CDataModel::GetInvalidHandles( CUtlVector< DmElementHandle_t > &handles ) { unsigned int nHandles = m_Handles.GetHandleCount(); for ( unsigned int i = 0; i < nHandles; ++i ) { DmElementHandle_t h = ( DmElementHandle_t )m_Handles.GetHandleFromIndex( i ); if ( !m_Handles.IsHandleValid( h ) ) { handles.AddToTail( h ); } } } void CDataModel::MarkHandlesValid( CUtlVector< DmElementHandle_t > &handles ) { int nHandles = handles.Count(); for ( int i = 0; i < nHandles; ++i ) { m_Handles.MarkHandleValid( handles[ i ] ); } } void CDataModel::MarkHandlesInvalid( CUtlVector< DmElementHandle_t > &handles ) { int nHandles = handles.Count(); for ( int i = 0; i < nHandles; ++i ) { m_Handles.MarkHandleInvalid( handles[ i ] ); } } CDmElement *CDataModel::GetElement( DmElementHandle_t hElement ) const { return ( hElement != DMELEMENT_HANDLE_INVALID ) ? m_Handles.GetHandle( hElement ) : NULL; } CUtlSymbolLarge CDataModel::GetElementType( DmElementHandle_t hElement ) const { CDmElement *pElement = ( hElement != DMELEMENT_HANDLE_INVALID ) ? m_Handles.GetHandle( hElement ) : NULL; if ( pElement == NULL ) return UTL_INVAL_SYMBOL_LARGE; return pElement->GetType(); } const char* CDataModel::GetElementName( DmElementHandle_t hElement ) const { CDmElement *pElement = ( hElement != DMELEMENT_HANDLE_INVALID ) ? m_Handles.GetHandle( hElement ) : NULL; if ( pElement == NULL ) return ""; return pElement->GetName(); } const DmObjectId_t& CDataModel::GetElementId( DmElementHandle_t hElement ) const { CDmElement *pElement = ( hElement != DMELEMENT_HANDLE_INVALID ) ? m_Handles.GetHandle( hElement ) : NULL; if ( pElement == NULL ) { static DmObjectId_t s_id; InvalidateUniqueId( &s_id ); return s_id; } return pElement->GetId(); } //----------------------------------------------------------------------------- // Attribute types //----------------------------------------------------------------------------- const char *CDataModel::GetAttributeNameForType( DmAttributeType_t attType ) const { return AttributeTypeName( attType ); } DmAttributeType_t CDataModel::GetAttributeTypeForName( const char *name ) const { return AttributeType( name ); } //----------------------------------------------------------------------------- // // Methods related to notification callbacks // //----------------------------------------------------------------------------- bool CDataModel::InstallNotificationCallback( IDmNotify *pNotify ) { return m_UndoMgr.InstallNotificationCallback( pNotify ); } void CDataModel::RemoveNotificationCallback( IDmNotify *pNotify ) { m_UndoMgr.RemoveNotificationCallback( pNotify ); } bool CDataModel::IsSuppressingNotify( ) const { return GetUndoMgr()->IsSuppressingNotify( ); } void CDataModel::SetSuppressingNotify( bool bSuppress ) { GetUndoMgr()->SetSuppressingNotify( bSuppress ); } void CDataModel::PushNotificationScope( const char *pReason, int nNotifySource, int nNotifyFlags ) { GetUndoMgr()->PushNotificationScope( pReason, nNotifySource, nNotifyFlags ); } void CDataModel::PopNotificationScope( bool bAbort ) { GetUndoMgr()->PopNotificationScope( bAbort ); } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- // Undo/Redo support //----------------------------------------------------------------------------- void CDataModel::SetUndoEnabled( bool enable ) { if ( enable ) { GetUndoMgr()->EnableUndo(); } else { GetUndoMgr()->DisableUndo(); } } bool CDataModel::IsUndoEnabled() const { return GetUndoMgr()->IsEnabled(); } bool CDataModel::UndoEnabledForElement( const CDmElement *pElement ) const { // elements not in any file don't participate in undo Assert( pElement ); return IsUndoEnabled() && pElement && pElement->GetFileId() != DMFILEID_INVALID; } bool CDataModel::IsDirty() const { return GetUndoMgr()->HasUndoData(); } bool CDataModel::CanUndo() const { return GetUndoMgr()->HasUndoData(); } bool CDataModel::CanRedo() const { return GetUndoMgr()->HasRedoData(); } void CDataModel::StartUndo( const char *undodesc, const char *redodesc, int nChainingID /* = 0 */ ) { GetUndoMgr()->PushUndo( undodesc, redodesc, nChainingID ); } void CDataModel::FinishUndo() { GetUndoMgr()->PushRedo(); } void CDataModel::AbortUndoableOperation() { GetUndoMgr()->AbortUndoableOperation(); } void CDataModel::ClearRedo() { if ( GetUndoMgr()->HasRedoData() ) { GetUndoMgr()->WipeRedo(); } } const char *CDataModel::GetUndoDesc() { return GetUndoMgr()->UndoDesc(); } const char *CDataModel::GetRedoDesc() { return GetUndoMgr()->RedoDesc(); } // From the UI, perform the Undo operation void CDataModel::Undo() { GetUndoMgr()->Undo(); } void CDataModel::Redo() { GetUndoMgr()->Redo(); } // if true, undo records spew as they are added void CDataModel::TraceUndo( bool state ) { GetUndoMgr()->TraceUndo( state ); } void CDataModel::AddUndoElement( IUndoElement *pElement ) { GetUndoMgr()->AddUndoElement( pElement ); } void CDataModel::ClearUndo() { GetUndoMgr()->WipeUndo(); GetUndoMgr()->WipeRedo(); m_bDeleteOrphanedElements = true; // next time we delete unreferenced elements, delete orphaned subtrees as well } CUtlSymbolLarge CDataModel::GetUndoDescInternal( const char *context ) { return GetUndoMgr()->GetUndoDescInternal( context ); } CUtlSymbolLarge CDataModel::GetRedoDescInternal( const char *context ) { return GetUndoMgr()->GetRedoDescInternal( context ); } void CDataModel::GetUndoInfo( CUtlVector< UndoInfo_t >& list ) { GetUndoMgr()->GetUndoInfo( list ); } const char *CDataModel::GetUndoString( CUtlSymbolLarge sym ) { return sym.String(); } //----------------------------------------------------------------------------- // // Methods related to attribute handles // //----------------------------------------------------------------------------- DmAttributeHandle_t CDataModel::AcquireAttributeHandle( CDmAttribute *pAttribute ) { DmAttributeHandle_t hAttribute = (DmAttributeHandle_t)m_AttributeHandles.AddHandle(); m_AttributeHandles.SetHandle( hAttribute, pAttribute ); return hAttribute; } void CDataModel::ReleaseAttributeHandle( DmAttributeHandle_t hAttribute ) { if ( hAttribute != DMATTRIBUTE_HANDLE_INVALID ) { m_AttributeHandles.RemoveHandle( hAttribute ); } } CDmAttribute *CDataModel::GetAttribute( DmAttributeHandle_t h ) { return m_AttributeHandles.GetHandle( h ); } bool CDataModel::IsAttributeHandleValid( DmAttributeHandle_t h ) const { return m_AttributeHandles.IsHandleValid( h ); } //----------------------------------------------------------------------------- // // Methods related to clipboard contexts // //----------------------------------------------------------------------------- void CDataModel::EmptyClipboard() { GetClipboardMgr()->EmptyClipboard( true ); } void CDataModel::SetClipboardData( CUtlVector< KeyValues * >& data, IClipboardCleanup *pfnOptionalCleanuFunction /*= 0*/ ) { GetClipboardMgr()->SetClipboardData( data, pfnOptionalCleanuFunction ); } void CDataModel::AddToClipboardData( KeyValues *add ) { GetClipboardMgr()->AddToClipboardData( add ); } void CDataModel::GetClipboardData( CUtlVector< KeyValues * >& data ) { GetClipboardMgr()->GetClipboardData( data ); } bool CDataModel::HasClipboardData() const { return GetClipboardMgr()->HasClipboardData(); } // Commits symbols in symbol table void CDataModel::CommitSymbols() { m_SymbolTable.Commit(); } void CDataModel::AddOnElementCreatedCallback( const char *pElementType, IDmeElementCreated *callback ) { IDmElementFactory *pFactory = NULL; int idx = m_Factories.Find( pElementType ); if ( idx == m_Factories.InvalidIndex() ) return; pFactory = m_Factories[ idx ]->GetFactory(); Assert( pFactory ); pFactory->AddOnElementCreatedCallback( callback ); } void CDataModel::RemoveOnElementCreatedCallback( const char *pElementType, IDmeElementCreated *callback ) { IDmElementFactory *pFactory = NULL; int idx = m_Factories.Find( pElementType ); if ( idx == m_Factories.InvalidIndex() ) return; pFactory = m_Factories[ idx ]->GetFactory(); Assert( pFactory ); pFactory->RemoveOnElementCreatedCallback( callback ); }