diff options
Diffstat (limited to 'datamodel/dmelement.cpp')
| -rw-r--r-- | datamodel/dmelement.cpp | 1420 |
1 files changed, 1420 insertions, 0 deletions
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 ); + } + } +} |