diff options
| author | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
|---|---|---|
| committer | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
| commit | 3bf9df6b2785fa6d951086978a3e66f49427166a (patch) | |
| tree | 2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /datamodel | |
| download | archived-source-engine-2018-hl2-src-master.tar.xz archived-source-engine-2018-hl2-src-master.zip | |
Diffstat (limited to 'datamodel')
| -rw-r--r-- | datamodel/DmElementFramework.cpp | 209 | ||||
| -rw-r--r-- | datamodel/DmElementFramework.h | 78 | ||||
| -rw-r--r-- | datamodel/clipboardmanager.cpp | 169 | ||||
| -rw-r--r-- | datamodel/clipboardmanager.h | 36 | ||||
| -rw-r--r-- | datamodel/datamodel.cpp | 2464 | ||||
| -rw-r--r-- | datamodel/datamodel.h | 527 | ||||
| -rw-r--r-- | datamodel/datamodel.vpc | 76 | ||||
| -rw-r--r-- | datamodel/dependencygraph.cpp | 331 | ||||
| -rw-r--r-- | datamodel/dependencygraph.h | 62 | ||||
| -rw-r--r-- | datamodel/dmattribute.cpp | 3265 | ||||
| -rw-r--r-- | datamodel/dmattributeinternal.h | 75 | ||||
| -rw-r--r-- | datamodel/dmelement.cpp | 1420 | ||||
| -rw-r--r-- | datamodel/dmelementdictionary.cpp | 468 | ||||
| -rw-r--r-- | datamodel/dmelementdictionary.h | 184 | ||||
| -rw-r--r-- | datamodel/dmelementfactoryhelper.cpp | 100 | ||||
| -rw-r--r-- | datamodel/dmserializerbinary.cpp | 587 | ||||
| -rw-r--r-- | datamodel/dmserializerbinary.h | 27 | ||||
| -rw-r--r-- | datamodel/dmserializerkeyvalues.cpp | 466 | ||||
| -rw-r--r-- | datamodel/dmserializerkeyvalues.h | 27 | ||||
| -rw-r--r-- | datamodel/dmserializerkeyvalues2.cpp | 1377 | ||||
| -rw-r--r-- | datamodel/dmserializerkeyvalues2.h | 27 | ||||
| -rw-r--r-- | datamodel/undomanager.cpp | 444 | ||||
| -rw-r--r-- | datamodel/undomanager.h | 124 |
23 files changed, 12543 insertions, 0 deletions
diff --git a/datamodel/DmElementFramework.cpp b/datamodel/DmElementFramework.cpp new file mode 100644 index 0000000..94813b2 --- /dev/null +++ b/datamodel/DmElementFramework.cpp @@ -0,0 +1,209 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= + +#include "DmElementFramework.h" +#include "datamodel.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + + +//----------------------------------------------------------------------------- +// Singleton instance +//----------------------------------------------------------------------------- +static CDmElementFramework g_DmElementFramework; +CDmElementFramework *g_pDmElementFrameworkImp = &g_DmElementFramework; +IDmElementFramework *g_pDmElementFramework = &g_DmElementFramework; + + +//----------------------------------------------------------------------------- +// Constructor +//----------------------------------------------------------------------------- +CDmElementFramework::CDmElementFramework() : m_phase( PH_EDIT ), m_dirtyElements( 128, 256 ) +{ +} + + +//----------------------------------------------------------------------------- +// Methods of IAppSystem +//----------------------------------------------------------------------------- +bool CDmElementFramework::Connect( CreateInterfaceFn factory ) +{ + return true; +} + +void CDmElementFramework::Disconnect() +{ +} + +void *CDmElementFramework::QueryInterface( const char *pInterfaceName ) +{ + if ( !V_strcmp( pInterfaceName, VDMELEMENTFRAMEWORK_VERSION ) ) + return (IDmElementFramework*)this; + + return NULL; +} + +InitReturnVal_t CDmElementFramework::Init( ) +{ + return INIT_OK; +} + +void CDmElementFramework::Shutdown() +{ + m_dependencyGraph.Cleanup(); +} + + +//----------------------------------------------------------------------------- +// element framework phase transition methods +//----------------------------------------------------------------------------- +void CDmElementFramework::EditApply() +{ + g_pDataModelImp->RemoveUnreferencedElements(); +} + +void CDmElementFramework::Resolve( bool clearDirtyFlags ) +{ + int nCount = m_dirtyElements.Count(); + for ( int ei = 0; ei < nCount; ++ei ) + { + DmElementHandle_t h = m_dirtyElements[ ei ]; + CDmElement *pElement = g_pDataModel->GetElement( h ); + if ( !pElement ) + continue; + + pElement->Resolve(); + + if ( clearDirtyFlags ) + { + CDmeElementAccessor::MarkDirty( pElement, false ); // marks element clean + CDmeElementAccessor::MarkAttributesClean( pElement ); // marks all attributes clean + } + } + + if ( clearDirtyFlags ) + { + m_dirtyElements.RemoveAll(); + } +} + + +//----------------------------------------------------------------------------- +// Returns the current phase +//----------------------------------------------------------------------------- +DmPhase_t CDmElementFramework::GetPhase() +{ + return FastGetPhase(); +} + +void CDmElementFramework::SetOperators( const CUtlVector< IDmeOperator* > &operators ) +{ + VPROF( "CDmElementFramework::SetOperators()" ); + m_dependencyGraph.Reset( operators ); +} + +void CDmElementFramework::BeginEdit() +{ + Assert( m_phase == PH_EDIT || m_phase == PH_OUTPUT ); + + if ( m_phase == PH_EDIT ) + { + m_phase = PH_EDIT_APPLY; + EditApply(); + + m_phase = PH_EDIT_RESOLVE; + Resolve( false ); + } + + m_phase = PH_EDIT; +} + +void CDmElementFramework::Operate( bool bResolve ) +{ + VPROF( "CDmElementFramework::Operate" ); + + Assert( m_phase == PH_EDIT || m_phase == PH_OUTPUT ); + + if ( m_phase == PH_EDIT ) + { + { + VPROF( "CDmElementFramework::PH_EDIT_APPLY" ); + m_phase = PH_EDIT_APPLY; + EditApply(); + } + + { + VPROF( "CDmElementFramework::PH_EDIT_RESOLVE" ); + m_phase = PH_EDIT_RESOLVE; + Resolve( false ); + } + } + + { + VPROF( "CDmElementFramework::PH_DEPENDENCY" ); + m_phase = PH_DEPENDENCY; + bool cycle = m_dependencyGraph.CullAndSortOperators(); + if ( cycle ) + { + Warning( "Operator cycle found during dependency graph traversal!\n" ); + } + } + + { + VPROF( "CDmElementFramework::PH_OPERATE" ); + m_phase = PH_OPERATE; + const CUtlVector< IDmeOperator* > &operatorsToRun = m_dependencyGraph.GetSortedOperators(); + uint on = operatorsToRun.Count(); + for ( uint oi = 0; oi < on; ++oi ) + { + operatorsToRun[ oi ]->Operate(); + } + } + + if ( bResolve ) + { + VPROF( "CDmElementFramework::PH_OPERATE_RESOLVE" ); + m_phase = PH_OPERATE_RESOLVE; + Resolve( true ); + + m_phase = PH_OUTPUT; + } +} + +void CDmElementFramework::Resolve() +{ + VPROF( "CDmElementFramework::Resolve" ); + + Assert( m_phase == PH_OPERATE ); + + m_phase = PH_OPERATE_RESOLVE; + Resolve( true ); + + m_phase = PH_OUTPUT; +} + +void CDmElementFramework::AddElementToDirtyList( DmElementHandle_t hElement ) +{ + m_dirtyElements.AddToTail( hElement ); +} + +void CDmElementFramework::RemoveCleanElementsFromDirtyList() +{ + int nCount = m_dirtyElements.Count(); + while ( --nCount >= 0 ) + { + DmElementHandle_t h = m_dirtyElements[ nCount ]; + CDmElement *pElement = g_pDataModel->GetElement( h ); + if ( !pElement ) + continue; + + if ( !CDmeElementAccessor::IsDirty( pElement ) ) + { + m_dirtyElements.FastRemove( nCount ); + } + } +} diff --git a/datamodel/DmElementFramework.h b/datamodel/DmElementFramework.h new file mode 100644 index 0000000..6cb9098 --- /dev/null +++ b/datamodel/DmElementFramework.h @@ -0,0 +1,78 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= + +#ifndef DMELEMENTFRAMEWORK_H +#define DMELEMENTFRAMEWORK_H + +#ifdef _WIN32 +#pragma once +#endif + +#include "datamodel/idatamodel.h" +#include "tier1/utlvector.h" +#include "dependencygraph.h" + + +//----------------------------------------------------------------------------- +// element framework implementation +//----------------------------------------------------------------------------- +class CDmElementFramework : public IDmElementFramework +{ +public: + CDmElementFramework(); + +public: + // Methods of IAppSystem + virtual bool Connect( CreateInterfaceFn factory ); + virtual void Disconnect(); + virtual void *QueryInterface( const char *pInterfaceName ); + virtual InitReturnVal_t Init(); + virtual void Shutdown(); + + // Methods of IDmElementFramework + virtual DmPhase_t GetPhase(); + virtual void SetOperators( const CUtlVector< IDmeOperator* > &operators ); + virtual void BeginEdit(); // ends in edit phase, forces apply/resolve if from edit phase + virtual void Operate( bool bResolve ); // ends in output phase + virtual void Resolve(); + +public: + // Other public methods + void AddElementToDirtyList( DmElementHandle_t hElement ); + void RemoveCleanElementsFromDirtyList(); + + // Non-virtual methods of identical virtual functions + DmPhase_t FastGetPhase(); + + +private: + void EditApply(); + + // Invoke the resolve method + void Resolve( bool clearDirtyFlags ); + + CDependencyGraph m_dependencyGraph; + CUtlVector< DmElementHandle_t > m_dirtyElements; + DmPhase_t m_phase; +}; + + +//----------------------------------------------------------------------------- +// Singleton +//----------------------------------------------------------------------------- +extern CDmElementFramework *g_pDmElementFrameworkImp; + + +//----------------------------------------------------------------------------- +// Inline methods +//----------------------------------------------------------------------------- +inline DmPhase_t CDmElementFramework::FastGetPhase() +{ + return m_phase; +} + + +#endif // DMELEMENTFRAMEWORK_H
\ No newline at end of file diff --git a/datamodel/clipboardmanager.cpp b/datamodel/clipboardmanager.cpp new file mode 100644 index 0000000..5ffa533 --- /dev/null +++ b/datamodel/clipboardmanager.cpp @@ -0,0 +1,169 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= + +#include "clipboardmanager.h" +#include "datamodel.h" +#include "tier1/KeyValues.h" + +#ifndef _LINUX +#define USE_WINDOWS_CLIPBOARD +#endif + +#if defined( USE_WINDOWS_CLIPBOARD ) +#include <windows.h> +#endif + +CClipboardManager::CClipboardManager( ) : + m_pfnCleanup( NULL ) +{ +} + +CClipboardManager::~CClipboardManager() +{ + EmptyClipboard( false ); +} + +void CClipboardManager::EmptyClipboard( bool bClearWindowsClipboard ) +{ + // Call optional cleanup function if there is one... + if ( m_pfnCleanup ) + { + m_pfnCleanup->ReleaseClipboardData( m_Data ); + } + int c = m_Data.Count(); + for ( int i = 0; i < c; ++i ) + { + m_Data[ i ]->deleteThis(); + } + m_Data.RemoveAll(); + m_pfnCleanup = NULL; + +#if defined( USE_WINDOWS_CLIPBOARD ) + if ( bClearWindowsClipboard ) + { + if ( ::OpenClipboard( ::GetDesktopWindow() ) ) + { + ::EmptyClipboard(); + ::CloseClipboard(); + } + } +#endif +} + +void CClipboardManager::SetClipboardData( CUtlVector< KeyValues * >& data, IClipboardCleanup *pfnOptionalCleanuFunction ) +{ + EmptyClipboard( true ); + m_Data = data; + m_pfnCleanup = pfnOptionalCleanuFunction; + +#if defined( USE_WINDOWS_CLIPBOARD ) + if ( m_Data.Count() >= 0 ) + { + // Only stick the first item's data into the clipboard + char const *text = m_Data[ 0 ]->GetString( "text", "" ); + if ( text && text[ 0 ] ) + { + int textLen = Q_strlen( text ); + + if ( ::OpenClipboard( ::GetDesktopWindow() ) ) + { + HANDLE hmem = ::GlobalAlloc(GMEM_MOVEABLE, textLen + 1); + if (hmem) + { + void *ptr = ::GlobalLock( hmem ); + if ( ptr ) + { + Q_memset( ptr, 0, textLen + 1 ); + Q_memcpy( ptr, text, textLen ); + ::GlobalUnlock( hmem ); + + ::SetClipboardData( CF_TEXT, hmem ); + } + } + ::CloseClipboard(); + } + } + } +#endif +} + +void CClipboardManager::AddToClipboardData( KeyValues *add ) +{ + m_Data.AddToTail( add ); +#if defined( USE_WINDOWS_CLIPBOARD ) + if ( m_Data.Count() >= 0 ) + { + // Only stick the first item's data into the clipboard + char const *text = m_Data[ 0 ]->GetString( "text", "" ); + if ( text && text[ 0 ] ) + { + int textLen = Q_strlen( text ); + + + if ( ::OpenClipboard( ::GetDesktopWindow() ) ) + { + ::EmptyClipboard(); + + HANDLE hmem = ::GlobalAlloc(GMEM_MOVEABLE, textLen + 1); + if (hmem) + { + void *ptr = ::GlobalLock( hmem ); + if ( ptr ) + { + Q_memset( ptr, 0, textLen + 1 ); + Q_memcpy( ptr, text, textLen ); + ::GlobalUnlock( hmem ); + + ::SetClipboardData( CF_TEXT, hmem ); + } + } + ::CloseClipboard(); + } + } + } +#endif +} + +void CClipboardManager::GetClipboardData( CUtlVector< KeyValues * >& data ) +{ + data.RemoveAll(); + data = m_Data; +#if defined( USE_WINDOWS_CLIPBOARD ) + if ( data.Count() == 0 ) + { + // See if windows has some text since we didn't have any internally + if ( ::OpenClipboard( ::GetDesktopWindow() ) ) + { + HANDLE hmem = ::GetClipboardData( CF_TEXT ); + if ( hmem ) + { + int len = GlobalSize( hmem ); + if ( len > 0 ) + { + void *ptr = GlobalLock(hmem); + if ( ptr ) + { + char buf[ 8192 ]; + len = min( len, 8191 ); + Q_memcpy( buf, ( char * )ptr, len ); + buf[ 8191 ] = 0; + GlobalUnlock(hmem); + + KeyValues *newData = new KeyValues( "ClipBoard", "text", buf ); + data.AddToTail( newData ); + } + } + } + ::CloseClipboard(); + } + } +#endif +} + +bool CClipboardManager::HasClipboardData() const +{ + return m_Data.Count() > 0 ? true : false; +} diff --git a/datamodel/clipboardmanager.h b/datamodel/clipboardmanager.h new file mode 100644 index 0000000..eecef44 --- /dev/null +++ b/datamodel/clipboardmanager.h @@ -0,0 +1,36 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= + +#ifndef CLIPBOARDMANAGER_H +#define CLIPBOARDMANAGER_H +#ifdef _WIN32 +#pragma once +#endif + +#include "tier1/utlvector.h" + +class KeyValues; +class IClipboardCleanup; + +// Clipboard: +// +class CClipboardManager +{ +public: + CClipboardManager(); + ~CClipboardManager(); + + void EmptyClipboard( bool bClearWindowsClipboard ); + void SetClipboardData( CUtlVector< KeyValues * >& data, IClipboardCleanup *pfnOptionalCleanuFunction ); + void AddToClipboardData( KeyValues *add ); + void GetClipboardData( CUtlVector< KeyValues * >& data ); + bool HasClipboardData() const; +private: + CUtlVector< KeyValues * > m_Data; + IClipboardCleanup *m_pfnCleanup; +}; + +#endif // CLIPBOARDMANAGER_H 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(); +} + + diff --git a/datamodel/datamodel.h b/datamodel/datamodel.h new file mode 100644 index 0000000..129f1ee --- /dev/null +++ b/datamodel/datamodel.h @@ -0,0 +1,527 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= + +#ifndef DATAMODEL_H +#define DATAMODEL_H +#ifdef _WIN32 +#pragma once +#endif + +#include "datamodel/dmattribute.h" +#include "datamodel/idatamodel.h" +#include "datamodel/dmelement.h" +#include "datamodel/dmehandle.h" +#include "tier1/uniqueid.h" +#include "tier1/utlsymbol.h" +#include "tier1/utllinkedlist.h" +#include "tier1/utldict.h" +#include "tier1/utlstring.h" +#include "tier1/utlhandletable.h" +#include "tier1/utlhash.h" +#include "tier2/tier2.h" +#include "clipboardmanager.h" +#include "undomanager.h" +#include "tier1/convar.h" +#include "tier0/vprof.h" + + +//----------------------------------------------------------------------------- +// forward declarations +//----------------------------------------------------------------------------- +class IDmElementFramework; +class IUndoElement; +class CDmElement; + +enum DmHandleReleasePolicy +{ + HR_ALWAYS, + HR_NEVER, + HR_IF_NOT_REFERENCED, +}; + + +//----------------------------------------------------------------------------- +// memory categories +//----------------------------------------------------------------------------- +enum +{ + MEMORY_CATEGORY_OUTER, + MEMORY_CATEGORY_ELEMENT_INTERNAL, + MEMORY_CATEGORY_DATAMODEL, + MEMORY_CATEGORY_REFERENCES, + MEMORY_CATEGORY_ATTRIBUTE_TREE, + MEMORY_CATEGORY_ATTRIBUTE_OVERHEAD, + MEMORY_CATEGORY_ATTRIBUTE_DATA, + MEMORY_CATEGORY_ATTRIBUTE_COUNT, + + MEMORY_CATEGORY_COUNT, +}; + + +//----------------------------------------------------------------------------- +// hash map of id->element, with the id storage optimized out +//----------------------------------------------------------------------------- +class CElementIdHash : public CUtlHash< DmElementHandle_t > +{ +public: + CElementIdHash( int nBucketCount = 0, int nGrowCount = 0, int nInitCount = 0 ) + : CUtlHash< DmElementHandle_t >( nBucketCount, nGrowCount, nInitCount, CompareFunc, KeyFunc ) + { + } + +protected: + typedef CUtlHash< DmElementHandle_t > BaseClass; + + static bool CompareFunc( DmElementHandle_t const& a, DmElementHandle_t const& b ) { return a == b; } + static bool IdCompareFunc( DmElementHandle_t const& hElement, DmObjectId_t const& id ) + { + CDmElement *pElement = g_pDataModel->GetElement( hElement ); + Assert( pElement ); + if ( !pElement ) + return false; + + return IsUniqueIdEqual( id, pElement->GetId() ); + } + + static unsigned int KeyFunc( DmElementHandle_t const& hElement ) + { + CDmElement *pElement = g_pDataModel->GetElement( hElement ); + Assert( pElement ); + if ( !pElement ) + return 0; + + return *( unsigned int* )&pElement->GetId(); + } + static unsigned int IdKeyFunc( DmObjectId_t const &src ) + { + return *(unsigned int*)&src; + } + +protected: + bool DoFind( DmObjectId_t const &src, unsigned int *pBucket, int *pIndex ) + { + // generate the data "key" + unsigned int key = IdKeyFunc( src ); + + // hash the "key" - get the correct hash table "bucket" + unsigned int ndxBucket; + if( m_bPowerOfTwo ) + { + *pBucket = ndxBucket = ( key & m_ModMask ); + } + else + { + int bucketCount = m_Buckets.Count(); + *pBucket = ndxBucket = key % bucketCount; + } + + int ndxKeyData; + CUtlVector< DmElementHandle_t > &bucket = m_Buckets[ndxBucket]; + int keyDataCount = bucket.Count(); + for( ndxKeyData = 0; ndxKeyData < keyDataCount; ndxKeyData++ ) + { + if( IdCompareFunc( bucket.Element( ndxKeyData ), src ) ) + break; + } + + if( ndxKeyData == keyDataCount ) + return false; + + *pIndex = ndxKeyData; + return true; + } + +public: + UtlHashHandle_t Find( DmElementHandle_t const &src ) { return BaseClass::Find( src ); } + UtlHashHandle_t Find( DmObjectId_t const &src ) + { + unsigned int ndxBucket; + int ndxKeyData; + + if ( DoFind( src, &ndxBucket, &ndxKeyData ) ) + return BuildHandle( ndxBucket, ndxKeyData ); + + return InvalidHandle(); + } +}; + +//----------------------------------------------------------------------------- +// struct to hold the set of elements in any given file +//----------------------------------------------------------------------------- + +struct FileElementSet_t +{ + FileElementSet_t( UtlSymId_t filename = UTL_INVAL_SYMBOL, UtlSymId_t format = UTL_INVAL_SYMBOL ) : + m_filename( filename ), m_format( format ), + m_hRoot( DMELEMENT_HANDLE_INVALID ), + m_bLoaded( true ), + m_nElements( 0 ) + { + } + FileElementSet_t( const FileElementSet_t& that ) : m_filename( that.m_filename ), m_format( that.m_format ), m_hRoot( DMELEMENT_HANDLE_INVALID ), m_bLoaded( that.m_bLoaded ), m_nElements( that.m_nElements ) + { + // the only time this should be copy constructed is when passing in an empty set to the parent array + // otherwise it could get prohibitively expensive time and memory wise + Assert( that.m_nElements == 0 ); + } + + UtlSymId_t m_filename; + UtlSymId_t m_format; + CDmeCountedHandle m_hRoot; + bool m_bLoaded; + int m_nElements; +}; + + +//----------------------------------------------------------------------------- +// Purpose: Versionable factor for element types +//----------------------------------------------------------------------------- +class CDataModel : public CBaseAppSystem< IDataModel > +{ + typedef CBaseAppSystem< IDataModel > BaseClass; + +public: + CDataModel(); + virtual ~CDataModel(); + +// External interface +public: + // Methods of IAppSystem + virtual bool Connect( CreateInterfaceFn factory ); + virtual void *QueryInterface( const char *pInterfaceName ); + virtual InitReturnVal_t Init(); + virtual void Shutdown(); + + // Methods of IDataModel + virtual void AddElementFactory( const char *pClassName, IDmElementFactory *pFactory ); + virtual bool HasElementFactory( const char *pElementType ) const; + virtual void SetDefaultElementFactory( IDmElementFactory *pFactory ); + virtual int GetFirstFactory() const; + virtual int GetNextFactory( int index ) const; + virtual bool IsValidFactory( int index ) const; + virtual char const *GetFactoryName( int index ) const; + virtual DmElementHandle_t CreateElement( UtlSymId_t typeSymbol, const char *pElementName, DmFileId_t fileid, const DmObjectId_t *pObjectID = NULL ); + virtual DmElementHandle_t CreateElement( const char *pTypeName, const char *pElementName, DmFileId_t fileid, const DmObjectId_t *pObjectID = NULL ); + virtual void DestroyElement( DmElementHandle_t hElement ); + virtual CDmElement* GetElement( DmElementHandle_t hElement ) const; + virtual UtlSymId_t GetElementType( DmElementHandle_t hElement ) const; + virtual const char* GetElementName( DmElementHandle_t hElement ) const; + virtual const DmObjectId_t& GetElementId( DmElementHandle_t hElement ) const; + virtual const char *GetAttributeNameForType( DmAttributeType_t attType ) const; + virtual DmAttributeType_t GetAttributeTypeForName( const char *name ) const; + + virtual void AddSerializer( IDmSerializer *pSerializer ); + virtual void AddLegacyUpdater( IDmLegacyUpdater *pUpdater ); + virtual void AddFormatUpdater( IDmFormatUpdater *pUpdater ); + virtual const char* GetFormatExtension( const char *pFormatName ); + virtual const char* GetFormatDescription( const char *pFormatName ); + virtual int GetFormatCount() const; + virtual const char * GetFormatName( int i ) const; + virtual const char * GetDefaultEncoding( const char *pFormatName ); + virtual int GetEncodingCount() const; + virtual const char * GetEncodingName( int i ) const; + virtual bool IsEncodingBinary( const char *pEncodingName ) const; + virtual bool DoesEncodingStoreVersionInFile( const char *pEncodingName ) const; + + virtual void SetSerializationDelimiter( CUtlCharConversion *pConv ); + virtual void SetSerializationArrayDelimiter( const char *pDelimiter ); + virtual bool IsUnserializing(); + virtual bool Serialize( CUtlBuffer &outBuf, const char *pEncodingName, const char *pFormatName, DmElementHandle_t hRoot ); + virtual bool Unserialize( CUtlBuffer &buf, const char *pEncodingName, const char *pSourceFormatName, const char *pFormatHint, + const char *pFileName, DmConflictResolution_t idConflictResolution, DmElementHandle_t &hRoot ); + virtual bool UpdateUnserializedElements( const char *pSourceFormatName, int nSourceFormatVersion, + DmFileId_t fileid, DmConflictResolution_t idConflictResolution, CDmElement **ppRoot ); + virtual IDmSerializer* FindSerializer( const char *pEncodingName ) const; + virtual IDmLegacyUpdater* FindLegacyUpdater( const char *pLegacyFormatName ) const; + virtual IDmFormatUpdater* FindFormatUpdater( const char *pFormatName ) const; + virtual bool SaveToFile( char const *pFileName, char const *pPathID, const char *pEncodingName, const char *pFormatName, CDmElement *pRoot ); + virtual DmFileId_t RestoreFromFile( char const *pFileName, char const *pPathID, const char *pFormatHint, CDmElement **ppRoot, DmConflictResolution_t idConflictResolution = CR_DELETE_NEW, DmxHeader_t *pHeaderOut = NULL ); + + virtual void SetKeyValuesElementCallback( IElementForKeyValueCallback *pCallbackInterface ); + virtual const char * GetKeyValuesElementName( const char *pszKeyName, int iNestingLevel ); + virtual UtlSymId_t GetSymbol( const char *pString ); + virtual const char * GetString( UtlSymId_t sym ) const; + virtual int GetElementsAllocatedSoFar(); + virtual int GetMaxNumberOfElements(); + virtual int GetAllocatedAttributeCount(); + virtual int GetAllocatedElementCount(); + virtual DmElementHandle_t FirstAllocatedElement(); + virtual DmElementHandle_t NextAllocatedElement( DmElementHandle_t hElement ); + virtual int EstimateMemoryUsage( DmElementHandle_t hElement, TraversalDepth_t depth = TD_DEEP ); + virtual void SetUndoEnabled( bool enable ); + virtual bool IsUndoEnabled() const; + virtual bool UndoEnabledForElement( const CDmElement *pElement ) const; + virtual bool IsDirty() const; + virtual bool CanUndo() const; + virtual bool CanRedo() const; + virtual void StartUndo( const char *undodesc, const char *redodesc, int nChainingID = 0 ); + virtual void FinishUndo(); + virtual void AbortUndoableOperation(); + virtual void ClearRedo(); + virtual const char *GetUndoDesc(); + virtual const char *GetRedoDesc(); + virtual void Undo(); + virtual void Redo(); + virtual void TraceUndo( bool state ); // if true, undo records spew as they are added + virtual void ClearUndo(); + virtual void GetUndoInfo( CUtlVector< UndoInfo_t >& list ); + virtual const char * GetUndoString( UtlSymId_t sym ); + virtual void AddUndoElement( IUndoElement *pElement ); + virtual UtlSymId_t GetUndoDescInternal( const char *context ); + virtual UtlSymId_t GetRedoDescInternal( const char *context ); + virtual void EmptyClipboard(); + virtual void SetClipboardData( CUtlVector< KeyValues * >& data, IClipboardCleanup *pfnOptionalCleanuFunction = 0 ); + virtual void AddToClipboardData( KeyValues *add ); + virtual void GetClipboardData( CUtlVector< KeyValues * >& data ); + virtual bool HasClipboardData() const; + + virtual CDmAttribute * GetAttribute( DmAttributeHandle_t h ); + virtual bool IsAttributeHandleValid( DmAttributeHandle_t h ) const; + virtual void OnlyCreateUntypedElements( bool bEnable ); + virtual int NumFileIds(); + virtual DmFileId_t GetFileId( int i ); + virtual DmFileId_t FindOrCreateFileId( const char *pFilename ); + virtual void RemoveFileId( DmFileId_t fileid ); + virtual DmFileId_t GetFileId( const char *pFilename ); + virtual const char * GetFileName( DmFileId_t fileid ); + virtual void SetFileName( DmFileId_t fileid, const char *pFileName ); + virtual const char * GetFileFormat( DmFileId_t fileid ); + virtual void SetFileFormat( DmFileId_t fileid, const char *pFormat ); + virtual DmElementHandle_t GetFileRoot( DmFileId_t fileid ); + virtual void SetFileRoot( DmFileId_t fileid, DmElementHandle_t hRoot ); + virtual bool IsFileLoaded( DmFileId_t fileid ); + virtual void MarkFileLoaded( DmFileId_t fileid ); + virtual void UnloadFile( DmFileId_t fileid ); + virtual int NumElementsInFile( DmFileId_t fileid ); + virtual void DontAutoDelete( DmElementHandle_t hElement ); + virtual void MarkHandleInvalid( DmElementHandle_t hElement ); + virtual void MarkHandleValid( DmElementHandle_t hElement ); + virtual DmElementHandle_t FindElement( const DmObjectId_t &id ); + virtual DmAttributeReferenceIterator_t FirstAttributeReferencingElement( DmElementHandle_t hElement ); + virtual DmAttributeReferenceIterator_t NextAttributeReferencingElement( DmAttributeReferenceIterator_t hAttrIter ); + virtual CDmAttribute * GetAttribute( DmAttributeReferenceIterator_t hAttrIter ); + virtual bool InstallNotificationCallback( IDmNotify *pNotify ); + virtual void RemoveNotificationCallback( IDmNotify *pNotify ); + virtual bool IsSuppressingNotify( ) const; + virtual void SetSuppressingNotify( bool bSuppress ); + virtual void PushNotificationScope( const char *pReason, int nNotifySource, int nNotifyFlags ); + virtual void PopNotificationScope( bool bAbort ); + virtual void SetUndoDepth( int nSize ); + virtual void DisplayMemoryStats(); + +public: + // Internal public methods + int GetCurrentFormatVersion( const char *pFormatName ); + + // CreateElement references the attribute list passed in via ref, so don't edit or purge ref's attribute list afterwards + CDmElement* CreateElement( const DmElementReference_t &ref, const char *pElementType, const char *pElementName, DmFileId_t fileid, const DmObjectId_t *pObjectID ); + void DeleteElement( DmElementHandle_t hElement, DmHandleReleasePolicy hrp = HR_ALWAYS ); + + // element handle related methods + DmElementHandle_t AcquireElementHandle(); + void ReleaseElementHandle( DmElementHandle_t hElement ); + + // Handles to attributes + DmAttributeHandle_t AcquireAttributeHandle( CDmAttribute *pAttribute ); + void ReleaseAttributeHandle( DmAttributeHandle_t hAttribute ); + + // remove orphaned element subtrees + void FindAndDeleteOrphanedElements(); + + // Event "mailing list" + DmMailingList_t CreateMailingList(); + void DestroyMailingList( DmMailingList_t list ); + void AddElementToMailingList( DmMailingList_t list, DmElementHandle_t h ); + + // Returns false if the mailing list is empty now + bool RemoveElementFromMailingList( DmMailingList_t list, DmElementHandle_t h ); + + // Returns false if the mailing list is empty now (can happen owing to stale attributes) + bool PostAttributeChanged( DmMailingList_t list, CDmAttribute *pAttribute ); + + void GetInvalidHandles( CUtlVector< DmElementHandle_t > &handles ); + void MarkHandlesValid( CUtlVector< DmElementHandle_t > &handles ); + void MarkHandlesInvalid( CUtlVector< DmElementHandle_t > &handles ); + + // 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 FindOrCreateElementHandle( const DmObjectId_t &id ); + + // changes an element's id and associated mappings - generally during unserialization + DmElementHandle_t ChangeElementId( DmElementHandle_t hElement, const DmObjectId_t &oldId, const DmObjectId_t &newId ); + + DmElementReference_t *FindElementReference( DmElementHandle_t hElement, DmObjectId_t **ppId = NULL ); + + void RemoveUnreferencedElements(); + + void RemoveElementFromFile( DmElementHandle_t hElement, DmFileId_t fileid ); + void AddElementToFile( DmElementHandle_t hElement, DmFileId_t fileid ); + + void NotifyState( int nNotifyFlags ); + + int EstimateMemoryOverhead() const; + + bool IsCreatingUntypedElements() const { return m_bOnlyCreateUntypedElements; } + + unsigned short GetSymbolCount() const; + +private: + struct MailingList_t + { + CUtlVector<DmElementHandle_t> m_Elements; + }; + + struct ElementIdHandlePair_t + { + DmObjectId_t m_id; + DmElementReference_t m_ref; + ElementIdHandlePair_t() {} + explicit ElementIdHandlePair_t( const DmObjectId_t &id ) : m_ref() + { + CopyUniqueId( id, &m_id ); + } + ElementIdHandlePair_t( const DmObjectId_t &id, const DmElementReference_t &ref ) : m_ref( ref ) + { + CopyUniqueId( id, &m_id ); + } + ElementIdHandlePair_t( const ElementIdHandlePair_t& that ) : m_ref( that.m_ref ) + { + CopyUniqueId( that.m_id, &m_id ); + } + ElementIdHandlePair_t &operator=( const ElementIdHandlePair_t &that ) + { + CopyUniqueId( that.m_id, &m_id ); + m_ref = that.m_ref; + return *this; + } + static unsigned int HashKey( const ElementIdHandlePair_t& that ) + { + return *( unsigned int* )&that.m_id.m_Value; + } + static bool Compare( const ElementIdHandlePair_t& a, const ElementIdHandlePair_t& b ) + { + return IsUniqueIdEqual( a.m_id, b.m_id ); + } + }; + +private: + CDmElement *Unserialize( CUtlBuffer& buf ); + void Serialize( CDmElement *element, CUtlBuffer& buf ); + + // Read the header, return the version (or false if it's not a DMX file) + bool ReadDMXHeader( CUtlBuffer &inBuf, DmxHeader_t *pHeader ) const; + const char *GetEncodingFromLegacyFormat( const char *pLegacyFormatName ) const; + bool IsValidNonDMXFormat( const char *pFormatName ) const; + bool IsLegacyFormat( const char *pFormatName ) const; + + // Returns the current undo manager + CUndoManager* GetUndoMgr(); + const CUndoManager* GetUndoMgr() const; + CClipboardManager *GetClipboardMgr(); + const CClipboardManager *GetClipboardMgr() const; + + void UnloadFile( DmFileId_t fileid, bool bDeleteElements ); + + friend class CDmeElementRefHelper; + friend class CDmAttribute; + template< class T > friend class CDmArrayAttributeOp; + + void OnElementReferenceAdded ( DmElementHandle_t hElement, CDmAttribute *pAttribute ); + void OnElementReferenceRemoved( DmElementHandle_t hElement, CDmAttribute *pAttribute ); + void OnElementReferenceAdded ( DmElementHandle_t hElement, bool bRefCount ); + void OnElementReferenceRemoved( DmElementHandle_t hElement, bool bRefCount ); + +private: + CUtlVector< IDmSerializer* > m_Serializers; + CUtlVector< IDmLegacyUpdater* > m_LegacyUpdaters; + CUtlVector< IDmFormatUpdater* > m_FormatUpdaters; + + IDmElementFactory *m_pDefaultFactory; + CUtlDict< IDmElementFactory*, int > m_Factories; + CUtlSymbolTable m_SymbolTable; + CUtlHandleTable< CDmElement, 20 > m_Handles; + CUtlHandleTable< CDmAttribute, 20 > m_AttributeHandles; + CUndoManager m_UndoMgr; + CUtlLinkedList< MailingList_t, DmMailingList_t > m_MailingLists; + + bool m_bIsUnserializing : 1; + bool m_bUnableToSetDefaultFactory : 1; + bool m_bOnlyCreateUntypedElements : 1; + bool m_bUnableToCreateOnlyUntypedElements : 1; + bool m_bDeleteOrphanedElements : 1; + + CUtlHandleTable< FileElementSet_t, 20 > m_openFiles; + + CElementIdHash m_elementIds; + CUtlHash< ElementIdHandlePair_t > m_unloadedIdElementMap; + CUtlVector< DmObjectId_t > m_unreferencedElementIds; + CUtlVector< DmElementHandle_t > m_unreferencedElementHandles; + + CClipboardManager m_ClipboardMgr; + IElementForKeyValueCallback *m_pKeyvaluesCallbackInterface; + + int m_nElementsAllocatedSoFar; + int m_nMaxNumberOfElements; +}; + +//----------------------------------------------------------------------------- +// Singleton +//----------------------------------------------------------------------------- +extern CDataModel *g_pDataModelImp; + + +//----------------------------------------------------------------------------- +// Inline methods +//----------------------------------------------------------------------------- +inline CUndoManager* CDataModel::GetUndoMgr() +{ + return &m_UndoMgr; +} + +inline const CUndoManager* CDataModel::GetUndoMgr() const +{ + return &m_UndoMgr; +} + +inline void CDataModel::NotifyState( int nNotifyFlags ) +{ + GetUndoMgr()->NotifyState( nNotifyFlags ); +} + +inline CClipboardManager *CDataModel::GetClipboardMgr() +{ + return &m_ClipboardMgr; +} + +inline const CClipboardManager *CDataModel::GetClipboardMgr() const +{ + return &m_ClipboardMgr; +} + + +//----------------------------------------------------------------------------- +// Methods of DmElement which are public to datamodel +//----------------------------------------------------------------------------- +class CDmeElementAccessor +{ +public: + static void Purge( CDmElement *pElement ) { pElement->Purge(); } + static void SetId( CDmElement *pElement, const DmObjectId_t &id ) { pElement->SetId( id ); } + static bool IsDirty( const CDmElement *pElement ) { return pElement->IsDirty(); } + static void MarkDirty( CDmElement *pElement, bool dirty = true ) { pElement->MarkDirty( dirty ); } + static void MarkAttributesClean( CDmElement *pElement ) { pElement->MarkAttributesClean(); } + static void MarkBeingUnserialized( CDmElement *pElement, bool beingUnserialized = true ) { pElement->MarkBeingUnserialized( beingUnserialized ); } + static bool IsBeingUnserialized( const CDmElement *pElement ) { return pElement->IsBeingUnserialized(); } + static void AddAttributeByPtr( CDmElement *pElement, CDmAttribute *ptr ) { pElement->AddAttributeByPtr( ptr ); } + static void RemoveAttributeByPtrNoDelete( CDmElement *pElement, CDmAttribute *ptr ) { pElement->RemoveAttributeByPtrNoDelete( ptr); } + static void ChangeHandle( CDmElement *pElement, DmElementHandle_t handle ) { pElement->ChangeHandle( handle ); } + static DmElementReference_t *GetReference( CDmElement *pElement ) { return pElement->GetReference(); } + static void SetReference( CDmElement *pElement, const DmElementReference_t &ref ) { pElement->SetReference( ref ); } + static int EstimateMemoryUsage( CDmElement *pElement, CUtlHash< DmElementHandle_t > &visited, TraversalDepth_t depth, int *pCategories ) { return pElement->EstimateMemoryUsage( visited, depth, pCategories ); } + static void PerformConstruction( CDmElement *pElement ) { pElement->PerformConstruction(); } + static void PerformDestruction( CDmElement *pElement ) { pElement->PerformDestruction(); } +}; + +#endif // DATAMODEL_H diff --git a/datamodel/datamodel.vpc b/datamodel/datamodel.vpc new file mode 100644 index 0000000..9e05870 --- /dev/null +++ b/datamodel/datamodel.vpc @@ -0,0 +1,76 @@ +//----------------------------------------------------------------------------- +// DATAMODEL.VPC +// +// Project Script +//----------------------------------------------------------------------------- + +$Macro SRCDIR ".." + +$include "$SRCDIR\vpc_scripts\source_lib_base.vpc" + +$Configuration +{ + $Compiler + { + $PreprocessorDefinitions "$BASE;DATAMODEL_LIB" + $PrecompiledHeaderFile "$(IntDir)/datamodel.pch" + } +} + +$Project "Datamodel" +{ + $Folder "Source Files" + { + $File "clipboardmanager.cpp" + $File "datamodel.cpp" + $File "dependencygraph.cpp" + $File "dmattribute.cpp" + $File "dmelement.cpp" + $File "dmelementdictionary.cpp" + $File "dmelementfactoryhelper.cpp" + $File "DmElementFramework.cpp" + $File "dmserializerbinary.cpp" + $File "dmserializerkeyvalues.cpp" + $File "dmserializerkeyvalues2.cpp" + $File "undomanager.cpp" + } + + $Folder "Header Files" + { + $File "clipboardmanager.h" + $File "datamodel.h" + $File "dependencygraph.h" + $File "dmattributeinternal.h" + $File "dmelementdictionary.h" + $File "$SRCDIR\public\datamodel\dmelementfactoryhelper.h" + $File "DmElementFramework.h" + $File "$SRCDIR\public\datamodel\dmelementhandle.h" + $File "dmserializerbinary.h" + $File "dmserializerkeyvalues.h" + $File "dmserializerkeyvalues2.h" + $File "undomanager.h" + } + + $Folder "external" + { + $File "$SRCDIR\public\tier0\basetypes.h" + $File "$SRCDIR\public\tier0\commonmacros.h" + $File "$SRCDIR\public\tier0\dbg.h" + $File "$SRCDIR\public\tier0\fasttimer.h" + $File "$SRCDIR\public\appframework\IAppSystem.h" + $File "$SRCDIR\public\tier1\interface.h" + $File "$SRCDIR\public\tier0\platform.h" + $File "$SRCDIR\public\tier0\protected_things.h" + $File "$SRCDIR\public\string_t.h" + } + + $Folder "Interface" + { + $File "$SRCDIR\public\datamodel\attributeflags.h" + $File "$SRCDIR\public\datamodel\dmattributetypes.h" + $File "$SRCDIR\public\datamodel\dmattributevar.h" + $File "$SRCDIR\public\datamodel\dmelement.h" + $File "$SRCDIR\public\datamodel\dmehandle.h" + $File "$SRCDIR\public\datamodel\idatamodel.h" + } +} diff --git a/datamodel/dependencygraph.cpp b/datamodel/dependencygraph.cpp new file mode 100644 index 0000000..2040b34 --- /dev/null +++ b/datamodel/dependencygraph.cpp @@ -0,0 +1,331 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= + +#include "dependencygraph.h" +#include "datamodel/idatamodel.h" +#include "datamodel/dmelement.h" +#include "mathlib/mathlib.h" // for swap + +#include "datamodel/dmattribute.h" +#include "datamodel/dmattributevar.h" + +#include "tier1/mempool.h" + +#include "tier0/vprof.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" +//----------------------------------------------------------------------------- +// Misc helper enums and classes for CDependencyGraph class +//----------------------------------------------------------------------------- +enum TraversalState_t +{ + TS_NOT_VISITED, + TS_VISITING, + TS_VISITED, +}; + +struct COperatorNode +{ + COperatorNode( IDmeOperator *pOp = NULL ) : + m_state( TS_NOT_VISITED ), + m_operator( pOp ), + m_bInList( false ) + { + } + + TraversalState_t m_state; + IDmeOperator *m_operator; + CUtlVector< CAttributeNode * > m_OutputAttributes; + bool m_bInList; +}; + +class CAttributeNode +{ +public: + CAttributeNode( CDmAttribute *attribute = NULL ) : + m_attribute( attribute ), + m_bIsOutputToOperator( false ) + { + } + + CDmAttribute *m_attribute; + CUtlVector< COperatorNode * > m_InputDependentOperators; + bool m_bIsOutputToOperator; +}; + +CClassMemoryPool< CAttributeNode > g_AttrNodePool( 1000 ); +CClassMemoryPool< COperatorNode > g_OperatorNodePool( 1000 ); + +bool HashEntryCompareFunc( CAttributeNode *const& lhs, CAttributeNode *const& rhs ) +{ + return lhs->m_attribute == rhs->m_attribute; +} + +uint HashEntryKeyFunc( CAttributeNode *const& keyinfo ) +{ + uint i = (uint)keyinfo->m_attribute; + return i >> 2; // since memory is allocated on a 4-byte (at least!) boundary +} + +//----------------------------------------------------------------------------- +// CDependencyGraph constructor - builds dependency graph from operators +//----------------------------------------------------------------------------- +CDependencyGraph::CDependencyGraph() : + m_attrNodes( 4096, 0, 0, HashEntryCompareFunc, HashEntryKeyFunc ) +{ +} + +void CDependencyGraph::Reset( const CUtlVector< IDmeOperator * > &operators ) +{ + VPROF_BUDGET( "CDependencyGraph::Reset", VPROF_BUDGETGROUP_TOOLS ); + + Cleanup(); + + CUtlVector< CDmAttribute * > attrs; // moved outside the loop to function as a temporary memory pool for performance + int on = operators.Count(); + CUtlRBTree< IDmeOperator * > operatorDict( 0, on * 2, DefLessFunc(IDmeOperator *) ); + for ( int i = 0; i < on; ++i ) + { + operatorDict.Insert( operators[i] ); + } + + m_opNodes.EnsureCapacity( on ); + for ( int oi = 0; oi < on; ++oi ) + { + IDmeOperator *pOp = operators[ oi ]; + Assert( pOp ); + if ( pOp == NULL ) + continue; + + COperatorNode *pOpNode = g_OperatorNodePool.Alloc(); + pOpNode->m_operator = pOp; + + attrs.RemoveAll(); + pOp->GetInputAttributes( attrs ); + int an = attrs.Count(); + for ( int ai = 0; ai < an; ++ai ) + { + CAttributeNode *pAttrNode = FindAttrNode( attrs[ ai ] ); + pAttrNode->m_InputDependentOperators.AddToTail( pOpNode ); + } + + attrs.RemoveAll(); + pOp->GetOutputAttributes( attrs ); + an = attrs.Count(); + for ( int ai = 0; ai < an; ++ai ) + { + CAttributeNode *pAttrNode = FindAttrNode( attrs[ ai ] ); + pAttrNode->m_bIsOutputToOperator = true; + pOpNode->m_OutputAttributes.AddToTail( pAttrNode ); + +#ifdef _DEBUG + // Look for dependent operators, add them if they are not in the array + // FIXME: Should this happen for input attributes too? + CDmElement* pElement = pAttrNode->m_attribute->GetOwner(); + IDmeOperator *pOperator = dynamic_cast< IDmeOperator* >( pElement ); + if ( pOperator ) + { + // Look for the operator in the existing list + if ( operatorDict.Find( pOperator ) == operatorDict.InvalidIndex() ) + { + CDmElement *pOp1 = dynamic_cast< CDmElement* >( pOperator ); + CDmElement *pOp2 = dynamic_cast< CDmElement* >( pOp ); + Warning( "Found dependent operator '%s' referenced by operator '%s' that wasn't in the scene or trackgroups!\n", pOp1->GetName(), pOp2->GetName() ); + } + } +#endif + } + + m_opNodes.AddToTail( pOpNode ); + } +} + +//----------------------------------------------------------------------------- +// CDependencyGraph destructor - releases temporary opNodes and attrNodes +//----------------------------------------------------------------------------- +CDependencyGraph::~CDependencyGraph() +{ + Cleanup(); +} + +void CDependencyGraph::Cleanup() +{ + VPROF_BUDGET( "CDependencyGraph::Cleanup", VPROF_BUDGETGROUP_TOOLS ); + + int on = m_opNodes.Count(); + for ( int oi = 0; oi < on; ++oi ) + { + g_OperatorNodePool.Free( m_opNodes[ oi ] ); + } + + UtlHashHandle_t h = m_attrNodes.GetFirstHandle(); + for ( ; h != m_attrNodes.InvalidHandle(); h = m_attrNodes.GetNextHandle( h ) ) + { + g_AttrNodePool.Free( m_attrNodes[ h ] ); + } + + m_opRoots.RemoveAll(); + m_opNodes.RemoveAll(); + m_attrNodes.RemoveAll(); + m_operators.RemoveAll(); +} + + +//----------------------------------------------------------------------------- +// caches changed operators as roots - typically once per frame, every frame +//----------------------------------------------------------------------------- +void CDependencyGraph::FindRoots() +{ + m_opRoots.RemoveAll(); + + uint oi; + uint on = m_opNodes.Count(); + + for ( oi = 0; oi < on; ++oi ) + { + COperatorNode *pOpNode = m_opNodes[ oi ]; + pOpNode->m_bInList = false; + pOpNode->m_state = TS_NOT_VISITED; + + IDmeOperator *pOp = pOpNode->m_operator; + if ( !pOp->IsDirty() ) + continue; + + m_opRoots.AddToTail( pOpNode ); + pOpNode->m_bInList = true; + } + + + + // Do we have an attribute which is an input to us which is not an output to some other op? + UtlHashHandle_t h = m_attrNodes.GetFirstHandle(); + for ( ; h != m_attrNodes.InvalidHandle(); h = m_attrNodes.GetNextHandle( h ) ) + { + CAttributeNode *pAttrNode = m_attrNodes[ h ]; + //Msg( "attrib %s %p\n", pAttrNode->m_attribute->GetName(), pAttrNode->m_attribute ); + if ( !pAttrNode->m_bIsOutputToOperator && + pAttrNode->m_attribute->IsFlagSet( FATTRIB_OPERATOR_DIRTY ) ) + { + on = pAttrNode->m_InputDependentOperators.Count(); + for ( oi = 0; oi < on; ++oi ) + { + COperatorNode *pOpNode = pAttrNode->m_InputDependentOperators[ oi ]; + if ( !pOpNode->m_bInList ) + { + m_opRoots.AddToTail( pOpNode ); + pOpNode->m_bInList = true; + } + } + } + + pAttrNode->m_attribute->RemoveFlag( FATTRIB_OPERATOR_DIRTY ); + } +} + + +//----------------------------------------------------------------------------- +// returns only the operators that need to be evaluated, sorted by dependencies +//----------------------------------------------------------------------------- +bool CDependencyGraph::CullAndSortOperators() +{ + FindRoots(); + + m_operators.RemoveAll(); + + bool cycle = GetOperatorOrdering( m_opRoots, m_operators ); // leaves to roots (outputs to inputs) + + int on = m_operators.Count(); + int oh = on / 2; + for ( int oi = 0; oi < oh; ++oi ) + { + V_swap( m_operators[ oi ], m_operators[ on - oi - 1 ] ); + } + return cycle; + +// return GetOperatorOrdering( m_opLeaves, operators ); // roots to leaves (inputs to outputs) +} + +//----------------------------------------------------------------------------- +// GetOperatorOrdering is just a recursive post-order depth-first-search +// since we have two types of nodes, we manually traverse to the opnodes grandchildren, which are opnodes +// (skipping children, which are attrnodes) +// returns true if a cycle found - in this case, an arbitrary link of the cycle will be ignored +//----------------------------------------------------------------------------- +bool CDependencyGraph::GetOperatorOrdering( CUtlVector< COperatorNode * > &pOpNodes, CUtlVector< IDmeOperator * > &operators ) +{ + bool cycle = false; + + uint on = pOpNodes.Count(); + for ( uint oi = 0; oi < on; ++oi ) + { + COperatorNode *pOpNode = pOpNodes[ oi ]; + if ( pOpNode->m_state != TS_NOT_VISITED ) + { + if ( pOpNode->m_state == TS_VISITING ) + { + cycle = true; + } + continue; + } + pOpNode->m_state = TS_VISITING; // mark as in being visited + + // DBG_PrintOperator( pIndent, pOpNode->m_operator ); + + // leaves to roots (outputs to inputs) + uint an = pOpNode->m_OutputAttributes.Count(); + for ( uint ai = 0; ai < an; ++ai ) + { + CAttributeNode *pAttrNode = pOpNode->m_OutputAttributes[ ai ]; + if ( GetOperatorOrdering( pAttrNode->m_InputDependentOperators, operators ) ) + { + cycle = true; + } + } + + operators.AddToTail( pOpNode->m_operator ); + pOpNode->m_state = TS_VISITED; // mark as done visiting + } + return cycle; +} + +//----------------------------------------------------------------------------- +// internal helper method - finds attrNode corresponding to pAttr +//----------------------------------------------------------------------------- +CAttributeNode *CDependencyGraph::FindAttrNode( CDmAttribute *pAttr ) +{ + VPROF_BUDGET( "CDependencyGraph::FindAttrNode", VPROF_BUDGETGROUP_TOOLS ); + + Assert( pAttr ); + + CAttributeNode search( pAttr ); + UtlHashHandle_t idx = m_attrNodes.Find( &search ); + if ( idx != m_attrNodes.InvalidHandle() ) + { + return m_attrNodes.Element( idx ); + } + + CAttributeNode *pAttrNode = 0; + { + VPROF( "CDependencyGraph::FindAttrNode_Alloc" ); + pAttrNode = g_AttrNodePool.Alloc(); + pAttrNode->m_attribute = pAttr; + } + { + VPROF( "CDependencyGraph::FindAttrNode_Alloc2" ); + m_attrNodes.Insert( pAttrNode ); + } + return pAttrNode; +} + +//----------------------------------------------------------------------------- +// temporary internal debugging function +//----------------------------------------------------------------------------- +void CDependencyGraph::DBG_PrintOperator( const char *pIndent, IDmeOperator *pOp ) +{ + CDmElement *pElement = dynamic_cast< CDmElement* >( pOp ); + Msg( "%s%s <%s> {\n", pIndent, pElement->GetName(), pElement->GetTypeString() ); +} diff --git a/datamodel/dependencygraph.h b/datamodel/dependencygraph.h new file mode 100644 index 0000000..1383c4a --- /dev/null +++ b/datamodel/dependencygraph.h @@ -0,0 +1,62 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= + +#ifndef DEPENDENCYGRAPH_H +#define DEPENDENCYGRAPH_H +#ifdef _WIN32 +#pragma once +#endif + +#include "tier1/utlvector.h" +#include "tier1/utlhash.h" + +//----------------------------------------------------------------------------- +// Forward declarations +//----------------------------------------------------------------------------- +class CDmAttribute; +class IDmeOperator; +struct COperatorNode; +class CAttributeNode; + + +//----------------------------------------------------------------------------- +// CDependencyGraph class - sorts operators based upon the input/output graph +//----------------------------------------------------------------------------- +class CDependencyGraph +{ +public: + CDependencyGraph(); + ~CDependencyGraph(); + + void Reset( const CUtlVector< IDmeOperator * > &operators ); + + // caches only the operators that need to be evaluated, sorted by dependencies + // returns true if a cycle found - in this case, an arbitrary link of the cycle will be ignored + bool CullAndSortOperators(); + + const CUtlVector< IDmeOperator* > &GetSortedOperators() const { return m_operators; } + +private: + static bool GetOperatorOrdering( CUtlVector< COperatorNode* > &pOpNodes, CUtlVector< IDmeOperator * > &operators ); + static void DBG_PrintOperator( const char *pIndent, IDmeOperator *pOp ); + + friend class CDmElementFramework; + + void Cleanup(); + void FindRoots(); + CAttributeNode *FindAttrNode( CDmAttribute *pAttr ); + + CUtlVector< COperatorNode* > m_opRoots; +// CUtlVector< COperatorNode* > m_opLeaves; + + CUtlVector< COperatorNode* > m_opNodes; + + CUtlHash< CAttributeNode* > m_attrNodes; + + CUtlVector< IDmeOperator* > m_operators; +}; + +#endif // DEPENDENCYGRAPH_H diff --git a/datamodel/dmattribute.cpp b/datamodel/dmattribute.cpp new file mode 100644 index 0000000..ba29592 --- /dev/null +++ b/datamodel/dmattribute.cpp @@ -0,0 +1,3265 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= + +#include "dmattributeinternal.h" +#include "datamodel/dmelement.h" +#include "dmelementdictionary.h" +#include "datamodel/idatamodel.h" +#include "datamodel.h" +#include "tier1/uniqueid.h" +#include "Color.h" +#include "mathlib/vector.h" +#include "tier1/utlstring.h" +#include "tier1/utlbuffer.h" +#include "tier1/KeyValues.h" +#include "tier1/mempool.h" +#include "mathlib/vmatrix.h" +#include "datamodel/dmattributevar.h" +#include <ctype.h> + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + + +//----------------------------------------------------------------------------- +// Tests equality +//----------------------------------------------------------------------------- +template< class T > +bool IsAttributeEqual( const T& src1, const T& src2 ) +{ + return src1 == src2; +} + +template< class T > +bool IsAttributeEqual( const CUtlVector<T> &src1, const CUtlVector<T> &src2 ) +{ + if ( src1.Count() != src2.Count() ) + return false; + + for ( int i=0; i < src1.Count(); i++ ) + { + if ( !( src1[i] == src2[i] ) ) + return false; + } + + return true; +} + + +//----------------------------------------------------------------------------- +// Typesafety check for element handles +//----------------------------------------------------------------------------- +static inline bool IsA( DmElementHandle_t hElement, UtlSymId_t type ) +{ + // treat NULL, deleted, and unloaded elements as being of any type - + // when set, undeleted or loaded, this should be checked again + CDmElement *pElement = g_pDataModel->GetElement( hElement ); + return pElement ? pElement->IsA( type ) : true; +} + + +//----------------------------------------------------------------------------- +// Element attributes are never directly unserialized +//----------------------------------------------------------------------------- +static bool Serialize( CUtlBuffer &buf, DmElementHandle_t src ) +{ + Assert( 0 ); + return false; +} + +static bool Unserialize( CUtlBuffer &buf, DmElementHandle_t &dest ) +{ + Assert( 0 ); + return false; +} + +static bool Serialize( CUtlBuffer &buf, const DmUnknownAttribute_t& src ) +{ + Assert( 0 ); + return false; +} + +static bool Unserialize( CUtlBuffer &buf, DmUnknownAttribute_t &dest ) +{ + Assert( 0 ); + return false; +} + +#include "tier1/utlbufferutil.h" + +//----------------------------------------------------------------------------- +// Internal interface for dealing with generic attribute operations +//----------------------------------------------------------------------------- +abstract_class IDmAttributeOp +{ +public: + virtual void* CreateAttributeData() = 0; + virtual void DestroyAttributeData( void *pData ) = 0; + virtual void SetDefaultValue( void *pData ) = 0; + virtual int DataSize() = 0; + virtual int ValueSize() = 0; + virtual bool SerializesOnMultipleLines() = 0; + virtual bool SkipUnserialize( CUtlBuffer& buf ) = 0; + virtual const char *AttributeTypeName() = 0; + + virtual void SetValue( CDmAttribute *pAttribute, DmAttributeType_t valueType, const void *pValue ) = 0; + virtual void SetMultiple( CDmAttribute *pAttribute, int i, int nCount, DmAttributeType_t valueType, const void *pValue ) = 0; + virtual void Set( CDmAttribute *pAttribute, int i, DmAttributeType_t valueType, const void *pValue ) = 0; + virtual void SetToDefaultValue( CDmAttribute *pAttribute ) = 0; + virtual bool Serialize( const CDmAttribute *pAttribute, CUtlBuffer &buf ) = 0; + virtual bool Unserialize( CDmAttribute *pAttribute, CUtlBuffer &buf ) = 0; + virtual bool SerializeElement( const CDmAttribute *pAttribute, int nElement, CUtlBuffer &buf ) = 0; + virtual bool UnserializeElement( CDmAttribute *pAttribute, CUtlBuffer &buf ) = 0; + virtual bool UnserializeElement( CDmAttribute *pAttribute, int nElement, CUtlBuffer &buf ) = 0; + virtual void OnUnserializationFinished( CDmAttribute *pAttribute ) = 0; +}; + + +//----------------------------------------------------------------------------- +// Global table of generic attribute operations looked up by type +//----------------------------------------------------------------------------- +static IDmAttributeOp* s_pAttrInfo[ AT_TYPE_COUNT ]; + + +//----------------------------------------------------------------------------- +// +// Implementation of IDmAttributeOp for single-valued attributes +// +//----------------------------------------------------------------------------- +template< class T > +class CDmAttributeOp : public IDmAttributeOp +{ +public: + virtual void* CreateAttributeData(); + virtual void DestroyAttributeData( void *pData ); + virtual void SetDefaultValue( void *pData ); + virtual int DataSize(); + virtual int ValueSize(); + virtual bool SerializesOnMultipleLines(); + virtual bool SkipUnserialize( CUtlBuffer& buf ); + virtual const char *AttributeTypeName(); + + virtual void SetValue( CDmAttribute *pAttribute, DmAttributeType_t valueType, const void *pValue ); + virtual void SetMultiple( CDmAttribute *pAttribute, int i, int nCount, DmAttributeType_t valueType, const void *pValue ); + virtual void Set( CDmAttribute *pAttribute, int i, DmAttributeType_t valueType, const void *pValue ); + virtual void SetToDefaultValue( CDmAttribute *pAttribute ); + virtual bool Serialize( const CDmAttribute *pData, CUtlBuffer &buf ); + virtual bool Unserialize( CDmAttribute *pAttribute, CUtlBuffer &buf ); + virtual bool SerializeElement( const CDmAttribute *pAttribute, int nElement, CUtlBuffer &buf ); + virtual bool UnserializeElement( CDmAttribute *pAttribute, CUtlBuffer &buf ); + virtual bool UnserializeElement( CDmAttribute *pAttribute, int nElement, CUtlBuffer &buf ); + virtual void OnUnserializationFinished( CDmAttribute *pAttribute ); +}; + + +//----------------------------------------------------------------------------- +// Memory pool useds for CDmAttribute data +// Over 8 bytes, use the small-block allocator (it aligns to 16 bytes) +//----------------------------------------------------------------------------- +CUtlMemoryPool g_DataAlloc4( sizeof( CDmAttribute ), 4, CUtlMemoryPool::GROW_SLOW, "4-byte data pool" ); +CUtlMemoryPool g_DataAlloc8( sizeof( CDmAttribute ), 8, CUtlMemoryPool::GROW_SLOW, "8-byte data pool" ); + +template< class T > void* NewData() +{ + return new typename CDmAttributeInfo< T >::StorageType_t; +} + +template< class T > void DeleteData( void *pData ) +{ + delete reinterpret_cast< typename CDmAttributeInfo< T >::StorageType_t * >( pData ); +} + +#define USE_SPECIAL_ALLOCATOR( _className, _allocator ) \ + template<> void* NewData< _className >() \ + { \ + void* pData = _allocator.Alloc( sizeof( CDmAttributeInfo< _className >::StorageType_t ) ); \ + return ::new( pData ) CDmAttributeInfo< _className >::StorageType_t(); \ + } \ + template<> void DeleteData<_className>( void *pData ) \ + { \ + typedef CDmAttributeInfo< _className >::StorageType_t D; \ + ( ( D * )pData )->~D(); \ + _allocator.Free( pData ); \ + } + +// make sure that the attribute data type sizes are what we think they are to choose the right allocator +struct CSizeTest +{ + CSizeTest() + { + // test internal value attribute sizes + COMPILE_TIME_ASSERT( sizeof( int ) == 4 ); + COMPILE_TIME_ASSERT( sizeof( float ) == 4 ); + COMPILE_TIME_ASSERT( sizeof( bool ) <= 4 ); + COMPILE_TIME_ASSERT( sizeof( Color ) == 4 ); + COMPILE_TIME_ASSERT( sizeof( DmElementAttribute_t ) <= 8 ); + COMPILE_TIME_ASSERT( sizeof( Vector2D ) == 8 ); + } +}; +static CSizeTest g_sizeTest; + +// turn memdbg off temporarily so we can get at placement new +#include "tier0/memdbgoff.h" + +USE_SPECIAL_ALLOCATOR( bool, g_DataAlloc4 ) +USE_SPECIAL_ALLOCATOR( int, g_DataAlloc4 ) +USE_SPECIAL_ALLOCATOR( float, g_DataAlloc4 ) +USE_SPECIAL_ALLOCATOR( DmElementHandle_t, g_DataAlloc4 ) +USE_SPECIAL_ALLOCATOR( Color, g_DataAlloc4 ) +USE_SPECIAL_ALLOCATOR( Vector2D, g_DataAlloc8 ) + +#include "tier0/memdbgon.h" + + +//----------------------------------------------------------------------------- +// Create, destroy attribute data +//----------------------------------------------------------------------------- +template< class T > +void* CDmAttributeOp<T>::CreateAttributeData() +{ + void *pData = NewData< T >(); + CDmAttributeInfo< T >::SetDefaultValue( *reinterpret_cast<T*>( pData ) ); + return pData; +} + +template<> void* CDmAttributeOp< DmUnknownAttribute_t >::CreateAttributeData() +{ + // Fail if someone tries to create an AT_UNKNOWN attribute + Assert(0); + return NULL; +} + +template< class T > +void CDmAttributeOp<T>::DestroyAttributeData( void *pData ) +{ + DeleteData< T >( pData ); +} + + +//----------------------------------------------------------------------------- +// Sets the data to a default value, no undo (used for construction) +//----------------------------------------------------------------------------- +template< class T > +void CDmAttributeOp<T>::SetDefaultValue( void *pData ) +{ + CDmAttributeInfo< T >::SetDefaultValue( *reinterpret_cast<T*>( pData ) ); +} + + +//----------------------------------------------------------------------------- +// Attribute type name, data size, value size +//----------------------------------------------------------------------------- +template< class T > +const char *CDmAttributeOp<T>::AttributeTypeName() +{ + return CDmAttributeInfo<T>::AttributeTypeName(); +} + +template< class T > +int CDmAttributeOp<T>::DataSize() +{ + return sizeof( typename CDmAttributeInfo< T >::StorageType_t ); +} + +template< class T > +int CDmAttributeOp<T>::ValueSize() +{ + return sizeof( T ); +} + + +//----------------------------------------------------------------------------- +// Value-setting methods +//----------------------------------------------------------------------------- +template< class T > +void CDmAttributeOp<T>::SetToDefaultValue( CDmAttribute *pAttribute ) +{ + T newValue; + CDmAttributeInfo< T >::SetDefaultValue( newValue ); + pAttribute->SetValue( newValue ); +} + +template< class T > +void CDmAttributeOp<T>::SetMultiple( CDmAttribute *pAttribute, int i, int nCount, DmAttributeType_t valueType, const void *pValue ) +{ + Assert(0); +} + +template< class T > +void CDmAttributeOp<T>::Set( CDmAttribute *pAttribute, int i, DmAttributeType_t valueType, const void *pValue ) +{ + Assert(0); +} + +template< class T > +void CDmAttributeOp<T>::SetValue( CDmAttribute *pAttribute, DmAttributeType_t valueType, const void *pValue ) +{ + Assert( pAttribute->GetType() == valueType ); + if ( pAttribute->GetType() == valueType ) + { + pAttribute->SetValue( *reinterpret_cast< const T* >( pValue ) ); + } +} + +#define SET_VALUE_TYPE( _srcType ) \ + case CDmAttributeInfo< _srcType >::ATTRIBUTE_TYPE: \ + pAttribute->SetValue( *reinterpret_cast< const _srcType* >( pValue ) ); \ + break; + +template<> +void CDmAttributeOp<int>::SetValue( CDmAttribute *pAttribute, DmAttributeType_t valueType, const void *pValue ) +{ + switch( valueType ) + { + SET_VALUE_TYPE( int ); + SET_VALUE_TYPE( float ); + SET_VALUE_TYPE( bool ); + + default: + Assert(0); + break; + } +} + +template<> +void CDmAttributeOp<float>::SetValue( CDmAttribute *pAttribute, DmAttributeType_t valueType, const void *pValue ) +{ + switch( valueType ) + { + SET_VALUE_TYPE( int ); + SET_VALUE_TYPE( float ); + SET_VALUE_TYPE( bool ); + + default: + Assert(0); + break; + } +} + +template<> +void CDmAttributeOp<bool>::SetValue( CDmAttribute *pAttribute, DmAttributeType_t valueType, const void *pValue ) +{ + switch( valueType ) + { + SET_VALUE_TYPE( int ); + SET_VALUE_TYPE( float ); + SET_VALUE_TYPE( bool ); + + default: + Assert(0); + break; + } +} + + +template<> +void CDmAttributeOp<QAngle>::SetValue( CDmAttribute *pAttribute, DmAttributeType_t valueType, const void *pValue ) +{ + switch( valueType ) + { + SET_VALUE_TYPE( QAngle ); + SET_VALUE_TYPE( Quaternion ); + + default: + Assert(0); + break; + } +} + +template<> +void CDmAttributeOp<Quaternion>::SetValue( CDmAttribute *pAttribute, DmAttributeType_t valueType, const void *pValue ) +{ + switch( valueType ) + { + SET_VALUE_TYPE( QAngle ); + SET_VALUE_TYPE( Quaternion ); + + default: + Assert(0); + break; + } +} + + +//----------------------------------------------------------------------------- +// Methods related to serialization +//----------------------------------------------------------------------------- +template< class T > +bool CDmAttributeOp<T>::SerializesOnMultipleLines() +{ + return ::SerializesOnMultipleLines< T >(); +} + +template< class T > +bool CDmAttributeOp<T>::SkipUnserialize( CUtlBuffer& buf ) +{ + T dummy; + ::Unserialize( buf, dummy ); + return buf.IsValid(); +} + +template< class T > +bool CDmAttributeOp<T>::Serialize( const CDmAttribute *pAttribute, CUtlBuffer &buf ) +{ + // NOTE: For this to work, the class must have a function defined of type + // bool Serialize( CUtlBuffer &buf, T &src ) + return ::Serialize( buf, pAttribute->GetValue<T>() ); +} + +template< class T > +bool CDmAttributeOp<T>::Unserialize( CDmAttribute *pAttribute, CUtlBuffer &buf ) +{ + // NOTE: For this to work, the class must have a function defined of type + // bool Unserialize( CUtlBuffer &buf, T &src ) + + T tempVal; + bool bRet = ::Unserialize( buf, tempVal ); + + // Don't need undo hook since this goes through SetValue route + pAttribute->SetValue( tempVal ); + + return bRet; +} + +template< class T > +bool CDmAttributeOp<T>::SerializeElement( const CDmAttribute *pData, int nElement, CUtlBuffer &buf ) +{ + Assert( 0 ); + return false; +} + +template< class T > +bool CDmAttributeOp<T>::UnserializeElement( CDmAttribute *pData, CUtlBuffer &buf ) +{ + Assert( 0 ); + return false; +} + +template< class T > +bool CDmAttributeOp<T>::UnserializeElement( CDmAttribute *pData, int nElement, CUtlBuffer &buf ) +{ + Assert( 0 ); + return false; +} + +template< class T > +void CDmAttributeOp<T>::OnUnserializationFinished( CDmAttribute *pAttribute ) +{ + CDmAttributeAccessor::OnChanged( pAttribute, false, true ); +} + + + +//----------------------------------------------------------------------------- +// +// Implementation of IDmAttributeOp for array attributes +// +//----------------------------------------------------------------------------- +template< class T > +class CDmArrayAttributeOp : public CDmAttributeOp< CUtlVector< T > > +{ + typedef typename CDmAttributeInfo< CUtlVector< T > >::StorageType_t D; + +public: + // Inherited from IDmAttributeOp + virtual void SetValue( CDmAttribute *pAttribute, DmAttributeType_t valueType, const void *pValue ); + virtual void SetMultiple( CDmAttribute *pAttribute, int i, int nCount, DmAttributeType_t valueType, const void *pValue ); + virtual void Set( CDmAttribute *pAttribute, int i, DmAttributeType_t valueType, const void *pValue ); + virtual bool Unserialize( CDmAttribute *pAttribute, CUtlBuffer &buf ); + virtual bool SerializeElement( const CDmAttribute *pData, int nElement, CUtlBuffer &buf ); + virtual bool UnserializeElement( CDmAttribute *pData, CUtlBuffer &buf ); + virtual bool UnserializeElement( CDmAttribute *pData, int nElement, CUtlBuffer &buf ); + virtual void OnUnserializationFinished( CDmAttribute *pAttribute ); + + // Other methods used by CDmaArrayBase + CDmArrayAttributeOp() : m_pAttribute( NULL ), m_pData( NULL ) {} + CDmArrayAttributeOp( CDmAttribute *pAttribute ) : m_pAttribute( pAttribute ), m_pData( (D*)m_pAttribute->GetAttributeData() ) {} + + // Count + int Count() const; + + // Insertion + int AddToTail( const T& src ); + int InsertBefore( int elem, const T& src ); + int InsertMultipleBefore( int elem, int num ); + + // Removal + void FastRemove( int elem ); + void Remove( int elem ); + void RemoveAll(); + void RemoveMultiple( int elem, int num ); + void Purge(); + + // Element Modification + void Set( int i, const T& value ); + void SetMultiple( int i, int nCount, const T* pValue ); + void Swap( int i, int j ); + + // Copy related methods + void CopyArray( const T *pArray, int size ); + void SwapArray( CUtlVector< T >& src ); // Performs a pointer swap + + void OnAttributeArrayElementAdded( int nFirstElem, int nLastElem, bool bUpdateElementReferences = true ); + void OnAttributeArrayElementRemoved( int nFirstElem, int nLastElem ); + +private: + bool ShouldInsertElement( const T& src ); + bool ShouldInsert( const T& src ); + void PerformCopyArray( const T *pArray, int nCount ); + D& Data() { return *m_pData; } + const D& Data() const { return *m_pData; } + + CDmAttribute *m_pAttribute; + D* m_pData; +}; + + +//----------------------------------------------------------------------------- +// +// Undo-related classes +// +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- +// Undo attribute name change +//----------------------------------------------------------------------------- +class CUndoAttributeRenameElement : public CUndoElement +{ + typedef CUndoElement BaseClass; + +public: + CUndoAttributeRenameElement( CDmAttribute *pAttribute, const char *newName ) + : BaseClass( "CUndoAttributeRenameElement" ) + { + Assert( pAttribute->GetOwner() && pAttribute->GetOwner()->GetFileId() != DMFILEID_INVALID ); + m_hOwner = pAttribute->GetOwner()->GetHandle(); + m_symAttributeOld = pAttribute->GetName(); + m_symAttributeNew = newName; + } + + virtual void Undo() + { + CDmElement *pOwner = GetOwner(); + if ( pOwner ) + { + pOwner->RenameAttribute( m_symAttributeNew.String(), m_symAttributeOld.String() ); + } + } + + virtual void Redo() + { + CDmElement *pOwner = GetOwner(); + if ( pOwner ) + { + pOwner->RenameAttribute( m_symAttributeOld.String(), m_symAttributeNew.String() ); + } + } + + virtual const char *GetDesc() + { + static char buf[ 128 ]; + + const char *base = BaseClass::GetDesc(); + Q_snprintf( buf, sizeof( buf ), "%s (%s -> %s)", base, m_symAttributeOld.String(), m_symAttributeNew.String() ); + return buf; + } + +private: + CDmElement *GetOwner() + { + return g_pDataModel->GetElement( m_hOwner ); + } + + CUtlSymbol m_symAttributeOld; + CUtlSymbol m_symAttributeNew; + DmElementHandle_t m_hOwner; +}; + +//----------------------------------------------------------------------------- +// Undo single-valued attribute value changed +//----------------------------------------------------------------------------- +template< class T > +class CUndoAttributeSetValueElement : public CUndoElement +{ + typedef CUndoElement BaseClass; +public: + CUndoAttributeSetValueElement( CDmAttribute *pAttribute, const T &newValue ) + : BaseClass( "CUndoAttributeSetValueElement" ) + { + Assert( pAttribute->GetOwner() && pAttribute->GetOwner()->GetFileId() != DMFILEID_INVALID ); + m_hOwner = pAttribute->GetOwner()->GetHandle(); + m_OldValue = pAttribute->GetValue<T>(); + m_Value = newValue; + m_symAttribute = pAttribute->GetNameSymbol( ); + } + + CDmElement *GetOwner() + { + return g_pDataModel->GetElement( m_hOwner ); + } + + virtual void Undo() + { + CDmAttribute *pAttribute = GetAttribute(); + if ( pAttribute && !pAttribute->IsFlagSet( FATTRIB_READONLY ) ) + { + pAttribute->SetValue<T>( m_OldValue ); + } + } + virtual void Redo() + { + CDmAttribute *pAttribute = GetAttribute(); + if ( pAttribute && !pAttribute->IsFlagSet( FATTRIB_READONLY ) ) + { + pAttribute->SetValue<T>( m_Value ); + } + } + + virtual const char *GetDesc() + { + static char buf[ 128 ]; + + const char *base = BaseClass::GetDesc(); + CDmAttribute *pAtt = GetAttribute(); + CUtlBuffer serialized( 0, 0, CUtlBuffer::TEXT_BUFFER ); + if ( pAtt && pAtt->GetType() != AT_ELEMENT ) + { + ::Serialize( serialized, m_Value ); + } + Q_snprintf( buf, sizeof( buf ), "%s(%s) = %s", base, g_pDataModel->GetString( m_symAttribute ), serialized.Base() ? (const char*)serialized.Base() : "\"\"" ); + return buf; + } + +private: + CDmAttribute *GetAttribute() + { + CDmElement *pOwner = GetOwner(); + if ( pOwner ) + { + const char *pAttributeName = g_pDataModel->GetString( m_symAttribute ); + return pOwner->GetAttribute( pAttributeName ); + } + return NULL; + } + + typedef T StorageType_t; + + CUtlSymbol m_symAttribute; + DmElementHandle_t m_hOwner; + StorageType_t m_OldValue; + StorageType_t m_Value; +}; + + + +//----------------------------------------------------------------------------- +// Base undo for array attributes +//----------------------------------------------------------------------------- +template< class T > +class CUndoAttributeArrayBase : public CUndoElement +{ + typedef CUndoElement BaseClass; + +public: + CUndoAttributeArrayBase( CDmAttribute *pAttribute, const char *pUndoName ) : BaseClass( pUndoName ) + { + m_hOwner = pAttribute->GetOwner()->GetHandle(); + m_symAttribute = pAttribute->GetNameSymbol( ); + } + +protected: + typedef typename CDmAttributeUndoStorageType< T >::UndoStorageType StorageType_t; + + CDmElement *GetOwner() + { + return g_pDataModel->GetElement( m_hOwner ); + } + + const char *GetAttributeName() + { + return g_pDataModel->GetString( m_symAttribute ); + } + + CDmAttribute *GetAttribute() + { + const char *pAttributeName = GetAttributeName(); + CDmElement *pOwner = GetOwner(); + if ( pOwner ) + return pOwner->GetAttribute( pAttributeName ); + Assert( 0 ); + return NULL; + } + +private: + CUtlSymbol m_symAttribute; + DmElementHandle_t m_hOwner; +}; + + +//----------------------------------------------------------------------------- +// Undo for setting a single element +//----------------------------------------------------------------------------- +template< class T > +class CUndoArrayAttributeSetValueElement : public CUndoAttributeArrayBase<T> +{ + typedef CUndoAttributeArrayBase<T> BaseClass; + +public: + CUndoArrayAttributeSetValueElement( CDmAttribute *pAttribute, int slot, const T &newValue ) : + BaseClass( pAttribute, "CUndoArrayAttributeSetValueElement" ), + m_nSlot( slot ) + { + Assert( pAttribute->GetOwner() && pAttribute->GetOwner()->GetFileId() != DMFILEID_INVALID ); + + CDmrArray<T> array( pAttribute ); + m_OldValue = array[ slot ]; + m_Value = newValue; + } + + virtual void Undo() + { + CDmrArray<T> array( GetAttribute() ); + if ( array.IsValid() ) + { + array.Set( m_nSlot, m_OldValue ); + } + } + + virtual void Redo() + { + CDmrArray<T> array( GetAttribute() ); + if ( array.IsValid() ) + { + array.Set( m_nSlot, m_Value ); + } + } + +private: + int m_nSlot; + typename CUndoAttributeArrayBase<T>::StorageType_t m_OldValue; + typename CUndoAttributeArrayBase<T>::StorageType_t m_Value; +}; + + +//----------------------------------------------------------------------------- +// Undo for setting a multiple elements +//----------------------------------------------------------------------------- +template< class T > +class CUndoArrayAttributeSetMultipleValueElement : public CUndoAttributeArrayBase<T> +{ + typedef CUndoAttributeArrayBase<T> BaseClass; + +public: + CUndoArrayAttributeSetMultipleValueElement( CDmAttribute *pAttribute, int nSlot, int nCount, const T *pNewValue ) : + BaseClass( pAttribute, "CUndoArrayAttributeSetMultipleValueElement" ), + m_nSlot( nSlot ), m_nCount( nCount ) + { + Assert( pAttribute->GetOwner() && pAttribute->GetOwner()->GetFileId() != DMFILEID_INVALID ); + m_pOldValue = new typename CUndoAttributeArrayBase<T>::StorageType_t[nCount]; + m_pValue = new typename CUndoAttributeArrayBase<T>::StorageType_t[nCount]; + + CDmrArray<T> array( pAttribute ); + for ( int i = 0; i < nCount; ++i ) + { + m_pOldValue[i] = array[ nSlot+i ]; + m_pValue[i] = pNewValue[ i ]; + } + } + + ~CUndoArrayAttributeSetMultipleValueElement() + { + // this is a hack necessitated by MSVC's lack of partially specialized member template support + // (ie otherwise I'd just create a CUndoArrayAttributeSetMultipleValueElement< DmElementHandle_t,BaseClass> version with this code) + // anyways, the casting hackiness only happens when the value is actually a DmElementHandle_t, so it's completely safe + if ( CDmAttributeInfo< T >::AttributeType() == AT_ELEMENT ) + { + DmElementHandle_t value = DMELEMENT_HANDLE_INVALID; + for ( int i = 0; i < m_nCount; ++i ) + { + m_pOldValue[ i ] = m_pValue[ i ] = *( T* )&value; + } + } + + delete[] m_pOldValue; + delete[] m_pValue; + } + + virtual void Undo() + { + CDmrArray<T> array( GetAttribute() ); + if ( array.IsValid() ) + { + for ( int i = 0; i < m_nCount; ++i ) + { + array.Set( m_nSlot+i, m_pOldValue[i] ); + } + } + } + + virtual void Redo() + { + CDmrArray<T> array( GetAttribute() ); + if ( array.IsValid() ) + { + for ( int i = 0; i < m_nCount; ++i ) + { + array.Set( m_nSlot+i, m_pValue[i] ); + } + } + } + +private: + int m_nSlot; + int m_nCount; + typename CUndoAttributeArrayBase<T>::StorageType_t *m_pOldValue; + typename CUndoAttributeArrayBase<T>::StorageType_t *m_pValue; +}; + + +//----------------------------------------------------------------------------- +// +// Implementation Undo for CDmAttributeTyped +// +//----------------------------------------------------------------------------- +template< class T > +class CUndoAttributeArrayInsertBefore : public CUndoAttributeArrayBase<T> +{ + typedef CUndoAttributeArrayBase<T> BaseClass; +public: + CUndoAttributeArrayInsertBefore( CDmAttribute *pAttribute, int slot, int count = 1 ) : + BaseClass( pAttribute, "CUndoAttributeArrayInsertBefore" ), + m_nIndex( slot ), m_nCount( count ) + { + Assert( pAttribute->GetOwner() && pAttribute->GetOwner()->GetFileId() != DMFILEID_INVALID ); + } + + virtual void Undo() + { + CDmrArray<T> array( GetAttribute() ); + if ( array.IsValid() ) + { + array.RemoveMultiple( m_nIndex, m_nCount ); + } + } + + virtual void Redo() + { + CDmrArray<T> array( GetAttribute() ); + if ( array.IsValid() ) + { + T defaultVal; + CDmAttributeInfo<T>::SetDefaultValue( defaultVal ); + + array.InsertMultipleBefore( m_nIndex, m_nCount ); + for( int i = 0; i < m_nCount; ++i ) + { + array.Set( m_nIndex + i, defaultVal ); + } + } + } + +private: + int m_nIndex; + int m_nCount; +}; + + +//----------------------------------------------------------------------------- +// +// Implementation Undo for inserting a copy +// +//----------------------------------------------------------------------------- +template< class T > +class CUndoAttributeArrayInsertCopyBefore : public CUndoAttributeArrayBase<T> +{ + typedef CUndoAttributeArrayBase<T> BaseClass; + +public: + CUndoAttributeArrayInsertCopyBefore( CDmAttribute *pAttribute, int slot, const T& newValue ) : + BaseClass( pAttribute, "CUndoAttributeArrayInsertCopyBefore" ), + m_nIndex( slot ), + m_newValue( newValue ) + { + Assert( pAttribute->GetOwner() && pAttribute->GetOwner()->GetFileId() != DMFILEID_INVALID ); + } + + virtual void Undo() + { + CDmrArray<T> array( GetAttribute() ); + if ( array.IsValid() ) + { + array.Remove( m_nIndex ); + } + } + + virtual void Redo() + { + CDmrArray<T> array( GetAttribute() ); + if ( array.IsValid() ) + { + array.InsertBefore( m_nIndex, m_newValue ); + } + } + +private: + int m_nIndex; + typename CUndoAttributeArrayBase<T>::StorageType_t m_newValue; +}; + + +//----------------------------------------------------------------------------- +// +// Implementation Undo for remove +// +//----------------------------------------------------------------------------- +template< class T > +class CUndoAttributeArrayRemoveElement : public CUndoAttributeArrayBase<T> +{ + typedef CUndoAttributeArrayBase<T> BaseClass; + +public: + CUndoAttributeArrayRemoveElement( CDmAttribute *pAttribute, bool fastRemove, int elem, int count ) : + BaseClass( pAttribute, "CUndoAttributeArrayRemoveElement" ), + m_bFastRemove( fastRemove ), m_nIndex( elem ), m_nCount( count ) + { + Assert( pAttribute->GetOwner() && pAttribute->GetOwner()->GetFileId() != DMFILEID_INVALID ); + Assert( m_nCount >= 1 ); + // If it's fastremove, count must == 1 + Assert( !m_bFastRemove || m_nCount == 1 ); + CDmrArray< T > array( pAttribute ); + Assert( array.IsValid() ); + for ( int i = 0 ; i < m_nCount; ++i ) + { + m_OldValues.AddToTail( array[ elem + i ] ); + } + } + + ~CUndoAttributeArrayRemoveElement() + { + // this is a hack necessitated by MSVC's lack of partially specialized member template support + // (ie otherwise I'd just create a CUndoArrayAttributeSetMultipleValueElement< DmElementHandle_t,BaseClass> version with this code) + // anyways, the casting hackiness only happens when the value is actually a DmElementHandle_t, so it's completely safe + if ( CDmAttributeInfo< T >::AttributeType() == AT_ELEMENT ) + { + DmElementHandle_t value = DMELEMENT_HANDLE_INVALID; + for ( int i = 0; i < m_nCount; ++i ) + { + m_OldValues[ i ] = *( T* )&value; + } + m_OldValues.RemoveAll(); + } + } + + virtual void Undo() + { + CDmrArray<T> array( GetAttribute() ); + if ( array.IsValid() ) + { + if ( m_bFastRemove ) + { + Assert( m_nCount == 1 ); + Assert( m_OldValues.Count() == 1 ); + + if ( array.Count() > m_nIndex ) + { + // Get value at previous index (it was moved down from the "end" before + T m_EndValue = array.Get( m_nIndex ); + + // Restore previous value + array.Set( m_nIndex, m_OldValues[ 0 ] ); + + // Put old value back to end of array + array.AddToTail( m_EndValue ); + } + else + { + Assert( array.Count() == m_nIndex ); + array.AddToTail( m_OldValues[ 0 ] ); + } + } + else + { + int insertPos = m_nIndex; + for ( int i = 0; i < m_nCount; ++i ) + { + array.InsertBefore( insertPos++, m_OldValues[ i ] ); + } + } + } + } + + virtual void Redo() + { + CDmrArray<T> array( GetAttribute() ); + if ( array.IsValid() ) + { + if ( m_bFastRemove ) + { + Assert( m_nCount == 1 ); + Assert( m_OldValues.Count() == 1 ); + + array.FastRemove( m_nIndex ); + } + else + { + array.RemoveMultiple( m_nIndex, m_nCount ); + } + } + } + + virtual const char *GetDesc() + { + static char buf[ 128 ]; + + const char *base = BaseClass::GetDesc(); + Q_snprintf( buf, sizeof( buf ), "%s (%s) = remove( pos %i, count %i )", base, GetAttributeName(), m_nIndex, m_nCount ); + return buf; + } + +private: + bool m_bFastRemove; + int m_nIndex; + int m_nCount; + CUtlVector< typename CUndoAttributeArrayBase<T>::StorageType_t > m_OldValues; +}; + + +template< class T > +class CUndoAttributeArrayCopyAllElement : public CUndoAttributeArrayBase<T> +{ + typedef CUndoAttributeArrayBase<T> BaseClass; +public: + CUndoAttributeArrayCopyAllElement( CDmAttribute *pAttribute, const T *pNewValues, int nNewSize, bool purgeOnRemove = false ) + : BaseClass( pAttribute, "CUndoAttributeArrayCopyAllElement" ), + m_bPurge( purgeOnRemove ) + { + Assert( pAttribute->GetOwner() && pAttribute->GetOwner()->GetFileId() != DMFILEID_INVALID ); + CDmrArray< T > att( pAttribute ); + Assert( att.IsValid() ); + + if ( pNewValues != NULL && nNewSize > 0 ) + { + m_pNewValues = new typename CUndoAttributeArrayBase<T>::StorageType_t[ nNewSize ]; + for ( int i = 0; i < nNewSize; ++i ) + { + m_pNewValues[ i ] = pNewValues[ i ]; + } + m_nNewSize = nNewSize; + } + else + { + m_pNewValues = NULL; + m_nNewSize = 0; + } + + int nOldSize = att.Count(); + const T *pOldValues = att.Base(); + if ( pOldValues != NULL && nOldSize > 0 ) + { + m_pOldValues = new typename CUndoAttributeArrayBase<T>::StorageType_t[ nOldSize ]; + for ( int i = 0; i < nOldSize; ++i ) + { + m_pOldValues[ i ] = pOldValues[ i ]; + } + m_nOldSize = nOldSize; + } + else + { + m_pOldValues = NULL; + m_nOldSize = 0; + } + } + + ~CUndoAttributeArrayCopyAllElement() + { + // this is a hack necessitated by MSVC's lack of partially specialized member template support + // (ie otherwise I'd just create a CUndoArrayAttributeSetMultipleValueElement< DmElementHandle_t,BaseClass> version with this code) + // anyways, the casting hackiness only happens when the value is actually a DmElementHandle_t, so it's completely safe + if ( CDmAttributeInfo< T >::AttributeType() == AT_ELEMENT ) + { + DmElementHandle_t value = DMELEMENT_HANDLE_INVALID; + for ( int i = 0; i < m_nOldSize; ++i ) + { + m_pOldValues[ i ] = *( T* )&value; + } + for ( int i = 0; i < m_nNewSize; ++i ) + { + m_pNewValues[ i ] = *( T* )&value; + } + } + + delete[] m_pOldValues; + delete[] m_pNewValues; + } + + virtual void Undo() + { + CDmrArray<T> array( GetAttribute() ); + if ( array.IsValid() ) + { + array.RemoveAll(); + for ( int i = 0; i < m_nOldSize; ++i ) + { + array.AddToTail( m_pOldValues[ i ] ); + } + } + } + + virtual void Redo() + { + CDmrArray<T> array( GetAttribute() ); + if ( array.IsValid() ) + { + array.RemoveAll(); + for ( int i = 0; i < m_nNewSize; ++i ) + { + array.AddToTail( m_pNewValues[ i ] ); + } + + if ( m_bPurge ) + { + Assert( array.Count() == 0 ); + array.Purge(); + } + } + } + +private: + typename CUndoAttributeArrayBase<T>::StorageType_t *m_pOldValues; + int m_nOldSize; + typename CUndoAttributeArrayBase<T>::StorageType_t *m_pNewValues; + int m_nNewSize; + bool m_bPurge; +}; + + + +//----------------------------------------------------------------------------- +// CDmArrayAttributeOp implementation. +//----------------------------------------------------------------------------- + + +//----------------------------------------------------------------------------- +// Callbacks when elements are added + removed +//----------------------------------------------------------------------------- +template< class T > +void CDmArrayAttributeOp<T>::OnAttributeArrayElementAdded( int nFirstElem, int nLastElem, bool bUpdateElementReferences ) +{ + CDmElement *pOwner = m_pAttribute->GetOwner(); + if ( m_pAttribute->IsFlagSet( FATTRIB_HAS_ARRAY_CALLBACK ) && !CDmeElementAccessor::IsBeingUnserialized( pOwner ) ) + { + pOwner->OnAttributeArrayElementAdded( m_pAttribute, nFirstElem, nLastElem ); + } +} + +template< > inline void CDmArrayAttributeOp< DmElementHandle_t >::OnAttributeArrayElementAdded( int nFirstElem, int nLastElem, bool bUpdateElementReferences ) +{ + CDmElement *pOwner = m_pAttribute->GetOwner(); + if ( m_pAttribute->IsFlagSet( FATTRIB_HAS_ARRAY_CALLBACK ) && !CDmeElementAccessor::IsBeingUnserialized( pOwner ) ) + { + pOwner->OnAttributeArrayElementAdded( m_pAttribute, nFirstElem, nLastElem ); + } + + if ( bUpdateElementReferences ) + { + for ( int i = nFirstElem; i <= nLastElem; ++i ) + { + g_pDataModelImp->OnElementReferenceAdded( Data()[ i ], m_pAttribute ); + } + } +} + +template< class T > +void CDmArrayAttributeOp<T>::OnAttributeArrayElementRemoved( int nFirstElem, int nLastElem ) +{ + CDmElement *pOwner = m_pAttribute->GetOwner(); + if ( m_pAttribute->IsFlagSet( FATTRIB_HAS_ARRAY_CALLBACK ) && !CDmeElementAccessor::IsBeingUnserialized( pOwner ) ) + { + pOwner->OnAttributeArrayElementRemoved( m_pAttribute, nFirstElem, nLastElem ); + } +} + +template< > void CDmArrayAttributeOp< DmElementHandle_t >::OnAttributeArrayElementRemoved( int nFirstElem, int nLastElem ) +{ + CDmElement *pOwner = m_pAttribute->GetOwner(); + if ( m_pAttribute->IsFlagSet( FATTRIB_HAS_ARRAY_CALLBACK ) && !CDmeElementAccessor::IsBeingUnserialized( pOwner ) ) + { + pOwner->OnAttributeArrayElementRemoved( m_pAttribute, nFirstElem, nLastElem ); + } + + for ( int i = nFirstElem; i <= nLastElem; ++i ) + { + g_pDataModelImp->OnElementReferenceRemoved( Data()[ i ], m_pAttribute ); + } +} + + +//----------------------------------------------------------------------------- +// Count +//----------------------------------------------------------------------------- +template< class T > +int CDmArrayAttributeOp<T>::Count() const +{ + return Data().Count(); +} + + +//----------------------------------------------------------------------------- +// Should we insert this element into the list? +//----------------------------------------------------------------------------- +template< class T > +inline bool CDmArrayAttributeOp<T>::ShouldInsertElement( const T& src ) +{ + return true; +} + +template<> inline bool CDmArrayAttributeOp<DmElementHandle_t>::ShouldInsertElement( const DmElementHandle_t& src ) +{ + // For element, we need to check that the type matches + if ( !IsA( src, Data().m_ElementType ) ) + return false; + + if ( m_pAttribute->IsFlagSet( FATTRIB_NODUPLICATES ) ) + { + // See if value exists + int idx = Data().Find( src ); + if ( idx != Data().InvalidIndex() ) + return false; + } + + return true; +} + +template< class T > +inline bool CDmArrayAttributeOp<T>::ShouldInsert( const T& src ) +{ + if ( !ShouldInsertElement( src ) ) + return false; + + return m_pAttribute->MarkDirty(); +} + + +//----------------------------------------------------------------------------- +// Insert Before +//----------------------------------------------------------------------------- +template< class T > +int CDmArrayAttributeOp<T>::InsertBefore( int elem, const T& src ) +{ + if ( !ShouldInsert( src ) ) + return Data().InvalidIndex(); + + if ( g_pDataModel->UndoEnabledForElement( m_pAttribute->GetOwner() ) ) + { + CUndoAttributeArrayInsertCopyBefore<T> *pUndo = new CUndoAttributeArrayInsertCopyBefore<T>( m_pAttribute, elem, src ); + g_pDataModel->AddUndoElement( pUndo ); + } + + m_pAttribute->PreChanged(); + int nIndex = Data().InsertBefore( elem, src ); + OnAttributeArrayElementAdded( nIndex, nIndex ); + m_pAttribute->OnChanged( true ); + return nIndex; +} + +template< class T > +inline int CDmArrayAttributeOp<T>::AddToTail( const T& src ) +{ + return InsertBefore( Data().Count(), src ); +} + + +//----------------------------------------------------------------------------- +// Insert Multiple Before +//----------------------------------------------------------------------------- +template< class T > +int CDmArrayAttributeOp<T>::InsertMultipleBefore( int elem, int num ) +{ + if ( !m_pAttribute->MarkDirty() ) + return Data().InvalidIndex(); + + // UNDO HOOK + if ( g_pDataModel->UndoEnabledForElement( m_pAttribute->GetOwner() ) ) + { + CUndoAttributeArrayInsertBefore<T> *pUndo = new CUndoAttributeArrayInsertBefore<T>( m_pAttribute, elem, num ); + g_pDataModel->AddUndoElement( pUndo ); + } + + m_pAttribute->PreChanged(); + int index = Data().InsertMultipleBefore( elem, num ); + for ( int i = 0; i < num; ++i ) + { + CDmAttributeInfo<T>::SetDefaultValue( Data()[ index + i ] ); + } + OnAttributeArrayElementAdded( index, index + num - 1 ); + m_pAttribute->OnChanged( true ); + return index; +} + + +//----------------------------------------------------------------------------- +// Removal +//----------------------------------------------------------------------------- +template< class T > +void CDmArrayAttributeOp<T>::FastRemove( int elem ) +{ + if ( !m_pAttribute->MarkDirty() ) + return; + + // UNDO HOOK + if ( g_pDataModel->UndoEnabledForElement( m_pAttribute->GetOwner() ) ) + { + CUndoAttributeArrayRemoveElement<T> *pUndo = new CUndoAttributeArrayRemoveElement<T>( m_pAttribute, true, elem, 1 ); + g_pDataModel->AddUndoElement( pUndo ); + } + + m_pAttribute->PreChanged(); + OnAttributeArrayElementRemoved( elem, elem ); + Data().FastRemove( elem ); + m_pAttribute->OnChanged( true ); +} + +template< class T > +void CDmArrayAttributeOp<T>::Remove( int elem ) +{ + if ( !Data().IsValidIndex( elem ) ) + return; + + if ( !m_pAttribute->MarkDirty() ) + return; + + // UNDO HOOK + if ( g_pDataModel->UndoEnabledForElement( m_pAttribute->GetOwner() ) ) + { + CUndoAttributeArrayRemoveElement<T> *pUndo = new CUndoAttributeArrayRemoveElement<T>( m_pAttribute, false, elem, 1 ); + g_pDataModel->AddUndoElement( pUndo ); + } + + m_pAttribute->PreChanged(); + OnAttributeArrayElementRemoved( elem, elem ); + Data().Remove( elem ); + m_pAttribute->OnChanged( true ); +} + +template< class T > +void CDmArrayAttributeOp<T>::RemoveAll() +{ + if ( !m_pAttribute->MarkDirty() ) + return; + + // UNDO HOOK + if ( g_pDataModel->UndoEnabledForElement( m_pAttribute->GetOwner() ) ) + { + CUndoAttributeArrayCopyAllElement<T> *pUndo = new CUndoAttributeArrayCopyAllElement<T>( m_pAttribute, NULL, 0 ); + g_pDataModel->AddUndoElement( pUndo ); + } + + m_pAttribute->PreChanged(); + OnAttributeArrayElementRemoved( 0, Data().Count() - 1 ); + Data().RemoveAll(); + m_pAttribute->OnChanged( true ); +} + +template< class T > +void CDmArrayAttributeOp<T>::RemoveMultiple( int elem, int num ) +{ + if ( !m_pAttribute->MarkDirty() ) + return; + + // UNDO HOOK + if ( g_pDataModel->UndoEnabledForElement( m_pAttribute->GetOwner() ) ) + { + CUndoAttributeArrayRemoveElement<T> *pUndo = new CUndoAttributeArrayRemoveElement<T>( m_pAttribute, false, elem, num ); + g_pDataModel->AddUndoElement( pUndo ); + } + + m_pAttribute->PreChanged(); + OnAttributeArrayElementRemoved( elem, elem + num - 1 ); + Data().RemoveMultiple( elem, num ); + m_pAttribute->OnChanged( true ); +} + +// Memory deallocation +template< class T > +void CDmArrayAttributeOp<T>::Purge() +{ + if ( !m_pAttribute->MarkDirty() ) + return; + + // UNDO HOOK + if ( g_pDataModel->UndoEnabledForElement( m_pAttribute->GetOwner() ) ) + { + CUndoAttributeArrayCopyAllElement<T> *pUndo = new CUndoAttributeArrayCopyAllElement<T>( m_pAttribute, NULL, true ); + g_pDataModel->AddUndoElement( pUndo ); + } + + m_pAttribute->PreChanged(); + OnAttributeArrayElementRemoved( 0, Data().Count() - 1 ); + Data().Purge(); + m_pAttribute->OnChanged( true ); +} + + +//----------------------------------------------------------------------------- +// Copy Array +//----------------------------------------------------------------------------- +template< class T > +void CDmArrayAttributeOp<T>::PerformCopyArray( const T *pArray, int nCount ) +{ + Data().CopyArray( pArray, nCount ); +} + +template<> void CDmArrayAttributeOp<DmElementHandle_t>::PerformCopyArray( const DmElementHandle_t *pArray, int nCount ) +{ + Data().RemoveAll(); + for ( int i = 0; i < nCount; ++i ) + { + if ( ShouldInsertElement( pArray[ i ] ) ) + { + Data().AddToTail( pArray[ i ] ); + } + } +} + +template< class T > +void CDmArrayAttributeOp<T>::CopyArray( const T *pArray, int nCount ) +{ + if ( Data().Base() == pArray ) + { + int nCurrentCount = Data().Count(); + if ( nCurrentCount > nCount ) + { + RemoveMultiple( nCount, nCurrentCount - nCount ); + } + else if ( nCurrentCount < nCount ) + { + InsertMultipleBefore( nCurrentCount, nCount - nCurrentCount ); + } + return; + } + + if ( !m_pAttribute->MarkDirty() ) + return; + + // UNDO HOOK + if ( g_pDataModel->UndoEnabledForElement( m_pAttribute->GetOwner() ) ) + { + CUndoAttributeArrayCopyAllElement<T> *pUndo = new CUndoAttributeArrayCopyAllElement<T>( m_pAttribute, pArray, nCount ); + g_pDataModel->AddUndoElement( pUndo ); + } + + m_pAttribute->PreChanged(); + OnAttributeArrayElementRemoved( 0, Data().Count() - 1 ); + PerformCopyArray( pArray, nCount ); + OnAttributeArrayElementAdded( 0, Data().Count() - 1 ); + m_pAttribute->OnChanged( true ); +} + + +//----------------------------------------------------------------------------- +// Swap Array +//----------------------------------------------------------------------------- +template< class T > +void CDmArrayAttributeOp<T>::SwapArray( CUtlVector< T >& src ) +{ + // this is basically just a faster version of CopyArray + // the end result (for purposes of undo) are the same + // but there's no copy - just a pointer/etc swap + if ( !m_pAttribute->MarkDirty() ) + return; + + // UNDO HOOK + if ( g_pDataModel->UndoEnabledForElement( m_pAttribute->GetOwner() ) ) + { + CUndoAttributeArrayCopyAllElement<T> *pUndo = new CUndoAttributeArrayCopyAllElement<T>( m_pAttribute, src.Base(), src.Count() ); + g_pDataModel->AddUndoElement( pUndo ); + } + + m_pAttribute->PreChanged(); + OnAttributeArrayElementRemoved( 0, Data().Count() - 1 ); + Data().Swap( src ); + OnAttributeArrayElementAdded( 0, Data().Count() - 1 ); + m_pAttribute->OnChanged( true ); +} + + +template< > void CDmArrayAttributeOp<DmElementHandle_t>::SwapArray( CUtlVector< DmElementHandle_t >& src ) +{ + // This feature doesn't work for elements.. + // Can't do it owing to typesafety reasons as well as supporting the NODUPLICATES feature. + Assert( 0 ); +} + + +//----------------------------------------------------------------------------- +// Set value +//----------------------------------------------------------------------------- +template< class T > +void CDmArrayAttributeOp<T>::Set( int i, const T& value ) +{ + if ( i < 0 || i >= Data().Count() ) + { + Assert( !"CDmAttributeArray<T>::Set out of range value!\n" ); + return; + } + + // Don't bother doing anything if the attribute is equal + if ( IsAttributeEqual( Data()[i], value ) ) + return; + + if ( !ShouldInsert( value ) ) + return; + + if ( g_pDataModel->UndoEnabledForElement( m_pAttribute->GetOwner() ) ) + { + CUndoArrayAttributeSetValueElement<T> *pUndo = new CUndoArrayAttributeSetValueElement<T>( m_pAttribute, i, value ); + g_pDataModel->AddUndoElement( pUndo ); + } + + m_pAttribute->PreChanged(); + OnAttributeArrayElementRemoved( i, i ); + Data()[i] = value; + OnAttributeArrayElementAdded( i, i ); + m_pAttribute->OnChanged( false ); +} + +template< class T > +void CDmArrayAttributeOp<T>::Set( CDmAttribute *pAttribute, int i, DmAttributeType_t valueType, const void *pValue ) +{ + if ( valueType == ArrayTypeToValueType( pAttribute->GetType() ) ) + { + // This version is in IDmAttributeOp + CDmArrayAttributeOp< T > array( pAttribute ); + array.Set( i, *(const T*)pValue ); + } +} + + +//----------------------------------------------------------------------------- +// Set multiple values +//----------------------------------------------------------------------------- +template< class T > +void CDmArrayAttributeOp<T>::SetMultiple( int i, int nCount, const T* pValue ) +{ + if ( i < 0 || ( i+nCount ) > Data().Count() ) + { + AssertMsg( 0, "CDmAttributeArray<T>::SetMultiple out of range value!\n" ); + return; + } + + // Test for equality + bool bEqual = true; + for ( int j = 0; j < nCount; ++j ) + { + if ( !IsAttributeEqual( Data()[i+j], pValue[j] ) ) + { + bEqual = false; + break; + } + } + if ( bEqual ) + return; + + if ( !m_pAttribute->MarkDirty() ) + return; + + if ( g_pDataModel->UndoEnabledForElement( m_pAttribute->GetOwner() ) ) + { + CUndoArrayAttributeSetMultipleValueElement<T> *pUndo = new CUndoArrayAttributeSetMultipleValueElement<T>( m_pAttribute, i, nCount, pValue ); + g_pDataModel->AddUndoElement( pUndo ); + } + + m_pAttribute->PreChanged(); + OnAttributeArrayElementRemoved( i, i+nCount-1 ); + for ( int j = 0; j < nCount; ++j ) + { + if ( ShouldInsertElement( pValue[j] ) ) + { + Data()[i+j] = pValue[j]; + } + } + OnAttributeArrayElementAdded( i, i+nCount-1 ); + m_pAttribute->OnChanged( false ); +} + +template< class T > +void CDmArrayAttributeOp<T>::SetMultiple( CDmAttribute *pAttribute, int i, int nCount, DmAttributeType_t valueType, const void *pValue ) +{ + if ( valueType == ArrayTypeToValueType( pAttribute->GetType() ) ) + { + // This version is in IDmAttributeOp + CDmArrayAttributeOp< T > array( pAttribute ); + array.SetMultiple( i, nCount, (const T*)pValue ); + } +} + + +//----------------------------------------------------------------------------- +// Version of SetValue that's in IDmAttributeOp +//----------------------------------------------------------------------------- +template< class T > +void CDmArrayAttributeOp<T>::SetValue( CDmAttribute *pAttribute, DmAttributeType_t valueType, const void *pValue ) +{ + Assert( pAttribute->GetType() == valueType ); + if ( pAttribute->GetType() == valueType ) + { + CDmArrayAttributeOp<T> accessor( pAttribute ); + const CUtlVector<T>* pArray = reinterpret_cast< const CUtlVector<T>* >( pValue ); + accessor.CopyArray( pArray->Base(), pArray->Count() ); + } +} + + +//----------------------------------------------------------------------------- +// Swap +//----------------------------------------------------------------------------- +template< class T > +void CDmArrayAttributeOp<T>::Swap( int i, int j ) +{ + if ( i == j ) + return; + + // TODO - define Swap<T> for all attribute types to make swapping strings + // and voids fast (via pointer swaps, rather than 3 copies!) + T vk = Data()[ i ]; + if ( IsAttributeEqual( vk, Data()[j] ) ) + return; + + if ( !m_pAttribute->MarkDirty() ) + return; + + if ( g_pDataModel->UndoEnabledForElement( m_pAttribute->GetOwner() ) ) + { + CUndoArrayAttributeSetValueElement<T> *pUndo = new CUndoArrayAttributeSetValueElement<T>( m_pAttribute, i, Data()[ j ] ); + g_pDataModel->AddUndoElement( pUndo ); + pUndo = new CUndoArrayAttributeSetValueElement<T>( m_pAttribute, j, vk ); + g_pDataModel->AddUndoElement( pUndo ); + } + + m_pAttribute->PreChanged(); + + OnAttributeArrayElementRemoved( i, i ); + Data()[i] = Data()[j]; + OnAttributeArrayElementAdded( i, i ); + + OnAttributeArrayElementRemoved( j, j ); + Data()[j] = vk; + OnAttributeArrayElementAdded( j, j ); + + m_pAttribute->OnChanged( false ); +} + + +//----------------------------------------------------------------------------- +// Methods related to serialization +//----------------------------------------------------------------------------- +template< class T > +bool CDmArrayAttributeOp<T>::Unserialize( CDmAttribute *pAttribute, CUtlBuffer &buf ) +{ + if ( !pAttribute->MarkDirty() ) + return false; + + MEM_ALLOC_CREDIT_CLASS(); + + CUtlVector< T > tempVal; + bool bRet = ::Unserialize( buf, tempVal ); + + // Don't need undo hook since this goes through Swap route + CDmArrayAttributeOp<T> accessor( pAttribute ); + accessor.SwapArray( tempVal ); + + return bRet; +} + +template<> bool CDmArrayAttributeOp<DmElementHandle_t>::Unserialize( CDmAttribute *pAttribute, CUtlBuffer &buf ) +{ + // Need to specialize this because element handles can't use SwapArray + // because it's incapable of doing type safety checks or looking for FATTRIB_NODUPLICATES + if ( !CDmAttributeAccessor::MarkDirty( pAttribute ) ) + return false; + + MEM_ALLOC_CREDIT_CLASS(); + + CUtlVector< DmElementHandle_t > tempVal; + bool bRet = ::Unserialize( buf, tempVal ); + + // Don't need undo hook since this goes through copy route + CDmArrayAttributeOp<DmElementHandle_t> accessor( pAttribute ); + accessor.CopyArray( tempVal.Base(), tempVal.Count() ); + + return bRet; +} + +// Serialization of a single element +template< class T > +bool CDmArrayAttributeOp<T>::SerializeElement( const CDmAttribute *pAttribute, int nElement, CUtlBuffer &buf ) +{ + CDmrArrayConst<T> array( pAttribute ); + return ::Serialize( buf, array[ nElement ] ); +} + +template< class T > +bool CDmArrayAttributeOp<T>::UnserializeElement( CDmAttribute *pAttribute, CUtlBuffer &buf ) +{ + if ( !CDmAttributeAccessor::MarkDirty( pAttribute ) ) + return false; + + MEM_ALLOC_CREDIT_CLASS(); + + T temp; + bool bReadElement = ::Unserialize( buf, temp ); + if ( bReadElement ) + { + pAttribute->PreChanged(); + + CDmArrayAttributeOp<T> accessor( pAttribute ); + accessor.AddToTail( temp ); + + pAttribute->OnChanged( true ); + } + return bReadElement; +} + +template< class T > +bool CDmArrayAttributeOp<T>::UnserializeElement( CDmAttribute *pAttribute, int nElement, CUtlBuffer &buf ) +{ + if ( !CDmAttributeAccessor::MarkDirty( pAttribute ) ) + return false; + + CDmrArray<T> array( pAttribute ); + if ( array.Count() <= nElement ) + return false; + + MEM_ALLOC_CREDIT_CLASS(); + + pAttribute->PreChanged(); + bool bReadElement = ::Unserialize( buf, *const_cast<T*>( &array[nElement] ) ); + if ( bReadElement ) + { + pAttribute->OnChanged(); + } + return bReadElement; +} + +template< class T > +void CDmArrayAttributeOp<T>::OnUnserializationFinished( CDmAttribute *pAttribute ) +{ + CDmArrayAttributeOp<T> ref( pAttribute ); + int nCount = ref.Count(); + if ( nCount > 0 ) + { + ref.OnAttributeArrayElementAdded( 0, nCount - 1, false ); + } + CDmAttributeAccessor::OnChanged( pAttribute, true, true ); +} + + +//----------------------------------------------------------------------------- +// +// CDmAttribute begins here +// +//----------------------------------------------------------------------------- + + +//----------------------------------------------------------------------------- +// Memory pool used for CDmAttribute +//----------------------------------------------------------------------------- +CUtlMemoryPool g_AttrAlloc( sizeof( CDmAttribute ), 32, CUtlMemoryPool::GROW_SLOW, "CDmAttribute pool" ); + + +//----------------------------------------------------------------------------- +// Class factory +//----------------------------------------------------------------------------- + +// turn memdbg off temporarily so we can get at placement new +#include "tier0/memdbgoff.h" + +CDmAttribute *CDmAttribute::CreateAttribute( CDmElement *pOwner, DmAttributeType_t type, const char *pAttributeName ) +{ + switch( type ) + { + case AT_UNKNOWN: + Assert( 0 ); + return NULL; + + default: + { + void *pMem = g_AttrAlloc.Alloc( sizeof( CDmAttribute ) ); + return ::new( pMem ) CDmAttribute( pOwner, type, pAttributeName ); + } + } +} + +CDmAttribute *CDmAttribute::CreateExternalAttribute( CDmElement *pOwner, DmAttributeType_t type, const char *pAttributeName, void *pExternalMemory ) +{ + switch( type ) + { + case AT_UNKNOWN: + Assert( 0 ); + return NULL; + + default: + { + void *pMem = g_AttrAlloc.Alloc( sizeof( CDmAttribute ) ); + return ::new( pMem ) CDmAttribute( pOwner, type, pAttributeName, pExternalMemory ); + } + } +} + +void CDmAttribute::DestroyAttribute( CDmAttribute *pAttribute ) +{ + if ( !pAttribute ) + return; + + switch( pAttribute->GetType() ) + { + case AT_UNKNOWN: + break; + + default: + pAttribute->~CDmAttribute(); + +#ifdef _DEBUG + memset( pAttribute, 0xDD, sizeof(CDmAttribute) ); +#endif + + g_AttrAlloc.Free( pAttribute ); + break; + } +} + +// turn memdbg back on after using placement new +#include "tier0/memdbgon.h" + + +//----------------------------------------------------------------------------- +// Constructor, destructor +//----------------------------------------------------------------------------- +CDmAttribute::CDmAttribute( CDmElement *pOwner, DmAttributeType_t type, const char *pAttributeName ) : + m_pData( NULL ) +{ + Init( pOwner, type, pAttributeName ); + CreateAttributeData(); +} + +CDmAttribute::CDmAttribute( CDmElement *pOwner, DmAttributeType_t type, const char *pAttributeName, void *pMemory ) : + m_pData( pMemory ) +{ + Init( pOwner, type, pAttributeName ); + s_pAttrInfo[ GetType() ]->SetDefaultValue( m_pData ); + AddFlag( FATTRIB_EXTERNAL ); +} + + +void CDmAttribute::Init( CDmElement *pOwner, DmAttributeType_t type, const char *pAttributeName ) +{ + // FIXME - this is just here temporarily to catch old code trying to create type and id attributes + // this shouldn't actually be illegal, since users should be able to create attributes of whatever name they want + Assert( V_strcmp( pAttributeName, "type" ) && V_strcmp( pAttributeName, "id" ) ); + + m_pOwner = pOwner; + m_Name = g_pDataModel->GetSymbol( pAttributeName ); + m_nFlags = type; + m_Handle = DMATTRIBUTE_HANDLE_INVALID; + m_pNext = NULL; + m_hMailingList = DMMAILINGLIST_INVALID; + + switch ( type ) + { + case AT_ELEMENT: + case AT_ELEMENT_ARRAY: + case AT_OBJECTID: + case AT_OBJECTID_ARRAY: + m_nFlags |= FATTRIB_TOPOLOGICAL; + break; + } +} + +CDmAttribute::~CDmAttribute() +{ + switch( GetType() ) + { + case AT_ELEMENT: + g_pDataModelImp->OnElementReferenceRemoved( GetValue<DmElementHandle_t>(), this ); + break; + + case AT_ELEMENT_ARRAY: + { + CDmrElementArray<> array( this ); + int nElements = array.Count(); + for ( int i = 0; i < nElements; ++i ) + { + g_pDataModelImp->OnElementReferenceRemoved( array.GetHandle( i ), this ); + } + } + break; + } + + CleanupMailingList(); + InvalidateHandle(); + DeleteAttributeData(); +} + + +//----------------------------------------------------------------------------- +// Creates the attribute data +//----------------------------------------------------------------------------- +void CDmAttribute::CreateAttributeData() +{ + // Free the attribute memory + if ( !IsFlagSet( FATTRIB_EXTERNAL ) ) + { + Assert( !m_pData ); + m_pData = s_pAttrInfo[ GetType() ]->CreateAttributeData( ); + } +} + + +//----------------------------------------------------------------------------- +// Deletes the attribute data +//----------------------------------------------------------------------------- +void CDmAttribute::DeleteAttributeData() +{ + // Free the attribute memory + if ( m_pData && !IsFlagSet( FATTRIB_EXTERNAL ) ) + { + s_pAttrInfo[ GetType() ]->DestroyAttributeData( m_pData ); + m_pData = NULL; + } +} + + +//----------------------------------------------------------------------------- +// Used only in attribute element arrays +//----------------------------------------------------------------------------- +void CDmAttribute::SetElementTypeSymbol( UtlSymId_t typeSymbol ) +{ + switch ( GetType() ) + { + case AT_ELEMENT: + { + DmElementAttribute_t *pData = GetData< DmElementHandle_t >(); + Assert( pData->m_Handle == DMELEMENT_HANDLE_INVALID || ::IsA( pData->m_Handle, typeSymbol ) ); + pData->m_ElementType = typeSymbol; + } + break; + + case AT_ELEMENT_ARRAY: + { +#ifdef _DEBUG + CDmrElementArray<> array( this ); + if ( array.GetElementType() != UTL_INVAL_SYMBOL ) + { + int i; + int c = array.Count(); + for ( i = 0; i < c; ++i ) + { + Assert( array.GetHandle( i ) == DMELEMENT_HANDLE_INVALID || ::IsA( array.GetHandle( i ), typeSymbol ) ); + } + } +#endif + + DmElementArray_t *pData = GetArrayData< DmElementHandle_t >(); + pData->m_ElementType = typeSymbol; + } + break; + + default: + Assert(0); + break; + } +} + +UtlSymId_t CDmAttribute::GetElementTypeSymbol() const +{ + switch ( GetType() ) + { + case AT_ELEMENT: + return GetData< DmElementHandle_t >()->m_ElementType; + + case AT_ELEMENT_ARRAY: + return GetArrayData< DmElementHandle_t >()->m_ElementType; + + default: + Assert(0); + break; + } + + return UTL_INVAL_SYMBOL; +} + + +//----------------------------------------------------------------------------- +// Is modification allowed in this phase? +//----------------------------------------------------------------------------- +bool CDmAttribute::ModificationAllowed() const +{ + if ( IsFlagSet( FATTRIB_READONLY ) ) + return false; + + DmPhase_t phase = g_pDmElementFramework->GetPhase(); + if ( phase == PH_EDIT ) + return true; + if ( ( phase == PH_OPERATE ) && !IsFlagSet( FATTRIB_TOPOLOGICAL ) ) + return true; + + return false; +} + +bool CDmAttribute::MarkDirty() +{ + if ( !ModificationAllowed() ) + { + Assert( 0 ); + return false; + } + + AddFlag( FATTRIB_DIRTY | FATTRIB_OPERATOR_DIRTY ); + CDmeElementAccessor::MarkDirty( m_pOwner ); + + return true; +} + + +//----------------------------------------------------------------------------- +// Called before and after the attribute has changed +//----------------------------------------------------------------------------- +void CDmAttribute::PreChanged() +{ + if ( IsFlagSet( FATTRIB_HAS_PRE_CALLBACK ) && !CDmeElementAccessor::IsBeingUnserialized( m_pOwner ) ) + { + m_pOwner->PreAttributeChanged( this ); + } + + // FIXME: What about mailing lists? +} + +void CDmAttribute::OnChanged( bool bArrayCountChanged, bool bIsTopological ) +{ + if ( IsFlagSet( FATTRIB_HAS_CALLBACK ) && !CDmeElementAccessor::IsBeingUnserialized( m_pOwner ) ) + { + m_pOwner->OnAttributeChanged( this ); + } + + if ( ( m_hMailingList != DMMAILINGLIST_INVALID ) && !CDmeElementAccessor::IsBeingUnserialized( m_pOwner ) ) + { + if ( !g_pDataModelImp->PostAttributeChanged( m_hMailingList, this ) ) + { + CleanupMailingList(); + } + } + + if ( bIsTopological || IsTopological( GetType() ) ) + { + g_pDataModelImp->NotifyState( NOTIFY_CHANGE_TOPOLOGICAL ); + } + else + { + g_pDataModelImp->NotifyState( bArrayCountChanged ? NOTIFY_CHANGE_ATTRIBUTE_ARRAY_SIZE : NOTIFY_CHANGE_ATTRIBUTE_VALUE ); + } +} + + +//----------------------------------------------------------------------------- +// Type conversion related methods +//----------------------------------------------------------------------------- +template< class T > bool CDmAttribute::IsTypeConvertable() const +{ + return ( CDmAttributeInfo< T >::ATTRIBUTE_TYPE == GetType() ); +} + +template<> bool CDmAttribute::IsTypeConvertable<bool>() const +{ + DmAttributeType_t type = GetType(); + return ( type == AT_BOOL || type == AT_INT || type == AT_FLOAT ); +} + +template<> bool CDmAttribute::IsTypeConvertable<int>() const +{ + DmAttributeType_t type = GetType(); + return ( type == AT_INT || type == AT_BOOL || type == AT_FLOAT ); +} + +template<> bool CDmAttribute::IsTypeConvertable<float>() const +{ + DmAttributeType_t type = GetType(); + return ( type == AT_FLOAT || type == AT_INT || type == AT_BOOL ); +} + +template<> bool CDmAttribute::IsTypeConvertable<QAngle>() const +{ + DmAttributeType_t type = GetType(); + return ( type == AT_QANGLE || type == AT_QUATERNION ); +} + +template<> bool CDmAttribute::IsTypeConvertable<Quaternion>() const +{ + DmAttributeType_t type = GetType(); + return ( type == AT_QUATERNION || type == AT_QANGLE); +} + +template< class T > void CDmAttribute::CopyData( const T& value ) +{ + *reinterpret_cast< T* >( m_pData ) = value; +} + +template< class T > void CDmAttribute::CopyDataOut( T& value ) const +{ + value = *reinterpret_cast< const T* >( m_pData ); +} + +template<> void CDmAttribute::CopyData( const bool& value ) +{ + switch( GetType() ) + { + case AT_BOOL: + *reinterpret_cast< bool* >( m_pData ) = value; + break; + + case AT_INT: + *reinterpret_cast< int* >( m_pData ) = value ? 1 : 0; + break; + + case AT_FLOAT: + *reinterpret_cast< float* >( m_pData ) = value ? 1.0f : 0.0f; + break; + } +} + +template<> void CDmAttribute::CopyDataOut( bool& value ) const +{ + switch( GetType() ) + { + case AT_BOOL: + value = *reinterpret_cast< bool* >( m_pData ); + break; + + case AT_INT: + value = *reinterpret_cast< int* >( m_pData ) != 0; + break; + + case AT_FLOAT: + value = *reinterpret_cast< float* >( m_pData ) != 0.0f; + break; + } +} + +template<> void CDmAttribute::CopyData( const int& value ) +{ + switch( GetType() ) + { + case AT_BOOL: + *reinterpret_cast< bool* >( m_pData ) = value != 0; + break; + + case AT_INT: + *reinterpret_cast< int* >( m_pData ) = value; + break; + + case AT_FLOAT: + *reinterpret_cast< float* >( m_pData ) = value; + break; + } +} + +template<> void CDmAttribute::CopyDataOut( int& value ) const +{ + switch( GetType() ) + { + case AT_BOOL: + value = *reinterpret_cast< bool* >( m_pData ) ? 1 : 0; + break; + + case AT_INT: + value = *reinterpret_cast< int* >( m_pData ); + break; + + case AT_FLOAT: + value = *reinterpret_cast< float* >( m_pData ); + break; + } +} + +template<> void CDmAttribute::CopyData( const float& value ) +{ + switch( GetType() ) + { + case AT_BOOL: + *reinterpret_cast< bool* >( m_pData ) = value != 0.0f; + break; + + case AT_INT: + *reinterpret_cast< int* >( m_pData ) = value; + break; + + case AT_FLOAT: + *reinterpret_cast< float* >( m_pData ) = value; + break; + } +} + +template<> void CDmAttribute::CopyDataOut( float& value ) const +{ + switch( GetType() ) + { + case AT_BOOL: + value = *reinterpret_cast< bool* >( m_pData ) ? 1.0f : 0.0f; + break; + + case AT_INT: + value = *reinterpret_cast< int* >( m_pData ); + break; + + case AT_FLOAT: + value = *reinterpret_cast< float* >( m_pData ); + break; + } +} + +template<> void CDmAttribute::CopyData( const QAngle& value ) +{ + switch( GetType() ) + { + case AT_QANGLE: + *reinterpret_cast< QAngle* >( m_pData ) = value; + break; + + case AT_QUATERNION: + { + Quaternion qValue; + AngleQuaternion( value, qValue ); + *reinterpret_cast< Quaternion* >( m_pData ) = qValue; + } + break; + } +} + +template<> void CDmAttribute::CopyDataOut( QAngle& value ) const +{ + switch( GetType() ) + { + case AT_QANGLE: + value = *reinterpret_cast< QAngle* >( m_pData ); + break; + + case AT_QUATERNION: + QuaternionAngles( *reinterpret_cast< Quaternion* >( m_pData ), value ); + break; + } +} + +template<> void CDmAttribute::CopyData( const Quaternion& value ) +{ + switch( GetType() ) + { + case AT_QANGLE: + { + QAngle aValue; + QuaternionAngles( value, aValue ); + *reinterpret_cast< QAngle* >( m_pData ) = aValue; + } + break; + + case AT_QUATERNION: + *reinterpret_cast< Quaternion* >( m_pData ) = value; + break; + } +} + +template<> void CDmAttribute::CopyDataOut( Quaternion& value ) const +{ + switch( GetType() ) + { + case AT_QANGLE: + AngleQuaternion( *reinterpret_cast< QAngle* >( m_pData ), value ); + break; + + case AT_QUATERNION: + value = *reinterpret_cast< Quaternion* >( m_pData ); + break; + } +} + +template<> void CDmAttribute::CopyData( const DmElementHandle_t& value ) +{ + g_pDataModelImp->OnElementReferenceRemoved( GetValue<DmElementHandle_t>(), this ); + *reinterpret_cast< DmElementHandle_t* >( m_pData ) = value; + g_pDataModelImp->OnElementReferenceAdded( value, this ); +} + + +//----------------------------------------------------------------------------- +// Should we be allowed to modify the attribute data? +//----------------------------------------------------------------------------- +template< class T > +bool CDmAttribute::ShouldModify( const T& value ) +{ + if ( !IsTypeConvertable<T>() ) + return false; + + if ( ( GetType() == CDmAttributeInfo<T>::ATTRIBUTE_TYPE ) && IsAttributeEqual( GetValue<T>(), value ) ) + return false; + + return MarkDirty(); +} + +template<> bool CDmAttribute::ShouldModify( const DmElementHandle_t& value ) +{ + if ( !IsTypeConvertable<DmElementHandle_t>() ) + return false; + + if ( IsAttributeEqual( GetValue<DmElementHandle_t>(), value ) ) + return false; + + DmElementAttribute_t *pData = GetData<DmElementHandle_t>(); + if ( pData->m_ElementType != UTL_INVAL_SYMBOL && !::IsA( value, pData->m_ElementType ) ) + return false; + + return MarkDirty(); +} + + +//----------------------------------------------------------------------------- +// Main entry point for single-valued SetValue +//----------------------------------------------------------------------------- +template< class T > +void CDmAttribute::SetValue( const T &value ) +{ + if ( !ShouldModify( value ) ) + return; + + // UNDO Hook + if ( g_pDataModel->UndoEnabledForElement( m_pOwner ) ) + { + CUndoAttributeSetValueElement<T> *pUndo = new CUndoAttributeSetValueElement<T>( this, value ); + g_pDataModel->AddUndoElement( pUndo ); + } + + bool bIsBeingUnserialized = CDmeElementAccessor::IsBeingUnserialized( m_pOwner ); + if ( IsFlagSet( FATTRIB_HAS_PRE_CALLBACK ) && !bIsBeingUnserialized ) + { + m_pOwner->PreAttributeChanged( this ); + } + + CopyData< T >( value ); + + if ( !bIsBeingUnserialized ) + { + if ( IsFlagSet( FATTRIB_HAS_CALLBACK ) ) + { + m_pOwner->OnAttributeChanged( this ); + } + + if ( m_hMailingList != DMMAILINGLIST_INVALID ) + { + if ( !g_pDataModelImp->PostAttributeChanged( m_hMailingList, this ) ) + { + CleanupMailingList(); + } + } + } + + g_pDataModelImp->NotifyState( IsTopological( GetType() ) ? NOTIFY_CHANGE_TOPOLOGICAL : NOTIFY_CHANGE_ATTRIBUTE_VALUE ); +} + + +//----------------------------------------------------------------------------- +// Versions that work on arrays +//----------------------------------------------------------------------------- +#define ATTRIBUTE_SET_VALUE_ARRAY( _type ) \ + template<> void CDmAttribute::SetValue( const CUtlVector< _type >& value ) \ + { \ + CDmArrayAttributeOp< _type > accessor( this ); \ + accessor.CopyArray( value.Base(), value.Count() ); \ + } + +void CDmAttribute::SetValue( const CDmAttribute *pAttribute ) +{ + s_pAttrInfo[ GetType() ]->SetValue( this, pAttribute->GetType(), pAttribute->GetAttributeData() ); +} + +void CDmAttribute::SetValue( CDmAttribute *pAttribute ) +{ + s_pAttrInfo[ GetType() ]->SetValue( this, pAttribute->GetType(), pAttribute->GetAttributeData() ); +} + +void CDmAttribute::SetValue( DmAttributeType_t valueType, const void *pValue ) +{ + s_pAttrInfo[ GetType() ]->SetValue( this, valueType, pValue ); +} + + +//----------------------------------------------------------------------------- +// Sets the attribute to its default value based on its type +//----------------------------------------------------------------------------- +void CDmAttribute::SetToDefaultValue() +{ + s_pAttrInfo[ GetType() ]->SetToDefaultValue( this ); +} + + +//----------------------------------------------------------------------------- +// Convert to and from string +//----------------------------------------------------------------------------- +void CDmAttribute::SetValueFromString( const char *pValue ) +{ + switch ( GetType() ) + { + case AT_STRING: + SetValue( pValue ); + break; + + default: + { + int nLen = pValue ? Q_strlen( pValue ) : 0; + if ( nLen == 0 ) + { + SetToDefaultValue(); + break; + } + + CUtlBuffer buf( pValue, nLen, CUtlBuffer::TEXT_BUFFER | CUtlBuffer::READ_ONLY ); + if ( !Unserialize( buf ) ) + { + SetToDefaultValue(); + } + } + break; + } +} + +const char *CDmAttribute::GetValueAsString( char *pBuffer, size_t nBufLen ) const +{ + Assert( pBuffer ); + CUtlBuffer buf( pBuffer, nBufLen, CUtlBuffer::TEXT_BUFFER ); + Serialize( buf ); + return pBuffer; +} + + +//----------------------------------------------------------------------------- +// Name, type +//----------------------------------------------------------------------------- +const char* CDmAttribute::GetTypeString() const +{ + return ::GetTypeString( GetType() ); +} + +const char *GetTypeString( DmAttributeType_t type ) +{ + if ( ( type >= 0 ) && ( type < AT_TYPE_COUNT ) ) + return s_pAttrInfo[ type ]->AttributeTypeName(); + return "unknown"; +} + + +void CDmAttribute::SetName( const char *pNewName ) +{ + if ( m_pOwner->HasAttribute( pNewName ) && Q_stricmp( GetName(), pNewName ) ) + { + Warning( "Tried to rename from '%s' to '%s', but '%s' already exists\n", + GetName(), pNewName, pNewName ); + return; + } + + if ( !MarkDirty() ) + return; + + // UNDO Hook + if ( g_pDataModel->UndoEnabledForElement( m_pOwner ) ) + { + CUndoAttributeRenameElement *pUndo = new CUndoAttributeRenameElement( this, pNewName ); + g_pDataModel->AddUndoElement( pUndo ); + } + + m_Name = g_pDataModel->GetSymbol( pNewName ); + g_pDataModelImp->NotifyState( NOTIFY_CHANGE_TOPOLOGICAL ); +} + + +//----------------------------------------------------------------------------- +// Serialization +//----------------------------------------------------------------------------- +bool CDmAttribute::SerializesOnMultipleLines() const +{ + return s_pAttrInfo[ GetType() ]->SerializesOnMultipleLines(); +} + +bool CDmAttribute::Serialize( CUtlBuffer &buf ) const +{ + return s_pAttrInfo[ GetType() ]->Serialize( this, buf ); +} + +bool CDmAttribute::Unserialize( CUtlBuffer &buf ) +{ + return s_pAttrInfo[ GetType() ]->Unserialize( this, buf ); +} + +bool CDmAttribute::SerializeElement( int nElement, CUtlBuffer &buf ) const +{ + return s_pAttrInfo[ GetType() ]->SerializeElement( this, nElement, buf ); +} + +bool CDmAttribute::UnserializeElement( CUtlBuffer &buf ) +{ + return s_pAttrInfo[ GetType() ]->UnserializeElement( this, buf ); +} + +bool CDmAttribute::UnserializeElement( int nElement, CUtlBuffer &buf ) +{ + return s_pAttrInfo[ GetType() ]->UnserializeElement( this, nElement, buf ); +} + +// Called by elements after unserialization of their attributes is complete +void CDmAttribute::OnUnserializationFinished() +{ + return s_pAttrInfo[ GetType() ]->OnUnserializationFinished( this ); +} + + + +//----------------------------------------------------------------------------- +// Methods related to attribute change notification +//----------------------------------------------------------------------------- +void CDmAttribute::CleanupMailingList() +{ + if ( m_hMailingList != DMMAILINGLIST_INVALID ) + { + g_pDataModelImp->DestroyMailingList( m_hMailingList ); + m_hMailingList = DMMAILINGLIST_INVALID; + } +} + +void CDmAttribute::NotifyWhenChanged( DmElementHandle_t h, bool bNotify ) +{ + if ( bNotify ) + { + if ( m_hMailingList == DMMAILINGLIST_INVALID ) + { + m_hMailingList = g_pDataModelImp->CreateMailingList(); + } + g_pDataModelImp->AddElementToMailingList( m_hMailingList, h ); + return; + } + + if ( m_hMailingList != DMMAILINGLIST_INVALID ) + { + if ( !g_pDataModelImp->RemoveElementFromMailingList( m_hMailingList, h ) ) + { + CleanupMailingList(); + } + } +} + + +//----------------------------------------------------------------------------- +// Get the attribute/create an attribute handle +//----------------------------------------------------------------------------- +DmAttributeHandle_t CDmAttribute::GetHandle( bool bCreate ) +{ + if ( (m_Handle == DMATTRIBUTE_HANDLE_INVALID) && bCreate ) + { + m_Handle = g_pDataModelImp->AcquireAttributeHandle( this ); + } + + Assert( (m_Handle == DMATTRIBUTE_HANDLE_INVALID) || g_pDataModel->IsAttributeHandleValid( m_Handle ) ); + return m_Handle; +} + +void CDmAttribute::InvalidateHandle() +{ + g_pDataModelImp->ReleaseAttributeHandle( m_Handle ); + m_Handle = DMATTRIBUTE_HANDLE_INVALID; +} + + +//----------------------------------------------------------------------------- +// Memory usage estimations +//----------------------------------------------------------------------------- +bool HandleCompare( const DmElementHandle_t &a, const DmElementHandle_t &b ) +{ + return a == b; +} + +unsigned int HandleHash( const DmElementHandle_t &h ) +{ + return (unsigned int)h; +} + +int CDmAttribute::EstimateMemoryUsage( TraversalDepth_t depth ) const +{ + CUtlHash< DmElementHandle_t > visited( 1024, 0, 0, HandleCompare, HandleHash ); + return EstimateMemoryUsageInternal( visited, depth, 0 ) ; +} + +int CDmAttribute::EstimateMemoryUsageInternal( CUtlHash< DmElementHandle_t > &visited, TraversalDepth_t depth, int *pCategories ) const +{ + int nOverhead = sizeof( *this ); + int nAttributeDataSize = s_pAttrInfo[ GetType() ]->DataSize(); + int nTotalMemory = nOverhead + nAttributeDataSize; + int nAttributeExtraDataSize = 0; + + if ( IsArrayType( GetType() ) ) + { + CDmrGenericArrayConst array( this ); + int nCount = array.Count(); + nAttributeExtraDataSize = nCount * s_pAttrInfo[ GetType() ]->ValueSize(); // Data in the UtlVector + int nMallocOverhead = ( array.Count() == 0 ) ? 0 : 8; // malloc overhead inside the vector + nOverhead += nMallocOverhead; + nTotalMemory += nAttributeExtraDataSize + nMallocOverhead; + } + + if ( pCategories ) + { + ++pCategories[MEMORY_CATEGORY_ATTRIBUTE_COUNT]; + pCategories[MEMORY_CATEGORY_ATTRIBUTE_DATA] += nAttributeDataSize + nAttributeExtraDataSize; + pCategories[MEMORY_CATEGORY_ATTRIBUTE_OVERHEAD] += nOverhead; + if ( !IsDataInline() ) + { + pCategories[MEMORY_CATEGORY_OUTER] -= nAttributeDataSize; + Assert( pCategories[MEMORY_CATEGORY_OUTER] >= 0 ); + nTotalMemory -= nAttributeDataSize; + } + } + + switch ( GetType() ) + { + case AT_STRING: + { + const CUtlString &value = GetValue<CUtlString>(); + if ( pCategories ) + { + pCategories[MEMORY_CATEGORY_ATTRIBUTE_DATA] += value.Length() + 1; + pCategories[MEMORY_CATEGORY_ATTRIBUTE_OVERHEAD] += 8; + } + return nTotalMemory + value.Length() + 1 + 8; // string's length skips trailing null + } + + case AT_STRING_ARRAY: + { + const CUtlVector< CUtlString > &array = GetValue< CUtlVector< CUtlString > >( ); + for ( int i = 0; i < array.Count(); ++i ) + { + int nStrLen = array[ i ].Length() + 1; + if ( pCategories ) + { + pCategories[MEMORY_CATEGORY_ATTRIBUTE_DATA] += nStrLen; + pCategories[MEMORY_CATEGORY_ATTRIBUTE_OVERHEAD] += 8; + } + nTotalMemory += nStrLen + 8; // string's length skips trailing null + } + return nTotalMemory; + } + + case AT_VOID: + { + const CUtlBinaryBlock &value = GetValue< CUtlBinaryBlock >(); + if ( pCategories ) + { + pCategories[MEMORY_CATEGORY_ATTRIBUTE_DATA] += value.Length(); + pCategories[MEMORY_CATEGORY_ATTRIBUTE_OVERHEAD] += 8; + } + return nTotalMemory + value.Length() + 8; + } + + case AT_VOID_ARRAY: + { + const CUtlVector< CUtlBinaryBlock > &array = GetValue< CUtlVector< CUtlBinaryBlock > >(); + for ( int i = 0; i < array.Count(); ++i ) + { + if ( pCategories ) + { + pCategories[MEMORY_CATEGORY_ATTRIBUTE_DATA] += array[ i ].Length(); + pCategories[MEMORY_CATEGORY_ATTRIBUTE_OVERHEAD] += 8; + } + nTotalMemory += array[ i ].Length() + 8; + } + return nTotalMemory; + } + + case AT_ELEMENT: + if ( ShouldTraverse( this, depth ) ) + { + CDmElement *pElement = GetValueElement<CDmElement>(); + if ( pElement ) + { + nTotalMemory += CDmeElementAccessor::EstimateMemoryUsage( pElement, visited, depth, pCategories ); + } + } + return nTotalMemory; + + case AT_ELEMENT_ARRAY: + if ( ShouldTraverse( this, depth ) ) + { + CDmrElementArrayConst<> array( this ); + for ( int i = 0; i < array.Count(); ++i ) + { + CDmElement *pElement = array[ i ]; + if ( pElement ) + { + nTotalMemory += CDmeElementAccessor::EstimateMemoryUsage( pElement, visited, depth, pCategories ); + } + } + } + return nTotalMemory; + } + + return nTotalMemory; +} + + +//----------------------------------------------------------------------------- +// +// CDmaArrayBase starts here +// +//----------------------------------------------------------------------------- + + +//----------------------------------------------------------------------------- +// Constructor +//----------------------------------------------------------------------------- +template< class T, class B > +CDmaArrayConstBase<T,B>::CDmaArrayConstBase( ) +{ + m_pAttribute = NULL; +} + + +//----------------------------------------------------------------------------- +// Search +//----------------------------------------------------------------------------- +template< class T, class B > +int CDmaArrayConstBase<T,B>::Find( const T &value ) const +{ + return Value().Find( value ); +} + + +//----------------------------------------------------------------------------- +// Insertion +//----------------------------------------------------------------------------- +template< class T, class B > +int CDmaArrayBase<T,B>::AddToTail() +{ + T defaultVal; + CDmAttributeInfo<T>::SetDefaultValue( defaultVal ); + CDmArrayAttributeOp<T> accessor( this->m_pAttribute ); + return accessor.InsertBefore( Value().Count(), defaultVal ); +} + +template< class T, class B > +int CDmaArrayBase<T,B>::InsertBefore( int elem ) +{ + T defaultVal; + CDmAttributeInfo<T>::SetDefaultValue( defaultVal ); + CDmArrayAttributeOp<T> accessor( this->m_pAttribute ); + return accessor.InsertBefore( elem, defaultVal ); +} + +template< class T, class B > +int CDmaArrayBase<T,B>::AddToTail( const T& src ) +{ + CDmArrayAttributeOp<T> accessor( this->m_pAttribute ); + return accessor.InsertBefore( Value().Count(), src ); +} + +template< class T, class B > +int CDmaArrayBase<T,B>::InsertBefore( int elem, const T& src ) +{ + CDmArrayAttributeOp<T> accessor( this->m_pAttribute ); + return accessor.InsertBefore( elem, src ); +} + +template< class T, class B > +int CDmaArrayBase<T,B>::AddMultipleToTail( int num ) +{ + CDmArrayAttributeOp<T> accessor( this->m_pAttribute ); + return accessor.InsertMultipleBefore( Value().Count(), num ); +} + +template< class T, class B > +int CDmaArrayBase<T,B>::InsertMultipleBefore( int elem, int num ) +{ + CDmArrayAttributeOp<T> accessor( this->m_pAttribute ); + return accessor.InsertMultipleBefore( elem, num ); +} + +template< class T, class B > +void CDmaArrayBase<T,B>::EnsureCount( int num ) +{ + int nCurrentCount = Value().Count(); + if ( nCurrentCount < num ) + { + AddMultipleToTail( num - nCurrentCount ); + } +} + + +//----------------------------------------------------------------------------- +// Element modification +//----------------------------------------------------------------------------- +template< class T, class B > +void CDmaArrayBase<T,B>::Set( int i, const T& value ) +{ + CDmArrayAttributeOp<T> accessor( this->m_pAttribute ); + return accessor.Set( i, value ); +} + +template< class T, class B > +void CDmaArrayBase<T,B>::SetMultiple( int i, int nCount, const T* pValue ) +{ + CDmArrayAttributeOp<T> accessor( this->m_pAttribute ); + accessor.SetMultiple( i, nCount, pValue ); +} + +template< class T, class B > +void CDmaArrayBase<T,B>::Swap( int i, int j ) +{ + CDmArrayAttributeOp<T> accessor( this->m_pAttribute ); + accessor.Swap( i, j ); +} + +template< class T, class B > +void CDmaArrayBase<T,B>::SwapArray( CUtlVector< T > &array ) +{ + CDmArrayAttributeOp<T> accessor( this->m_pAttribute ); + accessor.SwapArray( array ); +} + + +//----------------------------------------------------------------------------- +// Copy +//----------------------------------------------------------------------------- +template< class T, class B > +void CDmaArrayBase<T,B>::CopyArray( const T *pArray, int nCount ) +{ + CDmArrayAttributeOp<T> accessor( this->m_pAttribute ); + accessor.CopyArray( pArray, nCount ); +} + + +//----------------------------------------------------------------------------- +// Removal +//----------------------------------------------------------------------------- +template< class T, class B > +void CDmaArrayBase<T,B>::FastRemove( int elem ) +{ + CDmArrayAttributeOp<T> accessor( this->m_pAttribute ); + accessor.FastRemove( elem ); +} + +template< class T, class B > +void CDmaArrayBase<T,B>::Remove( int elem ) +{ + CDmArrayAttributeOp<T> accessor( this->m_pAttribute ); + accessor.Remove( elem ); +} + +template< class T, class B > +void CDmaArrayBase<T,B>::RemoveAll() +{ + CDmArrayAttributeOp<T> accessor( this->m_pAttribute ); + accessor.RemoveAll(); +} + +template< class T, class B > +void CDmaArrayBase<T,B>::RemoveMultiple( int elem, int num ) +{ + CDmArrayAttributeOp<T> accessor( this->m_pAttribute ); + accessor.RemoveMultiple( elem, num ); +} + + +//----------------------------------------------------------------------------- +// Memory management +//----------------------------------------------------------------------------- +template< class T, class B > +void CDmaArrayBase<T,B>::EnsureCapacity( int num ) +{ + Value().EnsureCapacity( num ); +} + +template< class T, class B > +void CDmaArrayBase<T,B>::Purge() +{ + CDmArrayAttributeOp<T> accessor( this->m_pAttribute ); + accessor.Purge(); +} + + +//----------------------------------------------------------------------------- +// Attribute initialization +//----------------------------------------------------------------------------- +template< class T, class B > +void CDmaDecorator<T,B>::Init( CDmElement *pOwner, const char *pAttributeName, int nFlags = 0 ) +{ + Assert( pOwner ); + this->m_pAttribute = pOwner->AddExternalAttribute( pAttributeName, CDmAttributeInfo<CUtlVector<T> >::AttributeType(), &Value() ); + Assert( m_pAttribute ); + if ( nFlags ) + { + this->m_pAttribute->AddFlag( nFlags ); + } +} + + +//----------------------------------------------------------------------------- +// Attribute attribute reference +//----------------------------------------------------------------------------- +template< class T, class BaseClass > +void CDmrDecoratorConst<T,BaseClass>::Init( const CDmAttribute* pAttribute ) +{ + if ( pAttribute && pAttribute->GetType() == CDmAttributeInfo< CUtlVector< T > >::AttributeType() ) + { + this->m_pAttribute = const_cast<CDmAttribute*>( pAttribute ); + Attach( this->m_pAttribute->GetAttributeData() ); + } + else + { + this->m_pAttribute = NULL; + Attach( NULL ); + } +} + +template< class T, class BaseClass > +void CDmrDecoratorConst<T,BaseClass>::Init( const CDmElement *pElement, const char *pAttributeName ) +{ + const CDmAttribute *pAttribute = NULL; + if ( pElement && pAttributeName && pAttributeName[0] ) + { + pAttribute = pElement->GetAttribute( pAttributeName ); + } + Init( pAttribute ); +} + +template< class T, class BaseClass > +bool CDmrDecoratorConst<T,BaseClass>::IsValid() const +{ + return this->m_pAttribute != NULL; +} + + +template< class T, class BaseClass > +void CDmrDecorator<T,BaseClass>::Init( CDmAttribute* pAttribute ) +{ + if ( pAttribute && pAttribute->GetType() == CDmAttributeInfo< CUtlVector< T > >::AttributeType() ) + { + this->m_pAttribute = pAttribute; + Attach( this->m_pAttribute->GetAttributeData() ); + } + else + { + this->m_pAttribute = NULL; + Attach( NULL ); + } +} + +template< class T, class BaseClass > +void CDmrDecorator<T,BaseClass>::Init( CDmElement *pElement, const char *pAttributeName, bool bAddAttribute ) +{ + CDmAttribute *pAttribute = NULL; + if ( pElement && pAttributeName && pAttributeName[0] ) + { + if ( bAddAttribute ) + { + pAttribute = pElement->AddAttribute( pAttributeName, CDmAttributeInfo< CUtlVector< T > >::AttributeType() ); + } + else + { + pAttribute = pElement->GetAttribute( pAttributeName ); + } + } + Init( pAttribute ); +} + +template< class T, class BaseClass > +bool CDmrDecorator<T,BaseClass>::IsValid() const +{ + return this->m_pAttribute != NULL; +} + + +//----------------------------------------------------------------------------- +// +// Generic array access +// +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- +// Helper macros to make switch statements based on type +//----------------------------------------------------------------------------- +#define ARRAY_METHOD_VOID( _type, _func ) \ + case CDmAttributeInfo< CUtlVector< _type > >::ATTRIBUTE_TYPE: \ + { \ + CDmrArray< _type > &array = *reinterpret_cast< CDmrArray< _type > * >( &arrayShared ); \ + array.Init( m_pAttribute ); \ + array._func; \ + } \ + break; + +#define APPLY_ARRAY_METHOD_VOID( _func ) \ + CDmrArray<int> arrayShared; \ + switch( m_pAttribute->GetType() ) \ + { \ + ARRAY_METHOD_VOID( bool, _func ) \ + ARRAY_METHOD_VOID( int, _func ) \ + ARRAY_METHOD_VOID( float, _func ) \ + ARRAY_METHOD_VOID( Color, _func ) \ + ARRAY_METHOD_VOID( Vector2D, _func ) \ + ARRAY_METHOD_VOID( Vector, _func ) \ + ARRAY_METHOD_VOID( Vector4D, _func ) \ + ARRAY_METHOD_VOID( QAngle, _func ) \ + ARRAY_METHOD_VOID( Quaternion, _func ) \ + ARRAY_METHOD_VOID( VMatrix, _func ) \ + ARRAY_METHOD_VOID( CUtlString, _func ) \ + ARRAY_METHOD_VOID( CUtlBinaryBlock, _func ) \ + ARRAY_METHOD_VOID( DmObjectId_t, _func ) \ + ARRAY_METHOD_VOID( DmElementHandle_t, _func ) \ + default: \ + break; \ + } + +#define ARRAY_METHOD_RET( _type, _func ) \ + case CDmAttributeInfo< CUtlVector< _type > >::ATTRIBUTE_TYPE: \ + { \ + CDmrArray< _type > &array = *reinterpret_cast< CDmrArray< _type > * >( &arrayShared ); \ + array.Init( m_pAttribute ); \ + return array._func; \ + } + +#define APPLY_ARRAY_METHOD_RET( _func ) \ + CDmrArray<int> arrayShared; \ + switch( m_pAttribute->GetType() ) \ + { \ + ARRAY_METHOD_RET( bool, _func ); \ + ARRAY_METHOD_RET( int, _func ); \ + ARRAY_METHOD_RET( float, _func ); \ + ARRAY_METHOD_RET( Color, _func ); \ + ARRAY_METHOD_RET( Vector2D, _func ); \ + ARRAY_METHOD_RET( Vector, _func ); \ + ARRAY_METHOD_RET( Vector4D, _func ); \ + ARRAY_METHOD_RET( QAngle, _func ); \ + ARRAY_METHOD_RET( Quaternion, _func ); \ + ARRAY_METHOD_RET( VMatrix, _func ); \ + ARRAY_METHOD_RET( CUtlString, _func ); \ + ARRAY_METHOD_RET( CUtlBinaryBlock, _func ); \ + ARRAY_METHOD_RET( DmObjectId_t, _func ); \ + ARRAY_METHOD_RET( DmElementHandle_t, _func ); \ + default: \ + break; \ + } + +CDmrGenericArrayConst::CDmrGenericArrayConst() : m_pAttribute( NULL ) +{ +} + +CDmrGenericArrayConst::CDmrGenericArrayConst( const CDmAttribute* pAttribute ) +{ + Init( pAttribute ); +} + +CDmrGenericArrayConst::CDmrGenericArrayConst( const CDmElement *pElement, const char *pAttributeName ) +{ + Init( pElement, pAttributeName ); +} + +void CDmrGenericArrayConst::Init( const CDmAttribute *pAttribute ) +{ + if ( pAttribute && IsArrayType( pAttribute->GetType() ) ) + { + m_pAttribute = const_cast<CDmAttribute*>( pAttribute ); + } + else + { + m_pAttribute = NULL; + } +} + +void CDmrGenericArrayConst::Init( const CDmElement *pElement, const char *pAttributeName ) +{ + const CDmAttribute *pAttribute = ( pElement && pAttributeName && pAttributeName[0] ) ? pElement->GetAttribute( pAttributeName ) : NULL; + Init( pAttribute ); +} + +int CDmrGenericArrayConst::Count() const +{ + APPLY_ARRAY_METHOD_RET( Count() ); + return 0; +} + +const void* CDmrGenericArrayConst::GetUntyped( int i ) const +{ + APPLY_ARRAY_METHOD_RET( GetUntyped( i ) ); + return NULL; +} + +const char* CDmrGenericArrayConst::GetAsString( int i, char *pBuffer, size_t nBufLen ) const +{ + if ( ( Count() > i ) && ( i >= 0 ) ) + { + CUtlBuffer buf( pBuffer, nBufLen, CUtlBuffer::TEXT_BUFFER ); + m_pAttribute->SerializeElement( i, buf ); + } + else + { + pBuffer[0] = 0; + } + return pBuffer; +} + + +CDmrGenericArray::CDmrGenericArray( CDmAttribute* pAttribute ) +{ + Init( pAttribute ); +} + +CDmrGenericArray::CDmrGenericArray( CDmElement *pElement, const char *pAttributeName ) +{ + Init( pElement, pAttributeName ); +} + +void CDmrGenericArray::EnsureCount( int num ) +{ + APPLY_ARRAY_METHOD_VOID( EnsureCount(num) ); +} + +int CDmrGenericArray::AddToTail() +{ + APPLY_ARRAY_METHOD_RET( AddToTail() ); + return -1; +} + +void CDmrGenericArray::Remove( int elem ) +{ + APPLY_ARRAY_METHOD_VOID( Remove(elem) ); +} + +void CDmrGenericArray::RemoveAll() +{ + APPLY_ARRAY_METHOD_VOID( RemoveAll() ); +} + +void CDmrGenericArray::SetMultiple( int i, int nCount, DmAttributeType_t valueType, const void *pValue ) +{ + s_pAttrInfo[ m_pAttribute->GetType() ]->SetMultiple( m_pAttribute, i, nCount, valueType, pValue ); +} + +void CDmrGenericArray::Set( int i, DmAttributeType_t valueType, const void *pValue ) +{ + s_pAttrInfo[ m_pAttribute->GetType() ]->Set( m_pAttribute, i, valueType, pValue ); +} + +void CDmrGenericArray::SetFromString( int i, const char *pValue ) +{ + if ( ( Count() > i ) && ( i >= 0 ) ) + { + int nLen = pValue ? Q_strlen( pValue ) : 0; + CUtlBuffer buf( pValue, nLen, CUtlBuffer::TEXT_BUFFER | CUtlBuffer::READ_ONLY ); + m_pAttribute->UnserializeElement( i, buf ); + } +} + + +//----------------------------------------------------------------------------- +// Skip unserialization for an attribute type (unserialize into a dummy variable) +//----------------------------------------------------------------------------- +bool SkipUnserialize( CUtlBuffer &buf, DmAttributeType_t type ) +{ + if ( type == AT_UNKNOWN ) + return false; + + return s_pAttrInfo[ type ]->SkipUnserialize( buf ); +} + + +//----------------------------------------------------------------------------- +// returns the number of attributes currently allocated +//----------------------------------------------------------------------------- +int GetAllocatedAttributeCount() +{ + return g_AttrAlloc.Count(); +} + + +//----------------------------------------------------------------------------- +// Attribute type->name and name->attribute type +//----------------------------------------------------------------------------- +const char *AttributeTypeName( DmAttributeType_t type ) +{ + if ( ( type >= 0 ) && ( type < AT_TYPE_COUNT ) ) + return s_pAttrInfo[ type ]->AttributeTypeName(); + return "unknown"; +} + +DmAttributeType_t AttributeType( const char *pName ) +{ + for ( int i = 0; i < AT_TYPE_COUNT; ++i ) + { + if ( !Q_stricmp( s_pAttrInfo[ i ]->AttributeTypeName(), pName ) ) + return (DmAttributeType_t)i; + } + + return AT_UNKNOWN; +} + + +//----------------------------------------------------------------------------- +// Explicit template instantiation for the known attribute types +//----------------------------------------------------------------------------- +template <class T> +class CInstantiateOp +{ +public: + CInstantiateOp() + { + s_pAttrInfo[ CDmAttributeInfo<T>::ATTRIBUTE_TYPE ] = new CDmAttributeOp< T >; + } +}; +static CInstantiateOp<DmUnknownAttribute_t> __s_AttrDmUnknownAttribute_t; + +#define INSTANTIATE_GENERIC_OPS( _className ) \ + template< > class CInstantiateOp< CUtlVector< _className > > \ + { \ + public: \ + CInstantiateOp() \ + { \ + s_pAttrInfo[ CDmAttributeInfo< CUtlVector< _className > >::ATTRIBUTE_TYPE ] = new CDmArrayAttributeOp< _className >; \ + } \ + }; \ + static CInstantiateOp< _className > __s_Attr ## _className; \ + static CInstantiateOp< CUtlVector< _className > > __s_AttrArray ## _className; + +#define DEFINE_ATTRIBUTE_TYPE( _type ) \ + INSTANTIATE_GENERIC_OPS( _type ) \ + ATTRIBUTE_SET_VALUE_ARRAY( _type ) \ + template void CDmAttribute::SetValue< _type >( const _type& value ); \ + template class CDmArrayAttributeOp< _type >; \ + template class CDmaArrayBase< _type, CDmaDataInternal< CUtlVector< _type > > >; \ + template class CDmaArrayBase< _type, CDmaDataExternal< CUtlVector< _type > > >; \ + template class CDmaArrayConstBase< _type, CDmaDataInternal< CUtlVector< _type > > >; \ + template class CDmaArrayConstBase< _type, CDmaDataExternal< CUtlVector< _type > > >; \ + template class CDmaDecorator< _type, CDmaArrayBase< _type, CDmaDataInternal< CUtlVector< _type > > > >; \ + template class CDmrDecorator< _type, CDmaArrayBase< _type, CDmaDataExternal< CUtlVector< _type > > > >; \ + template class CDmrDecoratorConst< _type, CDmaArrayConstBase< _type, CDmaDataExternal< CUtlVector< _type > > > >; + + +DEFINE_ATTRIBUTE_TYPE( int ) +DEFINE_ATTRIBUTE_TYPE( float ) +DEFINE_ATTRIBUTE_TYPE( bool ) +DEFINE_ATTRIBUTE_TYPE( Color ) +DEFINE_ATTRIBUTE_TYPE( Vector2D ) +DEFINE_ATTRIBUTE_TYPE( Vector ) +DEFINE_ATTRIBUTE_TYPE( Vector4D ) +DEFINE_ATTRIBUTE_TYPE( QAngle ) +DEFINE_ATTRIBUTE_TYPE( Quaternion ) +DEFINE_ATTRIBUTE_TYPE( VMatrix ) +DEFINE_ATTRIBUTE_TYPE( CUtlString ) +DEFINE_ATTRIBUTE_TYPE( CUtlBinaryBlock ) +DEFINE_ATTRIBUTE_TYPE( DmObjectId_t ) +DEFINE_ATTRIBUTE_TYPE( DmElementHandle_t ) + +template class CDmaDecorator< CUtlString, CDmaStringArrayBase< CDmaDataInternal< CUtlVector< CUtlString > > > >; +template class CDmrDecorator< CUtlString, CDmaStringArrayBase< CDmaDataExternal< CUtlVector< CUtlString > > > >; diff --git a/datamodel/dmattributeinternal.h b/datamodel/dmattributeinternal.h new file mode 100644 index 0000000..d6c9453 --- /dev/null +++ b/datamodel/dmattributeinternal.h @@ -0,0 +1,75 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= + +#ifndef DMATTRIBUTEINTERNAL_H +#define DMATTRIBUTEINTERNAL_H +#ifdef _WIN32 +#pragma once +#endif + +#include "datamodel/dmattribute.h" +#include "wchar.h" + + +//----------------------------------------------------------------------------- +// Forward declarations: +//----------------------------------------------------------------------------- +class IDataModelFactory; +class CUtlBuffer; +class Vector; +class Color; +class CUtlCharConversion; +class CDmElement; + + +//----------------------------------------------------------------------------- +// Utility class to allow datamodel objects to access private members of CDmAttribute +//----------------------------------------------------------------------------- +class CDmAttributeAccessor +{ +public: + static void OnChanged( CDmAttribute *pAttribute, bool bArrayCountChanged = false, bool bIsTopological = false ) + { + pAttribute->OnChanged( bArrayCountChanged, bIsTopological ); + } + + static void DestroyAttribute( CDmAttribute *pOldAttribute ) + { + CDmAttribute::DestroyAttribute( pOldAttribute ); + } + + static bool MarkDirty( CDmAttribute *pAttribute ) + { + return pAttribute->MarkDirty(); + } +}; + +//----------------------------------------------------------------------------- +// For serialization, set the delimiter rules +//----------------------------------------------------------------------------- +void SetSerializationDelimiter( CUtlCharConversion *pConv ); +void SetSerializationArrayDelimiter( const char *pDelimiter ); + + +//----------------------------------------------------------------------------- +// Skip unserialization for an attribute type (unserialize into a dummy variable) +//----------------------------------------------------------------------------- +bool SkipUnserialize( CUtlBuffer &buf, DmAttributeType_t type ); + + +//----------------------------------------------------------------------------- +// Attribute names/types +//----------------------------------------------------------------------------- +const char *AttributeTypeName( DmAttributeType_t type ); +DmAttributeType_t AttributeType( const char *pAttributeType ); + + +//----------------------------------------------------------------------------- +// returns the number of attributes currently allocated +//----------------------------------------------------------------------------- +int GetAllocatedAttributeCount(); + +#endif // DMATTRIBUTEINTERNAL_H diff --git a/datamodel/dmelement.cpp b/datamodel/dmelement.cpp new file mode 100644 index 0000000..98f21df --- /dev/null +++ b/datamodel/dmelement.cpp @@ -0,0 +1,1420 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= + +#include "datamodel/dmelement.h" +#include "tier0/dbg.h" +#include "datamodel.h" +#include "tier1/utllinkedlist.h" +#include "tier1/utlbuffer.h" +#include "datamodel/dmattribute.h" +#include "Color.h" +#include "mathlib/mathlib.h" +#include "mathlib/vmatrix.h" +#include "datamodel/dmelementfactoryhelper.h" +#include "datamodel/dmattributevar.h" +#include "dmattributeinternal.h" +#include "DmElementFramework.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//----------------------------------------------------------------------------- +// helper class to allow CDmeHandle access to g_pDataModelImp +//----------------------------------------------------------------------------- +void CDmeElementRefHelper::Ref( DmElementHandle_t hElement, bool bStrong ) +{ + g_pDataModelImp->OnElementReferenceAdded( hElement, bStrong ); +} + +void CDmeElementRefHelper::Unref( DmElementHandle_t hElement, bool bStrong ) +{ + g_pDataModelImp->OnElementReferenceRemoved( hElement, bStrong ); +} + +//----------------------------------------------------------------------------- +// element reference struct - containing attribute referrers and handle refcount +//----------------------------------------------------------------------------- +void DmElementReference_t::AddAttribute( CDmAttribute *pAttribute ) +{ + if ( m_attributes.m_hAttribute != DMATTRIBUTE_HANDLE_INVALID ) + { + DmAttributeList_t *pLink = new DmAttributeList_t; // TODO - create a fixed size allocator for these + pLink->m_hAttribute = m_attributes.m_hAttribute; + pLink->m_pNext = m_attributes.m_pNext; + m_attributes.m_pNext = pLink; + } + m_attributes.m_hAttribute = pAttribute->GetHandle(); +} + +void DmElementReference_t::RemoveAttribute( CDmAttribute *pAttribute ) +{ + DmAttributeHandle_t hAttribute = pAttribute->GetHandle(); + if ( m_attributes.m_hAttribute == hAttribute ) + { + DmAttributeList_t *pNext = m_attributes.m_pNext; + if ( pNext ) + { + m_attributes.m_hAttribute = pNext->m_hAttribute; + m_attributes.m_pNext = pNext->m_pNext; + delete pNext; + } + else + { + m_attributes.m_hAttribute = DMATTRIBUTE_HANDLE_INVALID; + } + return; + } + + for ( DmAttributeList_t *pLink = &m_attributes; pLink->m_pNext; pLink = pLink->m_pNext ) + { + DmAttributeList_t *pNext = pLink->m_pNext; + if ( pNext->m_hAttribute == hAttribute ) + { + pLink->m_pNext = pNext->m_pNext; + delete pNext; // TODO - create a fixed size allocator for these + return; + } + } + + Assert( 0 ); +} + + +//----------------------------------------------------------------------------- +// Class factory +//----------------------------------------------------------------------------- +IMPLEMENT_ELEMENT_FACTORY( DmElement, CDmElement ); + + +//----------------------------------------------------------------------------- +// For backward compat: DmeElement -> creates a CDmElement class +//----------------------------------------------------------------------------- +CDmElementFactory< CDmElement > g_CDmeElement_Factory( "DmeElement" ); +CDmElementFactoryHelper g_CDmeElement_Helper( "DmeElement", &g_CDmeElement_Factory, true ); + + +//----------------------------------------------------------------------------- +// Constructor, destructor +//----------------------------------------------------------------------------- +CDmElement::CDmElement( DmElementHandle_t handle, const char *pElementType, const DmObjectId_t &id, const char *pElementName, DmFileId_t fileid ) : + m_ref( handle ), m_Type( g_pDataModel->GetSymbol( pElementType ) ), m_fileId( fileid ), + m_pAttributes( NULL ), m_bDirty( false ), m_bBeingUnserialized( false ), m_bIsAcessible( true ) +{ + MEM_ALLOC_CREDIT(); + g_pDataModelImp->AddElementToFile( m_ref.m_hElement, m_fileId ); + m_Name.InitAndSet( this, "name", pElementName, FATTRIB_TOPOLOGICAL | FATTRIB_STANDARD ); + CopyUniqueId( id, &m_Id ); +} + +CDmElement::~CDmElement() +{ + g_pDataModelImp->RemoveElementFromFile( m_ref.m_hElement, m_fileId ); +} + +void CDmElement::PerformConstruction() +{ + OnConstruction(); +} + +void CDmElement::PerformDestruction() +{ + OnDestruction(); +} + + +//----------------------------------------------------------------------------- +// Purpose: Deletes all attributes +//----------------------------------------------------------------------------- +void CDmElement::Purge() +{ + // Don't create "undo" records for attribute changes here, since + // the entire element is getting deleted... + CDisableUndoScopeGuard guard; + + while ( m_pAttributes ) + { +#if defined( _DEBUG ) + // So you can see what attribute is being destroyed + const char *pName = m_pAttributes->GetName(); + NOTE_UNUSED( pName ); +#endif + CDmAttribute *pNext = m_pAttributes->NextAttribute(); + CDmAttribute::DestroyAttribute( m_pAttributes ); + m_pAttributes = pNext; + } + + g_pDataModelImp->NotifyState( NOTIFY_CHANGE_TOPOLOGICAL ); +} + + +void CDmElement::SetId( const DmObjectId_t &id ) +{ + CopyUniqueId( id, &m_Id ); +} + + +//----------------------------------------------------------------------------- +// RTTI implementation +//----------------------------------------------------------------------------- +void CDmElement::SetTypeSymbol( CUtlSymbol sym ) +{ + m_classType = sym; +} + +bool CDmElement::IsA( UtlSymId_t typeSymbol ) const +{ + // NOTE: This pattern here is used to avoid a zillion virtual function + // calls in the implementation of IsA. The IsA_Implementation is + // all static function calls. + return IsA_Implementation( typeSymbol ); +} + +int CDmElement::GetInheritanceDepth( UtlSymId_t typeSymbol ) const +{ + // NOTE: This pattern here is used to avoid a zillion virtual function + // calls in the implementation of IsA. The IsA_Implementation is + // all static function calls. + return GetInheritanceDepth_Implementation( typeSymbol, 0 ); +} + +// Helper for GetInheritanceDepth +int CDmElement::GetInheritanceDepth( const char *pTypeName ) const +{ + CUtlSymbol typeSymbol = g_pDataModel->GetSymbol( pTypeName ); + return GetInheritanceDepth( typeSymbol ); +} + + +//----------------------------------------------------------------------------- +// Is the element dirty? +//----------------------------------------------------------------------------- +bool CDmElement::IsDirty() const +{ + return m_bDirty; +} + +void CDmElement::MarkDirty( bool bDirty ) +{ + if ( bDirty && !m_bDirty ) + { + g_pDmElementFrameworkImp->AddElementToDirtyList( m_ref.m_hElement ); + } + m_bDirty = bDirty; +} + +void CDmElement::MarkAttributesClean() +{ + for ( CDmAttribute *pAttr = m_pAttributes; pAttr; pAttr = pAttr->NextAttribute() ) + { + // No Undo for flag changes + pAttr->RemoveFlag( FATTRIB_DIRTY ); + } +} + +void CDmElement::MarkBeingUnserialized( bool beingUnserialized ) +{ + if ( m_bBeingUnserialized != beingUnserialized ) + { + m_bBeingUnserialized = beingUnserialized; + + // After we finish unserialization, call OnAttributeChanged; assume everything changed + if ( !beingUnserialized ) + { + for( CDmAttribute *pAttribute = m_pAttributes; pAttribute; pAttribute = pAttribute->NextAttribute() ) + { + pAttribute->OnUnserializationFinished(); + } + + // loop referencing attributes, and call OnAttributeChanged on them as well + if ( m_ref.m_attributes.m_hAttribute != DMATTRIBUTE_HANDLE_INVALID ) + { + for ( DmAttributeList_t *pAttrLink = &m_ref.m_attributes; pAttrLink; pAttrLink = pAttrLink->m_pNext ) + { + CDmAttribute *pAttr = g_pDataModel->GetAttribute( pAttrLink->m_hAttribute ); + if ( !pAttr || pAttr->GetOwner()->GetFileId() == m_fileId ) + continue; // attributes in this file will already have OnAttributeChanged called on them + + pAttr->OnUnserializationFinished(); + } + } + + // Mostly used for backward compatibility reasons + CDmElement *pElement = g_pDataModel->GetElement( m_ref.m_hElement ); + pElement->OnElementUnserialized(); + + // Force a resolve also, and set it up to remove it from the dirty list + // after unserialization is complete + pElement->Resolve(); + MarkDirty( false ); + MarkAttributesClean(); + g_pDataModelImp->NotifyState( NOTIFY_CHANGE_TOPOLOGICAL ); + } + } +} + +bool CDmElement::IsBeingUnserialized() const +{ + return m_bBeingUnserialized; +} + + +// Should only be called from datamodel, who will take care of changing the fileset entry as well +void CDmElement::ChangeHandle( DmElementHandle_t handle ) +{ + m_ref.m_hElement = handle; +} + +// returns element reference struct w/ list of referrers and handle count +DmElementReference_t *CDmElement::GetReference() +{ + return &m_ref; +} + +void CDmElement::SetReference( const DmElementReference_t &ref ) +{ + Assert( !m_ref.IsWeaklyReferenced() ); + m_ref = ref; +} + + +int CDmElement::EstimateMemoryUsage( CUtlHash< DmElementHandle_t > &visited, TraversalDepth_t depth, int *pCategories ) +{ + if ( visited.Find( m_ref.m_hElement ) != visited.InvalidHandle() ) + return 0; + visited.Insert( m_ref.m_hElement ); + + int nDataModelUsage = g_pDataModelImp->EstimateMemoryOverhead( ); + int nReferenceUsage = m_ref.EstimateMemoryOverhead(); + CDmElement *pElement = g_pDataModel->GetElement( m_ref.m_hElement ); + int nInternalUsage = sizeof( *this ) - sizeof( CUtlString ); // NOTE: The utlstring is the 'name' attribute var + int nOuterUsage = pElement->AllocatedSize() - nInternalUsage; + Assert( nOuterUsage >= 0 ); + + if ( pCategories ) + { + pCategories[MEMORY_CATEGORY_OUTER] += nOuterUsage; + pCategories[MEMORY_CATEGORY_DATAMODEL] += nDataModelUsage; + pCategories[MEMORY_CATEGORY_REFERENCES] += nReferenceUsage; + pCategories[MEMORY_CATEGORY_ELEMENT_INTERNAL] += nInternalUsage; + } + + int nAttributeDataUsage = 0; + for ( CDmAttribute *pAttr = m_pAttributes; pAttr; pAttr = pAttr->NextAttribute() ) + { + nAttributeDataUsage += pAttr->EstimateMemoryUsageInternal( visited, depth, pCategories ); + } + + return nInternalUsage + nDataModelUsage + nReferenceUsage + nOuterUsage + nAttributeDataUsage; +} + +//----------------------------------------------------------------------------- +// these functions are here for the mark and sweep algorithm for deleting orphaned subtrees +// it's conservative, so it can return true for orphaned elements but false really means it isn't accessible +//----------------------------------------------------------------------------- +bool CDmElement::IsAccessible() const +{ + return m_bIsAcessible; +} + +void CDmElement::MarkAccessible( bool bAccessible ) +{ + m_bIsAcessible = bAccessible; +} + +void CDmElement::MarkAccessible( TraversalDepth_t depth /* = TD_ALL */ ) +{ + if ( m_bIsAcessible ) + return; + + m_bIsAcessible = true; + + for ( const CDmAttribute *pAttr = FirstAttribute(); pAttr != NULL; pAttr = pAttr->NextAttribute() ) + { + if ( !ShouldTraverse( pAttr, depth ) ) + continue; + + if ( pAttr->GetType() == AT_ELEMENT ) + { + CDmElement *pChild = pAttr->GetValueElement<CDmElement>(); + if ( !pChild ) + continue; + pChild->MarkAccessible( depth ); + } + 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; + pChild->MarkAccessible( depth ); + } + } + } +} + +//----------------------------------------------------------------------------- +// returns the first path to the element found traversing all element/element array attributes - not necessarily the shortest +//----------------------------------------------------------------------------- + +// do we want a true visited set to avoid retraversing the same subtree over and over again? +// for most dag trees, it's probably a perf loss, since multiple instances are rare, (and searching the visited set costs log(n)) +// for trees that include channels, it's probably a perf win, since many channels link into the same element most of the time +bool CDmElement::FindElement( const CDmElement *pElement, CUtlVector< ElementPathItem_t > &elementPath, TraversalDepth_t depth ) const +{ + if ( this == pElement ) + return true; + + ElementPathItem_t search( GetHandle() ); + if ( elementPath.Find( search ) != elementPath.InvalidIndex() ) + return false; + + int idx = elementPath.AddToTail( search ); + ElementPathItem_t &pathItem = elementPath[ idx ]; + + for ( const CDmAttribute *pAttr = FirstAttribute(); pAttr != NULL; pAttr = pAttr->NextAttribute() ) + { + if ( !ShouldTraverse( pAttr, depth ) ) + continue; + + if ( pAttr->GetType() == AT_ELEMENT ) + { + pathItem.hAttribute = const_cast< CDmAttribute* >( pAttr )->GetHandle(); + pathItem.nIndex = -1; + + CDmElement *pChild = pAttr->GetValueElement<CDmElement>(); + if ( pChild && pChild->FindElement( pElement, elementPath, depth ) ) + return true; + } + else if ( pAttr->GetType() == AT_ELEMENT_ARRAY ) + { + pathItem.hAttribute = const_cast< CDmAttribute* >( pAttr )->GetHandle(); + + CDmrElementArrayConst<> elementArrayAttr( pAttr ); + int nChildren = elementArrayAttr.Count(); + for ( int i = 0; i < nChildren; ++i ) + { + pathItem.nIndex = i; + + CDmElement *pChild = elementArrayAttr[ i ]; + if ( pChild && pChild->FindElement( pElement, elementPath, depth ) ) + return true; + } + } + } + + elementPath.Remove( idx ); + return false; +} + +bool CDmElement::FindReferer( DmElementHandle_t hElement, CUtlVector< ElementPathItem_t > &elementPath, TraversalDepth_t depth /* = TD_SHALLOW */ ) const +{ + DmElementHandle_t hThis = GetHandle(); + + DmAttributeReferenceIterator_t hAttr = g_pDataModel->FirstAttributeReferencingElement( hThis ); + for ( ; hAttr != DMATTRIBUTE_REFERENCE_ITERATOR_INVALID; hAttr = g_pDataModel->NextAttributeReferencingElement( hAttr ) ) + { + CDmAttribute *pAttr = g_pDataModel->GetAttribute( hAttr ); + if ( !pAttr ) + continue; + + if ( !ShouldTraverse( pAttr, depth ) ) + continue; + + DmElementHandle_t hOwner = pAttr->GetOwner()->GetHandle(); + if ( elementPath.Find( ElementPathItem_t( hOwner ) ) != elementPath.InvalidIndex() ) + return false; + + int i = elementPath.AddToTail(); + ElementPathItem_t &item = elementPath[ i ]; + item.hElement = hOwner; + item.hAttribute = pAttr->GetHandle(); + item.nIndex = -1; + if ( pAttr->GetType() == AT_ELEMENT_ARRAY ) + { + CDmrElementArray<> array( pAttr ); + item.nIndex = array.Find( hThis ); + } + + if ( hOwner == hElement ) + return true; + + CDmElement *pOwner = GetElement< CDmElement >( hOwner ); + if ( pOwner->FindReferer( hElement, elementPath, depth ) ) + return true; + + elementPath.Remove( i ); + } + + return false; +} + +void CDmElement::RemoveAllReferencesToElement( CDmElement *pElement ) +{ + for ( CDmAttribute *pAttr = FirstAttribute(); pAttr != NULL; pAttr = pAttr->NextAttribute() ) + { + if ( pAttr->GetType() == AT_ELEMENT ) + { + CDmElement *pChild = pAttr->GetValueElement<CDmElement>(); + if ( pChild == pElement ) + { + pAttr->SetValue( DMELEMENT_HANDLE_INVALID ); + } + } + else if ( pAttr->GetType() == AT_ELEMENT_ARRAY ) + { + CDmrElementArray<> elementArrayAttr( pAttr ); + int nChildren = elementArrayAttr.Count(); + for ( int i = nChildren - 1; i >= 0; --i ) + { + CDmElement *pChild = elementArrayAttr[ i ]; + if ( pChild == pElement ) + { + elementArrayAttr.Remove( i ); + } + } + } + } +} + +int CDmElement::EstimateMemoryUsage( TraversalDepth_t depth /* = TD_DEEP */ ) +{ + return g_pDataModel->EstimateMemoryUsage( GetHandle(), depth ); +} + + +//----------------------------------------------------------------------------- +// +// Implementation Undo for copied objects +// +//----------------------------------------------------------------------------- +CDmElement* CDmElement::CopyInternal( TraversalDepth_t depth /* = TD_DEEP */ ) const +{ + CDmElement *pCopy = GetElement< CDmElement >( g_pDataModel->CreateElement( GetType(), GetName(), GetFileId() ) ); + if ( pCopy ) + { + CopyAttributesTo( pCopy, depth ); + } + return pCopy; +} + +//----------------------------------------------------------------------------- +// Copy - implementation of shallow and deep element copying +// - allows attributes to be marked to always (or never) copy +//----------------------------------------------------------------------------- +void CDmElement::CopyAttributesTo( CDmElement *pCopy, TraversalDepth_t depth ) const +{ + CDisableUndoScopeGuard sg; + + CUtlMap< DmElementHandle_t, DmElementHandle_t, int > refmap( DefLessFunc( DmElementHandle_t ) ); + CopyAttributesTo( pCopy, refmap, depth ); + + CUtlHashFast< DmElementHandle_t > visited; + uint nPow2Size = 1; + while( nPow2Size < refmap.Count() ) + { + nPow2Size <<= 1; + } + visited.Init( nPow2Size ); + pCopy->FixupReferences( visited, refmap, depth ); +} + + +//----------------------------------------------------------------------------- +// Copy an element-type attribute +//----------------------------------------------------------------------------- +void CDmElement::CopyElementAttribute( const CDmAttribute *pSrcAttr, CDmAttribute *pDestAttr, CRefMap &refmap, TraversalDepth_t depth ) const +{ + DmElementHandle_t hSrc = pSrcAttr->GetValue<DmElementHandle_t>(); + CDmElement *pSrc = GetElement< CDmElement >( hSrc ); + + if ( pSrc == NULL ) + { + pDestAttr->SetValue( DMELEMENT_HANDLE_INVALID ); + return; + } + + if ( pSrc->IsShared() ) + { + pDestAttr->SetValue( pSrcAttr ); + return; + } + + int idx = refmap.Find( hSrc ); + if ( idx != refmap.InvalidIndex() ) + { + pDestAttr->SetValue( refmap[ idx ] ); + return; + } + + if ( ShouldTraverse( pSrcAttr, depth ) ) + { + DmElementHandle_t hDest = pDestAttr->GetValue<DmElementHandle_t>(); + if ( hDest == DMELEMENT_HANDLE_INVALID ) + { + hDest = g_pDataModel->CreateElement( pSrc->GetType(), pSrc->GetName(), pSrc->GetFileId() ); + pDestAttr->SetValue( hDest ); + } + + CDmElement *pDest = GetElement< CDmElement >( hDest ); + pSrc->CopyAttributesTo( pDest, refmap, depth ); + return; + } + + pDestAttr->SetValue( pSrcAttr ); +} + + +//----------------------------------------------------------------------------- +// Copy an element array-type attribute +//----------------------------------------------------------------------------- +void CDmElement::CopyElementArrayAttribute( const CDmAttribute *pAttr, CDmAttribute *pCopyAttr, CRefMap &refmap, TraversalDepth_t depth ) const +{ + CDmrElementArrayConst<> srcAttr( pAttr ); + CDmrElementArray<> destAttr( pCopyAttr ); + destAttr.RemoveAll(); // automatically releases each handle + + bool bCopy = ShouldTraverse( pAttr, depth ); + + int n = srcAttr.Count(); + destAttr.EnsureCapacity( n ); + + for ( int i = 0; i < n; ++i ) + { + DmElementHandle_t hSrc = srcAttr.GetHandle( i ); + CDmElement *pSrc = srcAttr[i]; + + if ( pSrc == NULL ) + { + destAttr.AddToTail( DMELEMENT_HANDLE_INVALID ); + continue; + } + + if ( pSrc->IsShared() ) + { + destAttr.AddToTail( srcAttr[ i ] ); + continue; + } + + int idx = refmap.Find( hSrc ); + if ( idx != refmap.InvalidIndex() ) + { + destAttr.AddToTail( refmap[ idx ] ); + continue; + } + + if ( bCopy ) + { + DmElementHandle_t hDest = g_pDataModel->CreateElement( pSrc->GetType(), pSrc->GetName(), pSrc->GetFileId() ); + destAttr.AddToTail( hDest ); + CDmElement *pDest = GetElement< CDmElement >( hDest ); + pSrc->CopyAttributesTo( pDest, refmap, depth ); + continue; + } + + destAttr.AddToTail( srcAttr[ i ] ); + } +} + + +//----------------------------------------------------------------------------- +// internal recursive copy method +// builds refmap of old element's handle -> copy's handle, and uses it to fixup references +//----------------------------------------------------------------------------- +void CDmElement::CopyAttributesTo( CDmElement *pCopy, CRefMap &refmap, TraversalDepth_t depth ) const +{ + refmap.Insert( this->GetHandle(), pCopy->GetHandle() ); + + // loop attrs, copying - element (and element array) attrs can be marked to always copy deep(er) + for ( const CDmAttribute *pAttr = FirstAttribute(); pAttr != NULL; pAttr = pAttr->NextAttribute() ) + { + DmAttributeType_t type = pAttr->GetType(); + const char *pAttrName = pAttr->GetName(); + CDmAttribute *pCopyAttr = pCopy->GetAttribute( pAttrName ); + + if ( pCopyAttr == NULL ) + { + pCopyAttr = pCopy->AddAttribute( pAttrName, type ); + + int flags = pAttr->GetFlags(); + Assert( ( flags & FATTRIB_EXTERNAL ) == 0 ); + flags &= ~FATTRIB_EXTERNAL; + + pCopyAttr->ClearFlags(); + pCopyAttr->AddFlag( flags ); + } + + // Temporarily remove the read-only flag from the copy while we copy into it + bool bReadOnly = pCopyAttr->IsFlagSet( FATTRIB_READONLY ); + if ( bReadOnly ) + { + pCopyAttr->RemoveFlag( FATTRIB_READONLY ); + } + + if ( type == AT_ELEMENT ) + { + CopyElementAttribute( pAttr, pCopyAttr, refmap, depth ); + } + else if ( type == AT_ELEMENT_ARRAY ) + { + CopyElementArrayAttribute( pAttr, pCopyAttr, refmap, depth ); + } + else + { + pCopyAttr->SetValue( pAttr ); + } + + if ( bReadOnly ) + { + pCopyAttr->AddFlag( FATTRIB_READONLY ); + } + } +} + +//----------------------------------------------------------------------------- +// FixupReferences +// fixes up any references that Copy wasn't able to figure out +// example: +// during a shallow copy, a channel doesn't copy its target element, +// but the targets parent might decide to copy it, (later on in the travesal) +// so the channel needs to change to refer to the copy +//----------------------------------------------------------------------------- +void CDmElement::FixupReferences( CUtlHashFast< DmElementHandle_t > &visited, const CRefMap &refmap, TraversalDepth_t depth ) +{ + if ( visited.Find( GetHandle() ) != visited.InvalidHandle() ) + return; + + visited.Insert( GetHandle(), DMELEMENT_HANDLE_INVALID ); // ignore data arguement - we're just using it as a set + + // loop attrs, copying - element (and element array) attrs can be marked to always copy deep(er) + for ( CDmAttribute *pAttr = FirstAttribute(); pAttr != NULL; pAttr = pAttr->NextAttribute() ) + { + DmAttributeType_t type = pAttr->GetType(); + bool bCopy = ShouldTraverse( pAttr, depth ); + + if ( type == AT_ELEMENT ) + { + DmElementHandle_t handle = pAttr->GetValue<DmElementHandle_t>(); + int idx = refmap.Find( handle ); + if ( idx == refmap.InvalidIndex() ) + { + CDmElement *pElement = GetElement< CDmElement >( handle ); + if ( pElement == NULL || !bCopy ) + continue; + + pElement->FixupReferences( visited, refmap, depth ); + } + else + { + pAttr->SetValue( refmap[ idx ] ); + } + } + else if ( type == AT_ELEMENT_ARRAY ) + { + CDmrElementArray<> attrArray( pAttr ); + int nElements = attrArray.Count(); + for ( int i = 0; i < nElements; ++i ) + { + DmElementHandle_t handle = attrArray.GetHandle( i ); + int idx = refmap.Find( handle ); + if ( idx == refmap.InvalidIndex() ) + { + CDmElement *pElement = GetElement< CDmElement >( handle ); + if ( pElement == NULL || !bCopy ) + continue; + + pElement->FixupReferences( visited, refmap, depth ); + } + else + { + attrArray.SetHandle( i, refmap[ idx ] ); + } + } + } + } +} + + +//----------------------------------------------------------------------------- +// Change type (only possible when versioning file formats) +//----------------------------------------------------------------------------- +void CDmElement::SetType( const char *pType ) +{ + if ( !g_pDataModelImp->IsCreatingUntypedElements() ) + { + Warning( "Unable to set type unless you're creating untyped elements!\n" ); + return; + } + + m_Type = g_pDataModel->GetSymbol( pType ); +} + + +//----------------------------------------------------------------------------- +// owning file +//----------------------------------------------------------------------------- +void CDmElement::SetFileId( DmFileId_t fileid ) +{ + g_pDataModelImp->RemoveElementFromFile( m_ref.m_hElement, m_fileId ); + m_fileId = fileid; + g_pDataModelImp->AddElementToFile( m_ref.m_hElement, fileid ); +} + + +//----------------------------------------------------------------------------- +// recursively set fileid's, with option to only change elements in the matched file +//----------------------------------------------------------------------------- +void CDmElement::SetFileId( DmFileId_t fileid, TraversalDepth_t depth, bool bOnlyIfMatch /* = false */ ) +{ + if ( depth != TD_NONE ) + { + CUtlHashFast< DmElementHandle_t > visited; + visited.Init( 4096 ); // this will make visited behave reasonably (perf-wise) for trees w/ around 4k elements in them + SetFileId_R( visited, fileid, depth, GetFileId(), bOnlyIfMatch ); + } + else + { + SetFileId( fileid ); + } +} + +void CDmElement::SetFileId_R( CUtlHashFast< DmElementHandle_t > &visited, DmFileId_t fileid, TraversalDepth_t depth, DmFileId_t match, bool bOnlyIfMatch ) +{ + if ( bOnlyIfMatch && match != GetFileId() ) + return; + + if ( visited.Find( GetHandle() ) != visited.InvalidHandle() ) + return; + + visited.Insert( GetHandle(), DMELEMENT_HANDLE_INVALID ); // ignore data arguement - we're just using it as a set + + SetFileId( fileid ); + + for ( CDmAttribute *pAttr = FirstAttribute(); pAttr != NULL; pAttr = pAttr->NextAttribute() ) + { + DmAttributeType_t type = pAttr->GetType(); + if ( !ShouldTraverse( pAttr, depth ) ) + continue; + + if ( type == AT_ELEMENT ) + { + CDmElement *pElement = pAttr->GetValueElement<CDmElement>(); + if ( pElement ) + { + pElement->SetFileId_R( visited, fileid, depth, match, bOnlyIfMatch ); + } + } + else if ( type == AT_ELEMENT_ARRAY ) + { + CDmrElementArray<> attrArray( pAttr ); + int nElements = attrArray.Count(); + for ( int i = 0; i < nElements; ++i ) + { + CDmElement *pElement = attrArray[ i ]; + if ( pElement ) + { + pElement->SetFileId_R( visited, fileid, depth, match, bOnlyIfMatch ); + } + } + } + } +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +DmElementHandle_t CDmElement::GetHandle() const +{ + Assert( m_ref.m_hElement != DMELEMENT_HANDLE_INVALID ); + return m_ref.m_hElement; +} + + +//----------------------------------------------------------------------------- +// Iteration +//----------------------------------------------------------------------------- +int CDmElement::AttributeCount() const +{ + int nAttrs = 0; + for ( CDmAttribute *pAttr = m_pAttributes; pAttr; pAttr = pAttr->NextAttribute() ) + { + ++nAttrs; + } + return nAttrs; +} + +CDmAttribute* CDmElement::FirstAttribute() +{ + return m_pAttributes; +} + +const CDmAttribute* CDmElement::FirstAttribute() const +{ + return m_pAttributes; +} + +bool CDmElement::HasAttribute( const char *pAttributeName, DmAttributeType_t type ) const +{ + CDmAttribute *pAttribute = FindAttribute( pAttributeName ); + if ( !pAttribute ) + return false; + + return ( type == AT_UNKNOWN || ( pAttribute->GetType() == type ) ); +} + + +//----------------------------------------------------------------------------- +// +// Implementation Undo for CDmAttributeTyped +// +//----------------------------------------------------------------------------- +class CUndoAttributeRemove : public CUndoElement +{ + typedef CUndoElement BaseClass; +public: + CUndoAttributeRemove( CDmElement *pElement, CDmAttribute *pOldAttribute, const char *attributeName, DmAttributeType_t type ) + : BaseClass( "CUndoAttributeRemove" ), + m_pElement( pElement ), + m_pOldAttribute( pOldAttribute ), + m_symAttribute( attributeName ), + m_Type( type ) + { + Assert( pElement && pElement->GetFileId() != DMFILEID_INVALID ); + } + + ~CUndoAttributeRemove() + { + // Kill old version... + CDmAttributeAccessor::DestroyAttribute( m_pOldAttribute ); + } + + virtual void Undo() + { + // Add it back in w/o any data + CDmAttribute *pAtt = m_pElement->AddAttribute( m_symAttribute.String(), m_Type ); + if ( pAtt ) + { + // Copy data from old version + pAtt->SetValue( m_pOldAttribute ); + } + } + virtual void Redo() + { + m_pElement->RemoveAttribute( m_symAttribute.String() ); + } + +private: + CDmElement *m_pElement; + CUtlSymbol m_symAttribute; + DmAttributeType_t m_Type; + CDmAttribute *m_pOldAttribute; +}; + + +//----------------------------------------------------------------------------- +// Containing object +//----------------------------------------------------------------------------- +void CDmElement::RemoveAttributeByPtrNoDelete( CDmAttribute *ptr ) +{ + for ( CDmAttribute **ppAttr = &m_pAttributes; *ppAttr; ppAttr = ( *ppAttr )->GetNextAttributeRef() ) + { + if ( ptr == *ppAttr ) + { + MarkDirty(); + + ptr->InvalidateHandle(); + *ppAttr = ( *ppAttr )->NextAttribute(); + + g_pDataModelImp->NotifyState( NOTIFY_CHANGE_TOPOLOGICAL ); + return; + } + } +} + + +//----------------------------------------------------------------------------- +// Attribute removal +//----------------------------------------------------------------------------- +void CDmElement::RemoveAttribute( CDmAttribute **pAttrRef ) +{ + CDmAttribute *pAttrToDelete = *pAttrRef; + + // Removal of external attributes is verboten + Assert( !pAttrToDelete->IsFlagSet( FATTRIB_EXTERNAL ) ); + if( pAttrToDelete->IsFlagSet( FATTRIB_EXTERNAL ) ) + return; + + MarkDirty(); + + // UNDO Hook + bool storedbyundo = false; + if ( g_pDataModel->UndoEnabledForElement( this ) ) + { + MEM_ALLOC_CREDIT_CLASS(); + CUndoAttributeRemove *pUndo = new CUndoAttributeRemove( this, pAttrToDelete, pAttrToDelete->GetName(), pAttrToDelete->GetType() ); + g_pDataModel->AddUndoElement( pUndo ); + storedbyundo = true; + } + + *pAttrRef = ( *pAttrRef )->NextAttribute(); + + if ( !storedbyundo ) + { + CDmAttribute::DestroyAttribute( pAttrToDelete ); + } + g_pDataModelImp->NotifyState( NOTIFY_CHANGE_TOPOLOGICAL ); +} + + +void CDmElement::RemoveAttribute( const char *pAttributeName ) +{ + UtlSymId_t find = g_pDataModel->GetSymbol( pAttributeName ); + for ( CDmAttribute **ppAttr = &m_pAttributes; *ppAttr; ppAttr = ( *ppAttr )->GetNextAttributeRef() ) + { + if ( find == ( *ppAttr )->GetNameSymbol() ) + { + RemoveAttribute( ppAttr ); + return; + } + } +} + +void CDmElement::RemoveAttributeByPtr( CDmAttribute *pAttribute ) +{ + Assert( pAttribute ); + for ( CDmAttribute **ppAttr = &m_pAttributes; *ppAttr; ppAttr = ( *ppAttr )->GetNextAttributeRef() ) + { + if ( pAttribute == *ppAttr ) + { + RemoveAttribute( ppAttr ); + return; + } + } +} + + +//----------------------------------------------------------------------------- +// Sets an attribute from a string +//----------------------------------------------------------------------------- +void CDmElement::SetValueFromString( const char *pAttributeName, const char *pValue ) +{ + CDmAttribute *pAttribute = FindAttribute( pAttributeName ); + if ( pAttribute ) + { + pAttribute->SetValueFromString( pValue ); + } +} + + +//----------------------------------------------------------------------------- +// Writes an attribute as a string +//----------------------------------------------------------------------------- +const char *CDmElement::GetValueAsString( const char *pAttributeName, char *pBuffer, size_t nBufLen ) const +{ + Assert( pBuffer ); + + const CDmAttribute *pAttribute = FindAttribute( pAttributeName ); + if ( pAttribute ) + return pAttribute->GetValueAsString( pBuffer, nBufLen ); + + pBuffer[ 0 ] = 0; + return pBuffer; +} + + +//----------------------------------------------------------------------------- +// +// Implementation Undo for CDmAttributeTyped +// +//----------------------------------------------------------------------------- +class CUndoAttributeAdd : public CUndoElement +{ + typedef CUndoElement BaseClass; + +public: + CUndoAttributeAdd( CDmElement *pElement, const char *attributeName ) + : BaseClass( "CUndoAttributeAdd" ), + m_hElement( pElement->GetHandle() ), + m_symAttribute( attributeName ), + m_pAttribute( NULL ), + m_bHoldingPtr( false ) + { + Assert( pElement && pElement->GetFileId() != DMFILEID_INVALID ); + } + + ~CUndoAttributeAdd() + { + if ( m_bHoldingPtr && m_pAttribute ) + { + CDmAttributeAccessor::DestroyAttribute( m_pAttribute ); + m_pAttribute = NULL; + } + } + + void SetAttributePtr( CDmAttribute *pAttribute ) + { + m_pAttribute = pAttribute; + } + + virtual void Undo() + { + if ( GetElement() ) + { + CDmeElementAccessor::RemoveAttributeByPtrNoDelete( GetElement(), m_pAttribute ); + m_bHoldingPtr = true; + } + } + + virtual void Redo() + { + if ( !GetElement() ) + return; + + CDmeElementAccessor::AddAttributeByPtr( GetElement(), m_pAttribute ); + m_bHoldingPtr = false; + } + + virtual const char *GetDesc() + { + static char buf[ 128 ]; + + const char *base = BaseClass::GetDesc(); + Q_snprintf( buf, sizeof( buf ), "%s(%s)", base, m_symAttribute.String() ); + return buf; + } + +private: + CDmElement *GetElement() + { + return g_pDataModel->GetElement( m_hElement ); + } + + DmElementHandle_t m_hElement; + CUtlSymbol m_symAttribute; + CDmAttribute *m_pAttribute; + bool m_bHoldingPtr; +}; + + +//----------------------------------------------------------------------------- +// Adds, removes attributes +//----------------------------------------------------------------------------- +void CDmElement::AddAttributeByPtr( CDmAttribute *ptr ) +{ + MarkDirty(); + + for ( CDmAttribute *pAttr = m_pAttributes; pAttr; pAttr = pAttr->NextAttribute() ) + { + if ( pAttr == ptr ) + { + Assert( 0 ); + return; + } + } + + *( ptr->GetNextAttributeRef() ) = m_pAttributes; + m_pAttributes = ptr; + + g_pDataModelImp->NotifyState( NOTIFY_CHANGE_TOPOLOGICAL ); +} + +CDmAttribute *CDmElement::CreateAttribute( const char *pAttributeName, DmAttributeType_t type ) +{ + Assert( !HasAttribute( pAttributeName ) ); + MarkDirty( ); + + // UNDO Hook + CUndoAttributeAdd *pUndo = NULL; + if ( g_pDataModel->UndoEnabledForElement( this ) ) + { + MEM_ALLOC_CREDIT_CLASS(); + pUndo = new CUndoAttributeAdd( this, pAttributeName ); + g_pDataModel->AddUndoElement( pUndo ); + } + + CDmAttribute *pAttribute = NULL; + { + CDisableUndoScopeGuard guard; + pAttribute = CDmAttribute::CreateAttribute( this, type, pAttributeName ); + *( pAttribute->GetNextAttributeRef() ) = m_pAttributes; + m_pAttributes = pAttribute; + if ( pUndo ) + { + pUndo->SetAttributePtr( pAttribute ); + } + g_pDataModelImp->NotifyState( NOTIFY_CHANGE_TOPOLOGICAL ); + } + return pAttribute; +} + +CDmAttribute* CDmElement::AddExternalAttribute( const char *pAttributeName, DmAttributeType_t type, void *pMemory ) +{ + MarkDirty( ); + + // Add will only add the attribute doesn't already exist + if ( HasAttribute( pAttributeName ) ) + { + Assert( 0 ); + return NULL; + } + + // UNDO Hook + CUndoAttributeAdd *pUndo = NULL; + if ( g_pDataModel->UndoEnabledForElement( this ) ) + { + MEM_ALLOC_CREDIT_CLASS(); + pUndo = new CUndoAttributeAdd( this, pAttributeName ); + g_pDataModel->AddUndoElement( pUndo ); + } + + CDmAttribute *pAttribute = NULL; + { + CDisableUndoScopeGuard guard; + pAttribute = CDmAttribute::CreateExternalAttribute( this, type, pAttributeName, pMemory ); + *( pAttribute->GetNextAttributeRef() ) = m_pAttributes; + m_pAttributes = pAttribute; + if ( pUndo ) + { + pUndo->SetAttributePtr( pAttribute ); + } + g_pDataModelImp->NotifyState( NOTIFY_CHANGE_TOPOLOGICAL ); + } + return pAttribute; +} + + +//----------------------------------------------------------------------------- +// Find an attribute in the list +//----------------------------------------------------------------------------- +CDmAttribute *CDmElement::FindAttribute( const char *pAttributeName ) const +{ + UtlSymId_t find = g_pDataModel->GetSymbol( pAttributeName ); + + for ( CDmAttribute *pAttr = m_pAttributes; pAttr; pAttr = pAttr->NextAttribute() ) + { + if ( find == pAttr->GetNameSymbol() ) + return pAttr; + } + + return NULL; +} + + +//----------------------------------------------------------------------------- +// attribute renaming +//----------------------------------------------------------------------------- +void CDmElement::RenameAttribute( const char *pAttributeName, const char *pNewName ) +{ + CDmAttribute *pAttr = FindAttribute( pAttributeName ); + if ( pAttr ) + { + pAttr->SetName( pNewName ); + } +} + + +//----------------------------------------------------------------------------- +// allows elements to chain OnAttributeChanged up to their parents (or at least, referrers) +//----------------------------------------------------------------------------- +void InvokeOnAttributeChangedOnReferrers( DmElementHandle_t hElement, CDmAttribute *pChangedAttr ) +{ + DmAttributeReferenceIterator_t ai = g_pDataModel->FirstAttributeReferencingElement( hElement ); + for ( ; ai != DMATTRIBUTE_REFERENCE_ITERATOR_INVALID; ai = g_pDataModel->NextAttributeReferencingElement( ai ) ) + { + CDmAttribute *pAttr = g_pDataModel->GetAttribute( ai ); + Assert( pAttr ); + if ( !pAttr ) + continue; + + if ( pAttr->IsFlagSet( FATTRIB_NEVERCOPY ) ) + continue; + + CDmElement *pOwner = pAttr->GetOwner(); + Assert( pOwner ); + if ( !pOwner ) + continue; + + pOwner->OnAttributeChanged( pChangedAttr ); + } +} + + +//----------------------------------------------------------------------------- +// Destroys an element and all elements it refers to via attributes +//----------------------------------------------------------------------------- +void DestroyElement( CDmElement *pElement, TraversalDepth_t depth ) +{ + if ( !pElement ) + return; + + CDmAttribute* pAttribute; + for ( pAttribute = pElement->FirstAttribute(); pAttribute; pAttribute = pAttribute->NextAttribute() ) + { + if ( !ShouldTraverse( pAttribute, depth ) ) + continue; + + switch( pAttribute->GetType() ) + { + case AT_ELEMENT: + { + CDmElement *pChild = pAttribute->GetValueElement<CDmElement>(); + DestroyElement( pChild, depth ); + } + break; + + case AT_ELEMENT_ARRAY: + { + CDmrElementArray<> array( pAttribute ); + int nElements = array.Count(); + for ( int i = 0; i < nElements; ++i ) + { + CDmElement *pChild = array[ i ]; + DestroyElement( pChild, depth ); + } + } + break; + } + } + + g_pDataModel->DestroyElement( pElement->GetHandle() ); +} + +//----------------------------------------------------------------------------- +// copy groups of elements together so that references between them are maintained +//----------------------------------------------------------------------------- +void CopyElements( const CUtlVector< CDmElement* > &from, CUtlVector< CDmElement* > &to, TraversalDepth_t depth /* = TD_DEEP */ ) +{ + CDisableUndoScopeGuard sg; + + CUtlMap< DmElementHandle_t, DmElementHandle_t, int > refmap( DefLessFunc( DmElementHandle_t ) ); + + int c = from.Count(); + for ( int i = 0; i < c; ++i ) + { + CDmElement *pFrom = from[ i ]; + CDmElement *pCopy = GetElement< CDmElement >( g_pDataModel->CreateElement( pFrom->GetType(), pFrom->GetName(), pFrom->GetFileId() ) ); + if ( pCopy ) + { + pFrom->CopyAttributesTo( pCopy, refmap, depth ); + } + to.AddToTail( pCopy ); + } + + CUtlHashFast< DmElementHandle_t > visited; + uint nPow2Size = 1; + while( nPow2Size < refmap.Count() ) + { + nPow2Size <<= 1; + } + visited.Init( nPow2Size ); + + for ( int i = 0; i < c; ++i ) + { + to[ i ]->FixupReferences( visited, refmap, depth ); + } +} + + +//----------------------------------------------------------------------------- +// +// element-specific unique name generation methods +// +//----------------------------------------------------------------------------- + +struct ElementArrayNameAccessor +{ + ElementArrayNameAccessor( const CUtlVector< DmElementHandle_t > &array ) : m_array( array ) {} + int Count() const + { + return m_array.Count(); + } + const char *operator[]( int i ) const + { + CDmElement *pElement = GetElement< CDmElement >( m_array[ i ] ); + return pElement ? pElement->GetName() : NULL; + } +private: + const CUtlVector< DmElementHandle_t > &m_array; +}; + +// returns startindex if none found, 2 if only "prefix" found, and n+1 if "prefixn" found +int GenerateUniqueNameIndex( const char *prefix, const CUtlVector< DmElementHandle_t > &array, int startindex ) +{ + return V_GenerateUniqueNameIndex( prefix, ElementArrayNameAccessor( array ), startindex ); +} + +bool GenerateUniqueName( char *name, int memsize, const char *prefix, const CUtlVector< DmElementHandle_t > &array ) +{ + return V_GenerateUniqueName( name, memsize, prefix, ElementArrayNameAccessor( array ) ); +} + +void MakeElementNameUnique( CDmElement *pElement, const char *prefix, const CUtlVector< DmElementHandle_t > &array, bool forceIndex ) +{ + if ( pElement == NULL || prefix == NULL ) + return; + + int i = GenerateUniqueNameIndex( prefix, array ); + if ( i <= 0 ) + { + if ( !forceIndex ) + { + pElement->SetName( prefix ); + return; + } + i = 1; // 1 means that no names of "prefix*" were found, but we want to generate a 1-based index + } + + int prefixLength = Q_strlen( prefix ); + int newlen = prefixLength + ( int )log10( ( float )i ) + 1; + if ( newlen < 256 ) + { + char name[256]; + Q_snprintf( name, sizeof( name ), "%s%d", prefix, i ); + pElement->SetName( name ); + } + else + { + char *name = new char[ newlen + 1 ]; + Q_snprintf( name, sizeof( name ), "%s%d", prefix, i ); + pElement->SetName( name ); + delete[] name; + } +} + +void RemoveElementFromRefereringAttributes( CDmElement *pElement, bool bPreserveOrder /*= true*/ ) +{ + for ( DmAttributeReferenceIterator_t i = g_pDataModel->FirstAttributeReferencingElement( pElement->GetHandle() ); + i != DMATTRIBUTE_REFERENCE_ITERATOR_INVALID; + i = g_pDataModel->FirstAttributeReferencingElement( pElement->GetHandle() ) ) // always re-get the FIRST attribute, since we're removing from this list + { + CDmAttribute *pAttribute = g_pDataModel->GetAttribute( i ); + Assert( pAttribute ); + if ( !pAttribute ) + continue; + + if ( IsArrayType( pAttribute->GetType() ) ) + { + CDmrElementArray<> array( pAttribute ); + int iElem = array.Find( pElement ); + Assert( iElem != array.InvalidIndex() ); + if ( bPreserveOrder ) + { + array.Remove( iElem ); + } + else + { + array.FastRemove( iElem ); + } + } + else + { + pAttribute->SetValue( DMELEMENT_HANDLE_INVALID ); + } + } +} diff --git a/datamodel/dmelementdictionary.cpp b/datamodel/dmelementdictionary.cpp new file mode 100644 index 0000000..5f11370 --- /dev/null +++ b/datamodel/dmelementdictionary.cpp @@ -0,0 +1,468 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= + +#include "dmelementdictionary.h" +#include "datamodel/dmelement.h" +#include "datamodel/dmattribute.h" +#include "datamodel/dmattributevar.h" +#include "datamodel/idatamodel.h" +#include "datamodel.h" + + +//----------------------------------------------------------------------------- +// Constructor +//----------------------------------------------------------------------------- +CDmElementDictionary::CDmElementDictionary() + : m_idmap( 1024, 0, 0, DmIdPair_t::Compare, DmIdPair_t::HashKey ) +{ +} + + +//----------------------------------------------------------------------------- +// Clears the dictionary +//----------------------------------------------------------------------------- +void CDmElementDictionary::Clear() +{ + m_Dict.Purge(); + m_Attributes.Purge(); + m_ArrayAttributes.Purge(); + m_elementsToDelete.Purge(); +} + + +//----------------------------------------------------------------------------- +// Inserts an element into the table +//----------------------------------------------------------------------------- +DmElementDictHandle_t CDmElementDictionary::InsertElement( CDmElement *pElement ) +{ + // Insert it into the reconnection table + return m_Dict.AddToTail( pElement ? pElement->GetHandle() : DMELEMENT_HANDLE_INVALID ); +} + + +//----------------------------------------------------------------------------- +// Returns a particular element +//----------------------------------------------------------------------------- +CDmElement *CDmElementDictionary::GetElement( DmElementDictHandle_t handle ) +{ + if ( handle == ELEMENT_DICT_HANDLE_INVALID ) + return NULL; + + return g_pDataModel->GetElement( m_Dict[ handle ] ); +} + + +//----------------------------------------------------------------------------- +// Adds an attribute to the fixup list +//----------------------------------------------------------------------------- +void CDmElementDictionary::AddAttribute( CDmAttribute *pAttribute, const DmObjectId_t &objectId ) +{ + if ( m_elementsToDelete.Find( pAttribute->GetOwner()->GetHandle() ) != m_elementsToDelete.InvalidIndex() ) + return; // don't add attributes if their element is being deleted + + int i = m_Attributes.AddToTail(); + m_Attributes[i].m_nType = AT_OBJECTID; + m_Attributes[i].m_pAttribute = pAttribute; + CopyUniqueId( objectId, &m_Attributes[i].m_ObjectId ); +} + + +//----------------------------------------------------------------------------- +// Adds an element of an attribute array to the fixup list +//----------------------------------------------------------------------------- +void CDmElementDictionary::AddArrayAttribute( CDmAttribute *pAttribute, DmElementDictHandle_t hElement ) +{ + if ( m_elementsToDelete.Find( pAttribute->GetOwner()->GetHandle() ) != m_elementsToDelete.InvalidIndex() ) + return; // don't add attributes if their element is being deleted + + int i = m_ArrayAttributes.AddToTail(); + m_ArrayAttributes[i].m_nType = AT_ELEMENT; + m_ArrayAttributes[i].m_pAttribute = pAttribute; + m_ArrayAttributes[i].m_hElement = hElement; +} + +void CDmElementDictionary::AddArrayAttribute( CDmAttribute *pAttribute, const DmObjectId_t &objectId ) +{ + if ( m_elementsToDelete.Find( pAttribute->GetOwner()->GetHandle() ) != m_elementsToDelete.InvalidIndex() ) + return; // don't add attributes if their element is being deleted + + int i = m_ArrayAttributes.AddToTail(); + m_ArrayAttributes[i].m_nType = AT_OBJECTID; + m_ArrayAttributes[i].m_pAttribute = pAttribute; + CopyUniqueId( objectId, &m_ArrayAttributes[i].m_ObjectId ); +} + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void CDmElementDictionary::RemoveAttributeInfosOfElement( AttributeList_t &attributes, DmElementHandle_t hElement ) +{ + while ( attributes.Count() > 0 && attributes.Tail().m_pAttribute->GetOwner()->GetHandle() == hElement ) + { + attributes.Remove( attributes.Count() - 1 ); + } +} + +DmElementHandle_t CDmElementDictionary::SetElementId( DmElementDictHandle_t hDictHandle, const DmObjectId_t &newId, DmConflictResolution_t idConflictResolution ) +{ + DmElementHandle_t hElement = m_Dict[ hDictHandle ]; + CDmElement *pElement = g_pDataModel->GetElement( hElement ); + Assert( pElement ); + if ( !pElement ) + return DMELEMENT_HANDLE_INVALID; + + const DmObjectId_t &oldId = pElement->GetId(); + + if ( idConflictResolution == CR_FORCE_COPY ) + { + m_idmap.Insert( DmIdPair_t( newId, oldId ) ); // map the newId back to the old id, and keep the old id + return hElement; + } + + DmElementHandle_t newHandle = g_pDataModelImp->ChangeElementId( hElement, oldId, newId ); + if ( newHandle != DMELEMENT_HANDLE_INVALID ) + { + // if ChangeElementId returns a handle, the id has been changed + if ( newHandle != hElement ) + { + int i = m_Dict.Find( hElement ); + if ( i != m_Dict.InvalidIndex() ) + { + m_Dict[ i ] = newHandle; + } + } + return newHandle; // either keeping the old handle, with the new id, or found a new handle associated with that new id + } + + // id not changed because that id is already in use + if ( idConflictResolution == CR_DELETE_NEW ) + { + DmElementHandle_t hExistingElement = g_pDataModel->FindElement( newId ); + + int i = m_elementsToDelete.AddToTail( ); + m_elementsToDelete[i].m_hDictHandle = hDictHandle; + m_elementsToDelete[i].m_hElementToDelete = hElement; + m_elementsToDelete[i].m_hReplacementElement = hExistingElement; + + // remove all element ref attributes read in before the id (typically none) + RemoveAttributeInfosOfElement( m_Attributes, hElement ); + RemoveAttributeInfosOfElement( m_ArrayAttributes, hElement ); + + return DMELEMENT_HANDLE_INVALID; + } + + if ( idConflictResolution == CR_DELETE_OLD ) + { + DmElementHandle_t hExistingElement = g_pDataModel->FindElement( newId ); + Assert( hExistingElement != DMELEMENT_HANDLE_INVALID ); + if ( hExistingElement == DMELEMENT_HANDLE_INVALID ) + return DMELEMENT_HANDLE_INVALID; // unexpected error in ChangeElementId (failed due to something other than a conflict) + + g_pDataModelImp->DeleteElement( hExistingElement, HR_NEVER ); // need to keep the handle around until ChangeElemendId + newHandle = g_pDataModelImp->ChangeElementId( hElement, oldId, newId ); + Assert( newHandle == hExistingElement ); + + int i = m_Dict.Find( hElement ); + if ( i != m_Dict.InvalidIndex() ) + { + m_Dict[ i ] = newHandle; + } + + return newHandle; + } + + if ( idConflictResolution == CR_COPY_NEW ) + { + m_idmap.Insert( DmIdPair_t( newId, oldId ) ); // map the newId back to the old id, and keep the old id + return hElement; + } + + Assert( 0 ); + return DMELEMENT_HANDLE_INVALID; +} + + +//----------------------------------------------------------------------------- +// Finds an element into the table +//----------------------------------------------------------------------------- +DmElementDictHandle_t CDmElementDictionary::FindElement( CDmElement *pElement ) +{ + return m_Dict.Find( pElement ? pElement->GetHandle() : DMELEMENT_HANDLE_INVALID ); +} + + +//----------------------------------------------------------------------------- +// Hook up all element references (which were unserialized as object ids) +//----------------------------------------------------------------------------- +void CDmElementDictionary::HookUpElementAttributes() +{ + int n = m_Attributes.Count(); + for ( int i = 0; i < n; ++i ) + { + Assert( m_Attributes[i].m_pAttribute->GetType() == AT_ELEMENT ); + Assert( m_Attributes[i].m_nType == AT_OBJECTID ); + + UtlHashHandle_t h = m_idmap.Find( DmIdPair_t( m_Attributes[i].m_ObjectId ) ); + DmObjectId_t &id = h == m_idmap.InvalidHandle() ? m_Attributes[i].m_ObjectId : m_idmap[ h ].m_newId; + + // 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 hElement = g_pDataModelImp->FindOrCreateElementHandle( id ); + m_Attributes[i].m_pAttribute->SetValue<DmElementHandle_t>( hElement ); + } +} + + +//----------------------------------------------------------------------------- +// Hook up all element array references +//----------------------------------------------------------------------------- +void CDmElementDictionary::HookUpElementArrayAttributes() +{ + // Find unique array attributes; we need to clear them all before adding stuff. + // This clears them of stuff added during their construction phase. + int n = m_ArrayAttributes.Count(); + CUtlRBTree< CDmAttribute*, unsigned short > lookup( 0, n, DefLessFunc(CDmAttribute*) ); + for ( int i = 0; i < n; ++i ) + { + Assert( m_ArrayAttributes[i].m_pAttribute->GetType() == AT_ELEMENT_ARRAY ); + CDmAttribute *pElementArray = m_ArrayAttributes[i].m_pAttribute; + CDmrElementArray<> array( pElementArray ); + if ( lookup.Find( pElementArray ) == lookup.InvalidIndex() ) + { + array.RemoveAll(); + lookup.Insert( pElementArray ); + } + } + + for ( int i = 0; i < n; ++i ) + { + Assert( m_ArrayAttributes[i].m_pAttribute->GetType() == AT_ELEMENT_ARRAY ); + + CDmrElementArray<> array( m_ArrayAttributes[i].m_pAttribute ); + + if ( m_ArrayAttributes[i].m_nType == AT_ELEMENT ) + { + CDmElement *pElement = GetElement( m_ArrayAttributes[i].m_hElement ); + array.AddToTail( pElement ); + } + else + { + UtlHashHandle_t h = m_idmap.Find( DmIdPair_t( m_ArrayAttributes[i].m_ObjectId ) ); + DmObjectId_t &id = ( h == m_idmap.InvalidHandle() ) ? m_ArrayAttributes[i].m_ObjectId : m_idmap[ h ].m_newId; + + // 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 hElement = g_pDataModelImp->FindOrCreateElementHandle( id ); + int nIndex = array.AddToTail(); + array.SetHandle( nIndex, hElement ); + } + } +} + + +//----------------------------------------------------------------------------- +// Hook up all element references (which were unserialized as object ids) +//----------------------------------------------------------------------------- +void CDmElementDictionary::HookUpElementReferences() +{ + int nElementsToDelete = m_elementsToDelete.Count(); + for ( int i = 0; i < nElementsToDelete; ++i ) + { + DmElementDictHandle_t hDictIndex = m_elementsToDelete[i].m_hDictHandle; + DmElementHandle_t hElement = m_Dict[ hDictIndex ]; + g_pDataModelImp->DeleteElement( hElement ); + m_Dict[ hDictIndex ] = m_elementsToDelete[i].m_hReplacementElement; + } + + HookUpElementArrayAttributes(); + HookUpElementAttributes(); +} + + + +//----------------------------------------------------------------------------- +// +// Element dictionary used in serialization +// +//----------------------------------------------------------------------------- +CDmElementSerializationDictionary::CDmElementSerializationDictionary() : + m_Dict( 1024, 0, CDmElementSerializationDictionary::LessFunc ) +{ +} + + +//----------------------------------------------------------------------------- +// Used to sort the list of elements +//----------------------------------------------------------------------------- +bool CDmElementSerializationDictionary::LessFunc( const ElementInfo_t &lhs, const ElementInfo_t &rhs ) +{ + return lhs.m_pElement < rhs.m_pElement; +} + + +//----------------------------------------------------------------------------- +// Finds the handle of the element +//----------------------------------------------------------------------------- +DmElementDictHandle_t CDmElementSerializationDictionary::Find( CDmElement *pElement ) +{ + ElementInfo_t find; + find.m_pElement = pElement; + return m_Dict.Find( find ); +} + + +//----------------------------------------------------------------------------- +// Creates the list of all things to serialize +//----------------------------------------------------------------------------- +void CDmElementSerializationDictionary::BuildElementList_R( CDmElement *pElement, bool bFlatMode, bool bIsRoot ) +{ + if ( !pElement ) + return; + + // FIXME: Right here we should ask the element if it's an external + // file reference and exit immediately if so. + + // This means we've already encountered this guy. + // Therefore, he can never be a root element + DmElementDictHandle_t h = Find( pElement ); + if ( h != m_Dict.InvalidIndex() ) + { + m_Dict[h].m_bRoot = true; + return; + } + + ElementInfo_t info; + info.m_bRoot = bFlatMode || bIsRoot; + info.m_pElement = pElement; + m_Dict.Insert( info ); + + for ( CDmAttribute *pAttribute = pElement->FirstAttribute(); pAttribute; pAttribute = pAttribute->NextAttribute() ) + { + if ( pAttribute->IsFlagSet( FATTRIB_DONTSAVE ) ) + continue; + + switch( pAttribute->GetType() ) + { + case AT_ELEMENT: + { + CDmElement *pChild = pAttribute->GetValueElement<CDmElement>(); + if ( !pChild || pChild->GetFileId() != pElement->GetFileId() ) + break; + + BuildElementList_R( pChild, bFlatMode, false ); + } + break; + + case AT_ELEMENT_ARRAY: + { + CDmrElementArray<> array( pAttribute ); + int nCount = array.Count(); + for ( int i = 0; i < nCount; ++i ) + { + CDmElement *pChild = array[i]; + if ( !pChild || pChild->GetFileId() != pElement->GetFileId() ) + break; + + BuildElementList_R( pChild, bFlatMode, false ); + } + } + break; + } + } +} + +void CDmElementSerializationDictionary::BuildElementList( CDmElement *pElement, bool bFlatMode ) +{ + BuildElementList_R( pElement, bFlatMode, true ); +} + + +//----------------------------------------------------------------------------- +// Should I inline the serialization of this element? +//----------------------------------------------------------------------------- +bool CDmElementSerializationDictionary::ShouldInlineElement( CDmElement *pElement ) +{ + // This means we've already encountered this guy. + // Therefore, he can never be a root element + DmElementDictHandle_t h = Find( pElement ); + if ( h != m_Dict.InvalidIndex() ) + return !m_Dict[h].m_bRoot; + + // If we didn't find the element, it means it's a reference to an external + // element (or it's NULL), so don't inline ie. + return false; +} + + +//----------------------------------------------------------------------------- +// Clears the dictionary +//----------------------------------------------------------------------------- +void CDmElementSerializationDictionary::Clear() +{ + m_Dict.RemoveAll(); +} + + +//----------------------------------------------------------------------------- +// How many root elements do we have? +//----------------------------------------------------------------------------- +int CDmElementSerializationDictionary::RootElementCount() const +{ + int nCount = 0; + DmElementDictHandle_t h = m_Dict.FirstInorder(); + while( h != m_Dict.InvalidIndex() ) + { + if ( m_Dict[h].m_bRoot ) + { + ++nCount; + } + h = m_Dict.NextInorder( h ); + } + return nCount; +} + + +//----------------------------------------------------------------------------- +// Iterates over all root elements to serialize +//----------------------------------------------------------------------------- +DmElementDictHandle_t CDmElementSerializationDictionary::FirstRootElement() const +{ + // NOTE - this code only works with BlockMemory or Memory (NOT FixedMemory) + + // NOTE: I don't have to use First/NextInorder here because there + // are guaranteed to be no removals from the dictionary. + // Also, using inorder traversal won't get my actual root element to be first in the file + int nCount = m_Dict.Count(); + for ( DmElementDictHandle_t h = 0; h < nCount; ++h ) + { + if ( m_Dict[h].m_bRoot ) + return h; + } + return ELEMENT_DICT_HANDLE_INVALID; +} + +DmElementDictHandle_t CDmElementSerializationDictionary::NextRootElement( DmElementDictHandle_t h ) const +{ + // NOTE - this code only works with BlockMemory or Memory (NOT FixedMemory) + + // NOTE: I don't have to use First/NextInorder here because there + // are guaranteed to be no removals from the dictionary. + // Also, using inorder traversal won't get my actual root element to be first in the file + ++h; + int nCount = m_Dict.Count(); + for ( ; h < nCount; ++h ) + { + if ( m_Dict[h].m_bRoot ) + return h; + } + return ELEMENT_DICT_HANDLE_INVALID; +} + +CDmElement *CDmElementSerializationDictionary::GetRootElement( DmElementDictHandle_t h ) +{ + Assert( m_Dict[h].m_bRoot ); + return m_Dict[h].m_pElement; +} + + diff --git a/datamodel/dmelementdictionary.h b/datamodel/dmelementdictionary.h new file mode 100644 index 0000000..c0ae14a --- /dev/null +++ b/datamodel/dmelementdictionary.h @@ -0,0 +1,184 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= + +#ifndef DMELEMENTDICTIONARY_H +#define DMELEMENTDICTIONARY_H + +#ifdef _WIN32 +#pragma once +#endif + + +#include "tier1/utlvector.h" +#include "datamodel/idatamodel.h" +#include "datamodel/dmattribute.h" +#include "tier1/utlrbtree.h" +#include "tier1/utlhash.h" + + +//----------------------------------------------------------------------------- +// Forward declarations +//----------------------------------------------------------------------------- +class CDmElement; +class CDmAttribute; + + +//----------------------------------------------------------------------------- +// Element dictionary used in unserialization +//----------------------------------------------------------------------------- +typedef int DmElementDictHandle_t; +enum +{ + ELEMENT_DICT_HANDLE_INVALID = (DmElementDictHandle_t)~0 +}; + + +class CDmElementDictionary +{ +public: + CDmElementDictionary(); + + DmElementDictHandle_t InsertElement( CDmElement *pElement ); + CDmElement *GetElement( DmElementDictHandle_t handle ); + void AddAttribute( CDmAttribute *pAttribute, const DmObjectId_t &pElementId ); + void AddArrayAttribute( CDmAttribute *pAttribute, DmElementDictHandle_t hChild ); + void AddArrayAttribute( CDmAttribute *pAttribute, const DmObjectId_t &pElementId ); + + DmElementHandle_t SetElementId( DmElementDictHandle_t hDictHandle, + const DmObjectId_t &newId, + DmConflictResolution_t idConflictResolution ); + + // Finds an element into the table + DmElementDictHandle_t FindElement( CDmElement *pElement ); + + // Hook up all element references (which were unserialized as object ids) + void HookUpElementReferences(); + + // Clears the dictionary + void Clear(); + + // iteration through elements + DmElementDictHandle_t FirstElement() { return 0; } + DmElementDictHandle_t NextElement( DmElementDictHandle_t h ) + { + return m_Dict.IsValidIndex( h+1 ) ? h+1 : ELEMENT_DICT_HANDLE_INVALID; + } + + +private: + struct AttributeInfo_t + { + CDmAttribute *m_pAttribute; + int m_nType; // AT_ELEMENT or AT_OBJECTID + union + { + DmElementDictHandle_t m_hElement; + DmObjectId_t m_ObjectId; + }; + }; + typedef CUtlVector<AttributeInfo_t> AttributeList_t; + + + struct DmIdPair_t + { + DmObjectId_t m_oldId; + DmObjectId_t m_newId; + DmIdPair_t() {} + DmIdPair_t( const DmObjectId_t &id ) + { + CopyUniqueId( id, &m_oldId ); + } + DmIdPair_t( const DmObjectId_t &oldId, const DmObjectId_t &newId ) + { + CopyUniqueId( oldId, &m_oldId ); + CopyUniqueId( newId, &m_newId ); + } + DmIdPair_t &operator=( const DmIdPair_t &that ) + { + CopyUniqueId( that.m_oldId, &m_oldId ); + CopyUniqueId( that.m_newId, &m_newId ); + return *this; + } + static unsigned int HashKey( const DmIdPair_t& that ) + { + return *( unsigned int* )&that.m_oldId.m_Value; + } + static bool Compare( const DmIdPair_t& a, const DmIdPair_t& b ) + { + return IsUniqueIdEqual( a.m_oldId, b.m_oldId ); + } + }; + + struct DeletionInfo_t + { + DeletionInfo_t() {} + DeletionInfo_t( DmElementHandle_t hElement ) : m_hElementToDelete( hElement ) {} + bool operator==( const DeletionInfo_t& src ) const { return m_hElementToDelete == src.m_hElementToDelete; } + + DmElementDictHandle_t m_hDictHandle; + DmElementHandle_t m_hElementToDelete; + DmElementHandle_t m_hReplacementElement; + }; + + // Hook up all element references (which were unserialized as object ids) + void HookUpElementAttributes(); + void HookUpElementArrayAttributes(); + + void RemoveAttributeInfosOfElement( AttributeList_t &attributes, DmElementHandle_t hElement ); + + CUtlVector< DmElementHandle_t > m_Dict; + AttributeList_t m_Attributes; + AttributeList_t m_ArrayAttributes; + + CUtlVector< DeletionInfo_t > m_elementsToDelete; + CUtlHash< DmIdPair_t > m_idmap; +}; + + +//----------------------------------------------------------------------------- +// Element dictionary used in serialization +//----------------------------------------------------------------------------- +class CDmElementSerializationDictionary +{ +public: + CDmElementSerializationDictionary(); + + // Creates the list of all things to serialize + void BuildElementList( CDmElement *pRoot, bool bFlatMode ); + + // Should I inline the serialization of this element? + bool ShouldInlineElement( CDmElement *pElement ); + + // Clears the dictionary + void Clear(); + + // Iterates over all root elements to serialize + DmElementDictHandle_t FirstRootElement() const; + DmElementDictHandle_t NextRootElement( DmElementDictHandle_t h ) const; + CDmElement* GetRootElement( DmElementDictHandle_t h ); + + // Finds the handle of the element + DmElementDictHandle_t Find( CDmElement *pElement ); + + // How many root elements do we have? + int RootElementCount() const; + +private: + struct ElementInfo_t + { + bool m_bRoot; + CDmElement* m_pElement; + }; + + // Creates the list of all things to serialize + void BuildElementList_R( CDmElement *pRoot, bool bFlatMode, bool bIsRoot ); + static bool LessFunc( const ElementInfo_t &lhs, const ElementInfo_t &rhs ); + + CUtlBlockRBTree< ElementInfo_t, DmElementDictHandle_t > m_Dict; +}; + + +#endif // DMELEMENTDICTIONARY_H diff --git a/datamodel/dmelementfactoryhelper.cpp b/datamodel/dmelementfactoryhelper.cpp new file mode 100644 index 0000000..4a71c87 --- /dev/null +++ b/datamodel/dmelementfactoryhelper.cpp @@ -0,0 +1,100 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= + +#include "datamodel/dmelementfactoryhelper.h" +#include "tier0/dbg.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +CDmElementFactoryHelper *CDmElementFactoryHelper::s_pHelpers[2] = { NULL, NULL }; + + +//----------------------------------------------------------------------------- +// Constructor +//----------------------------------------------------------------------------- +CDmElementFactoryHelper::CDmElementFactoryHelper( const char *classname, IDmElementFactoryInternal *pFactory, bool bIsStandardFactory ) +{ + m_pNext = s_pHelpers[bIsStandardFactory]; + s_pHelpers[bIsStandardFactory] = this; + + // Set attributes + Assert( pFactory ); + m_pFactory = pFactory; + Assert( classname ); + m_pszClassname = classname; +} + +//----------------------------------------------------------------------------- +// Purpose: Returns next object in list +// Output : CDmElementFactoryHelper +//----------------------------------------------------------------------------- +CDmElementFactoryHelper *CDmElementFactoryHelper::GetNext( void ) +{ + return m_pNext; +} + + +//----------------------------------------------------------------------------- +// Installs all factories into the datamodel system +//----------------------------------------------------------------------------- +// NOTE: The name of this extern is defined by the macro IMPLEMENT_ELEMENT_FACTORY +extern CDmElementFactoryHelper g_CDmElement_Helper; + +void CDmElementFactoryHelper::InstallFactories( ) +{ + // Just set up the type symbols of the other factories + CDmElementFactoryHelper *p = s_pHelpers[0]; + while ( p ) + { + // Add factories to database + if ( !p->GetFactory()->IsAbstract() ) + { + g_pDataModel->AddElementFactory( p->GetClassname(), p->GetFactory() ); + } + + // Set up the type symbol. Note this can't be done at + // constructor time since we don't have a DataModel pointer then + p->GetFactory()->SetElementTypeSymbol( g_pDataModel->GetSymbol( p->GetClassname() ) ); + p = p->GetNext(); + } + + p = s_pHelpers[1]; + while ( p ) + { + // Add factories to database, but not if they've been overridden + if ( !g_pDataModel->HasElementFactory( p->GetClassname() ) ) + { + if ( !p->GetFactory()->IsAbstract() ) + { + g_pDataModel->AddElementFactory( p->GetClassname(), p->GetFactory() ); + } + + // Set up the type symbol. Note this can't be done at + // constructor time since we don't have a DataModel pointer then + + // Backward compat--don't let the type symbol be 'DmeElement' + if ( Q_stricmp( p->GetClassname(), "DmeElement" ) ) + { + p->GetFactory()->SetElementTypeSymbol( g_pDataModel->GetSymbol( p->GetClassname() ) ); + } + } + + p = p->GetNext(); + } + + // Also install the DmElement factory as the default factory + g_pDataModel->SetDefaultElementFactory( g_CDmElement_Helper.GetFactory() ); +} + + +//----------------------------------------------------------------------------- +// Installs all DmElement factories +//----------------------------------------------------------------------------- +void InstallDmElementFactories( ) +{ + CDmElementFactoryHelper::InstallFactories( ); +} diff --git a/datamodel/dmserializerbinary.cpp b/datamodel/dmserializerbinary.cpp new file mode 100644 index 0000000..f036317 --- /dev/null +++ b/datamodel/dmserializerbinary.cpp @@ -0,0 +1,587 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= + +#include "dmserializerbinary.h" +#include "datamodel/idatamodel.h" +#include "datamodel.h" +#include "datamodel/dmelement.h" +#include "datamodel/dmattributevar.h" +#include "dmattributeinternal.h" +#include "dmelementdictionary.h" +#include "tier1/utlbuffer.h" +#include "DmElementFramework.h" + + +//----------------------------------------------------------------------------- +// Forward declarations +//----------------------------------------------------------------------------- +class CUtlBuffer; +class CBaseSceneObject; + + +//----------------------------------------------------------------------------- +// special element indices +//----------------------------------------------------------------------------- +enum +{ + ELEMENT_INDEX_NULL = -1, + ELEMENT_INDEX_EXTERNAL = -2, +}; + + +//----------------------------------------------------------------------------- +// Serialization class for Binary output +//----------------------------------------------------------------------------- +class CDmSerializerBinary : public IDmSerializer +{ +public: + CDmSerializerBinary() {} + // Inherited from IDMSerializer + virtual const char *GetName() const { return "binary"; } + virtual const char *GetDescription() const { return "Binary"; } + virtual bool StoresVersionInFile() const { return true; } + virtual bool IsBinaryFormat() const { return true; } + virtual int GetCurrentVersion() const { return 2; } + virtual bool Serialize( CUtlBuffer &buf, CDmElement *pRoot ); + virtual bool Unserialize( CUtlBuffer &buf, const char *pEncodingName, int nEncodingVersion, + const char *pSourceFormatName, int nFormatVersion, + DmFileId_t fileid, DmConflictResolution_t idConflictResolution, CDmElement **ppRoot ); + +private: + // Methods related to serialization + void SerializeElementIndex( CUtlBuffer& buf, CDmElementSerializationDictionary& list, DmElementHandle_t hElement, DmFileId_t fileid ); + void SerializeElementAttribute( CUtlBuffer& buf, CDmElementSerializationDictionary& list, CDmAttribute *pAttribute ); + void SerializeElementArrayAttribute( CUtlBuffer& buf, CDmElementSerializationDictionary& list, CDmAttribute *pAttribute ); + bool SerializeAttributes( CUtlBuffer& buf, CDmElementSerializationDictionary& list, unsigned short *symbolToIndexMap, CDmElement *pElement ); + bool SaveElementDict( CUtlBuffer& buf, unsigned short *symbolToIndexMap, CDmElement *pElement ); + bool SaveElement( CUtlBuffer& buf, CDmElementSerializationDictionary& dict, unsigned short *symbolToIndexMap, CDmElement *pElement); + + // Methods related to unserialization + DmElementHandle_t UnserializeElementIndex( CUtlBuffer &buf, CUtlVector<CDmElement*> &elementList ); + void UnserializeElementAttribute( CUtlBuffer &buf, CDmAttribute *pAttribute, CUtlVector<CDmElement*> &elementList ); + void UnserializeElementArrayAttribute( CUtlBuffer &buf, CDmAttribute *pAttribute, CUtlVector<CDmElement*> &elementList ); + bool UnserializeAttributes( CUtlBuffer &buf, CDmElement *pElement, CUtlVector<CDmElement*> &elementList, UtlSymId_t *symbolTable ); + bool UnserializeElements( CUtlBuffer &buf, DmFileId_t fileid, DmConflictResolution_t idConflictResolution, CDmElement **ppRoot, UtlSymId_t *symbolTable ); +}; + + +//----------------------------------------------------------------------------- +// Singleton instance +//----------------------------------------------------------------------------- +static CDmSerializerBinary s_DMSerializerBinary; + +void InstallBinarySerializer( IDataModel *pFactory ) +{ + pFactory->AddSerializer( &s_DMSerializerBinary ); +} + + +//----------------------------------------------------------------------------- +// Write out the index of the element to avoid looks at read time +//----------------------------------------------------------------------------- +void CDmSerializerBinary::SerializeElementIndex( CUtlBuffer& buf, CDmElementSerializationDictionary& list, DmElementHandle_t hElement, DmFileId_t fileid ) +{ + if ( hElement == DMELEMENT_HANDLE_INVALID ) + { + buf.PutInt( ELEMENT_INDEX_NULL ); // invalid handle + } + else + { + CDmElement *pElement = g_pDataModel->GetElement( hElement ); + if ( pElement ) + { + if ( pElement->GetFileId() == fileid ) + { + buf.PutInt( list.Find( pElement ) ); + } + else + { + buf.PutInt( ELEMENT_INDEX_EXTERNAL ); + char idstr[ 40 ]; + UniqueIdToString( pElement->GetId(), idstr, sizeof( idstr ) ); + buf.PutString( idstr ); + } + } + else + { + DmObjectId_t *pId = NULL; + DmElementReference_t *pRef = g_pDataModelImp->FindElementReference( hElement, &pId ); + if ( pRef && pId ) + { + buf.PutInt( ELEMENT_INDEX_EXTERNAL ); + char idstr[ 40 ]; + UniqueIdToString( *pId, idstr, sizeof( idstr ) ); + buf.PutString( idstr ); + } + else + { + buf.PutInt( ELEMENT_INDEX_NULL ); // invalid handle + } + } + } +} + + +//----------------------------------------------------------------------------- +// Writes out element attributes +//----------------------------------------------------------------------------- +void CDmSerializerBinary::SerializeElementAttribute( CUtlBuffer& buf, CDmElementSerializationDictionary& list, CDmAttribute *pAttribute ) +{ + SerializeElementIndex( buf, list, pAttribute->GetValue< DmElementHandle_t >(), pAttribute->GetOwner()->GetFileId() ); +} + + +//----------------------------------------------------------------------------- +// Writes out element array attributes +//----------------------------------------------------------------------------- +void CDmSerializerBinary::SerializeElementArrayAttribute( CUtlBuffer& buf, CDmElementSerializationDictionary& list, CDmAttribute *pAttribute ) +{ + DmFileId_t fileid = pAttribute->GetOwner()->GetFileId(); + CDmrElementArray<> vec( pAttribute ); + + int nCount = vec.Count(); + buf.PutInt( nCount ); + for ( int i = 0; i < nCount; ++i ) + { + SerializeElementIndex( buf, list, vec.GetHandle(i), fileid ); + } +} + + +//----------------------------------------------------------------------------- +// Writes out all attributes +//----------------------------------------------------------------------------- +bool CDmSerializerBinary::SerializeAttributes( CUtlBuffer& buf, CDmElementSerializationDictionary& list, unsigned short *symbolToIndexMap, CDmElement *pElement ) +{ + // Collect the attributes to be written + CDmAttribute **ppAttributes = ( CDmAttribute** )_alloca( pElement->AttributeCount() * sizeof( CDmAttribute* ) ); + int nAttributes = 0; + for ( CDmAttribute *pAttribute = pElement->FirstAttribute(); pAttribute; pAttribute = pAttribute->NextAttribute() ) + { + if ( pAttribute->IsFlagSet( FATTRIB_DONTSAVE | FATTRIB_STANDARD ) ) + continue; + + ppAttributes[ nAttributes++ ] = pAttribute; + } + + // Now write them all out in reverse order, since FirstAttribute is actually the *last* attribute for perf reasons + buf.PutInt( nAttributes ); + for ( int i = nAttributes - 1; i >= 0; --i ) + { + CDmAttribute *pAttribute = ppAttributes[ i ]; + Assert( pAttribute ); + + buf.PutShort( symbolToIndexMap[ pAttribute->GetNameSymbol() ] ); + buf.PutChar( pAttribute->GetType() ); + switch( pAttribute->GetType() ) + { + default: + pAttribute->Serialize( buf ); + break; + + case AT_ELEMENT: + SerializeElementAttribute( buf, list, pAttribute ); + break; + + case AT_ELEMENT_ARRAY: + SerializeElementArrayAttribute( buf, list, pAttribute ); + break; + } + } + + return buf.IsValid(); +} + + +bool CDmSerializerBinary::SaveElement( CUtlBuffer& buf, CDmElementSerializationDictionary& list, unsigned short *symbolToIndexMap, CDmElement *pElement ) +{ + SerializeAttributes( buf, list, symbolToIndexMap, pElement ); + return buf.IsValid(); +} + +bool CDmSerializerBinary::SaveElementDict( CUtlBuffer& buf, unsigned short *symbolToIndexMap, CDmElement *pElement ) +{ + buf.PutShort( symbolToIndexMap[ pElement->GetType() ] ); + buf.PutString( pElement->GetName() ); + buf.Put( &pElement->GetId(), sizeof(DmObjectId_t) ); + return buf.IsValid(); +} + +void MarkSymbol( UtlSymId_t *indexToSymbolMap, unsigned short *symbolToIndexMap, unsigned short &nSymbols, UtlSymId_t sym ) +{ + if ( symbolToIndexMap[ sym ] != 0xffff ) + return; + + symbolToIndexMap[ sym ] = nSymbols; + indexToSymbolMap[ nSymbols ] = sym; + nSymbols++; +} + +void MarkSymbols( UtlSymId_t *indexToSymbolMap, unsigned short *symbolToIndexMap, unsigned short &nSymbols, CDmElement *pElement ) +{ + MarkSymbol( indexToSymbolMap, symbolToIndexMap, nSymbols, pElement->GetType() ); + for ( CDmAttribute *pAttr = pElement->FirstAttribute(); pAttr; pAttr = pAttr->NextAttribute() ) + { + MarkSymbol( indexToSymbolMap, symbolToIndexMap, nSymbols, pAttr->GetNameSymbol() ); + } +} + +bool CDmSerializerBinary::Serialize( CUtlBuffer &outBuf, CDmElement *pRoot ) +{ + // Save elements, attribute links + CDmElementSerializationDictionary dict; + dict.BuildElementList( pRoot, true ); + + // TODO - consider allowing dmxconvert to skip the collection step, since the only datamodel symbols will be the ones from the file + + unsigned short nTotalSymbols = g_pDataModelImp->GetSymbolCount(); + UtlSymId_t *indexToSymbolMap = ( UtlSymId_t * )stackalloc( nTotalSymbols * sizeof( UtlSymId_t ) ); + unsigned short *symbolToIndexMap = ( unsigned short* )stackalloc( nTotalSymbols * sizeof( unsigned short ) ); + V_memset( indexToSymbolMap, 0xff, nTotalSymbols * sizeof( UtlSymId_t ) ); + V_memset( symbolToIndexMap, 0xff, nTotalSymbols * sizeof( unsigned short ) ); + + // collect list of attribute names and element types into string table + unsigned short nUsedSymbols = 0; + DmElementDictHandle_t i; + for ( i = dict.FirstRootElement(); i != ELEMENT_DICT_HANDLE_INVALID; i = dict.NextRootElement(i) ) + { + MarkSymbols( indexToSymbolMap, symbolToIndexMap, nUsedSymbols, dict.GetRootElement( i ) ); + } + Assert( nUsedSymbols <= nTotalSymbols ); + + // write out the symbol table for this file (may be significantly smaller than datamodel's full symbol table) + outBuf.PutShort( nUsedSymbols ); + for ( int si = 0; si < nUsedSymbols; ++si ) + { + UtlSymId_t sym = indexToSymbolMap[ si ]; + const char *pStr = g_pDataModel->GetString( sym ); + outBuf.PutString( pStr ); + } + + // First write out the dictionary of all elements (to avoid later stitching up in unserialize) + outBuf.PutInt( dict.RootElementCount() ); + for ( i = dict.FirstRootElement(); i != ELEMENT_DICT_HANDLE_INVALID; i = dict.NextRootElement(i) ) + { + SaveElementDict( outBuf, symbolToIndexMap, dict.GetRootElement( i ) ); + } + + // Now write out the attributes of each of those elements + for ( i = dict.FirstRootElement(); i != ELEMENT_DICT_HANDLE_INVALID; i = dict.NextRootElement(i) ) + { + SaveElement( outBuf, dict, symbolToIndexMap, dict.GetRootElement( i ) ); + } + + return true; +} + + +//----------------------------------------------------------------------------- +// Reads an element index and converts it to a handle (local or external) +//----------------------------------------------------------------------------- +DmElementHandle_t CDmSerializerBinary::UnserializeElementIndex( CUtlBuffer &buf, CUtlVector<CDmElement*> &elementList ) +{ + int nElementIndex = buf.GetInt(); + Assert( nElementIndex < elementList.Count() ); + if ( nElementIndex == ELEMENT_INDEX_EXTERNAL ) + { + char idstr[ 40 ]; + buf.GetString( idstr ); + DmObjectId_t id; + UniqueIdFromString( &id, idstr, sizeof( idstr ) ); + return g_pDataModelImp->FindOrCreateElementHandle( id ); + } + + Assert( nElementIndex >= 0 || nElementIndex == ELEMENT_INDEX_NULL ); + if ( nElementIndex < 0 || !elementList[ nElementIndex ] ) + return DMELEMENT_HANDLE_INVALID; + + return elementList[ nElementIndex ]->GetHandle(); +} + + +//----------------------------------------------------------------------------- +// Reads an element attribute +//----------------------------------------------------------------------------- +void CDmSerializerBinary::UnserializeElementAttribute( CUtlBuffer &buf, CDmAttribute *pAttribute, CUtlVector<CDmElement*> &elementList ) +{ + DmElementHandle_t hElement = UnserializeElementIndex( buf, elementList ); + if ( !pAttribute ) + return; + + pAttribute->SetValue( hElement ); +} + + +//----------------------------------------------------------------------------- +// Reads an element array attribute +//----------------------------------------------------------------------------- +void CDmSerializerBinary::UnserializeElementArrayAttribute( CUtlBuffer &buf, CDmAttribute *pAttribute, CUtlVector<CDmElement*> &elementList ) +{ + int nElementCount = buf.GetInt(); + + if ( !pAttribute || pAttribute->GetType() != AT_ELEMENT_ARRAY ) + { + // Parse past the data + for ( int i = 0; i < nElementCount; ++i ) + { + UnserializeElementIndex( buf, elementList ); + } + return; + } + + CDmrElementArray<> array( pAttribute ); + array.RemoveAll(); + array.EnsureCapacity( nElementCount ); + for ( int i = 0; i < nElementCount; ++i ) + { + DmElementHandle_t hElement = UnserializeElementIndex( buf, elementList ); + array.AddToTail( hElement ); + } +} + + +//----------------------------------------------------------------------------- +// Reads a single element +//----------------------------------------------------------------------------- +bool CDmSerializerBinary::UnserializeAttributes( CUtlBuffer &buf, CDmElement *pElement, CUtlVector<CDmElement*> &elementList, UtlSymId_t *symbolTable ) +{ + char nameBuf[ 1024 ]; + + int nAttributeCount = buf.GetInt(); + for ( int i = 0; i < nAttributeCount; ++i ) + { + const char *pName = NULL; + if ( symbolTable ) + { + unsigned short nName = buf.GetShort(); + pName = g_pDataModel->GetString( symbolTable[ nName ] ); + } + else + { + buf.GetString( nameBuf ); + pName = nameBuf; + } + DmAttributeType_t nAttributeType = (DmAttributeType_t)buf.GetChar(); + + Assert( pName != NULL && pName[ 0 ] != '\0' ); + Assert( nAttributeType != AT_UNKNOWN ); + + CDmAttribute *pAttribute = pElement ? pElement->AddAttribute( pName, nAttributeType ) : NULL; + if ( pElement && !pAttribute ) + { + Warning("Dm: Attempted to read an attribute (\"%s\") of an inappropriate type!\n", pName ); + return false; + } + + switch( nAttributeType ) + { + default: + if ( !pAttribute ) + { + SkipUnserialize( buf, nAttributeType ); + } + else + { + pAttribute->Unserialize( buf ); + } + break; + + case AT_ELEMENT: + UnserializeElementAttribute( buf, pAttribute, elementList ); + break; + + case AT_ELEMENT_ARRAY: + UnserializeElementArrayAttribute( buf, pAttribute, elementList ); + break; + } + } + + return buf.IsValid(); +} + +struct DmIdPair_t +{ + DmObjectId_t m_oldId; + DmObjectId_t m_newId; + DmIdPair_t &operator=( const DmIdPair_t &that ) + { + CopyUniqueId( that.m_oldId, &m_oldId ); + CopyUniqueId( that.m_newId, &m_newId ); + return *this; + } + static unsigned int HashKey( const DmIdPair_t& that ) + { + return *( unsigned int* )&that.m_oldId.m_Value; + } + static bool Compare( const DmIdPair_t& a, const DmIdPair_t& b ) + { + return IsUniqueIdEqual( a.m_oldId, b.m_oldId ); + } +}; + +DmElementHandle_t CreateElementWithFallback( const char *pType, const char *pName, DmFileId_t fileid, const DmObjectId_t &id ) +{ + DmElementHandle_t hElement = g_pDataModel->CreateElement( pType, pName, fileid, &id ); + if ( hElement == DMELEMENT_HANDLE_INVALID ) + { + Warning("Binary: Element uses unknown element type %s\n", pType ); + hElement = g_pDataModel->CreateElement( "DmElement", pName, fileid, &id ); + Assert( hElement != DMELEMENT_HANDLE_INVALID ); + } + return hElement; +} + +//----------------------------------------------------------------------------- +// Main entry point for the unserialization +//----------------------------------------------------------------------------- +bool CDmSerializerBinary::Unserialize( CUtlBuffer &buf, const char *pEncodingName, int nEncodingVersion, + const char *pSourceFormatName, int nSourceFormatVersion, + DmFileId_t fileid, DmConflictResolution_t idConflictResolution, CDmElement **ppRoot ) +{ + Assert( !V_stricmp( pEncodingName, GetName() ) ); + if ( V_stricmp( pEncodingName, GetName() ) != 0 ) + return false; + + Assert( nEncodingVersion >= 0 && nEncodingVersion <= 2 ); + if ( nEncodingVersion < 0 || nEncodingVersion > 2 ) + return false; + + bool bReadSymbolTable = nEncodingVersion >= 2; + + // Read string table + unsigned short nStrings = 0; + UtlSymId_t *symbolTable = NULL; + if ( bReadSymbolTable ) + { + char stringBuf[ 256 ]; + + nStrings = buf.GetShort(); + symbolTable = ( UtlSymId_t* )stackalloc( nStrings * sizeof( UtlSymId_t ) ); + for ( int i = 0; i < nStrings; ++i ) + { + buf.GetString( stringBuf ); + symbolTable[ i ] = g_pDataModel->GetSymbol( stringBuf ); + } + } + + bool bSuccess = UnserializeElements( buf, fileid, idConflictResolution, ppRoot, symbolTable ); + if ( !bSuccess ) + return false; + + return g_pDataModel->UpdateUnserializedElements( pSourceFormatName, nSourceFormatVersion, fileid, idConflictResolution, ppRoot ); +} + +bool CDmSerializerBinary::UnserializeElements( CUtlBuffer &buf, DmFileId_t fileid, DmConflictResolution_t idConflictResolution, CDmElement **ppRoot, UtlSymId_t *symbolTable ) +{ + *ppRoot = NULL; + + // Read in the element count. + int nElementCount = buf.GetInt(); + if ( !nElementCount ) + return true; + + int nMaxIdConflicts = min( nElementCount, g_pDataModel->GetAllocatedElementCount() ); + int nExpectedIdCopyConflicts = ( idConflictResolution == CR_FORCE_COPY || idConflictResolution == CR_COPY_NEW ) ? nMaxIdConflicts : 0; + int nBuckets = min( 0x10000, max( 16, nExpectedIdCopyConflicts / 16 ) ); // CUtlHash can only address up to 65k buckets + CUtlHash< DmIdPair_t > idmap( nBuckets, 0, 0, DmIdPair_t::Compare, DmIdPair_t::HashKey ); + + // Read + create all elements + CUtlVector<CDmElement*> elementList( 0, nElementCount ); + for ( int i = 0; i < nElementCount; ++i ) + { + char pName[2048]; + DmObjectId_t id; + + char typeBuf[ 256 ]; + const char *pType = NULL; + if ( symbolTable ) + { + unsigned short nType = buf.GetShort(); + pType = g_pDataModel->GetString( symbolTable[ nType ] ); + } + else + { + buf.GetString( typeBuf ); + pType = typeBuf; + } + + buf.GetString( pName ); + buf.Get( &id, sizeof(DmObjectId_t) ); + + if ( idConflictResolution == CR_FORCE_COPY ) + { + DmIdPair_t idpair; + CopyUniqueId( id, &idpair.m_oldId ); + CreateUniqueId( &idpair.m_newId ); + idmap.Insert( idpair ); + + CopyUniqueId( idpair.m_newId, &id ); + } + + DmElementHandle_t hElement = DMELEMENT_HANDLE_INVALID; + DmElementHandle_t hExistingElement = g_pDataModel->FindElement( id ); + if ( hExistingElement != DMELEMENT_HANDLE_INVALID ) + { + // id is already in use - need to resolve conflict + + if ( idConflictResolution == CR_DELETE_NEW ) + { + elementList.AddToTail( g_pDataModel->GetElement( hExistingElement ) ); + continue; // just don't create this element + } + else if ( idConflictResolution == CR_DELETE_OLD ) + { + g_pDataModelImp->DeleteElement( hExistingElement, HR_NEVER ); // keep the handle around until CreateElementWithFallback + hElement = CreateElementWithFallback( pType, pName, fileid, id ); + Assert( hElement == hExistingElement ); + } + else if ( idConflictResolution == CR_COPY_NEW ) + { + DmIdPair_t idpair; + CopyUniqueId( id, &idpair.m_oldId ); + CreateUniqueId( &idpair.m_newId ); + idmap.Insert( idpair ); + + hElement = CreateElementWithFallback( pType, pName, fileid, idpair.m_newId ); + } + else + Assert( 0 ); + } + + // if not found, then create it + if ( hElement == DMELEMENT_HANDLE_INVALID ) + { + hElement = CreateElementWithFallback( pType, pName, fileid, id ); + } + + CDmElement *pElement = g_pDataModel->GetElement( hElement ); + CDmeElementAccessor::MarkBeingUnserialized( pElement, true ); + elementList.AddToTail( pElement ); + } + + // The root is the 0th element + *ppRoot = elementList[ 0 ]; + + // Now read all attributes + for ( int i = 0; i < nElementCount; ++i ) + { + CDmElement *pInternal = elementList[ i ]; + UnserializeAttributes( buf, pInternal->GetFileId() == fileid ? pInternal : NULL, elementList, symbolTable ); + } + + for ( int i = 0; i < nElementCount; ++i ) + { + CDmElement *pElement = elementList[ i ]; + if ( pElement->GetFileId() == fileid ) + { + // mark all unserialized elements as done unserializing, and call Resolve() + CDmeElementAccessor::MarkBeingUnserialized( pElement, false ); + } + } + + g_pDmElementFrameworkImp->RemoveCleanElementsFromDirtyList( ); + return buf.IsValid(); +} diff --git a/datamodel/dmserializerbinary.h b/datamodel/dmserializerbinary.h new file mode 100644 index 0000000..118b685 --- /dev/null +++ b/datamodel/dmserializerbinary.h @@ -0,0 +1,27 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Outputs directly into a binary format +// +//============================================================================= + +#ifndef DMSERIALIZERBINARY_H +#define DMSERIALIZERBINARY_H + +#ifdef _WIN32 +#pragma once +#endif + + +//----------------------------------------------------------------------------- +// Forward declarations +//----------------------------------------------------------------------------- +class IDataModel; + + +//----------------------------------------------------------------------------- +// Installation methods for standard serializers +//----------------------------------------------------------------------------- +void InstallBinarySerializer( IDataModel *pFactory ); + + +#endif // DMSERIALIZERBINARY_H
\ No newline at end of file diff --git a/datamodel/dmserializerkeyvalues.cpp b/datamodel/dmserializerkeyvalues.cpp new file mode 100644 index 0000000..24a45de --- /dev/null +++ b/datamodel/dmserializerkeyvalues.cpp @@ -0,0 +1,466 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= + +#include "dmserializerkeyvalues.h" +#include "datamodel/idatamodel.h" +#include "datamodel.h" +#include "datamodel/dmelement.h" +#include "datamodel/dmattributevar.h" +#include "dmattributeinternal.h" +#include "tier1/KeyValues.h" +#include "tier1/utlbuffer.h" +#include "tier1/utlvector.h" +#include <limits.h> +#include "DmElementFramework.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + + +//----------------------------------------------------------------------------- +// Forward declarations +//----------------------------------------------------------------------------- +class CUtlBuffer; +class CBaseSceneObject; + + +//----------------------------------------------------------------------------- +// Used to remap keyvalues names +//----------------------------------------------------------------------------- +struct AttributeRemap_t +{ + const char *m_pKeyValuesName; + const char *m_pDmeName; +}; + +static AttributeRemap_t s_pAttributeRemap[] = +{ + { "type", "_type" }, // FIXME - remove this once we've made type no longer be an attribute + { "name", "_name" }, + { "id", "_id" }, // FIXME - remove this once we've made id no longer be an attribute + { NULL, NULL } +}; + + +//----------------------------------------------------------------------------- +// Serialization class for Key Values +//----------------------------------------------------------------------------- +class CDmSerializerKeyValues : public IDmSerializer +{ +public: + // Inherited from IDMSerializer + virtual const char *GetName() const { return "keyvalues"; } + virtual const char *GetDescription() const { return "KeyValues"; } + virtual bool StoresVersionInFile() const { return false; } + virtual bool IsBinaryFormat() const { return false; } + virtual int GetCurrentVersion() const { return 0; } // doesn't store a version + virtual bool Serialize( CUtlBuffer &buf, CDmElement *pRoot ); + virtual bool Unserialize( CUtlBuffer &buf, const char *pEncodingName, int nEncodingVersion, + const char *pSourceFormatName, int nSourceFormatVersion, + DmFileId_t fileid, DmConflictResolution_t idConflictResolution, CDmElement **ppRoot ); + +private: + // Methods related to serialization + void SerializeSubKeys( CUtlBuffer& buf, CDmAttribute *pSubKeys ); + bool SerializeAttributes( CUtlBuffer& buf, CDmElement *pElement ); + bool SerializeElement( CUtlBuffer& buf, CDmElement *pElement ); + + // Methods related to unserialization + DmElementHandle_t UnserializeElement( KeyValues *pKeyValues, int iNestingLevel ); + void UnserializeAttribute( CDmElement *pElement, KeyValues *pKeyValues ); + DmElementHandle_t CreateDmElement( const char *pElementType, const char *pElementName ); + CDmElement* UnserializeFromKeyValues( KeyValues *pKeyValues ); + + // Deterimines the attribute type of a keyvalue + DmAttributeType_t DetermineAttributeType( KeyValues *pKeyValues ); + + // For unserialization + CUtlVector<DmElementHandle_t> m_ElementList; + DmElementHandle_t m_hRoot; + DmFileId_t m_fileid; +}; + + +//----------------------------------------------------------------------------- +// Singleton instance +//----------------------------------------------------------------------------- +static CDmSerializerKeyValues s_DMSerializerKeyValues; + +void InstallKeyValuesSerializer( IDataModel *pFactory ) +{ + pFactory->AddSerializer( &s_DMSerializerKeyValues ); +} + + +//----------------------------------------------------------------------------- +// Serializes a single element attribute +//----------------------------------------------------------------------------- +void CDmSerializerKeyValues::SerializeSubKeys( CUtlBuffer& buf, CDmAttribute *pSubKeys ) +{ + CDmrElementArray<> array( pSubKeys ); + int c = array.Count(); + for ( int i = 0; i < c; ++i ) + { + CDmElement *pChild = array[i]; + if ( pChild ) + { + SerializeElement( buf, pChild ); + } + } +} + + +//----------------------------------------------------------------------------- +// Serializes all attributes in an element +//----------------------------------------------------------------------------- +bool CDmSerializerKeyValues::SerializeAttributes( CUtlBuffer& buf, CDmElement *pElement ) +{ + // Collect the attributes to be written + CDmAttribute **ppAttributes = ( CDmAttribute** )_alloca( pElement->AttributeCount() * sizeof( CDmAttribute* ) ); + int nAttributes = 0; + for ( CDmAttribute *pAttribute = pElement->FirstAttribute(); pAttribute; pAttribute = pAttribute->NextAttribute() ) + { + if ( pAttribute->IsFlagSet( FATTRIB_DONTSAVE | FATTRIB_STANDARD ) ) + continue; + + ppAttributes[ nAttributes++ ] = pAttribute; + } + + // Now write them all out in reverse order, since FirstAttribute is actually the *last* attribute for perf reasons + for ( int i = nAttributes - 1; i >= 0; --i ) + { + CDmAttribute *pAttribute = ppAttributes[ i ]; + Assert( pAttribute ); + + const char *pName = pAttribute->GetName(); + + // Rename "_type", "_name", or "_id" fields, since they are special fields + for ( int iAttr = 0; s_pAttributeRemap[i].m_pKeyValuesName; ++i ) + { + if ( !Q_stricmp( pName, s_pAttributeRemap[iAttr].m_pDmeName ) ) + { + pName = s_pAttributeRemap[iAttr].m_pKeyValuesName; + break; + } + } + + DmAttributeType_t nAttrType = pAttribute->GetType(); + if ( ( nAttrType == AT_ELEMENT_ARRAY ) && !Q_stricmp( pName, "subkeys" ) ) + { + SerializeSubKeys( buf, pAttribute ); + continue; + } + + buf.Printf( "\"%s\" ", pName ); + + switch( nAttrType ) + { + case AT_VOID: + case AT_STRING_ARRAY: + case AT_VOID_ARRAY: + case AT_ELEMENT: + case AT_ELEMENT_ARRAY: + Warning("KeyValues: Can't serialize attribute of type %s into KeyValues files!\n", + g_pDataModel->GetAttributeNameForType( nAttrType ) ); + buf.PutChar( '\"' ); + buf.PutChar( '\"' ); + break; + + case AT_FLOAT: + case AT_INT: + case AT_BOOL: + pAttribute->Serialize( buf ); + break; + + case AT_VECTOR4: + case AT_VECTOR3: + case AT_VECTOR2: + case AT_STRING: + default: + buf.PutChar( '\"' ); + buf.PushTab(); + pAttribute->Serialize( buf ); + buf.PopTab(); + buf.PutChar( '\"' ); + break; + } + + buf.PutChar( '\n' ); + } + + return true; +} + +bool CDmSerializerKeyValues::SerializeElement( CUtlBuffer& buf, CDmElement *pElement ) +{ + buf.Printf( "\"%s\"\n{\n", pElement->GetName() ); + buf.PushTab(); + SerializeAttributes( buf, pElement ); + buf.PopTab(); + buf.Printf( "}\n" ); + return true; +} + +bool CDmSerializerKeyValues::Serialize( CUtlBuffer &outBuf, CDmElement *pRoot ) +{ + if ( !pRoot ) + return true; + + CDmAttribute* pSubKeys = pRoot->GetAttribute( "subkeys" ); + if ( !pSubKeys ) + return true; + + //SetSerializationDelimiter( GetCStringCharConversion() ); + SerializeSubKeys( outBuf, pSubKeys ); + //SetSerializationDelimiter( NULL ); + return true; +} + + +//----------------------------------------------------------------------------- +// Creates a scene object, adds it to the element dictionary +//----------------------------------------------------------------------------- +DmElementHandle_t CDmSerializerKeyValues::CreateDmElement( const char *pElementType, const char *pElementName ) +{ + // See if we can create an element of that type + DmElementHandle_t hElement = g_pDataModel->CreateElement( pElementType, pElementName, m_fileid ); + if ( hElement == DMELEMENT_HANDLE_INVALID ) + { + Warning("KeyValues: Element uses unknown element type %s\n", pElementType ); + return DMELEMENT_HANDLE_INVALID; + } + + m_ElementList.AddToTail( hElement ); + + CDmElement *pElement = g_pDataModel->GetElement( hElement ); + CDmeElementAccessor::MarkBeingUnserialized( pElement, true ); + return hElement; +} + + +//----------------------------------------------------------------------------- +// Deterimines the attribute type of a keyvalue +//----------------------------------------------------------------------------- +DmAttributeType_t CDmSerializerKeyValues::DetermineAttributeType( KeyValues *pKeyValues ) +{ + // FIXME: Add detection of vectors/matrices? + switch( pKeyValues->GetDataType() ) + { + default: + case KeyValues::TYPE_NONE: + Assert( 0 ); + return AT_UNKNOWN; + + case KeyValues::TYPE_STRING: + { + float f1, f2, f3, f4; + if ( sscanf( pKeyValues->GetString(), "%f %f %f %f", &f1, &f2, &f3, &f4 ) == 4 ) + return AT_VECTOR4; + if ( sscanf( pKeyValues->GetString(), "%f %f %f",&f1, &f2, &f3 ) == 3 ) + return AT_VECTOR3; + if ( sscanf( pKeyValues->GetString(), "%f %f", &f1, &f2 ) == 2 ) + return AT_VECTOR2; + + int i = pKeyValues->GetInt( NULL, INT_MAX ); + if ( ( sscanf( pKeyValues->GetString(), "%d", &i ) == 1 ) && + ( !strchr( pKeyValues->GetString(), '.' ) ) ) + return AT_INT; + + if ( sscanf( pKeyValues->GetString(), "%f", &f1 ) == 1 ) + return AT_FLOAT; + + return AT_STRING; + } + + case KeyValues::TYPE_INT: + return AT_INT; + + case KeyValues::TYPE_FLOAT: + return AT_FLOAT; + + case KeyValues::TYPE_PTR: + return AT_VOID; + + case KeyValues::TYPE_COLOR: + return AT_COLOR; + } +} + + +//----------------------------------------------------------------------------- +// Reads an attribute for an element +//----------------------------------------------------------------------------- +void CDmSerializerKeyValues::UnserializeAttribute( CDmElement *pElement, KeyValues *pKeyValues ) +{ + // It's an attribute + const char *pAttributeName = pKeyValues->GetName(); + const char *pAttributeValue = pKeyValues->GetString(); + + // Convert to lower case + CUtlString pLowerName = pAttributeName; + pLowerName.ToLower(); + + // Rename "type", "name", or "id" fields, since they are special fields + for ( int i = 0; s_pAttributeRemap[i].m_pKeyValuesName; ++i ) + { + if ( !Q_stricmp( pLowerName, s_pAttributeRemap[i].m_pKeyValuesName ) ) + { + pLowerName = s_pAttributeRemap[i].m_pDmeName; + break; + } + } + + // Element types are stored out by GUID, we need to hang onto the guid and + // link it back up once all elements have been loaded from the file + DmAttributeType_t type = DetermineAttributeType( pKeyValues ); + + // In this case, we have an inlined element or element array attribute + if ( type == AT_UNKNOWN ) + { + // Assume this is an empty attribute or attribute array element + Warning("Dm Unserialize: Attempted to read an attribute (\"%s\") of an inappropriate type!\n", pLowerName.Get() ); + return; + } + + CDmAttribute *pAttribute = pElement->AddAttribute( pLowerName, type ); + if ( !pAttribute ) + { + Warning("Dm Unserialize: Attempted to read an attribute (\"%s\") of an inappropriate type!\n", pLowerName.Get() ); + return; + } + + switch( type ) + { + case AT_STRING: + { + // Strings have different delimiter rules for KeyValues, + // so let's just directly copy the string instead of going through unserialize + pAttribute->SetValue( pAttributeValue ); + } + break; + + default: + { + int nLen = Q_strlen( pAttributeValue ); + CUtlBuffer buf( pAttributeValue, nLen, CUtlBuffer::TEXT_BUFFER | CUtlBuffer::READ_ONLY ); + pAttribute->Unserialize( buf ); + } + break; + } +} + + +//----------------------------------------------------------------------------- +// Reads a single element +//----------------------------------------------------------------------------- +DmElementHandle_t CDmSerializerKeyValues::UnserializeElement( KeyValues *pKeyValues, int iNestingLevel ) +{ + const char *pElementName = pKeyValues->GetName( ); + const char *pszKeyValuesElement = g_pDataModel->GetKeyValuesElementName( pElementName, iNestingLevel ); + if ( !pszKeyValuesElement ) + { + pszKeyValuesElement = "DmElement"; + } + + DmElementHandle_t handle = CreateDmElement( pszKeyValuesElement, pElementName ); + Assert( handle != DMELEMENT_HANDLE_INVALID ); + + iNestingLevel++; + + CDmElement *pElement = g_pDataModel->GetElement( handle ); + CDmrElementArray<> subKeys; + for ( KeyValues *pSub = pKeyValues->GetFirstSubKey(); pSub != NULL ; pSub = pSub->GetNextKey() ) + { + // Read in a subkey + if ( pSub->GetDataType() == KeyValues::TYPE_NONE ) + { + if ( !subKeys.IsValid() ) + { + subKeys.Init( pElement->AddAttribute( "subkeys", AT_ELEMENT_ARRAY ) ); + } + + DmElementHandle_t hChild = UnserializeElement( pSub, iNestingLevel ); + if ( hChild != DMELEMENT_HANDLE_INVALID ) + { + subKeys.AddToTail( hChild ); + } + } + else + { + UnserializeAttribute( pElement, pSub ); + } + } + + return handle; +} + + +//----------------------------------------------------------------------------- +// Main entry point for the unserialization +//----------------------------------------------------------------------------- +CDmElement* CDmSerializerKeyValues::UnserializeFromKeyValues( KeyValues *pKeyValues ) +{ + m_ElementList.RemoveAll(); + + m_hRoot = CreateDmElement( "DmElement", "root" ); + CDmElement *pRoot = g_pDataModel->GetElement( m_hRoot ); + CDmrElementArray<> subkeys( pRoot->AddAttribute( "subkeys", AT_ELEMENT_ARRAY ) ); + + int iNestingLevel = 0; + + for ( KeyValues *pElementKey = pKeyValues; pElementKey != NULL; pElementKey = pElementKey->GetNextKey() ) + { + DmElementHandle_t hChild = UnserializeElement( pElementKey, iNestingLevel ); + if ( hChild != DMELEMENT_HANDLE_INVALID ) + { + subkeys.AddToTail( hChild ); + } + } + + // mark all unserialized elements as done unserializing, and call Resolve() + int c = m_ElementList.Count(); + for ( int i = 0; i < c; ++i ) + { + CDmElement *pElement = g_pDataModel->GetElement( m_ElementList[i] ); + CDmeElementAccessor::MarkBeingUnserialized( pElement, false ); + } + + g_pDmElementFrameworkImp->RemoveCleanElementsFromDirtyList( ); + m_ElementList.RemoveAll(); + return pRoot; +} + + +//----------------------------------------------------------------------------- +// Main entry point for the unserialization +//----------------------------------------------------------------------------- +bool CDmSerializerKeyValues::Unserialize( CUtlBuffer &buf, const char *pEncodingName, int nEncodingVersion, + const char *pSourceFormatName, int nSourceFormatVersion, + DmFileId_t fileid, DmConflictResolution_t idConflictResolution, CDmElement **ppRoot ) +{ + Assert( !V_stricmp( pEncodingName, "keyvalues" ) ); + + *ppRoot = NULL; + + KeyValues *kv = new KeyValues( "keyvalues file" ); + if ( !kv ) + return false; + + m_fileid = fileid; + + bool bOk = kv->LoadFromBuffer( "keyvalues file", buf ); + if ( bOk ) + { + //SetSerializationDelimiter( GetCStringCharConversion() ); + *ppRoot = UnserializeFromKeyValues( kv ); + //SetSerializationDelimiter( NULL ); + } + + m_fileid = DMFILEID_INVALID; + + kv->deleteThis(); + return bOk; +} diff --git a/datamodel/dmserializerkeyvalues.h b/datamodel/dmserializerkeyvalues.h new file mode 100644 index 0000000..f17b6bd --- /dev/null +++ b/datamodel/dmserializerkeyvalues.h @@ -0,0 +1,27 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= + +#ifndef DMSERIALIZERKEYVALUES_H +#define DMSERIALIZERKEYVALUES_H + +#ifdef _WIN32 +#pragma once +#endif + + +//----------------------------------------------------------------------------- +// Forward declarations +//----------------------------------------------------------------------------- +class IDataModel; + + +//----------------------------------------------------------------------------- +// Installation methods for standard serializers +//----------------------------------------------------------------------------- +void InstallKeyValuesSerializer( IDataModel *pFactory ); + + +#endif // DMSERIALIZER_H
\ No newline at end of file diff --git a/datamodel/dmserializerkeyvalues2.cpp b/datamodel/dmserializerkeyvalues2.cpp new file mode 100644 index 0000000..dbc8cb5 --- /dev/null +++ b/datamodel/dmserializerkeyvalues2.cpp @@ -0,0 +1,1377 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= + +#include "dmserializerkeyvalues2.h" +#include <ctype.h> +#include "datamodel/idatamodel.h" +#include "datamodel.h" +#include "datamodel/dmelement.h" +#include "datamodel/dmattributevar.h" +#include "dmattributeinternal.h" +#include "dmelementdictionary.h" +#include "DmElementFramework.h" +#include "tier1/utlbuffer.h" +#include <limits.h> + + +//----------------------------------------------------------------------------- +// Forward declarations +//----------------------------------------------------------------------------- +class CUtlBuffer; + + +//----------------------------------------------------------------------------- +// a simple class to keep track of a stack of valid parsed symbols +//----------------------------------------------------------------------------- +class CKeyValues2ErrorStack +{ +public: + CKeyValues2ErrorStack(); + + // Sets the filename to report with errors; sets the line number to 0 + void SetFilename( const char *pFilename ); + + // Current line control + void IncrementCurrentLine(); + void SetCurrentLine( int nLine ); + int GetCurrentLine() const; + + // entering a new keyvalues block, save state for errors + // Not save symbols instead of pointers because the pointers can move! + int Push( CUtlSymbol symName ); + + // exiting block, error isn't in this block, remove. + void Pop(); + + // Allows you to keep the same stack level, but change the name as you parse peers + void Reset( int stackLevel, CUtlSymbol symName ); + + // Hit an error, report it and the parsing stack for context + void ReportError( const char *pError, ... ); + +private: + enum + { + MAX_ERROR_STACK = 64 + }; + + CUtlSymbol m_errorStack[MAX_ERROR_STACK]; + const char *m_pFilename; + int m_nFileLine; + int m_errorIndex; + int m_maxErrorIndex; +}; + + +//----------------------------------------------------------------------------- +// Singleton instance +//----------------------------------------------------------------------------- +static CKeyValues2ErrorStack g_KeyValues2ErrorStack; + + +//----------------------------------------------------------------------------- +// Constructor +//----------------------------------------------------------------------------- +CKeyValues2ErrorStack::CKeyValues2ErrorStack() : + m_pFilename("NULL"), m_errorIndex(0), m_maxErrorIndex(0), m_nFileLine(1) +{ +} + + +//----------------------------------------------------------------------------- +// Sets the filename +//----------------------------------------------------------------------------- +void CKeyValues2ErrorStack::SetFilename( const char *pFilename ) +{ + m_pFilename = pFilename; + m_maxErrorIndex = 0; + m_nFileLine = 1; +} + + +//----------------------------------------------------------------------------- +// Current line control +//----------------------------------------------------------------------------- +void CKeyValues2ErrorStack::IncrementCurrentLine() +{ + ++m_nFileLine; +} + +void CKeyValues2ErrorStack::SetCurrentLine( int nLine ) +{ + m_nFileLine = nLine; +} + +int CKeyValues2ErrorStack::GetCurrentLine() const +{ + return m_nFileLine; +} + + +//----------------------------------------------------------------------------- +// entering a new keyvalues block, save state for errors +// Not save symbols instead of pointers because the pointers can move! +//----------------------------------------------------------------------------- +int CKeyValues2ErrorStack::Push( CUtlSymbol symName ) +{ + if ( m_errorIndex < MAX_ERROR_STACK ) + { + m_errorStack[m_errorIndex] = symName; + } + m_errorIndex++; + m_maxErrorIndex = max( m_maxErrorIndex, (m_errorIndex-1) ); + return m_errorIndex-1; +} + + +//----------------------------------------------------------------------------- +// exiting block, error isn't in this block, remove. +//----------------------------------------------------------------------------- +void CKeyValues2ErrorStack::Pop() +{ + m_errorIndex--; + Assert(m_errorIndex>=0); +} + + +//----------------------------------------------------------------------------- +// Allows you to keep the same stack level, but change the name as you parse peers +//----------------------------------------------------------------------------- +void CKeyValues2ErrorStack::Reset( int stackLevel, CUtlSymbol symName ) +{ + Assert( stackLevel >= 0 && stackLevel < m_errorIndex ); + m_errorStack[stackLevel] = symName; +} + + +//----------------------------------------------------------------------------- +// Hit an error, report it and the parsing stack for context +//----------------------------------------------------------------------------- +void CKeyValues2ErrorStack::ReportError( const char *pFmt, ... ) +{ + char temp[2048]; + + va_list args; + va_start( args, pFmt ); + Q_vsnprintf( temp, sizeof( temp ), pFmt, args ); + va_end( args ); + + char temp2[2048]; + Q_snprintf( temp2, sizeof( temp2 ), "%s(%d) : %s\n", m_pFilename, m_nFileLine, temp ); + Warning( temp2 ); + + for ( int i = 0; i < m_maxErrorIndex; i++ ) + { + if ( !m_errorStack[i].IsValid() ) + continue; + + if ( i < m_errorIndex ) + { + Warning( "%s, ", g_pDataModel->GetString( m_errorStack[i] ) ); + } + else + { + Warning( "(*%s*), ", g_pDataModel->GetString( m_errorStack[i] ) ); + } + } + Warning( "\n" ); +} + + +//----------------------------------------------------------------------------- +// a simple helper that creates stack entries as it goes in & out of scope +//----------------------------------------------------------------------------- +class CKeyValues2ErrorContext +{ +public: + CKeyValues2ErrorContext( const char *pSymName ) + { + Init( g_pDataModel->GetSymbol( pSymName ) ); + } + + CKeyValues2ErrorContext( CUtlSymbol symName ) + { + Init( symName ); + } + + ~CKeyValues2ErrorContext() + { + g_KeyValues2ErrorStack.Pop(); + } + + void Reset( CUtlSymbol symName ) + { + g_KeyValues2ErrorStack.Reset( m_stackLevel, symName ); + } + +private: + void Init( CUtlSymbol symName ) + { + m_stackLevel = g_KeyValues2ErrorStack.Push( symName ); + } + + int m_stackLevel; +}; + + +//----------------------------------------------------------------------------- +// Serialization class for Key Values 2 +//----------------------------------------------------------------------------- +class CDmSerializerKeyValues2 : public IDmSerializer +{ +public: + CDmSerializerKeyValues2( bool bFlatMode ) : m_bFlatMode( bFlatMode ) {} + + // Inherited from IDMSerializer + virtual const char *GetName() const { return m_bFlatMode ? "keyvalues2_flat" : "keyvalues2"; } + virtual const char *GetDescription() const { return m_bFlatMode ? "KeyValues2 (flat)" : "KeyValues2"; } + virtual bool StoresVersionInFile() const { return true; } + virtual bool IsBinaryFormat() const { return false; } + virtual int GetCurrentVersion() const { return 1; } + virtual bool Serialize( CUtlBuffer &buf, CDmElement *pRoot ); + virtual bool Unserialize( CUtlBuffer &buf, const char *pEncodingName, int nEncodingVersion, + const char *pSourceFormatName, int nSourceFormatVersion, + DmFileId_t fileid, DmConflictResolution_t idConflictResolution, CDmElement **ppRoot ); + +private: + enum TokenType_t + { + TOKEN_INVALID = -1, // A bogus token + TOKEN_OPEN_BRACE, // { + TOKEN_CLOSE_BRACE, // } + TOKEN_OPEN_BRACKET, // [ + TOKEN_CLOSE_BRACKET, // ] + TOKEN_COMMA, // , +// TOKEN_STRING, // Any non-quoted string + TOKEN_DELIMITED_STRING, // Any quoted string + TOKEN_INCLUDE, // #include + TOKEN_EOF, // End of buffer + }; + + // Methods related to serialization + void SerializeArrayAttribute( CUtlBuffer& buf, CDmAttribute *pAttribute ); + void SerializeElementAttribute( CUtlBuffer& buf, CDmElementSerializationDictionary &dict, CDmAttribute *pAttribute ); + void SerializeElementArrayAttribute( CUtlBuffer& buf, CDmElementSerializationDictionary &dict, CDmAttribute *pAttribute ); + bool SerializeAttributes( CUtlBuffer& buf, CDmElementSerializationDictionary &dict, CDmElement *pElement ); + bool SaveElement( CUtlBuffer& buf, CDmElementSerializationDictionary &dict, CDmElement *pElement, bool bWriteDelimiters = true ); + + // Methods related to unserialization + void EatWhitespacesAndComments( CUtlBuffer &buf ); + TokenType_t ReadToken( CUtlBuffer &buf, CUtlBuffer &token ); + DmElementDictHandle_t CreateDmElement( const char *pElementType ); + bool UnserializeAttributeValueFromToken( CDmAttribute *pAttribute, CUtlBuffer &tokenBuf ); + bool UnserializeElementAttribute( CUtlBuffer &buf, DmElementDictHandle_t hElement, const char *pAttributeName, const char *pElementType ); + bool UnserializeElementArrayAttribute( CUtlBuffer &buf, DmElementDictHandle_t hElement, const char *pAttributeName ); + bool UnserializeArrayAttribute( CUtlBuffer &buf, DmElementDictHandle_t hElement, const char *pAttributeName, DmAttributeType_t nAttrType ); + bool UnserializeAttribute( CUtlBuffer &buf, DmElementDictHandle_t hElement, const char *pAttributeName, DmAttributeType_t nAttrType ); + bool UnserializeElement( CUtlBuffer &buf, const char *pElementType, DmElementDictHandle_t *pHandle ); + bool UnserializeElement( CUtlBuffer &buf, DmElementDictHandle_t *pHandle ); + bool UnserializeElements( CUtlBuffer &buf, DmFileId_t fileid, DmConflictResolution_t idConflictResolution, CDmElement **ppRoot ); + + // For unserialization + CDmElementDictionary m_ElementDict; + DmElementDictHandle_t m_hRoot; + bool m_bFlatMode; + DmConflictResolution_t m_idConflictResolution; + DmFileId_t m_fileid; +}; + + +//----------------------------------------------------------------------------- +// Singleton instance +//----------------------------------------------------------------------------- +static CDmSerializerKeyValues2 s_DMSerializerKeyValues2( false ); +static CDmSerializerKeyValues2 s_DMSerializerKeyValues2Flat( true ); + +void InstallKeyValues2Serializer( IDataModel *pFactory ) +{ + pFactory->AddSerializer( &s_DMSerializerKeyValues2 ); + pFactory->AddSerializer( &s_DMSerializerKeyValues2Flat ); +} + + +//----------------------------------------------------------------------------- +// Serializes a single element attribute +//----------------------------------------------------------------------------- +void CDmSerializerKeyValues2::SerializeElementAttribute( CUtlBuffer& buf, CDmElementSerializationDictionary &dict, CDmAttribute *pAttribute ) +{ + CDmElement *pElement = pAttribute->GetValueElement<CDmElement>(); + if ( dict.ShouldInlineElement( pElement ) ) + { + buf.Printf( "\"%s\"\n{\n", pElement->GetTypeString() ); + if ( pElement ) + { + SaveElement( buf, dict, pElement, false ); + } + buf.Printf( "}\n" ); + } + else + { + buf.Printf( "\"%s\" \"", g_pDataModel->GetAttributeNameForType( AT_ELEMENT ) ); + if ( pElement ) + { + ::Serialize( buf, pElement->GetId() ); + } + buf.PutChar( '\"' ); + } +} + + +//----------------------------------------------------------------------------- +// Serializes an array element attribute +//----------------------------------------------------------------------------- +void CDmSerializerKeyValues2::SerializeElementArrayAttribute( CUtlBuffer& buf, CDmElementSerializationDictionary &dict, CDmAttribute *pAttribute ) +{ + CDmrElementArray<> array( pAttribute ); + + buf.Printf( "\n[\n" ); + buf.PushTab(); + + int nCount = array.Count(); + for ( int i = 0; i < nCount; ++i ) + { + CDmElement *pElement = array[i]; + if ( dict.ShouldInlineElement( pElement ) ) + { + buf.Printf( "\"%s\"\n{\n", pElement->GetTypeString() ); + if ( pElement ) + { + SaveElement( buf, dict, pElement, false ); + } + buf.PutChar( '}' ); + } + else + { + const char *pAttributeType = AttributeTypeName( AT_ELEMENT ); + buf.Printf( "\"%s\" \"", pAttributeType ); + if ( pElement ) + { + ::Serialize( buf, pElement->GetId() ); + } + buf.PutChar( '\"' ); + } + + if ( i != nCount - 1 ) + { + buf.PutChar( ',' ); + } + buf.PutChar( '\n' ); + } + + buf.PopTab(); + buf.Printf( "]" ); +} + + +//----------------------------------------------------------------------------- +// Serializes array attributes +//----------------------------------------------------------------------------- +void CDmSerializerKeyValues2::SerializeArrayAttribute( CUtlBuffer& buf, CDmAttribute *pAttribute ) +{ + CDmrGenericArray array( pAttribute ); + int nCount = array.Count(); + + buf.PutString( "\n[\n" ); + buf.PushTab(); + + for ( int i = 0; i < nCount; ++i ) + { + if ( pAttribute->GetType() != AT_STRING_ARRAY ) + { + buf.PutChar( '\"' ); + buf.PushTab(); + } + + array.GetAttribute()->SerializeElement( i, buf ); + + if ( pAttribute->GetType() != AT_STRING_ARRAY ) + { + buf.PopTab(); + buf.PutChar( '\"' ); + } + + if ( i != nCount - 1 ) + { + buf.PutChar( ',' ); + } + buf.PutChar( '\n' ); + } + buf.PopTab(); + buf.PutChar( ']' ); +} + + +//----------------------------------------------------------------------------- +// Serializes all attributes in an element +//----------------------------------------------------------------------------- +bool CDmSerializerKeyValues2::SerializeAttributes( CUtlBuffer& buf, CDmElementSerializationDictionary &dict, CDmElement *pElement ) +{ + // Collect the attributes to be written + CDmAttribute **ppAttributes = ( CDmAttribute** )_alloca( pElement->AttributeCount() * sizeof( CDmAttribute* ) ); + int nAttributes = 0; + for ( CDmAttribute *pAttribute = pElement->FirstAttribute(); pAttribute; pAttribute = pAttribute->NextAttribute() ) + { + if ( pAttribute->IsFlagSet( FATTRIB_DONTSAVE ) ) + continue; + + ppAttributes[ nAttributes++ ] = pAttribute; + } + + // Now write them all out in reverse order, since FirstAttribute is actually the *last* attribute for perf reasons + for ( int i = nAttributes - 1; i >= 0; --i ) + { + CDmAttribute *pAttribute = ppAttributes[ i ]; + Assert( pAttribute ); + + const char *pName = pAttribute->GetName( ); + DmAttributeType_t nAttrType = pAttribute->GetType(); + if ( nAttrType != AT_ELEMENT ) + { + buf.Printf( "\"%s\" \"%s\" ", pName, g_pDataModel->GetAttributeNameForType( nAttrType ) ); + } + else + { + // Elements either serialize their type name or "element" depending on whether they are inlined + buf.Printf( "\"%s\" ", pName ); + } + + switch( nAttrType ) + { + default: + if ( nAttrType >= AT_FIRST_ARRAY_TYPE ) + { + SerializeArrayAttribute( buf, pAttribute ); + } + else + { + if ( pAttribute->SerializesOnMultipleLines() ) + { + buf.PutChar( '\n' ); + } + + buf.PutChar( '\"' ); + buf.PushTab(); + pAttribute->Serialize( buf ); + buf.PopTab(); + buf.PutChar( '\"' ); + } + break; + + case AT_STRING: + // Don't explicitly add string delimiters; serialization does that. + pAttribute->Serialize( buf ); + break; + + case AT_ELEMENT: + SerializeElementAttribute( buf, dict, pAttribute ); + break; + + case AT_ELEMENT_ARRAY: + SerializeElementArrayAttribute( buf, dict, pAttribute ); + break; + } + + buf.PutChar( '\n' ); + } + + return true; +} + +bool CDmSerializerKeyValues2::SaveElement( CUtlBuffer& buf, CDmElementSerializationDictionary &dict, CDmElement *pElement, bool bWriteDelimiters ) +{ + if ( bWriteDelimiters ) + { + buf.Printf( "\"%s\"\n{\n", pElement->GetTypeString() ); + } + buf.PushTab(); + + // explicitly serialize id, now that it's no longer an attribute + buf.Printf( "\"id\" \"%s\" ", g_pDataModel->GetAttributeNameForType( AT_OBJECTID ) ); + buf.PutChar( '\"' ); + ::Serialize( buf, pElement->GetId() ); + buf.PutString( "\"\n" ); + + SerializeAttributes( buf, dict, pElement ); + + buf.PopTab(); + if ( bWriteDelimiters ) + { + buf.Printf( "}\n" ); + } + return true; +} + +bool CDmSerializerKeyValues2::Serialize( CUtlBuffer &outBuf, CDmElement *pRoot ) +{ + SetSerializationDelimiter( GetCStringCharConversion() ); + SetSerializationArrayDelimiter( "," ); + + // Save elements, attribute links + CDmElementSerializationDictionary dict; + dict.BuildElementList( pRoot, m_bFlatMode ); + + // Save elements to buffer + DmElementDictHandle_t i; + for ( i = dict.FirstRootElement(); i != ELEMENT_DICT_HANDLE_INVALID; i = dict.NextRootElement(i) ) + { + SaveElement( outBuf, dict, dict.GetRootElement( i ) ); + outBuf.PutChar( '\n' ); + } + + SetSerializationDelimiter( NULL ); + SetSerializationArrayDelimiter( NULL ); + + return true; +} + + +//----------------------------------------------------------------------------- +// Eats whitespaces and c++ style comments +//----------------------------------------------------------------------------- +#pragma warning (disable:4706) + +void CDmSerializerKeyValues2::EatWhitespacesAndComments( CUtlBuffer &buf ) +{ + // eating white spaces and remarks loop + int nMaxPut = buf.TellMaxPut() - buf.TellGet(); + int nOffset = 0; + while ( nOffset < nMaxPut ) + { + // Eat whitespaces, keep track of line count + const char *pPeek = NULL; + while ( pPeek = (const char *)buf.PeekGet( sizeof(char), nOffset ) ) + { + if ( !V_isspace( *pPeek ) ) + break; + + if ( *pPeek == '\n' ) + { + g_KeyValues2ErrorStack.IncrementCurrentLine(); + } + if ( ++nOffset >= nMaxPut ) + break; + } + + // If we don't have a a c++ style comment next, we're done + pPeek = (const char *)buf.PeekGet( 2 * sizeof(char), nOffset ); + if ( ( nOffset >= nMaxPut ) || !pPeek || ( pPeek[0] != '/' ) || ( pPeek[1] != '/' ) ) + break; + + // Deal with c++ style comments + nOffset += 2; + + // read complete line + while ( pPeek = (const char *)buf.PeekGet( sizeof(char), nOffset ) ) + { + if ( *pPeek == '\n' ) + break; + if ( ++nOffset >= nMaxPut ) + break; + } + + g_KeyValues2ErrorStack.IncrementCurrentLine(); + } + + buf.SeekGet( CUtlBuffer::SEEK_CURRENT, nOffset ); +} + +#pragma warning (default:4706) + +//----------------------------------------------------------------------------- +// Reads a single token, points the token utlbuffer at it +//----------------------------------------------------------------------------- +CDmSerializerKeyValues2::TokenType_t CDmSerializerKeyValues2::ReadToken( CUtlBuffer &buf, CUtlBuffer &token ) +{ + EatWhitespacesAndComments( buf ); + + // if message text buffers go over this size + // change this value to make sure they will fit + // affects loading of last active chat window + if ( !buf.IsValid() || ( buf.TellGet() == buf.TellMaxPut() ) ) + return TOKEN_EOF; + + // Compute token length and type + int nLength = 0; + TokenType_t t = TOKEN_INVALID; + char c = *((const char *)buf.PeekGet()); + switch( c ) + { + case '{': + nLength = 1; + t = TOKEN_OPEN_BRACE; + break; + + case '}': + nLength = 1; + t = TOKEN_CLOSE_BRACE; + break; + + case '[': + nLength = 1; + t = TOKEN_OPEN_BRACKET; + break; + + case ']': + nLength = 1; + t = TOKEN_CLOSE_BRACKET; + break; + + case ',': + nLength = 1; + t = TOKEN_COMMA; + break; + + case '\"': + // NOTE: The -1 is because peek includes room for the /0 + nLength = buf.PeekDelimitedStringLength( GetCStringCharConversion(), false ) - 1; + if ( (nLength <= 1) || ( *(const char *)buf.PeekGet( nLength - 1 ) != '\"' )) + { + g_KeyValues2ErrorStack.ReportError( "Unexpected EOF in quoted string" ); + t = TOKEN_INVALID; + } + else + { + t = TOKEN_DELIMITED_STRING; + } + break; + + default: + t = TOKEN_INVALID; + break; + } + + token.EnsureCapacity( nLength ); + buf.Get( token.Base(), nLength ); + token.SeekGet( CUtlBuffer::SEEK_HEAD, 0 ); + token.SeekPut( CUtlBuffer::SEEK_HEAD, nLength ); + + // Count the number of crs in the token + update the current line + const char *pMem = (const char *)token.Base(); + for ( int i = 0; i < nLength; ++i ) + { + if ( pMem[i] == '\n' ) + { + g_KeyValues2ErrorStack.IncrementCurrentLine(); + } + } + + return t; +} + + +//----------------------------------------------------------------------------- +// Creates a scene object, adds it to the element dictionary +//----------------------------------------------------------------------------- +DmElementDictHandle_t CDmSerializerKeyValues2::CreateDmElement( const char *pElementType ) +{ + // See if we can create an element of that type + DmElementHandle_t hElement = g_pDataModel->CreateElement( pElementType, "", m_fileid ); + if ( hElement == DMELEMENT_HANDLE_INVALID ) + { + g_KeyValues2ErrorStack.ReportError("Element uses unknown element type %s\n", pElementType ); + return ELEMENT_DICT_HANDLE_INVALID; + } + + CDmElement *pElement = g_pDataModel->GetElement( hElement ); + CDmeElementAccessor::MarkBeingUnserialized( pElement, true ); + return m_ElementDict.InsertElement( pElement ); +} + + +//----------------------------------------------------------------------------- +// Reads an attribute for an element +//----------------------------------------------------------------------------- +bool CDmSerializerKeyValues2::UnserializeElementAttribute( CUtlBuffer &buf, DmElementDictHandle_t hElement, const char *pAttributeName, const char *pElementType ) +{ + CDmElement *pElement = m_ElementDict.GetElement( hElement ); + CDmAttribute *pAttribute = pElement->AddAttribute( pAttributeName, AT_ELEMENT ); + if ( !pAttribute ) + { + g_KeyValues2ErrorStack.ReportError("Attempted to read an attribute (\"%s\") of unknown type %s!\n", pAttributeName, pElementType ); + return false; + } + + DmElementDictHandle_t h; + bool bOk = UnserializeElement( buf, pElementType, &h ); + if ( bOk ) + { + CDmElement *pNewElement = m_ElementDict.GetElement( h ); + pAttribute->SetValue( pNewElement ? pNewElement->GetHandle() : DMELEMENT_HANDLE_INVALID ); + } + return bOk; +} + + +//----------------------------------------------------------------------------- +// Reads an attribute for an element array +//----------------------------------------------------------------------------- +bool CDmSerializerKeyValues2::UnserializeElementArrayAttribute( CUtlBuffer &buf, DmElementDictHandle_t hElement, const char *pAttributeName ) +{ + CDmElement *pElement = m_ElementDict.GetElement( hElement ); + CDmAttribute *pAttribute = pElement->AddAttribute( pAttributeName, AT_ELEMENT_ARRAY ); + if ( !pAttribute ) + { + g_KeyValues2ErrorStack.ReportError("Attempted to read an attribute (\"%s\") of an inappropriate type!\n", pAttributeName ); + return false; + } + + // Arrays first must have a '[' specified + TokenType_t token; + CUtlBuffer tokenBuf( 0, 0, CUtlBuffer::TEXT_BUFFER ); + CUtlCharConversion *pConv; + token = ReadToken( buf, tokenBuf ); + if ( token != TOKEN_OPEN_BRACKET ) + { + g_KeyValues2ErrorStack.ReportError( "Expecting '[', didn't find it!" ); + return false; + } + + int nElementIndex = 0; + + // Now read a list of array values, separated by commas + while ( buf.IsValid() ) + { + token = ReadToken( buf, tokenBuf ); + if ( token == TOKEN_INVALID || token == TOKEN_EOF ) + { + g_KeyValues2ErrorStack.ReportError( "Expecting ']', didn't find it!" ); + return false; + } + + // Then, keep reading until we hit a ']' + if ( token == TOKEN_CLOSE_BRACKET ) + break; + + // If we've already read in an array value, we need to read a comma next + if ( nElementIndex > 0 ) + { + if ( token != TOKEN_COMMA ) + { + g_KeyValues2ErrorStack.ReportError( "Expecting ',', didn't find it!" ); + return false; + } + + // Read in the next thing, which should be a value + token = ReadToken( buf, tokenBuf ); + } + + // Ok, we must be reading an array type value + if ( token != TOKEN_DELIMITED_STRING ) + { + g_KeyValues2ErrorStack.ReportError( "Expecting element type, didn't find it!" ); + return false; + } + + // Get the element type out + pConv = GetCStringCharConversion(); + int nLength = tokenBuf.PeekDelimitedStringLength( pConv ); + char *pElementType = (char*)stackalloc( nLength * sizeof(char) ); + tokenBuf.GetDelimitedString( pConv, pElementType, nLength ); + + // Use the element type to figure out if we're using a element reference or an inlined element + if ( !Q_strncmp( pElementType, g_pDataModel->GetAttributeNameForType( AT_ELEMENT ), nLength ) ) + { + token = ReadToken( buf, tokenBuf ); + + // Ok, we must be reading an array type value + if ( token != TOKEN_DELIMITED_STRING ) + { + g_KeyValues2ErrorStack.ReportError( "Expecting element reference, didn't find it!" ); + return false; + } + + // Get the element type out + pConv = GetCStringCharConversion(); + nLength = tokenBuf.PeekDelimitedStringLength( pConv ); + char *pElementId = (char*)stackalloc( nLength * sizeof(char) ); + tokenBuf.GetDelimitedString( pConv, pElementId, nLength ); + + DmObjectId_t id; + if ( !UniqueIdFromString( &id, pElementId ) ) + { + g_KeyValues2ErrorStack.ReportError( "Encountered invalid element ID data!" ); + return false; + } + + Assert( IsUniqueIdValid( id ) ); + m_ElementDict.AddArrayAttribute( pAttribute, id ); + } + else + { + DmElementDictHandle_t hArrayElement; + bool bOk = UnserializeElement( buf, pElementType, &hArrayElement ); + if ( !bOk ) + return false; + m_ElementDict.AddArrayAttribute( pAttribute, hArrayElement ); + } + + // Ok, we've read in another value + ++nElementIndex; + } + + return true; +} + + +//----------------------------------------------------------------------------- +// Unserializes an attribute from a token buffer +//----------------------------------------------------------------------------- +bool CDmSerializerKeyValues2::UnserializeAttributeValueFromToken( CDmAttribute *pAttribute, CUtlBuffer &tokenBuf ) +{ + // NOTE: This code is necessary because the attribute code is using Scanf + // which is not really friendly toward delimiters, so we must pass in + // non-delimited buffers. Sucky. There must be a better way of doing this + const char *pBuf = (const char*)tokenBuf.Base(); + int nLength = tokenBuf.TellMaxPut(); + char *pTemp = (char*)stackalloc( nLength + 1 ); + + bool bIsString = ( pAttribute->GetType() == AT_STRING ) || ( pAttribute->GetType() == AT_STRING_ARRAY ); + if ( !bIsString ) + { + nLength = tokenBuf.PeekDelimitedStringLength( GetCStringCharConversion() ); + tokenBuf.GetDelimitedString( GetCStringCharConversion(), pTemp, nLength + 1 ); + pBuf = pTemp; + } + else + { + SetSerializationDelimiter( GetCStringCharConversion() ); + } + + bool bOk; + CUtlBuffer buf( pBuf, nLength, CUtlBuffer::TEXT_BUFFER | CUtlBuffer::READ_ONLY ); + if ( pAttribute->GetType() < AT_FIRST_ARRAY_TYPE ) + { + bOk = pAttribute->Unserialize( buf ); + } + else + { + bOk = pAttribute->UnserializeElement( buf ); + } + + if ( bIsString ) + { + SetSerializationDelimiter( NULL ); + } + + return bOk; +} + + +//----------------------------------------------------------------------------- +// Reads an attribute for an element array +//----------------------------------------------------------------------------- +bool CDmSerializerKeyValues2::UnserializeArrayAttribute( CUtlBuffer &buf, DmElementDictHandle_t hElement, const char *pAttributeName, DmAttributeType_t nAttrType ) +{ + CDmElement *pElement = m_ElementDict.GetElement( hElement ); + CDmAttribute *pAttribute = pElement->AddAttribute( pAttributeName, nAttrType ); + if ( !pAttribute ) + { + g_KeyValues2ErrorStack.ReportError("Attempted to read an attribute (\"%s\") of an inappropriate type %s!\n", + pAttributeName, g_pDataModel->GetAttributeNameForType( nAttrType ) ); + return false; + } + + // Arrays first must have a '[' specified + TokenType_t token; + CUtlBuffer tokenBuf( 0, 0, CUtlBuffer::TEXT_BUFFER ); + token = ReadToken( buf, tokenBuf ); + if ( token != TOKEN_OPEN_BRACKET ) + { + g_KeyValues2ErrorStack.ReportError( "Expecting '[', didn't find it!" ); + return false; + } + + int nElementIndex = 0; + + // Now read a list of array values, separated by commas + while ( buf.IsValid() ) + { + token = ReadToken( buf, tokenBuf ); + if ( token == TOKEN_INVALID || token == TOKEN_EOF ) + { + g_KeyValues2ErrorStack.ReportError( "Expecting ']', didn't find it!" ); + return false; + } + + // Then, keep reading until we hit a ']' + if ( token == TOKEN_CLOSE_BRACKET ) + break; + + // If we've already read in an array value, we need to read a comma next + if ( nElementIndex > 0 ) + { + if ( token != TOKEN_COMMA ) + { + g_KeyValues2ErrorStack.ReportError( "Expecting ',', didn't find it!" ); + return false; + } + + // Read in the next thing, which should be a value + token = ReadToken( buf, tokenBuf ); + } + + // Ok, we must be reading an attributearray value + if ( token != TOKEN_DELIMITED_STRING ) + { + g_KeyValues2ErrorStack.ReportError( "Expecting array attribute value, didn't find it!" ); + return false; + } + + if ( !UnserializeAttributeValueFromToken( pAttribute, tokenBuf ) ) + { + g_KeyValues2ErrorStack.ReportError("Error reading in array attribute \"%s\" element %d", pAttributeName, nElementIndex ); + return false; + } + + // Ok, we've read in another value + ++nElementIndex; + } + + return true; +} + + +//----------------------------------------------------------------------------- +// Reads an attribute for an element +//----------------------------------------------------------------------------- +bool CDmSerializerKeyValues2::UnserializeAttribute( CUtlBuffer &buf, + DmElementDictHandle_t hElement, const char *pAttributeName, DmAttributeType_t nAttrType ) +{ + // Read the attribute value + CUtlBuffer tokenBuf( 0, 0, CUtlBuffer::TEXT_BUFFER ); + TokenType_t token = ReadToken( buf, tokenBuf ); + if ( token != TOKEN_DELIMITED_STRING ) + { + g_KeyValues2ErrorStack.ReportError( "Expecting quoted attribute value for attribute \"%s\", didn't find one!", pAttributeName ); + return false; + } + + if ( ( nAttrType == AT_OBJECTID ) && !V_stricmp( pAttributeName, "id" ) ) + { + CUtlCharConversion *pConv = GetCStringCharConversion(); + int nLength = tokenBuf.PeekDelimitedStringLength( pConv ); + char *pElementId = (char*)stackalloc( nLength * sizeof(char) ); + tokenBuf.GetDelimitedString( pConv, pElementId, nLength ); + + DmObjectId_t id; + if ( !UniqueIdFromString( &id, pElementId ) ) + { + g_KeyValues2ErrorStack.ReportError( "Encountered invalid element ID data!" ); + return false; + } + + m_ElementDict.SetElementId( hElement, id, m_idConflictResolution ); + + return true; + } + + CDmElement *pElement = m_ElementDict.GetElement( hElement ); + CDmAttribute *pAttribute = pElement->AddAttribute( pAttributeName, nAttrType ); + if ( !pAttribute ) + { + g_KeyValues2ErrorStack.ReportError("Attempted to read an attribute (\"%s\") of an inappropriate type %s!\n", + pAttributeName, g_pDataModel->GetAttributeNameForType( nAttrType ) ); + return false; + } + + switch( nAttrType ) + { + case AT_ELEMENT: + { + // Get the attribute value out + CUtlCharConversion *pConv = GetCStringCharConversion(); + int nLength = tokenBuf.PeekDelimitedStringLength( pConv ); + char *pAttributeValue = (char*)stackalloc( nLength * sizeof(char) ); + tokenBuf.GetDelimitedString( pConv, pAttributeValue, nLength ); + + // No string? that's ok, it means we have a NULL pointer + if ( !pAttributeValue[0] ) + return true; + + DmObjectId_t id; + if ( !UniqueIdFromString( &id, pAttributeValue ) ) + { + g_KeyValues2ErrorStack.ReportError("Invalid format for element ID encountered for attribute \"%s\"", pAttributeName ); + return false; + } + + m_ElementDict.AddAttribute( pAttribute, id ); + } + return true; + + default: + if ( UnserializeAttributeValueFromToken( pAttribute, tokenBuf ) ) + return true; + + g_KeyValues2ErrorStack.ReportError("Error reading attribute \"%s\"", pAttributeName ); + return false; + } +} + + +/* +//----------------------------------------------------------------------------- +// Purpose: +// Input : includedKeys - +//----------------------------------------------------------------------------- +void KeyValues::AppendIncludedKeys( CUtlVector< KeyValues * >& includedKeys ) +{ + // Append any included keys, too... + int includeCount = includedKeys.Count(); + int i; + for ( i = 0; i < includeCount; i++ ) + { + KeyValues *kv = includedKeys[ i ]; + Assert( kv ); + + KeyValues *insertSpot = this; + while ( insertSpot->GetNextKey() ) + { + insertSpot = insertSpot->GetNextKey(); + } + + insertSpot->SetNextKey( kv ); + } +} + +void KeyValues::ParseIncludedKeys( char const *resourceName, const char *filetoinclude, + IBaseFileSystem* pFileSystem, const char *pPathID, CUtlVector< KeyValues * >& includedKeys ) +{ + Assert( resourceName ); + Assert( filetoinclude ); + Assert( pFileSystem ); + + // Load it... + if ( !pFileSystem ) + { + return; + } + + // Get relative subdirectory + char fullpath[ 512 ]; + Q_strncpy( fullpath, resourceName, sizeof( fullpath ) ); + + // Strip off characters back to start or first / + bool done = false; + int len = Q_strlen( fullpath ); + while ( !done ) + { + if ( len <= 0 ) + { + break; + } + + if ( fullpath[ len - 1 ] == '\\' || + fullpath[ len - 1 ] == '/' ) + { + break; + } + + // zero it + fullpath[ len - 1 ] = 0; + --len; + } + + // Append included file + Q_strncat( fullpath, filetoinclude, sizeof( fullpath ), COPY_ALL_CHARACTERS ); + + KeyValues *newKV = new KeyValues( fullpath ); + + // CUtlSymbol save = s_CurrentFileSymbol; // did that had any use ??? + + newKV->UsesEscapeSequences( m_bHasEscapeSequences ); // use same format as parent + + if ( newKV->LoadFromFile( pFileSystem, fullpath, pPathID ) ) + { + includedKeys.AddToTail( newKV ); + } + else + { + DevMsg( "KeyValues::ParseIncludedKeys: Couldn't load included keyvalue file %s\n", fullpath ); + newKV->deleteThis(); + } + + // s_CurrentFileSymbol = save; +} + +//----------------------------------------------------------------------------- +// Read from a buffer... +//----------------------------------------------------------------------------- +bool KeyValues::LoadFromBuffer( char const *resourceName, const char *pBuffer, IBaseFileSystem* pFileSystem , const char *pPathID ) +{ + char *pfile = const_cast<char *>(pBuffer); + + KeyValues *pPreviousKey = NULL; + KeyValues *pCurrentKey = this; + CUtlVector< KeyValues * > includedKeys; + bool wasQuoted; + g_KeyValues2ErrorStack.SetFilename( resourceName ); + do + { + // the first thing must be a key + const char *s = ReadToken( &pfile, wasQuoted ); + + if ( !pfile || !s || *s == 0 ) + break; + + if ( !Q_stricmp( s, "#include" ) ) // special include macro (not a key name) + { + s = ReadToken( &pfile, wasQuoted ); + // Name of subfile to load is now in s + + if ( !s || *s == 0 ) + { + g_KeyValues2ErrorStack.ReportError("#include is NULL " ); + } + else + { + ParseIncludedKeys( resourceName, s, pFileSystem, pPathID, includedKeys ); + } + + continue; + } + + if ( !pCurrentKey ) + { + pCurrentKey = new KeyValues( s ); + Assert( pCurrentKey ); + + pCurrentKey->UsesEscapeSequences( m_bHasEscapeSequences ); // same format has parent use + + if ( pPreviousKey ) + { + pPreviousKey->SetNextKey( pCurrentKey ); + } + } + else + { + pCurrentKey->SetName( s ); + } + + // get the '{' + s = ReadToken( &pfile, wasQuoted ); + + if ( s && *s == '{' && !wasQuoted ) + { + // header is valid so load the file + pCurrentKey->RecursiveLoadFromBuffer( resourceName, &pfile ); + } + else + { + g_KeyValues2ErrorStack.ReportError("LoadFromBuffer: missing {" ); + } + + pPreviousKey = pCurrentKey; + pCurrentKey = NULL; + } while ( pfile != NULL ); + + AppendIncludedKeys( includedKeys ); + + g_KeyValues2ErrorStack.SetFilename( "" ); + + return true; +} +*/ + + +//----------------------------------------------------------------------------- +// Unserializes a single element given the type name +//----------------------------------------------------------------------------- +bool CDmSerializerKeyValues2::UnserializeElement( CUtlBuffer &buf, const char *pElementType, DmElementDictHandle_t *pHandle ) +{ + *pHandle = ELEMENT_DICT_HANDLE_INVALID; + + // Create the element + DmElementDictHandle_t hElement = CreateDmElement( pElementType ); + if ( hElement == ELEMENT_DICT_HANDLE_INVALID ) + return false; + + // Report errors relative to this type name + CKeyValues2ErrorContext errorReport( pElementType ); + + TokenType_t token; + CUtlBuffer tokenBuf( 0, 0, CUtlBuffer::TEXT_BUFFER ); + CUtlCharConversion *pConv; + int nLength; + + // Then we expect a '{' + token = ReadToken( buf, tokenBuf ); + if ( token != TOKEN_OPEN_BRACE ) + { + g_KeyValues2ErrorStack.ReportError( "Expecting '{', didn't find it!" ); + return false; + } + + while ( buf.IsValid() ) + { + token = ReadToken( buf, tokenBuf ); + if ( token == TOKEN_INVALID || token == TOKEN_EOF ) + { + g_KeyValues2ErrorStack.ReportError( "Expecting '}', didn't find it!" ); + return false; + } + + // Then, keep reading until we hit a '}' + if ( token == TOKEN_CLOSE_BRACE ) + break; + + // Ok, we must be reading an attribute + if ( token != TOKEN_DELIMITED_STRING ) + { + g_KeyValues2ErrorStack.ReportError( "Expecting attribute name, didn't find it!" ); + return false; + } + + // First, read an attribute name + pConv = GetCStringCharConversion(); + nLength = tokenBuf.PeekDelimitedStringLength( pConv ); + char *pAttributeName = (char*)stackalloc( nLength * sizeof(char) ); + tokenBuf.GetDelimitedString( pConv, pAttributeName, nLength ); + + // Next, read an attribute type + token = ReadToken( buf, tokenBuf ); + if ( token != TOKEN_DELIMITED_STRING ) + { + g_KeyValues2ErrorStack.ReportError( "Expecting attribute type for attribute %s, didn't find it!", pAttributeName ); + return false; + } + + pConv = GetCStringCharConversion(); + nLength = tokenBuf.PeekDelimitedStringLength( pConv ); + char *pAttributeType = (char*)stackalloc( nLength * sizeof(char) ); + tokenBuf.GetDelimitedString( pConv, pAttributeType, nLength ); + DmAttributeType_t nAttrType = g_pDataModel->GetAttributeTypeForName( pAttributeType ); + + // Next, read an attribute value + bool bOk = true; + switch( nAttrType ) + { + case AT_UNKNOWN: + bOk = UnserializeElementAttribute( buf, hElement, pAttributeName, pAttributeType ); + break; + + case AT_ELEMENT_ARRAY: + bOk = UnserializeElementArrayAttribute( buf, hElement, pAttributeName ); + break; + + default: + if ( nAttrType >= AT_FIRST_ARRAY_TYPE ) + { + bOk = UnserializeArrayAttribute( buf, hElement, pAttributeName, nAttrType ); + } + else + { + bOk = UnserializeAttribute( buf, hElement, pAttributeName, nAttrType ); + } + break; + } + + if ( !bOk ) + return false; + } + + *pHandle = hElement; + return true; +} + + +//----------------------------------------------------------------------------- +// Unserializes a single element +//----------------------------------------------------------------------------- +bool CDmSerializerKeyValues2::UnserializeElement( CUtlBuffer &buf, DmElementDictHandle_t *pHandle ) +{ + *pHandle = ELEMENT_DICT_HANDLE_INVALID; + + // First, read the type name + CUtlBuffer tokenBuf( 0, 0, CUtlBuffer::TEXT_BUFFER ); + CUtlCharConversion* pConv; + + TokenType_t token = ReadToken( buf, tokenBuf ); + if ( token == TOKEN_INVALID ) + return false; + + if ( token == TOKEN_EOF ) + return true; + + // Get the type name out + if ( token != TOKEN_DELIMITED_STRING ) + { + g_KeyValues2ErrorStack.ReportError( "Expecting element type name, didn't find it!" ); + return false; + } + + pConv = GetCStringCharConversion(); + int nLength = tokenBuf.PeekDelimitedStringLength( pConv ); + char *pTypeName = (char*)stackalloc( nLength * sizeof(char) ); + tokenBuf.GetDelimitedString( pConv, pTypeName, nLength ); + + return UnserializeElement( buf, pTypeName, pHandle ); +} + + +//----------------------------------------------------------------------------- +// Main entry point for the unserialization +//----------------------------------------------------------------------------- +bool CDmSerializerKeyValues2::Unserialize( CUtlBuffer &buf, const char *pEncodingName, int nEncodingVersion, + const char *pSourceFormatName, int nSourceFormatVersion, + DmFileId_t fileid, DmConflictResolution_t idConflictResolution, CDmElement **ppRoot ) +{ + bool bSuccess = UnserializeElements( buf, fileid, idConflictResolution, ppRoot ); + if ( !bSuccess ) + return false; + + return g_pDataModel->UpdateUnserializedElements( pSourceFormatName, nSourceFormatVersion, fileid, idConflictResolution, ppRoot ); +} + +bool CDmSerializerKeyValues2::UnserializeElements( CUtlBuffer &buf, DmFileId_t fileid, DmConflictResolution_t idConflictResolution, CDmElement **ppRoot ) +{ + *ppRoot = NULL; + + m_idConflictResolution = idConflictResolution; + + m_fileid = fileid; + + g_KeyValues2ErrorStack.SetFilename( g_pDataModel->GetFileName( fileid ) ); + m_hRoot = ELEMENT_DICT_HANDLE_INVALID; + m_ElementDict.Clear(); + + bool bOk = true; + while ( buf.IsValid() ) + { + DmElementDictHandle_t h; + bOk = UnserializeElement( buf, &h ); + if ( !bOk || ( h == ELEMENT_DICT_HANDLE_INVALID ) ) + break; + + if ( m_hRoot == ELEMENT_DICT_HANDLE_INVALID ) + { + m_hRoot = h; + } + } + + // do this *before* getting the root, since the first element might be deleted due to id conflicts + m_ElementDict.HookUpElementReferences(); + + *ppRoot = m_ElementDict.GetElement( m_hRoot ); + + // mark all unserialized elements as done unserializing, and call Resolve() + for ( DmElementDictHandle_t h = m_ElementDict.FirstElement(); + h != ELEMENT_DICT_HANDLE_INVALID; + h = m_ElementDict.NextElement( h ) ) + { + CDmElement *pElement = m_ElementDict.GetElement( h ); + if ( !pElement ) + continue; + + CDmeElementAccessor::MarkBeingUnserialized( pElement, false ); + } + + m_fileid = DMFILEID_INVALID; + + g_pDmElementFrameworkImp->RemoveCleanElementsFromDirtyList( ); + m_ElementDict.Clear(); + return bOk; +} diff --git a/datamodel/dmserializerkeyvalues2.h b/datamodel/dmserializerkeyvalues2.h new file mode 100644 index 0000000..6649f11 --- /dev/null +++ b/datamodel/dmserializerkeyvalues2.h @@ -0,0 +1,27 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= + +#ifndef DMSERIALIZERKEYVALUES2_H +#define DMSERIALIZERKEYVALUES2_H + +#ifdef _WIN32 +#pragma once +#endif + + +//----------------------------------------------------------------------------- +// Forward declarations +//----------------------------------------------------------------------------- +class IDataModel; + + +//----------------------------------------------------------------------------- +// Installation methods for standard serializers +//----------------------------------------------------------------------------- +void InstallKeyValues2Serializer( IDataModel *pFactory ); + + +#endif // DMSERIALIZERKEYVALUES2_H
\ No newline at end of file diff --git a/datamodel/undomanager.cpp b/datamodel/undomanager.cpp new file mode 100644 index 0000000..8ae513b --- /dev/null +++ b/datamodel/undomanager.cpp @@ -0,0 +1,444 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= + +#include "undomanager.h" +#include "datamodel.h" + +extern CDataModel *g_pDataModelImp; + +CUtlSymbolTableMT CUndoManager::s_UndoSymbolTable; + + +CUndoManager::CUndoManager( ) : + m_bEnabled( true ), + m_bDiscarded( false ), + m_nMaxUndoDepth( 4096 ), + m_nNesting( 0 ), + m_nNotifyNesting( 0 ), + m_bStreamStart( false ), + m_bTrace( false ), + m_bSuppressingNotify( false ), + m_nItemsAddedSinceStartOfStream( 0 ), + m_nNotifySource( 0 ), + m_nNotifyFlags( 0 ), + m_nChainingID( 0 ), + m_PreviousChainingID( 0 ) +{ +} + +CUndoManager::~CUndoManager() +{ +} + +void CUndoManager::Shutdown() +{ + WipeUndo(); + WipeRedo(); +} + +bool CUndoManager::InstallNotificationCallback( IDmNotify *pNotify ) +{ + if ( m_Notifiers.Find( pNotify ) >= 0 ) + return false; + m_Notifiers.AddToTail( pNotify ); + return true; +} + +void CUndoManager::RemoveNotificationCallback( IDmNotify *pNotify ) +{ + m_Notifiers.FindAndRemove( pNotify ); +} + +bool CUndoManager::IsSuppressingNotify( ) const +{ + return m_bSuppressingNotify; +} + +void CUndoManager::SetSuppressingNotify( bool bSuppress ) +{ + m_bSuppressingNotify = bSuppress; +} + +void CUndoManager::Trace( const char *fmt, ... ) +{ + if ( !m_bTrace ) + return; + + char str[ 2048 ]; + va_list argptr; + va_start( argptr, fmt ); + _vsnprintf( str, sizeof( str ) - 1, fmt, argptr ); + va_end( argptr ); + str[ sizeof( str ) - 1 ] = 0; + + char spaces[ 128 ]; + Q_memset( spaces, 0, sizeof( spaces ) ); + for ( int i = 0; i < ( m_nNesting * 3 ); ++i ) + { + if ( i >= 127 ) + break; + spaces[ i ] = ' '; + } + + Msg( "%s%s", spaces, str ); +} + +void CUndoManager::SetUndoDepth( int nMaxUndoDepth ) +{ + Assert( !HasUndoData() ); + m_nMaxUndoDepth = nMaxUndoDepth; +} + +void CUndoManager::EnableUndo() +{ + m_bEnabled = true; +} + +void CUndoManager::DisableUndo() +{ + m_bEnabled = false; +} + +bool CUndoManager::HasUndoData() const +{ + return m_UndoList.Count() != 0; +} + +bool CUndoManager::UndoDataDiscarded() const +{ + return m_bDiscarded; +} + +bool CUndoManager::HasRedoData() const +{ + return m_RedoStack.Count() > 0; +} + +void CUndoManager::PushNotificationScope( const char *pReason, int nNotifySource, int nNotifyFlags ) +{ + if ( m_nNotifyNesting++ == 0 ) + { + m_pNotifyReason = pReason; + m_nNotifySource = nNotifySource; + m_nNotifyFlags = nNotifyFlags; + } +} + +void CUndoManager::PopNotificationScope( bool bAbort ) +{ + --m_nNotifyNesting; + Assert( m_nNotifyNesting >= 0 ); + if ( m_nNotifyNesting == 0 ) + { + if ( !m_bSuppressingNotify && ( ( m_nNotifyFlags & NOTIFY_CHANGE_MASK ) != 0 ) ) + { + int nNotifyCount = m_Notifiers.Count(); + for( int i = 0; i < nNotifyCount; ++i ) + { + m_Notifiers[i]->NotifyDataChanged( m_pNotifyReason, m_nNotifySource, m_nNotifyFlags ); + } + } + m_nNotifySource = 0; + m_nNotifyFlags = 0; + } +} + + + +void CUndoManager::PushUndo( const char *udesc, const char *rdesc, int nChainingID ) +{ + if ( !IsEnabled() ) + return; + + Trace( "[%d] Pushing undo '%s'\n", m_nNesting + 1, udesc ); + + if ( m_nNesting++ == 0 ) + { + m_PreviousChainingID = m_nChainingID; + m_nChainingID = nChainingID; + m_UndoDesc = s_UndoSymbolTable.AddString( udesc ); + m_RedoDesc = ( udesc == rdesc ) ? m_UndoDesc : s_UndoSymbolTable.AddString( rdesc ); + m_bStreamStart = true; + m_nItemsAddedSinceStartOfStream = 0; + } +} + +void CUndoManager::PushRedo() +{ + if ( !IsEnabled() ) + return; + + Trace( "[%d] Popping undo '%s'\n", m_nNesting, s_UndoSymbolTable.String( m_UndoDesc ) ); + + --m_nNesting; + Assert( m_nNesting >= 0 ); + if ( m_nNesting == 0 ) + { + if ( m_nItemsAddedSinceStartOfStream > 0 ) + { + WipeRedo(); + + // Accumulate this operation into the previous "undo" operation if there is one + if ( m_nChainingID != 0 && + m_PreviousChainingID == m_nChainingID ) + { + // Walk undo list backward looking for previous end of stream and unmark that indicator + int i = m_UndoList.Tail(); + while ( i != m_UndoList.InvalidIndex() ) + { + IUndoElement *e = m_UndoList[ i ]; + if ( e && e->IsEndOfStream() ) + { + e->SetEndOfStream( false ); + break; + } + i = m_UndoList.Previous( i ); + } + } + } + + m_nItemsAddedSinceStartOfStream = 0; + } +} + +void CUndoManager::AbortUndoableOperation() +{ + if ( !IsEnabled() ) + return; + + bool hasItems = m_nItemsAddedSinceStartOfStream > 0 ? true : false; + + Trace( "[%d] Aborting undo '%s'\n", m_nNesting, s_UndoSymbolTable.String( m_UndoDesc ) ); + + // Close off context + PushRedo(); + + if ( m_nNesting == 0 && hasItems ) + { + Undo(); + WipeRedo(); + } +} + +void CUndoManager::WipeUndo() +{ + CDisableUndoScopeGuard sg; + + FOR_EACH_LL( m_UndoList, elem ) + { + Trace( "WipeUndo '%s'\n", m_UndoList[ elem ]->GetDesc() ); + + m_UndoList[ elem ]->Release(); + } + m_UndoList.RemoveAll(); + m_PreviousChainingID = 0; +} + +void CUndoManager::WipeRedo() +{ + int c = m_RedoStack.Count(); + if ( c == 0 ) + return; + + CUtlVector< DmElementHandle_t > handles; + g_pDataModelImp->GetInvalidHandles( handles ); + g_pDataModelImp->MarkHandlesValid( handles ); + + CDisableUndoScopeGuard sg; + + for ( int i = 0; i < c ; ++i ) + { + IUndoElement *elem; + elem = m_RedoStack[ i ]; + + Trace( "WipeRedo '%s'\n", elem->GetDesc() ); + + elem->Release(); + } + + m_RedoStack.Clear(); + + g_pDataModelImp->MarkHandlesInvalid( handles ); +} + +void CUndoManager::AddUndoElement( IUndoElement *pElement ) +{ + Assert( IsEnabled() ); + + if ( !pElement ) + return; + + ++m_nItemsAddedSinceStartOfStream; + + WipeRedo(); + + /* + // For later + if ( m_UndoList.Count() >= m_nMaxUndos ) + { + m_bDiscarded = true; + } + */ + + Trace( "AddUndoElement '%s'\n", pElement->GetDesc() ); + + m_UndoList.AddToTail( pElement ); + + if ( m_bStreamStart ) + { + pElement->SetEndOfStream( true ); + m_bStreamStart = false; + } +} + +void CUndoManager::Undo() +{ + CNotifyScopeGuard notify( "CUndoManager::Undo", NOTIFY_SOURCE_UNDO, NOTIFY_SETDIRTYFLAG ); + + Trace( "Undo\n======\n" ); + + bool saveEnabled = m_bEnabled; + m_bEnabled = false; + bool bEndOfStream = false; + while ( !bEndOfStream && m_UndoList.Count() > 0 ) + { + int i = m_UndoList.Tail(); + IUndoElement *action = m_UndoList[ i ]; + Assert( action ); + + Trace( " %s\n", action->GetDesc() ); + + action->Undo(); + m_RedoStack.Push( action ); + bEndOfStream = action->IsEndOfStream(); + m_UndoList.Remove( i ); + } + + Trace( "======\n\n" ); + + m_bEnabled = saveEnabled; + m_PreviousChainingID = 0; +} + +void CUndoManager::Redo() +{ + CNotifyScopeGuard notify( "CUndoManager::Redo", NOTIFY_SOURCE_UNDO, NOTIFY_SETDIRTYFLAG ); + + Trace( "Redo\n======\n" ); + + bool saveEnabled = m_bEnabled; + m_bEnabled = false; + bool bEndOfStream = false; + while ( !bEndOfStream && m_RedoStack.Count() > 0 ) + { + IUndoElement *action = NULL; + m_RedoStack.Pop( action ); + Assert( action ); + + Trace( " %s\n", action->GetDesc() ); + + action->Redo(); + m_UndoList.AddToTail( action ); + if ( m_RedoStack.Count() > 0 ) + { + action = m_RedoStack.Top(); + bEndOfStream = action->IsEndOfStream(); + } + } + + Trace( "======\n\n" ); + + m_bEnabled = saveEnabled; + m_PreviousChainingID = 0; +} + +const char *CUndoManager::UndoDesc() const +{ + if ( m_UndoList.Count() <= 0 ) + return ""; + + int i = m_UndoList.Tail(); + IUndoElement *action = m_UndoList[ i ]; + return action->UndoDesc(); +} + +const char *CUndoManager::RedoDesc() const +{ + if ( m_RedoStack.Count() <= 0 ) + { + return ""; + } + + IUndoElement *action = m_RedoStack.Top(); + return action->RedoDesc(); +} + +UtlSymId_t CUndoManager::GetUndoDescInternal( const char *context ) +{ + if ( m_nNesting <= 0 ) + { + static CUtlSymbolTable s_DescErrorsTable; + static CUtlVector< CUtlSymbol > s_DescErrors; + CUtlSymbol sym = s_DescErrorsTable.AddString( context ); + if ( s_DescErrors.Find( sym ) == s_DescErrors.InvalidIndex() ) + { + Warning( "CUndoManager::GetUndoDescInternal: undoable operation missing CUndoScopeGuard in application\nContext( %s )\n", context ); + s_DescErrors.AddToTail( sym ); + } + return s_UndoSymbolTable.AddString( context ); + } + return m_UndoDesc; +} + +UtlSymId_t CUndoManager::GetRedoDescInternal( const char *context ) +{ + if ( m_nNesting <= 0 ) + { + // Warning( "CUndoManager::GetRedoDescInternal: undoable operation missing CUndoScopeGuard in application\nContext( %s )", context ); + return s_UndoSymbolTable.AddString( context ); + } + return m_RedoDesc; +} + +void CUndoManager::GetUndoInfo( CUtlVector< UndoInfo_t >& list ) +{ + // Needs to persist after function returns... + static CUtlSymbolTable table; + + int ops = 0; + for ( int i = m_UndoList.Tail(); i != m_UndoList.InvalidIndex(); i = m_UndoList.Previous( i ) ) + { + ++ops; + IUndoElement *action = m_UndoList[ i ]; + Assert( action ); + bool bEndOfStream = action->IsEndOfStream(); + + UndoInfo_t info; + info.undo = action->UndoDesc(); + info.redo = action->RedoDesc(); + + // This is a hack because GetDesc() returns a static char buf[] and so the last one will clobber them all + // So we have the requester pass in a temporary string table so we can get a ptr to a CUtlSymbol in the table + // and use that. Sigh. + const char *desc = action->GetDesc(); + CUtlSymbol sym = table.AddString( desc ); + info.desc = table.String( sym ); + info.terminator = bEndOfStream; + info.numoperations = bEndOfStream ? ops : 1; + + list.AddToTail( info ); + + if ( bEndOfStream ) + { + ops = 0; + } + } +} + +void CUndoManager::TraceUndo( bool state ) +{ + m_bTrace = state; +} diff --git a/datamodel/undomanager.h b/datamodel/undomanager.h new file mode 100644 index 0000000..030d9fc --- /dev/null +++ b/datamodel/undomanager.h @@ -0,0 +1,124 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= + +#ifndef UNDOMANAGER_H +#define UNDOMANAGER_H +#ifdef _WIN32 +#pragma once +#endif + +#include "tier1/utlsymbol.h" +#include "tier1/utllinkedlist.h" +#include "tier1/utlstack.h" + + +//----------------------------------------------------------------------------- +// Forward declarations +//----------------------------------------------------------------------------- +class IUndoElement; +struct UndoInfo_t; +class IDmNotify; + + +//----------------------------------------------------------------------------- +// Undo/Redo stuff: +//----------------------------------------------------------------------------- +class CUndoManager +{ +public: + CUndoManager(); + ~CUndoManager(); + + void Shutdown(); + void SetUndoDepth( int nMaxUndoDepth ); + void AddUndoElement( IUndoElement *pElement ); + void Undo(); + void Redo(); + void TraceUndo( bool state ); + + void EnableUndo(); + void DisableUndo(); + bool IsEnabled() const; + bool HasUndoData() const; + bool UndoDataDiscarded() const; + bool HasRedoData() const; + + void WipeUndo(); + void WipeRedo(); + + const char *UndoDesc() const; + const char *RedoDesc() const; + + void PushUndo( char const *udesc, char const *rdesc, int nChainingID ); + void PushRedo(); + void AbortUndoableOperation(); + + UtlSymId_t GetUndoDescInternal( const char *context ); + UtlSymId_t GetRedoDescInternal( const char *context ); + + void GetUndoInfo( CUtlVector< UndoInfo_t >& list ); + + bool InstallNotificationCallback( IDmNotify *pNotify ); + void RemoveNotificationCallback( IDmNotify *pNotify ); + bool IsSuppressingNotify( ) const; + void SetSuppressingNotify( bool bSuppress ); + void PushNotificationScope( const char *pReason, int nNotifySource, int nNotifyFlags ); + void PopNotificationScope( bool bAbort ); + + void NotifyState( int nNotifyFlags ); + + static const char *GetUndoString( CUtlSymbol sym ); + +private: + void Trace( PRINTF_FORMAT_STRING const char *fmt, ... ); + + CUtlLinkedList< IUndoElement *, int > m_UndoList; + CUtlStack< IUndoElement * > m_RedoStack; + CUtlVector< IDmNotify* > m_Notifiers; + int m_nMaxUndoDepth; + int m_nNesting; + int m_nNotifyNesting; + CUtlSymbol m_UndoDesc; + CUtlSymbol m_RedoDesc; + int m_nNotifySource; + int m_nNotifyFlags; + const char* m_pNotifyReason; + int m_nItemsAddedSinceStartOfStream; + // Signals that next undo operation is the "Start" of a stream + bool m_bStreamStart : 1; + bool m_bTrace : 1; + bool m_bDiscarded : 1; + bool m_bEnabled : 1; + bool m_bSuppressingNotify : 1; + + static CUtlSymbolTableMT s_UndoSymbolTable; + int m_nChainingID; + int m_PreviousChainingID; +}; + + +//----------------------------------------------------------------------------- +// Is undo enabled +//----------------------------------------------------------------------------- +inline bool CUndoManager::IsEnabled() const +{ + return m_bEnabled; +} + +inline void CUndoManager::NotifyState( int nNotifyFlags ) +{ + // FIXME: Should suppress prevent notification being sent, + // or prevent notification flags from being set in the first place? + m_nNotifyFlags |= nNotifyFlags; +} + +inline const char *CUndoManager::GetUndoString( CUtlSymbol sym ) +{ + return s_UndoSymbolTable.String( sym ); +} + + +#endif // UNDOMANAGER_H |