Changeset View
Changeset View
Standalone View
Standalone View
source/ps/TemplateLoader.cpp
/* Copyright (C) 2020 Wildfire Games. | /* Copyright (C) 2021 Wildfire Games. | ||||
* This file is part of 0 A.D. | * This file is part of 0 A.D. | ||||
* | * | ||||
* 0 A.D. is free software: you can redistribute it and/or modify | * 0 A.D. is free software: you can redistribute it and/or modify | ||||
* it under the terms of the GNU General Public License as published by | * it under the terms of the GNU General Public License as published by | ||||
* the Free Software Foundation, either version 2 of the License, or | * the Free Software Foundation, either version 2 of the License, or | ||||
* (at your option) any later version. | * (at your option) any later version. | ||||
* | * | ||||
* 0 A.D. is distributed in the hope that it will be useful, | * 0 A.D. is distributed in the hope that it will be useful, | ||||
Show All 20 Lines | |||||
static CParamNode NULL_NODE(false); | static CParamNode NULL_NODE(false); | ||||
bool CTemplateLoader::LoadTemplateFile(const std::string& templateName, int depth) | bool CTemplateLoader::LoadTemplateFile(const std::string& templateName, int depth) | ||||
{ | { | ||||
// If this file was already loaded, we don't need to do anything | // If this file was already loaded, we don't need to do anything | ||||
if (m_TemplateFileData.find(templateName) != m_TemplateFileData.end()) | if (m_TemplateFileData.find(templateName) != m_TemplateFileData.end()) | ||||
return true; | return true; | ||||
// Handle infinite loops more gracefully than running out of stack space and crashing | |||||
if (depth > 100) | |||||
{ | |||||
LOGERROR("Probable infinite inheritance loop in entity template '%s'", templateName.c_str()); | |||||
return false; | |||||
} | |||||
// Handle special case "actor|foo" | // Handle special case "actor|foo" | ||||
if (templateName.find("actor|") == 0) | if (templateName.find("actor|") == 0) | ||||
{ | { | ||||
ConstructTemplateActor(templateName.substr(6), m_TemplateFileData[templateName]); | LoadAndApplyLayer(m_TemplateFileData[templateName], "special/actor", templateName.substr(6), depth); | ||||
return true; | return true; | ||||
} | } | ||||
// Handle special case "bar|foo" | if (!LoadAndApplyLayer(m_TemplateFileData[templateName], templateName, "", depth)) | ||||
size_t pos = templateName.find_first_of('|'); | return false; | ||||
if (pos != std::string::npos) | |||||
return true; | |||||
} | |||||
bool CTemplateLoader::LoadAndApplyLayer(CParamNode& node, const std::string& layerName, const std::string& parentName, int depth) | |||||
{ | { | ||||
std::string prefix = templateName.substr(0, pos); | // Handle infinite loops more gracefully than running out of stack space and crashing | ||||
std::string baseName = templateName.substr(pos+1); | if (depth > 100) | ||||
{ | |||||
LOGERROR("Probable infinite inheritance loop in template layer '%s'", layerName.c_str()); | |||||
return false; | |||||
} | |||||
if (!LoadTemplateFile(baseName, depth+1)) | size_t pos = layerName.find_last_of('|'); | ||||
if (pos != std::string::npos) | |||||
{ | { | ||||
LOGERROR("Failed to load entity template '%s'", baseName.c_str()); | // Apply first the right-most pattern (without incrementing depth), then recurse the left. | ||||
if (!LoadAndApplyLayer(node, layerName.substr(pos+1), parentName, depth)) | |||||
return false; | |||||
if (!LoadAndApplyLayer(node, layerName.substr(0, pos), layerName.substr(pos+1), depth+1)) | |||||
return false; | return false; | ||||
return true; | |||||
} | } | ||||
VfsPath path = VfsPath(TEMPLATE_ROOT) / L"special" / L"filter" / wstring_from_utf8(prefix + ".xml"); | // If we're here, we're dealing with a single layer. Load it. | ||||
ParamNodeLayer layer; | |||||
auto layerIt = m_ParamNodeLayers.find(layerName); | |||||
if (layerIt != m_ParamNodeLayers.end()) | |||||
// Copy the layer - we may modify its parent. | |||||
layer = layerIt->second; | |||||
else | |||||
{ | |||||
VfsPath path = VfsPath(TEMPLATE_ROOT) / wstring_from_utf8(layerName + ".xml"); | |||||
// 'Filters' can be applied as shorthand - assume this is one if the original path isn't found. | |||||
if (!VfsFileExists(path)) | if (!VfsFileExists(path)) | ||||
path = VfsPath(TEMPLATE_ROOT) / L"special" / L"filter" / wstring_from_utf8(layerName + ".xml"); | |||||
if (!layer.LoadXML(path)) | |||||
wraitii: As a matter of fact, this isn't even necessary. We _could_ just put everything in filter. | |||||
{ | { | ||||
LOGERROR("Invalid subset '%s'", prefix.c_str()); | LOGERROR("Failed to load layer '%s'", layerName); | ||||
return false; | return false; | ||||
} | } | ||||
// Cache the layer for future re-use. | |||||
CXeromyces xero; | m_ParamNodeLayers[layerName] = layer; | ||||
PSRETURN ok = xero.Load(g_VFS, path); | |||||
if (ok != PSRETURN_OK) | |||||
return false; // (Xeromyces already logged an error with the full filename) | |||||
m_TemplateFileData[templateName] = m_TemplateFileData[baseName]; | |||||
CParamNode::LoadXML(m_TemplateFileData[templateName], xero, path.string().c_str()); | |||||
return true; | |||||
} | } | ||||
const std::string& parent = layer.GetParent(); | |||||
// Normal case: templateName is an XML file: | // If the layer defines an explicit parent, we must load that and apply it before ourselves. | ||||
if (!parent.empty()) | |||||
VfsPath path = VfsPath(TEMPLATE_ROOT) / wstring_from_utf8(templateName + ".xml"); | { | ||||
CXeromyces xero; | // At depth 0, we can try loading the parent as a 'full' CParamNode, which will cache it. | ||||
PSRETURN ok = xero.Load(g_VFS, path); | // This can help if several layers have a common parent. | ||||
if (ok != PSRETURN_OK) | if (depth == 0) | ||||
return false; // (Xeromyces already logged an error with the full filename) | |||||
int attr_parent = xero.GetAttributeID("parent"); | |||||
CStr parentName = xero.GetRoot().GetAttributes().GetNamedItem(attr_parent); | |||||
if (!parentName.empty()) | |||||
{ | { | ||||
// To prevent needless complexity in template design, we don't allow |-separated strings as parents | ENSURE (parentName.empty()); | ||||
if (parentName.find('|') != parentName.npos) | if (!LoadTemplateFile(parent, depth+1)) | ||||
{ | { | ||||
LOGERROR("Invalid parent '%s' in entity template '%s'", parentName.c_str(), templateName.c_str()); | LOGERROR("Failed to load parent '%s' of template layer '%s'", parentName, layerName); | ||||
return false; | return false; | ||||
} | } | ||||
node = m_TemplateFileData[parent]; | |||||
// Ensure the parent is loaded | } | ||||
if (!LoadTemplateFile(parentName, depth+1)) | // Otherwise, load this and forward it our parent. | ||||
else if (!LoadAndApplyLayer(node, parent, parentName, depth+1)) | |||||
{ | { | ||||
LOGERROR("Failed to load parent '%s' of entity template '%s'", parentName.c_str(), templateName.c_str()); | LOGERROR("Failed to load parent '%s' of template layer '%s'", parent, layerName); | ||||
return false; | return false; | ||||
} | } | ||||
CParamNode& parentData = m_TemplateFileData[parentName]; | |||||
// Initialise this template with its parent | |||||
m_TemplateFileData[templateName] = parentData; | |||||
} | } | ||||
// Otherwise, overwrite the parent. | |||||
// Load the new file into the template data (overriding parent values) | else if (!parentName.empty()) | ||||
CParamNode::LoadXML(m_TemplateFileData[templateName], xero, wstring_from_utf8(templateName).c_str()); | layer.SetParent(parentName); | ||||
// Apply the layer. | |||||
if (!layer.ApplyOnNode(node)) | |||||
{ | |||||
LOGERROR("Failed to apply layer '%s'", layerName); | |||||
return false; | |||||
} | |||||
return true; | return true; | ||||
} | } | ||||
static Status AddToTemplates(const VfsPath& pathname, const CFileInfo& UNUSED(fileInfo), const uintptr_t cbData) | static Status AddToTemplates(const VfsPath& pathname, const CFileInfo& UNUSED(fileInfo), const uintptr_t cbData) | ||||
{ | { | ||||
std::vector<std::string>& templates = *(std::vector<std::string>*)cbData; | std::vector<std::string>& templates = *(std::vector<std::string>*)cbData; | ||||
▲ Show 20 Lines • Show All 57 Lines • ▼ Show 20 Lines | const CParamNode& CTemplateLoader::GetTemplateFileData(const std::string& templateName) | ||||
// Load the template if necessary | // Load the template if necessary | ||||
if (!LoadTemplateFile(templateName, 0)) | if (!LoadTemplateFile(templateName, 0)) | ||||
{ | { | ||||
LOGERROR("Failed to load entity template '%s'", templateName.c_str()); | LOGERROR("Failed to load entity template '%s'", templateName.c_str()); | ||||
return NULL_NODE; | return NULL_NODE; | ||||
} | } | ||||
return m_TemplateFileData[templateName]; | return m_TemplateFileData[templateName]; | ||||
} | } | ||||
Done Inline ActionsWish this could be an actual template to make it moddable, considering the only thing that changes is the actor. Not in this diff though. Stan: Wish this could be an actual template to make it moddable, considering the only thing that… | |||||
void CTemplateLoader::ConstructTemplateActor(const std::string& actorName, CParamNode& out) | |||||
{ | |||||
// Copy the actor template | |||||
out = GetTemplateFileData("special/actor"); | |||||
// Initialize the actor's name and make it an Atlas selectable entity. | |||||
std::wstring actorNameW = wstring_from_utf8(actorName); | |||||
std::string name = utf8_from_wstring(CParamNode::EscapeXMLString(actorNameW)); | |||||
std::string xml = "<Entity>" | |||||
"<VisualActor><Actor>" + name + "</Actor><ActorOnly/></VisualActor>" | |||||
// Arbitrary-sized Footprint definition to make actors' selection outlines show up in Atlas. | |||||
"<Footprint><Circle radius='2.0'/><Height>1.0</Height></Footprint>" | |||||
"<Selectable>" | |||||
"<EditorOnly/>" | |||||
"<Overlay><Texture><MainTexture>128x128/ellipse.png</MainTexture><MainTextureMask>128x128/ellipse_mask.png</MainTextureMask></Texture></Overlay>" | |||||
"</Selectable>" | |||||
"</Entity>"; | |||||
CParamNode::LoadXMLString(out, xml.c_str(), actorNameW.c_str()); | |||||
} |
Wildfire Games · Phabricator
As a matter of fact, this isn't even necessary. We _could_ just put everything in filter.