diff options
Diffstat (limited to 'datamodel/datamodel.cpp')
| -rw-r--r-- | datamodel/datamodel.cpp | 2464 |
1 files changed, 2464 insertions, 0 deletions
diff --git a/datamodel/datamodel.cpp b/datamodel/datamodel.cpp new file mode 100644 index 0000000..a569e65 --- /dev/null +++ b/datamodel/datamodel.cpp @@ -0,0 +1,2464 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= + +#include "datamodel/idatamodel.h" +#include "datamodel/dmattributevar.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 "tier2/utlstreambuffer.h" +#include "tier2/fileutils.h" + +// 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<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< UtlSymId_t, int > g_typeHistogram( 0, 100, DefLessFunc( UtlSymId_t ) ); +#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 ( hElement != DMELEMENT_HANDLE_INVALID ) + 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 hElementCur = ( DmElementHandle_t )m_Handles.GetHandleFromIndex( i ); + if ( hElementCur != DMELEMENT_HANDLE_INVALID ) + return hElementCur; + } + + 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; +} + +void CDataModel::DisplayMemoryStats( ) +{ + CUtlMap< UtlSymId_t, DmMemoryInfo_t > typeHistogram( 0, 100, DefLessFunc( UtlSymId_t ) ); + CUtlHash< DmElementHandle_t > visited( 1024, 0, 0, HandleCompare, HandleHash ); + + 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; + + 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; + } + + // 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 ]; + int nTotalSize = 0; + int nTotalCount = 0; + int nTotalData = 0; + memset( pTotals, 0, sizeof(pTotals) ); + 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", GetString( typeHistogram.Key( pSortInfo[i].m_nIndex ) ), + 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]; + } + + 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" ); +} + + +//----------------------------------------------------------------------------- +// Global symbol table for the datamodel system +//----------------------------------------------------------------------------- +UtlSymId_t CDataModel::GetSymbol( const char *pString ) +{ + return m_SymbolTable.AddString( pString ); +} + +const char * CDataModel::GetString( UtlSymId_t sym ) const +{ + return m_SymbolTable.String( sym ); +} + +unsigned short CDataModel::GetSymbolCount() const // this can't ever overflow a ushort, since UtlSymId_t is a ushort, and one of its entries is invalid (0xffff) +{ + return m_SymbolTable.GetNumStrings(); // this is only useful because symbols are never removed +} + + +//----------------------------------------------------------------------------- +// 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; + } + } + + bool bIsBinary = IsEncodingBinary( pEncodingName ); + CUtlStreamBuffer buf( pFullPath, pPathID, bIsBinary ? 0 : CUtlBuffer::TEXT_BUFFER, true ); + if ( !buf.IsValid() ) + { + Warning( "CDataModel: Unable to open file \"%s\"\n", pFullPath ); + return false; + } + return Serialize( buf, pEncodingName, pFormatName, pRoot->GetHandle() ); +} + +DmFileId_t CDataModel::RestoreFromFile( char const *pFileName, char const *pPathID, const char *pFormatHint, 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 ( !pFormatHint ) + { + Warning( "CDataModel: Unable to determine DMX format for file %s\n", pFullPath ); + return DMFILEID_INVALID; + } + + if ( !IsValidNonDMXFormat( pFormatHint ) ) + { + Warning( "CDataModel: Invalid DMX format hint '%s' for file %s\n", pFormatHint, pFullPath ); + return DMFILEID_INVALID; + } + + // non-dmx file importers don't have versions or encodings, just formats + V_strncpy( pHeader->encodingName, pFormatHint, sizeof( pHeader->encodingName ) ); + V_strncpy( pHeader->formatName, pFormatHint, sizeof( pHeader->formatName ) ); + } + + bool bIsBinary = IsEncodingBinary( pHeader->encodingName ); + CUtlStreamBuffer buf( pFullPath, pPathID, bIsBinary ? CUtlBuffer::READ_ONLY : CUtlBuffer::READ_ONLY | CUtlBuffer::TEXT_BUFFER ); + if ( !buf.IsValid() ) + { + Warning( "CDataModel: Unable to open file '%s'\n", pFullPath ); + return DMFILEID_INVALID; + } + + DmElementHandle_t hRootElement; + if ( !Unserialize( buf, pHeader->encodingName, pHeader->formatName, pFormatHint, pFullPath, idConflictResolution, hRootElement ) ) + return DMFILEID_INVALID; + + *ppRoot = g_pDataModel->GetElement( hRootElement ); + + DmFileId_t fileid = g_pDataModel->GetFileId( pFullPath ); + Assert( fileid != DMFILEID_INVALID ); + return fileid; +} + +//----------------------------------------------------------------------------- +// Serialization of a element tree into a utlbuffer +//----------------------------------------------------------------------------- +bool CDataModel::Serialize( CUtlBuffer &outBuf, const char *pEncodingName, const char *pFormatName, DmElementHandle_t hRoot ) +{ + // 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 *pFormatName ) const +{ + IDmSerializer *pSerializer = FindSerializer( pFormatName ); + 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 ) +{ + if ( IsValidNonDMXFormat( pFormatName ) ) + return 0; // unversioned format + + 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 *pSourceFormatName, const char *pFormatHint, + const char *pFileName, DmConflictResolution_t idConflictResolution, DmElementHandle_t &hRoot ) +{ + ClearUndo(); + CDisableUndoScopeGuard sg; + + Assert( pEncodingName && *pEncodingName ); + if ( !pEncodingName || !*pEncodingName ) + return false; + Assert( pSourceFormatName && *pSourceFormatName ); + if ( !pSourceFormatName || !*pSourceFormatName ) + return false; + + // 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 = true; // for formats that don't store a format, files are currently always at the current version + if ( bStoresVersionInFile ) + { + bool bOk = ReadDMXHeader( inBuf, &header ); + if ( !bOk ) + { + Warning( "Unserialize: unable to read DMX header!\n" ); + return false; + } + + if ( IsLegacyFormat( header.formatName ) ) + { + if ( GetCurrentFormatVersion( GENERIC_DMX_FORMAT ) == 1 ) + { + IDmLegacyUpdater *pLegacyUpdater = FindLegacyUpdater( header.formatName ); + bIsCurrentVersion = !pLegacyUpdater || pLegacyUpdater->IsLatestVersion(); + } + else + { + bIsCurrentVersion = false; + } + } + else + { + bIsCurrentVersion = GetCurrentFormatVersion( header.formatName ) == header.nFormatVersion; + } + } + + // 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[ 256 ]; + V_ExtractFilePath( pFileName, path, sizeof( path ) ); + + char tempFileName[ 256 ]; + if ( !V_IsAbsolutePath( path ) ) + { + g_pFullFileSystem->GetCurrentDirectory( path, sizeof( path ) ); + } + + V_ComposeFileName( path, "_temp_conversion_file_.dmx", tempFileName, sizeof( tempFileName ) ); + V_RemoveDotSlashes( tempFileName ); + + const char *pDestEncodingName = "binary"; + const char *pDestFormatName = IsLegacyFormat( header.formatName ) ? GENERIC_DMX_FORMAT : header.formatName; + char cmdline[ 256 ]; + V_snprintf( cmdline, sizeof( cmdline ), "dmxconvert -allowdebug -i %s -o %s -oe %s -of %s", pFileName, tempFileName, pDestEncodingName, pDestFormatName ); + + ProcessHandle_t hProcess = PROCESS_HANDLE_INVALID; + if ( g_pProcessUtils ) + { + hProcess = g_pProcessUtils->StartProcess( cmdline, false ); + } + if ( hProcess == PROCESS_HANDLE_INVALID ) + { + Warning( "Unserialize: Unable to run conversion process \"%s\"\n", cmdline ); + return false; + } + + g_pProcessUtils->WaitUntilProcessCompletes( hProcess ); + g_pProcessUtils->CloseProcess( hProcess ); + + bool bSuccess; + { + CUtlStreamBuffer strbuf( tempFileName, NULL, CUtlBuffer::READ_ONLY ); + if ( !strbuf.IsValid() ) + { + Warning( "Unserialize: 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( strbuf, pDestEncodingName, pDestFormatName, pDestFormatName, pFileName, idConflictResolution, hRoot ); + } + + 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 ( V_strcmp( pSourceFormatName, header.formatName ) != 0 ) + return false; + + 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, pSourceFormatName, header.nFormatVersion, + fileid, idConflictResolution, &pRoot ); + hRoot = pRoot ? pRoot->GetHandle() : DMELEMENT_HANDLE_INVALID; + + SetFileFormat( fileid, pSourceFormatName ); + SetFileRoot( fileid, hRoot ); + + 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 false; + + 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 ); +} + +DmFileId_t CDataModel::FindOrCreateFileId( const char *pFilename ) +{ + Assert( pFilename && *pFilename ); + if ( !pFilename || !*pFilename ) + return DMFILEID_INVALID; + + DmFileId_t fileid = GetFileId( pFilename ); + 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( pFilename ) ) ); + 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 ) +{ + UtlSymId_t filenameSym = GetSymbol( pFilename ); + + 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 ? GetString( fes->m_filename ) : NULL; +} + +void CDataModel::SetFileName( DmFileId_t fileid, const char *pFileName ) +{ + FileElementSet_t *fes = m_openFiles.GetHandle( fileid ); + Assert( fes ); + if ( !fes ) + return; + + fes->m_filename = GetSymbol( pFileName ); +} + +const char *CDataModel::GetFileFormat( DmFileId_t fileid ) +{ + FileElementSet_t *fes = m_openFiles.GetHandle( fileid ); + Assert( fes || fileid == DMFILEID_INVALID ); + return fes ? GetString( fes->m_format ) : 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; +} + +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, true ); +} + +void CDataModel::OnElementReferenceAdded( DmElementHandle_t hElement, CDmAttribute *pAttribute ) +{ + Assert( pAttribute ); + if ( !pAttribute ) + return; + + if ( hElement == DMELEMENT_HANDLE_INVALID ) + return; + + DmObjectId_t *pId; + DmElementReference_t *pRef = FindElementReference( hElement, &pId ); + if ( !pRef ) + return; + +// Msg( "OnElementReferenceAdded: %s 0x%x '%s' referenced by 0x%x '%s'\n", +// GetString( GetElementType( hElement ) ), hElement, GetElementName( hElement ), +// pAttribute->GetOwner()->GetHandle(), pAttribute->GetName() ); + + pRef->AddAttribute( pAttribute ); +} + +void CDataModel::OnElementReferenceAdded( DmElementHandle_t hElement, bool bRefCount ) +{ + if ( hElement == DMELEMENT_HANDLE_INVALID ) + return; + + DmObjectId_t *pId; + DmElementReference_t *pRef = FindElementReference( hElement, &pId ); + if ( !pRef ) + return; + +// Msg( "OnElementReferenceAdded: %s 0x%x \"%s\" referenced by %s handle\n", +// GetString( GetElementType( hElement ) ), hElement, GetElementName( hElement ), +// bRefCount ? "refcounted" : "weak" ); + + if ( bRefCount ) + { + ++pRef->m_nStrongHandleCount; + } + else + { + ++pRef->m_nWeakHandleCount; + } +} + +void CDataModel::OnElementReferenceRemoved( DmElementHandle_t hElement, CDmAttribute *pAttribute ) +{ + MEM_ALLOC_CREDIT(); + + Assert( pAttribute ); + if ( !pAttribute ) + return; + + if ( hElement == DMELEMENT_HANDLE_INVALID ) + return; + + DmObjectId_t *pId; + DmElementReference_t *pRef = FindElementReference( hElement, &pId ); + if ( !pRef ) + return; + +// Msg( "OnElementReferenceRemoved: %s 0x%x '%s' referenced by 0x%x '%s'\n", +// GetString( GetElementType( hElement ) ), hElement, GetElementName( hElement ), +// pAttribute->GetOwner()->GetHandle(), pAttribute->GetName() ); + + pRef->RemoveAttribute( pAttribute ); + + if ( !pRef->IsStronglyReferenced() ) + { + if ( pId ) + { + if ( !pRef->IsWeaklyReferenced() ) + { + int i = m_unreferencedElementIds.AddToTail(); + CopyUniqueId( *pId, &m_unreferencedElementIds[ i ] ); + } + } + else + { + Assert( GetElement( hElement ) ); + m_unreferencedElementHandles.AddToTail( hElement ); + } + +// Msg( " - marked as unreferenced!\n"); + } +} + +void CDataModel::OnElementReferenceRemoved( DmElementHandle_t hElement, bool bRefCount ) +{ + MEM_ALLOC_CREDIT(); + + if ( hElement == DMELEMENT_HANDLE_INVALID ) + return; + + DmObjectId_t *pId; + DmElementReference_t *pRef = FindElementReference( hElement, &pId ); + if ( !pRef ) + return; + +// Msg( "OnElementReferenceRemoved: %s 0x%x \"%s\" referenced by %s handle\n", +// GetString( GetElementType( hElement ) ), hElement, GetElementName( hElement ), +// bRefCount ? "refcounted" : "weak" ); + + if ( bRefCount ) + { + --pRef->m_nStrongHandleCount; + } + else + { + --pRef->m_nWeakHandleCount; + } + + if ( !pRef->IsStronglyReferenced() ) + { + if ( pId ) + { + if ( !pRef->IsWeaklyReferenced() ) + { + int i = m_unreferencedElementIds.AddToTail(); + CopyUniqueId( *pId, &m_unreferencedElementIds[ i ] ); + } + } + else if ( bRefCount ) + { + // only unref elements if strong reference changing + // this prevents [creation, weak ref, weak unref] from deleting element + Assert( GetElement( hElement ) ); + m_unreferencedElementHandles.AddToTail( hElement ); + } + +// Msg( " - marked as unreferenced!\n"); + } +} + +void CDataModel::RemoveUnreferencedElements() +{ + CDisableUndoScopeGuard sg; + + int nElementIds = m_unreferencedElementIds.Count(); + for ( int i = 0; i < nElementIds; ++i ) + { + UtlHashHandle_t h = m_unloadedIdElementMap.Find( ElementIdHandlePair_t( m_unreferencedElementIds[ i ] ) ); + if ( h == m_unloadedIdElementMap.InvalidHandle() ) + continue; + + if ( m_unloadedIdElementMap[ h ].m_ref.IsWeaklyReferenced() ) + continue; // don't remove if it's been referenced again - this allows an unref followed by a ref in the same edit phase + +// Msg( "Removing reference: 0x%x\n", m_unloadedIdElementMap[ h ].m_ref.m_hElement ); + + m_unloadedIdElementMap.Remove( h ); + } + m_unreferencedElementIds.RemoveAll(); + + // this is intentionally calling Count() every time through, since DestroyElement may cause more elements to be added to the list + for ( int i = 0; i < m_unreferencedElementHandles.Count(); ++i ) + { + DmElementHandle_t hElement = m_unreferencedElementHandles[ i ]; + + CDmElement *pElement = GetElement( hElement ); +// Assert( pElement ); + if ( !pElement ) + continue; + +// Msg( "%s '%s' %08x unref'ed to 0\n", pElement->GetTypeString(), pElement->GetName(), pElement->GetHandle() ); + + if ( CDmeElementAccessor::GetReference( pElement )->IsStronglyReferenced() ) + continue; + +// Msg( " -deleted\n" ); + + DeleteElement( hElement ); + } + m_unreferencedElementHandles.RemoveAll(); + + if ( m_bDeleteOrphanedElements ) + { + m_bDeleteOrphanedElements = false; + FindAndDeleteOrphanedElements(); + } +} + +void CDataModel::FindAndDeleteOrphanedElements() +{ +#if 1 // this appears to be faster, and is fully implemented + // mark & sweep algorithm for elements + + // clear accessible flag from all elements + 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; + + pElement->MarkAccessible( false ); + } + + // 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->MarkAccessible( TD_ALL ); + } + + // 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; + + if ( CDmeElementAccessor::GetReference( pElement )->m_nStrongHandleCount == 0 ) + continue; + + pElement->MarkAccessible( TD_ALL ); + } + + // 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; + + if ( pElement->IsAccessible() ) + continue; + + DeleteElement( hElement ); + } +#else + // root finding algorithm on elements + + // JDTODO - this incorrectly deletes elements that are referenced by a counted handle, but aren't under the file root + + CUtlVector< ElementPathItem_t > path; + 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; + + DmElementHandle_t hRoot = GetFileRoot( fileid ); + + path.RemoveAll(); + if ( hRoot == hElement || pElement->FindReferer( hRoot, path, TD_ALL ) ) + continue; + + DeleteElement( hElement ); + } +#endif +} + + +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 ]; +} + + +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 )( int )&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 )( int )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( const char *pClassName, IDmElementFactory *pFactory ) +{ + Assert( pClassName && pFactory ); + int idx = m_Factories.Find( pClassName ); + if ( idx == m_Factories.InvalidIndex() ) + { + m_Factories.Insert( pClassName, pFactory ); + } + else + { + // Override the factory? + m_Factories[idx] = pFactory; + Warning( "Factory for element type '%s' already exists\n", pClassName ); + } +} + +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( UtlSymId_t typeSymbol, const char *pElementName, DmFileId_t fileid, const DmObjectId_t *pObjectID ) +{ + return CreateElement( GetString( typeSymbol ), pElementName, fileid, pObjectID ); +} + +DmElementHandle_t CDataModel::CreateElement( const char *pElementType, const char *pElementName, DmFileId_t fileid, const DmObjectId_t *pObjectID ) +{ + 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<CDmElement>( hElement ) && GetElement<CDmElement>( 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: + CDmeCountedHandle 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 ) +{ +// 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 + { + int idx = m_Factories.Find( pElementType ); + if ( idx == m_Factories.InvalidIndex() ) + { + Warning( "Unable to create unknown element %s!\n", pElementType ); + return NULL; + } + else + { + m_bUnableToCreateOnlyUntypedElements = true; + pFactory = m_Factories[ idx ]; + } + } + Assert( pFactory ); + + // Create an undo element + CUndoCreateElement *pUndo = NULL; + if ( g_pDataModel->IsUndoEnabled() && fileid != DMFILEID_INVALID ) // elements not in any file don't participate in undo + { + pUndo = new CUndoCreateElement(); + g_pDataModel->AddUndoElement( pUndo ); + } + + CDisableUndoScopeGuard sg; + + CDmElement *pElement = pFactory->Create( ref.m_hElement, pElementType, pElementName, fileid, *pObjectID ); + if ( pElement ) + { + ++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 ); + + if ( pUndo ) + { + pUndo->SetElement( ref.m_hElement ); + } + + NotifyState( NOTIFY_CHANGE_TOPOLOGICAL ); + +#ifdef _ELEMENT_HISTOGRAM_ + UtlSymId_t 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; +} + + +class CUndoDestroyElement : public CUndoElement +{ + typedef CUndoElement BaseClass; +public: + CUndoDestroyElement( DmElementHandle_t hElement ) : + BaseClass( "CUndoDestroyElement" ), + m_bKill( true ), + m_hElement( hElement ) + { + Assert( GetElement<CDmElement>( hElement ) && GetElement<CDmElement>( hElement )->GetFileId() != DMFILEID_INVALID ); + 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 ); + } + + virtual void Redo() + { + m_bKill = true; + g_pDataModelImp->MarkHandleInvalid( m_hElement ); + } + +private: + CDmeCountedHandle 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( GetElement( hElement ) ) ) + { + 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, pInternal->GetName(), GetFileName( pInternal->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 ]; + } + + 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; +} + +UtlSymId_t CDataModel::GetElementType( DmElementHandle_t hElement ) const +{ + CDmElement *pElement = ( hElement != DMELEMENT_HANDLE_INVALID ) ? m_Handles.GetHandle( hElement ) : NULL; + if ( pElement == NULL ) + return UTL_INVAL_SYMBOL; + 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 ); +} + + +//----------------------------------------------------------------------------- +// Event "mailing list" s +//----------------------------------------------------------------------------- +DmMailingList_t CDataModel::CreateMailingList() +{ + return m_MailingLists.AddToTail(); +} + +void CDataModel::DestroyMailingList( DmMailingList_t list ) +{ + m_MailingLists.Remove( list ); +} + +void CDataModel::AddElementToMailingList( DmMailingList_t list, DmElementHandle_t h ) +{ + // Make sure it's not already in the list + Assert( m_MailingLists[list].m_Elements.Find( h ) < 0 ); + m_MailingLists[list].m_Elements.AddToTail( h ); +} + +bool CDataModel::RemoveElementFromMailingList( DmMailingList_t list, DmElementHandle_t h ) +{ + // Make sure we find it! + MailingList_t &mailingList = m_MailingLists[list]; + int i = mailingList.m_Elements.Find( h ); + Assert( i >= 0 ); + mailingList.m_Elements.FastRemove( i ); + return ( mailingList.m_Elements.Count() != 0 ); +} + +bool CDataModel::PostAttributeChanged( DmMailingList_t list, CDmAttribute *pAttribute ) +{ + MailingList_t &mailingList = m_MailingLists[list]; + int nCount = mailingList.m_Elements.Count(); + for ( int i = nCount; --i >= 0; ) + { + DmElementHandle_t hElement = mailingList.m_Elements[i]; + CDmElement *pElement = GetElement( hElement ); + if ( pElement ) + { + pElement->OnAttributeChanged( pAttribute ); + } + else + { + // The element handle is stale; remove it. + mailingList.m_Elements.FastRemove( i ); + } + } + + return ( mailingList.m_Elements.Count() != 0 ); +} + + +//----------------------------------------------------------------------------- +// +// 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 +} + +UtlSymId_t CDataModel::GetUndoDescInternal( const char *context ) +{ + return GetUndoMgr()->GetUndoDescInternal( context ); +} + +UtlSymId_t CDataModel::GetRedoDescInternal( const char *context ) +{ + return GetUndoMgr()->GetRedoDescInternal( context ); +} + +void CDataModel::GetUndoInfo( CUtlVector< UndoInfo_t >& list ) +{ + GetUndoMgr()->GetUndoInfo( list ); +} + +const char *CDataModel::GetUndoString( UtlSymId_t sym ) +{ + return CUndoManager::GetUndoString( sym ); +} + + +//----------------------------------------------------------------------------- +// +// 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(); +} + + |