Changeset View
Changeset View
Standalone View
Standalone View
source/ps/ParamNodeLayer.cpp
- This file was added.
/* Copyright (C) 2021 Wildfire Games. | |||||
* This file is part of 0 A.D. | |||||
* | |||||
* 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 | |||||
* the Free Software Foundation, either version 2 of the License, or | |||||
* (at your option) any later version. | |||||
* | |||||
* 0 A.D. is distributed in the hope that it will be useful, | |||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||||
* GNU General Public License for more details. | |||||
* | |||||
* You should have received a copy of the GNU General Public License | |||||
* along with 0 A.D. If not, see <http://www.gnu.org/licenses/>. | |||||
*/ | |||||
#include "precompiled.h" | |||||
#include "ParamNodeLayer.h" | |||||
#include "ps/Filesystem.h" | |||||
#include "ps/XML/Xeromyces.h" | |||||
#include "simulation2/system/ParamNode.h" | |||||
#include <boost/algorithm/string.hpp> | |||||
#include <boost/algorithm/string/join.hpp> // this isn't in string.hpp in old Boosts | |||||
#include <unordered_map> | |||||
class ParamNodeLayer::Impl | |||||
{ | |||||
public: | |||||
Impl(CXeromyces&& x) : initialised(true), value(std::move(x)) {}; | |||||
~Impl() | |||||
{ | |||||
if (initialised) | |||||
value.xmb.~CXeromyces(); | |||||
} | |||||
// TODO: swap this out for a variant. | |||||
bool initialised = false; | |||||
union data { | |||||
void* placeholder; | |||||
CXeromyces xmb; | |||||
data(CXeromyces&& x) : xmb(std::move(x)) {}; | |||||
~data(){}; | |||||
} value; | |||||
}; | |||||
// Defined here so shared_ptr has full access to Impl. | |||||
ParamNodeLayer::ParamNodeLayer() {} | |||||
ParamNodeLayer::~ParamNodeLayer() {} | |||||
bool ParamNodeLayer::Load(const VfsPath& path) | |||||
{ | |||||
return LoadXML(path); | |||||
} | |||||
bool ParamNodeLayer::LoadXML(const VfsPath& path, const std::string& validator) | |||||
{ | |||||
CXeromyces xero; | |||||
PSRETURN ok = xero.Load(g_VFS, path, validator); | |||||
if (ok != PSRETURN_OK) | |||||
return false; // (Xeromyces already logged an error) | |||||
int attr_parent = xero.GetAttributeID("parent"); | |||||
CStr parentName = xero.GetRoot().GetAttributes().GetNamedItem(attr_parent); | |||||
if (!parentName.empty()) | |||||
m_Parent = parentName; | |||||
m_Data = std::make_shared<Impl>(std::move(xero)); | |||||
return true; | |||||
} | |||||
bool ParamNodeLayer::LoadXML(const std::string& xml, const std::string& validator) | |||||
{ | |||||
CXeromyces xero; | |||||
PSRETURN ok = xero.LoadString(xml.c_str(), validator); | |||||
if (ok != PSRETURN_OK) | |||||
return false; // (Xeromyces already logged an error) | |||||
int attr_parent = xero.GetAttributeID("parent"); | |||||
CStr parentName = xero.GetRoot().GetAttributes().GetNamedItem(attr_parent); | |||||
if (!parentName.empty()) | |||||
m_Parent = parentName; | |||||
m_Data = std::make_shared<Impl>(std::move(xero)); | |||||
return true; | |||||
} | |||||
std::string ParamNodeLayer::GetParent() const | |||||
{ | |||||
return m_Parent; | |||||
} | |||||
void ParamNodeLayer::SetParent(const std::string& parent) | |||||
{ | |||||
m_Parent = parent; | |||||
} | |||||
template<> | |||||
bool ParamNodeLayer::ApplyOnNode<>(CParamNode& node, const XMBFile& xmb, const XMBElement& element, const std::string& nodeName) | |||||
{ | |||||
node.ResetScriptVal(); | |||||
auto& m_Childs = node.m_Childs; | |||||
std::string name = nodeName; | |||||
CStrW value = element.GetText().FromUTF8(); | |||||
bool hasSetValue = false; | |||||
// Look for special attributes | |||||
int at_disable = xmb.GetAttributeID("disable"); | |||||
int at_replace = xmb.GetAttributeID("replace"); | |||||
int at_filtered = xmb.GetAttributeID("filtered"); | |||||
int at_merge = xmb.GetAttributeID("merge"); | |||||
int at_op = xmb.GetAttributeID("op"); | |||||
int at_datatype = xmb.GetAttributeID("datatype"); | |||||
int at_parentName = xmb.GetAttributeID("parentName"); | |||||
enum op { | |||||
INVALID, | |||||
ADD, | |||||
MUL, | |||||
MUL_ROUND | |||||
} op = INVALID; | |||||
bool replacing = false; | |||||
bool filtering = false; | |||||
bool merging = false; | |||||
{ | |||||
XERO_ITER_ATTR(element, attr) | |||||
{ | |||||
if (attr.Name == at_parentName) | |||||
{ | |||||
m_Childs.try_emplace(name).first->second.m_Value = wstring_from_utf8(m_Parent); | |||||
return true; | |||||
} | |||||
else if (attr.Name == at_disable) | |||||
{ | |||||
m_Childs.erase(name); | |||||
return true; | |||||
} | |||||
else if (attr.Name == at_replace) | |||||
{ | |||||
m_Childs.erase(name); | |||||
replacing = true; | |||||
} | |||||
else if (attr.Name == at_filtered) | |||||
{ | |||||
filtering = true; | |||||
} | |||||
else if (attr.Name == at_merge) | |||||
{ | |||||
if (m_Childs.find(name) == m_Childs.end()) | |||||
return true; | |||||
merging = true; | |||||
} | |||||
else if (attr.Name == at_op) | |||||
{ | |||||
if (std::wstring(attr.Value.begin(), attr.Value.end()) == L"add") | |||||
op = ADD; | |||||
else if (std::wstring(attr.Value.begin(), attr.Value.end()) == L"mul") | |||||
op = MUL; | |||||
else if (std::wstring(attr.Value.begin(), attr.Value.end()) == L"mul_round") | |||||
op = MUL_ROUND; | |||||
else | |||||
LOGWARNING("Invalid op '%ls'", attr.Value); | |||||
} | |||||
} | |||||
} | |||||
{ | |||||
XERO_ITER_ATTR(element, attr) | |||||
{ | |||||
if (attr.Name == at_datatype && std::wstring(attr.Value.begin(), attr.Value.end()) == L"tokens") | |||||
{ | |||||
CParamNode& node = m_Childs[name]; | |||||
// Split into tokens | |||||
std::vector<std::wstring> oldTokens; | |||||
std::vector<std::wstring> newTokens; | |||||
if (!replacing && !node.m_Value.empty()) // ignore the old tokens if replace="" was given | |||||
boost::algorithm::split(oldTokens, node.m_Value, boost::algorithm::is_space(), boost::algorithm::token_compress_on); | |||||
if (!value.empty()) | |||||
boost::algorithm::split(newTokens, value, boost::algorithm::is_space(), boost::algorithm::token_compress_on); | |||||
// Merge the two lists | |||||
std::vector<std::wstring> tokens = oldTokens; | |||||
for (size_t i = 0; i < newTokens.size(); ++i) | |||||
{ | |||||
if (newTokens[i][0] == L'-') | |||||
{ | |||||
std::vector<std::wstring>::iterator tokenIt = std::find(tokens.begin(), tokens.end(), newTokens[i].substr(1)); | |||||
if (tokenIt != tokens.end()) | |||||
tokens.erase(tokenIt); | |||||
else | |||||
LOGWARNING("TODO"); | |||||
//LOGWARNING("[ParamNode] Could not remove token '%s' from node '%s'%s; not present in list nor inherited (possible typo?)", | |||||
// utf8_from_wstring(newTokens[i].substr(1)), name, sourceIdentifier ? (" in '" + utf8_from_wstring(sourceIdentifier) + "'").c_str() : ""); | |||||
} | |||||
else | |||||
{ | |||||
if (std::find(oldTokens.begin(), oldTokens.end(), newTokens[i]) == oldTokens.end()) | |||||
tokens.push_back(newTokens[i]); | |||||
} | |||||
} | |||||
node.m_Value = boost::algorithm::join(tokens, L" "); | |||||
hasSetValue = true; | |||||
break; | |||||
} | |||||
} | |||||
} | |||||
// Add this element as a child node | |||||
CParamNode& childNode = m_Childs[name]; | |||||
if (op != INVALID) | |||||
{ | |||||
// TODO: Support parsing of data types other than fixed; log warnings in other cases | |||||
fixed oldval = childNode.ToFixed(); | |||||
fixed mod = fixed::FromString(CStrW(value)); | |||||
switch (op) | |||||
{ | |||||
case ADD: | |||||
childNode.m_Value = (oldval + mod).ToString().FromUTF8(); | |||||
break; | |||||
case MUL: | |||||
childNode.m_Value = oldval.Multiply(mod).ToString().FromUTF8(); | |||||
break; | |||||
case MUL_ROUND: | |||||
childNode.m_Value = fixed::FromInt(oldval.Multiply(mod).ToInt_RoundToNearest()).ToString().FromUTF8(); | |||||
break; | |||||
default: | |||||
break; | |||||
} | |||||
hasSetValue = true; | |||||
} | |||||
if (!hasSetValue && !merging) | |||||
childNode.m_Value = value; | |||||
// We also need to reset node's script val, even if it has no children | |||||
// or if the attributes change. | |||||
childNode.ResetScriptVal(); | |||||
// For the filtered case | |||||
CParamNode::ChildrenMap childs; | |||||
// Recurse through the element's children | |||||
std::unordered_map<std::string, int> collisions; | |||||
XERO_ITER_EL(element, child) | |||||
{ | |||||
std::string childname = xmb.GetElementString(child.GetNodeName()); | |||||
int& coll = collisions.try_emplace(childname, 0).first->second; | |||||
if (coll++ > 0) | |||||
childname += "@" + std::to_string(coll); | |||||
ApplyOnNode<const XMBFile&, const XMBElement&, const std::string&>(childNode, xmb, child, childname); | |||||
if (filtering) | |||||
{ | |||||
if (childNode.m_Childs.find(childname) != childNode.m_Childs.end()) | |||||
childs[childname] = std::move(childNode.m_Childs[childname]); | |||||
} | |||||
} | |||||
if (filtering) | |||||
childNode.m_Childs.swap(childs); | |||||
// Add the element's attributes, prefixing names with "@" | |||||
XERO_ITER_ATTR(element, attr) | |||||
{ | |||||
// Skip special attributes | |||||
if (attr.Name == at_replace || attr.Name == at_op || attr.Name == at_merge || attr.Name == at_filtered || attr.Name == at_parentName) | |||||
continue; | |||||
// Add any others | |||||
std::string attrName = xmb.GetAttributeString(attr.Name); | |||||
childNode.m_Childs["@" + attrName].m_Value = attr.Value.FromUTF8(); | |||||
} | |||||
return true; | |||||
} | |||||
// Defined after the implementations otherwise templates are instantiated. | |||||
bool ParamNodeLayer::ApplyOnNode(CParamNode& node) | |||||
{ | |||||
const XMBFile& xmb = m_Data->value.xmb; | |||||
const XMBElement& element = xmb.GetRoot(); | |||||
const std::string& name = xmb.GetElementString(element.GetNodeName()); // TODO: is GetElementString inefficient? | |||||
return ApplyOnNode(node, xmb, element, name); | |||||
} |
Wildfire Games · Phabricator