//====== Copyright © 1996-2004, 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; //----------------------------------------------------------------------------- // 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 int GetImportedVersion() 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 ); virtual const char *GetImportedFormat() const { return "dmx"; } 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->IsStandard() || 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(); // Rename "_name" dme attribute back to "name" keyvalues field if ( !Q_stricmp( pName, "_name" ) ) { pName = "name"; } 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::DisableOnChangedCallbacks( pElement ); 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; Q_strlower( pLowerName.Get() ); // Rename "name" keyvalues field to "_name" dme attribute to avoid name collision if ( !Q_stricmp( pLowerName, "name" ) ) { pLowerName = "_name"; } // 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.String() ); return; } CDmAttribute *pAttribute = pElement->AddAttribute( pLowerName, type ); if ( !pAttribute ) { Warning("Dm Unserialize: Attempted to read an attribute (\"%s\") of an inappropriate type!\n", pLowerName.String() ); 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::EnableOnChangedCallbacks( pElement ); CDmeElementAccessor::FinishUnserialization( pElement ); } 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; }