You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1420 lines
40 KiB
1420 lines
40 KiB
//========= 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 ); |
|
} |
|
} |
|
}
|
|
|