Torque3D/Engine/source/persistence/taml/xml/tamlXmlReader.cpp
JeffR 630285def6 Shifts handling of material and terrain material definitions to be written into the asset definition taml file instead of having an extra loose file
Also updates importers to correctly handle this change
Adds ability for taml XML serialization to properly assign array'd fields
Adds 'inheritFrom' field to simobjects for when objects with parent objects are serialized
AssetBase how inherits from SimGroup so it can have objects like material definitions embedded in them for save/load in the taml definition file
Updated loading/handling logic in terrain material asset to be more similar to regular material assets
2022-01-30 11:50:16 -06:00

501 lines
16 KiB
C++

//-----------------------------------------------------------------------------
// Copyright (c) 2013 GarageGames, LLC
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//-----------------------------------------------------------------------------
#include "persistence/taml/xml/tamlXmlReader.h"
// Debug Profiling.
#include "platform/profiler.h"
#include <tinyxml2.h>
#include "persistence/taml/fsTinyXml.h"
//-----------------------------------------------------------------------------
SimObject* TamlXmlReader::read( FileStream& stream )
{
// Debug Profiling.
PROFILE_SCOPE(TamlXmlReader_Read);
// Create document.
VfsXMLDocument xmlDocument;
// Load document from stream.
if ( !xmlDocument.LoadFile(stream) )
{
// Warn!
Con::warnf("Taml: Could not load Taml XML file from stream.");
return NULL;
}
// Parse root element.
SimObject* pSimObject = parseElement( xmlDocument.RootElement() );
// Reset parse.
resetParse();
return pSimObject;
}
//-----------------------------------------------------------------------------
void TamlXmlReader::resetParse( void )
{
// Debug Profiling.
PROFILE_SCOPE(TamlXmlReader_ResetParse);
// Clear object reference map.
mObjectReferenceMap.clear();
}
//-----------------------------------------------------------------------------
SimObject* TamlXmlReader::parseElement( tinyxml2::XMLElement* pXmlElement )
{
// Debug Profiling.
PROFILE_SCOPE(TamlXmlReader_ParseElement);
SimObject* pSimObject = NULL;
// Fetch element name.
StringTableEntry typeName = StringTable->insert( pXmlElement->Value() );
// Fetch reference to Id.
const U32 tamlRefToId = getTamlRefToId( pXmlElement );
// Do we have a reference to Id?
if ( tamlRefToId != 0 )
{
// Yes, so fetch reference.
typeObjectReferenceHash::Iterator referenceItr = mObjectReferenceMap.find( tamlRefToId );
// Did we find the reference?
if ( referenceItr == mObjectReferenceMap.end() )
{
// No, so warn.
Con::warnf( "Taml: Could not find a reference Id of '%d'", tamlRefToId );
return NULL;
}
// Return object.
return referenceItr->value;
}
// No, so fetch reference Id.
const U32 tamlRefId = getTamlRefId( pXmlElement );
#ifdef TORQUE_DEBUG
// Format the type location.
char typeLocationBuffer[64];
dSprintf( typeLocationBuffer, sizeof(typeLocationBuffer), "Taml [format='xml' line=%d]", pXmlElement->GetLineNum() );
// Create type.
pSimObject = Taml::createType( typeName, mpTaml, typeLocationBuffer );
#else
// Create type.
pSimObject = Taml::createType( typeName, mpTaml );
#endif
// Finish if we couldn't create the type.
if ( pSimObject == NULL )
return NULL;
// Find Taml callbacks.
TamlCallbacks* pCallbacks = dynamic_cast<TamlCallbacks*>( pSimObject );
// Are there any Taml callbacks?
if ( pCallbacks != NULL )
{
// Yes, so call it.
mpTaml->tamlPreRead( pCallbacks );
}
// Parse attributes.
parseAttributes( pXmlElement, pSimObject );
// Fetch object name.
StringTableEntry objectName = StringTable->insert( getTamlObjectName( pXmlElement ) );
// Does the object require a name?
if ( objectName == StringTable->EmptyString() )
{
// No, so just register anonymously.
pSimObject->registerObject();
}
else
{
// Yes, so register a named object.
pSimObject->registerObject( objectName );
// Was the name assigned?
if ( pSimObject->getName() != objectName )
{
// No, so warn that the name was rejected.
#ifdef TORQUE_DEBUG
Con::warnf( "Taml::parseElement() - Registered an instance of type '%s' but a request to name it '%s' was rejected. This is typically because an object of that name already exists. '%s'", typeName, objectName, typeLocationBuffer );
#else
Con::warnf( "Taml::parseElement() - Registered an instance of type '%s' but a request to name it '%s' was rejected. This is typically because an object of that name already exists.", typeName, objectName );
#endif
}
}
// Do we have a reference Id?
if ( tamlRefId != 0 )
{
// Yes, so insert reference.
mObjectReferenceMap.insertUnique( tamlRefId, pSimObject );
}
// Fetch any children.
tinyxml2::XMLNode* pChildXmlNode = pXmlElement->FirstChild();
TamlCustomNodes customProperties;
// Do we have any element children?
if ( pChildXmlNode != NULL )
{
// Fetch the Taml children.
TamlChildren* pChildren = dynamic_cast<TamlChildren*>( pSimObject );
// Fetch any container child class specifier.
AbstractClassRep* pContainerChildClass = pSimObject->getClassRep()->getContainerChildClass( true );
// Iterate siblings.
do
{
// Fetch element.
tinyxml2::XMLElement* pChildXmlElement = pChildXmlNode->ToElement();
// Move to next sibling.
pChildXmlNode = pChildXmlNode->NextSibling();
// Skip if this is not an element?
if ( pChildXmlElement == NULL )
continue;
// Is this a standard child element?
if ( dStrchr( pChildXmlElement->Value(), '.' ) == NULL )
{
// Is this a Taml child?
if ( pChildren == NULL )
{
// No, so warn.
Con::warnf("Taml: Child element '%s' found under parent '%s' but object cannot have children.",
pChildXmlElement->Value(),
pXmlElement->Value() );
// Skip.
continue;
}
// Yes, so parse child element.
SimObject* pChildSimObject = parseElement( pChildXmlElement );
// Skip if the child was not created.
if ( pChildSimObject == NULL )
continue;
// Do we have a container child class?
if ( pContainerChildClass != NULL )
{
// Yes, so is the child object the correctly derived type?
if ( !pChildSimObject->getClassRep()->isClass( pContainerChildClass ) )
{
// No, so warn.
Con::warnf("Taml: Child element '%s' found under parent '%s' but object is restricted to children of type '%s'.",
pChildSimObject->getClassName(),
pSimObject->getClassName(),
pContainerChildClass->getClassName() );
// NOTE: We can't delete the object as it may be referenced elsewhere!
pChildSimObject = NULL;
// Skip.
continue;
}
}
// Add child.
pChildren->addTamlChild( pChildSimObject );
// Find Taml callbacks for child.
TamlCallbacks* pChildCallbacks = dynamic_cast<TamlCallbacks*>( pChildSimObject );
// Do we have callbacks on the child?
if ( pChildCallbacks != NULL )
{
// Yes, so perform callback.
mpTaml->tamlAddParent( pChildCallbacks, pSimObject );
}
}
else
{
// No, so parse custom element.
parseCustomElement( pChildXmlElement, customProperties );
}
}
while( pChildXmlNode != NULL );
// Call custom read.
mpTaml->tamlCustomRead( pCallbacks, customProperties );
}
// Are there any Taml callbacks?
if ( pCallbacks != NULL )
{
// Yes, so call it.
mpTaml->tamlPostRead( pCallbacks, customProperties );
}
// Return object.
return pSimObject;
}
//-----------------------------------------------------------------------------
void TamlXmlReader::parseAttributes( tinyxml2::XMLElement* pXmlElement, SimObject* pSimObject )
{
// Debug Profiling.
PROFILE_SCOPE(TamlXmlReader_ParseAttributes);
// Sanity!
AssertFatal( pSimObject != NULL, "Taml: Cannot parse attributes on a NULL object." );
// Iterate attributes.
for ( const tinyxml2::XMLAttribute* pAttribute = pXmlElement->FirstAttribute(); pAttribute; pAttribute = pAttribute->Next() )
{
// Insert attribute name.
StringTableEntry attributeName = StringTable->insert( pAttribute->Name() );
// Ignore if this is a Taml attribute.
if ( attributeName == tamlRefIdName ||
attributeName == tamlRefToIdName ||
attributeName == tamlNamedObjectName )
continue;
//See if we have any sort of array index
S32 suffixNum;
String trimmedName = String::GetTrailingNumber(attributeName, suffixNum);
if (!trimmedName.equal(attributeName))
{
char arrayIndexStr[32];
dItoa(suffixNum, arrayIndexStr);
// Set the field.
pSimObject->setPrefixedDataField(StringTable->insert(trimmedName.c_str()), arrayIndexStr, pAttribute->Value());
}
else
{
// Set the field.
pSimObject->setPrefixedDataField(attributeName, NULL, pAttribute->Value());
}
}
}
//-----------------------------------------------------------------------------
void TamlXmlReader::parseCustomElement( tinyxml2::XMLElement* pXmlElement, TamlCustomNodes& customNodes )
{
// Debug Profiling.
PROFILE_SCOPE(TamlXmlReader_ParseCustomElement);
// Is this a standard child element?
const char* pPeriod = dStrchr( pXmlElement->Value(), '.' );
// Sanity!
AssertFatal( pPeriod != NULL, "Parsing extended element but no period character found." );
// Fetch any custom XML node.
tinyxml2::XMLNode* pCustomXmlNode = pXmlElement->FirstChild();
// Finish is no XML node exists.
if ( pCustomXmlNode == NULL )
return;
// Yes, so add custom node.
TamlCustomNode* pCustomNode = customNodes.addNode( pPeriod+1 );
do
{
// Fetch element.
tinyxml2::XMLElement* pCustomXmlElement = pCustomXmlNode->ToElement();
// Move to next sibling.
pCustomXmlNode = pCustomXmlNode->NextSibling();
// Skip if this is not an element.
if ( pCustomXmlElement == NULL )
continue;
// Parse custom node.
parseCustomNode( pCustomXmlElement, pCustomNode );
}
while ( pCustomXmlNode != NULL );
}
//-----------------------------------------------------------------------------
void TamlXmlReader::parseCustomNode( tinyxml2::XMLElement* pXmlElement, TamlCustomNode* pCustomNode )
{
// Is the node a proxy object?
if ( getTamlRefId( pXmlElement ) != 0 || getTamlRefToId( pXmlElement ) != 0 )
{
// Yes, so parse proxy object.
SimObject* pProxyObject = parseElement( pXmlElement );
// Add child node.
pCustomNode->addNode( pProxyObject );
return;
}
// Yes, so add child node.
TamlCustomNode* pChildNode = pCustomNode->addNode( pXmlElement->Value() );
// Iterate attributes.
for ( const tinyxml2::XMLAttribute* pAttribute = pXmlElement->FirstAttribute(); pAttribute; pAttribute = pAttribute->Next() )
{
// Insert attribute name.
StringTableEntry attributeName = StringTable->insert( pAttribute->Name() );
// Skip if a Taml reference attribute.
if ( attributeName == tamlRefIdName || attributeName == tamlRefToIdName )
continue;
// Add node field.
pChildNode->addField( attributeName, pAttribute->Value() );
}
// Fetch any element text.
const char* pElementText = pXmlElement->GetText();
// Do we have any element text?
if ( pElementText != NULL )
{
// Yes, so store it.
pChildNode->setNodeText( pElementText );
}
// Fetch any children.
tinyxml2::XMLNode* pChildXmlNode = pXmlElement->FirstChild();
// Do we have any element children?
if ( pChildXmlNode != NULL )
{
do
{
// Yes, so fetch child element.
tinyxml2::XMLElement* pChildXmlElement = pChildXmlNode->ToElement();
// Move to next sibling.
pChildXmlNode = pChildXmlNode->NextSibling();
// Skip if this is not an element.
if ( pChildXmlElement == NULL )
continue;
// Parse custom node.
parseCustomNode( pChildXmlElement, pChildNode );
}
while( pChildXmlNode != NULL );
}
}
//-----------------------------------------------------------------------------
U32 TamlXmlReader::getTamlRefId( tinyxml2::XMLElement* pXmlElement )
{
// Debug Profiling.
PROFILE_SCOPE(TamlXmlReader_GetTamlRefId);
// Iterate attributes.
for ( const tinyxml2::XMLAttribute* pAttribute = pXmlElement->FirstAttribute(); pAttribute; pAttribute = pAttribute->Next() )
{
// Insert attribute name.
StringTableEntry attributeName = StringTable->insert( pAttribute->Name() );
// Skip if not the correct attribute.
if ( attributeName != tamlRefIdName )
continue;
// Return it.
return dAtoi( pAttribute->Value() );
}
// Not found.
return 0;
}
//-----------------------------------------------------------------------------
U32 TamlXmlReader::getTamlRefToId( tinyxml2::XMLElement* pXmlElement )
{
// Debug Profiling.
PROFILE_SCOPE(TamlXmlReader_GetTamlRefToId);
// Iterate attributes.
for ( const tinyxml2::XMLAttribute* pAttribute = pXmlElement->FirstAttribute(); pAttribute; pAttribute = pAttribute->Next() )
{
// Insert attribute name.
StringTableEntry attributeName = StringTable->insert( pAttribute->Name() );
// Skip if not the correct attribute.
if ( attributeName != tamlRefToIdName )
continue;
// Return it.
return dAtoi( pAttribute->Value() );
}
// Not found.
return 0;
}
//-----------------------------------------------------------------------------
const char* TamlXmlReader::getTamlObjectName( tinyxml2::XMLElement* pXmlElement )
{
// Debug Profiling.
PROFILE_SCOPE(TamlXmlReader_GetTamlObjectName);
// Iterate attributes.
for ( const tinyxml2::XMLAttribute* pAttribute = pXmlElement->FirstAttribute(); pAttribute; pAttribute = pAttribute->Next() )
{
// Insert attribute name.
StringTableEntry attributeName = StringTable->insert( pAttribute->Name() );
// Skip if not the correct attribute.
if ( attributeName != tamlNamedObjectName )
continue;
// Return it.
return pAttribute->Value();
}
// Not found.
return NULL;
}