diff --git a/datacache/datacache.cpp b/datacache/datacache.cpp new file mode 100644 index 0000000..2e183be --- /dev/null +++ b/datacache/datacache.cpp @@ -0,0 +1,1389 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= + +#include "datacache/idatacache.h" + +#ifdef _LINUX +#include +#endif + +#include "tier0/vprof.h" +#include "basetypes.h" +#include "convar.h" +#include "interface.h" +#include "datamanager.h" +#include "utlrbtree.h" +#include "utlhash.h" +#include "utlmap.h" +#include "generichash.h" +#include "filesystem.h" +#include "datacache.h" +#include "utlvector.h" +#include "fmtstr.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//----------------------------------------------------------------------------- +// Singleton +//----------------------------------------------------------------------------- +CDataCache g_DataCache; + +EXPOSE_SINGLE_INTERFACE_GLOBALVAR( CDataCache, IDataCache, DATACACHE_INTERFACE_VERSION, g_DataCache ); + + +//----------------------------------------------------------------------------- +// +// Data Cache class implemenations +// +//----------------------------------------------------------------------------- + + +//----------------------------------------------------------------------------- +// Console commands +//----------------------------------------------------------------------------- +ConVar developer( "developer", "0", FCVAR_DEVELOPMENTONLY ); +static ConVar mem_force_flush( "mem_force_flush", "0", FCVAR_CHEAT, "Force cache flush of unlocked resources on every alloc" ); +static int g_iDontForceFlush; + +//----------------------------------------------------------------------------- +// DataCacheItem_t +//----------------------------------------------------------------------------- + +DEFINE_FIXEDSIZE_ALLOCATOR_MT( DataCacheItem_t, 4096/sizeof(DataCacheItem_t), CUtlMemoryPool::GROW_SLOW ); + +void DataCacheItem_t::DestroyResource() +{ + if ( pSection ) + { + pSection->DiscardItemData( this, DC_AGE_DISCARD ); + } + delete this; +} + + +//----------------------------------------------------------------------------- +// CDataCacheSection +//----------------------------------------------------------------------------- + +CDataCacheSection::CDataCacheSection( CDataCache *pSharedCache, IDataCacheClient *pClient, const char *pszName ) + : m_pClient( pClient ), + m_LRU( pSharedCache->m_LRU ), + m_mutex( pSharedCache->m_mutex ), + m_pSharedCache( pSharedCache ), + m_nFrameUnlockCounter( 0 ), + m_options( 0 ) +{ + memset( &m_status, 0, sizeof(m_status) ); + AssertMsg1( strlen(pszName) <= DC_MAX_CLIENT_NAME, "Cache client name too long \"%s\"", pszName ); + Q_strncpy( szName, pszName, sizeof(szName) ); + + for ( int i = 0; i < DC_MAX_THREADS_FRAMELOCKED; i++ ) + { + FrameLock_t *pFrameLock = new FrameLock_t; + pFrameLock->m_iThread = i; + m_FreeFrameLocks.Push( pFrameLock ); + } +} + +CDataCacheSection::~CDataCacheSection() +{ + FrameLock_t *pFrameLock; + while ( ( pFrameLock = m_FreeFrameLocks.Pop() ) != NULL ) + { + delete pFrameLock; + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Controls cache size. +//----------------------------------------------------------------------------- +void CDataCacheSection::SetLimits( const DataCacheLimits_t &limits ) +{ + m_limits = limits; + AssertMsg( m_limits.nMinBytes == 0 && m_limits.nMinItems == 0, "Cache minimums not yet implemented" ); +} + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +const DataCacheLimits_t &CDataCacheSection::GetLimits() +{ + return m_limits; +} + + +//----------------------------------------------------------------------------- +// Purpose: Controls cache options. +//----------------------------------------------------------------------------- +void CDataCacheSection::SetOptions( unsigned options ) +{ + m_options = options; +} + + +//----------------------------------------------------------------------------- +// Purpose: Get the current state of the section +//----------------------------------------------------------------------------- +void CDataCacheSection::GetStatus( DataCacheStatus_t *pStatus, DataCacheLimits_t *pLimits ) +{ + if ( pStatus ) + { + *pStatus = m_status; + } + + if ( pLimits ) + { + *pLimits = m_limits; + } +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void CDataCacheSection::EnsureCapacity( unsigned nBytes, unsigned nItems ) +{ + VPROF( "CDataCacheSection::EnsureCapacity" ); + + if ( m_limits.nMaxItems != (unsigned)-1 || m_limits.nMaxBytes != (unsigned)-1 ) + { + unsigned nNewSectionBytes = GetNumBytes() + nBytes; + + if ( nNewSectionBytes > m_limits.nMaxBytes ) + { + Purge( nNewSectionBytes - m_limits.nMaxBytes ); + } + + if ( GetNumItems() >= m_limits.nMaxItems ) + { + PurgeItems( ( GetNumItems() - m_limits.nMaxItems ) + 1 ); + } + } + + m_pSharedCache->EnsureCapacity( nBytes ); +} + +//----------------------------------------------------------------------------- +// Purpose: Add an item to the cache. Purges old items if over budget, returns false if item was already in cache. +//----------------------------------------------------------------------------- +bool CDataCacheSection::Add( DataCacheClientID_t clientId, const void *pItemData, unsigned size, DataCacheHandle_t *pHandle ) +{ + return AddEx( clientId, pItemData, size, DCAF_DEFAULT, pHandle ); +} + +//----------------------------------------------------------------------------- +// Purpose: Add an item to the cache. Purges old items if over budget, returns false if item was already in cache. +//----------------------------------------------------------------------------- +bool CDataCacheSection::AddEx( DataCacheClientID_t clientId, const void *pItemData, unsigned size, unsigned flags, DataCacheHandle_t *pHandle ) +{ + VPROF( "CDataCacheSection::Add" ); + + if ( mem_force_flush.GetBool() ) + { + m_pSharedCache->Flush(); + } + + if ( ( m_options & DC_VALIDATE ) && Find( clientId ) ) + { + Error( "Duplicate add to data cache\n" ); + return false; + } + + EnsureCapacity( size ); + + DataCacheItemData_t itemData = + { + pItemData, + size, + clientId, + this + }; + + memhandle_t hMem = m_LRU.CreateResource( itemData, true ); + + Assert( hMem != (memhandle_t)0 && hMem != (memhandle_t)DC_INVALID_HANDLE ); + + AccessItem( hMem )->hLRU = hMem; + + if ( pHandle ) + { + *pHandle = (DataCacheHandle_t)hMem; + } + + NoteAdd( size ); + + OnAdd( clientId, (DataCacheHandle_t)hMem ); + + g_iDontForceFlush++; + + if ( flags & DCAF_LOCK ) + { + Lock( (DataCacheHandle_t)hMem ); + } + // Add implies a frame lock. A no-op if not in frame lock + FrameLock( (DataCacheHandle_t)hMem ); + + g_iDontForceFlush--; + + m_LRU.UnlockResource( hMem ); + + return true; +} + + +//----------------------------------------------------------------------------- +// Purpose: Finds an item in the cache, returns NULL if item is not in cache. +//----------------------------------------------------------------------------- +DataCacheHandle_t CDataCacheSection::Find( DataCacheClientID_t clientId ) +{ + VPROF( "CDataCacheSection::Find" ); + + m_status.nFindRequests++; + + DataCacheHandle_t hResult = DoFind( clientId ); + + if ( hResult != DC_INVALID_HANDLE ) + { + m_status.nFindHits++; + } + + return hResult; +} + +//--------------------------------------------------------- +DataCacheHandle_t CDataCacheSection::DoFind( DataCacheClientID_t clientId ) +{ + AUTO_LOCK( m_mutex ); + memhandle_t hCurrent; + + hCurrent = GetFirstUnlockedItem(); + + while ( hCurrent != INVALID_MEMHANDLE ) + { + if ( AccessItem( hCurrent )->clientId == clientId ) + { + m_status.nFindHits++; + return (DataCacheHandle_t)hCurrent; + } + hCurrent = GetNextItem( hCurrent ); + } + + hCurrent = GetFirstLockedItem(); + + while ( hCurrent != INVALID_MEMHANDLE ) + { + if ( AccessItem( hCurrent )->clientId == clientId ) + { + m_status.nFindHits++; + return (DataCacheHandle_t)hCurrent; + } + hCurrent = GetNextItem( hCurrent ); + } + + return DC_INVALID_HANDLE; +} + + +//----------------------------------------------------------------------------- +// Purpose: Get an item out of the cache and remove it. No callbacks are executed. +//----------------------------------------------------------------------------- +DataCacheRemoveResult_t CDataCacheSection::Remove( DataCacheHandle_t handle, const void **ppItemData, unsigned *pItemSize, bool bNotify ) +{ + VPROF( "CDataCacheSection::Remove" ); + + if ( handle != DC_INVALID_HANDLE ) + { + memhandle_t lruHandle = (memhandle_t)handle; + if ( m_LRU.LockCount( lruHandle ) > 0 ) + { + return DC_LOCKED; + } + + AUTO_LOCK( m_mutex ); + + DataCacheItem_t *pItem = AccessItem( lruHandle ); + if ( pItem ) + { + if ( ppItemData ) + { + *ppItemData = pItem->pItemData; + } + + if ( pItemSize ) + { + *pItemSize = pItem->size; + } + + DiscardItem( lruHandle, ( bNotify ) ? DC_REMOVED : DC_NONE ); + + return DC_OK; + } + } + + return DC_NOT_FOUND; +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +bool CDataCacheSection::IsPresent( DataCacheHandle_t handle ) +{ + return ( m_LRU.GetResource_NoLockNoLRUTouch( (memhandle_t)handle ) != NULL ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Lock an item in the cache, returns NULL if item is not in the cache. +//----------------------------------------------------------------------------- +void *CDataCacheSection::Lock( DataCacheHandle_t handle ) +{ + VPROF( "CDataCacheSection::Lock" ); + + if ( mem_force_flush.GetBool() && !g_iDontForceFlush) + Flush(); + + if ( handle != DC_INVALID_HANDLE ) + { + DataCacheItem_t *pItem = m_LRU.LockResource( (memhandle_t)handle ); + if ( pItem ) + { + if ( m_LRU.LockCount( (memhandle_t)handle ) == 1 ) + { + NoteLock( pItem->size ); + } + return const_cast(pItem->pItemData); + } + } + + return NULL; +} + + +//----------------------------------------------------------------------------- +// Purpose: Unlock a previous lock. +//----------------------------------------------------------------------------- +int CDataCacheSection::Unlock( DataCacheHandle_t handle ) +{ + VPROF( "CDataCacheSection::Unlock" ); + + int iNewLockCount = 0; + if ( handle != DC_INVALID_HANDLE ) + { + AssertMsg( AccessItem( (memhandle_t)handle ) != NULL, "Attempted to unlock nonexistent cache entry" ); + unsigned nBytesUnlocked = 0; + m_mutex.Lock(); + iNewLockCount = m_LRU.UnlockResource( (memhandle_t)handle ); + if ( iNewLockCount == 0 ) + { + nBytesUnlocked = AccessItem( (memhandle_t)handle )->size; + } + m_mutex.Unlock(); + if ( nBytesUnlocked ) + { + NoteUnlock( nBytesUnlocked ); + EnsureCapacity( 0 ); + } + } + return iNewLockCount; +} + + +//----------------------------------------------------------------------------- +// Purpose: Lock the mutex +//----------------------------------------------------------------------------- +void CDataCacheSection::LockMutex() +{ + g_iDontForceFlush++; + m_mutex.Lock(); +} + + +//----------------------------------------------------------------------------- +// Purpose: Unlock the mutex +//----------------------------------------------------------------------------- +void CDataCacheSection::UnlockMutex() +{ + g_iDontForceFlush--; + m_mutex.Unlock(); +} + +//----------------------------------------------------------------------------- +// Purpose: Get without locking +//----------------------------------------------------------------------------- +void *CDataCacheSection::Get( DataCacheHandle_t handle, bool bFrameLock ) +{ + VPROF( "CDataCacheSection::Get" ); + + if ( mem_force_flush.GetBool() && !g_iDontForceFlush) + Flush(); + + if ( handle != DC_INVALID_HANDLE ) + { + if ( bFrameLock && IsFrameLocking() ) + return FrameLock( handle ); + + AUTO_LOCK( m_mutex ); + DataCacheItem_t *pItem = m_LRU.GetResource_NoLock( (memhandle_t)handle ); + if ( pItem ) + { + return const_cast( pItem->pItemData ); + } + } + + return NULL; +} + + +//----------------------------------------------------------------------------- +// Purpose: Get without locking +//----------------------------------------------------------------------------- +void *CDataCacheSection::GetNoTouch( DataCacheHandle_t handle, bool bFrameLock ) +{ + VPROF( "CDataCacheSection::GetNoTouch" ); + + if ( handle != DC_INVALID_HANDLE ) + { + if ( bFrameLock && IsFrameLocking() ) + return FrameLock( handle ); + + AUTO_LOCK( m_mutex ); + DataCacheItem_t *pItem = m_LRU.GetResource_NoLockNoLRUTouch( (memhandle_t)handle ); + if ( pItem ) + { + return const_cast( pItem->pItemData ); + } + } + + return NULL; +} + + +//----------------------------------------------------------------------------- +// Purpose: "Frame locking" (not game frame). A crude way to manage locks over relatively +// short periods. Does not affect normal locks/unlocks +//----------------------------------------------------------------------------- +int CDataCacheSection::BeginFrameLocking() +{ + FrameLock_t *pFrameLock = m_ThreadFrameLock.Get(); + if ( pFrameLock ) + { + pFrameLock->m_iLock++; + } + else + { + while ( ( pFrameLock = m_FreeFrameLocks.Pop() ) == NULL ) + { + ThreadPause(); + ThreadSleep( 1 ); + } + pFrameLock->m_iLock = 1; + pFrameLock->m_pFirst = NULL; + m_ThreadFrameLock.Set( pFrameLock ); + } + return pFrameLock->m_iLock; +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +bool CDataCacheSection::IsFrameLocking() +{ + FrameLock_t *pFrameLock = m_ThreadFrameLock.Get(); + return ( pFrameLock != NULL ); +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void *CDataCacheSection::FrameLock( DataCacheHandle_t handle ) +{ + VPROF( "CDataCacheSection::FrameLock" ); + + if ( mem_force_flush.GetBool() && !g_iDontForceFlush) + Flush(); + + void *pResult = NULL; + FrameLock_t *pFrameLock = m_ThreadFrameLock.Get(); + if ( pFrameLock ) + { + DataCacheItem_t *pItem = m_LRU.LockResource( (memhandle_t)handle ); + + if ( pItem ) + { + int iThread = pFrameLock->m_iThread; + if ( pItem->pNextFrameLocked[iThread] == DC_NO_NEXT_LOCKED ) + { + pItem->pNextFrameLocked[iThread] = pFrameLock->m_pFirst; + pFrameLock->m_pFirst = pItem; + Lock( handle ); + } + + pResult = const_cast(pItem->pItemData); + m_LRU.UnlockResource( (memhandle_t)handle ); + } + } + + return pResult; +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +int CDataCacheSection::EndFrameLocking() +{ + FrameLock_t* pFrameLock = m_ThreadFrameLock.Get(); + Assert(pFrameLock->m_iLock > 0); + + if (pFrameLock->m_iLock == 1) + { + VPROF("CDataCacheSection::EndFrameLocking"); + + if (pFrameLock->m_pFirst) + { + AUTO_LOCK(m_mutex); + + DataCacheItem_t* pItem = pFrameLock->m_pFirst; + DataCacheItem_t* pNext; + int iThread = pFrameLock->m_iThread; + while (pItem) + { + pNext = pItem->pNextFrameLocked[iThread]; + pItem->pNextFrameLocked[iThread] = DC_NO_NEXT_LOCKED; + Unlock(pItem->hLRU); + pItem = pNext; + } + } + + m_FreeFrameLocks.Push(pFrameLock); + return 0; + } + else + { + pFrameLock->m_iLock--; + } + return pFrameLock->m_iLock; +} + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +int *CDataCacheSection::GetFrameUnlockCounterPtr() +{ + return &m_nFrameUnlockCounter; +} + + +//----------------------------------------------------------------------------- +// Purpose: Lock management, not for the feint of heart +//----------------------------------------------------------------------------- +int CDataCacheSection::GetLockCount( DataCacheHandle_t handle ) +{ + return m_LRU.LockCount( (memhandle_t)handle ); +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +int CDataCacheSection::BreakLock( DataCacheHandle_t handle ) +{ + return m_LRU.BreakLock( (memhandle_t)handle ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Explicitly mark an item as "recently used" +//----------------------------------------------------------------------------- +bool CDataCacheSection::Touch( DataCacheHandle_t handle ) +{ + m_LRU.TouchResource( (memhandle_t)handle ); + return true; +} + + +//----------------------------------------------------------------------------- +// Purpose: Explicitly mark an item as "least recently used". +//----------------------------------------------------------------------------- +bool CDataCacheSection::Age( DataCacheHandle_t handle ) +{ + m_LRU.MarkAsStale( (memhandle_t)handle ); + return true; +} + + +//----------------------------------------------------------------------------- +// Purpose: Empty the cache. Returns bytes released, will remove locked items if force specified +//----------------------------------------------------------------------------- +unsigned CDataCacheSection::Flush( bool bUnlockedOnly, bool bNotify ) +{ + VPROF( "CDataCacheSection::Flush" ); + + AUTO_LOCK( m_mutex ); + + DataCacheNotificationType_t notificationType = ( bNotify )? DC_FLUSH_DISCARD : DC_NONE; + + memhandle_t hCurrent; + memhandle_t hNext; + + unsigned nBytesFlushed = 0; + unsigned nBytesCurrent = 0; + + hCurrent = GetFirstUnlockedItem(); + + while ( hCurrent != INVALID_MEMHANDLE ) + { + hNext = GetNextItem( hCurrent ); + nBytesCurrent = AccessItem( hCurrent )->size; + + if ( DiscardItem( hCurrent, notificationType ) ) + { + nBytesFlushed += nBytesCurrent; + } + hCurrent = hNext; + } + + if ( !bUnlockedOnly ) + { + hCurrent = GetFirstLockedItem(); + + while ( hCurrent != INVALID_MEMHANDLE ) + { + hNext = GetNextItem( hCurrent ); + nBytesCurrent = AccessItem( hCurrent )->size; + + if ( DiscardItem( hCurrent, notificationType ) ) + { + nBytesFlushed += nBytesCurrent; + } + hCurrent = hNext; + } + } + + return nBytesFlushed; +} + + +//----------------------------------------------------------------------------- +// Purpose: Dump the oldest items to free the specified amount of memory. Returns amount actually freed +//----------------------------------------------------------------------------- +unsigned CDataCacheSection::Purge( unsigned nBytes ) +{ + VPROF( "CDataCacheSection::Purge" ); + + AUTO_LOCK( m_mutex ); + + unsigned nBytesPurged = 0; + unsigned nBytesCurrent = 0; + + memhandle_t hCurrent = GetFirstUnlockedItem(); + memhandle_t hNext; + + while ( hCurrent != INVALID_MEMHANDLE && nBytes > 0 ) + { + hNext = GetNextItem( hCurrent ); + nBytesCurrent = AccessItem( hCurrent )->size; + + if ( DiscardItem( hCurrent, DC_FLUSH_DISCARD ) ) + { + nBytesPurged += nBytesCurrent; + nBytes -= min( nBytesCurrent, nBytes ); + } + hCurrent = hNext; + } + + return nBytesPurged; +} + +//----------------------------------------------------------------------------- +// Purpose: Dump the oldest items to free the specified number of items. Returns number actually freed +//----------------------------------------------------------------------------- +unsigned CDataCacheSection::PurgeItems( unsigned nItems ) +{ + AUTO_LOCK( m_mutex ); + + unsigned nPurged = 0; + + memhandle_t hCurrent = GetFirstUnlockedItem(); + memhandle_t hNext; + + while ( hCurrent != INVALID_MEMHANDLE && nItems ) + { + hNext = GetNextItem( hCurrent ); + + if ( DiscardItem( hCurrent, DC_FLUSH_DISCARD ) ) + { + nItems--; + nPurged++; + } + hCurrent = hNext; + } + + return nPurged; +} + + +//----------------------------------------------------------------------------- +// Purpose: Output the state of the section +//----------------------------------------------------------------------------- +void CDataCacheSection::OutputReport( DataCacheReportType_t reportType ) +{ + m_pSharedCache->OutputReport( reportType, GetName() ); +} + +//----------------------------------------------------------------------------- +// Purpose: Updates the size of a specific item +// Input : handle - +// newSize - +//----------------------------------------------------------------------------- +void CDataCacheSection::UpdateSize( DataCacheHandle_t handle, unsigned int nNewSize ) +{ + DataCacheItem_t *pItem = m_LRU.LockResource( (memhandle_t)handle ); + if ( !pItem ) + { + // If it's gone from memory, size is already irrelevant + return; + } + + unsigned oldSize = pItem->size; + + if ( oldSize != nNewSize ) + { + // Update the size + pItem->size = nNewSize; + + int bytesAdded = nNewSize - oldSize; + // If change would grow cache size, then purge items until we have room + if ( bytesAdded > 0 ) + { + m_pSharedCache->EnsureCapacity( bytesAdded ); + } + + m_LRU.NotifySizeChanged( (memhandle_t)handle, oldSize, nNewSize ); + NoteSizeChanged( oldSize, nNewSize ); + } + + m_LRU.UnlockResource( (memhandle_t)handle ); +} + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +memhandle_t CDataCacheSection::GetFirstUnlockedItem() +{ + memhandle_t hCurrent; + + hCurrent = m_LRU.GetFirstUnlocked(); + + while ( hCurrent != INVALID_MEMHANDLE ) + { + if ( AccessItem( hCurrent )->pSection == this ) + { + return hCurrent; + } + hCurrent = m_LRU.GetNext( hCurrent ); + } + return INVALID_MEMHANDLE; +} + + +memhandle_t CDataCacheSection::GetFirstLockedItem() +{ + memhandle_t hCurrent; + + hCurrent = m_LRU.GetFirstLocked(); + + while ( hCurrent != INVALID_MEMHANDLE ) + { + if ( AccessItem( hCurrent )->pSection == this ) + { + return hCurrent; + } + hCurrent = m_LRU.GetNext( hCurrent ); + } + return INVALID_MEMHANDLE; +} + + +memhandle_t CDataCacheSection::GetNextItem( memhandle_t hCurrent ) +{ + hCurrent = m_LRU.GetNext( hCurrent ); + + while ( hCurrent != INVALID_MEMHANDLE ) + { + if ( AccessItem( hCurrent )->pSection == this ) + { + return hCurrent; + } + hCurrent = m_LRU.GetNext( hCurrent ); + } + return INVALID_MEMHANDLE; +} + +bool CDataCacheSection::DiscardItem( memhandle_t hItem, DataCacheNotificationType_t type ) +{ + DataCacheItem_t *pItem = AccessItem( hItem ); + if ( DiscardItemData( pItem, type ) ) + { + if ( m_LRU.LockCount( hItem ) ) + { + m_LRU.BreakLock( hItem ); + NoteUnlock( pItem->size ); + } + + FrameLock_t *pFrameLock = m_ThreadFrameLock.Get(); + if ( pFrameLock ) + { + int iThread = pFrameLock->m_iThread; + if ( pItem->pNextFrameLocked[iThread] != DC_NO_NEXT_LOCKED ) + { + if ( pFrameLock->m_pFirst == pItem ) + { + pFrameLock->m_pFirst = pItem->pNextFrameLocked[iThread]; + } + else + { + DataCacheItem_t *pCurrent = pFrameLock->m_pFirst; + while ( pCurrent ) + { + if ( pCurrent->pNextFrameLocked[iThread] == pItem ) + { + pCurrent->pNextFrameLocked[iThread] = pItem->pNextFrameLocked[iThread]; + break; + } + pCurrent = pCurrent->pNextFrameLocked[iThread]; + } + } + pItem->pNextFrameLocked[iThread] = DC_NO_NEXT_LOCKED; + } + + } + +#ifdef _DEBUG + for ( int i = 0; i < DC_MAX_THREADS_FRAMELOCKED; i++ ) + { + if ( pItem->pNextFrameLocked[i] != DC_NO_NEXT_LOCKED ) + { + DebuggerBreak(); // higher level code needs to handle better + } + } +#endif + + pItem->pSection = NULL; // inhibit callbacks from lower level resource system + m_LRU.DestroyResource( hItem ); + return true; + } + return false; +} + +bool CDataCacheSection::DiscardItemData( DataCacheItem_t *pItem, DataCacheNotificationType_t type ) +{ + if ( pItem ) + { + if ( type != DC_NONE ) + { + Assert( type == DC_AGE_DISCARD || type == DC_FLUSH_DISCARD || DC_REMOVED ); + + if ( type == DC_AGE_DISCARD && m_pSharedCache->IsInFlush() ) + type = DC_FLUSH_DISCARD; + + DataCacheNotification_t notification = + { + type, + GetName(), + pItem->clientId, + pItem->pItemData, + pItem->size + }; + + bool bResult = m_pClient->HandleCacheNotification( notification ); + AssertMsg( bResult, "Refusal of cache drop not yet implemented!" ); + + if ( bResult ) + { + NoteRemove( pItem->size ); + } + + return bResult; + } + + OnRemove( pItem->clientId ); + + pItem->pSection = NULL; + pItem->pItemData = NULL, + pItem->clientId = 0; + + NoteRemove( pItem->size ); + + return true; + } + return false; +} + + +//----------------------------------------------------------------------------- +// CDataCacheSectionFastFind +//----------------------------------------------------------------------------- +DataCacheHandle_t CDataCacheSectionFastFind::DoFind( DataCacheClientID_t clientId ) +{ + AUTO_LOCK( m_mutex ); + UtlHashFastHandle_t hHash = m_Handles.Find( Hash4( &clientId ) ); + if( hHash != m_Handles.InvalidHandle() ) + return m_Handles[hHash]; + return DC_INVALID_HANDLE; +} + + +void CDataCacheSectionFastFind::OnAdd( DataCacheClientID_t clientId, DataCacheHandle_t hCacheItem ) +{ + AUTO_LOCK( m_mutex ); + Assert( m_Handles.Find( Hash4( &clientId ) ) == m_Handles.InvalidHandle()); + m_Handles.FastInsert( Hash4( &clientId ), hCacheItem ); +} + + +void CDataCacheSectionFastFind::OnRemove( DataCacheClientID_t clientId ) +{ + AUTO_LOCK( m_mutex ); + UtlHashFastHandle_t hHash = m_Handles.Find( Hash4( &clientId ) ); + Assert( hHash != m_Handles.InvalidHandle()); + if( hHash != m_Handles.InvalidHandle() ) + return m_Handles.Remove( hHash ); +} + + +//----------------------------------------------------------------------------- +// CDataCache +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- +// Convar callback to change data cache +//----------------------------------------------------------------------------- +void DataCacheSize_f( IConVar *pConVar, const char *pOldString, float flOldValue ) +{ + ConVarRef var( pConVar ); + int nOldValue = (int)flOldValue; + if ( var.GetInt() != nOldValue ) + { + g_DataCache.SetSize( var.GetInt() * 1024 * 1024 ); + } +} +ConVar datacachesize( "datacachesize", "64", FCVAR_DEVELOPMENTONLY, "Size in MB.", true, 32, true, 512, DataCacheSize_f ); + +//----------------------------------------------------------------------------- +// Connect, disconnect +//----------------------------------------------------------------------------- +bool CDataCache::Connect( CreateInterfaceFn factory ) +{ + if ( !BaseClass::Connect( factory ) ) + return false; + + g_DataCache.SetSize( datacachesize.GetInt() * 1024 * 1024 ); + g_pDataCache = this; + + return true; +} + +void CDataCache::Disconnect() +{ + g_pDataCache = NULL; + BaseClass::Disconnect(); +} + + +//----------------------------------------------------------------------------- +// Init, Shutdown +//----------------------------------------------------------------------------- +InitReturnVal_t CDataCache::Init( void ) +{ + return BaseClass::Init(); +} + +void CDataCache::Shutdown( void ) +{ + Flush( false, false ); + BaseClass::Shutdown(); +} + + +//----------------------------------------------------------------------------- +// Query interface +//----------------------------------------------------------------------------- +void *CDataCache::QueryInterface( const char *pInterfaceName ) +{ + // Loading the datacache DLL mounts *all* interfaces + // This includes the backward-compatible interfaces + IStudioDataCache + CreateInterfaceFn factory = Sys_GetFactoryThis(); // This silly construction is necessary + return factory( pInterfaceName, NULL ); // to prevent the LTCG compiler from crashing. +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +CDataCache::CDataCache() + : m_mutex( m_LRU.AccessMutex() ) +{ + memset( &m_status, 0, sizeof(m_status) ); + m_bInFlush = false; +} + +//----------------------------------------------------------------------------- +// Purpose: Controls cache size. +//----------------------------------------------------------------------------- +void CDataCache::SetSize( int nMaxBytes ) +{ + m_LRU.SetTargetSize( nMaxBytes ); + m_LRU.FlushToTargetSize(); + + nMaxBytes /= 1024 * 1024; + + if ( datacachesize.GetInt() != nMaxBytes ) + { + datacachesize.SetValue( nMaxBytes ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Controls cache options. +//----------------------------------------------------------------------------- +void CDataCache::SetOptions( unsigned options ) +{ + for ( int i = 0; m_Sections.Count(); i++ ) + { + m_Sections[i]->SetOptions( options ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Controls cache section size. +//----------------------------------------------------------------------------- +void CDataCache::SetSectionLimits( const char *pszSectionName, const DataCacheLimits_t &limits ) +{ + IDataCacheSection *pSection = FindSection( pszSectionName ); + + if ( !pSection ) + { + DevMsg( "Cannot find requested cache section \"%s\"", pszSectionName ); + return; + } + + pSection->SetLimits( limits ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Get the current state of the cache +//----------------------------------------------------------------------------- +void CDataCache::GetStatus( DataCacheStatus_t *pStatus, DataCacheLimits_t *pLimits ) +{ + if ( pStatus ) + { + *pStatus = m_status; + } + + if ( pLimits ) + { + Construct( pLimits ); + pLimits->nMaxBytes = m_LRU.TargetSize(); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Add a section to the cache +//----------------------------------------------------------------------------- +IDataCacheSection *CDataCache::AddSection( IDataCacheClient *pClient, const char *pszSectionName, const DataCacheLimits_t &limits, bool bSupportFastFind ) +{ + CDataCacheSection *pSection; + + pSection = (CDataCacheSection *)FindSection( pszSectionName ); + if ( pSection ) + { + AssertMsg1( pSection->GetClient() == pClient, "Duplicate cache section name \"%s\"", pszSectionName ); + return pSection; + } + + if ( !bSupportFastFind ) + pSection = new CDataCacheSection( this, pClient, pszSectionName ); + else + pSection = new CDataCacheSectionFastFind( this, pClient, pszSectionName ); + + pSection->SetLimits( limits ); + + m_Sections.AddToTail( pSection ); + return pSection; +} + + +//----------------------------------------------------------------------------- +// Purpose: Remove a section from the cache +//----------------------------------------------------------------------------- +void CDataCache::RemoveSection( const char *pszClientName, bool bCallFlush ) +{ + int iSection = FindSectionIndex( pszClientName ); + + if ( iSection != m_Sections.InvalidIndex() ) + { + if ( bCallFlush ) + { + m_Sections[iSection]->Flush( false ); + } + delete m_Sections[iSection]; + m_Sections.FastRemove( iSection ); + return; + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Find a section of the cache +//----------------------------------------------------------------------------- +IDataCacheSection *CDataCache::FindSection( const char *pszClientName ) +{ + int iSection = FindSectionIndex( pszClientName ); + + if ( iSection != m_Sections.InvalidIndex() ) + { + return m_Sections[iSection]; + } + return NULL; +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void CDataCache::EnsureCapacity( unsigned nBytes ) +{ + VPROF( "CDataCache::EnsureCapacity" ); + + m_LRU.EnsureCapacity( nBytes ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Dump the oldest items to free the specified amount of memory. Returns amount actually freed +//----------------------------------------------------------------------------- +unsigned CDataCache::Purge( unsigned nBytes ) +{ + VPROF( "CDataCache::Purge" ); + + return m_LRU.Purge( nBytes ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Empty the cache. Returns bytes released, will remove locked items if force specified +//----------------------------------------------------------------------------- +unsigned CDataCache::Flush( bool bUnlockedOnly, bool bNotify ) +{ + VPROF( "CDataCache::Flush" ); + + unsigned result; + + if ( m_bInFlush ) + { + return 0; + } + + m_bInFlush = true; + + if ( bUnlockedOnly ) + { + result = m_LRU.FlushAllUnlocked(); + } + else + { + result = m_LRU.FlushAll(); + } + + m_bInFlush = false; + + return result; +} + +//----------------------------------------------------------------------------- +// Purpose: Output the state of the cache +//----------------------------------------------------------------------------- +void CDataCache::OutputReport( DataCacheReportType_t reportType, const char *pszSection ) +{ + int i; + + AUTO_LOCK( m_mutex ); + int bytesUsed = m_LRU.UsedSize(); + int bytesTotal = m_LRU.TargetSize(); + + float percent = 100.0f * (float)bytesUsed / (float)bytesTotal; + + CUtlVector lruList, lockedlist; + + m_LRU.GetLockHandleList( lockedlist ); + m_LRU.GetLRUHandleList( lruList ); + + CDataCacheSection *pSection = NULL; + if ( pszSection ) + { + pSection = (CDataCacheSection *)FindSection( pszSection ); + if ( !pSection ) + { + Msg( "Unknown cache section %s\n", pszSection ); + return; + } + } + + if ( reportType == DC_DETAIL_REPORT ) + { + CUtlRBTree< memhandle_t, int > sortedbysize( 0, 0, SortMemhandlesBySizeLessFunc ); + for ( i = 0; i < lockedlist.Count(); ++i ) + { + if ( !pSection || AccessItem( lockedlist[ i ] )->pSection == pSection ) + sortedbysize.Insert( lockedlist[ i ] ); + } + + for ( i = 0; i < lruList.Count(); ++i ) + { + if ( !pSection || AccessItem( lruList[ i ] )->pSection == pSection ) + sortedbysize.Insert( lruList[ i ] ); + } + + for ( i = sortedbysize.FirstInorder(); i != sortedbysize.InvalidIndex(); i = sortedbysize.NextInorder( i ) ) + { + OutputItemReport( sortedbysize[ i ] ); + } + OutputReport( DC_SUMMARY_REPORT, pszSection ); + } + else if ( reportType == DC_DETAIL_REPORT_LRU ) + { + for ( i = 0; i < lockedlist.Count(); ++i ) + { + if ( !pSection || AccessItem( lockedlist[ i ] )->pSection == pSection ) + OutputItemReport( lockedlist[ i ] ); + } + + for ( i = 0; i < lruList.Count(); ++i ) + { + if ( !pSection || AccessItem( lruList[ i ] )->pSection == pSection ) + OutputItemReport( lruList[ i ] ); + } + OutputReport( DC_SUMMARY_REPORT, pszSection ); + } + else if ( reportType == DC_SUMMARY_REPORT ) + { + if ( !pszSection ) + { + // summary for all of the sections + for ( int i = 0; i < m_Sections.Count(); ++i ) + { + if ( m_Sections[i]->GetName() ) + { + OutputReport( DC_SUMMARY_REPORT, m_Sections[i]->GetName() ); + } + } + Msg( "Summary: %i resources total %s, %.2f %% of capacity\n", lockedlist.Count() + lruList.Count(), Q_pretifymem( bytesUsed, 2, true ), percent ); + } + else + { + // summary for the specified section + DataCacheItem_t *pItem; + int sectionBytes = 0; + int sectionCount = 0; + for ( i = 0; i < lockedlist.Count(); ++i ) + { + if ( AccessItem( lockedlist[ i ] )->pSection == pSection ) + { + pItem = g_DataCache.m_LRU.GetResource_NoLockNoLRUTouch( lockedlist[i] ); + sectionBytes += pItem->size; + sectionCount++; + } + } + for ( i = 0; i < lruList.Count(); ++i ) + { + if ( AccessItem( lruList[ i ] )->pSection == pSection ) + { + pItem = g_DataCache.m_LRU.GetResource_NoLockNoLRUTouch( lruList[i] ); + sectionBytes += pItem->size; + sectionCount++; + } + } + int sectionSize = 1; + float sectionPercent; + if ( pSection->GetLimits().nMaxBytes == (unsigned int)-1 ) + { + // section unrestricted, base on total size + sectionSize = bytesTotal; + } + else if ( pSection->GetLimits().nMaxBytes ) + { + sectionSize = pSection->GetLimits().nMaxBytes; + } + sectionPercent = 100.0f * (float)sectionBytes/(float)sectionSize; + Msg( "Section [%s]: %i resources total %s, %.2f %% of limit (%s)\n", pszSection, sectionCount, Q_pretifymem( sectionBytes, 2, true ), sectionPercent, Q_pretifymem( sectionSize, 2, true ) ); + } + } +} + +//------------------------------------- + +void CDataCache::OutputItemReport( memhandle_t hItem ) +{ + AUTO_LOCK( m_mutex ); + DataCacheItem_t *pItem = m_LRU.GetResource_NoLockNoLRUTouch( hItem ); + if ( !pItem ) + return; + + CDataCacheSection *pSection = pItem->pSection; + + char name[DC_MAX_ITEM_NAME+1]; + + name[0] = 0; + pSection->GetClient()->GetItemName( pItem->clientId, pItem->pItemData, name, DC_MAX_ITEM_NAME ); + + Msg( "\t%16.16s : %12s : 0x%08x, 0x%p, 0x%p : %s : %s\n", + Q_pretifymem( pItem->size, 2, true ), + pSection->GetName(), + pItem->clientId, pItem->pItemData, hItem, + ( name[0] ) ? name : "unknown", + ( m_LRU.LockCount( hItem ) ) ? CFmtStr( "Locked %d", m_LRU.LockCount( hItem ) ).operator const char*() : "" ); +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +int CDataCache::FindSectionIndex( const char *pszSection ) +{ + for ( int i = 0; i < m_Sections.Count(); i++ ) + { + if ( stricmp( m_Sections[i]->GetName(), pszSection ) == 0 ) + return i; + } + return m_Sections.InvalidIndex(); +} + + +//----------------------------------------------------------------------------- +// Sorting utility used by the data cache report +//----------------------------------------------------------------------------- +bool CDataCache::SortMemhandlesBySizeLessFunc( const memhandle_t& lhs, const memhandle_t& rhs ) +{ + DataCacheItem_t *pItem1 = g_DataCache.m_LRU.GetResource_NoLockNoLRUTouch( lhs ); + DataCacheItem_t *pItem2 = g_DataCache.m_LRU.GetResource_NoLockNoLRUTouch( rhs ); + + Assert( pItem1 ); + Assert( pItem2 ); + + return pItem1->size < pItem2->size; +} + + diff --git a/datacache/datacache.h b/datacache/datacache.h new file mode 100644 index 0000000..fb3a201 --- /dev/null +++ b/datacache/datacache.h @@ -0,0 +1,384 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//===========================================================================// + +#ifndef DATACACHE_H +#define DATACACHE_H + +#ifdef _WIN32 +#pragma once +#endif + +#include "datamanager.h" +#include "utlhash.h" +#include "mempool.h" +#include "tier0/tslist.h" +#include "datacache_common.h" +#include "tier3/tier3.h" + + +//----------------------------------------------------------------------------- +// +// Data Cache class declarations +// +//----------------------------------------------------------------------------- +class CDataCache; +class CDataCacheSection; + +//----------------------------------------------------------------------------- + +struct DataCacheItemData_t +{ + const void* pItemData; + unsigned size; + DataCacheClientID_t clientId; + CDataCacheSection* pSection; +}; + +//------------------------------------- + +#define DC_NO_NEXT_LOCKED ((DataCacheItem_t *)0xffffffff) +#define DC_MAX_THREADS_FRAMELOCKED 4 + +struct DataCacheItem_t : DataCacheItemData_t +{ + DataCacheItem_t(const DataCacheItemData_t& data) + : DataCacheItemData_t(data), + hLRU(INVALID_MEMHANDLE) + { + memset(pNextFrameLocked, 0xff, sizeof(pNextFrameLocked)); + } + + static DataCacheItem_t* CreateResource(const DataCacheItemData_t& data) { return new DataCacheItem_t(data); } + static unsigned int EstimatedSize(const DataCacheItemData_t& data) { return data.size; } + void DestroyResource(); + DataCacheItem_t* GetData() { return this; } + unsigned int Size() { return size; } + + memhandle_t hLRU; + DataCacheItem_t* pNextFrameLocked[DC_MAX_THREADS_FRAMELOCKED]; + + DECLARE_FIXEDSIZE_ALLOCATOR_MT(DataCacheItem_t); +}; + +//------------------------------------- + +typedef CDataManager CDataCacheLRU; + +//----------------------------------------------------------------------------- +// CDataCacheSection +// +// Purpose: Implements a sub-section of the global cache. Subsections are +// areas of the cache with thier own memory constraints and common +// management. +//----------------------------------------------------------------------------- +class CDataCacheSection : public IDataCacheSection +{ +public: + CDataCacheSection(CDataCache* pSharedCache, IDataCacheClient* pClient, const char* pszName); + ~CDataCacheSection(); + + IDataCache* GetSharedCache(); + IDataCacheClient* GetClient() { return m_pClient; } + const char* GetName() { return szName; } + + //-------------------------------------------------------- + // IDataCacheSection methods + //-------------------------------------------------------- + virtual void SetLimits(const DataCacheLimits_t& limits); + const DataCacheLimits_t& GetLimits(); + virtual void SetOptions(unsigned options); + virtual void GetStatus(DataCacheStatus_t* pStatus, DataCacheLimits_t* pLimits = NULL); + + inline unsigned GetNumBytes() { return m_status.nBytes; } + inline unsigned GetNumItems() { return m_status.nItems; } + + inline unsigned GetNumBytesLocked() { return m_status.nBytesLocked; } + inline unsigned GetNumItemsLocked() { return m_status.nItemsLocked; } + + inline unsigned GetNumBytesUnlocked() { return m_status.nBytes - m_status.nBytesLocked; } + inline unsigned GetNumItemsUnlocked() { return m_status.nItems - m_status.nItemsLocked; } + + virtual void EnsureCapacity(unsigned nBytes, unsigned nItems = 1); + + //-------------------------------------------------------- + + virtual bool Add(DataCacheClientID_t clientId, const void* pItemData, unsigned size, DataCacheHandle_t* pHandle); + virtual bool AddEx(DataCacheClientID_t clientId, const void* pItemData, unsigned size, unsigned flags, DataCacheHandle_t* pHandle); + virtual DataCacheHandle_t Find(DataCacheClientID_t clientId); + virtual DataCacheRemoveResult_t Remove(DataCacheHandle_t handle, const void** ppItemData = NULL, unsigned* pItemSize = NULL, bool bNotify = false); + virtual bool IsPresent(DataCacheHandle_t handle); + + //-------------------------------------------------------- + + virtual void* Lock(DataCacheHandle_t handle); + virtual int Unlock(DataCacheHandle_t handle); + virtual void* Get(DataCacheHandle_t handle, bool bFrameLock = false); + virtual void* GetNoTouch(DataCacheHandle_t handle, bool bFrameLock = false); + virtual void LockMutex(); + virtual void UnlockMutex(); + + //-------------------------------------------------------- + + virtual int BeginFrameLocking(); + virtual bool IsFrameLocking(); + virtual void* FrameLock(DataCacheHandle_t handle); + virtual int EndFrameLocking(); + + //-------------------------------------------------------- + + virtual int GetLockCount(DataCacheHandle_t handle); + virtual int BreakLock(DataCacheHandle_t handle); + + //-------------------------------------------------------- + + virtual int* GetFrameUnlockCounterPtr(); + int m_nFrameUnlockCounter; + + //-------------------------------------------------------- + + virtual bool Touch(DataCacheHandle_t handle); + virtual bool Age(DataCacheHandle_t handle); + + //-------------------------------------------------------- + + virtual unsigned Flush(bool bUnlockedOnly = true, bool bNotify = true); + virtual unsigned Purge(unsigned nBytes); + unsigned PurgeItems(unsigned nItems); + + //-------------------------------------------------------- + + virtual void OutputReport(DataCacheReportType_t reportType = DC_SUMMARY_REPORT); + + virtual void UpdateSize(DataCacheHandle_t handle, unsigned int nNewSize); + +private: + friend void DataCacheItem_t::DestroyResource(); + + virtual void OnAdd(DataCacheClientID_t clientId, DataCacheHandle_t hCacheItem) {} + virtual DataCacheHandle_t DoFind(DataCacheClientID_t clientId); + virtual void OnRemove(DataCacheClientID_t clientId) {} + + memhandle_t GetFirstUnlockedItem(); + memhandle_t GetFirstLockedItem(); + memhandle_t GetNextItem(memhandle_t); + DataCacheItem_t* AccessItem(memhandle_t hCurrent); + bool DiscardItem(memhandle_t hItem, DataCacheNotificationType_t type); + bool DiscardItemData(DataCacheItem_t* pItem, DataCacheNotificationType_t type); + void NoteAdd(int size); + void NoteRemove(int size); + void NoteLock(int size); + void NoteUnlock(int size); + void NoteSizeChanged(int oldSize, int newSize); + + struct FrameLock_t + { + //$ WARNING: This needs a TSLNodeBase_t as the first item in here. + TSLNodeBase_t base; + int m_iLock; + DataCacheItem_t* m_pFirst; + int m_iThread; + }; + typedef CThreadLocal CThreadFrameLock; + + CDataCacheLRU& m_LRU; + CThreadFrameLock m_ThreadFrameLock; + DataCacheStatus_t m_status; + DataCacheLimits_t m_limits; + IDataCacheClient* m_pClient; + unsigned m_options; + CDataCache* m_pSharedCache; + char szName[DC_MAX_CLIENT_NAME + 1]; + CTSSimpleList m_FreeFrameLocks; + +protected: + CThreadFastMutex& m_mutex; +}; + + +//----------------------------------------------------------------------------- +// CDataCacheSectionFastFind +// +// Purpose: A section variant that allows clients to have cache support tracking +// efficiently (a true cache, not just an LRU) +//----------------------------------------------------------------------------- +class CDataCacheSectionFastFind : public CDataCacheSection +{ +public: + CDataCacheSectionFastFind(CDataCache* pSharedCache, IDataCacheClient* pClient, const char* pszName) + : CDataCacheSection(pSharedCache, pClient, pszName) + { + m_Handles.Init(1024); + } + +private: + virtual DataCacheHandle_t DoFind(DataCacheClientID_t clientId); + virtual void OnAdd(DataCacheClientID_t clientId, DataCacheHandle_t hCacheItem); + virtual void OnRemove(DataCacheClientID_t clientId); + + CUtlHashFast m_Handles; +}; + + +//----------------------------------------------------------------------------- +// CDataCache +// +// Purpose: The global shared cache. Manages sections and overall budgets. +// +//----------------------------------------------------------------------------- +class CDataCache : public CTier3AppSystem< IDataCache > +{ + typedef CTier3AppSystem< IDataCache > BaseClass; + +public: + CDataCache(); + + //-------------------------------------------------------- + // IAppSystem methods + //-------------------------------------------------------- + virtual bool Connect(CreateInterfaceFn factory); + virtual void Disconnect(); + virtual void* QueryInterface(const char* pInterfaceName); + virtual InitReturnVal_t Init(); + virtual void Shutdown(); + + //-------------------------------------------------------- + // IDataCache methods + //-------------------------------------------------------- + + virtual void SetSize(int nMaxBytes); + virtual void SetOptions(unsigned options); + virtual void SetSectionLimits(const char* pszSectionName, const DataCacheLimits_t& limits); + virtual void GetStatus(DataCacheStatus_t* pStatus, DataCacheLimits_t* pLimits = NULL); + + //-------------------------------------------------------- + + virtual IDataCacheSection* AddSection(IDataCacheClient* pClient, const char* pszSectionName, const DataCacheLimits_t& limits = DataCacheLimits_t(), bool bSupportFastFind = false); + virtual void RemoveSection(const char* pszClientName, bool bCallFlush = true); + virtual IDataCacheSection* FindSection(const char* pszClientName); + + //-------------------------------------------------------- + + void EnsureCapacity(unsigned nBytes); + virtual unsigned Purge(unsigned nBytes); + virtual unsigned Flush(bool bUnlockedOnly = true, bool bNotify = true); + + //-------------------------------------------------------- + + virtual void OutputReport(DataCacheReportType_t reportType = DC_SUMMARY_REPORT, const char* pszSection = NULL); + + //-------------------------------------------------------- + + inline unsigned GetNumBytes() { return m_status.nBytes; } + inline unsigned GetNumItems() { return m_status.nItems; } + + inline unsigned GetNumBytesLocked() { return m_status.nBytesLocked; } + inline unsigned GetNumItemsLocked() { return m_status.nItemsLocked; } + + inline unsigned GetNumBytesUnlocked() { return m_status.nBytes - m_status.nBytesLocked; } + inline unsigned GetNumItemsUnlocked() { return m_status.nItems - m_status.nItemsLocked; } + +private: + //----------------------------------------------------- + + friend class CDataCacheSection; + + //----------------------------------------------------- + + DataCacheItem_t* AccessItem(memhandle_t hCurrent); + + bool IsInFlush() { return m_bInFlush; } + int FindSectionIndex(const char* pszSection); + + // Utilities used by the data cache report + void OutputItemReport(memhandle_t hItem); + static bool SortMemhandlesBySizeLessFunc(const memhandle_t& lhs, const memhandle_t& rhs); + + //----------------------------------------------------- + + CDataCacheLRU m_LRU; + DataCacheStatus_t m_status; + CUtlVector m_Sections; + bool m_bInFlush; + CThreadFastMutex& m_mutex; +}; + +//--------------------------------------------------------- + +extern CDataCache g_DataCache; + +//----------------------------------------------------------------------------- + +inline DataCacheItem_t* CDataCache::AccessItem(memhandle_t hCurrent) +{ + return m_LRU.GetResource_NoLockNoLRUTouch(hCurrent); +} + +//----------------------------------------------------------------------------- + +inline IDataCache* CDataCacheSection::GetSharedCache() +{ + return m_pSharedCache; +} + +inline DataCacheItem_t* CDataCacheSection::AccessItem(memhandle_t hCurrent) +{ + return m_pSharedCache->AccessItem(hCurrent); +} + +// Note: if status updates are moved out of a mutexed section, will need to change these to use interlocked instructions + +inline void CDataCacheSection::NoteSizeChanged(int oldSize, int newSize) +{ + int nBytes = (newSize - oldSize); + + m_status.nBytes += nBytes; + m_status.nBytesLocked += nBytes; + ThreadInterlockedExchangeAdd(&m_pSharedCache->m_status.nBytes, nBytes); + ThreadInterlockedExchangeAdd(&m_pSharedCache->m_status.nBytesLocked, nBytes); +} + +inline void CDataCacheSection::NoteAdd(int size) +{ + m_status.nBytes += size; + m_status.nItems++; + + ThreadInterlockedExchangeAdd(&m_pSharedCache->m_status.nBytes, size); + ThreadInterlockedIncrement(&m_pSharedCache->m_status.nItems); +} + +inline void CDataCacheSection::NoteRemove(int size) +{ + m_status.nBytes -= size; + m_status.nItems--; + + ThreadInterlockedExchangeAdd(&m_pSharedCache->m_status.nBytes, -size); + ThreadInterlockedDecrement(&m_pSharedCache->m_status.nItems); +} + +inline void CDataCacheSection::NoteLock(int size) +{ + m_status.nBytesLocked += size; + m_status.nItemsLocked++; + + ThreadInterlockedExchangeAdd(&m_pSharedCache->m_status.nBytesLocked, size); + ThreadInterlockedIncrement(&m_pSharedCache->m_status.nItemsLocked); +} + +inline void CDataCacheSection::NoteUnlock(int size) +{ + m_status.nBytesLocked -= size; + m_status.nItemsLocked--; + + ThreadInterlockedExchangeAdd(&m_pSharedCache->m_status.nBytesLocked, -size); + ThreadInterlockedDecrement(&m_pSharedCache->m_status.nItemsLocked); + + // something has been unlocked, assume cached pointers are now invalid + m_nFrameUnlockCounter++; +} + +//----------------------------------------------------------------------------- + +#endif // DATACACHE_H \ No newline at end of file diff --git a/datacache/datacache.vpc b/datacache/datacache.vpc new file mode 100644 index 0000000..27a64f5 --- /dev/null +++ b/datacache/datacache.vpc @@ -0,0 +1,55 @@ +//----------------------------------------------------------------------------- +// DATACACHE.VPC +// +// Project Script +//----------------------------------------------------------------------------- + +$macro SRCDIR ".." +$Macro OUTBINDIR "$SRCDIR\..\game\bin" + +$include "$SRCDIR\vpc_scripts\source_dll_base.vpc" + +$Configuration +{ + $Linker + { + $SystemLibraries "iconv" [$OSXALL] + } + $Compiler + { + $PreprocessorDefinitions "$BASE;MDLCACHE_DLL_EXPORT" + } +} + + +$Project "datacache" +{ + $Folder "Source Files" + { + $File "datacache.cpp" + $File "mdlcache.cpp" + $File "$SRCDIR\public\studio.cpp" + $File "$SRCDIR\public\studio_virtualmodel.cpp" + $File "..\common\studiobyteswap.cpp" + } + + $Folder "Header Files" + { + $File "datacache.h" + $File "datacache_common.h" + $File "$SRCDIR\public\studio.h" + $File "..\common\studiobyteswap.h" + } + + $Folder "Interface" + { + $File "$SRCDIR\public\datacache\idatacache.h" + $File "$SRCDIR\public\datacache\imdlcache.h" + } + + $folder "Link Libraries" + { + $Lib tier2 + $Lib tier3 + } +} diff --git a/datacache/datacache_common.h b/datacache/datacache_common.h new file mode 100644 index 0000000..7140511 --- /dev/null +++ b/datacache/datacache_common.h @@ -0,0 +1,31 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//===========================================================================// + +#ifndef DATACACHE_COMMON_H +#define DATACACHE_COMMON_H + +#if defined( _WIN32 ) +#pragma once +#endif + +#include "tier3/tier3.h" + + +//----------------------------------------------------------------------------- +// Forward declarations +//----------------------------------------------------------------------------- +class IDataCacheSection; +FORWARD_DECLARE_HANDLE( memhandle_t ); +typedef memhandle_t DataCacheHandle_t; + + +//----------------------------------------------------------------------------- +// Console commands +//----------------------------------------------------------------------------- +extern ConVar developer; + + +#endif // DATACACHE_COMMON_H diff --git a/datacache/mdlcache.cpp b/datacache/mdlcache.cpp new file mode 100644 index 0000000..46245e3 --- /dev/null +++ b/datacache/mdlcache.cpp @@ -0,0 +1,3924 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// The copyright to the contents herein is the property of Valve, L.L.C. +// The contents may be used and/or copied only with the written permission of +// Valve, L.L.C., or in accordance with the terms and conditions stipulated in +// the agreement/contract under which the contents have been supplied. +// +// model loading and caching +// +//===========================================================================// + +#include +#include "tier0/vprof.h" +#include "tier0/icommandline.h" +#include "tier1/utllinkedlist.h" +#include "tier1/utlmap.h" +#include "datacache/imdlcache.h" +#include "istudiorender.h" +#include "filesystem.h" +#include "optimize.h" +#include "materialsystem/imaterialsystemhardwareconfig.h" +#include "materialsystem/imesh.h" +#include "datacache/idatacache.h" +#include "studio.h" +#include "vcollide.h" +#include "utldict.h" +#include "convar.h" +#include "datacache_common.h" +#include "mempool.h" +#include "vphysics_interface.h" +#include "phyfile.h" +#include "studiobyteswap.h" +#include "tier2/fileutils.h" +#include "filesystem/IQueuedLoader.h" +#include "tier1/lzmaDecoder.h" +#include "functors.h" + +// XXX remove this later. (henryg) +#if 0 && defined(_DEBUG) && defined(_WIN32) && !defined(_X360) +typedef struct LARGE_INTEGER { unsigned long long QuadPart; } LARGE_INTEGER; +extern "C" void __stdcall OutputDebugStringA( const char *lpOutputString ); +extern "C" long __stdcall QueryPerformanceCounter( LARGE_INTEGER *lpPerformanceCount ); +extern "C" long __stdcall QueryPerformanceFrequency( LARGE_INTEGER *lpPerformanceCount ); +namespace { + class CDebugMicroTimer + { + public: + CDebugMicroTimer(const char* n) : name(n) { QueryPerformanceCounter(&start); } + ~CDebugMicroTimer() { + LARGE_INTEGER end; + char outbuf[128]; + QueryPerformanceCounter(&end); + if (!freq) QueryPerformanceFrequency((LARGE_INTEGER*)&freq); + V_snprintf(outbuf, 128, "%s %6d us\n", name, (int)((end.QuadPart - start.QuadPart) * 1000000 / freq)); + OutputDebugStringA(outbuf); + } + LARGE_INTEGER start; + const char* name; + static long long freq; + }; + long long CDebugMicroTimer::freq = 0; +} +#define DEBUG_SCOPE_TIMER(name) CDebugMicroTimer dbgLocalTimer(#name) +#else +#define DEBUG_SCOPE_TIMER(name) (void)0 +#endif + +#ifdef _RETAIL +#define NO_LOG_MDLCACHE 1 +#endif + +#ifdef NO_LOG_MDLCACHE +#define LogMdlCache() 0 +#else +#define LogMdlCache() mod_trace_load.GetBool() +#endif + +#define MdlCacheMsg if ( !LogMdlCache() ) ; else Msg +#define MdlCacheWarning if ( !LogMdlCache() ) ; else Warning + +#if defined( _X360 ) +#define AsyncMdlCache() 0 // Explicitly OFF for 360 (incompatible) +#else +#define AsyncMdlCache() 0 +#endif + +#define ERROR_MODEL "models/error.mdl" +#define IDSTUDIOHEADER (('T'<<24)+('S'<<16)+('D'<<8)+'I') + +#define MakeCacheID( handle, type ) ( ( (uint)(handle) << 16 ) | (uint)(type) ) +#define HandleFromCacheID( id) ( (MDLHandle_t)((id) >> 16) ) +#define TypeFromCacheID( id ) ( (MDLCacheDataType_t)((id) & 0xffff) ) + +enum +{ + STUDIODATA_FLAGS_STUDIOMESH_LOADED = 0x0001, + STUDIODATA_FLAGS_VCOLLISION_LOADED = 0x0002, + STUDIODATA_ERROR_MODEL = 0x0004, + STUDIODATA_FLAGS_NO_STUDIOMESH = 0x0008, + STUDIODATA_FLAGS_NO_VERTEX_DATA = 0x0010, + STUDIODATA_FLAGS_VCOLLISION_SHARED = 0x0020, + STUDIODATA_FLAGS_LOCKED_MDL = 0x0040, +}; + +// only models with type "mod_studio" have this data +struct studiodata_t +{ + // The .mdl file + DataCacheHandle_t m_MDLCache; + + // the vphysics.dll collision model + vcollide_t m_VCollisionData; + + studiohwdata_t m_HardwareData; +#if defined( USE_HARDWARE_CACHE ) + DataCacheHandle_t m_HardwareDataCache; +#endif + + unsigned short m_nFlags; + + short m_nRefCount; + + // pointer to the virtual version of the model + virtualmodel_t *m_pVirtualModel; + + // array of cache handles to demand loaded virtual model data + int m_nAnimBlockCount; + DataCacheHandle_t *m_pAnimBlock; + unsigned long *m_iFakeAnimBlockStall; + + // vertex data is usually compressed to save memory (model decal code only needs some data) + DataCacheHandle_t m_VertexCache; + bool m_VertexDataIsCompressed; + + int m_nAutoplaySequenceCount; + unsigned short *m_pAutoplaySequenceList; + + void *m_pUserData; + + DECLARE_FIXEDSIZE_ALLOCATOR_MT( studiodata_t ); +}; + +DEFINE_FIXEDSIZE_ALLOCATOR_MT( studiodata_t, 128, CUtlMemoryPool::GROW_SLOW ); + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +class CTempAllocHelper +{ +public: + CTempAllocHelper() + { + m_pData = NULL; + } + + ~CTempAllocHelper() + { + Free(); + } + + void *Get() + { + return m_pData; + } + + void Alloc( int nSize ) + { + m_pData = malloc( nSize ); + } + + void Free() + { + if ( m_pData ) + { + free( m_pData ); + m_pData = NULL; + } + } +private: + void *m_pData; +}; + +//----------------------------------------------------------------------------- +// ConVars +//----------------------------------------------------------------------------- +static ConVar r_rootlod( "r_rootlod", "0", FCVAR_ARCHIVE ); +static ConVar mod_forcedata( "mod_forcedata", ( AsyncMdlCache() ) ? "0" : "1", 0, "Forces all model file data into cache on model load." ); +static ConVar mod_test_not_available( "mod_test_not_available", "0", FCVAR_CHEAT ); +static ConVar mod_test_mesh_not_available( "mod_test_mesh_not_available", "0", FCVAR_CHEAT ); +static ConVar mod_test_verts_not_available( "mod_test_verts_not_available", "0", FCVAR_CHEAT ); +static ConVar mod_load_mesh_async( "mod_load_mesh_async", ( AsyncMdlCache() ) ? "1" : "0" ); +static ConVar mod_load_anims_async( "mod_load_anims_async", ( IsX360() || AsyncMdlCache() ) ? "1" : "0" ); +static ConVar mod_load_vcollide_async( "mod_load_vcollide_async", ( AsyncMdlCache() ) ? "1" : "0" ); +static ConVar mod_trace_load( "mod_trace_load", "0" ); +static ConVar mod_lock_mdls_on_load( "mod_lock_mdls_on_load", ( IsX360() ) ? "1" : "0" ); +static ConVar mod_load_fakestall( "mod_load_fakestall", "0", 0, "Forces all ANI file loading to stall for specified ms\n"); + +//----------------------------------------------------------------------------- +// Utility functions +//----------------------------------------------------------------------------- + +#if defined( USE_HARDWARE_CACHE ) +unsigned ComputeHardwareDataSize( studiohwdata_t *pData ) +{ + unsigned size = 0; + for ( int i = pData->m_RootLOD; i < pData->m_NumLODs; i++ ) + { + studioloddata_t *pLOD = &pData->m_pLODs[i]; + for ( int j = 0; j < pData->m_NumStudioMeshes; j++ ) + { + studiomeshdata_t *pMeshData = &pLOD->m_pMeshData[j]; + for ( int k = 0; k < pMeshData->m_NumGroup; k++ ) + { + size += pMeshData->m_pMeshGroup[k].m_pMesh->ComputeMemoryUsed(); + } + } + } + return size; +} +#endif + +//----------------------------------------------------------------------------- +// Async support +//----------------------------------------------------------------------------- + +#define MDLCACHE_NONE ((MDLCacheDataType_t)-1) + +struct AsyncInfo_t +{ + AsyncInfo_t() : hControl( NULL ), hModel( MDLHANDLE_INVALID ), type( MDLCACHE_NONE ), iAnimBlock( 0 ) {} + + FSAsyncControl_t hControl; + MDLHandle_t hModel; + MDLCacheDataType_t type; + int iAnimBlock; +}; + +const int NO_ASYNC = CUtlLinkedList< AsyncInfo_t >::InvalidIndex(); + +//------------------------------------- + +CUtlMap g_AsyncInfoMap( DefLessFunc( int ) ); +CThreadFastMutex g_AsyncInfoMapMutex; + +inline int MakeAsyncInfoKey( MDLHandle_t hModel, MDLCacheDataType_t type, int iAnimBlock ) +{ + Assert( type <= 7 && iAnimBlock < 8*1024 ); + return ( ( ( (int)hModel) << 16 ) | ( (int)type << 13 ) | iAnimBlock ); +} + +inline int GetAsyncInfoIndex( MDLHandle_t hModel, MDLCacheDataType_t type, int iAnimBlock = 0 ) +{ + AUTO_LOCK( g_AsyncInfoMapMutex ); + int key = MakeAsyncInfoKey( hModel, type, iAnimBlock ); + int i = g_AsyncInfoMap.Find( key ); + if ( i == g_AsyncInfoMap.InvalidIndex() ) + { + return NO_ASYNC; + } + return g_AsyncInfoMap[i]; +} + +inline int SetAsyncInfoIndex( MDLHandle_t hModel, MDLCacheDataType_t type, int iAnimBlock, int index ) +{ + AUTO_LOCK( g_AsyncInfoMapMutex ); + Assert( index == NO_ASYNC || GetAsyncInfoIndex( hModel, type, iAnimBlock ) == NO_ASYNC ); + int key = MakeAsyncInfoKey( hModel, type, iAnimBlock ); + if ( index == NO_ASYNC ) + { + g_AsyncInfoMap.Remove( key ); + } + else + { + g_AsyncInfoMap.Insert( key, index ); + } + + return index; +} + +inline int SetAsyncInfoIndex( MDLHandle_t hModel, MDLCacheDataType_t type, int index ) +{ + return SetAsyncInfoIndex( hModel, type, 0, index ); +} + +//----------------------------------------------------------------------------- +// QUEUED LOADING +// Populates the cache by pushing expected MDL's (and all of their data). +// The Model cache i/o behavior is unchanged during gameplay, ideally the cache +// should yield miss free behaviour. +//----------------------------------------------------------------------------- + +struct ModelParts_t +{ + enum BufferType_t + { + BUFFER_MDL = 0, + BUFFER_VTX = 1, + BUFFER_VVD = 2, + BUFFER_PHY = 3, + BUFFER_MAXPARTS, + }; + + ModelParts_t() + { + nLoadedParts = 0; + nExpectedParts = 0; + hMDL = MDLHANDLE_INVALID; + hFileCache = 0; + bHeaderLoaded = false; + bMaterialsPending = false; + bTexturesPending = false; + } + + // thread safe, only one thread will get a positive result + bool DoFinalProcessing() + { + // indicates that all buffers have arrived + // when all parts are present, returns true ( guaranteed once ), and marked as completed + return nLoadedParts.AssignIf( nExpectedParts, nExpectedParts | 0x80000000 ); + } + + CUtlBuffer Buffers[BUFFER_MAXPARTS]; + MDLHandle_t hMDL; + + // async material loading on PC + FileCacheHandle_t hFileCache; + bool bHeaderLoaded; + bool bMaterialsPending; + bool bTexturesPending; + CUtlVector< IMaterial* > Materials; + + // bit flags + CInterlockedInt nLoadedParts; + int nExpectedParts; + +private: + ModelParts_t(const ModelParts_t&); // no impl + ModelParts_t& operator=(const ModelParts_t&); // no impl +}; + +struct CleanupModelParts_t +{ + FileCacheHandle_t hFileCache; + CUtlVector< IMaterial* > Materials; +}; + +//----------------------------------------------------------------------------- +// Implementation of the simple studio data cache (no caching) +//----------------------------------------------------------------------------- +class CMDLCache : public CTier3AppSystem< IMDLCache >, public IStudioDataCache, public CDefaultDataCacheClient +{ + typedef CTier3AppSystem< IMDLCache > BaseClass; + +public: + CMDLCache(); + + // Inherited from IAppSystem + virtual bool Connect( CreateInterfaceFn factory ); + virtual void Disconnect(); + virtual void *QueryInterface( const char *pInterfaceName ); + virtual InitReturnVal_t Init(); + virtual void Shutdown(); + + // Inherited from IStudioDataCache + bool VerifyHeaders( studiohdr_t *pStudioHdr ); + vertexFileHeader_t *CacheVertexData( studiohdr_t *pStudioHdr ); + + // Inherited from IMDLCache + virtual MDLHandle_t FindMDL( const char *pMDLRelativePath ); + virtual int AddRef( MDLHandle_t handle ); + virtual int Release( MDLHandle_t handle ); + virtual int GetRef( MDLHandle_t handle ); + virtual void MarkAsLoaded(MDLHandle_t handle); + + virtual studiohdr_t *GetStudioHdr( MDLHandle_t handle ); + virtual studiohwdata_t *GetHardwareData( MDLHandle_t handle ); + virtual vcollide_t *GetVCollide( MDLHandle_t handle ) { return GetVCollideEx( handle, true); } + virtual vcollide_t *GetVCollideEx( MDLHandle_t handle, bool synchronousLoad = true ); + virtual unsigned char *GetAnimBlock( MDLHandle_t handle, int nBlock ); + virtual virtualmodel_t *GetVirtualModel( MDLHandle_t handle ); + virtual virtualmodel_t *GetVirtualModelFast( const studiohdr_t *pStudioHdr, MDLHandle_t handle ); + virtual int GetAutoplayList( MDLHandle_t handle, unsigned short **pOut ); + virtual void TouchAllData( MDLHandle_t handle ); + virtual void SetUserData( MDLHandle_t handle, void* pData ); + virtual void *GetUserData( MDLHandle_t handle ); + virtual bool IsErrorModel( MDLHandle_t handle ); + virtual void SetCacheNotify( IMDLCacheNotify *pNotify ); + virtual vertexFileHeader_t *GetVertexData( MDLHandle_t handle ); + virtual void Flush( MDLCacheFlush_t nFlushFlags = MDLCACHE_FLUSH_ALL ); + virtual void Flush( MDLHandle_t handle, int nFlushFlags = MDLCACHE_FLUSH_ALL ); + virtual const char *GetModelName( MDLHandle_t handle ); + + IDataCacheSection *GetCacheSection( MDLCacheDataType_t type ) + { + switch ( type ) + { + case MDLCACHE_STUDIOHWDATA: + case MDLCACHE_VERTEXES: + // meshes and vertexes are isolated to their own section + return m_pMeshCacheSection; + + case MDLCACHE_ANIMBLOCK: + // anim blocks have their own section + return m_pAnimBlockCacheSection; + + default: + // everybody else + return m_pModelCacheSection; + } + } + + void *AllocData( MDLCacheDataType_t type, int size ); + void FreeData( MDLCacheDataType_t type, void *pData ); + void CacheData( DataCacheHandle_t *c, void *pData, int size, const char *name, MDLCacheDataType_t type, DataCacheClientID_t id = (DataCacheClientID_t)-1 ); + void *CheckData( DataCacheHandle_t c, MDLCacheDataType_t type ); + void *CheckDataNoTouch( DataCacheHandle_t c, MDLCacheDataType_t type ); + void UncacheData( DataCacheHandle_t c, MDLCacheDataType_t type, bool bLockedOk = false ); + + void DisableAsync() { mod_load_mesh_async.SetValue( 0 ); mod_load_anims_async.SetValue( 0 ); } + + virtual void BeginLock(); + virtual void EndLock(); + virtual int *GetFrameUnlockCounterPtrOLD(); + virtual int *GetFrameUnlockCounterPtr( MDLCacheDataType_t type ); + + virtual void FinishPendingLoads(); + + // Task switch + void ReleaseMaterialSystemObjects(); + void RestoreMaterialSystemObjects( int nChangeFlags ); + virtual bool GetVCollideSize( MDLHandle_t handle, int *pVCollideSize ); + + virtual void BeginMapLoad(); + virtual void EndMapLoad(); + + virtual void InitPreloadData( bool rebuild ); + virtual void ShutdownPreloadData(); + + virtual bool IsDataLoaded( MDLHandle_t handle, MDLCacheDataType_t type ); + + virtual studiohdr_t *LockStudioHdr( MDLHandle_t handle ); + virtual void UnlockStudioHdr( MDLHandle_t handle ); + + virtual bool PreloadModel( MDLHandle_t handle ); + virtual void ResetErrorModelStatus( MDLHandle_t handle ); + + virtual void MarkFrame(); + + // Queued loading + void ProcessQueuedData( ModelParts_t *pModelParts, bool bHeaderOnly = false ); + static void QueuedLoaderCallback_MDL( void *pContext, void *pContext2, const void *pData, int nSize, LoaderError_t loaderError ); + static void ProcessDynamicLoad( ModelParts_t *pModelParts ); + static void CleanupDynamicLoad( CleanupModelParts_t *pCleanup ); + +private: + // Inits, shuts downs studiodata_t + void InitStudioData( MDLHandle_t handle ); + void ShutdownStudioData( MDLHandle_t handle ); + + // Returns the *actual* name of the model (could be an error model if the requested model didn't load) + const char *GetActualModelName( MDLHandle_t handle ); + + // Constructs a filename based on a model handle + void MakeFilename( MDLHandle_t handle, const char *pszExtension, char *pszFileName, int nMaxLength ); + + // Inform filesystem that we unloaded a particular file + void NotifyFileUnloaded( MDLHandle_t handle, const char *pszExtension ); + + // Attempts to load a MDL file, validates that it's ok. + bool ReadMDLFile( MDLHandle_t handle, const char *pMDLFileName, CUtlBuffer &buf ); + + // Unserializes the VCollide file associated w/ models (the vphysics representation) + void UnserializeVCollide( MDLHandle_t handle, bool synchronousLoad ); + + // Destroys the VCollide associated w/ models + void DestroyVCollide( MDLHandle_t handle ); + + // Unserializes the MDL + studiohdr_t *UnserializeMDL( MDLHandle_t handle, void *pData, int nDataSize, bool bDataValid ); + + // Unserializes an animation block from disk + unsigned char *UnserializeAnimBlock( MDLHandle_t handle, int nBlock ); + + // Allocates/frees the anim blocks + void AllocateAnimBlocks( studiodata_t *pStudioData, int nCount ); + void FreeAnimBlocks( MDLHandle_t handle ); + + // Allocates/frees the virtual model + void AllocateVirtualModel( MDLHandle_t handle ); + void FreeVirtualModel( MDLHandle_t handle ); + + // Purpose: Pulls all submodels/.ani file models into the cache + void UnserializeAllVirtualModelsAndAnimBlocks( MDLHandle_t handle ); + + // Loads/unloads the static meshes + bool LoadHardwareData( MDLHandle_t handle ); // returns false if not ready + void UnloadHardwareData( MDLHandle_t handle, bool bCacheRemove = true, bool bLockedOk = false ); + + // Allocates/frees autoplay sequence list + void AllocateAutoplaySequences( studiodata_t *pStudioData, int nCount ); + void FreeAutoplaySequences( studiodata_t *pStudioData ); + + FSAsyncStatus_t LoadData( const char *pszFilename, const char *pszPathID, bool bAsync, FSAsyncControl_t *pControl ) { return LoadData( pszFilename, pszPathID, NULL, 0, 0, bAsync, pControl ); } + FSAsyncStatus_t LoadData( const char *pszFilename, const char *pszPathID, void *pDest, int nBytes, int nOffset, bool bAsync, FSAsyncControl_t *pControl ); + vertexFileHeader_t *LoadVertexData( studiohdr_t *pStudioHdr ); + vertexFileHeader_t *BuildAndCacheVertexData( studiohdr_t *pStudioHdr, vertexFileHeader_t *pRawVvdHdr ); + bool BuildHardwareData( MDLHandle_t handle, studiodata_t *pStudioData, studiohdr_t *pStudioHdr, OptimizedModel::FileHeader_t *pVtxHdr ); + void ConvertFlexData( studiohdr_t *pStudioHdr ); + + int ProcessPendingAsync( int iAsync ); + void ProcessPendingAsyncs( MDLCacheDataType_t type = MDLCACHE_NONE ); + bool ClearAsync( MDLHandle_t handle, MDLCacheDataType_t type, int iAnimBlock, bool bAbort = false ); + + const char *GetVTXExtension(); + + virtual bool HandleCacheNotification( const DataCacheNotification_t ¬ification ); + virtual bool GetItemName( DataCacheClientID_t clientId, const void *pItem, char *pDest, unsigned nMaxLen ); + + virtual bool GetAsyncLoad( MDLCacheDataType_t type ); + virtual bool SetAsyncLoad( MDLCacheDataType_t type, bool bAsync ); + + // Creates the 360 file if it doesn't exist or is out of date + int UpdateOrCreate( studiohdr_t *pHdr, const char *pFilename, char *pX360Filename, int maxLen, const char *pPathID, bool bForce = false ); + + // Attempts to read the platform native file - on 360 it can read and swap Win32 file as a fallback + bool ReadFileNative( char *pFileName, const char *pPath, CUtlBuffer &buf, int nMaxBytes = 0 ); + + // Creates a thin cache entry (to be used for model decals) from fat vertex data + vertexFileHeader_t * CreateThinVertexes( vertexFileHeader_t * originalData, const studiohdr_t * pStudioHdr, int * cacheLength ); + + // Processes raw data (from an I/O source) into the cache. Sets the cache state as expected for bad data. + bool ProcessDataIntoCache( MDLHandle_t handle, MDLCacheDataType_t type, int iAnimBlock, void *pData, int nDataSize, bool bDataValid ); + + void BreakFrameLock( bool bModels = true, bool bMesh = true ); + void RestoreFrameLock(); + +private: + IDataCacheSection *m_pModelCacheSection; + IDataCacheSection *m_pMeshCacheSection; + IDataCacheSection *m_pAnimBlockCacheSection; + + int m_nModelCacheFrameLocks; + int m_nMeshCacheFrameLocks; + + CUtlDict< studiodata_t*, MDLHandle_t > m_MDLDict; + + IMDLCacheNotify *m_pCacheNotify; + + CUtlFixedLinkedList< AsyncInfo_t > m_PendingAsyncs; + + CThreadFastMutex m_QueuedLoadingMutex; + CThreadFastMutex m_AsyncMutex; + + bool m_bLostVideoMemory : 1; + bool m_bConnected : 1; + bool m_bInitialized : 1; +}; + +//----------------------------------------------------------------------------- +// Singleton interface +//----------------------------------------------------------------------------- +static CMDLCache g_MDLCache; +EXPOSE_SINGLE_INTERFACE_GLOBALVAR( CMDLCache, IMDLCache, MDLCACHE_INTERFACE_VERSION, g_MDLCache ); +EXPOSE_SINGLE_INTERFACE_GLOBALVAR( CMDLCache, IStudioDataCache, STUDIO_DATA_CACHE_INTERFACE_VERSION, g_MDLCache ); + + +//----------------------------------------------------------------------------- +// Task switch +//----------------------------------------------------------------------------- +static void ReleaseMaterialSystemObjects( ) +{ + g_MDLCache.ReleaseMaterialSystemObjects(); +} + +static void RestoreMaterialSystemObjects( int nChangeFlags ) +{ + g_MDLCache.RestoreMaterialSystemObjects( nChangeFlags ); +} + + + +//----------------------------------------------------------------------------- +// Constructor +//----------------------------------------------------------------------------- +CMDLCache::CMDLCache() : BaseClass( false ) +{ + m_bLostVideoMemory = false; + m_bConnected = false; + m_bInitialized = false; + m_pCacheNotify = NULL; + m_pModelCacheSection = NULL; + m_pMeshCacheSection = NULL; + m_pAnimBlockCacheSection = NULL; + m_nModelCacheFrameLocks = 0; + m_nMeshCacheFrameLocks = 0; +} + + +//----------------------------------------------------------------------------- +// Connect, disconnect +//----------------------------------------------------------------------------- +bool CMDLCache::Connect( CreateInterfaceFn factory ) +{ + // Connect can be called twice, because this inherits from 2 appsystems. + if ( m_bConnected ) + return true; + + if ( !BaseClass::Connect( factory ) ) + return false; + + if ( !g_pMaterialSystemHardwareConfig || !g_pPhysicsCollision || !g_pStudioRender || !g_pMaterialSystem ) + return false; + + m_bConnected = true; + if( g_pMaterialSystem ) + { + g_pMaterialSystem->AddReleaseFunc( ::ReleaseMaterialSystemObjects ); + g_pMaterialSystem->AddRestoreFunc( ::RestoreMaterialSystemObjects ); + } + + return true; +} + +void CMDLCache::Disconnect() +{ + if ( g_pMaterialSystem && m_bConnected ) + { + g_pMaterialSystem->RemoveReleaseFunc( ::ReleaseMaterialSystemObjects ); + g_pMaterialSystem->RemoveRestoreFunc( ::RestoreMaterialSystemObjects ); + m_bConnected = false; + } + + BaseClass::Disconnect(); +} + + +//----------------------------------------------------------------------------- +// Query Interface +//----------------------------------------------------------------------------- +void *CMDLCache::QueryInterface( const char *pInterfaceName ) +{ + if (!Q_strncmp( pInterfaceName, STUDIO_DATA_CACHE_INTERFACE_VERSION, Q_strlen(STUDIO_DATA_CACHE_INTERFACE_VERSION) + 1)) + return (IStudioDataCache*)this; + + if (!Q_strncmp( pInterfaceName, MDLCACHE_INTERFACE_VERSION, Q_strlen(MDLCACHE_INTERFACE_VERSION) + 1)) + return (IMDLCache*)this; + + return NULL; +} + + +//----------------------------------------------------------------------------- +// Init/Shutdown +//----------------------------------------------------------------------------- + +#define MODEL_CACHE_MODEL_SECTION_NAME "ModelData" +#define MODEL_CACHE_MESH_SECTION_NAME "ModelMesh" +#define MODEL_CACHE_ANIMBLOCK_SECTION_NAME "AnimBlock" + +// #define ENABLE_CACHE_WATCH 1 + +#if defined( ENABLE_CACHE_WATCH ) +static ConVar cache_watch( "cache_watch", "", 0 ); + +static void CacheLog( const char *fileName, const char *accessType ) +{ + if ( Q_stristr( fileName, cache_watch.GetString() ) ) + { + Msg( "%s access to %s\n", accessType, fileName ); + } +} +#endif + +InitReturnVal_t CMDLCache::Init() +{ + // Can be called twice since it inherits from 2 appsystems + if ( m_bInitialized ) + return INIT_OK; + + InitReturnVal_t nRetVal = BaseClass::Init(); + if ( nRetVal != INIT_OK ) + return nRetVal; + + if ( !m_pModelCacheSection ) + { + m_pModelCacheSection = g_pDataCache->AddSection( this, MODEL_CACHE_MODEL_SECTION_NAME ); + } + + if ( !m_pMeshCacheSection ) + { + unsigned int meshLimit = (unsigned)-1; + DataCacheLimits_t limits( meshLimit, (unsigned)-1, 0, 0 ); + m_pMeshCacheSection = g_pDataCache->AddSection( this, MODEL_CACHE_MESH_SECTION_NAME, limits ); + } + + if ( !m_pAnimBlockCacheSection ) + { + // 360 tuned to worst case, ep_outland_12a, less than 6 MB is not a viable working set + unsigned int animBlockLimit = IsX360() ? 6*1024*1024 : (unsigned)-1; + DataCacheLimits_t limits( animBlockLimit, (unsigned)-1, 0, 0 ); + m_pAnimBlockCacheSection = g_pDataCache->AddSection( this, MODEL_CACHE_ANIMBLOCK_SECTION_NAME, limits ); + } + + if ( IsX360() ) + { + // By default, source data is assumed to be non-native to the 360. + StudioByteSwap::ActivateByteSwapping( true ); + StudioByteSwap::SetCollisionInterface( g_pPhysicsCollision ); + } + m_bLostVideoMemory = false; + m_bInitialized = true; + +#if defined( ENABLE_CACHE_WATCH ) + g_pFullFileSystem->AddLoggingFunc( &CacheLog ); +#endif + + return INIT_OK; +} + +void CMDLCache::Shutdown() +{ + if ( !m_bInitialized ) + return; +#if defined( ENABLE_CACHE_WATCH ) + g_pFullFileSystem->RemoveLoggingFunc( CacheLog ); +#endif + m_bInitialized = false; + + if ( m_pModelCacheSection || m_pMeshCacheSection ) + { + // Free all MDLs that haven't been cleaned up + MDLHandle_t i = m_MDLDict.First(); + while ( i != m_MDLDict.InvalidIndex() ) + { + ShutdownStudioData( i ); + i = m_MDLDict.Next( i ); + } + + m_MDLDict.Purge(); + + if ( m_pModelCacheSection ) + { + g_pDataCache->RemoveSection( MODEL_CACHE_MODEL_SECTION_NAME ); + m_pModelCacheSection = NULL; + } + if ( m_pMeshCacheSection ) + { + g_pDataCache->RemoveSection( MODEL_CACHE_MESH_SECTION_NAME ); + m_pMeshCacheSection = NULL; + } + } + + if ( m_pAnimBlockCacheSection ) + { + g_pDataCache->RemoveSection( MODEL_CACHE_ANIMBLOCK_SECTION_NAME ); + m_pAnimBlockCacheSection = NULL; + } + + BaseClass::Shutdown(); +} + + +//----------------------------------------------------------------------------- +// Flushes an MDLHandle_t +//----------------------------------------------------------------------------- +void CMDLCache::Flush( MDLHandle_t handle, int nFlushFlags ) +{ + studiodata_t *pStudioData = m_MDLDict[handle]; + Assert( pStudioData != NULL ); + + bool bIgnoreLock = ( nFlushFlags & MDLCACHE_FLUSH_IGNORELOCK ) != 0; + + // release the hardware portion + if ( nFlushFlags & MDLCACHE_FLUSH_STUDIOHWDATA ) + { + if ( ClearAsync( handle, MDLCACHE_STUDIOHWDATA, 0, true ) ) + { + m_pMeshCacheSection->Unlock( pStudioData->m_VertexCache ); + } + UnloadHardwareData( handle, true, bIgnoreLock ); + } + + // free collision + if ( nFlushFlags & MDLCACHE_FLUSH_VCOLLIDE ) + { + DestroyVCollide( handle ); + } + + // Free animations + if ( nFlushFlags & MDLCACHE_FLUSH_VIRTUALMODEL ) + { + FreeVirtualModel( handle ); + } + + if ( nFlushFlags & MDLCACHE_FLUSH_ANIMBLOCK ) + { + FreeAnimBlocks( handle ); + } + + if ( nFlushFlags & MDLCACHE_FLUSH_AUTOPLAY ) + { + // Free autoplay sequences + FreeAutoplaySequences( pStudioData ); + } + + if ( nFlushFlags & MDLCACHE_FLUSH_STUDIOHDR ) + { + MdlCacheMsg( "MDLCache: Free studiohdr %s\n", GetModelName( handle ) ); + + if ( pStudioData->m_nFlags & STUDIODATA_FLAGS_LOCKED_MDL ) + { + GetCacheSection( MDLCACHE_STUDIOHDR )->Unlock( pStudioData->m_MDLCache ); + pStudioData->m_nFlags &= ~STUDIODATA_FLAGS_LOCKED_MDL; + } + UncacheData( pStudioData->m_MDLCache, MDLCACHE_STUDIOHDR, bIgnoreLock ); + pStudioData->m_MDLCache = NULL; + } + + if ( nFlushFlags & MDLCACHE_FLUSH_VERTEXES ) + { + MdlCacheMsg( "MDLCache: Free VVD %s\n", GetModelName( handle ) ); + + ClearAsync( handle, MDLCACHE_VERTEXES, 0, true ); + + UncacheData( pStudioData->m_VertexCache, MDLCACHE_VERTEXES, bIgnoreLock ); + pStudioData->m_VertexCache = NULL; + } + + // Now check whatever files are not loaded, make sure file system knows + // that we don't have them loaded. + if ( !IsDataLoaded( handle, MDLCACHE_STUDIOHDR ) ) + NotifyFileUnloaded( handle, ".mdl" ); + + if ( !IsDataLoaded( handle, MDLCACHE_STUDIOHWDATA ) ) + NotifyFileUnloaded( handle, GetVTXExtension() ); + + if ( !IsDataLoaded( handle, MDLCACHE_VERTEXES ) ) + NotifyFileUnloaded( handle, ".vvd" ); + + if ( !IsDataLoaded( handle, MDLCACHE_VCOLLIDE ) ) + NotifyFileUnloaded( handle, ".phy" ); +} + + +//----------------------------------------------------------------------------- +// Inits, shuts downs studiodata_t +//----------------------------------------------------------------------------- +void CMDLCache::InitStudioData( MDLHandle_t handle ) +{ + Assert( m_MDLDict[handle] == NULL ); + + studiodata_t *pStudioData = new studiodata_t; + m_MDLDict[handle] = pStudioData; + memset( pStudioData, 0, sizeof( studiodata_t ) ); +} + +void CMDLCache::ShutdownStudioData( MDLHandle_t handle ) +{ + Flush( handle ); + + studiodata_t *pStudioData = m_MDLDict[handle]; + Assert( pStudioData != NULL ); + delete pStudioData; + m_MDLDict[handle] = NULL; +} + + +//----------------------------------------------------------------------------- +// Sets the cache notify +//----------------------------------------------------------------------------- +void CMDLCache::SetCacheNotify( IMDLCacheNotify *pNotify ) +{ + m_pCacheNotify = pNotify; +} + + +//----------------------------------------------------------------------------- +// Returns the name of the model +//----------------------------------------------------------------------------- +const char *CMDLCache::GetModelName( MDLHandle_t handle ) +{ + if ( handle == MDLHANDLE_INVALID ) + return ERROR_MODEL; + + return m_MDLDict.GetElementName( handle ); +} + + +//----------------------------------------------------------------------------- +// Returns the *actual* name of the model (could be an error model) +//----------------------------------------------------------------------------- +const char *CMDLCache::GetActualModelName( MDLHandle_t handle ) +{ + if ( handle == MDLHANDLE_INVALID ) + return ERROR_MODEL; + + if ( m_MDLDict[handle]->m_nFlags & STUDIODATA_ERROR_MODEL ) + return ERROR_MODEL; + + return m_MDLDict.GetElementName( handle ); +} + + +//----------------------------------------------------------------------------- +// Constructs a filename based on a model handle +//----------------------------------------------------------------------------- +void CMDLCache::MakeFilename( MDLHandle_t handle, const char *pszExtension, char *pszFileName, int nMaxLength ) +{ + Q_strncpy( pszFileName, GetActualModelName( handle ), nMaxLength ); + Q_SetExtension( pszFileName, pszExtension, nMaxLength ); + Q_FixSlashes( pszFileName ); +#ifdef _LINUX + Q_strlower( pszFileName ); +#endif +} + +//----------------------------------------------------------------------------- +void CMDLCache::NotifyFileUnloaded( MDLHandle_t handle, const char *pszExtension ) +{ + if ( handle == MDLHANDLE_INVALID ) + return; + if ( !m_MDLDict.IsValidIndex( handle ) ) + return; + + char szFilename[MAX_PATH]; + V_strcpy_safe( szFilename, m_MDLDict.GetElementName( handle ) ); + V_SetExtension( szFilename, pszExtension, sizeof(szFilename) ); + V_FixSlashes( szFilename ); + g_pFullFileSystem->NotifyFileUnloaded( szFilename, "game" ); +} + + +//----------------------------------------------------------------------------- +// Finds an MDL +//----------------------------------------------------------------------------- +MDLHandle_t CMDLCache::FindMDL( const char *pMDLRelativePath ) +{ + // can't trust provided path + // ensure provided path correctly resolves (Dictionary is case-insensitive) + char szFixedName[MAX_PATH]; + V_strncpy( szFixedName, pMDLRelativePath, sizeof( szFixedName ) ); + V_RemoveDotSlashes( szFixedName, '/' ); + + MDLHandle_t handle = m_MDLDict.Find( szFixedName ); + if ( handle == m_MDLDict.InvalidIndex() ) + { + handle = m_MDLDict.Insert( szFixedName, NULL ); + InitStudioData( handle ); + } + + AddRef( handle ); + return handle; +} + +//----------------------------------------------------------------------------- +// Reference counting +//----------------------------------------------------------------------------- +int CMDLCache::AddRef( MDLHandle_t handle ) +{ + return ++m_MDLDict[handle]->m_nRefCount; +} + +int CMDLCache::Release( MDLHandle_t handle ) +{ + // Deal with shutdown order issues (i.e. datamodel shutting down after mdlcache) + if ( !m_bInitialized ) + return 0; + + // NOTE: It can be null during shutdown because multiple studiomdls + // could be referencing the same virtual model + if ( !m_MDLDict[handle] ) + return 0; + + Assert( m_MDLDict[handle]->m_nRefCount > 0 ); + + int nRefCount = --m_MDLDict[handle]->m_nRefCount; + if ( nRefCount <= 0 ) + { + ShutdownStudioData( handle ); + m_MDLDict.RemoveAt( handle ); + } + + return nRefCount; +} + +int CMDLCache::GetRef( MDLHandle_t handle ) +{ + if ( !m_bInitialized ) + return 0; + + if ( !m_MDLDict[handle] ) + return 0; + + return m_MDLDict[handle]->m_nRefCount; +} + +//----------------------------------------------------------------------------- +// Unserializes the PHY file associated w/ models (the vphysics representation) +//----------------------------------------------------------------------------- +void CMDLCache::UnserializeVCollide( MDLHandle_t handle, bool synchronousLoad ) +{ + VPROF( "CMDLCache::UnserializeVCollide" ); + + // FIXME: Should the vcollde be played into cacheable memory? + studiodata_t *pStudioData = m_MDLDict[handle]; + + int iAsync = GetAsyncInfoIndex( handle, MDLCACHE_VCOLLIDE ); + + if ( iAsync == NO_ASYNC ) + { + // clear existing data + pStudioData->m_nFlags &= ~STUDIODATA_FLAGS_VCOLLISION_LOADED; + memset( &pStudioData->m_VCollisionData, 0, sizeof( pStudioData->m_VCollisionData ) ); + +#if 0 + // FIXME: ywb + // If we don't ask for the virtual model to load, then we can get a hitch later on after startup + // Should we async load the sub .mdls during startup assuming they'll all be resident by the time the level can actually + // start drawing? + if ( pStudioData->m_pVirtualModel || synchronousLoad ) +#endif + { + virtualmodel_t *pVirtualModel = GetVirtualModel( handle ); + if ( pVirtualModel ) + { + for ( int i = 1; i < pVirtualModel->m_group.Count(); i++ ) + { + MDLHandle_t sharedHandle = (MDLHandle_t) (int)pVirtualModel->m_group[i].cache & 0xffff; + studiodata_t *pData = m_MDLDict[sharedHandle]; + if ( !(pData->m_nFlags & STUDIODATA_FLAGS_VCOLLISION_LOADED) ) + { + UnserializeVCollide( sharedHandle, synchronousLoad ); + } + if ( pData->m_VCollisionData.solidCount > 0 ) + { + pStudioData->m_VCollisionData = pData->m_VCollisionData; + pStudioData->m_nFlags |= STUDIODATA_FLAGS_VCOLLISION_SHARED; + return; + } + } + } + } + + char pFileName[MAX_PATH]; + MakeFilename( handle, ".phy", pFileName, sizeof(pFileName) ); + if ( IsX360() ) + { + char pX360Filename[MAX_PATH]; + UpdateOrCreate( NULL, pFileName, pX360Filename, sizeof( pX360Filename ), "GAME" ); + Q_strncpy( pFileName, pX360Filename, sizeof(pX360Filename) ); + } + + bool bAsyncLoad = mod_load_vcollide_async.GetBool() && !synchronousLoad; + + MdlCacheMsg( "MDLCache: %s load vcollide %s\n", bAsyncLoad ? "Async" : "Sync", GetModelName( handle ) ); + + AsyncInfo_t info; + if ( IsDebug() ) + { + memset( &info, 0xdd, sizeof( AsyncInfo_t ) ); + } + info.hModel = handle; + info.type = MDLCACHE_VCOLLIDE; + info.iAnimBlock = 0; + info.hControl = NULL; + LoadData( pFileName, "GAME", bAsyncLoad, &info.hControl ); + { + AUTO_LOCK( m_AsyncMutex ); + iAsync = SetAsyncInfoIndex( handle, MDLCACHE_VCOLLIDE, m_PendingAsyncs.AddToTail( info ) ); + } + } + else if ( synchronousLoad ) + { + AsyncInfo_t *pInfo; + { + AUTO_LOCK( m_AsyncMutex ); + pInfo = &m_PendingAsyncs[iAsync]; + } + if ( pInfo->hControl ) + { + g_pFullFileSystem->AsyncFinish( pInfo->hControl, true ); + } + } + + ProcessPendingAsync( iAsync ); +} + + +//----------------------------------------------------------------------------- +// Free model's collision data +//----------------------------------------------------------------------------- +void CMDLCache::DestroyVCollide( MDLHandle_t handle ) +{ + studiodata_t *pStudioData = m_MDLDict[handle]; + + if ( pStudioData->m_nFlags & STUDIODATA_FLAGS_VCOLLISION_SHARED ) + return; + + if ( pStudioData->m_nFlags & STUDIODATA_FLAGS_VCOLLISION_LOADED ) + { + pStudioData->m_nFlags &= ~STUDIODATA_FLAGS_VCOLLISION_LOADED; + if ( pStudioData->m_VCollisionData.solidCount ) + { + if ( m_pCacheNotify ) + { + m_pCacheNotify->OnDataUnloaded( MDLCACHE_VCOLLIDE, handle ); + } + + MdlCacheMsg("MDLCache: Unload vcollide %s\n", GetModelName( handle ) ); + + g_pPhysicsCollision->VCollideUnload( &pStudioData->m_VCollisionData ); + } + } +} + + +//----------------------------------------------------------------------------- +// Unserializes the PHY file associated w/ models (the vphysics representation) +//----------------------------------------------------------------------------- +vcollide_t *CMDLCache::GetVCollideEx( MDLHandle_t handle, bool synchronousLoad /*= true*/ ) +{ + if ( mod_test_not_available.GetBool() ) + return NULL; + + if ( handle == MDLHANDLE_INVALID ) + return NULL; + + studiodata_t *pStudioData = m_MDLDict[handle]; + + if ( ( pStudioData->m_nFlags & STUDIODATA_FLAGS_VCOLLISION_LOADED ) == 0 ) + { + UnserializeVCollide( handle, synchronousLoad ); + } + + // We've loaded an empty collision file or no file was found, so return NULL + if ( !pStudioData->m_VCollisionData.solidCount ) + return NULL; + + return &pStudioData->m_VCollisionData; +} + + +bool CMDLCache::GetVCollideSize( MDLHandle_t handle, int *pVCollideSize ) +{ + *pVCollideSize = 0; + + studiodata_t *pStudioData = m_MDLDict[handle]; + if ( ( pStudioData->m_nFlags & STUDIODATA_FLAGS_VCOLLISION_LOADED ) == 0 ) + return false; + + vcollide_t *pCollide = &pStudioData->m_VCollisionData; + for ( int j = 0; j < pCollide->solidCount; j++ ) + { + *pVCollideSize += g_pPhysicsCollision->CollideSize( pCollide->solids[j] ); + } + *pVCollideSize += pCollide->descSize; + return true; +} + +//----------------------------------------------------------------------------- +// Allocates/frees the anim blocks +//----------------------------------------------------------------------------- +void CMDLCache::AllocateAnimBlocks( studiodata_t *pStudioData, int nCount ) +{ + Assert( pStudioData->m_pAnimBlock == NULL ); + + pStudioData->m_nAnimBlockCount = nCount; + pStudioData->m_pAnimBlock = new DataCacheHandle_t[pStudioData->m_nAnimBlockCount]; + + memset( pStudioData->m_pAnimBlock, 0, sizeof(DataCacheHandle_t) * pStudioData->m_nAnimBlockCount ); + + pStudioData->m_iFakeAnimBlockStall = new unsigned long [pStudioData->m_nAnimBlockCount]; + memset( pStudioData->m_iFakeAnimBlockStall, 0, sizeof( unsigned long ) * pStudioData->m_nAnimBlockCount ); +} + +void CMDLCache::FreeAnimBlocks( MDLHandle_t handle ) +{ + studiodata_t *pStudioData = m_MDLDict[handle]; + + if ( pStudioData->m_pAnimBlock ) + { + for (int i = 0; i < pStudioData->m_nAnimBlockCount; ++i ) + { + MdlCacheMsg( "MDLCache: Free Anim block: %d\n", i ); + + ClearAsync( handle, MDLCACHE_ANIMBLOCK, i, true ); + if ( pStudioData->m_pAnimBlock[i] ) + { + UncacheData( pStudioData->m_pAnimBlock[i], MDLCACHE_ANIMBLOCK, true ); + } + } + + delete[] pStudioData->m_pAnimBlock; + pStudioData->m_pAnimBlock = NULL; + + delete[] pStudioData->m_iFakeAnimBlockStall; + pStudioData->m_iFakeAnimBlockStall = NULL; + } + + pStudioData->m_nAnimBlockCount = 0; +} + + +//----------------------------------------------------------------------------- +// Unserializes an animation block from disk +//----------------------------------------------------------------------------- +unsigned char *CMDLCache::UnserializeAnimBlock( MDLHandle_t handle, int nBlock ) +{ + VPROF( "CMDLCache::UnserializeAnimBlock" ); + + if ( IsX360() && g_pQueuedLoader->IsMapLoading() ) + { + // anim block i/o is not allowed at this stage + return NULL; + } + + // Block 0 is never used!!! + Assert( nBlock > 0 ); + + studiodata_t *pStudioData = m_MDLDict[handle]; + + int iAsync = GetAsyncInfoIndex( handle, MDLCACHE_ANIMBLOCK, nBlock ); + + if ( iAsync == NO_ASYNC ) + { + studiohdr_t *pStudioHdr = GetStudioHdr( handle ); + + // FIXME: For consistency, the block name maybe shouldn't have 'model' in it. + char const *pModelName = pStudioHdr->pszAnimBlockName(); + mstudioanimblock_t *pBlock = pStudioHdr->pAnimBlock( nBlock ); + int nSize = pBlock->dataend - pBlock->datastart; + if ( nSize == 0 ) + return NULL; + + // allocate space in the cache + pStudioData->m_pAnimBlock[nBlock] = NULL; + + char pFileName[MAX_PATH]; + Q_strncpy( pFileName, pModelName, sizeof(pFileName) ); + Q_FixSlashes( pFileName ); +#ifdef _LINUX + Q_strlower( pFileName ); +#endif + if ( IsX360() ) + { + char pX360Filename[MAX_PATH]; + UpdateOrCreate( pStudioHdr, pFileName, pX360Filename, sizeof( pX360Filename ), "GAME" ); + Q_strncpy( pFileName, pX360Filename, sizeof(pX360Filename) ); + } + + MdlCacheMsg( "MDLCache: Begin load Anim Block %s (block %i)\n", GetModelName( handle ), nBlock ); + + AsyncInfo_t info; + if ( IsDebug() ) + { + memset( &info, 0xdd, sizeof( AsyncInfo_t ) ); + } + info.hModel = handle; + info.type = MDLCACHE_ANIMBLOCK; + info.iAnimBlock = nBlock; + info.hControl = NULL; + LoadData( pFileName, "GAME", NULL, nSize, pBlock->datastart, mod_load_anims_async.GetBool(), &info.hControl ); + { + AUTO_LOCK( m_AsyncMutex ); + iAsync = SetAsyncInfoIndex( handle, MDLCACHE_ANIMBLOCK, nBlock, m_PendingAsyncs.AddToTail( info ) ); + } + } + + ProcessPendingAsync( iAsync ); + + return ( unsigned char * )CheckData( pStudioData->m_pAnimBlock[nBlock], MDLCACHE_ANIMBLOCK ); +} + +//----------------------------------------------------------------------------- +// Gets at an animation block associated with an MDL +//----------------------------------------------------------------------------- +unsigned char *CMDLCache::GetAnimBlock( MDLHandle_t handle, int nBlock ) +{ + if ( mod_test_not_available.GetBool() ) + return NULL; + + if ( handle == MDLHANDLE_INVALID ) + return NULL; + + // Allocate animation blocks if we don't have them yet + studiodata_t *pStudioData = m_MDLDict[handle]; + if ( pStudioData->m_pAnimBlock == NULL ) + { + studiohdr_t *pStudioHdr = GetStudioHdr( handle ); + AllocateAnimBlocks( pStudioData, pStudioHdr->numanimblocks ); + } + + // check for request being in range + if ( nBlock < 0 || nBlock >= pStudioData->m_nAnimBlockCount) + return NULL; + + // Check the cache to see if the animation is in memory + unsigned char *pData = ( unsigned char * )CheckData( pStudioData->m_pAnimBlock[nBlock], MDLCACHE_ANIMBLOCK ); + if ( !pData ) + { + pStudioData->m_pAnimBlock[nBlock] = NULL; + + // It's not in memory, read it off of disk + pData = UnserializeAnimBlock( handle, nBlock ); + } + + if (mod_load_fakestall.GetInt()) + { + unsigned int t = Plat_MSTime(); + if (pStudioData->m_iFakeAnimBlockStall[nBlock] == 0 || pStudioData->m_iFakeAnimBlockStall[nBlock] > t) + { + pStudioData->m_iFakeAnimBlockStall[nBlock] = t; + } + + if ((int)(t - pStudioData->m_iFakeAnimBlockStall[nBlock]) < mod_load_fakestall.GetInt()) + { + return NULL; + } + } + return pData; +} + + +//----------------------------------------------------------------------------- +// Allocates/frees autoplay sequence list +//----------------------------------------------------------------------------- +void CMDLCache::AllocateAutoplaySequences( studiodata_t *pStudioData, int nCount ) +{ + FreeAutoplaySequences( pStudioData ); + + pStudioData->m_nAutoplaySequenceCount = nCount; + pStudioData->m_pAutoplaySequenceList = new unsigned short[nCount]; +} + +void CMDLCache::FreeAutoplaySequences( studiodata_t *pStudioData ) +{ + if ( pStudioData->m_pAutoplaySequenceList ) + { + delete[] pStudioData->m_pAutoplaySequenceList; + pStudioData->m_pAutoplaySequenceList = NULL; + } + + pStudioData->m_nAutoplaySequenceCount = 0; +} + + +//----------------------------------------------------------------------------- +// Gets the autoplay list +//----------------------------------------------------------------------------- +int CMDLCache::GetAutoplayList( MDLHandle_t handle, unsigned short **pAutoplayList ) +{ + if ( pAutoplayList ) + { + *pAutoplayList = NULL; + } + + if ( handle == MDLHANDLE_INVALID ) + return 0; + + virtualmodel_t *pVirtualModel = GetVirtualModel( handle ); + if ( pVirtualModel ) + { + if ( pAutoplayList && pVirtualModel->m_autoplaySequences.Count() ) + { + *pAutoplayList = pVirtualModel->m_autoplaySequences.Base(); + } + return pVirtualModel->m_autoplaySequences.Count(); + } + + // FIXME: Should we cache autoplay info here on demand instead of in unserializeMDL? + studiodata_t *pStudioData = m_MDLDict[handle]; + if ( pAutoplayList ) + { + *pAutoplayList = pStudioData->m_pAutoplaySequenceList; + } + + return pStudioData->m_nAutoplaySequenceCount; +} + + +//----------------------------------------------------------------------------- +// Allocates/frees the virtual model +//----------------------------------------------------------------------------- +void CMDLCache::AllocateVirtualModel( MDLHandle_t handle ) +{ + studiodata_t *pStudioData = m_MDLDict[handle]; + Assert( pStudioData->m_pVirtualModel == NULL ); + pStudioData->m_pVirtualModel = new virtualmodel_t; + + // FIXME: The old code slammed these; could have leaked memory? + Assert( pStudioData->m_nAnimBlockCount == 0 ); + Assert( pStudioData->m_pAnimBlock == NULL ); +} + +void CMDLCache::FreeVirtualModel( MDLHandle_t handle ) +{ + studiodata_t *pStudioData = m_MDLDict[handle]; + if ( pStudioData && pStudioData->m_pVirtualModel ) + { + int nGroupCount = pStudioData->m_pVirtualModel->m_group.Count(); + Assert( (nGroupCount >= 1) && pStudioData->m_pVirtualModel->m_group[0].cache == (void*)(uintp)handle ); + + // NOTE: Start at *1* here because the 0th element contains a reference to *this* handle + for ( int i = 1; i < nGroupCount; ++i ) + { + MDLHandle_t h = (MDLHandle_t)(int)pStudioData->m_pVirtualModel->m_group[i].cache&0xffff; + FreeVirtualModel( h ); + Release( h ); + } + + delete pStudioData->m_pVirtualModel; + pStudioData->m_pVirtualModel = NULL; + } +} + + +//----------------------------------------------------------------------------- +// Returns the virtual model +//----------------------------------------------------------------------------- +virtualmodel_t *CMDLCache::GetVirtualModel( MDLHandle_t handle ) +{ + if ( mod_test_not_available.GetBool() ) + return NULL; + + if ( handle == MDLHANDLE_INVALID ) + return NULL; + + studiohdr_t *pStudioHdr = GetStudioHdr( handle ); + + if ( pStudioHdr == NULL ) + return NULL; + + return GetVirtualModelFast( pStudioHdr, handle ); +} + +virtualmodel_t *CMDLCache::GetVirtualModelFast( const studiohdr_t *pStudioHdr, MDLHandle_t handle ) +{ + if (pStudioHdr->numincludemodels == 0) + return NULL; + + studiodata_t *pStudioData = m_MDLDict[handle]; + if ( !pStudioData ) + return NULL; + + if ( !pStudioData->m_pVirtualModel ) + { + DevMsg( 2, "Loading virtual model for %s\n", pStudioHdr->pszName() ); + + CMDLCacheCriticalSection criticalSection( this ); + + AllocateVirtualModel( handle ); + + // Group has to be zero to ensure refcounting is correct + int nGroup = pStudioData->m_pVirtualModel->m_group.AddToTail( ); + Assert( nGroup == 0 ); + pStudioData->m_pVirtualModel->m_group[nGroup].cache = (void *)(uintp)handle; + + // Add all dependent data + pStudioData->m_pVirtualModel->AppendModels( 0, pStudioHdr ); + } + + return pStudioData->m_pVirtualModel; +} + +//----------------------------------------------------------------------------- +// Purpose: Pulls all submodels/.ani file models into the cache +// to avoid runtime hitches and load animations at load time, set mod_forcedata to be 1 +//----------------------------------------------------------------------------- +void CMDLCache::UnserializeAllVirtualModelsAndAnimBlocks( MDLHandle_t handle ) +{ + if ( handle == MDLHANDLE_INVALID ) + return; + + // might be re-loading, discard old virtualmodel to force rebuild + // unfortunately, the virtualmodel does build data into the cacheable studiohdr + FreeVirtualModel( handle ); + + if ( IsX360() && g_pQueuedLoader->IsMapLoading() ) + { + // queued loading has to do it + return; + } + + // don't load the submodel data + if ( !mod_forcedata.GetBool() ) + return; + + // if not present, will instance and load the submodels + GetVirtualModel( handle ); + + if ( IsX360() ) + { + // 360 does not drive the anims into its small cache section + return; + } + + // Note that the animblocks start at 1!!! + studiohdr_t *pStudioHdr = GetStudioHdr( handle ); + for ( int i = 1 ; i < (int)pStudioHdr->numanimblocks; ++i ) + { + GetAnimBlock( handle, i ); + } + + ProcessPendingAsyncs( MDLCACHE_ANIMBLOCK ); +} + + +//----------------------------------------------------------------------------- +// Loads the static meshes +//----------------------------------------------------------------------------- +bool CMDLCache::LoadHardwareData( MDLHandle_t handle ) +{ + Assert( handle != MDLHANDLE_INVALID ); + + // Don't try to load VTX files if we don't have focus... + if ( m_bLostVideoMemory ) + return false; + + studiodata_t *pStudioData = m_MDLDict[handle]; + + CMDLCacheCriticalSection criticalSection( this ); + + // Load up the model + studiohdr_t *pStudioHdr = GetStudioHdr( handle ); + if ( !pStudioHdr || !pStudioHdr->numbodyparts ) + { + pStudioData->m_nFlags |= STUDIODATA_FLAGS_NO_STUDIOMESH; + return true; + } + + if ( pStudioData->m_nFlags & STUDIODATA_FLAGS_NO_STUDIOMESH ) + { + return false; + } + + if ( LogMdlCache() && + GetAsyncInfoIndex( handle, MDLCACHE_STUDIOHWDATA ) == NO_ASYNC && + GetAsyncInfoIndex( handle, MDLCACHE_VERTEXES ) == NO_ASYNC ) + { + MdlCacheMsg( "MDLCache: Begin load studiomdl %s\n", GetModelName( handle ) ); + } + + // Vertex data is required to call LoadModel(), so make sure that's ready + if ( !GetVertexData( handle ) ) + { + if ( pStudioData->m_nFlags & STUDIODATA_FLAGS_NO_VERTEX_DATA ) + { + pStudioData->m_nFlags |= STUDIODATA_FLAGS_NO_STUDIOMESH; + } + return false; + } + + int iAsync = GetAsyncInfoIndex( handle, MDLCACHE_STUDIOHWDATA ); + + if ( iAsync == NO_ASYNC ) + { + m_pMeshCacheSection->Lock( pStudioData->m_VertexCache ); + + // load and persist the vtx file + // use model name for correct path + char pFileName[MAX_PATH]; + MakeFilename( handle, GetVTXExtension(), pFileName, sizeof(pFileName) ); + if ( IsX360() ) + { + char pX360Filename[MAX_PATH]; + UpdateOrCreate( pStudioHdr, pFileName, pX360Filename, sizeof( pX360Filename ), "GAME" ); + Q_strncpy( pFileName, pX360Filename, sizeof(pX360Filename) ); + } + + MdlCacheMsg("MDLCache: Begin load VTX %s\n", GetModelName( handle ) ); + + AsyncInfo_t info; + if ( IsDebug() ) + { + memset( &info, 0xdd, sizeof( AsyncInfo_t ) ); + } + info.hModel = handle; + info.type = MDLCACHE_STUDIOHWDATA; + info.iAnimBlock = 0; + info.hControl = NULL; + LoadData( pFileName, "GAME", mod_load_mesh_async.GetBool(), &info.hControl ); + { + AUTO_LOCK( m_AsyncMutex ); + iAsync = SetAsyncInfoIndex( handle, MDLCACHE_STUDIOHWDATA, m_PendingAsyncs.AddToTail( info ) ); + } + } + + if ( ProcessPendingAsync( iAsync ) > 0 ) + { + if ( pStudioData->m_nFlags & STUDIODATA_FLAGS_NO_STUDIOMESH ) + { + return false; + } + + return ( pStudioData->m_HardwareData.m_NumStudioMeshes != 0 ); + } + + return false; +} + +void CMDLCache::ConvertFlexData( studiohdr_t *pStudioHdr ) +{ + float flVertAnimFixedPointScale = pStudioHdr->VertAnimFixedPointScale(); + + for ( int i = 0; i < pStudioHdr->numbodyparts; i++ ) + { + mstudiobodyparts_t *pBody = pStudioHdr->pBodypart( i ); + for ( int j = 0; j < pBody->nummodels; j++ ) + { + mstudiomodel_t *pModel = pBody->pModel( j ); + for ( int k = 0; k < pModel->nummeshes; k++ ) + { + mstudiomesh_t *pMesh = pModel->pMesh( k ); + for ( int l = 0; l < pMesh->numflexes; l++ ) + { + mstudioflex_t *pFlex = pMesh->pFlex( l ); + bool bIsWrinkleAnim = ( pFlex->vertanimtype == STUDIO_VERT_ANIM_WRINKLE ); + for ( int m = 0; m < pFlex->numverts; m++ ) + { + mstudiovertanim_t *pVAnim = bIsWrinkleAnim ? + pFlex->pVertanimWrinkle( m ) : pFlex->pVertanim( m ); + pVAnim->ConvertToFixed( flVertAnimFixedPointScale ); + } + } + } + } + } +} + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +bool CMDLCache::BuildHardwareData( MDLHandle_t handle, studiodata_t *pStudioData, studiohdr_t *pStudioHdr, OptimizedModel::FileHeader_t *pVtxHdr ) +{ + if ( pVtxHdr ) + { + MdlCacheMsg("MDLCache: Alloc VTX %s\n", pStudioHdr->pszName() ); + + // check header + if ( pVtxHdr->version != OPTIMIZED_MODEL_FILE_VERSION ) + { + Warning( "Error Index File for '%s' version %d should be %d\n", pStudioHdr->pszName(), pVtxHdr->version, OPTIMIZED_MODEL_FILE_VERSION ); + pVtxHdr = NULL; + } + else if ( pVtxHdr->checkSum != pStudioHdr->checksum ) + { + Warning( "Error Index File for '%s' checksum %d should be %d\n", pStudioHdr->pszName(), pVtxHdr->checkSum, pStudioHdr->checksum ); + pVtxHdr = NULL; + } + } + + if ( !pVtxHdr ) + { + pStudioData->m_nFlags |= STUDIODATA_FLAGS_NO_STUDIOMESH; + return false; + } + + CTempAllocHelper pOriginalData; + if ( IsX360() ) + { + unsigned char *pInputData = (unsigned char *)pVtxHdr + sizeof( OptimizedModel::FileHeader_t ); + if ( CLZMA::IsCompressed( pInputData ) ) + { + // vtx arrives compressed, decode and cache the results + unsigned int nOriginalSize = CLZMA::GetActualSize( pInputData ); + pOriginalData.Alloc( sizeof( OptimizedModel::FileHeader_t ) + nOriginalSize ); + V_memcpy( pOriginalData.Get(), pVtxHdr, sizeof( OptimizedModel::FileHeader_t ) ); + unsigned int nOutputSize = CLZMA::Uncompress( pInputData, sizeof( OptimizedModel::FileHeader_t ) + (unsigned char *)pOriginalData.Get() ); + if ( nOutputSize != nOriginalSize ) + { + // decoder failure + return false; + } + + pVtxHdr = (OptimizedModel::FileHeader_t *)pOriginalData.Get(); + } + } + + MdlCacheMsg( "MDLCache: Load studiomdl %s\n", pStudioHdr->pszName() ); + + Assert( GetVertexData( handle ) ); + + BeginLock(); + bool bLoaded = g_pStudioRender->LoadModel( pStudioHdr, pVtxHdr, &pStudioData->m_HardwareData ); + EndLock(); + + if ( bLoaded ) + { + pStudioData->m_nFlags |= STUDIODATA_FLAGS_STUDIOMESH_LOADED; + } + else + { + pStudioData->m_nFlags |= STUDIODATA_FLAGS_NO_STUDIOMESH; + } + + if ( m_pCacheNotify ) + { + m_pCacheNotify->OnDataLoaded( MDLCACHE_STUDIOHWDATA, handle ); + } + +#if defined( USE_HARDWARE_CACHE ) + GetCacheSection( MDLCACHE_STUDIOHWDATA )->Add( MakeCacheID( handle, MDLCACHE_STUDIOHWDATA ), &pStudioData->m_HardwareData, ComputeHardwareDataSize( &pStudioData->m_HardwareData ), &pStudioData->m_HardwareDataCache ); +#endif + return true; +} + + +//----------------------------------------------------------------------------- +// Loads the static meshes +//----------------------------------------------------------------------------- +void CMDLCache::UnloadHardwareData( MDLHandle_t handle, bool bCacheRemove, bool bLockedOk ) +{ + if ( handle == MDLHANDLE_INVALID ) + return; + + // Don't load it if it's loaded + studiodata_t *pStudioData = m_MDLDict[handle]; + if ( pStudioData->m_nFlags & STUDIODATA_FLAGS_STUDIOMESH_LOADED ) + { +#if defined( USE_HARDWARE_CACHE ) + if ( bCacheRemove ) + { + if ( GetCacheSection( MDLCACHE_STUDIOHWDATA )->BreakLock( pStudioData->m_HardwareDataCache ) && !bLockedOk ) + { + DevMsg( "Warning: freed a locked resource\n" ); + Assert( 0 ); + } + + GetCacheSection( MDLCACHE_STUDIOHWDATA )->Remove( pStudioData->m_HardwareDataCache ); + } +#endif + + if ( m_pCacheNotify ) + { + m_pCacheNotify->OnDataUnloaded( MDLCACHE_STUDIOHWDATA, handle ); + } + + MdlCacheMsg("MDLCache: Unload studiomdl %s\n", GetModelName( handle ) ); + + g_pStudioRender->UnloadModel( &pStudioData->m_HardwareData ); + memset( &pStudioData->m_HardwareData, 0, sizeof( pStudioData->m_HardwareData ) ); + pStudioData->m_nFlags &= ~STUDIODATA_FLAGS_STUDIOMESH_LOADED; + + NotifyFileUnloaded( handle, ".mdl" ); + + } +} + + +//----------------------------------------------------------------------------- +// Returns the hardware data associated with an MDL +//----------------------------------------------------------------------------- +studiohwdata_t *CMDLCache::GetHardwareData( MDLHandle_t handle ) +{ + if ( mod_test_not_available.GetBool() ) + return NULL; + + if ( mod_test_mesh_not_available.GetBool() ) + return NULL; + + studiodata_t *pStudioData = m_MDLDict[handle]; + m_pMeshCacheSection->LockMutex(); + if ( ( pStudioData->m_nFlags & (STUDIODATA_FLAGS_STUDIOMESH_LOADED | STUDIODATA_FLAGS_NO_STUDIOMESH) ) == 0 ) + { + m_pMeshCacheSection->UnlockMutex(); + if ( !LoadHardwareData( handle ) ) + { + return NULL; + } + } + else + { +#if defined( USE_HARDWARE_CACHE ) + CheckData( pStudioData->m_HardwareDataCache, MDLCACHE_STUDIOHWDATA ); +#endif + m_pMeshCacheSection->UnlockMutex(); + } + + // didn't load, don't return an empty pointer + if ( pStudioData->m_nFlags & STUDIODATA_FLAGS_NO_STUDIOMESH ) + return NULL; + + return &pStudioData->m_HardwareData; +} + + +//----------------------------------------------------------------------------- +// Task switch +//----------------------------------------------------------------------------- +void CMDLCache::ReleaseMaterialSystemObjects() +{ + Assert( !m_bLostVideoMemory ); + m_bLostVideoMemory = true; + + BreakFrameLock( false ); + + // Free all hardware data + MDLHandle_t i = m_MDLDict.First(); + while ( i != m_MDLDict.InvalidIndex() ) + { + UnloadHardwareData( i ); + i = m_MDLDict.Next( i ); + } + + RestoreFrameLock(); +} + +void CMDLCache::RestoreMaterialSystemObjects( int nChangeFlags ) +{ + Assert( m_bLostVideoMemory ); + m_bLostVideoMemory = false; + + BreakFrameLock( false ); + + // Restore all hardware data + MDLHandle_t i = m_MDLDict.First(); + while ( i != m_MDLDict.InvalidIndex() ) + { + studiodata_t *pStudioData = m_MDLDict[i]; + + bool bIsMDLInMemory = GetCacheSection( MDLCACHE_STUDIOHDR )->IsPresent( pStudioData->m_MDLCache ); + + // If the vertex format changed, we have to free the data because we may be using different .vtx files. + if ( nChangeFlags & MATERIAL_RESTORE_VERTEX_FORMAT_CHANGED ) + { + MdlCacheMsg( "MDLCache: Free studiohdr\n" ); + MdlCacheMsg( "MDLCache: Free VVD\n" ); + MdlCacheMsg( "MDLCache: Free VTX\n" ); + + // FIXME: Do we have to free m_MDLCache + m_VertexCache? + // Certainly we have to free m_IndexCache, cause that's a dx-level specific vtx file. + ClearAsync( i, MDLCACHE_STUDIOHWDATA, 0, true ); + + Flush( i, MDLCACHE_FLUSH_VERTEXES ); + } + + // Only restore the hardware data of those studiohdrs which are currently in memory + if ( bIsMDLInMemory ) + { + GetHardwareData( i ); + } + + i = m_MDLDict.Next( i ); + } + + RestoreFrameLock(); +} + + +void CMDLCache::MarkAsLoaded(MDLHandle_t handle) +{ + if ( mod_lock_mdls_on_load.GetBool() ) + { + g_MDLCache.GetStudioHdr(handle); + if ( !( m_MDLDict[handle]->m_nFlags & STUDIODATA_FLAGS_LOCKED_MDL ) ) + { + m_MDLDict[handle]->m_nFlags |= STUDIODATA_FLAGS_LOCKED_MDL; + GetCacheSection( MDLCACHE_STUDIOHDR )->Lock( m_MDLDict[handle]->m_MDLCache ); + } + } +} + + +//----------------------------------------------------------------------------- +// Callback for UpdateOrCreate utility function - swaps any studiomdl file type. +//----------------------------------------------------------------------------- +static bool MdlcacheCreateCallback( const char *pSourceName, const char *pTargetName, const char *pPathID, void *pHdr ) +{ + // Missing studio files are permissible and not spewed as errors + bool retval = false; + CUtlBuffer sourceBuf; + bool bOk = g_pFullFileSystem->ReadFile( pSourceName, NULL, sourceBuf ); + if ( bOk ) + { + CUtlBuffer targetBuf; + targetBuf.EnsureCapacity( sourceBuf.TellPut() + BYTESWAP_ALIGNMENT_PADDING ); + + int bytes = StudioByteSwap::ByteswapStudioFile( pTargetName, targetBuf.Base(), sourceBuf.Base(), sourceBuf.TellPut(), (studiohdr_t*)pHdr ); + if ( bytes ) + { + // If the file was an .mdl, attempt to swap the .ani as well + if ( Q_stristr( pSourceName, ".mdl" ) ) + { + char szANISourceName[ MAX_PATH ]; + Q_StripExtension( pSourceName, szANISourceName, sizeof( szANISourceName ) ); + Q_strncat( szANISourceName, ".ani", sizeof( szANISourceName ), COPY_ALL_CHARACTERS ); + UpdateOrCreate( szANISourceName, NULL, 0, pPathID, MdlcacheCreateCallback, true, targetBuf.Base() ); + } + + targetBuf.SeekPut( CUtlBuffer::SEEK_HEAD, bytes ); + g_pFullFileSystem->WriteFile( pTargetName, pPathID, targetBuf ); + retval = true; + } + else + { + Warning( "Failed to create %s\n", pTargetName ); + } + } + return retval; +} + +//----------------------------------------------------------------------------- +// Calls utility function to create .360 version of a file. +//----------------------------------------------------------------------------- +int CMDLCache::UpdateOrCreate( studiohdr_t *pHdr, const char *pSourceName, char *pTargetName, int targetLen, const char *pPathID, bool bForce ) +{ + return ::UpdateOrCreate( pSourceName, pTargetName, targetLen, pPathID, MdlcacheCreateCallback, bForce, pHdr ); +} + +//----------------------------------------------------------------------------- +// Purpose: Attempts to read a file native to the current platform +//----------------------------------------------------------------------------- +bool CMDLCache::ReadFileNative( char *pFileName, const char *pPath, CUtlBuffer &buf, int nMaxBytes ) +{ + bool bOk = false; + + if ( IsX360() ) + { + // Read the 360 version + char pX360Filename[ MAX_PATH ]; + UpdateOrCreate( NULL, pFileName, pX360Filename, sizeof( pX360Filename ), pPath ); + bOk = g_pFullFileSystem->ReadFile( pX360Filename, pPath, buf, nMaxBytes ); + } + else + { + // Read the PC version + bOk = g_pFullFileSystem->ReadFile( pFileName, pPath, buf, nMaxBytes ); + } + + return bOk; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +studiohdr_t *CMDLCache::UnserializeMDL( MDLHandle_t handle, void *pData, int nDataSize, bool bDataValid ) +{ + if ( !bDataValid || nDataSize <= 0 || pData == NULL) + { + return NULL; + } + + CTempAllocHelper pOriginalData; + if ( IsX360() ) + { + if ( CLZMA::IsCompressed( (unsigned char *)pData ) ) + { + // mdl arrives compressed, decode and cache the results + unsigned int nOriginalSize = CLZMA::GetActualSize( (unsigned char *)pData ); + pOriginalData.Alloc( nOriginalSize ); + unsigned int nOutputSize = CLZMA::Uncompress( (unsigned char *)pData, (unsigned char *)pOriginalData.Get() ); + if ( nOutputSize != nOriginalSize ) + { + // decoder failure + return NULL; + } + + pData = pOriginalData.Get(); + nDataSize = nOriginalSize; + } + } + + studiohdr_t *pStudioHdrIn = (studiohdr_t *)pData; + + if ( r_rootlod.GetInt() > 0 ) + { + // raw data is already setup for lod 0, override otherwise + Studio_SetRootLOD( pStudioHdrIn, r_rootlod.GetInt() ); + } + + // critical! store a back link to our data + // this is fetched when re-establishing dependent cached data (vtx/vvd) + pStudioHdrIn->virtualModel = (void *)(uintp)handle; + + MdlCacheMsg( "MDLCache: Alloc studiohdr %s\n", GetModelName( handle ) ); + + // allocate cache space + MemAlloc_PushAllocDbgInfo( "Models:StudioHdr", 0); + studiohdr_t *pHdr = (studiohdr_t *)AllocData( MDLCACHE_STUDIOHDR, pStudioHdrIn->length ); + MemAlloc_PopAllocDbgInfo(); + if ( !pHdr ) + return NULL; + + CacheData( &m_MDLDict[handle]->m_MDLCache, pHdr, pStudioHdrIn->length, GetModelName( handle ), MDLCACHE_STUDIOHDR, MakeCacheID( handle, MDLCACHE_STUDIOHDR) ); + + if ( mod_lock_mdls_on_load.GetBool() ) + { + GetCacheSection( MDLCACHE_STUDIOHDR )->Lock( m_MDLDict[handle]->m_MDLCache ); + m_MDLDict[handle]->m_nFlags |= STUDIODATA_FLAGS_LOCKED_MDL; + } + + // FIXME: Is there any way we can compute the size to load *before* loading in + // and read directly into cache memory? It would be nice to reduce cache overhead here. + // move the complete, relocatable model to the cache + memcpy( pHdr, pStudioHdrIn, pStudioHdrIn->length ); + + // On first load, convert the flex deltas from fp16 to 16-bit fixed-point + if ( (pHdr->flags & STUDIOHDR_FLAGS_FLEXES_CONVERTED) == 0 ) + { + ConvertFlexData( pHdr ); + + // Mark as converted so it only happens once + pHdr->flags |= STUDIOHDR_FLAGS_FLEXES_CONVERTED; + } + + if ( m_pCacheNotify ) + { + m_pCacheNotify->OnDataLoaded( MDLCACHE_STUDIOHDR, handle ); + } + + return pHdr; +} + + +//----------------------------------------------------------------------------- +// Attempts to load a MDL file, validates that it's ok. +//----------------------------------------------------------------------------- +bool CMDLCache::ReadMDLFile( MDLHandle_t handle, const char *pMDLFileName, CUtlBuffer &buf ) +{ + VPROF( "CMDLCache::ReadMDLFile" ); + + char pFileName[ MAX_PATH ]; + Q_strncpy( pFileName, pMDLFileName, sizeof( pFileName ) ); + Q_FixSlashes( pFileName ); +#ifdef _LINUX + Q_strlower( pFileName ); +#endif + + MdlCacheMsg( "MDLCache: Load studiohdr %s\n", pFileName ); + + MEM_ALLOC_CREDIT(); + + bool bOk = ReadFileNative( pFileName, "GAME", buf ); + if ( !bOk ) + { + DevWarning( "Failed to load %s!\n", pMDLFileName ); + return false; + } + + if ( IsX360() ) + { + if ( CLZMA::IsCompressed( (unsigned char *)buf.PeekGet() ) ) + { + // mdl arrives compressed, decode and cache the results + unsigned int nOriginalSize = CLZMA::GetActualSize( (unsigned char *)buf.PeekGet() ); + void *pOriginalData = malloc( nOriginalSize ); + unsigned int nOutputSize = CLZMA::Uncompress( (unsigned char *)buf.PeekGet(), (unsigned char *)pOriginalData ); + if ( nOutputSize != nOriginalSize ) + { + // decoder failure + free( pOriginalData ); + return false; + } + + // replace caller's buffer + buf.Purge(); + buf.Put( pOriginalData, nOriginalSize ); + free( pOriginalData ); + } + } + + studiohdr_t *pStudioHdr = (studiohdr_t*)buf.PeekGet(); + if ( !pStudioHdr ) + { + DevWarning( "Failed to read model %s from buffer!\n", pMDLFileName ); + return false; + } + if ( pStudioHdr->id != IDSTUDIOHEADER ) + { + DevWarning( "Model %s not a .MDL format file!\n", pMDLFileName ); + return false; + } + + // critical! store a back link to our data + // this is fetched when re-establishing dependent cached data (vtx/vvd) + pStudioHdr->virtualModel = (void*)(uintp)handle; + + // Make sure all dependent files are valid + if ( !VerifyHeaders( pStudioHdr ) ) + { + DevWarning( "Model %s has mismatched .vvd + .vtx files!\n", pMDLFileName ); + return false; + } + + return true; +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +studiohdr_t *CMDLCache::LockStudioHdr( MDLHandle_t handle ) +{ + if ( handle == MDLHANDLE_INVALID ) + { + return NULL; + } + + CMDLCacheCriticalSection cacheCriticalSection( this ); + studiohdr_t *pStdioHdr = GetStudioHdr( handle ); + // @TODO (toml 9/12/2006) need this?: AddRef( handle ); + if ( !pStdioHdr ) + { + return NULL; + } + + GetCacheSection( MDLCACHE_STUDIOHDR )->Lock( m_MDLDict[handle]->m_MDLCache ); + return pStdioHdr; +} + +void CMDLCache::UnlockStudioHdr( MDLHandle_t handle ) +{ + if ( handle == MDLHANDLE_INVALID ) + { + return; + } + + CMDLCacheCriticalSection cacheCriticalSection( this ); + studiohdr_t *pStdioHdr = GetStudioHdr( handle ); + if ( pStdioHdr ) + { + GetCacheSection( MDLCACHE_STUDIOHDR )->Unlock( m_MDLDict[handle]->m_MDLCache ); + } + // @TODO (toml 9/12/2006) need this?: Release( handle ); +} + +//----------------------------------------------------------------------------- +// Loading the data in +//----------------------------------------------------------------------------- +studiohdr_t *CMDLCache::GetStudioHdr( MDLHandle_t handle ) +{ + if ( handle == MDLHANDLE_INVALID ) + return NULL; + + // Returning a pointer to data inside the cache when it's unlocked is just a bad idea. + // It's technically legal, but the pointer can get invalidated if anything else looks at the cache. + // Don't do that. + // Assert( m_pModelCacheSection->IsFrameLocking() ); + // Assert( m_pMeshCacheSection->IsFrameLocking() ); + +#if _DEBUG + VPROF_INCREMENT_COUNTER( "GetStudioHdr", 1 ); +#endif + studiohdr_t *pHdr = (studiohdr_t*)CheckData( m_MDLDict[handle]->m_MDLCache, MDLCACHE_STUDIOHDR ); + if ( !pHdr ) + { + m_MDLDict[handle]->m_MDLCache = NULL; + + CMDLCacheCriticalSection cacheCriticalSection( this ); + + // load the file + const char *pModelName = GetActualModelName( handle ); + if ( developer.GetInt() > 1 ) + { + DevMsg( "Loading %s\n", pModelName ); + } + + // Load file to temporary space + CUtlBuffer buf; + if ( !ReadMDLFile( handle, pModelName, buf ) ) + { + bool bOk = false; + if ( ( m_MDLDict[handle]->m_nFlags & STUDIODATA_ERROR_MODEL ) == 0 ) + { + buf.Clear(); // clear buffer for next file read + + m_MDLDict[handle]->m_nFlags |= STUDIODATA_ERROR_MODEL; + bOk = ReadMDLFile( handle, ERROR_MODEL, buf ); + } + + if ( !bOk ) + { + if (IsOSX()) + { + // rbarris wants this to go somewhere like the console.log prior to crashing, which is what the Error call will do next + printf("\n ##### Model %s not found and %s couldn't be loaded", pModelName, ERROR_MODEL ); + fflush( stdout ); + } + Error( "Model %s not found and %s couldn't be loaded", pModelName, ERROR_MODEL ); + return NULL; + } + } + + // put it in the cache + if ( ProcessDataIntoCache( handle, MDLCACHE_STUDIOHDR, 0, buf.Base(), buf.TellMaxPut(), true ) ) + { + pHdr = (studiohdr_t*)CheckData( m_MDLDict[handle]->m_MDLCache, MDLCACHE_STUDIOHDR ); + } + } + + return pHdr; +} + + +//----------------------------------------------------------------------------- +// Gets/sets user data associated with the MDL +//----------------------------------------------------------------------------- +void CMDLCache::SetUserData( MDLHandle_t handle, void* pData ) +{ + if ( handle == MDLHANDLE_INVALID ) + return; + + m_MDLDict[handle]->m_pUserData = pData; +} + +void *CMDLCache::GetUserData( MDLHandle_t handle ) +{ + if ( handle == MDLHANDLE_INVALID ) + return NULL; + return m_MDLDict[handle]->m_pUserData; +} + + +//----------------------------------------------------------------------------- +// Polls information about a particular mdl +//----------------------------------------------------------------------------- +bool CMDLCache::IsErrorModel( MDLHandle_t handle ) +{ + if ( handle == MDLHANDLE_INVALID ) + return false; + + return (m_MDLDict[handle]->m_nFlags & STUDIODATA_ERROR_MODEL) != 0; +} + + +//----------------------------------------------------------------------------- +// Brings all data associated with an MDL into memory +//----------------------------------------------------------------------------- +void CMDLCache::TouchAllData( MDLHandle_t handle ) +{ + studiohdr_t *pStudioHdr = GetStudioHdr( handle ); + virtualmodel_t *pVModel = GetVirtualModel( handle ); + if ( pVModel ) + { + // skip self, start at children + // ensure all sub models are cached + for ( int i=1; im_group.Count(); ++i ) + { + MDLHandle_t childHandle = (MDLHandle_t)(int)pVModel->m_group[i].cache&0xffff; + if ( childHandle != MDLHANDLE_INVALID ) + { + // FIXME: Should this be calling TouchAllData on the child? + GetStudioHdr( childHandle ); + } + } + } + + if ( !IsX360() ) + { + // cache the anims + // Note that the animblocks start at 1!!! + for ( int i=1; i< (int)pStudioHdr->numanimblocks; ++i ) + { + pStudioHdr->GetAnimBlock( i ); + } + } + + // cache the vertexes + if ( pStudioHdr->numbodyparts ) + { + CacheVertexData( pStudioHdr ); + GetHardwareData( handle ); + } +} + + +//----------------------------------------------------------------------------- +// Flushes all data +//----------------------------------------------------------------------------- +void CMDLCache::Flush( MDLCacheFlush_t nFlushFlags ) +{ + // Free all MDLs that haven't been cleaned up + MDLHandle_t i = m_MDLDict.First(); + while ( i != m_MDLDict.InvalidIndex() ) + { + Flush( i, nFlushFlags ); + i = m_MDLDict.Next( i ); + } +} + +//----------------------------------------------------------------------------- +// Cache handlers +//----------------------------------------------------------------------------- +static const char *g_ppszTypes[] = +{ + "studiohdr", // MDLCACHE_STUDIOHDR + "studiohwdata", // MDLCACHE_STUDIOHWDATA + "vcollide", // MDLCACHE_VCOLLIDE + "animblock", // MDLCACHE_ANIMBLOCK + "virtualmodel", // MDLCACHE_VIRTUALMODEL + "vertexes", // MDLCACHE_VERTEXES +}; + +bool CMDLCache::HandleCacheNotification( const DataCacheNotification_t ¬ification ) +{ + switch ( notification.type ) + { + case DC_AGE_DISCARD: + case DC_FLUSH_DISCARD: + case DC_REMOVED: + { + MdlCacheMsg( "MDLCache: Data cache discard %s %s\n", g_ppszTypes[TypeFromCacheID( notification.clientId )], GetModelName( HandleFromCacheID( notification.clientId ) ) ); + + if ( (DataCacheClientID_t)notification.pItemData == notification.clientId || + TypeFromCacheID(notification.clientId) != MDLCACHE_STUDIOHWDATA ) + { + Assert( notification.pItemData ); + FreeData( TypeFromCacheID(notification.clientId), (void *)notification.pItemData ); + } + else + { + UnloadHardwareData( HandleFromCacheID( notification.clientId ), false ); + } + return true; + } + } + + return CDefaultDataCacheClient::HandleCacheNotification( notification ); +} + +bool CMDLCache::GetItemName( DataCacheClientID_t clientId, const void *pItem, char *pDest, unsigned nMaxLen ) +{ + if ( (DataCacheClientID_t)pItem == clientId ) + { + return false; + } + + MDLHandle_t handle = HandleFromCacheID( clientId ); + MDLCacheDataType_t type = TypeFromCacheID( clientId ); + + Q_snprintf( pDest, nMaxLen, "%s - %s", g_ppszTypes[type], GetModelName( handle ) ); + + return false; +} + +//----------------------------------------------------------------------------- +// Flushes all data +//----------------------------------------------------------------------------- +void CMDLCache::BeginLock() +{ + m_pModelCacheSection->BeginFrameLocking(); + m_pMeshCacheSection->BeginFrameLocking(); +} + +//----------------------------------------------------------------------------- +// Flushes all data +//----------------------------------------------------------------------------- +void CMDLCache::EndLock() +{ + m_pModelCacheSection->EndFrameLocking(); + m_pMeshCacheSection->EndFrameLocking(); +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void CMDLCache::BreakFrameLock( bool bModels, bool bMesh ) +{ + if ( bModels ) + { + if ( m_pModelCacheSection->IsFrameLocking() ) + { + Assert( !m_nModelCacheFrameLocks ); + m_nModelCacheFrameLocks = 0; + do + { + m_nModelCacheFrameLocks++; + } while ( m_pModelCacheSection->EndFrameLocking() ); + } + + } + + if ( bMesh ) + { + if ( m_pMeshCacheSection->IsFrameLocking() ) + { + Assert( !m_nMeshCacheFrameLocks ); + m_nMeshCacheFrameLocks = 0; + do + { + m_nMeshCacheFrameLocks++; + } while ( m_pMeshCacheSection->EndFrameLocking() ); + } + } + +} + +void CMDLCache::RestoreFrameLock() +{ + while ( m_nModelCacheFrameLocks ) + { + m_pModelCacheSection->BeginFrameLocking(); + m_nModelCacheFrameLocks--; + } + while ( m_nMeshCacheFrameLocks ) + { + m_pMeshCacheSection->BeginFrameLocking(); + m_nMeshCacheFrameLocks--; + } +} + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +int *CMDLCache::GetFrameUnlockCounterPtrOLD() +{ + return GetCacheSection( MDLCACHE_STUDIOHDR )->GetFrameUnlockCounterPtr(); +} + +int *CMDLCache::GetFrameUnlockCounterPtr( MDLCacheDataType_t type ) +{ + return GetCacheSection( type )->GetFrameUnlockCounterPtr(); +} + +//----------------------------------------------------------------------------- +// Completes all pending async operations +//----------------------------------------------------------------------------- +void CMDLCache::FinishPendingLoads() +{ + if ( !ThreadInMainThread() ) + { + return; + } + + AUTO_LOCK( m_AsyncMutex ); + + // finish just our known jobs + int iAsync = m_PendingAsyncs.Head(); + while ( iAsync != m_PendingAsyncs.InvalidIndex() ) + { + AsyncInfo_t &info = m_PendingAsyncs[iAsync]; + if ( info.hControl ) + { + g_pFullFileSystem->AsyncFinish( info.hControl, true ); + } + iAsync = m_PendingAsyncs.Next( iAsync ); + } + + ProcessPendingAsyncs(); +} + +//----------------------------------------------------------------------------- +// Notify map load has started +//----------------------------------------------------------------------------- +void CMDLCache::BeginMapLoad() +{ + BreakFrameLock(); + + studiodata_t *pStudioData; + + // Unlock prior map MDLs prior to load + MDLHandle_t i = m_MDLDict.First(); + while ( i != m_MDLDict.InvalidIndex() ) + { + pStudioData = m_MDLDict[i]; + if ( pStudioData->m_nFlags & STUDIODATA_FLAGS_LOCKED_MDL ) + { + GetCacheSection( MDLCACHE_STUDIOHDR )->Unlock( pStudioData->m_MDLCache ); + pStudioData->m_nFlags &= ~STUDIODATA_FLAGS_LOCKED_MDL; + } + i = m_MDLDict.Next( i ); + } +} + +//----------------------------------------------------------------------------- +// Notify map load is complete +//----------------------------------------------------------------------------- +void CMDLCache::EndMapLoad() +{ + FinishPendingLoads(); + + // Remove all stray MDLs not referenced during load + if ( mod_lock_mdls_on_load.GetBool() ) + { + studiodata_t *pStudioData; + MDLHandle_t i = m_MDLDict.First(); + while ( i != m_MDLDict.InvalidIndex() ) + { + pStudioData = m_MDLDict[i]; + if ( !(pStudioData->m_nFlags & STUDIODATA_FLAGS_LOCKED_MDL) ) + { + Flush( i, MDLCACHE_FLUSH_STUDIOHDR ); + } + i = m_MDLDict.Next( i ); + } + } + + RestoreFrameLock(); +} + + +//----------------------------------------------------------------------------- +// Is a particular part of the model data loaded? +//----------------------------------------------------------------------------- +bool CMDLCache::IsDataLoaded( MDLHandle_t handle, MDLCacheDataType_t type ) +{ + if ( handle == MDLHANDLE_INVALID || !m_MDLDict.IsValidIndex( handle ) ) + return false; + + studiodata_t *pData = m_MDLDict[ handle ]; + switch( type ) + { + case MDLCACHE_STUDIOHDR: + return GetCacheSection( MDLCACHE_STUDIOHDR )->IsPresent( pData->m_MDLCache ); + + case MDLCACHE_STUDIOHWDATA: + return ( pData->m_nFlags & STUDIODATA_FLAGS_STUDIOMESH_LOADED ) != 0; + + case MDLCACHE_VCOLLIDE: + return ( pData->m_nFlags & STUDIODATA_FLAGS_VCOLLISION_LOADED ) != 0; + + case MDLCACHE_ANIMBLOCK: + { + if ( !pData->m_pAnimBlock ) + return false; + + for (int i = 0; i < pData->m_nAnimBlockCount; ++i ) + { + if ( !pData->m_pAnimBlock[i] ) + return false; + + if ( !GetCacheSection( type )->IsPresent( pData->m_pAnimBlock[i] ) ) + return false; + } + return true; + } + + case MDLCACHE_VIRTUALMODEL: + return ( pData->m_pVirtualModel != 0 ); + + case MDLCACHE_VERTEXES: + return m_pMeshCacheSection->IsPresent( pData->m_VertexCache ); + } + return false; +} + + +//----------------------------------------------------------------------------- +// Get the correct extension for our dx +//----------------------------------------------------------------------------- +const char *CMDLCache::GetVTXExtension() +{ + if ( IsPC() ) + { + if ( g_pMaterialSystemHardwareConfig->GetDXSupportLevel() >= 90 ) + { + return ".dx90.vtx"; + } + else if ( g_pMaterialSystemHardwareConfig->GetDXSupportLevel() >= 80 ) + { + return ".dx80.vtx"; + } + else + { + return ".sw.vtx"; + } + } + + return ".dx90.vtx"; +} + +//----------------------------------------------------------------------------- +// Minimal presence and header validation, no data loads +// Return true if successful, false otherwise. +//----------------------------------------------------------------------------- +bool CMDLCache::VerifyHeaders( studiohdr_t *pStudioHdr ) +{ + VPROF( "CMDLCache::VerifyHeaders" ); + + if ( developer.GetInt() < 2 ) + { + return true; + } + + // model has no vertex data + if ( !pStudioHdr->numbodyparts ) + { + // valid + return true; + } + + char pFileName[ MAX_PATH ]; + MDLHandle_t handle = (MDLHandle_t)(int)pStudioHdr->virtualModel&0xffff; + + MakeFilename( handle, ".vvd", pFileName, sizeof(pFileName) ); + + MdlCacheMsg("MDLCache: Load VVD (verify) %s\n", pFileName ); + + // vvd header only + CUtlBuffer vvdHeader( 0, sizeof(vertexFileHeader_t) ); + if ( !ReadFileNative( pFileName, "GAME", vvdHeader, sizeof(vertexFileHeader_t) ) ) + { + return false; + } + + vertexFileHeader_t *pVertexHdr = (vertexFileHeader_t*)vvdHeader.PeekGet(); + + // check + if (( pVertexHdr->id != MODEL_VERTEX_FILE_ID ) || + ( pVertexHdr->version != MODEL_VERTEX_FILE_VERSION ) || + ( pVertexHdr->checksum != pStudioHdr->checksum )) + { + return false; + } + + // load the VTX file + // use model name for correct path + MakeFilename( handle, GetVTXExtension(), pFileName, sizeof(pFileName) ); + + MdlCacheMsg("MDLCache: Load VTX (verify) %s\n", pFileName ); + + // vtx header only + CUtlBuffer vtxHeader( 0, sizeof(OptimizedModel::FileHeader_t) ); + if ( !ReadFileNative( pFileName, "GAME", vtxHeader, sizeof(OptimizedModel::FileHeader_t) ) ) + { + return false; + } + + // check + OptimizedModel::FileHeader_t *pVtxHdr = (OptimizedModel::FileHeader_t*)vtxHeader.PeekGet(); + if (( pVtxHdr->version != OPTIMIZED_MODEL_FILE_VERSION ) || + ( pVtxHdr->checkSum != pStudioHdr->checksum )) + { + return false; + } + + // valid + return true; +} + + +//----------------------------------------------------------------------------- +// Cache model's specified dynamic data +//----------------------------------------------------------------------------- +vertexFileHeader_t *CMDLCache::CacheVertexData( studiohdr_t *pStudioHdr ) +{ + VPROF( "CMDLCache::CacheVertexData" ); + + vertexFileHeader_t *pVvdHdr; + MDLHandle_t handle; + + Assert( pStudioHdr ); + + handle = (MDLHandle_t)(int)pStudioHdr->virtualModel&0xffff; + Assert( handle != MDLHANDLE_INVALID ); + + pVvdHdr = (vertexFileHeader_t *)CheckData( m_MDLDict[handle]->m_VertexCache, MDLCACHE_VERTEXES ); + if ( pVvdHdr ) + { + return pVvdHdr; + } + + m_MDLDict[handle]->m_VertexCache = NULL; + + return LoadVertexData( pStudioHdr ); +} + +//----------------------------------------------------------------------------- +// Start an async transfer +//----------------------------------------------------------------------------- +FSAsyncStatus_t CMDLCache::LoadData( const char *pszFilename, const char *pszPathID, void *pDest, int nBytes, int nOffset, bool bAsync, FSAsyncControl_t *pControl ) +{ + if ( !*pControl ) + { + if ( IsX360() && g_pQueuedLoader->IsMapLoading() ) + { + DevWarning( "CMDLCache: Non-Optimal loading path for %s\n", pszFilename ); + } + + FileAsyncRequest_t asyncRequest; + asyncRequest.pszFilename = pszFilename; + asyncRequest.pszPathID = pszPathID; + asyncRequest.pData = pDest; + asyncRequest.nBytes = nBytes; + asyncRequest.nOffset = nOffset; + + if ( !pDest ) + { + asyncRequest.flags = FSASYNC_FLAGS_ALLOCNOFREE; + } + + if ( !bAsync ) + { + asyncRequest.flags |= FSASYNC_FLAGS_SYNC; + } + + MEM_ALLOC_CREDIT(); + return g_pFullFileSystem->AsyncRead( asyncRequest, pControl ); + } + + return FSASYNC_ERR_FAILURE; +} + +//----------------------------------------------------------------------------- +// Determine the maximum number of 'real' bone influences used by any vertex in a model +// (100% binding to bone zero doesn't count) +//----------------------------------------------------------------------------- +int ComputeMaxRealBoneInfluences( vertexFileHeader_t * vertexFile, int lod ) +{ + const mstudiovertex_t * verts = vertexFile->GetVertexData(); + int numVerts = vertexFile->numLODVertexes[ lod ]; + Assert(verts); + + int maxWeights = 0; + for (int i = 0;i < numVerts;i++) + { + if ( verts[i].m_BoneWeights.numbones > 0 ) + { + int numWeights = 0; + for (int j = 0;j < MAX_NUM_BONES_PER_VERT;j++) + { + if ( verts[i].m_BoneWeights.weight[j] > 0 ) + numWeights = j + 1; + } + if ( ( numWeights == 1 ) && ( verts[i].m_BoneWeights.bone[0] == 0 ) ) + { + // 100% binding to first bone - not really skinned (the first bone is just the model transform) + numWeights = 0; + } + maxWeights = max( numWeights, maxWeights ); + } + } + return maxWeights; +} + +//----------------------------------------------------------------------------- +// Generate thin vertices (containing just the data needed to do model decals) +//----------------------------------------------------------------------------- +vertexFileHeader_t * CMDLCache::CreateThinVertexes( vertexFileHeader_t * originalData, const studiohdr_t * pStudioHdr, int * cacheLength ) +{ + int rootLod = min( (int)pStudioHdr->rootLOD, ( originalData->numLODs - 1 ) ); + int numVerts = originalData->numLODVertexes[ rootLod ] + 1; // Add 1 vert to support prefetch during array access + + int numBoneInfluences = ComputeMaxRealBoneInfluences( originalData, rootLod ); + // Only store (N-1) weights (all N weights sum to 1, so we can re-compute the Nth weight later) + int numStoredWeights = max( 0, ( numBoneInfluences - 1 ) ); + + int vertexSize = 2*sizeof( Vector ) + numBoneInfluences*sizeof( unsigned char ) + numStoredWeights*sizeof( float ); + *cacheLength = sizeof( vertexFileHeader_t ) + sizeof( thinModelVertices_t ) + numVerts*vertexSize; + + // Allocate cache space for the thin data + MemAlloc_PushAllocDbgInfo( "Models:Vertex data", 0); + vertexFileHeader_t * pNewVvdHdr = (vertexFileHeader_t *)AllocData( MDLCACHE_VERTEXES, *cacheLength ); + MemAlloc_PopAllocDbgInfo(); + + Assert( pNewVvdHdr ); + if ( pNewVvdHdr ) + { + // Copy the header and set it up to hold thin vertex data + memcpy( (void *)pNewVvdHdr, (void *)originalData, sizeof( vertexFileHeader_t ) ); + pNewVvdHdr->id = MODEL_VERTEX_FILE_THIN_ID; + pNewVvdHdr->numFixups = 0; + pNewVvdHdr->fixupTableStart = 0; + pNewVvdHdr->tangentDataStart = 0; + pNewVvdHdr->vertexDataStart = sizeof( vertexFileHeader_t ); + + // Set up the thin vertex structure + thinModelVertices_t * pNewThinVerts = (thinModelVertices_t *)( pNewVvdHdr + 1 ); + Vector * pPositions = (Vector *)( pNewThinVerts + 1 ); + float * pBoneWeights = (float *)( pPositions + numVerts ); + // Alloc the (short) normals here to avoid mis-aligning the float data + unsigned short * pNormals = (unsigned short *)( pBoneWeights + numVerts*numStoredWeights ); + // Alloc the (char) indices here to avoid mis-aligning the float/short data + char * pBoneIndices = (char *)( pNormals + numVerts ); + if ( numStoredWeights == 0 ) + pBoneWeights = NULL; + if ( numBoneInfluences == 0 ) + pBoneIndices = NULL; + pNewThinVerts->Init( numBoneInfluences, pPositions, pNormals, pBoneWeights, pBoneIndices ); + + // Copy over the original data + const mstudiovertex_t * srcVertexData = originalData->GetVertexData(); + for ( int i = 0; i < numVerts; i++ ) + { + pNewThinVerts->SetPosition( i, srcVertexData[ i ].m_vecPosition ); + pNewThinVerts->SetNormal( i, srcVertexData[ i ].m_vecNormal ); + if ( numBoneInfluences > 0 ) + { + mstudioboneweight_t boneWeights; + boneWeights.numbones = numBoneInfluences; + for ( int j = 0; j < numStoredWeights; j++ ) + { + boneWeights.weight[ j ] = srcVertexData[ i ].m_BoneWeights.weight[ j ]; + } + for ( int j = 0; j < numBoneInfluences; j++ ) + { + boneWeights.bone[ j ] = srcVertexData[ i ].m_BoneWeights.bone[ j ]; + } + pNewThinVerts->SetBoneWeights( i, boneWeights ); + } + } + } + + return pNewVvdHdr; +} + +//----------------------------------------------------------------------------- +// Process the provided raw data into the cache. Distributes to low level +// unserialization or build methods. +//----------------------------------------------------------------------------- +bool CMDLCache::ProcessDataIntoCache( MDLHandle_t handle, MDLCacheDataType_t type, int iAnimBlock, void *pData, int nDataSize, bool bDataValid ) +{ + studiohdr_t *pStudioHdrCurrent = NULL; + if ( type != MDLCACHE_STUDIOHDR ) + { + // can only get the studiohdr once the header has been processed successfully into the cache + // causes a ProcessDataIntoCache() with the studiohdr data + pStudioHdrCurrent = GetStudioHdr( handle ); + if ( !pStudioHdrCurrent ) + { + return false; + } + } + + studiodata_t *pStudioDataCurrent = m_MDLDict[handle]; + + if ( !pStudioDataCurrent ) + { + return false; + } + + switch ( type ) + { + case MDLCACHE_STUDIOHDR: + { + pStudioHdrCurrent = UnserializeMDL( handle, pData, nDataSize, bDataValid ); + if ( !pStudioHdrCurrent ) + { + return false; + } + + if (!Studio_ConvertStudioHdrToNewVersion( pStudioHdrCurrent )) + { + Warning( "MDLCache: %s needs to be recompiled\n", pStudioHdrCurrent->pszName() ); + } + + if ( pStudioHdrCurrent->numincludemodels == 0 ) + { + // perf optimization, calculate once and cache off the autoplay sequences + int nCount = pStudioHdrCurrent->CountAutoplaySequences(); + if ( nCount ) + { + AllocateAutoplaySequences( m_MDLDict[handle], nCount ); + pStudioHdrCurrent->CopyAutoplaySequences( m_MDLDict[handle]->m_pAutoplaySequenceList, nCount ); + } + } + + // Load animations + UnserializeAllVirtualModelsAndAnimBlocks( handle ); + break; + } + + case MDLCACHE_VERTEXES: + { + if ( bDataValid ) + { + BuildAndCacheVertexData( pStudioHdrCurrent, (vertexFileHeader_t *)pData ); + } + else + { + pStudioDataCurrent->m_nFlags |= STUDIODATA_FLAGS_NO_VERTEX_DATA; + if ( pStudioHdrCurrent->numbodyparts ) + { + // expected data not valid + Warning( "MDLCache: Failed load of .VVD data for %s\n", pStudioHdrCurrent->pszName() ); + return false; + } + } + break; + } + + case MDLCACHE_STUDIOHWDATA: + { + if ( bDataValid ) + { + BuildHardwareData( handle, pStudioDataCurrent, pStudioHdrCurrent, (OptimizedModel::FileHeader_t *)pData ); + } + else + { + pStudioDataCurrent->m_nFlags |= STUDIODATA_FLAGS_NO_STUDIOMESH; + if ( pStudioHdrCurrent->numbodyparts ) + { + // expected data not valid + Warning( "MDLCache: Failed load of .VTX data for %s\n", pStudioHdrCurrent->pszName() ); + return false; + } + } + + m_pMeshCacheSection->Unlock( pStudioDataCurrent->m_VertexCache ); + m_pMeshCacheSection->Age( pStudioDataCurrent->m_VertexCache ); + + // FIXME: thin VVD data on PC too (have to address alt-tab, various DX8/DX7/debug software paths in studiorender, tools, etc) + static bool bCompressedVVDs = CommandLine()->CheckParm( "-no_compressed_vvds" ) == NULL; + if ( IsX360() && !( pStudioDataCurrent->m_nFlags & STUDIODATA_FLAGS_NO_STUDIOMESH ) && bCompressedVVDs ) + { + // Replace the cached vertex data with a thin version (used for model decals). + // Flexed meshes require the fat data to remain, for CPU mesh anim. + if ( pStudioHdrCurrent->numflexdesc == 0 ) + { + vertexFileHeader_t *originalVertexData = GetVertexData( handle ); + Assert( originalVertexData ); + if ( originalVertexData ) + { + int thinVertexDataSize = 0; + vertexFileHeader_t *thinVertexData = CreateThinVertexes( originalVertexData, pStudioHdrCurrent, &thinVertexDataSize ); + Assert( thinVertexData && ( thinVertexDataSize > 0 ) ); + if ( thinVertexData && ( thinVertexDataSize > 0 ) ) + { + // Remove the original cache entry (and free it) + Flush( handle, MDLCACHE_FLUSH_VERTEXES | MDLCACHE_FLUSH_IGNORELOCK ); + // Add the new one + CacheData( &pStudioDataCurrent->m_VertexCache, thinVertexData, thinVertexDataSize, pStudioHdrCurrent->pszName(), MDLCACHE_VERTEXES, MakeCacheID( handle, MDLCACHE_VERTEXES) ); + } + } + } + } + + break; + } + + case MDLCACHE_ANIMBLOCK: + { + MEM_ALLOC_CREDIT_( __FILE__ ": Anim Blocks" ); + + if ( bDataValid ) + { + MdlCacheMsg( "MDLCache: Finish load anim block %s (block %i)\n", pStudioHdrCurrent->pszName(), iAnimBlock ); + + char pCacheName[MAX_PATH]; + Q_snprintf( pCacheName, MAX_PATH, "%s (block %i)", pStudioHdrCurrent->pszName(), iAnimBlock ); + + if ( IsX360() ) + { + if ( CLZMA::IsCompressed( (unsigned char *)pData ) ) + { + // anim block arrives compressed, decode and cache the results + unsigned int nOriginalSize = CLZMA::GetActualSize( (unsigned char *)pData ); + + // get a "fake" (not really aligned) optimal read buffer, as expected by the free logic + void *pOriginalData = g_pFullFileSystem->AllocOptimalReadBuffer( FILESYSTEM_INVALID_HANDLE, nOriginalSize, 0 ); + unsigned int nOutputSize = CLZMA::Uncompress( (unsigned char *)pData, (unsigned char *)pOriginalData ); + if ( nOutputSize != nOriginalSize ) + { + // decoder failure + g_pFullFileSystem->FreeOptimalReadBuffer( pOriginalData ); + return false; + } + + // input i/o buffer is now unused + g_pFullFileSystem->FreeOptimalReadBuffer( pData ); + + // datacache will now own the data + pData = pOriginalData; + nDataSize = nOriginalSize; + } + } + + CacheData( &pStudioDataCurrent->m_pAnimBlock[iAnimBlock], pData, nDataSize, pCacheName, MDLCACHE_ANIMBLOCK, MakeCacheID( handle, MDLCACHE_ANIMBLOCK) ); + } + else + { + MdlCacheMsg( "MDLCache: Failed load anim block %s (block %i)\n", pStudioHdrCurrent->pszName(), iAnimBlock ); + if ( pStudioDataCurrent->m_pAnimBlock ) + { + pStudioDataCurrent->m_pAnimBlock[iAnimBlock] = NULL; + } + return false; + } + break; + } + + case MDLCACHE_VCOLLIDE: + { + // always marked as loaded, vcollides are not present for every model + pStudioDataCurrent->m_nFlags |= STUDIODATA_FLAGS_VCOLLISION_LOADED; + + if ( bDataValid ) + { + MdlCacheMsg( "MDLCache: Finish load vcollide for %s\n", pStudioHdrCurrent->pszName() ); + + CTempAllocHelper pOriginalData; + if ( IsX360() ) + { + if ( CLZMA::IsCompressed( (unsigned char *)pData ) ) + { + // phy arrives compressed, decode and cache the results + unsigned int nOriginalSize = CLZMA::GetActualSize( (unsigned char *)pData ); + pOriginalData.Alloc( nOriginalSize ); + unsigned int nOutputSize = CLZMA::Uncompress( (unsigned char *)pData, (unsigned char *)pOriginalData.Get() ); + if ( nOutputSize != nOriginalSize ) + { + // decoder failure + return NULL; + } + + pData = pOriginalData.Get(); + nDataSize = nOriginalSize; + } + } + + CUtlBuffer buf( pData, nDataSize, CUtlBuffer::READ_ONLY ); + buf.SeekPut( CUtlBuffer::SEEK_HEAD, nDataSize ); + + phyheader_t header; + buf.Get( &header, sizeof( phyheader_t ) ); + if ( ( header.size == sizeof( header ) ) && header.solidCount > 0 ) + { + int nBufSize = buf.TellMaxPut() - buf.TellGet(); + vcollide_t *pCollide = &pStudioDataCurrent->m_VCollisionData; + g_pPhysicsCollision->VCollideLoad( pCollide, header.solidCount, (const char*)buf.PeekGet(), nBufSize ); + if ( m_pCacheNotify ) + { + m_pCacheNotify->OnDataLoaded( MDLCACHE_VCOLLIDE, handle ); + } + } + } + else + { + MdlCacheWarning( "MDLCache: Failed load of .PHY data for %s\n", pStudioHdrCurrent->pszName() ); + return false; + } + break; + } + + default: + Assert( 0 ); + } + + // success + return true; +} + +//----------------------------------------------------------------------------- +// Returns: +// <0: indeterminate at this time +// =0: pending +// >0: completed +//----------------------------------------------------------------------------- +int CMDLCache::ProcessPendingAsync( int iAsync ) +{ + if ( !ThreadInMainThread() ) + { + return -1; + } + + ASSERT_NO_REENTRY(); + + void *pData = NULL; + int nBytesRead = 0; + + AsyncInfo_t *pInfo; + { + AUTO_LOCK( m_AsyncMutex ); + pInfo = &m_PendingAsyncs[iAsync]; + } + Assert( pInfo->hControl ); + + FSAsyncStatus_t status = g_pFullFileSystem->AsyncGetResult( pInfo->hControl, &pData, &nBytesRead ); + if ( status == FSASYNC_STATUS_PENDING ) + { + return 0; + } + + AsyncInfo_t info = *pInfo; + pInfo = &info; + ClearAsync( pInfo->hModel, pInfo->type, pInfo->iAnimBlock ); + + switch ( pInfo->type ) + { + case MDLCACHE_VERTEXES: + case MDLCACHE_STUDIOHWDATA: + case MDLCACHE_VCOLLIDE: + { + ProcessDataIntoCache( pInfo->hModel, pInfo->type, 0, pData, nBytesRead, status == FSASYNC_OK ); + g_pFullFileSystem->FreeOptimalReadBuffer( pData ); + break; + } + + case MDLCACHE_ANIMBLOCK: + { + // cache assumes ownership of valid async'd data + if ( !ProcessDataIntoCache( pInfo->hModel, MDLCACHE_ANIMBLOCK, pInfo->iAnimBlock, pData, nBytesRead, status == FSASYNC_OK ) ) + { + g_pFullFileSystem->FreeOptimalReadBuffer( pData ); + } + break; + } + + default: + Assert( 0 ); + } + + return 1; +} + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void CMDLCache::ProcessPendingAsyncs( MDLCacheDataType_t type ) +{ + if ( !ThreadInMainThread() ) + { + return; + } + + if ( !m_PendingAsyncs.Count() ) + { + return; + } + + static bool bReentering; + if ( bReentering ) + { + return; + } + bReentering = true; + + AUTO_LOCK( m_AsyncMutex ); + + // Process all of the completed loads that were requested before a new one. This ensures two + // things -- the LRU is in correct order, and it catches precached items lurking + // in the async queue that have only been requested once (thus aren't being cached + // and might lurk forever, e.g., wood gibs in the citadel) + int current = m_PendingAsyncs.Head(); + while ( current != m_PendingAsyncs.InvalidIndex() ) + { + int next = m_PendingAsyncs.Next( current ); + + if ( type == MDLCACHE_NONE || m_PendingAsyncs[current].type == type ) + { + // process, also removes from list + if ( ProcessPendingAsync( current ) <= 0 ) + { + // indeterminate or pending + break; + } + } + + current = next; + } + + bReentering = false; +} + +//----------------------------------------------------------------------------- +// Cache model's specified dynamic data +//----------------------------------------------------------------------------- +bool CMDLCache::ClearAsync( MDLHandle_t handle, MDLCacheDataType_t type, int iAnimBlock, bool bAbort ) +{ + int iAsyncInfo = GetAsyncInfoIndex( handle, type, iAnimBlock ); + if ( iAsyncInfo != NO_ASYNC ) + { + AsyncInfo_t *pInfo; + { + AUTO_LOCK( m_AsyncMutex ); + pInfo = &m_PendingAsyncs[iAsyncInfo]; + } + if ( pInfo->hControl ) + { + if ( bAbort ) + { + g_pFullFileSystem->AsyncAbort( pInfo->hControl ); + void *pData; + int ignored; + if ( g_pFullFileSystem->AsyncGetResult( pInfo->hControl, &pData, &ignored ) == FSASYNC_OK ) + { + g_pFullFileSystem->FreeOptimalReadBuffer( pData ); + } + } + g_pFullFileSystem->AsyncRelease( pInfo->hControl ); + pInfo->hControl = NULL; + } + + SetAsyncInfoIndex( handle, type, iAnimBlock, NO_ASYNC ); + { + AUTO_LOCK( m_AsyncMutex ); + m_PendingAsyncs.Remove( iAsyncInfo ); + } + + return true; + } + return false; +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +bool CMDLCache::GetAsyncLoad( MDLCacheDataType_t type ) +{ + switch ( type ) + { + case MDLCACHE_STUDIOHDR: + return false; + case MDLCACHE_STUDIOHWDATA: + return mod_load_mesh_async.GetBool(); + case MDLCACHE_VCOLLIDE: + return mod_load_vcollide_async.GetBool(); + case MDLCACHE_ANIMBLOCK: + return mod_load_anims_async.GetBool(); + case MDLCACHE_VIRTUALMODEL: + return false; + case MDLCACHE_VERTEXES: + return mod_load_mesh_async.GetBool(); + } + return false; +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +bool CMDLCache::SetAsyncLoad( MDLCacheDataType_t type, bool bAsync ) +{ + bool bRetVal = false; + switch ( type ) + { + case MDLCACHE_STUDIOHDR: + break; + case MDLCACHE_STUDIOHWDATA: + bRetVal = mod_load_mesh_async.GetBool(); + mod_load_mesh_async.SetValue( bAsync ); + break; + case MDLCACHE_VCOLLIDE: + bRetVal = mod_load_vcollide_async.GetBool(); + mod_load_vcollide_async.SetValue( bAsync ); + break; + case MDLCACHE_ANIMBLOCK: + bRetVal = mod_load_anims_async.GetBool(); + mod_load_anims_async.SetValue( bAsync ); + break; + case MDLCACHE_VIRTUALMODEL: + return false; + break; + case MDLCACHE_VERTEXES: + bRetVal = mod_load_mesh_async.GetBool(); + mod_load_mesh_async.SetValue( bAsync ); + break; + } + return bRetVal; +} + +//----------------------------------------------------------------------------- +// Cache model's specified dynamic data +//----------------------------------------------------------------------------- +vertexFileHeader_t *CMDLCache::BuildAndCacheVertexData( studiohdr_t *pStudioHdr, vertexFileHeader_t *pRawVvdHdr ) +{ + MDLHandle_t handle = (MDLHandle_t)(int)pStudioHdr->virtualModel&0xffff; + vertexFileHeader_t *pVvdHdr; + + MdlCacheMsg( "MDLCache: Load VVD for %s\n", pStudioHdr->pszName() ); + + Assert( pRawVvdHdr ); + + // check header + if ( pRawVvdHdr->id != MODEL_VERTEX_FILE_ID ) + { + Warning( "Error Vertex File for '%s' id %d should be %d\n", pStudioHdr->pszName(), pRawVvdHdr->id, MODEL_VERTEX_FILE_ID ); + return NULL; + } + if ( pRawVvdHdr->version != MODEL_VERTEX_FILE_VERSION ) + { + Warning( "Error Vertex File for '%s' version %d should be %d\n", pStudioHdr->pszName(), pRawVvdHdr->version, MODEL_VERTEX_FILE_VERSION ); + return NULL; + } + if ( pRawVvdHdr->checksum != pStudioHdr->checksum ) + { + Warning( "Error Vertex File for '%s' checksum %d should be %d\n", pStudioHdr->pszName(), pRawVvdHdr->checksum, pStudioHdr->checksum ); + return NULL; + } + + Assert( pRawVvdHdr->numLODs ); + if ( !pRawVvdHdr->numLODs ) + { + return NULL; + } + + CTempAllocHelper pOriginalData; + if ( IsX360() ) + { + unsigned char *pInput = (unsigned char *)pRawVvdHdr + sizeof( vertexFileHeader_t ); + if ( CLZMA::IsCompressed( pInput ) ) + { + // vvd arrives compressed, decode and cache the results + unsigned int nOriginalSize = CLZMA::GetActualSize( pInput ); + pOriginalData.Alloc( sizeof( vertexFileHeader_t ) + nOriginalSize ); + V_memcpy( pOriginalData.Get(), pRawVvdHdr, sizeof( vertexFileHeader_t ) ); + unsigned int nOutputSize = CLZMA::Uncompress( pInput, sizeof( vertexFileHeader_t ) + (unsigned char *)pOriginalData.Get() ); + if ( nOutputSize != nOriginalSize ) + { + // decoder failure + return NULL; + } + + pRawVvdHdr = (vertexFileHeader_t *)pOriginalData.Get(); + } + } + + bool bNeedsTangentS = IsX360() || (g_pMaterialSystemHardwareConfig->GetDXSupportLevel() >= 80); + int rootLOD = min( (int)pStudioHdr->rootLOD, pRawVvdHdr->numLODs - 1 ); + + // determine final cache footprint, possibly truncated due to lod + int cacheLength = Studio_VertexDataSize( pRawVvdHdr, rootLOD, bNeedsTangentS ); + + MdlCacheMsg("MDLCache: Alloc VVD %s\n", GetModelName( handle ) ); + + // allocate cache space + MemAlloc_PushAllocDbgInfo( "Models:Vertex data", 0); + pVvdHdr = (vertexFileHeader_t *)AllocData( MDLCACHE_VERTEXES, cacheLength ); + MemAlloc_PopAllocDbgInfo(); + + GetCacheSection( MDLCACHE_VERTEXES )->BeginFrameLocking(); + + CacheData( &m_MDLDict[handle]->m_VertexCache, pVvdHdr, cacheLength, pStudioHdr->pszName(), MDLCACHE_VERTEXES, MakeCacheID( handle, MDLCACHE_VERTEXES) ); + + // expected 32 byte alignment + Assert( ((int64)pVvdHdr & 0x1F) == 0 ); + + // load minimum vertexes and fixup + Studio_LoadVertexes( pRawVvdHdr, pVvdHdr, rootLOD, bNeedsTangentS ); + + GetCacheSection( MDLCACHE_VERTEXES )->EndFrameLocking(); + + return pVvdHdr; +} + +//----------------------------------------------------------------------------- +// Load and cache model's specified dynamic data +//----------------------------------------------------------------------------- +vertexFileHeader_t *CMDLCache::LoadVertexData( studiohdr_t *pStudioHdr ) +{ + char pFileName[MAX_PATH]; + MDLHandle_t handle; + + Assert( pStudioHdr ); + handle = (MDLHandle_t)(int)pStudioHdr->virtualModel&0xffff; + Assert( !m_MDLDict[handle]->m_VertexCache ); + + studiodata_t *pStudioData = m_MDLDict[handle]; + + if ( pStudioData->m_nFlags & STUDIODATA_FLAGS_NO_VERTEX_DATA ) + { + return NULL; + } + + int iAsync = GetAsyncInfoIndex( handle, MDLCACHE_VERTEXES ); + + if ( iAsync == NO_ASYNC ) + { + // load the VVD file + // use model name for correct path + MakeFilename( handle, ".vvd", pFileName, sizeof(pFileName) ); + if ( IsX360() ) + { + char pX360Filename[MAX_PATH]; + UpdateOrCreate( pStudioHdr, pFileName, pX360Filename, sizeof( pX360Filename ), "GAME" ); + Q_strncpy( pFileName, pX360Filename, sizeof(pX360Filename) ); + } + + MdlCacheMsg( "MDLCache: Begin load VVD %s\n", pFileName ); + + AsyncInfo_t info; + if ( IsDebug() ) + { + memset( &info, 0xdd, sizeof( AsyncInfo_t ) ); + } + info.hModel = handle; + info.type = MDLCACHE_VERTEXES; + info.iAnimBlock = 0; + info.hControl = NULL; + LoadData( pFileName, "GAME", mod_load_mesh_async.GetBool(), &info.hControl ); + { + AUTO_LOCK( m_AsyncMutex ); + iAsync = SetAsyncInfoIndex( handle, MDLCACHE_VERTEXES, m_PendingAsyncs.AddToTail( info ) ); + } + } + + ProcessPendingAsync( iAsync ); + + return (vertexFileHeader_t *)CheckData( m_MDLDict[handle]->m_VertexCache, MDLCACHE_VERTEXES ); +} + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +vertexFileHeader_t *CMDLCache::GetVertexData( MDLHandle_t handle ) +{ + if ( mod_test_not_available.GetBool() ) + return NULL; + + if ( mod_test_verts_not_available.GetBool() ) + return NULL; + + return CacheVertexData( GetStudioHdr( handle ) ); +} + + +//----------------------------------------------------------------------------- +// Allocates a cacheable item +//----------------------------------------------------------------------------- +void *CMDLCache::AllocData( MDLCacheDataType_t type, int size ) +{ + void *pData = _aligned_malloc( size, 32 ); + + if ( !pData ) + { + Error( "CMDLCache:: Out of memory" ); + return NULL; + } + + return pData; +} + + +//----------------------------------------------------------------------------- +// Caches an item +//----------------------------------------------------------------------------- +void CMDLCache::CacheData( DataCacheHandle_t *c, void *pData, int size, const char *name, MDLCacheDataType_t type, DataCacheClientID_t id ) +{ + if ( !pData ) + { + return; + } + + if ( id == (DataCacheClientID_t)-1 ) + id = (DataCacheClientID_t)pData; + + GetCacheSection( type )->Add(id, pData, size, c ); +} + +//----------------------------------------------------------------------------- +// returns the cached data, and moves to the head of the LRU list +// if present, otherwise returns NULL +//----------------------------------------------------------------------------- +void *CMDLCache::CheckData( DataCacheHandle_t c, MDLCacheDataType_t type ) +{ + return GetCacheSection( type )->Get( c, true ); +} + +//----------------------------------------------------------------------------- +// returns the cached data, if present, otherwise returns NULL +//----------------------------------------------------------------------------- +void *CMDLCache::CheckDataNoTouch( DataCacheHandle_t c, MDLCacheDataType_t type ) +{ + return GetCacheSection( type )->GetNoTouch( c, true ); +} + +//----------------------------------------------------------------------------- +// Frees a cache item +//----------------------------------------------------------------------------- +void CMDLCache::UncacheData( DataCacheHandle_t c, MDLCacheDataType_t type, bool bLockedOk ) +{ + if ( c == DC_INVALID_HANDLE ) + return; + + if ( !GetCacheSection( type )->IsPresent( c ) ) + return; + + if ( GetCacheSection( type )->BreakLock( c ) && !bLockedOk ) + { + DevMsg( "Warning: freed a locked resource\n" ); + Assert( 0 ); + } + + const void *pItemData; + GetCacheSection( type )->Remove( c, &pItemData ); + + FreeData( type, (void *)pItemData ); +} + + +//----------------------------------------------------------------------------- +// Frees memory for an item +//----------------------------------------------------------------------------- +void CMDLCache::FreeData( MDLCacheDataType_t type, void *pData ) +{ + if ( type != MDLCACHE_ANIMBLOCK ) + { + _aligned_free( (void *)pData ); + } + else + { + g_pFullFileSystem->FreeOptimalReadBuffer( pData ); + } +} + + +void CMDLCache::InitPreloadData( bool rebuild ) +{ +} + +void CMDLCache::ShutdownPreloadData() +{ +} + +//----------------------------------------------------------------------------- +// Work function for processing a model delivered by the queued loader. +// ProcessDataIntoCache() is invoked for each MDL datum. +//----------------------------------------------------------------------------- +void CMDLCache::ProcessQueuedData( ModelParts_t *pModelParts, bool bHeaderOnly ) +{ + void *pData; + int nSize; + + // the studiohdr is critical, ensure it's setup as expected + MDLHandle_t handle = pModelParts->hMDL; + studiohdr_t *pStudioHdr = NULL; + if ( !pModelParts->bHeaderLoaded && ( pModelParts->nLoadedParts & ( 1 << ModelParts_t::BUFFER_MDL ) ) ) + { + DEBUG_SCOPE_TIMER(mdl); + pData = pModelParts->Buffers[ModelParts_t::BUFFER_MDL].Base(); + nSize = pModelParts->Buffers[ModelParts_t::BUFFER_MDL].TellMaxPut(); + ProcessDataIntoCache( handle, MDLCACHE_STUDIOHDR, 0, pData, nSize, nSize != 0 ); + LockStudioHdr( handle ); + g_pFullFileSystem->FreeOptimalReadBuffer( pData ); + pModelParts->bHeaderLoaded = true; + } + + if ( bHeaderOnly ) + { + return; + } + + bool bAbort = false; + pStudioHdr = (studiohdr_t *)CheckDataNoTouch( m_MDLDict[handle]->m_MDLCache, MDLCACHE_STUDIOHDR ); + if ( !pStudioHdr ) + { + // The header is expected to be loaded and locked, everything depends on it! + // but if the async read fails, we might not have it + //Assert( 0 ); + DevWarning( "CMDLCache:: Error MDLCACHE_STUDIOHDR not present for '%s'\n", GetModelName( handle ) ); + + // cannot unravel any of this model's dependant data, abort any further processing + bAbort = true; + } + + if ( pModelParts->nLoadedParts & ( 1 << ModelParts_t::BUFFER_PHY ) ) + { + DEBUG_SCOPE_TIMER(phy); + // regardless of error, call job callback so caller can do cleanup of their context + pData = pModelParts->Buffers[ModelParts_t::BUFFER_PHY].Base(); + nSize = bAbort ? 0 : pModelParts->Buffers[ModelParts_t::BUFFER_PHY].TellMaxPut(); + ProcessDataIntoCache( handle, MDLCACHE_VCOLLIDE, 0, pData, nSize, nSize != 0 ); + g_pFullFileSystem->FreeOptimalReadBuffer( pData ); + } + + // vvd vertexes before vtx + if ( pModelParts->nLoadedParts & ( 1 << ModelParts_t::BUFFER_VVD ) ) + { + DEBUG_SCOPE_TIMER(vvd); + pData = pModelParts->Buffers[ModelParts_t::BUFFER_VVD].Base(); + nSize = bAbort ? 0 : pModelParts->Buffers[ModelParts_t::BUFFER_VVD].TellMaxPut(); + ProcessDataIntoCache( handle, MDLCACHE_VERTEXES, 0, pData, nSize, nSize != 0 ); + g_pFullFileSystem->FreeOptimalReadBuffer( pData ); + } + + // can construct meshes after vvd and vtx vertexes arrive + if ( pModelParts->nLoadedParts & ( 1 << ModelParts_t::BUFFER_VTX ) ) + { + DEBUG_SCOPE_TIMER(vtx); + pData = pModelParts->Buffers[ModelParts_t::BUFFER_VTX].Base(); + nSize = bAbort ? 0 : pModelParts->Buffers[ModelParts_t::BUFFER_VTX].TellMaxPut(); + + // ProcessDataIntoCache() will do an unlock, so lock + studiodata_t *pStudioData = m_MDLDict[handle]; + GetCacheSection( MDLCACHE_STUDIOHWDATA )->Lock( pStudioData->m_VertexCache ); + { + // constructing the static meshes isn't thread safe + AUTO_LOCK( m_QueuedLoadingMutex ); + ProcessDataIntoCache( handle, MDLCACHE_STUDIOHWDATA, 0, pData, nSize, nSize != 0 ); + } + g_pFullFileSystem->FreeOptimalReadBuffer( pData ); + } + + UnlockStudioHdr( handle ); + delete pModelParts; +} + +//----------------------------------------------------------------------------- +// Journals each of the incoming MDL components until all arrive (or error). +// Not all components exist, but that information is not known at job submission. +//----------------------------------------------------------------------------- +void CMDLCache::QueuedLoaderCallback_MDL( void *pContext, void *pContext2, const void *pData, int nSize, LoaderError_t loaderError ) +{ + // validity is denoted by a nonzero buffer + nSize = ( loaderError == LOADERERROR_NONE ) ? nSize : 0; + + // journal each incoming buffer + ModelParts_t *pModelParts = (ModelParts_t *)pContext; + ModelParts_t::BufferType_t bufferType = static_cast< ModelParts_t::BufferType_t >((int)pContext2); + pModelParts->Buffers[bufferType].SetExternalBuffer( (void *)pData, nSize, nSize, CUtlBuffer::READ_ONLY ); + pModelParts->nLoadedParts += (1 << bufferType); + + // wait for all components + if ( pModelParts->DoFinalProcessing() ) + { + if ( !IsPC() ) + { + // now have all components, process the raw data into the cache + g_MDLCache.ProcessQueuedData( pModelParts ); + } + else + { + // PC background load path. pull in material dependencies on the fly. + Assert( ThreadInMainThread() ); + + g_MDLCache.ProcessQueuedData( pModelParts, true ); + + // preload all possible paths to VMTs + { + DEBUG_SCOPE_TIMER(findvmt); + MaterialLock_t hMatLock = materials->Lock(); + if ( studiohdr_t * pHdr = g_MDLCache.GetStudioHdr( pModelParts->hMDL ) ) + { + if ( !(pHdr->flags & STUDIOHDR_FLAGS_OBSOLETE) ) + { + char buf[MAX_PATH]; + V_strcpy( buf, "materials/" ); + int prefixLen = V_strlen( buf ); + + for ( int t = 0; t < pHdr->numtextures; ++t ) + { + // XXX this does not take remaps from vtxdata into account; + // right now i am not caring about that. we will hitch if any + // LODs remap to materials that are not in the header. (henryg) + const char *pTexture = pHdr->pTexture(t)->pszName(); + pTexture += ( pTexture[0] == CORRECT_PATH_SEPARATOR || pTexture[0] == INCORRECT_PATH_SEPARATOR ); + for ( int cd = 0; cd < pHdr->numcdtextures; ++cd ) + { + const char *pCdTexture = pHdr->pCdtexture( cd ); + pCdTexture += ( pCdTexture[0] == CORRECT_PATH_SEPARATOR || pCdTexture[0] == INCORRECT_PATH_SEPARATOR ); + V_ComposeFileName( pCdTexture, pTexture, buf + prefixLen, MAX_PATH - prefixLen ); + V_strncat( buf, ".vmt", MAX_PATH, COPY_ALL_CHARACTERS ); + pModelParts->bMaterialsPending = true; + const char *pbuf = buf; + g_pFullFileSystem->AddFilesToFileCache( pModelParts->hFileCache, &pbuf, 1, "GAME" ); + if ( materials->IsMaterialLoaded( buf + prefixLen ) ) + { + // found a loaded one. still cache it in case it unloads, + // but we can stop adding more potential paths to the cache + // since this one is known to be valid. + break; + } + } + } + } + } + materials->Unlock(hMatLock); + } + + // queue functor which will start polling every frame by re-queuing itself + g_pQueuedLoader->QueueDynamicLoadFunctor( CreateFunctor( ProcessDynamicLoad, pModelParts ) ); + } + } +} + +void CMDLCache::ProcessDynamicLoad( ModelParts_t *pModelParts ) +{ + Assert( IsPC() && ThreadInMainThread() ); + + if ( !g_pFullFileSystem->IsFileCacheLoaded( pModelParts->hFileCache ) ) + { + // poll again next frame... + g_pQueuedLoader->QueueDynamicLoadFunctor( CreateFunctor( ProcessDynamicLoad, pModelParts ) ); + return; + } + + if ( pModelParts->bMaterialsPending ) + { + DEBUG_SCOPE_TIMER(processvmt); + pModelParts->bMaterialsPending = false; + pModelParts->bTexturesPending = true; + + MaterialLock_t hMatLock = materials->Lock(); + materials->SetAsyncTextureLoadCache( pModelParts->hFileCache ); + + // Load all the materials + studiohdr_t * pHdr = g_MDLCache.GetStudioHdr( pModelParts->hMDL ); + if ( pHdr && !(pHdr->flags & STUDIOHDR_FLAGS_OBSOLETE) ) + { + // build strings inside a buffer that already contains a materials/ prefix + char buf[MAX_PATH]; + V_strcpy( buf, "materials/" ); + int prefixLen = V_strlen( buf ); + + // XXX this does not take remaps from vtxdata into account; + // right now i am not caring about that. we will hitch if any + // LODs remap to materials that are not in the header. (henryg) + for ( int t = 0; t < pHdr->numtextures; ++t ) + { + const char *pTexture = pHdr->pTexture(t)->pszName(); + pTexture += ( pTexture[0] == CORRECT_PATH_SEPARATOR || pTexture[0] == INCORRECT_PATH_SEPARATOR ); + for ( int cd = 0; cd < pHdr->numcdtextures; ++cd ) + { + const char *pCdTexture = pHdr->pCdtexture( cd ); + pCdTexture += ( pCdTexture[0] == CORRECT_PATH_SEPARATOR || pCdTexture[0] == INCORRECT_PATH_SEPARATOR ); + V_ComposeFileName( pCdTexture, pTexture, buf + prefixLen, MAX_PATH - prefixLen ); + IMaterial* pMaterial = materials->FindMaterial( buf + prefixLen, TEXTURE_GROUP_MODEL, false ); + if ( !IsErrorMaterial( pMaterial ) && !pMaterial->IsPrecached() ) + { + pModelParts->Materials.AddToTail( pMaterial ); + pMaterial->IncrementReferenceCount(); + // Force texture loads while material system is set to capture + // them and redirect to an error texture... this will populate + // the file cache with all the requested textures + pMaterial->RefreshPreservingMaterialVars(); + break; + } + } + } + } + + materials->SetAsyncTextureLoadCache( NULL ); + materials->Unlock( hMatLock ); + + // poll again next frame... dont want to do too much work right now + g_pQueuedLoader->QueueDynamicLoadFunctor( CreateFunctor( ProcessDynamicLoad, pModelParts ) ); + return; + } + + if ( pModelParts->bTexturesPending ) + { + DEBUG_SCOPE_TIMER(matrefresh); + pModelParts->bTexturesPending = false; + + // Perform the real material loads now while raw texture files are cached. + FOR_EACH_VEC( pModelParts->Materials, i ) + { + IMaterial* pMaterial = pModelParts->Materials[i]; + if ( !IsErrorMaterial( pMaterial ) && pMaterial->IsPrecached() ) + { + // Do a full reload to get the correct textures and computed flags + pMaterial->Refresh(); + } + } + + // poll again next frame... dont want to do too much work right now + g_pQueuedLoader->QueueDynamicLoadFunctor( CreateFunctor( ProcessDynamicLoad, pModelParts ) ); + return; + } + + // done. finish and clean up. + Assert( !pModelParts->bTexturesPending && !pModelParts->bMaterialsPending ); + + // pull out cached items we want to overlap with final processing + CleanupModelParts_t *pCleanup = new CleanupModelParts_t; + pCleanup->hFileCache = pModelParts->hFileCache; + pCleanup->Materials.Swap( pModelParts->Materials ); + g_pQueuedLoader->QueueDynamicLoadFunctor( CreateFunctor( CleanupDynamicLoad, pCleanup ) ); + + { + DEBUG_SCOPE_TIMER(processall); + g_MDLCache.ProcessQueuedData( pModelParts ); // pModelParts is deleted here + } +} + +void CMDLCache::CleanupDynamicLoad( CleanupModelParts_t *pCleanup ) +{ + Assert( IsPC() && ThreadInMainThread() ); + + // remove extra material refs, unload cached files + FOR_EACH_VEC( pCleanup->Materials, i ) + { + pCleanup->Materials[i]->DecrementReferenceCount(); + } + + g_pFullFileSystem->DestroyFileCache( pCleanup->hFileCache ); + + delete pCleanup; +} + +//----------------------------------------------------------------------------- +// Build a queued loader job to get the MDL ant all of its components into the cache. +//----------------------------------------------------------------------------- +bool CMDLCache::PreloadModel( MDLHandle_t handle ) +{ + if ( g_pQueuedLoader->IsDynamic() == false ) + { + if ( !IsX360() ) + { + return false; + } + + if ( !g_pQueuedLoader->IsMapLoading() || handle == MDLHANDLE_INVALID ) + { + return false; + } + } + + if ( !g_pQueuedLoader->IsBatching() ) + { + // batching must be active, following code depends on its behavior + DevWarning( "CMDLCache:: Late preload of model '%s'\n", GetModelName( handle ) ); + return false; + } + + // determine existing presence + // actual necessity is not established here, allowable absent files need their i/o error to occur + bool bNeedsMDL = !IsDataLoaded( handle, MDLCACHE_STUDIOHDR ); + bool bNeedsVTX = !IsDataLoaded( handle, MDLCACHE_STUDIOHWDATA ); + bool bNeedsVVD = !IsDataLoaded( handle, MDLCACHE_VERTEXES ); + bool bNeedsPHY = !IsDataLoaded( handle, MDLCACHE_VCOLLIDE ); + if ( !bNeedsMDL && !bNeedsVTX && !bNeedsVVD && !bNeedsPHY ) + { + // nothing to do + return true; + } + + char szFilename[MAX_PATH]; + char szNameOnDisk[MAX_PATH]; + V_strncpy( szFilename, GetActualModelName( handle ), sizeof( szFilename ) ); + V_StripExtension( szFilename, szFilename, sizeof( szFilename ) ); + + // need to gather all model parts (mdl, vtx, vvd, phy, ani) + ModelParts_t *pModelParts = new ModelParts_t; + pModelParts->hMDL = handle; + pModelParts->hFileCache = g_pFullFileSystem->CreateFileCache(); + + // create multiple loader jobs to perform gathering i/o operations + LoaderJob_t loaderJob; + loaderJob.m_pPathID = "GAME"; + loaderJob.m_pCallback = QueuedLoaderCallback_MDL; + loaderJob.m_pContext = (void *)pModelParts; + loaderJob.m_Priority = LOADERPRIORITY_DURINGPRELOAD; + loaderJob.m_bPersistTargetData = true; + + if ( bNeedsMDL ) + { + V_snprintf( szNameOnDisk, sizeof( szNameOnDisk ), "%s%s.mdl", szFilename, GetPlatformExt() ); + loaderJob.m_pFilename = szNameOnDisk; + loaderJob.m_pContext2 = (void *)ModelParts_t::BUFFER_MDL; + g_pQueuedLoader->AddJob( &loaderJob ); + pModelParts->nExpectedParts |= 1 << ModelParts_t::BUFFER_MDL; + } + + if ( bNeedsVTX ) + { + // vtx extensions are .xxx.vtx, need to re-form as, ???.xxx.yyy.vtx + char szTempName[MAX_PATH]; + V_snprintf( szNameOnDisk, sizeof( szNameOnDisk ), "%s%s", szFilename, GetVTXExtension() ); + V_StripExtension( szNameOnDisk, szTempName, sizeof( szTempName ) ); + V_snprintf( szNameOnDisk, sizeof( szNameOnDisk ), "%s%s.vtx", szTempName, GetPlatformExt() ); + loaderJob.m_pFilename = szNameOnDisk; + loaderJob.m_pContext2 = (void *)ModelParts_t::BUFFER_VTX; + g_pQueuedLoader->AddJob( &loaderJob ); + pModelParts->nExpectedParts |= 1 << ModelParts_t::BUFFER_VTX; + } + + if ( bNeedsVVD ) + { + V_snprintf( szNameOnDisk, sizeof( szNameOnDisk ), "%s%s.vvd", szFilename, GetPlatformExt() ); + loaderJob.m_pFilename = szNameOnDisk; + loaderJob.m_pContext2 = (void *)ModelParts_t::BUFFER_VVD; + g_pQueuedLoader->AddJob( &loaderJob ); + pModelParts->nExpectedParts |= 1 << ModelParts_t::BUFFER_VVD; + } + + if ( bNeedsPHY ) + { + V_snprintf( szNameOnDisk, sizeof( szNameOnDisk ), "%s%s.phy", szFilename, GetPlatformExt() ); + loaderJob.m_pFilename = szNameOnDisk; + loaderJob.m_pContext2 = (void *)ModelParts_t::BUFFER_PHY; + g_pQueuedLoader->AddJob( &loaderJob ); + pModelParts->nExpectedParts |= 1 << ModelParts_t::BUFFER_PHY; + } + + if ( !pModelParts->nExpectedParts ) + { + g_pFullFileSystem->DestroyFileCache( pModelParts->hFileCache ); + delete pModelParts; + } + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Clear the STUDIODATA_ERROR_MODEL flag. +//----------------------------------------------------------------------------- +void CMDLCache::ResetErrorModelStatus( MDLHandle_t handle ) +{ + if ( handle == MDLHANDLE_INVALID ) + return; + + m_MDLDict[handle]->m_nFlags &= ~STUDIODATA_ERROR_MODEL; +} + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void CMDLCache::MarkFrame() +{ + ProcessPendingAsyncs(); +} + +//----------------------------------------------------------------------------- +// Purpose: bind studiohdr_t support functions to the mdlcacher +//----------------------------------------------------------------------------- +const studiohdr_t *studiohdr_t::FindModel( void **cache, char const *pModelName ) const +{ + MDLHandle_t handle = g_MDLCache.FindMDL( pModelName ); + *cache = (void*)(uintp)handle; + return g_MDLCache.GetStudioHdr( handle ); +} + +virtualmodel_t *studiohdr_t::GetVirtualModel( void ) const +{ + if (numincludemodels == 0) + return NULL; + + return g_MDLCache.GetVirtualModelFast( this, (MDLHandle_t)(int)virtualModel&0xffff ); +} + +byte *studiohdr_t::GetAnimBlock( int i ) const +{ + return g_MDLCache.GetAnimBlock( (MDLHandle_t)(int)virtualModel&0xffff, i ); +} + +int studiohdr_t::GetAutoplayList( unsigned short **pOut ) const +{ + return g_MDLCache.GetAutoplayList( (MDLHandle_t)(int)virtualModel&0xffff, pOut ); +} + +const studiohdr_t *virtualgroup_t::GetStudioHdr( void ) const +{ + return g_MDLCache.GetStudioHdr( (MDLHandle_t)(int)cache&0xffff ); +} + diff --git a/datacache/xbox/xbox.def b/datacache/xbox/xbox.def new file mode 100644 index 0000000..2050c49 --- /dev/null +++ b/datacache/xbox/xbox.def @@ -0,0 +1,3 @@ +LIBRARY datacache_360.dll +EXPORTS + CreateInterface @1 \ No newline at end of file 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 +#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( 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( hElement ) && GetElement( 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( hElement ) && GetElement( 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 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 + +// 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 &src1, const CUtlVector &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::CreateAttributeData() +{ + void *pData = NewData< T >(); + CDmAttributeInfo< T >::SetDefaultValue( *reinterpret_cast( 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::DestroyAttributeData( void *pData ) +{ + DeleteData< T >( pData ); +} + + +//----------------------------------------------------------------------------- +// Sets the data to a default value, no undo (used for construction) +//----------------------------------------------------------------------------- +template< class T > +void CDmAttributeOp::SetDefaultValue( void *pData ) +{ + CDmAttributeInfo< T >::SetDefaultValue( *reinterpret_cast( pData ) ); +} + + +//----------------------------------------------------------------------------- +// Attribute type name, data size, value size +//----------------------------------------------------------------------------- +template< class T > +const char *CDmAttributeOp::AttributeTypeName() +{ + return CDmAttributeInfo::AttributeTypeName(); +} + +template< class T > +int CDmAttributeOp::DataSize() +{ + return sizeof( typename CDmAttributeInfo< T >::StorageType_t ); +} + +template< class T > +int CDmAttributeOp::ValueSize() +{ + return sizeof( T ); +} + + +//----------------------------------------------------------------------------- +// Value-setting methods +//----------------------------------------------------------------------------- +template< class T > +void CDmAttributeOp::SetToDefaultValue( CDmAttribute *pAttribute ) +{ + T newValue; + CDmAttributeInfo< T >::SetDefaultValue( newValue ); + pAttribute->SetValue( newValue ); +} + +template< class T > +void CDmAttributeOp::SetMultiple( CDmAttribute *pAttribute, int i, int nCount, DmAttributeType_t valueType, const void *pValue ) +{ + Assert(0); +} + +template< class T > +void CDmAttributeOp::Set( CDmAttribute *pAttribute, int i, DmAttributeType_t valueType, const void *pValue ) +{ + Assert(0); +} + +template< class T > +void CDmAttributeOp::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::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::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::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::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::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::SerializesOnMultipleLines() +{ + return ::SerializesOnMultipleLines< T >(); +} + +template< class T > +bool CDmAttributeOp::SkipUnserialize( CUtlBuffer& buf ) +{ + T dummy; + ::Unserialize( buf, dummy ); + return buf.IsValid(); +} + +template< class T > +bool CDmAttributeOp::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() ); +} + +template< class T > +bool CDmAttributeOp::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::SerializeElement( const CDmAttribute *pData, int nElement, CUtlBuffer &buf ) +{ + Assert( 0 ); + return false; +} + +template< class T > +bool CDmAttributeOp::UnserializeElement( CDmAttribute *pData, CUtlBuffer &buf ) +{ + Assert( 0 ); + return false; +} + +template< class T > +bool CDmAttributeOp::UnserializeElement( CDmAttribute *pData, int nElement, CUtlBuffer &buf ) +{ + Assert( 0 ); + return false; +} + +template< class T > +void CDmAttributeOp::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(); + 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( m_OldValue ); + } + } + virtual void Redo() + { + CDmAttribute *pAttribute = GetAttribute(); + if ( pAttribute && !pAttribute->IsFlagSet( FATTRIB_READONLY ) ) + { + pAttribute->SetValue( 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 +{ + typedef CUndoAttributeArrayBase 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 array( pAttribute ); + m_OldValue = array[ slot ]; + m_Value = newValue; + } + + virtual void Undo() + { + CDmrArray array( GetAttribute() ); + if ( array.IsValid() ) + { + array.Set( m_nSlot, m_OldValue ); + } + } + + virtual void Redo() + { + CDmrArray array( GetAttribute() ); + if ( array.IsValid() ) + { + array.Set( m_nSlot, m_Value ); + } + } + +private: + int m_nSlot; + typename CUndoAttributeArrayBase::StorageType_t m_OldValue; + typename CUndoAttributeArrayBase::StorageType_t m_Value; +}; + + +//----------------------------------------------------------------------------- +// Undo for setting a multiple elements +//----------------------------------------------------------------------------- +template< class T > +class CUndoArrayAttributeSetMultipleValueElement : public CUndoAttributeArrayBase +{ + typedef CUndoAttributeArrayBase 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::StorageType_t[nCount]; + m_pValue = new typename CUndoAttributeArrayBase::StorageType_t[nCount]; + + CDmrArray 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 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 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::StorageType_t *m_pOldValue; + typename CUndoAttributeArrayBase::StorageType_t *m_pValue; +}; + + +//----------------------------------------------------------------------------- +// +// Implementation Undo for CDmAttributeTyped +// +//----------------------------------------------------------------------------- +template< class T > +class CUndoAttributeArrayInsertBefore : public CUndoAttributeArrayBase +{ + typedef CUndoAttributeArrayBase 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 array( GetAttribute() ); + if ( array.IsValid() ) + { + array.RemoveMultiple( m_nIndex, m_nCount ); + } + } + + virtual void Redo() + { + CDmrArray array( GetAttribute() ); + if ( array.IsValid() ) + { + T defaultVal; + CDmAttributeInfo::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 +{ + typedef CUndoAttributeArrayBase 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 array( GetAttribute() ); + if ( array.IsValid() ) + { + array.Remove( m_nIndex ); + } + } + + virtual void Redo() + { + CDmrArray array( GetAttribute() ); + if ( array.IsValid() ) + { + array.InsertBefore( m_nIndex, m_newValue ); + } + } + +private: + int m_nIndex; + typename CUndoAttributeArrayBase::StorageType_t m_newValue; +}; + + +//----------------------------------------------------------------------------- +// +// Implementation Undo for remove +// +//----------------------------------------------------------------------------- +template< class T > +class CUndoAttributeArrayRemoveElement : public CUndoAttributeArrayBase +{ + typedef CUndoAttributeArrayBase 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 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 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::StorageType_t > m_OldValues; +}; + + +template< class T > +class CUndoAttributeArrayCopyAllElement : public CUndoAttributeArrayBase +{ + typedef CUndoAttributeArrayBase 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::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::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 array( GetAttribute() ); + if ( array.IsValid() ) + { + array.RemoveAll(); + for ( int i = 0; i < m_nOldSize; ++i ) + { + array.AddToTail( m_pOldValues[ i ] ); + } + } + } + + virtual void Redo() + { + CDmrArray 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::StorageType_t *m_pOldValues; + int m_nOldSize; + typename CUndoAttributeArrayBase::StorageType_t *m_pNewValues; + int m_nNewSize; + bool m_bPurge; +}; + + + +//----------------------------------------------------------------------------- +// CDmArrayAttributeOp implementation. +//----------------------------------------------------------------------------- + + +//----------------------------------------------------------------------------- +// Callbacks when elements are added + removed +//----------------------------------------------------------------------------- +template< class T > +void CDmArrayAttributeOp::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::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::Count() const +{ + return Data().Count(); +} + + +//----------------------------------------------------------------------------- +// Should we insert this element into the list? +//----------------------------------------------------------------------------- +template< class T > +inline bool CDmArrayAttributeOp::ShouldInsertElement( const T& src ) +{ + return true; +} + +template<> inline bool CDmArrayAttributeOp::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::ShouldInsert( const T& src ) +{ + if ( !ShouldInsertElement( src ) ) + return false; + + return m_pAttribute->MarkDirty(); +} + + +//----------------------------------------------------------------------------- +// Insert Before +//----------------------------------------------------------------------------- +template< class T > +int CDmArrayAttributeOp::InsertBefore( int elem, const T& src ) +{ + if ( !ShouldInsert( src ) ) + return Data().InvalidIndex(); + + if ( g_pDataModel->UndoEnabledForElement( m_pAttribute->GetOwner() ) ) + { + CUndoAttributeArrayInsertCopyBefore *pUndo = new CUndoAttributeArrayInsertCopyBefore( 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::AddToTail( const T& src ) +{ + return InsertBefore( Data().Count(), src ); +} + + +//----------------------------------------------------------------------------- +// Insert Multiple Before +//----------------------------------------------------------------------------- +template< class T > +int CDmArrayAttributeOp::InsertMultipleBefore( int elem, int num ) +{ + if ( !m_pAttribute->MarkDirty() ) + return Data().InvalidIndex(); + + // UNDO HOOK + if ( g_pDataModel->UndoEnabledForElement( m_pAttribute->GetOwner() ) ) + { + CUndoAttributeArrayInsertBefore *pUndo = new CUndoAttributeArrayInsertBefore( 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::SetDefaultValue( Data()[ index + i ] ); + } + OnAttributeArrayElementAdded( index, index + num - 1 ); + m_pAttribute->OnChanged( true ); + return index; +} + + +//----------------------------------------------------------------------------- +// Removal +//----------------------------------------------------------------------------- +template< class T > +void CDmArrayAttributeOp::FastRemove( int elem ) +{ + if ( !m_pAttribute->MarkDirty() ) + return; + + // UNDO HOOK + if ( g_pDataModel->UndoEnabledForElement( m_pAttribute->GetOwner() ) ) + { + CUndoAttributeArrayRemoveElement *pUndo = new CUndoAttributeArrayRemoveElement( 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::Remove( int elem ) +{ + if ( !Data().IsValidIndex( elem ) ) + return; + + if ( !m_pAttribute->MarkDirty() ) + return; + + // UNDO HOOK + if ( g_pDataModel->UndoEnabledForElement( m_pAttribute->GetOwner() ) ) + { + CUndoAttributeArrayRemoveElement *pUndo = new CUndoAttributeArrayRemoveElement( 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::RemoveAll() +{ + if ( !m_pAttribute->MarkDirty() ) + return; + + // UNDO HOOK + if ( g_pDataModel->UndoEnabledForElement( m_pAttribute->GetOwner() ) ) + { + CUndoAttributeArrayCopyAllElement *pUndo = new CUndoAttributeArrayCopyAllElement( 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::RemoveMultiple( int elem, int num ) +{ + if ( !m_pAttribute->MarkDirty() ) + return; + + // UNDO HOOK + if ( g_pDataModel->UndoEnabledForElement( m_pAttribute->GetOwner() ) ) + { + CUndoAttributeArrayRemoveElement *pUndo = new CUndoAttributeArrayRemoveElement( 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::Purge() +{ + if ( !m_pAttribute->MarkDirty() ) + return; + + // UNDO HOOK + if ( g_pDataModel->UndoEnabledForElement( m_pAttribute->GetOwner() ) ) + { + CUndoAttributeArrayCopyAllElement *pUndo = new CUndoAttributeArrayCopyAllElement( 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::PerformCopyArray( const T *pArray, int nCount ) +{ + Data().CopyArray( pArray, nCount ); +} + +template<> void CDmArrayAttributeOp::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::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 *pUndo = new CUndoAttributeArrayCopyAllElement( 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::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 *pUndo = new CUndoAttributeArrayCopyAllElement( 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::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::Set( int i, const T& value ) +{ + if ( i < 0 || i >= Data().Count() ) + { + Assert( !"CDmAttributeArray::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 *pUndo = new CUndoArrayAttributeSetValueElement( 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::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::SetMultiple( int i, int nCount, const T* pValue ) +{ + if ( i < 0 || ( i+nCount ) > Data().Count() ) + { + AssertMsg( 0, "CDmAttributeArray::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 *pUndo = new CUndoArrayAttributeSetMultipleValueElement( 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::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::SetValue( CDmAttribute *pAttribute, DmAttributeType_t valueType, const void *pValue ) +{ + Assert( pAttribute->GetType() == valueType ); + if ( pAttribute->GetType() == valueType ) + { + CDmArrayAttributeOp accessor( pAttribute ); + const CUtlVector* pArray = reinterpret_cast< const CUtlVector* >( pValue ); + accessor.CopyArray( pArray->Base(), pArray->Count() ); + } +} + + +//----------------------------------------------------------------------------- +// Swap +//----------------------------------------------------------------------------- +template< class T > +void CDmArrayAttributeOp::Swap( int i, int j ) +{ + if ( i == j ) + return; + + // TODO - define Swap 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 *pUndo = new CUndoArrayAttributeSetValueElement( m_pAttribute, i, Data()[ j ] ); + g_pDataModel->AddUndoElement( pUndo ); + pUndo = new CUndoArrayAttributeSetValueElement( 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::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 accessor( pAttribute ); + accessor.SwapArray( tempVal ); + + return bRet; +} + +template<> bool CDmArrayAttributeOp::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 accessor( pAttribute ); + accessor.CopyArray( tempVal.Base(), tempVal.Count() ); + + return bRet; +} + +// Serialization of a single element +template< class T > +bool CDmArrayAttributeOp::SerializeElement( const CDmAttribute *pAttribute, int nElement, CUtlBuffer &buf ) +{ + CDmrArrayConst array( pAttribute ); + return ::Serialize( buf, array[ nElement ] ); +} + +template< class T > +bool CDmArrayAttributeOp::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 accessor( pAttribute ); + accessor.AddToTail( temp ); + + pAttribute->OnChanged( true ); + } + return bReadElement; +} + +template< class T > +bool CDmArrayAttributeOp::UnserializeElement( CDmAttribute *pAttribute, int nElement, CUtlBuffer &buf ) +{ + if ( !CDmAttributeAccessor::MarkDirty( pAttribute ) ) + return false; + + CDmrArray array( pAttribute ); + if ( array.Count() <= nElement ) + return false; + + MEM_ALLOC_CREDIT_CLASS(); + + pAttribute->PreChanged(); + bool bReadElement = ::Unserialize( buf, *const_cast( &array[nElement] ) ); + if ( bReadElement ) + { + pAttribute->OnChanged(); + } + return bReadElement; +} + +template< class T > +void CDmArrayAttributeOp::OnUnserializationFinished( CDmAttribute *pAttribute ) +{ + CDmArrayAttributeOp 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(), 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() const +{ + DmAttributeType_t type = GetType(); + return ( type == AT_BOOL || type == AT_INT || type == AT_FLOAT ); +} + +template<> bool CDmAttribute::IsTypeConvertable() const +{ + DmAttributeType_t type = GetType(); + return ( type == AT_INT || type == AT_BOOL || type == AT_FLOAT ); +} + +template<> bool CDmAttribute::IsTypeConvertable() const +{ + DmAttributeType_t type = GetType(); + return ( type == AT_FLOAT || type == AT_INT || type == AT_BOOL ); +} + +template<> bool CDmAttribute::IsTypeConvertable() const +{ + DmAttributeType_t type = GetType(); + return ( type == AT_QANGLE || type == AT_QUATERNION ); +} + +template<> bool CDmAttribute::IsTypeConvertable() 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(), 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() ) + return false; + + if ( ( GetType() == CDmAttributeInfo::ATTRIBUTE_TYPE ) && IsAttributeEqual( GetValue(), value ) ) + return false; + + return MarkDirty(); +} + +template<> bool CDmAttribute::ShouldModify( const DmElementHandle_t& value ) +{ + if ( !IsTypeConvertable() ) + return false; + + if ( IsAttributeEqual( GetValue(), value ) ) + return false; + + DmElementAttribute_t *pData = GetData(); + 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 *pUndo = new CUndoAttributeSetValueElement( 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(); + 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(); + 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::CDmaArrayConstBase( ) +{ + m_pAttribute = NULL; +} + + +//----------------------------------------------------------------------------- +// Search +//----------------------------------------------------------------------------- +template< class T, class B > +int CDmaArrayConstBase::Find( const T &value ) const +{ + return Value().Find( value ); +} + + +//----------------------------------------------------------------------------- +// Insertion +//----------------------------------------------------------------------------- +template< class T, class B > +int CDmaArrayBase::AddToTail() +{ + T defaultVal; + CDmAttributeInfo::SetDefaultValue( defaultVal ); + CDmArrayAttributeOp accessor( this->m_pAttribute ); + return accessor.InsertBefore( Value().Count(), defaultVal ); +} + +template< class T, class B > +int CDmaArrayBase::InsertBefore( int elem ) +{ + T defaultVal; + CDmAttributeInfo::SetDefaultValue( defaultVal ); + CDmArrayAttributeOp accessor( this->m_pAttribute ); + return accessor.InsertBefore( elem, defaultVal ); +} + +template< class T, class B > +int CDmaArrayBase::AddToTail( const T& src ) +{ + CDmArrayAttributeOp accessor( this->m_pAttribute ); + return accessor.InsertBefore( Value().Count(), src ); +} + +template< class T, class B > +int CDmaArrayBase::InsertBefore( int elem, const T& src ) +{ + CDmArrayAttributeOp accessor( this->m_pAttribute ); + return accessor.InsertBefore( elem, src ); +} + +template< class T, class B > +int CDmaArrayBase::AddMultipleToTail( int num ) +{ + CDmArrayAttributeOp accessor( this->m_pAttribute ); + return accessor.InsertMultipleBefore( Value().Count(), num ); +} + +template< class T, class B > +int CDmaArrayBase::InsertMultipleBefore( int elem, int num ) +{ + CDmArrayAttributeOp accessor( this->m_pAttribute ); + return accessor.InsertMultipleBefore( elem, num ); +} + +template< class T, class B > +void CDmaArrayBase::EnsureCount( int num ) +{ + int nCurrentCount = Value().Count(); + if ( nCurrentCount < num ) + { + AddMultipleToTail( num - nCurrentCount ); + } +} + + +//----------------------------------------------------------------------------- +// Element modification +//----------------------------------------------------------------------------- +template< class T, class B > +void CDmaArrayBase::Set( int i, const T& value ) +{ + CDmArrayAttributeOp accessor( this->m_pAttribute ); + return accessor.Set( i, value ); +} + +template< class T, class B > +void CDmaArrayBase::SetMultiple( int i, int nCount, const T* pValue ) +{ + CDmArrayAttributeOp accessor( this->m_pAttribute ); + accessor.SetMultiple( i, nCount, pValue ); +} + +template< class T, class B > +void CDmaArrayBase::Swap( int i, int j ) +{ + CDmArrayAttributeOp accessor( this->m_pAttribute ); + accessor.Swap( i, j ); +} + +template< class T, class B > +void CDmaArrayBase::SwapArray( CUtlVector< T > &array ) +{ + CDmArrayAttributeOp accessor( this->m_pAttribute ); + accessor.SwapArray( array ); +} + + +//----------------------------------------------------------------------------- +// Copy +//----------------------------------------------------------------------------- +template< class T, class B > +void CDmaArrayBase::CopyArray( const T *pArray, int nCount ) +{ + CDmArrayAttributeOp accessor( this->m_pAttribute ); + accessor.CopyArray( pArray, nCount ); +} + + +//----------------------------------------------------------------------------- +// Removal +//----------------------------------------------------------------------------- +template< class T, class B > +void CDmaArrayBase::FastRemove( int elem ) +{ + CDmArrayAttributeOp accessor( this->m_pAttribute ); + accessor.FastRemove( elem ); +} + +template< class T, class B > +void CDmaArrayBase::Remove( int elem ) +{ + CDmArrayAttributeOp accessor( this->m_pAttribute ); + accessor.Remove( elem ); +} + +template< class T, class B > +void CDmaArrayBase::RemoveAll() +{ + CDmArrayAttributeOp accessor( this->m_pAttribute ); + accessor.RemoveAll(); +} + +template< class T, class B > +void CDmaArrayBase::RemoveMultiple( int elem, int num ) +{ + CDmArrayAttributeOp accessor( this->m_pAttribute ); + accessor.RemoveMultiple( elem, num ); +} + + +//----------------------------------------------------------------------------- +// Memory management +//----------------------------------------------------------------------------- +template< class T, class B > +void CDmaArrayBase::EnsureCapacity( int num ) +{ + Value().EnsureCapacity( num ); +} + +template< class T, class B > +void CDmaArrayBase::Purge() +{ + CDmArrayAttributeOp accessor( this->m_pAttribute ); + accessor.Purge(); +} + + +//----------------------------------------------------------------------------- +// Attribute initialization +//----------------------------------------------------------------------------- +template< class T, class B > +void CDmaDecorator::Init( CDmElement *pOwner, const char *pAttributeName, int nFlags = 0 ) +{ + Assert( pOwner ); + this->m_pAttribute = pOwner->AddExternalAttribute( pAttributeName, CDmAttributeInfo >::AttributeType(), &Value() ); + Assert( m_pAttribute ); + if ( nFlags ) + { + this->m_pAttribute->AddFlag( nFlags ); + } +} + + +//----------------------------------------------------------------------------- +// Attribute attribute reference +//----------------------------------------------------------------------------- +template< class T, class BaseClass > +void CDmrDecoratorConst::Init( const CDmAttribute* pAttribute ) +{ + if ( pAttribute && pAttribute->GetType() == CDmAttributeInfo< CUtlVector< T > >::AttributeType() ) + { + this->m_pAttribute = const_cast( pAttribute ); + Attach( this->m_pAttribute->GetAttributeData() ); + } + else + { + this->m_pAttribute = NULL; + Attach( NULL ); + } +} + +template< class T, class BaseClass > +void CDmrDecoratorConst::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::IsValid() const +{ + return this->m_pAttribute != NULL; +} + + +template< class T, class BaseClass > +void CDmrDecorator::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::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::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 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 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( 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 CInstantiateOp +{ +public: + CInstantiateOp() + { + s_pAttrInfo[ CDmAttributeInfo::ATTRIBUTE_TYPE ] = new CDmAttributeOp< T >; + } +}; +static CInstantiateOp __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(); + 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(); + 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(); + 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(); + 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(); + 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(); + 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(); + 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(); + 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( 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(); + 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 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..da15ed4 --- /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 &elementList ); + void UnserializeElementAttribute( CUtlBuffer &buf, CDmAttribute *pAttribute, CUtlVector &elementList ); + void UnserializeElementArrayAttribute( CUtlBuffer &buf, CDmAttribute *pAttribute, CUtlVector &elementList ); + bool UnserializeAttributes( CUtlBuffer &buf, CDmElement *pElement, CUtlVector &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 &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 &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 &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 &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( 0x20000, max( 64, nExpectedIdCopyConflicts / 64 ) ); // 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 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 +#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 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 +#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 + + +//----------------------------------------------------------------------------- +// 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(); + 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(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 diff --git a/dmserializers/dmebaseimporter.cpp b/dmserializers/dmebaseimporter.cpp new file mode 100644 index 0000000..7fc10bc --- /dev/null +++ b/dmserializers/dmebaseimporter.cpp @@ -0,0 +1,42 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= +#include "dmserializers.h" +#include "dmebaseimporter.h" + +CDmeBaseImporter::CDmeBaseImporter( char const *formatName, char const *nextFormatName ) : + m_pFormatName( formatName ), + m_pNextSerializer( nextFormatName ) +{ +} + +bool CDmeBaseImporter::IsLatestVersion() const +{ + return g_pDataModel->FindLegacyUpdater( m_pNextSerializer ) == NULL; +} + +// Updates ppRoot to first non-legacy generic dmx format, returns false if the conversion fails +bool CDmeBaseImporter::Update( CDmElement **ppRoot ) +{ + if ( !DoFixup( *ppRoot ) ) + return false; + + if ( !m_pNextSerializer ) + return true; + + // Chain + IDmLegacyUpdater *pUpdater = g_pDataModel->FindLegacyUpdater( m_pNextSerializer ); + if ( !pUpdater ) + return true; + + return pUpdater->Update( ppRoot ); +} + + + +CSFMBaseImporter::CSFMBaseImporter( char const *formatName, char const *nextFormatName ) : + BaseClass( formatName, nextFormatName ) +{ +} diff --git a/dmserializers/dmebaseimporter.h b/dmserializers/dmebaseimporter.h new file mode 100644 index 0000000..1a74d77 --- /dev/null +++ b/dmserializers/dmebaseimporter.h @@ -0,0 +1,43 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= + +#ifndef DMEBASEIMPORTER_H +#define DMEBASEIMPORTER_H +#ifdef _WIN32 +#pragma once +#endif + +#include "datamodel/idatamodel.h" + +class CDmeBaseImporter : public IDmLegacyUpdater +{ + typedef IDmLegacyUpdater BaseClass; + +public: + CDmeBaseImporter( char const *formatName, char const *nextFormatName ); + + virtual const char *GetName() const { return m_pFormatName; } + virtual bool IsLatestVersion() const; + + virtual bool Update( CDmElement **ppRoot ); + +private: + virtual bool DoFixup( CDmElement *pRoot ) = 0; + +protected: + char const *m_pFormatName; + char const *m_pNextSerializer; +}; + +class CSFMBaseImporter : public CDmeBaseImporter +{ + typedef CDmeBaseImporter BaseClass; + +public: + CSFMBaseImporter( char const *formatName, char const *nextFormatName ); +}; + +#endif // DMEBASEIMPORTER_H diff --git a/dmserializers/dmserializers.cpp b/dmserializers/dmserializers.cpp new file mode 100644 index 0000000..860bcf9 --- /dev/null +++ b/dmserializers/dmserializers.cpp @@ -0,0 +1,163 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// The copyright to the contents herein is the property of Valve, L.L.C. +// The contents may be used and/or copied only with the written permission of +// Valve, L.L.C., or in accordance with the terms and conditions stipulated in +// the agreement/contract under which the contents have been supplied. +// +// $Header: $ +// $NoKeywords: $ +// +// Converts from any one DMX file format to another +// +//============================================================================= + +#include "dmserializers.h" +#include "dmserializers/idmserializers.h" +#include "appframework/iappsystem.h" +#include "filesystem.h" +#include "datamodel/idatamodel.h" +#include "datamodel/dmelementfactoryhelper.h" +#include "tier2/tier2.h" + + +//----------------------------------------------------------------------------- +// format updater macros +//----------------------------------------------------------------------------- + +#define DECLARE_FORMAT_UPDATER( _name, _description, _extension, _version, _encoding ) \ + class CDmFormatUpdater_ ## _name : public IDmFormatUpdater \ + { \ + public: \ + CDmFormatUpdater_ ## _name() {} \ + virtual const char *GetName() const { return #_name; } \ + virtual const char *GetDescription() const { return _description; } \ + virtual const char *GetExtension() const { return _extension; } \ + virtual const char *GetDefaultEncoding() const { return _encoding; } \ + virtual int GetCurrentVersion() const { return _version; } \ + virtual bool Update( CDmElement **pRoot, int nSourceVersion ) { return true; } \ + }; \ + static CDmFormatUpdater_ ## _name s_FormatUpdater ## _name; \ + void InstallFormatUpdater_ ## _name( IDataModel *pFactory ) \ + { \ + pFactory->AddFormatUpdater( &s_FormatUpdater ## _name ); \ + } + +#define INSTALL_FORMAT_UPDATER( _name ) InstallFormatUpdater_ ## _name( g_pDataModel ) + + +//----------------------------------------------------------------------------- +// format updaters +//----------------------------------------------------------------------------- + +DECLARE_FORMAT_UPDATER( dmx, "Generic DMX", "dmx", 1, "binary" ) +DECLARE_FORMAT_UPDATER( movieobjects, "Generic MovieObjects", "dmx", 1, "binary" ) +DECLARE_FORMAT_UPDATER( sfm, "Generic SFM", "dmx", 1, "binary" ) +DECLARE_FORMAT_UPDATER( sfm_session, "SFM Session", "dmx", 1, "binary" ) +DECLARE_FORMAT_UPDATER( sfm_trackgroup, "SFM TrackGroup", "dmx", 1, "binary" ) +DECLARE_FORMAT_UPDATER( pcf, "Particle Configuration File", "dmx", 1, "binary" ) +DECLARE_FORMAT_UPDATER( preset, "Preset File", "dmx", 1, "keyvalues2" ) +DECLARE_FORMAT_UPDATER( facial_animation, "Facial Animation File", "dmx", 1, "binary" ) +DECLARE_FORMAT_UPDATER( model, "DMX Model", "dmx", 1, "binary" ) +//DECLARE_FORMAT_UPDATER( animation, "DMX Animation", "dmx", 1, "binary" ) +//DECLARE_FORMAT_UPDATER( dcc_makefile, "DMX Makefile", "dmx", 1, "keyvalues2" ) + + +//----------------------------------------------------------------------------- +// The application object +//----------------------------------------------------------------------------- +class CDmSerializers : public CBaseAppSystem< IDmSerializers > +{ + typedef CBaseAppSystem< IDmSerializers > BaseClass; + +public: + // Inherited from IAppSystem + virtual bool Connect( CreateInterfaceFn factory ); + virtual void *QueryInterface( const char *pInterfaceName ); + virtual InitReturnVal_t Init(); +}; + + +//----------------------------------------------------------------------------- +// Singleton interface +//----------------------------------------------------------------------------- +static CDmSerializers g_DmSerializers; +IDmSerializers *g_pDmSerializers = &g_DmSerializers; + + +//----------------------------------------------------------------------------- +// Here's where the app systems get to learn about each other +//----------------------------------------------------------------------------- +bool CDmSerializers::Connect( CreateInterfaceFn factory ) +{ + if ( !BaseClass::Connect( factory ) ) + return false; + + if ( !factory( FILESYSTEM_INTERFACE_VERSION, NULL ) ) + { + Warning( "DmSerializers needs the file system to function" ); + return false; + } + + // Here's the main point where all DM element classes get installed + // Necessary to do it here so all type symbols for all DME classes are set + // up prior to install + InstallDmElementFactories( ); + + return true; +} + + +//----------------------------------------------------------------------------- +// Here's where systems can access other interfaces implemented by this object +//----------------------------------------------------------------------------- +void *CDmSerializers::QueryInterface( const char *pInterfaceName ) +{ + if ( !V_strcmp( pInterfaceName, DMSERIALIZERS_INTERFACE_VERSION ) ) + return (IDmSerializers*)this; + + return NULL; +} + + +//----------------------------------------------------------------------------- +// Init, shutdown +//----------------------------------------------------------------------------- +InitReturnVal_t CDmSerializers::Init() +{ + InitReturnVal_t nRetVal = BaseClass::Init(); + if ( nRetVal != INIT_OK ) + return nRetVal; + + // Install non-dmx importers + InstallActBusyImporter( g_pDataModel ); + InstallVMTImporter( g_pDataModel ); + InstallVMFImporter( g_pDataModel ); + + // Install legacy dmx importers + InstallSFMV1Importer( g_pDataModel ); + InstallSFMV2Importer( g_pDataModel ); + InstallSFMV3Importer( g_pDataModel ); + InstallSFMV4Importer( g_pDataModel ); + InstallSFMV5Importer( g_pDataModel ); + InstallSFMV6Importer( g_pDataModel ); + InstallSFMV7Importer( g_pDataModel ); + InstallSFMV8Importer( g_pDataModel ); + InstallSFMV9Importer( g_pDataModel ); + + // install dmx format updaters + INSTALL_FORMAT_UPDATER( dmx ); + INSTALL_FORMAT_UPDATER( movieobjects ); + INSTALL_FORMAT_UPDATER( sfm ); + INSTALL_FORMAT_UPDATER( sfm_session ); + INSTALL_FORMAT_UPDATER( sfm_trackgroup ); + INSTALL_FORMAT_UPDATER( pcf ); + INSTALL_FORMAT_UPDATER( preset ); + INSTALL_FORMAT_UPDATER( facial_animation ); + INSTALL_FORMAT_UPDATER( model ); +// INSTALL_FORMAT_UPDATER( animation ); +// INSTALL_FORMAT_UPDATER( dcc_makefile ); + + return INIT_OK; +} + diff --git a/dmserializers/dmserializers.h b/dmserializers/dmserializers.h new file mode 100644 index 0000000..24e07a2 --- /dev/null +++ b/dmserializers/dmserializers.h @@ -0,0 +1,55 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// The copyright to the contents herein is the property of Valve, L.L.C. +// The contents may be used and/or copied only with the written permission of +// Valve, L.L.C., or in accordance with the terms and conditions stipulated in +// the agreement/contract under which the contents have been supplied. +// +// $Header: $ +// $NoKeywords: $ +// +// Main header file for the serializers DLL +// +//============================================================================= + +#ifndef DMSERIALIZERS_H +#define DMSERIALIZERS_H + +#ifdef _WIN32 +#pragma once +#endif + +#include "datamodel/dmelement.h" +#include "datamodel/dmattribute.h" +#include "datamodel/dmattributevar.h" + +//----------------------------------------------------------------------------- +// Forward declarations +//----------------------------------------------------------------------------- +class IDataModel; + + +//----------------------------------------------------------------------------- +// Externally defined importers +//----------------------------------------------------------------------------- +void InstallActBusyImporter( IDataModel *pFactory ); +void InstallVMTImporter( IDataModel *pFactory ); +void InstallSFMV1Importer( IDataModel *pFactory ); +void InstallSFMV2Importer( IDataModel *pFactory ); +void InstallSFMV3Importer( IDataModel *pFactory ); +void InstallSFMV4Importer( IDataModel *pFactory ); +void InstallSFMV5Importer( IDataModel *pFactory ); +void InstallSFMV6Importer( IDataModel *pFactory ); +void InstallSFMV7Importer( IDataModel *pFactory ); +void InstallSFMV8Importer( IDataModel *pFactory ); +void InstallSFMV9Importer( IDataModel *pFactory ); +void InstallVMFImporter( IDataModel *pFactory ); + +void InstallDMXUpdater( IDataModel *pFactory ); +void InstallSFMSessionUpdater( IDataModel *pFactory ); +void InstallPCFUpdater( IDataModel *pFactory ); + + +#endif // DMSERIALIZERS_H + + diff --git a/dmserializers/dmserializers.vpc b/dmserializers/dmserializers.vpc new file mode 100644 index 0000000..6e7226d --- /dev/null +++ b/dmserializers/dmserializers.vpc @@ -0,0 +1,46 @@ +//----------------------------------------------------------------------------- +// DMSERIALIZERS.VPC +// +// Project Script +//----------------------------------------------------------------------------- + +$Macro SRCDIR ".." +$include "$SRCDIR\vpc_scripts\source_lib_base.vpc" + +$Configuration +{ + $Compiler + { + $PreprocessorDefinitions "$BASE;DMSERIALIZERS_LIB" + } +} + +$Project "Dmserializers" +{ + $Folder "Source Files" + { + $File "dmebaseimporter.cpp" + $File "dmserializers.cpp" + $File "importactbusy.cpp" + $File "importkeyvaluebase.cpp" + $File "importsfmv1.cpp" + $File "importsfmv2.cpp" + $File "importsfmv3.cpp" + $File "importsfmv4.cpp" + $File "importsfmv5.cpp" + $File "importsfmv6.cpp" + $File "importsfmv7.cpp" + $File "importsfmv8.cpp" + $File "importsfmv9.cpp" + $File "importvmf.cpp" + $File "importvmt.cpp" + } + + $Folder "Header Files" + { + $File "dmebaseimporter.h" + $File "dmserializers.h" + $File "$SRCDIR\public\dmserializers\idmserializers.h" + $File "importkeyvaluebase.h" + } +} diff --git a/dmserializers/importactbusy.cpp b/dmserializers/importactbusy.cpp new file mode 100644 index 0000000..2ca816e --- /dev/null +++ b/dmserializers/importactbusy.cpp @@ -0,0 +1,182 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= + +#include "importkeyvaluebase.h" +#include "dmserializers.h" +#include "datamodel/idatamodel.h" +#include "datamodel/dmelement.h" +#include "tier1/KeyValues.h" +#include "tier1/utlbuffer.h" +#include "datamodel/dmattribute.h" + + +//----------------------------------------------------------------------------- +// Serialization class for Key Values +//----------------------------------------------------------------------------- +class CImportActBusy : public CImportKeyValueBase +{ +public: + virtual const char *GetName() const { return "actbusy"; } + virtual const char *GetDescription() const { return "ActBusy Script File"; } + virtual int GetCurrentVersion() const { return 0; } // doesn't store a version + + bool Serialize( CUtlBuffer &outBuf, CDmElement *pRoot ); + CDmElement* UnserializeFromKeyValues( KeyValues *pKeyValues ); + +private: + // Reads a single element + bool UnserializeActBusyKey( CDmAttribute *pChildren, KeyValues *pKeyValues ); + + // Writes out the actbusy header + void SerializeHeader( CUtlBuffer &buf ); +}; + + +//----------------------------------------------------------------------------- +// Singleton instance +//----------------------------------------------------------------------------- +static CImportActBusy s_ImportActBusy; + +void InstallActBusyImporter( IDataModel *pFactory ) +{ + pFactory->AddSerializer( &s_ImportActBusy ); +} + + +//----------------------------------------------------------------------------- +// Writes out the actbusy header +//----------------------------------------------------------------------------- +void CImportActBusy::SerializeHeader( CUtlBuffer &buf ) +{ + buf.Printf( "// \"act busy name\"\t\tThis is the name that a mapmaker must specify in the hint node.\n" ); + buf.Printf( "// {\n" ); + buf.Printf( "// \t\"busy_anim\"\t\t\t\"Activity Name\".\n" ); + buf.Printf( "// \t\"entry_anim\"\t\t\"Activity Name\"\n" ); + buf.Printf( "// \t\"exit_anim\"\t\t\t\"Activity Name\"\n" ); + buf.Printf( "// \t\"busy_sequence\"\t\t\"Sequence Name\". If specified, this is used over the activity name. Specify it in the hint node.\n" ); + buf.Printf( "// \t\"entry_sequence\"\t\"Sequence Name\". If specified, this is used over the entry anim.\n" ); + buf.Printf( "// \t\"exit_sequence\"\t\t\"Sequence Name\". If specified, this is used over the exit anim.\n" ); + buf.Printf( "// \t\"min_time\"\t\t\t\"Minimum time to spend in this busy anim\"\n" ); + buf.Printf( "// \t\"max_time\"\t\t\t\"Maximum time to spend in this busy anim\" 0 = only stop when interrupted by external event\n" ); + buf.Printf( "// \t\"interrupts\"\t\tOne of:\n" ); + buf.Printf( "// \t\t\t\t\t\t\"BA_INT_NONE\"\t\tbreak out only when time runs out. No external influence will break me out.\n" ); + buf.Printf( "// \t\t\t\t\t\t\"BA_INT_DANGER\"\t\tbreak out of this anim only if threatened\n" ); + buf.Printf( "// \t\t\t\t\t\t\"BA_INT_PLAYER\"\t\tbreak out of this anim if I can see the player, or I'm threatened\n" ); + buf.Printf( "// \t\t\t\t\t\t\"BA_INT_AMBUSH\"\t\tsomeone please define this - I have no idea what it does\n" ); + buf.Printf( "// \t\t\t\t\t\t\"BA_INT_COMBAT\"\t\tbreak out of this anim if combat occurs in my line of sight (bullet hits, grenades, etc), -OR- the max time is reached\n" ); + buf.Printf( "// }\n" ); + buf.Printf( "//\n" ); +} + + +//----------------------------------------------------------------------------- +// Writes out a new actbusy file +//----------------------------------------------------------------------------- +bool CImportActBusy::Serialize( CUtlBuffer &buf, CDmElement *pRoot ) +{ + SerializeHeader( buf ); + buf.Printf( "\"ActBusy.txt\"\n" ); + buf.Printf( "{\n" ); + + CDmAttribute *pChildren = pRoot->GetAttribute( "children" ); + if ( !pChildren || pChildren->GetType() != AT_ELEMENT_ARRAY ) + return NULL; + + CDmrElementArray<> children( pChildren ); + int nCount = children.Count(); + + buf.PushTab(); + for ( int i = 0; i < nCount; ++i ) + { + CDmElement *pChild = children[i]; + buf.Printf( "\"%s\"\n", pChild->GetName() ); + buf.Printf( "{\n" ); + + buf.PushTab(); + PrintStringAttribute( pChild, buf, "busy_anim", true ); + PrintStringAttribute( pChild, buf, "entry_anim", true ); + PrintStringAttribute( pChild, buf, "exit_anim", true ); + PrintStringAttribute( pChild, buf, "busy_sequence", true ); + PrintStringAttribute( pChild, buf, "entry_sequence", true ); + PrintStringAttribute( pChild, buf, "exit_sequence", true ); + PrintFloatAttribute( pChild, buf, "min_time" ); + PrintFloatAttribute( pChild, buf, "max_time" ); + PrintStringAttribute( pChild, buf, "interrupts" ); + buf.PopTab(); + + buf.Printf( "}\n" ); + } + buf.PopTab(); + + buf.Printf( "}\n" ); + + return true; +} + + +//----------------------------------------------------------------------------- +// Reads a single element +//----------------------------------------------------------------------------- +bool CImportActBusy::UnserializeActBusyKey( CDmAttribute *pChildren, KeyValues *pKeyValues ) +{ + CDmElement *pActBusy = CreateDmElement( "DmElement", pKeyValues->GetName(), NULL ); + if ( !pActBusy ) + return false; + + // Each act busy needs to have an editortype associated with it so it displays nicely in editors + pActBusy->SetValue( "editorType", "actBusy" ); + + float flZero = 0.0f; + AddStringAttribute( pActBusy, pKeyValues, "busy_anim", "" ); + AddStringAttribute( pActBusy, pKeyValues, "entry_anim", "" ); + AddStringAttribute( pActBusy, pKeyValues, "exit_anim", "" ); + AddStringAttribute( pActBusy, pKeyValues, "busy_sequence", "" ); + AddStringAttribute( pActBusy, pKeyValues, "entry_sequence", "" ); + AddStringAttribute( pActBusy, pKeyValues, "exit_sequence", "" ); + AddFloatAttribute( pActBusy, pKeyValues, "min_time", &flZero ); + AddFloatAttribute( pActBusy, pKeyValues, "max_time", &flZero ); + AddStringAttribute( pActBusy, pKeyValues, "interrupts", "BA_INT_NONE" ); + + CDmrElementArray<> children( pChildren ); + children.AddToTail( pActBusy ); + + return true; +} + + +//----------------------------------------------------------------------------- +// Main entry point for the unserialization +//----------------------------------------------------------------------------- +CDmElement* CImportActBusy::UnserializeFromKeyValues( KeyValues *pKeyValues ) +{ + // Create the main element + CDmElement *pElement = CreateDmElement( "DmElement", "ActBusyList", NULL ); + if ( !pElement ) + return NULL; + + // Each act busy list needs to have an editortype associated with it so it displays nicely in editors + pElement->SetValue( "editorType", "actBusyList" ); + + // All actbusy keys are elements of a single element array attribute 'children' + CDmAttribute *pChildren = pElement->AddAttribute( "children", AT_ELEMENT_ARRAY ); + if ( !pChildren ) + return NULL; + + // Under the root are all the actbusy keys + for ( KeyValues *pActBusyKey = pKeyValues->GetFirstTrueSubKey(); pActBusyKey != NULL; pActBusyKey = pActBusyKey->GetNextTrueSubKey() ) + { + if ( !UnserializeActBusyKey( pChildren, pActBusyKey ) ) + { + Warning( "Error importing actbusy element %s\n", pActBusyKey->GetName() ); + return NULL; + } + } + + // Resolve all element references recursively + RecursivelyResolveElement( pElement ); + + return pElement; +} diff --git a/dmserializers/importkeyvaluebase.cpp b/dmserializers/importkeyvaluebase.cpp new file mode 100644 index 0000000..88d2308 --- /dev/null +++ b/dmserializers/importkeyvaluebase.cpp @@ -0,0 +1,292 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= + +#include "importkeyvaluebase.h" +#include "dmserializers.h" +#include "datamodel/idatamodel.h" +#include "datamodel/dmelement.h" +#include "tier1/KeyValues.h" +#include "tier1/utlbuffer.h" +#include + + +//----------------------------------------------------------------------------- +// Default serialization method +//----------------------------------------------------------------------------- +bool CImportKeyValueBase::Serialize( CUtlBuffer &outBuf, CDmElement *pRoot ) +{ + Warning( "Serialization not supported for importing from keyvalues files\n"); + return false; +} + + +//----------------------------------------------------------------------------- +// Creates a new element +//----------------------------------------------------------------------------- +CDmElement* CImportKeyValueBase::CreateDmElement( const char *pElementType, const char *pElementName, DmObjectId_t *pId ) +{ + // See if we can create an element of that type + DmElementHandle_t hElement = g_pDataModel->CreateElement( pElementType, pElementName, DMFILEID_INVALID, pId ); + if ( hElement == DMELEMENT_HANDLE_INVALID ) + { + Warning("%s: Element uses unknown element type %s\n", m_pFileName, pElementType ); + return NULL; + } + + return g_pDataModel->GetElement( hElement ); +} + + +//----------------------------------------------------------------------------- +// Used to output typed attributes to keyvalues +//----------------------------------------------------------------------------- +void CImportKeyValueBase::PrintBoolAttribute( CDmElement* pElement, CUtlBuffer &outBuf, const char *pKeyName ) +{ + if ( pElement->HasAttribute( pKeyName ) ) + { + CDmAttribute *pAttribute = pElement->GetAttribute( pKeyName ); + if ( pAttribute->GetType() == AT_BOOL ) + { + outBuf.Printf("\"%s\" \"%d\"\n", pKeyName, pAttribute->GetValue( ) ); + } + } +} + +void CImportKeyValueBase::PrintIntAttribute( CDmElement* pElement, CUtlBuffer &outBuf, const char *pKeyName ) +{ + if ( pElement->HasAttribute( pKeyName ) ) + { + CDmAttribute *pAttribute = pElement->GetAttribute( pKeyName ); + if ( pAttribute->GetType() == AT_INT ) + { + outBuf.Printf("\"%s\" \"%d\"\n", pKeyName, pAttribute->GetValue( ) ); + } + } +} + +void CImportKeyValueBase::PrintFloatAttribute( CDmElement* pElement, CUtlBuffer &outBuf, const char *pKeyName ) +{ + if ( pElement->HasAttribute( pKeyName ) ) + { + CDmAttribute *pAttribute = pElement->GetAttribute( pKeyName ); + if ( pAttribute->GetType() == AT_FLOAT ) + { + outBuf.Printf("\"%s\" \"%.10f\"\n", pKeyName, pAttribute->GetValue( ) ); + } + } +} + +void CImportKeyValueBase::PrintStringAttribute( CDmElement* pElement, CUtlBuffer &outBuf, const char *pKeyName, bool bSkipEmptryStrings, bool bPrintValueOnly ) +{ + if ( pElement->HasAttribute( pKeyName ) ) + { + CDmAttribute *pAttribute = pElement->GetAttribute( pKeyName ); + if ( pAttribute->GetType() == AT_STRING ) + { + const char *pValue = pAttribute->GetValueString(); + if ( !bSkipEmptryStrings || pValue[0] ) + { + if ( !bPrintValueOnly ) + { + outBuf.Printf("\"%s\" \"%s\"\n", pKeyName, pValue ); + } + else + { + outBuf.Printf("\"%s\"\n", pValue ); + } + } + } + } +} + + +//----------------------------------------------------------------------------- +// Used to add typed attributes from keyvalues +//----------------------------------------------------------------------------- +bool CImportKeyValueBase::AddBoolAttribute( CDmElement* pElement, KeyValues *pKeyValues, const char *pKeyName, bool *pDefault ) +{ + KeyValues *pKey = pKeyValues->FindKey( pKeyName ); + bool bValue; + if ( pKey ) + { + bValue = pKey->GetInt() != 0; + } + else + { + if ( !pDefault ) + return true; + bValue = *pDefault; + } + + return pElement->SetValue( pKeyName, bValue ) != NULL; +} + + +//----------------------------------------------------------------------------- +// Used to add typed attributes from keyvalues +//----------------------------------------------------------------------------- +bool CImportKeyValueBase::AddIntAttribute( CDmElement* pElement, KeyValues *pKeyValues, const char *pKeyName, int *pDefault ) +{ + KeyValues *pKey = pKeyValues->FindKey( pKeyName ); + int nValue; + if ( pKey ) + { + nValue = pKey->GetInt(); + } + else + { + if ( !pDefault ) + return true; + nValue = *pDefault; + } + + return pElement->SetValue( pKeyName, nValue ) != NULL; +} + +bool CImportKeyValueBase::AddFloatAttribute( CDmElement* pElement, KeyValues *pKeyValues, const char *pKeyName, float *pDefault ) +{ + KeyValues *pKey = pKeyValues->FindKey( pKeyName ); + float flValue; + if ( pKey ) + { + flValue = pKey->GetFloat(); + } + else + { + if ( !pDefault ) + return true; + flValue = *pDefault; + } + + return pElement->SetValue( pKeyName, flValue ) != NULL; +} + +bool CImportKeyValueBase::AddStringAttribute( CDmElement* pElement, KeyValues *pKeyValues, const char *pKeyName, const char *pDefault ) +{ + KeyValues *pKey = pKeyValues->FindKey( pKeyName ); + const char *pValue = ""; + if ( pKey ) + { + pValue = pKey->GetString(); + } + else + { + if ( !pDefault ) + return true; + pValue = pDefault; + } + + return pElement->SetValue( pKeyName, pValue ) != NULL; +} + + +//----------------------------------------------------------------------------- +// Used to add typed attributes from keyvalues +//----------------------------------------------------------------------------- +bool CImportKeyValueBase::AddBoolAttributeFlags( CDmElement* pElement, KeyValues *pKeyValue, const char *pKeyName, int nFlags, bool *pDefault ) +{ + if ( !AddBoolAttribute( pElement, pKeyValue, pKeyName, pDefault ) ) + return false; + + CDmAttribute *pAttribute = pElement->GetAttribute( pKeyName ); + pAttribute->AddFlag( nFlags ); + return true; +} + +bool CImportKeyValueBase::AddIntAttributeFlags( CDmElement* pElement, KeyValues *pKeyValue, const char *pKeyName, int nFlags, int *pDefault ) +{ + if ( !AddIntAttribute( pElement, pKeyValue, pKeyName, pDefault ) ) + return false; + + CDmAttribute *pAttribute = pElement->GetAttribute( pKeyName ); + pAttribute->AddFlag( nFlags ); + return true; +} + +bool CImportKeyValueBase::AddFloatAttributeFlags( CDmElement* pElement, KeyValues *pKeyValue, const char *pKeyName, int nFlags, float *pDefault ) +{ + if ( !AddFloatAttribute( pElement, pKeyValue, pKeyName, pDefault ) ) + return false; + + CDmAttribute *pAttribute = pElement->GetAttribute( pKeyName ); + pAttribute->AddFlag( nFlags ); + return true; +} + +bool CImportKeyValueBase::AddStringAttributeFlags( CDmElement* pElement, KeyValues *pKeyValue, const char *pKeyName, int nFlags, const char *pDefault ) +{ + if ( !AddStringAttribute( pElement, pKeyValue, pKeyName, pDefault ) ) + return false; + + CDmAttribute *pAttribute = pElement->GetAttribute( pKeyName ); + pAttribute->AddFlag( nFlags ); + return true; +} + + +//----------------------------------------------------------------------------- +// Recursively resolves all attributes pointing to elements +//----------------------------------------------------------------------------- +void CImportKeyValueBase::RecursivelyResolveElement( CDmElement* pElement ) +{ + if ( !pElement ) + return; + + pElement->Resolve(); + + CDmAttribute *pAttribute = pElement->FirstAttribute(); + while ( pAttribute ) + { + switch ( pAttribute->GetType() ) + { + case AT_ELEMENT: + { + CDmElement *pElementAt = pAttribute->GetValueElement(); + RecursivelyResolveElement( pElementAt ); + } + break; + + case AT_ELEMENT_ARRAY: + { + CDmrElementArray<> array( pAttribute ); + int nCount = array.Count(); + for ( int i = 0; i < nCount; ++i ) + { + CDmElement *pElementAt = array[ i ]; + RecursivelyResolveElement( pElementAt ); + } + } + break; + } + + pAttribute = pAttribute->NextAttribute( ); + } +} + + +//----------------------------------------------------------------------------- +// Main entry point for the unserialization +//----------------------------------------------------------------------------- +bool CImportKeyValueBase::Unserialize( CUtlBuffer &buf, const char *pEncodingName, int nEncodingVersion, + const char *pSourceFormatName, int nSourceFormatVersion, + DmFileId_t fileid, DmConflictResolution_t idConflictResolution, CDmElement **ppRoot ) +{ + *ppRoot = NULL; + m_pFileName = g_pDataModel->GetFileName( fileid ); + + KeyValues *kv = new KeyValues( "dmx file" ); + if ( !kv ) + return false; + + bool bOk = kv->LoadFromBuffer( "dmx file", buf ); + if ( bOk ) + { + *ppRoot = UnserializeFromKeyValues( kv ); + } + + kv->deleteThis(); + return bOk; +} diff --git a/dmserializers/importkeyvaluebase.h b/dmserializers/importkeyvaluebase.h new file mode 100644 index 0000000..771d048 --- /dev/null +++ b/dmserializers/importkeyvaluebase.h @@ -0,0 +1,84 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= + +#ifndef IMPORTKEYVALUEBASE_H +#define IMPORTKEYVALUEBASE_H + +#ifdef _WIN32 +#pragma once +#endif + +#include "datamodel/idatamodel.h" + + +//----------------------------------------------------------------------------- +// Forward declarations +//----------------------------------------------------------------------------- +class CUtlBuffer; +class KeyValues; +class CDmElement; + + +//----------------------------------------------------------------------------- +// Serialization class for Key Values +//----------------------------------------------------------------------------- +abstract_class CImportKeyValueBase : public IDmSerializer +{ +public: + // Inherited from IDMSerializer + virtual bool StoresVersionInFile() const { return false; } + virtual bool IsBinaryFormat() const { return false; } + 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 ); + +protected: + // Main entry point for derived classes to implement unserialization + virtual CDmElement* UnserializeFromKeyValues( KeyValues *pKeyValues ) = 0; + + // Returns the file name associated with the unserialization + const char *FileName() const; + + // Creates new elements + CDmElement* CreateDmElement( const char *pElementType, const char *pElementName, DmObjectId_t *pId ); + + // Recursively resolves all attributes pointing to elements + void RecursivelyResolveElement( CDmElement* pElement ); + + // Used to add typed attributes from keyvalues + bool AddBoolAttribute( CDmElement* pElement, KeyValues *pKeyValue, const char *pKeyName, bool *pDefault = NULL ); + bool AddIntAttribute( CDmElement* pElement, KeyValues *pKeyValue, const char *pKeyName, int *pDefault = NULL ); + bool AddFloatAttribute( CDmElement* pElement, KeyValues *pKeyValue, const char *pKeyName, float *pDefault = NULL ); + bool AddStringAttribute( CDmElement* pElement, KeyValues *pKeyValue, const char *pKeyName, const char *pDefault = NULL ); + + // Used to add typed attributes from keyvalues + bool AddBoolAttributeFlags( CDmElement* pElement, KeyValues *pKeyValue, const char *pKeyName, int nFlags, bool *pDefault = NULL ); + bool AddIntAttributeFlags( CDmElement* pElement, KeyValues *pKeyValue, const char *pKeyName, int nFlags, int *pDefault = NULL ); + bool AddFloatAttributeFlags( CDmElement* pElement, KeyValues *pKeyValue, const char *pKeyName, int nFlags, float *pDefault = NULL ); + bool AddStringAttributeFlags( CDmElement* pElement, KeyValues *pKeyValue, const char *pKeyName, int nFlags, const char *pDefault = NULL ); + + // Used to output typed attributes to keyvalues + void PrintBoolAttribute( CDmElement* pElement, CUtlBuffer &outBuf, const char *pKeyName ); + void PrintIntAttribute( CDmElement* pElement, CUtlBuffer &outBuf, const char *pKeyName ); + void PrintFloatAttribute( CDmElement* pElement, CUtlBuffer &outBuf, const char *pKeyName ); + void PrintStringAttribute( CDmElement* pElement, CUtlBuffer &outBuf, const char *pKeyName, bool bSkipEmptryStrings = false, bool bPrintValueOnly = false ); + +private: + const char *m_pFileName; +}; + + +//----------------------------------------------------------------------------- +// Returns the file name associated with the unserialization +//----------------------------------------------------------------------------- +inline const char *CImportKeyValueBase::FileName() const +{ + return m_pFileName; +} + + +#endif // IMPORTKEYVALUEBASE_H diff --git a/dmserializers/importsfmv1.cpp b/dmserializers/importsfmv1.cpp new file mode 100644 index 0000000..1da3f5e --- /dev/null +++ b/dmserializers/importsfmv1.cpp @@ -0,0 +1,219 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= + +#include "dmserializers.h" +#include "dmebaseimporter.h" +#include "datamodel/idatamodel.h" +#include "datamodel/dmattribute.h" +#include "datamodel/dmelement.h" + +#include + +//----------------------------------------------------------------------------- +// Format converter +//----------------------------------------------------------------------------- +class CImportSFMV1 : public CSFMBaseImporter +{ + typedef CSFMBaseImporter BaseClass; +public: + CImportSFMV1( char const *formatName, char const *nextFormatName ); + +private: + virtual bool DoFixup( CDmElement *pSourceRoot ); + + // Fixes up a single time attribute - converting from float seconds to int tenths-of-a-millisecond + void ConvertTimeAttribute( CDmElement *pElementInternal, const char *pOldName, const char *pNewName ); + + // Fixes up a single timeframe + void FixupTimeframe( CDmElement *pElementInternal ); + + // Fixes up a single log - converting from int milliseconds to int tenths-of-a-millisecond + void FixupLog( CDmElement *pElementInternal ); + + CUtlRBTree< CDmElement*, int > m_fixedElements; +}; + + +//----------------------------------------------------------------------------- +// Singleton instance +//----------------------------------------------------------------------------- +static CImportSFMV1 s_ImportDmxV1( "sfm_v1", "sfm_v2" ); + +void InstallSFMV1Importer( IDataModel *pFactory ) +{ + pFactory->AddLegacyUpdater( &s_ImportDmxV1 ); +} + + +//----------------------------------------------------------------------------- +// Constructor +//----------------------------------------------------------------------------- +CImportSFMV1::CImportSFMV1( char const *formatName, char const *nextFormatName ) : + BaseClass( formatName, nextFormatName ) +{ + m_fixedElements.SetLessFunc( DefLessFunc( CDmElement * ) ); +} + + +//----------------------------------------------------------------------------- +// Fixes up all elements +//----------------------------------------------------------------------------- +bool CImportSFMV1::DoFixup( CDmElement *pElementInternal ) +{ + if ( !pElementInternal ) + return true; + + if ( m_fixedElements.Find( pElementInternal ) != m_fixedElements.InvalidIndex() ) + return true; + + m_fixedElements.Insert( pElementInternal ); + + const char *pType = pElementInternal->GetTypeString(); + if ( !Q_strcmp( pType, "DmeTimeFrame" ) ) + { + FixupTimeframe( pElementInternal ); + } + else if ( !Q_strcmp( pType, "DmeLog" ) || + !Q_strcmp( pType, "DmeIntLog" ) || + !Q_strcmp( pType, "DmeFloatLog" ) || + !Q_strcmp( pType, "DmeBoolLog" ) || + !Q_strcmp( pType, "DmeColorLog" ) || + !Q_strcmp( pType, "DmeVector2Log" ) || + !Q_strcmp( pType, "DmeVector3Log" ) || + !Q_strcmp( pType, "DmeVector4Log" ) || + !Q_strcmp( pType, "DmeQAngleLog" ) || + !Q_strcmp( pType, "DmeQuaternionLog" ) || + !Q_strcmp( pType, "DmeVMatrixLog" ) ) + { + FixupLog( pElementInternal ); + } + + + for ( CDmAttribute *pAttribute = pElementInternal->FirstAttribute(); pAttribute; pAttribute = pAttribute->NextAttribute() ) + { + if ( pAttribute->GetType() == AT_ELEMENT ) + { + CDmElement *pElement = pAttribute->GetValueElement( ); + DoFixup( pElement ); + continue; + } + + if ( pAttribute->GetType() == AT_ELEMENT_ARRAY ) + { + CDmrElementArray<> array( pAttribute ); + int nCount = array.Count(); + for ( int i = 0; i < nCount; ++i ) + { + CDmElement *pChild = array[ i ]; + DoFixup( pChild ); + } + continue; + } + } + + return true; +} + +//----------------------------------------------------------------------------- +// Fixes up a single time attribute - converting from float seconds to int tenths-of-a-millisecond +//----------------------------------------------------------------------------- +void CImportSFMV1::ConvertTimeAttribute( CDmElement *pElementInternal, const char *pOldName, const char *pNewName ) +{ + float time = 0.0f; + CDmAttribute *pOldAttr = pElementInternal->GetAttribute( pOldName ); + if ( !pOldAttr ) + { + Warning( "*** Problem in file encountered!\n" ); + Warning( "*** TimeFrame \"%s\" is missing attribute \"%s\"!\n", pElementInternal->GetName(), pOldName ); + Warning( "*** Setting new attribute \"%s\" to 0\n", pNewName ); + } + else if ( pOldAttr->GetType() != AT_FLOAT ) + { + Warning( "*** Problem in file encountered!\n" ); + Warning( "*** TimeFrame \"%s\" has attribute \"%s\" with an unexpected type (expected float)!\n", pElementInternal->GetName(), pOldName ); + } + else + { + time = pOldAttr->GetValue< float >(); + pElementInternal->RemoveAttribute( pOldName ); + } + + CDmAttribute *pNewAttr = NULL; + + // this is disabled because even dmxconvert installs *some* movieobjects factories, when it probably shouldn't + // the method of installing movieobjects factories will change at some point in the future, and we can turn on this safety check then +#if 0 + int i = g_pDataModel->GetFirstFactory(); + if ( g_pDataModel->IsValidFactory( i ) ) + { + // factories installed - most likely from within movieobjects.lib + // ie there may be different ways of allocating attributes, so it's not safe to add them here + pNewAttr = pElementInternal->GetAttribute( pNewName ); + if ( !pNewAttr || pNewAttr->GetType() != AT_INT ) + { + Assert( 0 ); + Warning( "*** Converter error - expected element \"%s\" to contain int attribute \"%s\"!\n", pElementInternal->GetName(), pNewName ); + Warning( "*** - if you get this error, the converter is out of sync with the element library!\n" ); + return; + } + } + else + { +#endif + // no factories installed - most likely from within dmxconvert.exe + // ie we're just working with CDmElement subclasses, so it's safe to add attributes + pNewAttr = pElementInternal->AddAttribute( pNewName, AT_INT ); + if ( !pNewAttr ) + { + Assert( 0 ); + Warning( "*** Converter error - element \"%s\" already has a non-int attribute \"%s\"!\n", pElementInternal->GetName(), pNewName ); + return; + } +#if 0 + } +#endif + + pNewAttr->SetValue< int >( floor( time * 10000 + 0.5f ) ); +} + +//----------------------------------------------------------------------------- +// Fixes up a single timeframe +//----------------------------------------------------------------------------- +void CImportSFMV1::FixupTimeframe( CDmElement *pElementInternal ) +{ + ConvertTimeAttribute( pElementInternal, "start", "startTime" ); + ConvertTimeAttribute( pElementInternal, "duration", "durationTime" ); + ConvertTimeAttribute( pElementInternal, "offset", "offsetTime" ); +} + +//----------------------------------------------------------------------------- +// Fixes up a single log - converting from int milliseconds to int tenths-of-a-millisecond +//----------------------------------------------------------------------------- +void CImportSFMV1::FixupLog( CDmElement *pElementInternal ) +{ + CDmAttribute *pAttr = pElementInternal->GetAttribute( "times" ); + if ( !pAttr ) + { + Warning( "*** Problem in file encountered!\n" ); + Warning( "*** Log \"%s\" is missing attribute \"%s\"!\n", pElementInternal->GetName(), "times" ); + return; + } + + if ( pAttr->GetType() != AT_INT_ARRAY ) + { + Warning( "*** Problem in file encountered!\n" ); + Warning( "*** Log \"%s\" has attribute \"%s\" with an unexpected type (expected int array)!\n", pElementInternal->GetName(), "times" ); + return; + } + + CDmrArray array( pAttr ); + int c = array.Count(); + for ( int i = 0; i < c; ++i ) + { + // convert all log times from int milliseconds to int tenths-of-a-millisecond + array.Set( i, 10 * array[i] ); + } +} diff --git a/dmserializers/importsfmv2.cpp b/dmserializers/importsfmv2.cpp new file mode 100644 index 0000000..829ae0c --- /dev/null +++ b/dmserializers/importsfmv2.cpp @@ -0,0 +1,294 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= + +#include "dmserializers.h" +#include "dmebaseimporter.h" +#include "datamodel/idatamodel.h" +#include "datamodel/dmelement.h" +#include "tier1/KeyValues.h" +#include "tier1/utlbuffer.h" +#include "tier1/utlmap.h" +#include + + +//----------------------------------------------------------------------------- +// Format converter +//----------------------------------------------------------------------------- +class CImportSFMV2 : public CSFMBaseImporter +{ + typedef CSFMBaseImporter BaseClass; +public: + CImportSFMV2( char const *formatName, char const *nextFormatName ); + +private: + virtual bool DoFixup( CDmElement *pSourceRoot ); + + + void FixupElement( CDmElement *pElement ); + // Fixes up all elements + void BuildList( CDmElement *pElement, CUtlRBTree< CDmElement *, int >& list ); +}; + + +//----------------------------------------------------------------------------- +// Singleton instance +//----------------------------------------------------------------------------- +static CImportSFMV2 s_ImportSFMV2( "sfm_v2", "sfm_v3" ); + +void InstallSFMV2Importer( IDataModel *pFactory ) +{ + pFactory->AddLegacyUpdater( &s_ImportSFMV2 ); +} + + +//----------------------------------------------------------------------------- +// Constructor +//----------------------------------------------------------------------------- +CImportSFMV2::CImportSFMV2( char const *formatName, char const *nextFormatName ) : + BaseClass( formatName, nextFormatName ) +{ +} + + +struct LayerType_t +{ + char const *loglayertype; + int datatype; + char const *logtype; +}; + +static LayerType_t g_LayerTypes[] = +{ + { "DmeIntLogLayer", AT_INT_ARRAY, "DmeIntLog" }, + { "DmeFloatLogLayer", AT_FLOAT_ARRAY, "DmeFloatLog" }, + { "DmeBoolLogLayer", AT_BOOL_ARRAY, "DmeBoolLog" }, + // AT_STRING_ARRAY, + // AT_VOID_ARRAY, + // AT_OBJECTID_ARRAY, + { "DmeColorLogLayer", AT_COLOR_ARRAY, "DmeColorLog" }, + { "DmeVector2LogLayer", AT_VECTOR2_ARRAY, "DmeVector2Log" }, + { "DmeVector3LogLayer", AT_VECTOR3_ARRAY, "DmeVector3Log" }, + { "DmeVector4LogLayer", AT_VECTOR4_ARRAY, "DmeVector4Log" }, + { "DmeQAngleLogLayer", AT_QANGLE_ARRAY, "DmeQAngleLog" }, + { "DmeQuaternionLogLayer", AT_QUATERNION_ARRAY, "DmeQuaternionLog" }, + { "DmeVMatrixLogLayer", AT_VMATRIX_ARRAY, "DmeVMatrixLog" }, + // AT_ELEMENT_ARRAY + // NO ARRAY TYPES EITHER!!! +}; + +int GetLogType( char const *type ) +{ + int c = ARRAYSIZE( g_LayerTypes ); + for ( int i = 0; i < c; ++i ) + { + if ( !Q_stricmp( type, g_LayerTypes[ i ].logtype ) ) + return g_LayerTypes[ i ].datatype; + } + return AT_UNKNOWN; +} + +char const *GetLogLayerType( int nDataType ) +{ + int c = ARRAYSIZE( g_LayerTypes ); + for ( int i = 0; i < c; ++i ) + { + if ( nDataType == g_LayerTypes[ i ].datatype ) + return g_LayerTypes[ i ].loglayertype; + } + return NULL; +} + +char const *GetLogLayerType( char const *logType ) +{ + int c = ARRAYSIZE( g_LayerTypes ); + for ( int i = 0; i < c; ++i ) + { + if ( !Q_stricmp( logType, g_LayerTypes[ i ].logtype ) ) + return g_LayerTypes[ i ].loglayertype; + } + return NULL; +} + +template< class T > +void CopyValues( int layerType, CDmElement *pElement, CDmElement *pLayer, CDmAttribute *pInTimeAttribute, CDmAttribute *pInCurveTypeAttribute ) +{ + CDmAttribute *pInValueAttribute = pElement->GetAttribute( "values" ); + if ( !pInValueAttribute ) + { + Assert( 0 ); + return; + } + + CDmrArray outValues( pLayer->AddAttribute( "values", (DmAttributeType_t)layerType ) ); + CDmrArray outTimes( pLayer->AddAttribute( "times", AT_INT_ARRAY ) ); + CDmrArray outCurveTypes; + if ( pInCurveTypeAttribute ) + { + outCurveTypes.Init( pLayer->AddAttribute( "curvetypes", AT_INT_ARRAY ) ); + } + + CDmrArray inValues( pInValueAttribute ); + CDmrArray inTimes( pInTimeAttribute ); + CDmrArray inCurveTypes( pInCurveTypeAttribute ); + + Assert( inValues.Count() == inTimes.Count() ); + int c = inValues.Count(); + for ( int i = 0; i < c; ++i ) + { + outTimes.AddToTail( inTimes[ i ] ); + outValues.AddToTail( inValues[ i ] ); + if ( outCurveTypes.IsValid() ) + { + outCurveTypes.AddToTail( inCurveTypes[ i ] ); + } + } +} + + +//----------------------------------------------------------------------------- +// Fixes up all elements +//----------------------------------------------------------------------------- +void CImportSFMV2::FixupElement( CDmElement *pElement ) +{ + if ( !pElement ) + return; + + // Perform the fixup + const char *pType = pElement->GetTypeString(); + int layerType = GetLogType( pType ); + if ( layerType != AT_UNKNOWN ) + { + /* + char buf[ 128 ]; + g_pDataModel->ToString( pElement->GetId(), buf, sizeof( buf ) ); + + Msg( "Processing %s %s id %s\n", + pElement->GetTypeString(), pElement->GetName(), buf ); + */ + + // Find attribute arrays for times, values and curvetypes + CDmAttribute *pTimes = pElement->GetAttribute( "times" ); + CDmAttribute *pCurveTypes = NULL; + + // FIX + CDmAttribute *pAttr = pElement->AddAttribute( "usecurvetypes", AT_BOOL ); + if ( pAttr->GetValue() ) + { + pCurveTypes = pElement->GetAttribute( "curvetypes" ); + } + + // Get the default layer (added when the new style log is created) + CDmrElementArray<> layers( pElement->AddAttribute( "layers", AT_ELEMENT_ARRAY ) ); + CDmElement *layer = NULL; + if ( layers.Count() == 0 ) + { + DmElementHandle_t hElement = g_pDataModel->CreateElement( GetLogLayerType( layerType ), GetLogLayerType( layerType ), pElement->GetFileId() ); + layer = g_pDataModel->GetElement( hElement ); + layers.AddToTail( layer ); + } + else + { + Assert( layers.Count() == 1 ); + layer = layers[ 0 ]; + } + + // Copy data + switch ( layerType ) + { + default: + case AT_UNKNOWN: + break; + case AT_FLOAT_ARRAY: + CopyValues< float >( layerType, pElement, layer, pTimes, pCurveTypes ); + break; + case AT_INT_ARRAY: + CopyValues< int >( layerType, pElement, layer, pTimes, pCurveTypes ); + break; + case AT_BOOL_ARRAY: + CopyValues< bool >( layerType, pElement, layer, pTimes, pCurveTypes ); + break; + case AT_COLOR_ARRAY: + CopyValues< Color >( layerType, pElement, layer, pTimes, pCurveTypes ); + break; + case AT_VECTOR2_ARRAY: + CopyValues< Vector2D >( layerType, pElement, layer, pTimes, pCurveTypes ); + break; + case AT_VECTOR3_ARRAY: + CopyValues< Vector >( layerType, pElement, layer, pTimes, pCurveTypes ); + break; + case AT_VECTOR4_ARRAY: + CopyValues< Vector4D >( layerType, pElement, layer, pTimes, pCurveTypes ); + break; + case AT_QANGLE_ARRAY: + CopyValues< QAngle >( layerType, pElement, layer, pTimes, pCurveTypes ); + break; + case AT_QUATERNION_ARRAY: + CopyValues< Quaternion >( layerType, pElement, layer, pTimes, pCurveTypes ); + break; + case AT_VMATRIX_ARRAY: + CopyValues< VMatrix >( layerType, pElement, layer, pTimes, pCurveTypes ); + break; + } + + // Set the back pointer + CDmAttribute *ownerLog = layer->AddAttribute( "ownerlog", AT_ELEMENT ); + Assert( ownerLog ); + ownerLog->SetValue( pElement->GetHandle() ); + + // Delete the base attributes + pElement->RemoveAttribute( "times" ); + pElement->RemoveAttribute( "values" ); + pElement->RemoveAttribute( "curvetypes" ); + } +} + +// Fixes up all elements +//----------------------------------------------------------------------------- +void CImportSFMV2::BuildList( CDmElement *pElement, CUtlRBTree< CDmElement *, int >& list ) +{ + if ( !pElement ) + return; + + if ( list.Find( pElement ) != list.InvalidIndex() ) + return; + + list.Insert( pElement ); + + // Descene to bottom of tree, then do fixup coming back up the tree + for ( CDmAttribute *pAttribute = pElement->FirstAttribute(); pAttribute; pAttribute = pAttribute->NextAttribute() ) + { + if ( pAttribute->GetType() == AT_ELEMENT ) + { + CDmElement *pElementAt = pAttribute->GetValueElement( ); + BuildList( pElementAt, list ); + continue; + } + + if ( pAttribute->GetType() == AT_ELEMENT_ARRAY ) + { + CDmrElementArray<> array( pAttribute ); + int nCount = array.Count(); + for ( int i = 0; i < nCount; ++i ) + { + CDmElement *pChild = array[ i ]; + BuildList( pChild, list ); + } + continue; + } + } +} + +bool CImportSFMV2::DoFixup( CDmElement *pSourceRoot ) +{ + CUtlRBTree< CDmElement *, int > fixlist( 0, 0, DefLessFunc( CDmElement * ) ); + BuildList( pSourceRoot, fixlist ); + for ( int i = fixlist.FirstInorder(); i != fixlist.InvalidIndex() ; i = fixlist.NextInorder( i ) ) + { + // Search and replace in the entire tree! + FixupElement( fixlist[ i ] ); + } + return true; +} diff --git a/dmserializers/importsfmv3.cpp b/dmserializers/importsfmv3.cpp new file mode 100644 index 0000000..d421eb3 --- /dev/null +++ b/dmserializers/importsfmv3.cpp @@ -0,0 +1,227 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= + +#include "dmserializers.h" +#include "dmebaseimporter.h" +#include "datamodel/idatamodel.h" +#include "datamodel/dmelement.h" +#include "tier1/KeyValues.h" +#include "tier1/utlbuffer.h" +#include "tier1/utlmap.h" +#include + + +//----------------------------------------------------------------------------- +// Format converter +//----------------------------------------------------------------------------- +class CImportSFMV3 : public CSFMBaseImporter +{ + typedef CSFMBaseImporter BaseClass; +public: + CImportSFMV3( char const *formatName, char const *nextFormatName ); + +private: + virtual bool DoFixup( CDmElement *pSourceRoot ); + + + void FixupElement( CDmElement *pElement ); + // Fixes up all elements + void BuildList( CDmElement *pElement, CUtlRBTree< CDmElement *, int >& list ); +}; + + +//----------------------------------------------------------------------------- +// Singleton instance +//----------------------------------------------------------------------------- +static CImportSFMV3 s_ImportSFMV3( "sfm_v3", "sfm_v4" ); + +void InstallSFMV3Importer( IDataModel *pFactory ) +{ + pFactory->AddLegacyUpdater( &s_ImportSFMV3 ); +} + + +//----------------------------------------------------------------------------- +// Constructor +//----------------------------------------------------------------------------- +CImportSFMV3::CImportSFMV3( char const *formatName, char const *nextFormatName ) : + BaseClass( formatName, nextFormatName ) +{ +} + + +struct LogToCurveInfoTypeMap_t +{ + const char *pLogType; + const char *pLogLayerType; + const char *pCurveInfoType; +}; + +LogToCurveInfoTypeMap_t g_typeMap[] = +{ + { "DmeIntLog", "DmeIntLogLayer", "DmeIntCurveInfo" }, + { "DmeFloatLog", "DmeFloatLogLayer", "DmeFloatCurveInfo" }, + { "DmeBoolLog", "DmeBoolLogLayer", "DmeBoolCurveInfo" }, + // string, + // void, + // objectid, + { "DmeColorLog", "DmeColorLogLayer", "DmeColorCurveInfo" }, + { "DmeVector2Log", "DmeVector2LogLayer", "DmeVector2CurveInfo" }, + { "DmeVector3Log", "DmeVector3LogLayer", "DmeVector3CurveInfo" }, + { "DmeVector4Log", "DmeVector4LogLayer", "DmeVector4CurveInfo" }, + { "DmeQAngleLog", "DmeQAngleLogLayer", "DmeQAngleCurveInfo" }, + { "DmeQuaternionLog", "DmeQuaternionLogLayer","DmeQuaternionCurveInfo" }, + { "DmeVMatrixLog", "DmeVMatrixLogLayer", "DmeVMatrixCurveInfo" }, +}; + +const char *GetCurveInfoTypeFromLogType( const char *pLogType ) +{ + int c = ARRAYSIZE( g_typeMap ); + for ( int i = 0; i < c; ++i ) + { + if ( !Q_stricmp( pLogType, g_typeMap[ i ].pLogType ) ) + return g_typeMap[ i ].pCurveInfoType; + } + return NULL; +} + +bool IsLogLayerType( const char *pLogLayerType ) +{ + int c = ARRAYSIZE( g_typeMap ); + for ( int i = 0; i < c; ++i ) + { + if ( !Q_stricmp( pLogLayerType, g_typeMap[ i ].pLogLayerType ) ) + return true; + } + return false; +} + +void MoveAttribute( CDmElement *pFromElement, const char *pFromAttrName, CDmElement *pToElement = NULL, const char *pToAttrName = NULL, DmAttributeType_t toType = AT_UNKNOWN ) +{ + if ( !pToAttrName ) + { + pToAttrName = pFromAttrName; + } + + if ( pToElement ) + { + CDmAttribute *pFromAttr = pFromElement->GetAttribute( pFromAttrName ); + const void *pValue = pFromAttr->GetValueUntyped(); + DmAttributeType_t fromType = pFromAttr->GetType(); + if ( toType == AT_UNKNOWN ) + { + toType = fromType; + } + + CDmAttribute *pToAttr = pToElement->AddAttribute( pToAttrName, toType ); + if ( !pToAttr ) + { + Warning( "*** Problem in converter encountered!\n" ); + Warning( "*** Unable to find or add attribute \"%s\" to element \"%s\"!\n", pToAttrName, pToElement->GetName() ); + } + else if ( fromType != toType ) + { + Warning( "*** Problem in file encountered!\n" ); + Warning( "*** Element \"%s\" has attribute \"%s\" with an unexpected type!\n", pFromElement->GetName(), pFromAttrName ); + } + else + { + pToAttr->SetValue( toType, pValue ); + } + } + + pFromElement->RemoveAttribute( pFromAttrName ); +} + +// Fixes up all elements +//----------------------------------------------------------------------------- +void CImportSFMV3::FixupElement( CDmElement *pElement ) +{ + if ( !pElement ) + return; + + const char *pType = pElement->GetTypeString(); + + // log layer + if ( IsLogLayerType( pType ) ) + { + pElement->RemoveAttribute( "ownerlog" ); + return; + } + + // log + const char *pCurveInfoType = GetCurveInfoTypeFromLogType( pType ); + if ( !pCurveInfoType ) + return; + + CDmElement *pCurveInfo = NULL; + CDmAttribute *pUseCurveTypeAttr = pElement->GetAttribute( "usecurvetypes" ); + if ( pUseCurveTypeAttr && pUseCurveTypeAttr->GetValue() ) + { + DmElementHandle_t hElement = g_pDataModel->CreateElement( "curve info", pCurveInfoType, pElement->GetFileId() ); + pCurveInfo = g_pDataModel->GetElement( hElement ); + } + pElement->RemoveAttribute( "usecurvetypes" ); + + MoveAttribute( pElement, "defaultcurvetype", pCurveInfo, "defaultCurveType", AT_INT ); + MoveAttribute( pElement, "defaultedgezerovalue",pCurveInfo, "defaultEdgeZeroValue" ); + MoveAttribute( pElement, "useedgeinfo", pCurveInfo, "useEdgeInfo", AT_BOOL ); + MoveAttribute( pElement, "rightedgetime", pCurveInfo, "rightEdgeTime", AT_INT ); + MoveAttribute( pElement, "left_edge_active", pCurveInfo, "leftEdgeActive", AT_BOOL ); + MoveAttribute( pElement, "right_edge_active", pCurveInfo, "rightEdgeActive", AT_BOOL ); + MoveAttribute( pElement, "left_edge_curvetype", pCurveInfo, "leftEdgeCurveType", AT_INT ); + MoveAttribute( pElement, "right_edge_curvetype",pCurveInfo, "rightEdgeCurveType", AT_INT ); + MoveAttribute( pElement, "left_edge_value", pCurveInfo, "leftEdgeValue" ); + MoveAttribute( pElement, "right_edge_value", pCurveInfo, "rightEdgeValue" ); +} + +// Fixes up all elements +//----------------------------------------------------------------------------- +void CImportSFMV3::BuildList( CDmElement *pElement, CUtlRBTree< CDmElement *, int >& list ) +{ + if ( !pElement ) + return; + + if ( list.Find( pElement ) != list.InvalidIndex() ) + return; + + list.Insert( pElement ); + + // Descend to bottom of tree, then do fixup coming back up the tree + for ( CDmAttribute *pAttribute = pElement->FirstAttribute(); pAttribute; pAttribute = pAttribute->NextAttribute() ) + { + if ( pAttribute->GetType() == AT_ELEMENT ) + { + CDmElement *pElementAt = pAttribute->GetValueElement( ); + BuildList( pElementAt, list ); + continue; + } + + if ( pAttribute->GetType() == AT_ELEMENT_ARRAY ) + { + CDmrElementArray<> array( pAttribute ); + int nCount = array.Count(); + for ( int i = 0; i < nCount; ++i ) + { + CDmElement *pChild = array[ i ]; + BuildList( pChild, list ); + } + continue; + } + } +} + +bool CImportSFMV3::DoFixup( CDmElement *pSourceRoot ) +{ + CUtlRBTree< CDmElement *, int > fixlist( 0, 0, DefLessFunc( CDmElement * ) ); + BuildList( pSourceRoot, fixlist ); + for ( int i = fixlist.FirstInorder(); i != fixlist.InvalidIndex() ; i = fixlist.NextInorder( i ) ) + { + // Search and replace in the entire tree! + FixupElement( fixlist[ i ] ); + } + return true; +} diff --git a/dmserializers/importsfmv4.cpp b/dmserializers/importsfmv4.cpp new file mode 100644 index 0000000..278fe12 --- /dev/null +++ b/dmserializers/importsfmv4.cpp @@ -0,0 +1,123 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= + +#include "dmserializers.h" +#include "dmebaseimporter.h" +#include "datamodel/idatamodel.h" +#include "datamodel/dmelement.h" +#include "tier1/KeyValues.h" +#include "tier1/utlbuffer.h" +#include "tier1/utlmap.h" +#include + + +//----------------------------------------------------------------------------- +// Format converter +//----------------------------------------------------------------------------- +class CImportSFMV4 : public CSFMBaseImporter +{ + typedef CSFMBaseImporter BaseClass; +public: + CImportSFMV4( char const *formatName, char const *nextFormatName ); + +private: + virtual bool DoFixup( CDmElement *pSourceRoot ); + + + void FixupElement( CDmElement *pElement ); + // Fixes up all elements + void BuildList( CDmElement *pElement, CUtlRBTree< CDmElement *, int >& list ); +}; + + +//----------------------------------------------------------------------------- +// Singleton instance +//----------------------------------------------------------------------------- +static CImportSFMV4 s_ImportSFMV4( "sfm_v4", "sfm_v5" ); + +void InstallSFMV4Importer( IDataModel *pFactory ) +{ + pFactory->AddLegacyUpdater( &s_ImportSFMV4 ); +} + + +//----------------------------------------------------------------------------- +// Constructor +//----------------------------------------------------------------------------- +CImportSFMV4::CImportSFMV4( char const *formatName, char const *nextFormatName ) : + BaseClass( formatName, nextFormatName ) +{ +} + +// Fixes up all elements +//----------------------------------------------------------------------------- +void CImportSFMV4::FixupElement( CDmElement *pElement ) +{ + if ( !pElement ) + return; + + const char *pType = pElement->GetTypeString(); + + if ( !V_stricmp( pType, "DmeCamera" ) ) + { + CDmAttribute *pOldToneMapScaleAttr = pElement->GetAttribute( "toneMapScale" ); + float fNewBloomScale = pOldToneMapScaleAttr->GetValue( ); + + Assert( !pElement->HasAttribute("bloomScale") ); + + CDmAttribute *pNewBloomScaleAttr = pElement->AddAttribute( "bloomScale", AT_FLOAT ); + pNewBloomScaleAttr->SetValue( fNewBloomScale ); + pOldToneMapScaleAttr->SetValue( 1.0f ); + } +} + +// Fixes up all elements +//----------------------------------------------------------------------------- +void CImportSFMV4::BuildList( CDmElement *pElement, CUtlRBTree< CDmElement *, int >& list ) +{ + if ( !pElement ) + return; + + if ( list.Find( pElement ) != list.InvalidIndex() ) + return; + + list.Insert( pElement ); + + // Descend to bottom of tree, then do fixup coming back up the tree + for ( CDmAttribute *pAttribute = pElement->FirstAttribute(); pAttribute; pAttribute = pAttribute->NextAttribute() ) + { + if ( pAttribute->GetType() == AT_ELEMENT ) + { + CDmElement *pElementAt = pAttribute->GetValueElement( ); + BuildList( pElementAt, list ); + continue; + } + + if ( pAttribute->GetType() == AT_ELEMENT_ARRAY ) + { + CDmrElementArray<> array( pAttribute ); + int nCount = array.Count(); + for ( int i = 0; i < nCount; ++i ) + { + CDmElement *pChild = array[ i ]; + BuildList( pChild, list ); + } + continue; + } + } +} + +bool CImportSFMV4::DoFixup( CDmElement *pSourceRoot ) +{ + CUtlRBTree< CDmElement *, int > fixlist( 0, 0, DefLessFunc( CDmElement * ) ); + BuildList( pSourceRoot, fixlist ); + for ( int i = fixlist.FirstInorder(); i != fixlist.InvalidIndex() ; i = fixlist.NextInorder( i ) ) + { + // Search and replace in the entire tree! + FixupElement( fixlist[ i ] ); + } + return true; +} diff --git a/dmserializers/importsfmv5.cpp b/dmserializers/importsfmv5.cpp new file mode 100644 index 0000000..3c3d6db --- /dev/null +++ b/dmserializers/importsfmv5.cpp @@ -0,0 +1,120 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= + +#include "dmserializers.h" +#include "dmebaseimporter.h" +#include "datamodel/idatamodel.h" +#include "datamodel/dmelement.h" +#include "tier1/KeyValues.h" +#include "tier1/utlbuffer.h" +#include "tier1/utlmap.h" +#include + + +//----------------------------------------------------------------------------- +// Format converter +//----------------------------------------------------------------------------- +class CImportSFMV5 : public CSFMBaseImporter +{ + typedef CSFMBaseImporter BaseClass; +public: + CImportSFMV5( char const *formatName, char const *nextFormatName ); + +private: + virtual bool DoFixup( CDmElement *pSourceRoot ); + + + void FixupElement( CDmElement *pElement ); + // Fixes up all elements + void BuildList( CDmElement *pElement, CUtlRBTree< CDmElement *, int >& list ); +}; + + +//----------------------------------------------------------------------------- +// Singleton instance +//----------------------------------------------------------------------------- +static CImportSFMV5 s_ImportSFMV5( "sfm_v5", "sfm_v6" ); + +void InstallSFMV5Importer( IDataModel *pFactory ) +{ + pFactory->AddLegacyUpdater( &s_ImportSFMV5 ); +} + + +//----------------------------------------------------------------------------- +// Constructor +//----------------------------------------------------------------------------- +CImportSFMV5::CImportSFMV5( char const *formatName, char const *nextFormatName ) : + BaseClass( formatName, nextFormatName ) +{ +} + + +//----------------------------------------------------------------------------- +// Fixes up all elements +//----------------------------------------------------------------------------- +void CImportSFMV5::FixupElement( CDmElement *pElement ) +{ + if ( !pElement ) + return; + + const char *pType = pElement->GetTypeString(); + + if ( !V_stricmp( pType, "DmeSpotLight" ) ) + { + pElement->SetType( "DmeProjectedLight" ); + } +} + + +//----------------------------------------------------------------------------- +// Fixes up all elements +//----------------------------------------------------------------------------- +void CImportSFMV5::BuildList( CDmElement *pElement, CUtlRBTree< CDmElement *, int >& list ) +{ + if ( !pElement ) + return; + + if ( list.Find( pElement ) != list.InvalidIndex() ) + return; + + list.Insert( pElement ); + + // Descend to bottom of tree, then do fixup coming back up the tree + for ( CDmAttribute *pAttribute = pElement->FirstAttribute(); pAttribute; pAttribute = pAttribute->NextAttribute() ) + { + if ( pAttribute->GetType() == AT_ELEMENT ) + { + CDmElement *pElementAt = pAttribute->GetValueElement( ); + BuildList( pElementAt, list ); + continue; + } + + if ( pAttribute->GetType() == AT_ELEMENT_ARRAY ) + { + CDmrElementArray<> array( pAttribute ); + int nCount = array.Count(); + for ( int i = 0; i < nCount; ++i ) + { + CDmElement *pChild = array[ i ]; + BuildList( pChild, list ); + } + continue; + } + } +} + +bool CImportSFMV5::DoFixup( CDmElement *pSourceRoot ) +{ + CUtlRBTree< CDmElement *, int > fixlist( 0, 0, DefLessFunc( CDmElement * ) ); + BuildList( pSourceRoot, fixlist ); + for ( int i = fixlist.FirstInorder(); i != fixlist.InvalidIndex() ; i = fixlist.NextInorder( i ) ) + { + // Search and replace in the entire tree! + FixupElement( fixlist[ i ] ); + } + return true; +} diff --git a/dmserializers/importsfmv6.cpp b/dmserializers/importsfmv6.cpp new file mode 100644 index 0000000..bcceaa1 --- /dev/null +++ b/dmserializers/importsfmv6.cpp @@ -0,0 +1,140 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= + +#include "dmserializers.h" +#include "dmebaseimporter.h" +#include "datamodel/idatamodel.h" +#include "datamodel/dmelement.h" +#include "tier1/KeyValues.h" +#include "tier1/utlbuffer.h" +#include "tier1/utlmap.h" +#include + + +//----------------------------------------------------------------------------- +// Format converter +//----------------------------------------------------------------------------- +class CImportSFMV6 : public CSFMBaseImporter +{ + typedef CSFMBaseImporter BaseClass; +public: + CImportSFMV6( char const *formatName, char const *nextFormatName ); + +private: + virtual bool DoFixup( CDmElement *pSourceRoot ); + + Quaternion DirectionToOrientation( const Vector &dir ); + + void FixupElement( CDmElement *pElement ); + // Fixes up all elements + void BuildList( CDmElement *pElement, CUtlRBTree< CDmElement *, int >& list ); +}; + + +//----------------------------------------------------------------------------- +// Singleton instance +//----------------------------------------------------------------------------- +static CImportSFMV6 s_ImportSFMV6( "sfm_v6", "sfm_v7" ); + +void InstallSFMV6Importer( IDataModel *pFactory ) +{ + pFactory->AddLegacyUpdater( &s_ImportSFMV6 ); +} + + +//----------------------------------------------------------------------------- +// Constructor +//----------------------------------------------------------------------------- +CImportSFMV6::CImportSFMV6( char const *formatName, char const *nextFormatName ) : + BaseClass( formatName, nextFormatName ) +{ +} + +Quaternion CImportSFMV6::DirectionToOrientation( const Vector &dir ) +{ + Vector up( 0, 0, 1 ); + Vector right = CrossProduct( dir, up ); + if ( right.IsLengthLessThan( 0.001f ) ) + { + up.Init( 1, 0, 0 ); + right = CrossProduct( dir, up ); + } + right.NormalizeInPlace(); + up = CrossProduct( right, dir ); + + Quaternion q; + BasisToQuaternion( dir, right, up, q ); + return q; +} + +//----------------------------------------------------------------------------- +// Fixes up all elements +//----------------------------------------------------------------------------- +void CImportSFMV6::FixupElement( CDmElement *pElement ) +{ + if ( !pElement ) + return; + + const char *pType = pElement->GetTypeString(); + + if ( !V_stricmp( pType, "DmeProjectedLight" ) ) + { + Vector vDir = pElement->GetValue( "direction" ); + pElement->RemoveAttribute( "direction" ); + Quaternion q = DirectionToOrientation( vDir ); + pElement->SetValue( "orientation", q ); + } +} + + +//----------------------------------------------------------------------------- +// Fixes up all elements +//----------------------------------------------------------------------------- +void CImportSFMV6::BuildList( CDmElement *pElement, CUtlRBTree< CDmElement *, int >& list ) +{ + if ( !pElement ) + return; + + if ( list.Find( pElement ) != list.InvalidIndex() ) + return; + + list.Insert( pElement ); + + // Descend to bottom of tree, then do fixup coming back up the tree + for ( CDmAttribute *pAttribute = pElement->FirstAttribute(); pAttribute; pAttribute = pAttribute->NextAttribute() ) + { + if ( pAttribute->GetType() == AT_ELEMENT ) + { + CDmElement *pElementAt = pAttribute->GetValueElement( ); + BuildList( pElementAt, list ); + continue; + } + + if ( pAttribute->GetType() == AT_ELEMENT_ARRAY ) + { + CDmrElementArray<> array( pAttribute ); + int nCount = array.Count(); + for ( int i = 0; i < nCount; ++i ) + { + CDmElement *pChild = array[ i ]; + BuildList( pChild, list ); + } + continue; + } + } +} + +bool CImportSFMV6::DoFixup( CDmElement *pSourceRoot ) +{ + CUtlRBTree< CDmElement *, int > fixlist( 0, 0, DefLessFunc( CDmElement * ) ); + BuildList( pSourceRoot, fixlist ); + for ( int i = fixlist.FirstInorder(); i != fixlist.InvalidIndex() ; i = fixlist.NextInorder( i ) ) + { + // Search and replace in the entire tree! + FixupElement( fixlist[ i ] ); + } + return true; +} diff --git a/dmserializers/importsfmv7.cpp b/dmserializers/importsfmv7.cpp new file mode 100644 index 0000000..a66bf18 --- /dev/null +++ b/dmserializers/importsfmv7.cpp @@ -0,0 +1,144 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= + +#include "dmserializers.h" +#include "dmebaseimporter.h" +#include "datamodel/idatamodel.h" +#include "datamodel/dmelement.h" +#include "tier1/KeyValues.h" +#include "tier1/utlbuffer.h" +#include "tier1/utlmap.h" +#include + + +//----------------------------------------------------------------------------- +// Format converter +//----------------------------------------------------------------------------- +class CImportSFMV7 : public CSFMBaseImporter +{ + typedef CSFMBaseImporter BaseClass; +public: + CImportSFMV7( char const *formatName, char const *nextFormatName ); + +private: + virtual bool DoFixup( CDmElement *pSourceRoot ); + + + void FixupElement( CDmElement *pElement ); + // Fixes up all elements + void BuildList( CDmElement *pElement, CUtlRBTree< CDmElement *, int >& list ); +}; + + +//----------------------------------------------------------------------------- +// Singleton instance +//----------------------------------------------------------------------------- +static CImportSFMV7 s_ImportSFMV7( "sfm_v7", "sfm_v8" ); + +void InstallSFMV7Importer( IDataModel *pFactory ) +{ + pFactory->AddLegacyUpdater( &s_ImportSFMV7 ); +} + + +//----------------------------------------------------------------------------- +// Constructor +//----------------------------------------------------------------------------- +CImportSFMV7::CImportSFMV7( char const *formatName, char const *nextFormatName ) : + BaseClass( formatName, nextFormatName ) +{ +} + + +//----------------------------------------------------------------------------- +// Fixes up all elements +//----------------------------------------------------------------------------- +void CImportSFMV7::FixupElement( CDmElement *pElement ) +{ + if ( !pElement ) + return; + + const char *pType = pElement->GetTypeString(); + + if ( !V_stricmp( pType, "DmeAnimationSet" ) ) + { + // Add a level of indirection in animation sets + // Modify the type of all controls from DmElement to DmeAnimationSetControl + CDmrElementArray<> srcPresets( pElement, "presets" ); + if ( srcPresets.IsValid() ) + { + CDmrElementArray<> presetGroupArray( pElement, "presetGroups", true ); + CDmElement *pPresetGroup = CreateElement< CDmElement >( "custom", pElement->GetFileId() ); + pPresetGroup->SetType( "DmePresetGroup" ); + CDmrElementArray<> presets( pPresetGroup, "presets", true ); + presetGroupArray.AddToTail( pPresetGroup ); + + int nCount = srcPresets.Count(); + for ( int i = 0; i < nCount; ++i ) + { + CDmElement *pPreset = srcPresets[i]; + if ( pPreset ) + { + pPreset->SetType( "DmePreset" ); + presets.AddToTail( pPreset ); + } + } + + srcPresets.RemoveAll(); + } + pElement->RemoveAttribute( "presets" ); + } +} + + +//----------------------------------------------------------------------------- +// Fixes up all elements +//----------------------------------------------------------------------------- +void CImportSFMV7::BuildList( CDmElement *pElement, CUtlRBTree< CDmElement *, int >& list ) +{ + if ( !pElement ) + return; + + if ( list.Find( pElement ) != list.InvalidIndex() ) + return; + + list.Insert( pElement ); + + // Descend to bottom of tree, then do fixup coming back up the tree + for ( CDmAttribute *pAttribute = pElement->FirstAttribute(); pAttribute; pAttribute = pAttribute->NextAttribute() ) + { + if ( pAttribute->GetType() == AT_ELEMENT ) + { + CDmElement *pElementAt = pAttribute->GetValueElement( ); + BuildList( pElementAt, list ); + continue; + } + + if ( pAttribute->GetType() == AT_ELEMENT_ARRAY ) + { + CDmrElementArray<> array( pAttribute ); + int nCount = array.Count(); + for ( int i = 0; i < nCount; ++i ) + { + CDmElement *pChild = array[ i ]; + BuildList( pChild, list ); + } + continue; + } + } +} + +bool CImportSFMV7::DoFixup( CDmElement *pSourceRoot ) +{ + CUtlRBTree< CDmElement *, int > fixlist( 0, 0, DefLessFunc( CDmElement * ) ); + BuildList( pSourceRoot, fixlist ); + for ( int i = fixlist.FirstInorder(); i != fixlist.InvalidIndex() ; i = fixlist.NextInorder( i ) ) + { + // Search and replace in the entire tree! + FixupElement( fixlist[ i ] ); + } + return true; +} diff --git a/dmserializers/importsfmv8.cpp b/dmserializers/importsfmv8.cpp new file mode 100644 index 0000000..dda78c8 --- /dev/null +++ b/dmserializers/importsfmv8.cpp @@ -0,0 +1,139 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= + +#include "dmserializers.h" +#include "dmebaseimporter.h" +#include "datamodel/idatamodel.h" +#include "datamodel/dmelement.h" +#include "tier1/KeyValues.h" +#include "tier1/utlbuffer.h" +#include "tier1/utlmap.h" +#include + + +//----------------------------------------------------------------------------- +// Format converter +//----------------------------------------------------------------------------- +class CImportSFMV8 : public CSFMBaseImporter +{ + typedef CSFMBaseImporter BaseClass; +public: + CImportSFMV8( const char *formatName, const char *nextFormatName ); + +private: + virtual bool DoFixup( CDmElement *pSourceRoot ); + + + void FixupElement( CDmElement *pElement ); + // Fixes up all elements + void BuildList( CDmElement *pElement, CUtlRBTree< CDmElement *, int >& list ); +}; + + +//----------------------------------------------------------------------------- +// Singleton instance +//----------------------------------------------------------------------------- +static CImportSFMV8 s_ImportSFMV8( "sfm_v8", "sfm_v9" ); + +void InstallSFMV8Importer( IDataModel *pFactory ) +{ + pFactory->AddLegacyUpdater( &s_ImportSFMV8 ); +} + + +//----------------------------------------------------------------------------- +// Constructor +//----------------------------------------------------------------------------- +CImportSFMV8::CImportSFMV8( const char *formatName, const char *nextFormatName ) : + BaseClass( formatName, nextFormatName ) +{ +} + + +//----------------------------------------------------------------------------- +// Fixes up all elements +//----------------------------------------------------------------------------- +void CImportSFMV8::FixupElement( CDmElement *pElement ) +{ + if ( !pElement ) + return; + + const char *pType = pElement->GetTypeString(); + + if ( !V_stricmp( pType, "DmeAnimationSet" ) ) + { + // Remove 'midpoint' from all controls, and + // Add 'defaultBalance' and 'defaultMultilevel' to all non-transform controls + CDmrElementArray<> srcControls( pElement, "controls" ); + if ( srcControls.IsValid() ) + { + int nCount = srcControls.Count(); + for ( int i = 0; i < nCount; ++i ) + { + CDmElement *pControl = srcControls[i]; + if ( pControl ) + { + if ( !pControl->GetValue( "transform" ) ) + { + pControl->InitValue( "defaultBalance", 0.5f ); + pControl->InitValue( "defaultMultilevel", 0.5f ); + pControl->RemoveAttribute( "midpoint" ); + } + } + } + } + } +} + + +//----------------------------------------------------------------------------- +// Fixes up all elements +//----------------------------------------------------------------------------- +void CImportSFMV8::BuildList( CDmElement *pElement, CUtlRBTree< CDmElement *, int >& list ) +{ + if ( !pElement ) + return; + + if ( list.Find( pElement ) != list.InvalidIndex() ) + return; + + list.Insert( pElement ); + + // Descend to bottom of tree, then do fixup coming back up the tree + for ( CDmAttribute *pAttribute = pElement->FirstAttribute(); pAttribute; pAttribute = pAttribute->NextAttribute() ) + { + if ( pAttribute->GetType() == AT_ELEMENT ) + { + CDmElement *pElementAt = pAttribute->GetValueElement( ); + BuildList( pElementAt, list ); + continue; + } + + if ( pAttribute->GetType() == AT_ELEMENT_ARRAY ) + { + CDmrElementArray<> array( pAttribute ); + int nCount = array.Count(); + for ( int i = 0; i < nCount; ++i ) + { + CDmElement *pChild = array[ i ]; + BuildList( pChild, list ); + } + continue; + } + } +} + +bool CImportSFMV8::DoFixup( CDmElement *pSourceRoot ) +{ + CUtlRBTree< CDmElement *, int > fixlist( 0, 0, DefLessFunc( CDmElement * ) ); + BuildList( pSourceRoot, fixlist ); + for ( int i = fixlist.FirstInorder(); i != fixlist.InvalidIndex() ; i = fixlist.NextInorder( i ) ) + { + // Search and replace in the entire tree! + FixupElement( fixlist[ i ] ); + } + return true; +} diff --git a/dmserializers/importsfmv9.cpp b/dmserializers/importsfmv9.cpp new file mode 100644 index 0000000..6a5fbf7 --- /dev/null +++ b/dmserializers/importsfmv9.cpp @@ -0,0 +1,142 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: fixed "color" attribute of lights to be of type Color, rather than Vector4 +// this should have been put in a *long* time ago, but I somehow missed creating the updater between 3 and 4 +// fortunately, since all updates happen on untyped elements, it's reasonably safe to do this out of order +// +//============================================================================= + +#include "dmserializers.h" +#include "dmebaseimporter.h" +#include "datamodel/idatamodel.h" +#include "datamodel/dmelement.h" +#include "tier1/utlbuffer.h" +#include "tier1/utlmap.h" +#include + + +//----------------------------------------------------------------------------- +// Format converter +//----------------------------------------------------------------------------- +class CImportSFMV9 : public CSFMBaseImporter +{ + typedef CSFMBaseImporter BaseClass; +public: + CImportSFMV9( const char *formatName, const char *nextFormatName ); + +private: + virtual bool DoFixup( CDmElement *pSourceRoot ); + + + void FixupElement( CDmElement *pElement ); + // Fixes up all elements + void BuildList( CDmElement *pElement, CUtlRBTree< CDmElement *, int >& list ); +}; + + +//----------------------------------------------------------------------------- +// Singleton instance +//----------------------------------------------------------------------------- +static CImportSFMV9 s_ImportSFMV9( "sfm_v9", "sfm_v10" ); + +void InstallSFMV9Importer( IDataModel *pFactory ) +{ + pFactory->AddLegacyUpdater( &s_ImportSFMV9 ); +} + + +//----------------------------------------------------------------------------- +// Constructor +//----------------------------------------------------------------------------- +CImportSFMV9::CImportSFMV9( const char *formatName, const char *nextFormatName ) : +BaseClass( formatName, nextFormatName ) +{ +} + + +//----------------------------------------------------------------------------- +// Fixes up all elements +//----------------------------------------------------------------------------- +void CImportSFMV9::FixupElement( CDmElement *pElement ) +{ + if ( !pElement ) + return; + + const char *pType = pElement->GetTypeString(); + if ( !V_stricmp( pType, "DmeLight" ) || + !V_stricmp( pType, "DmeDirectionalLight" ) || + !V_stricmp( pType, "DmeProjectedLight" ) || + !V_stricmp( pType, "DmePointLight" ) || + !V_stricmp( pType, "DmeSpotLight" ) || + !V_stricmp( pType, "DmeAmbientLight" ) ) + { + const CDmAttribute *pOldAttr = pElement->GetAttribute( "color", AT_VECTOR4 ); + if ( !pOldAttr ) + return; + + Color color; + + { // scoping this section of code since vecColor is invalid after RemoveAttribute + const Vector4D &vecColor = pOldAttr->GetValue< Vector4D >(); + for ( int i = 0; i < 4; ++i ) + { + color[ i ] = ( int )clamp( vecColor[ i ], 0.0f, 255.0f ); + } + + pElement->RemoveAttribute( "color" ); + } + + CDmAttribute *pNewAttr = pElement->AddAttribute( "color", AT_COLOR ); + pNewAttr->SetValue( color ); + } +} + + +//----------------------------------------------------------------------------- +// Fixes up all elements +//----------------------------------------------------------------------------- +void CImportSFMV9::BuildList( CDmElement *pElement, CUtlRBTree< CDmElement *, int >& list ) +{ + if ( !pElement ) + return; + + if ( list.Find( pElement ) != list.InvalidIndex() ) + return; + + list.Insert( pElement ); + + // Descend to bottom of tree, then do fixup coming back up the tree + for ( CDmAttribute *pAttribute = pElement->FirstAttribute(); pAttribute; pAttribute = pAttribute->NextAttribute() ) + { + if ( pAttribute->GetType() == AT_ELEMENT ) + { + CDmElement *pElementAt = pAttribute->GetValueElement( ); + BuildList( pElementAt, list ); + continue; + } + + if ( pAttribute->GetType() == AT_ELEMENT_ARRAY ) + { + CDmrElementArray<> array( pAttribute ); + int nCount = array.Count(); + for ( int i = 0; i < nCount; ++i ) + { + CDmElement *pChild = array[ i ]; + BuildList( pChild, list ); + } + continue; + } + } +} + +bool CImportSFMV9::DoFixup( CDmElement *pSourceRoot ) +{ + CUtlRBTree< CDmElement *, int > fixlist( 0, 0, DefLessFunc( CDmElement * ) ); + BuildList( pSourceRoot, fixlist ); + for ( int i = fixlist.FirstInorder(); i != fixlist.InvalidIndex() ; i = fixlist.NextInorder( i ) ) + { + // Search and replace in the entire tree! + FixupElement( fixlist[ i ] ); + } + return true; +} diff --git a/dmserializers/importvmf.cpp b/dmserializers/importvmf.cpp new file mode 100644 index 0000000..7f57c6d --- /dev/null +++ b/dmserializers/importvmf.cpp @@ -0,0 +1,629 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= + +#include "importkeyvaluebase.h" +#include "dmserializers.h" +#include "datamodel/idatamodel.h" +#include "datamodel/dmelement.h" +#include "tier1/KeyValues.h" +#include "tier1/utlbuffer.h" +#include "datamodel/dmattribute.h" + + +//----------------------------------------------------------------------------- +// Serialization class for VMF files (map files) +//----------------------------------------------------------------------------- +class CImportVMF : public CImportKeyValueBase +{ +public: + virtual const char *GetName() const { return "vmf"; } + virtual const char *GetDescription() const { return "Valve Map File"; } + virtual int GetCurrentVersion() const { return 0; } // doesn't store a version + + bool Serialize( CUtlBuffer &outBuf, CDmElement *pRoot ); + CDmElement* UnserializeFromKeyValues( KeyValues *pKeyValues ); + +private: + // Reads a single entity + bool UnserializeEntityKey( CDmAttribute *pEntities, KeyValues *pKeyValues ); + + // Reads entity editor keys + bool UnserializeEntityEditorKey( CDmAttribute *pEditor, KeyValues *pKeyValues ); + + // Reads keys that we currently do nothing with + bool UnserializeUnusedKeys( DmElementHandle_t hOther, KeyValues *pKeyValues ); + + // Writes out all everything other than entities + bool SerializeOther( CUtlBuffer &buf, CDmAttribute *pOther, const char **ppFilter = 0 ); + + // Writes out all entities + bool SerializeEntities( CUtlBuffer &buf, CDmAttribute *pEntities ); + + // Writes out a single attribute recursively + bool SerializeAttribute( CUtlBuffer &buf, CDmAttribute *pAttribute, bool bElementArrays ); + + // Writes entity editor keys + bool SerializeEntityEditorKey( CUtlBuffer &buf, DmElementHandle_t hEditor ); + + // Updates the max hammer id + void UpdateMaxHammerId( KeyValues *pKeyValue ); + + // Max id read from the file + int m_nMaxHammerId; +}; + + +//----------------------------------------------------------------------------- +// Singleton instance +//----------------------------------------------------------------------------- +static CImportVMF s_ImportVMF; + +void InstallVMFImporter( IDataModel *pFactory ) +{ + pFactory->AddSerializer( &s_ImportVMF ); +} + + +//----------------------------------------------------------------------------- +// Deals with poorly-named key values for the DME system +//----------------------------------------------------------------------------- +static const char *s_pKeyRemapNames[][2] = +{ + { "id", "__id" }, + { "name", "__name" }, + { "type", "__type" }, + { NULL, NULL }, +}; + + +//----------------------------------------------------------------------------- +// Gets remap name for unserialization/serailzation +//----------------------------------------------------------------------------- +static const char *GetRemapName( const char *pName, bool bSerialization ) +{ + for ( int i = 0; s_pKeyRemapNames[i][0]; ++i ) + { + if ( !Q_stricmp( pName, s_pKeyRemapNames[i][bSerialization] ) ) + return s_pKeyRemapNames[i][1 - bSerialization]; + } + return pName; +} + + +//----------------------------------------------------------------------------- +// Writes out a single attribute recursively +//----------------------------------------------------------------------------- +bool CImportVMF::SerializeAttribute( CUtlBuffer &buf, CDmAttribute *pAttribute, bool bElementArrays ) +{ + if ( pAttribute->IsFlagSet( FATTRIB_STANDARD | FATTRIB_DONTSAVE ) ) + return true; + + const char *pFieldName = GetRemapName( pAttribute->GetName(), true ); + if ( !Q_stricmp( pFieldName, "editorType" ) ) + return true; + + if ( !IsArrayType( pAttribute->GetType() ) ) + { + if ( !bElementArrays ) + { + buf.Printf( "\"%s\" ", pFieldName ); + if ( pAttribute->GetType() != AT_STRING ) + { + buf.Printf( "\"" ); + } + g_pDataModel->SetSerializationDelimiter( GetCStringCharConversion() ); + pAttribute->Serialize( buf ); + g_pDataModel->SetSerializationDelimiter( NULL ); + if ( pAttribute->GetType() != AT_STRING ) + { + buf.Printf( "\"" ); + } + buf.Printf( "\n" ); + } + } + else + { + if ( bElementArrays ) + { + Assert( pAttribute->GetType() == AT_ELEMENT_ARRAY ); + if ( !SerializeOther( buf, pAttribute ) ) + return false; + } + } + + return true; +} + + +//----------------------------------------------------------------------------- +// Writes out all everything other than entities +//----------------------------------------------------------------------------- +bool CImportVMF::SerializeOther( CUtlBuffer &buf, CDmAttribute *pOther, const char **ppFilter ) +{ + CDmrElementArray<> array( pOther ); + int nCount = array.Count(); + for ( int i = 0; i < nCount; ++i ) + { + CDmElement *pElement = array[i]; + const char *pElementName = pElement->GetName(); + if ( ppFilter ) + { + int j; + for ( j = 0; ppFilter[j]; ++j ) + { + if ( !Q_stricmp( pElementName, ppFilter[j] ) ) + break; + } + + if ( !ppFilter[j] ) + continue; + } + + int nLen = Q_strlen( pElementName ) + 1; + char *pTemp = (char*)_alloca( nLen ); + Q_strncpy( pTemp, pElementName, nLen ); + Q_strlower( pTemp ); + buf.Printf( "%s\n", pTemp ); + buf.Printf( "{\n" ); + buf.PushTab(); + + // Normal attributes first + for( CDmAttribute *pAttribute = pElement->FirstAttribute(); pAttribute; pAttribute = pAttribute->NextAttribute() ) + { + if ( !SerializeAttribute( buf, pAttribute, false ) ) + return false; + } + + // Subkeys later + for( CDmAttribute *pAttribute = pElement->FirstAttribute(); pAttribute; pAttribute = pAttribute->NextAttribute() ) + { + if ( !SerializeAttribute( buf, pAttribute, true ) ) + return false; + } + + buf.PopTab(); + buf.Printf( "}\n" ); + } + + return true; +} + + +//----------------------------------------------------------------------------- +// Writes entity editor keys +//----------------------------------------------------------------------------- +bool CImportVMF::SerializeEntityEditorKey( CUtlBuffer &buf, DmElementHandle_t hEditor ) +{ + CDmElement *pEditorElement = g_pDataModel->GetElement( hEditor ); + if ( !pEditorElement ) + return true; + + buf.Printf( "editor\n" ); + buf.Printf( "{\n" ); + buf.PushTab(); + + { + CDmAttribute *pAttribute = pEditorElement->GetAttribute( "color" ); + if ( pAttribute ) + { + Color c = pAttribute->GetValue(); + buf.Printf( "\"color\" \"%d %d %d\"\n", c.r(), c.g(), c.b() ); + } + } + PrintIntAttribute( pEditorElement, buf, "id" ); // FIXME - id is a DmObjectId_t!!! This should never print anything! + PrintStringAttribute( pEditorElement, buf, "comments" ); + PrintBoolAttribute( pEditorElement, buf, "visgroupshown" ); + PrintBoolAttribute( pEditorElement, buf, "visgroupautoshown" ); + + for ( CDmAttribute *pAttribute = pEditorElement->FirstAttribute(); pAttribute != NULL; pAttribute = pAttribute->NextAttribute() ) + { + if ( pAttribute->IsFlagSet( FATTRIB_STANDARD | FATTRIB_DONTSAVE ) ) + continue; + + const char *pKeyName = pAttribute->GetName(); + if ( Q_stricmp( pKeyName, "color" ) && Q_stricmp( pKeyName, "id" ) && + Q_stricmp( pKeyName, "comments" ) && Q_stricmp( pKeyName, "visgroupshown" ) && + Q_stricmp( pKeyName, "visgroupautoshown" ) ) + { + PrintStringAttribute( pEditorElement, buf, pKeyName ); + } + } + + buf.PopTab(); + buf.Printf( "}\n" ); + + return true; +} + + +//----------------------------------------------------------------------------- +// Writes out all entities +//----------------------------------------------------------------------------- +bool CImportVMF::SerializeEntities( CUtlBuffer &buf, CDmAttribute *pEntities ) +{ + // FIXME: Make this serialize in the order in which it appears in the FGD + // to minimize diffs + CDmrElementArray<> array( pEntities ); + + int nCount = array.Count(); + for ( int i = 0; i < nCount; ++i ) + { + CDmElement *pElement = array[i]; + buf.Printf( "entity\n" ); + buf.Printf( "{\n" ); + buf.PushTab(); + buf.Printf( "\"id\" \"%s\"\n", pElement->GetName() ); + + for( CDmAttribute *pAttribute = pElement->FirstAttribute(); pAttribute; pAttribute = pAttribute->NextAttribute() ) + { + // Do 'editor' at the end to preserve ordering and not make terrible diffs + if ( !Q_stricmp( pAttribute->GetName(), "editor" ) ) + continue; + + if ( !SerializeAttribute( buf, pAttribute, false ) ) + return false; + } + + for( CDmAttribute *pAttribute = pElement->FirstAttribute(); pAttribute; pAttribute = pAttribute->NextAttribute() ) + { + // Do 'editor' at the end to preserve ordering and not make terrible diffs + if ( !Q_stricmp( pAttribute->GetName(), "editor" ) ) + continue; + + if ( !SerializeAttribute( buf, pAttribute, true ) ) + return false; + } + + // Do the 'editor' + CDmAttribute *pEditor = pElement->GetAttribute( "editor" ); + if ( pEditor ) + { + SerializeEntityEditorKey( buf, pEditor->GetValue() ); + } + + buf.PopTab(); + buf.Printf( "}\n" ); + } + + return true; +} + + +//----------------------------------------------------------------------------- +// Writes out a new VMF file +//----------------------------------------------------------------------------- +bool CImportVMF::Serialize( CUtlBuffer &buf, CDmElement *pRoot ) +{ + // This is done in this strange way (namely, serializing other twice) to minimize diffs + const char *pOtherFilter1[] = + { + "versioninfo", "visgroups", "viewsettings", "world", NULL + }; + + const char *pOtherFilter2[] = + { + "cameras", "cordon", "hidden", NULL + }; + + CDmAttribute *pOther = pRoot->GetAttribute( "other" ); + if ( pOther && pOther->GetType() == AT_ELEMENT_ARRAY ) + { + if ( !SerializeOther( buf, pOther, pOtherFilter1 ) ) + return false; + } + + // Serialize entities + CDmAttribute *pEntities = pRoot->GetAttribute( "entities" ); + if ( pEntities && pEntities->GetType() == AT_ELEMENT_ARRAY ) + { + if ( !SerializeEntities( buf, pEntities ) ) + return false; + } + + if ( pOther && pOther->GetType() == AT_ELEMENT_ARRAY ) + { + if ( !SerializeOther( buf, pOther, pOtherFilter2 ) ) + return false; + } + + return true; +} + + +//----------------------------------------------------------------------------- +// Updates the max hammer id +//----------------------------------------------------------------------------- +void CImportVMF::UpdateMaxHammerId( KeyValues *pField ) +{ + if ( !Q_stricmp( pField->GetName(), "id" ) ) + { + int nId = atoi( pField->GetString() ); + if ( nId > m_nMaxHammerId ) + { + m_nMaxHammerId = nId; + } + } +} + + +//----------------------------------------------------------------------------- +// Reads entity editor keys +//----------------------------------------------------------------------------- +bool CImportVMF::UnserializeEntityEditorKey( CDmAttribute *pEditorAttribute, KeyValues *pKeyValues ) +{ + CDmElement *pEditor; + DmElementHandle_t hEditor = pEditorAttribute->GetValue(); + if ( hEditor == DMELEMENT_HANDLE_INVALID ) + { + pEditor = CreateDmElement( "DmElement", "editor", NULL );; + if ( !pEditor ) + return false; + hEditor = pEditor->GetHandle(); + pEditorAttribute->SetValue( hEditor ); + } + else + { + pEditor = g_pDataModel->GetElement( hEditor ); + } + + int r, g, b; + if ( sscanf( pKeyValues->GetString( "color", "" ), "%d %d %d", &r, &g, &b ) == 3 ) + { + Color c( r, g, b, 255 ); + if ( !pEditor->SetValue( "color", c ) ) + return false; + } + KeyValues *pIdKey = pKeyValues->FindKey( "id" ); + if ( pIdKey ) + { + UpdateMaxHammerId( pIdKey ); + } + AddIntAttribute( pEditor, pKeyValues, "id" ); + AddStringAttribute( pEditor, pKeyValues, "comments" ); + AddBoolAttribute( pEditor, pKeyValues, "visgroupshown" ); + AddBoolAttribute( pEditor, pKeyValues, "visgroupautoshown" ); + + for ( KeyValues *pUserKey = pKeyValues->GetFirstValue(); pUserKey != NULL; pUserKey = pUserKey->GetNextValue() ) + { + const char *pKeyName = pUserKey->GetName(); + if ( Q_stricmp( pKeyName, "color" ) && Q_stricmp( pKeyName, "id" ) && + Q_stricmp( pKeyName, "comments" ) && Q_stricmp( pKeyName, "visgroupshown" ) && + Q_stricmp( pKeyName, "visgroupautoshown" ) ) + { + AddStringAttribute( pEditor, pKeyValues, pKeyName ); + } + } + + return true; +} + + +//----------------------------------------------------------------------------- +// Reads a single entity +//----------------------------------------------------------------------------- +bool CImportVMF::UnserializeEntityKey( CDmAttribute *pEntities, KeyValues *pKeyValues ) +{ + CDmElement *pEntity = CreateDmElement( "DmeVMFEntity", pKeyValues->GetString( "id", "-1" ), NULL ); + if ( !pEntity ) + return false; + + CDmrElementArray<> array( pEntities ); + array.AddToTail( pEntity ); + + // Each act busy needs to have an editortype associated with it so it displays nicely in editors + pEntity->SetValue( "editorType", "vmfEntity" ); + + const char *pClassName = pKeyValues->GetString( "classname", NULL ); + if ( !pClassName ) + return false; + + // Read the actual fields + for ( KeyValues *pField = pKeyValues->GetFirstValue(); pField != NULL; pField = pField->GetNextValue() ) + { + // FIXME: Knowing the FGD here would be useful for type determination. + // Look up the field by name based on class name + // In the meantime, just use the keyvalues type? + char pFieldName[512]; + Q_strncpy( pFieldName, pField->GetName(), sizeof(pFieldName) ); + Q_strlower( pFieldName ); + + // Don't do id: it's used as the name + // Not to mention it's a protected name + if ( !Q_stricmp( pFieldName, "id" ) ) + { + UpdateMaxHammerId( pField ); + continue; + } + + // Type, name, and editortype are protected names + Assert( Q_stricmp( pFieldName, "type" ) && Q_stricmp( pFieldName, "name" ) && Q_stricmp( pFieldName, "editortype" ) ); + + switch( pField->GetDataType() ) + { + case KeyValues::TYPE_INT: + if ( !AddIntAttributeFlags( pEntity, pKeyValues, pFieldName, FATTRIB_USERDEFINED ) ) + return false; + break; + + case KeyValues::TYPE_FLOAT: + if ( !AddFloatAttributeFlags( pEntity, pKeyValues, pFieldName, FATTRIB_USERDEFINED ) ) + return false; + break; + + case KeyValues::TYPE_STRING: + { + const char* pString = pField->GetString(); + if (!pString || !pString[0]) + return false; + + // Look for vectors + Vector4D v; + if ( sscanf( pString, "%f %f %f %f", &v.x, &v.y, &v.z, &v.w ) == 4 ) + { + if ( !pEntity->SetValue( pFieldName, v ) ) + return false; + CDmAttribute *pAttribute = pEntity->GetAttribute( pFieldName ); + pAttribute->AddFlag( FATTRIB_USERDEFINED ); + } + else if ( sscanf( pString, "%f %f %f", &v.x, &v.y, &v.z ) == 3 ) + { + if ( !pEntity->SetValue( pFieldName, v.AsVector3D() ) ) + { + QAngle ang( v.x, v.y, v.z ); + if ( !pEntity->SetValue( pFieldName, ang ) ) + return false; + } + CDmAttribute *pAttribute = pEntity->GetAttribute( pFieldName ); + pAttribute->AddFlag( FATTRIB_USERDEFINED ); + } + else + { + if ( !AddStringAttributeFlags( pEntity, pKeyValues, pFieldName, FATTRIB_USERDEFINED ) ) + return false; + } + } + break; + } + } + + // Read the subkeys + CDmAttribute *pEditor = pEntity->AddAttribute( "editor", AT_ELEMENT ); + CDmrElementArray<> otherKeys( pEntity->AddAttribute( "other", AT_ELEMENT_ARRAY ) ); + for ( KeyValues *pSubKey = pKeyValues->GetFirstTrueSubKey(); pSubKey != NULL; pSubKey = pSubKey->GetNextTrueSubKey() ) + { + bool bOk = false; + if ( !Q_stricmp( pSubKey->GetName(), "editor" ) ) + { + bOk = UnserializeEntityEditorKey( pEditor, pSubKey ); + } + else + { + // We don't currently do anything with the other keys + CDmElement *pOther = CreateDmElement( "DmElement", pSubKey->GetName(), NULL ); + otherKeys.AddToTail( pOther ); + bOk = UnserializeUnusedKeys( pOther->GetHandle(), pSubKey ); + } + + if ( !bOk ) + return false; + } + + return true; +} + + +//----------------------------------------------------------------------------- +// Reads keys that we currently do nothing with +//----------------------------------------------------------------------------- +bool CImportVMF::UnserializeUnusedKeys( DmElementHandle_t hOther, KeyValues *pKeyValues ) +{ + CDmElement *pOther = g_pDataModel->GetElement( hOther ); + + // Read the actual fields + for ( KeyValues *pField = pKeyValues->GetFirstValue(); pField != NULL; pField = pField->GetNextValue() ) + { + UpdateMaxHammerId( pField ); + const char *pFieldName = GetRemapName( pField->GetName(), false ); + pOther->SetValue( pFieldName, pField->GetString() ); + } + + // Read the subkeys + CDmrElementArray<> subKeys( pOther->AddAttribute( "subkeys", AT_ELEMENT_ARRAY ) ); + for ( KeyValues *pSubKey = pKeyValues->GetFirstTrueSubKey(); pSubKey != NULL; pSubKey = pSubKey->GetNextTrueSubKey() ) + { + CDmElement *pSubElement = CreateDmElement( "DmElement", pSubKey->GetName(), NULL ); + subKeys.AddToTail( pSubElement ); + if ( !UnserializeUnusedKeys( pSubElement->GetHandle(), pSubKey ) ) + return false; + } + return true; +} + + +/* +//----------------------------------------------------------------------------- +// Reads the cordon data +//----------------------------------------------------------------------------- +bool CImportVMF::UnserializeCordonKey( IDmAttributeElement *pCordon, KeyValues *pKeyValues ) +{ + DmElementHandle_t hCordon = pCordon->GetValue().Get(); + if ( hCordon == DMELEMENT_HANDLE_INVALID ) + { + hCordon = CreateDmElement( "DmElement", "cordon", NULL ); + if ( hCordon == DMELEMENT_HANDLE_INVALID ) + return false; + pCordon->SetValue( hCordon ); + } + + AddBoolAttribute( hCordon, pKeyValues, "active" ); + + Vector v; + if ( sscanf( pKeyValues->GetString( "mins", "" ), "(%f %f %f)", &v.x, &v.y, &v.z ) == 3 ) + { + if ( !DmElementAddAttribute( hCordon, "mins", v ) ) + return false; + } + if ( sscanf( pKeyValues->GetString( "maxs", "" ), "(%f %f %f)", &v.x, &v.y, &v.z ) == 3 ) + { + if ( !DmElementAddAttribute( hCordon, "maxs", v ) ) + return false; + } + return true; +} +*/ + + +//----------------------------------------------------------------------------- +// Main entry point for the unserialization +//----------------------------------------------------------------------------- +CDmElement* CImportVMF::UnserializeFromKeyValues( KeyValues *pKeyValues ) +{ + m_nMaxHammerId = 0; + + // Create the main element + CDmElement *pElement = CreateDmElement( "DmElement", "VMF", NULL ); + if ( !pElement ) + return NULL; + + // Each vmf needs to have an editortype associated with it so it displays nicely in editors + pElement->SetValue( "editorType", "VMF" ); + + // The VMF is a series of keyvalue blocks; either + // 'entity', 'cameras', 'cordon', 'world', 'versioninfo', or 'viewsettings' + CDmAttribute *pEntityArray = pElement->AddAttribute( "entities", AT_ELEMENT_ARRAY ); + + // All main keys are root keys + CDmrElementArray<> otherKeys( pElement->AddAttribute( "other", AT_ELEMENT_ARRAY ) ); + for ( ; pKeyValues != NULL; pKeyValues = pKeyValues->GetNextKey() ) + { + bool bOk = false; + if ( !Q_stricmp( pKeyValues->GetName(), "entity" ) ) + { + bOk = UnserializeEntityKey( pEntityArray, pKeyValues ); + } + else + { + // We don't currently do anything with + CDmElement *pOther = CreateDmElement( "DmElement", pKeyValues->GetName(), NULL ); + otherKeys.AddToTail( pOther ); + bOk = UnserializeUnusedKeys( pOther->GetHandle(), pKeyValues ); + } + + if ( !bOk ) + { + Warning( "Error importing VMF element %s\n", pKeyValues->GetName() ); + return NULL; + } + } + + // Resolve all element references recursively + RecursivelyResolveElement( pElement ); + + // Add the max id read in from the file to the root entity + pElement->SetValue( "maxHammerId", m_nMaxHammerId ); + + return pElement; +} diff --git a/dmserializers/importvmt.cpp b/dmserializers/importvmt.cpp new file mode 100644 index 0000000..bd29a52 --- /dev/null +++ b/dmserializers/importvmt.cpp @@ -0,0 +1,738 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= + +#include "importkeyvaluebase.h" +#include "dmserializers.h" +#include "datamodel/idatamodel.h" +#include "datamodel/dmelement.h" +#include "tier1/KeyValues.h" +#include "tier1/utlbuffer.h" +#include "datamodel/dmattribute.h" +#include "filesystem.h" +#include "tier2/tier2.h" + + +//----------------------------------------------------------------------------- +// Serialization class for Key Values +//----------------------------------------------------------------------------- +class CImportVMT : public CImportKeyValueBase +{ +public: + virtual const char *GetName() const { return "vmt"; } + virtual const char *GetDescription() const { return "Valve Material File"; } + virtual int GetCurrentVersion() const { return 0; } // doesn't store a version + + bool Serialize( CUtlBuffer &outBuf, CDmElement *pRoot ); + CDmElement* UnserializeFromKeyValues( KeyValues *pKeyValues ); + +private: + // Unserialize fallbacks + bool UnserializeFallbacks( CDmElement *pRoot, KeyValues *pFallbackKeyValues ); + + // Unserialize proxies + bool UnserializeProxies( CDmElement *pRoot, KeyValues *pKeyValues ); + + // Creates a shader parameter from a key value + bool UnserializeShaderParam( CDmElement *pRoot, KeyValues* pKeyValue ); + + // Creates a matrix material var + bool CreateMatrixMaterialVarFromKeyValue( CDmElement *pRoot, const char *pParamName, const char *pString ); + + // Creates a vector shader parameter + bool CreateVectorMaterialVarFromKeyValue( CDmElement *pRoot, const char *pParamName, const char *pString ); + + // Writes out a single shader parameter + bool SerializeShaderParameter( CUtlBuffer &buf, CDmAttribute *pAttribute ); + + // Writes out all shader parameters + bool SerializeShaderParameters( CUtlBuffer &buf, CDmElement *pRoot ); + + // Writes out all shader fallbacks + bool SerializeFallbacks( CUtlBuffer &buf, CDmElement *pRoot ); + + // Writes out all material proxies + bool SerializeProxies( CUtlBuffer &buf, CDmElement *pRoot ); + + // Handle patch files + void ExpandPatchFile( KeyValues *pKeyValues ); +}; + + +//----------------------------------------------------------------------------- +// Singleton instance +//----------------------------------------------------------------------------- +static CImportVMT s_ImportVMT; + +void InstallVMTImporter( IDataModel *pFactory ) +{ + pFactory->AddSerializer( &s_ImportVMT ); +} + + +//----------------------------------------------------------------------------- +// Writes out a single shader parameter +//----------------------------------------------------------------------------- +bool CImportVMT::SerializeShaderParameter( CUtlBuffer &buf, CDmAttribute *pAttribute ) +{ + // We have a shader parameter at this point. + switch ( pAttribute->GetType() ) + { + case AT_INT: + buf.Printf( "\"%s\" \"%d\"\n", pAttribute->GetName(), pAttribute->GetValue( ) ); + break; + + case AT_BOOL: + buf.Printf( "\"%s\" \"%d\"\n", pAttribute->GetName(), pAttribute->GetValue( ) ); + break; + + case AT_FLOAT: + buf.Printf( "\"%s\" \"%f\"\n", pAttribute->GetName(), pAttribute->GetValue( ) ); + break; + + case AT_STRING: + buf.Printf( "\"%s\" \"%s\"\n", pAttribute->GetName(), pAttribute->GetValue( ).Get() ); + break; + + case AT_VECTOR2: + { + const Vector2D &vec = pAttribute->GetValue( ); + buf.Printf( "\"%s\" \"[ %f %f ]\"\n", pAttribute->GetName(), vec.x, vec.y ); + } + break; + + case AT_VECTOR3: + { + const Vector &vec = pAttribute->GetValue( ); + buf.Printf( "\"%s\" \"[ %f %f %f ]\"\n", pAttribute->GetName(), vec.x, vec.y, vec.z ); + } + break; + + case AT_VECTOR4: + { + const Vector4D &vec = pAttribute->GetValue( ); + buf.Printf( "\"%s\" \"[ %f %f %f %f ]\"\n", pAttribute->GetName(), vec.x, vec.y, vec.z, vec.w ); + } + break; + + case AT_COLOR: + { + // NOTE: VMTs only support 3 component color (no alpha) + const Color &color = pAttribute->GetValue( ); + buf.Printf( "\"%s\" \"{ %d %d %d }\"\n", pAttribute->GetName(), color.r(), color.g(), color.b() ); + } + break; + + case AT_VMATRIX: + { + const VMatrix &mat = pAttribute->GetValue( ); + buf.Printf( "\"%s\" \"[ %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f ]\"\n", pAttribute->GetName(), + mat[0][0], mat[0][1], mat[0][2], mat[0][3], + mat[1][0], mat[1][1], mat[1][2], mat[1][3], + mat[2][0], mat[2][1], mat[2][2], mat[2][3], + mat[3][0], mat[3][1], mat[3][2], mat[3][3] ); + } + break; + + default: + Warning( "Attempted to serialize an unsupported shader parameter type %s (%s)\n", + pAttribute->GetName(), g_pDataModel->GetAttributeNameForType( pAttribute->GetType() ) ); + return false; + } + + return true; +} + + +//----------------------------------------------------------------------------- +// Writes out all shader parameters +//----------------------------------------------------------------------------- +bool CImportVMT::SerializeShaderParameters( CUtlBuffer &buf, CDmElement *pRoot ) +{ + for ( CDmAttribute *pAttribute = pRoot->FirstAttribute(); pAttribute; pAttribute = pAttribute->NextAttribute() ) + { + // Skip the standard attributes + if ( pAttribute->IsFlagSet( FATTRIB_STANDARD ) ) + continue; + + // Skip the shader name + const char *pName = pAttribute->GetName(); + if ( !Q_stricmp( pAttribute->GetName(), "shader" ) ) + continue; + + // Names that don't start with a $ or a % are not shader parameters + if ( pName[0] != '$' && pName[0] != '%' ) + continue; + + // Skip element array children; we'll handle them separately. + if ( pAttribute->GetType() == AT_ELEMENT_ARRAY ) + continue; + + // Write out the shader parameter + if ( !SerializeShaderParameter( buf, pAttribute ) ) + return false; + } + return true; +} + + +//----------------------------------------------------------------------------- +// Writes out all shader fallbacks +//----------------------------------------------------------------------------- +bool CImportVMT::SerializeFallbacks( CUtlBuffer &buf, CDmElement *pRoot ) +{ + if ( !pRoot->HasAttribute( "fallbacks" ) ) + return true; + + CDmAttribute *pFallbacks = pRoot->GetAttribute( "fallbacks" ); + if ( pFallbacks->GetType() != AT_ELEMENT_ARRAY ) + return false; + + CDmrElementArray<> array( pFallbacks ); + int nCount = array.Count(); + for ( int i = 0; i < nCount; ++i ) + { + CDmElement *pFallback = array[i]; + Assert( pFallback ); + + PrintStringAttribute( pFallback, buf, "shader", false, true ); + buf.Printf( "{\n" ); + buf.PushTab(); + if ( !SerializeShaderParameters( buf, pFallback ) ) + return false; + buf.PopTab(); + buf.Printf( "}\n" ); + } + return true; +} + + +//----------------------------------------------------------------------------- +// Writes out all material proxies +//----------------------------------------------------------------------------- +bool CImportVMT::SerializeProxies( CUtlBuffer &buf, CDmElement *pRoot ) +{ + if ( !pRoot->HasAttribute( "proxies" ) ) + return true; + + CDmAttribute *pProxies = pRoot->GetAttribute( "proxies" ); + if ( pProxies->GetType() != AT_ELEMENT_ARRAY ) + return false; + + CDmrElementArray<> array( pProxies ); + int nCount = array.Count(); + if ( nCount == 0 ) + return true; + + buf.Printf( "\"Proxies\"\n" ); + buf.Printf( "{\n" ); + buf.PushTab(); + for ( int i = 0; i < nCount; ++i ) + { + CDmElement *pProxy = array[i]; + Assert( pProxy ); + + PrintStringAttribute( pProxy, buf, "proxyType", false, true ); + buf.Printf( "{\n" ); + buf.PushTab(); + if ( !SerializeShaderParameters( buf, pProxy ) ) + return false; + buf.PopTab(); + buf.Printf( "}\n" ); + } + buf.PopTab(); + buf.Printf( "}\n" ); + return true; +} + + +//----------------------------------------------------------------------------- +// Writes out a new vmt file +//----------------------------------------------------------------------------- +bool CImportVMT::Serialize( CUtlBuffer &buf, CDmElement *pRoot ) +{ + PrintStringAttribute( pRoot, buf, "shader", false, true ); + buf.Printf( "{\n" ); + buf.PushTab(); + + if ( !SerializeShaderParameters( buf, pRoot ) ) + return false; + + if ( !SerializeFallbacks( buf, pRoot ) ) + return false; + + if ( !SerializeProxies( buf, pRoot ) ) + return false; + + buf.PopTab(); + buf.Printf( "}\n" ); + return true; +} + + +//----------------------------------------------------------------------------- +// Parser utilities +//----------------------------------------------------------------------------- +static inline bool IsWhitespace( char c ) +{ + return c == ' ' || c == '\t'; +} + +static inline bool IsEndline( char c ) +{ + return c == '\n' || c == '\0'; +} + +static inline bool IsVector( char const* v ) +{ + while (IsWhitespace(*v)) + { + ++v; + if (IsEndline(*v)) + return false; + } + return *v == '[' || *v == '{'; +} + + +//----------------------------------------------------------------------------- +// Creates a vector material var +//----------------------------------------------------------------------------- +int ParseVectorFromKeyValueString( const char *pParamName, const char* pScan, const char *pMaterialName, float vecVal[4] ) +{ + bool divideBy255 = false; + + // skip whitespace + while( IsWhitespace(*pScan) ) + { + ++pScan; + } + + if( *pScan == '{' ) + { + divideBy255 = true; + } + else + { + Assert( *pScan == '[' ); + } + + // skip the '[' + ++pScan; + int i; + for( i = 0; i < 4; i++ ) + { + // skip whitespace + while( IsWhitespace(*pScan) ) + { + ++pScan; + } + + if( IsEndline(*pScan) || *pScan == ']' || *pScan == '}' ) + { + if (*pScan != ']' && *pScan != '}') + { + Warning( "Warning in .VMT file (%s): no ']' or '}' found in vector key \"%s\".\n" + "Did you forget to surround the vector with \"s?\n", pMaterialName, pParamName ); + } + + // allow for vec2's, etc. + vecVal[i] = 0.0f; + break; + } + + char* pEnd; + + vecVal[i] = strtod( pScan, &pEnd ); + if (pScan == pEnd) + { + Warning( "Error in .VMT file: error parsing vector element \"%s\" in \"%s\"\n", pParamName, pMaterialName ); + return 0; + } + + pScan = pEnd; + } + + if( divideBy255 ) + { + vecVal[0] *= ( 1.0f / 255.0f ); + vecVal[1] *= ( 1.0f / 255.0f ); + vecVal[2] *= ( 1.0f / 255.0f ); + vecVal[3] *= ( 1.0f / 255.0f ); + } + + return i; +} + + +//----------------------------------------------------------------------------- +// Sets shader parameter attributes +//----------------------------------------------------------------------------- +template< class T > +inline bool SetShaderParamAttribute( CDmElement *pElement, const char *pAttributeName, const T &value ) +{ + if ( !pElement ) + return false; + + if ( !pElement->SetValue( pAttributeName, value ) ) + return false; + + CDmAttribute *pAttribute = pElement->GetAttribute( pAttributeName ); + pAttribute->AddFlag( FATTRIB_USERDEFINED ); + return true; +} + +inline bool SetShaderParamAttribute( CDmElement *pElement, const char *pAttributeName, const char *value ) +{ + if ( !pElement ) + return false; + + if ( !pElement->SetValue( pAttributeName, value ) ) + return false; + + CDmAttribute *pAttribute = pElement->GetAttribute( pAttributeName ); + pAttribute->AddFlag( FATTRIB_USERDEFINED ); + return true; +} + + +//----------------------------------------------------------------------------- +// Creates a vector shader parameter +//----------------------------------------------------------------------------- +bool CImportVMT::CreateVectorMaterialVarFromKeyValue( CDmElement *pElement, const char *pParamName, const char *pString ) +{ + Vector4D vecVal; + int nDim = ParseVectorFromKeyValueString( pParamName, pString, FileName(), vecVal.Base() ); + if ( nDim == 0 ) + return false; + + // Create the variable! + switch ( nDim ) + { + case 1: + return SetShaderParamAttribute( pElement, pParamName, vecVal[0] ); + case 2: + return SetShaderParamAttribute( pElement, pParamName, vecVal.AsVector2D() ); + case 3: + return SetShaderParamAttribute( pElement, pParamName, vecVal.AsVector3D() ); + case 4: + return SetShaderParamAttribute( pElement, pParamName, vecVal ); + } + + return false; +} + + +//----------------------------------------------------------------------------- +// Creates a matrix shader parameter +//----------------------------------------------------------------------------- +bool CImportVMT::CreateMatrixMaterialVarFromKeyValue( CDmElement *pElement, const char *pParamName, const char *pScan ) +{ + // Matrices can be specified one of two ways: + // [ # # # # # # # # # # # # # # # # ] + // or + // center # # scale # # rotate # translate # # + + VMatrix mat; + int count = sscanf( pScan, " [ %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f ]", + &mat.m[0][0], &mat.m[0][1], &mat.m[0][2], &mat.m[0][3], + &mat.m[1][0], &mat.m[1][1], &mat.m[1][2], &mat.m[1][3], + &mat.m[2][0], &mat.m[2][1], &mat.m[2][2], &mat.m[2][3], + &mat.m[3][0], &mat.m[3][1], &mat.m[3][2], &mat.m[3][3] ); + if (count == 16) + { + return SetShaderParamAttribute( pElement, pParamName, mat ); + } + + Vector2D scale, center; + float angle; + Vector2D translation; + count = sscanf( pScan, " center %f %f scale %f %f rotate %f translate %f %f", + ¢er.x, ¢er.y, &scale.x, &scale.y, &angle, &translation.x, &translation.y ); + if (count != 7) + return false; + + VMatrix temp; + MatrixBuildTranslation( mat, -center.x, -center.y, 0.0f ); + MatrixBuildScale( temp, scale.x, scale.y, 1.0f ); + MatrixMultiply( temp, mat, mat ); + MatrixBuildRotateZ( temp, angle ); + MatrixMultiply( temp, mat, mat ); + MatrixBuildTranslation( temp, center.x + translation.x, center.y + translation.y, 0.0f ); + MatrixMultiply( temp, mat, mat ); + + // Create the variable! + return SetShaderParamAttribute( pElement, pParamName, mat ); +} + + +//----------------------------------------------------------------------------- +// Creates a shader parameter from a key value +//----------------------------------------------------------------------------- +bool CImportVMT::UnserializeShaderParam( CDmElement *pRoot, KeyValues* pKeyValues ) +{ + char pParamName[512]; + Q_strncpy( pParamName, pKeyValues->GetName(), sizeof(pParamName) ); + Q_strlower( pParamName ); + + switch( pKeyValues->GetDataType() ) + { + case KeyValues::TYPE_INT: + return SetShaderParamAttribute( pRoot, pParamName, pKeyValues->GetInt() ); + + case KeyValues::TYPE_FLOAT: + return SetShaderParamAttribute( pRoot, pParamName, pKeyValues->GetFloat() ); + + case KeyValues::TYPE_STRING: + { + char const* pString = pKeyValues->GetString(); + + // Only valid if it's a texture attribute + if ( !pString || !pString[0] ) + return SetShaderParamAttribute( pRoot, pParamName, pString ); + + // Look for matrices + if ( CreateMatrixMaterialVarFromKeyValue( pRoot, pParamName, pString ) ) + return true; + + // Look for vectors + if ( !IsVector( pString ) ) + return SetShaderParamAttribute( pRoot, pParamName, pString ); + + // Parse the string as a vector... + return CreateVectorMaterialVarFromKeyValue( pRoot, pParamName, pString ); + } + } + + return false; +} + + +//----------------------------------------------------------------------------- +// Unserialize proxies +//----------------------------------------------------------------------------- +bool CImportVMT::UnserializeProxies( CDmElement *pElement, KeyValues *pKeyValues ) +{ + // Create a child element array to contain all material proxies + CDmAttribute *pProxies = pElement->AddAttribute( "proxies", AT_ELEMENT_ARRAY ); + if ( !pProxies ) + return false; + + CDmrElementArray<> array( pProxies ); + + // Proxies are a list of sub-keys, the name is the proxy name, subkeys are values + for ( KeyValues *pProxy = pKeyValues->GetFirstTrueSubKey(); pProxy != NULL; pProxy = pProxy->GetNextTrueSubKey() ) + { + CDmElement *pProxyElement = CreateDmElement( "DmElement", pProxy->GetName(), NULL ); + array.AddToTail( pProxyElement ); + pProxyElement->SetValue( "proxyType", pKeyValues->GetName() ); + pProxyElement->SetValue( "editorType", "vmtProxy" ); + + // Normal keys are proxy parameters + for ( KeyValues *pProxyParam = pProxy->GetFirstValue(); pProxyParam != NULL; pProxyParam = pProxyParam->GetNextValue() ) + { + switch( pProxyParam->GetDataType() ) + { + case KeyValues::TYPE_INT: + pProxyElement->SetValue( pProxyParam->GetName(), pProxyParam->GetInt() ); + return true; + + case KeyValues::TYPE_FLOAT: + pProxyElement->SetValue( pProxyParam->GetName(), pProxyParam->GetFloat() ); + return true; + + case KeyValues::TYPE_STRING: + pProxyElement->SetValue( pProxyParam->GetName(), pProxyParam->GetString() ); + return true; + + default: + Warning( "Unhandled proxy keyvalues type (proxy %s var %s)\n", pProxy->GetName(), pProxyParam->GetName() ); + return false; + } + } + } + + return true; +} + + +//----------------------------------------------------------------------------- +// Unserialize fallbacks +//----------------------------------------------------------------------------- +bool CImportVMT::UnserializeFallbacks( CDmElement *pElement, KeyValues *pFallbackKeyValues ) +{ + // Create a child element array to contain all material proxies + CDmAttribute *pFallbacks = pElement->AddAttribute( "fallbacks", AT_ELEMENT_ARRAY ); + if ( !pFallbacks ) + return false; + + CDmrElementArray<> array( pFallbacks ); + + CDmElement *pFallback = CreateDmElement( "DmElement", pFallbackKeyValues->GetName(), NULL ); + array.AddToTail( pFallback ); + pFallback->SetValue( "editorType", "vmtFallback" ); + + // Normal keys are shader parameters + for ( KeyValues *pShaderParam = pFallbackKeyValues->GetFirstValue(); pShaderParam != NULL; pShaderParam = pShaderParam->GetNextValue() ) + { + if ( !UnserializeShaderParam( pFallback, pShaderParam ) ) + { + Warning( "Error importing vmt shader parameter %s\n", pShaderParam->GetName() ); + return NULL; + } + } + + return true; +} + + +//----------------------------------------------------------------------------- +// VMT parser +//----------------------------------------------------------------------------- +void InsertKeyValues( KeyValues& dst, KeyValues& src, bool bCheckForExistence ) +{ + KeyValues *pSrcVar = src.GetFirstSubKey(); + while( pSrcVar ) + { + if ( !bCheckForExistence || dst.FindKey( pSrcVar->GetName() ) ) + { + switch( pSrcVar->GetDataType() ) + { + case KeyValues::TYPE_STRING: + dst.SetString( pSrcVar->GetName(), pSrcVar->GetString() ); + break; + case KeyValues::TYPE_INT: + dst.SetInt( pSrcVar->GetName(), pSrcVar->GetInt() ); + break; + case KeyValues::TYPE_FLOAT: + dst.SetFloat( pSrcVar->GetName(), pSrcVar->GetFloat() ); + break; + case KeyValues::TYPE_PTR: + dst.SetPtr( pSrcVar->GetName(), pSrcVar->GetPtr() ); + break; + } + } + pSrcVar = pSrcVar->GetNextKey(); + } + + if( bCheckForExistence ) + { + for( KeyValues *pScan = dst.GetFirstTrueSubKey(); pScan; pScan = pScan->GetNextTrueSubKey() ) + { + KeyValues *pTmp = src.FindKey( pScan->GetName() ); + if( !pTmp ) + continue; + // make sure that this is a subkey. + if( pTmp->GetDataType() != KeyValues::TYPE_NONE ) + continue; + InsertKeyValues( *pScan, *pTmp, bCheckForExistence ); + } + } +} + + +//----------------------------------------------------------------------------- +// Handle patch files +//----------------------------------------------------------------------------- +void CImportVMT::ExpandPatchFile( KeyValues *pKeyValues ) +{ + int count = 0; + while( count < 10 && stricmp( pKeyValues->GetName(), "patch" ) == 0 ) + { +// WriteKeyValuesToFile( "patch.txt", keyValues ); + const char *pIncludeFileName = pKeyValues->GetString( "include" ); + if( pIncludeFileName ) + { + KeyValues * includeKeyValues = new KeyValues( "vmt" ); + bool success = includeKeyValues->LoadFromFile( g_pFullFileSystem, pIncludeFileName, IsX360() ? "GAME" : NULL ); + if( success ) + { + KeyValues *pInsertSection = pKeyValues->FindKey( "insert" ); + if( pInsertSection ) + { + InsertKeyValues( *includeKeyValues, *pInsertSection, false ); + } + + KeyValues *pReplaceSection = pKeyValues->FindKey( "replace" ); + if( pReplaceSection ) + { + InsertKeyValues( *includeKeyValues, *pReplaceSection, true ); + } + + *pKeyValues = *includeKeyValues; + includeKeyValues->deleteThis(); + // Could add other commands here, like "delete", "rename", etc. + } + else + { + includeKeyValues->deleteThis(); + return; + } + } + else + { + return; + } + count++; + } + if( count >= 10 ) + { + Warning( "Infinite recursion in patch file?\n" ); + } +} + + +//----------------------------------------------------------------------------- +// Main entry point for the unserialization +//----------------------------------------------------------------------------- +CDmElement* CImportVMT::UnserializeFromKeyValues( KeyValues *pKeyValues ) +{ + ExpandPatchFile( pKeyValues ); + + // Create the main element + CDmElement *pRoot = CreateDmElement( "DmElement", "VMT", NULL ); + if ( !pRoot ) + return NULL; + + // Each material needs to have an editortype associated with it so it displays nicely in editors + pRoot->SetValue( "editorType", "vmt" ); + + // Each material needs a proxy list and a fallback list + if ( !pRoot->AddAttribute( "proxies", AT_ELEMENT_ARRAY ) ) + return NULL; + if ( !pRoot->AddAttribute( "fallbacks", AT_ELEMENT_ARRAY ) ) + return NULL; + + // The keyvalues name is the shader name + pRoot->SetValue( "shader", pKeyValues->GetName() ); + + // Normal keys are shader parameters + for ( KeyValues *pShaderParam = pKeyValues->GetFirstValue(); pShaderParam != NULL; pShaderParam = pShaderParam->GetNextValue() ) + { + if ( !UnserializeShaderParam( pRoot, pShaderParam ) ) + { + Warning( "Error importing vmt shader parameter %s\n", pShaderParam->GetName() ); + return NULL; + } + } + + // Subkeys are either proxies or fallbacks + for ( KeyValues *pSubKey = pKeyValues->GetFirstTrueSubKey(); pSubKey != NULL; pSubKey = pSubKey->GetNextTrueSubKey() ) + { + if ( !Q_stricmp( pSubKey->GetName(), "Proxies" ) ) + { + UnserializeProxies( pRoot, pSubKey ); + } + else + { + UnserializeFallbacks( pRoot, pSubKey ); + } + } + + // Resolve all element references recursively + RecursivelyResolveElement( pRoot ); + + return pRoot; +} diff --git a/vpc_scripts/projects.vgc b/vpc_scripts/projects.vgc index db4cad9..e09e107 100644 --- a/vpc_scripts/projects.vgc +++ b/vpc_scripts/projects.vgc @@ -12,23 +12,39 @@ $Project "appframework" { "appframework\appframework.vpc" } + $Project "captioncompiler" { - "utils\captioncompiler\captioncompiler.vpc" [$WIN32] + "utils\captioncompiler\captioncompiler.vpc" } $Project "client" { - "game\client\client_hl2mp.vpc" [($WIN32||$POSIX) && $HL2MP] - "game\client\client_episodic.vpc" [($WIN32||$POSIX) && $EPISODIC] - "game\client\client_hl2.vpc" [($WIN32||$POSIX) && $HL2] - "game\client\client_sdk.vpc" [($WIN32||$POSIX) && $SDK] + "game\client\client_hl2mp.vpc" [($WIN32||$WIN64||$POSIX) && $HL2MP] + "game\client\client_episodic.vpc" [($WIN32||$WIN64||$POSIX) && $EPISODIC] + "game\client\client_hl2.vpc" [($WIN32||$WIN64||$POSIX) && $HL2] + "game\client\client_sdk.vpc" [($WIN32||$WIN64||$POSIX) && $SDK] +} + +$Project "datamodel" +{ + "datamodel\datamodel.vpc" +} + +$Project "datacache" +{ + "datacache\datacache.vpc" +} + +$Project "dmserializers" +{ + "dmserializers\dmserializers.vpc" } $Project "fgdlib" { - "fgdlib\fgdlib.vpc" [$WIN32] + "fgdlib\fgdlib.vpc" } $Project "game_shader_dx9"