summaryrefslogtreecommitdiff
path: root/datamodel
diff options
context:
space:
mode:
authorFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
committerFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
commit3bf9df6b2785fa6d951086978a3e66f49427166a (patch)
tree2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /datamodel
downloadarchived-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.cpp209
-rw-r--r--datamodel/DmElementFramework.h78
-rw-r--r--datamodel/clipboardmanager.cpp169
-rw-r--r--datamodel/clipboardmanager.h36
-rw-r--r--datamodel/datamodel.cpp2464
-rw-r--r--datamodel/datamodel.h527
-rw-r--r--datamodel/datamodel.vpc76
-rw-r--r--datamodel/dependencygraph.cpp331
-rw-r--r--datamodel/dependencygraph.h62
-rw-r--r--datamodel/dmattribute.cpp3265
-rw-r--r--datamodel/dmattributeinternal.h75
-rw-r--r--datamodel/dmelement.cpp1420
-rw-r--r--datamodel/dmelementdictionary.cpp468
-rw-r--r--datamodel/dmelementdictionary.h184
-rw-r--r--datamodel/dmelementfactoryhelper.cpp100
-rw-r--r--datamodel/dmserializerbinary.cpp587
-rw-r--r--datamodel/dmserializerbinary.h27
-rw-r--r--datamodel/dmserializerkeyvalues.cpp466
-rw-r--r--datamodel/dmserializerkeyvalues.h27
-rw-r--r--datamodel/dmserializerkeyvalues2.cpp1377
-rw-r--r--datamodel/dmserializerkeyvalues2.h27
-rw-r--r--datamodel/undomanager.cpp444
-rw-r--r--datamodel/undomanager.h124
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