0ad/source/simulation2/system/ParamNode.cpp
Yves c02a7e1a7b SpiderMonkey 31 upgrade
This upgrade also introduces exact stack rooting (see to the wiki:
JSRootingGuide) and fixes problems with moving GC. This allows us to
enable generational garbage collection (GGC).
Measurements a few months ago have shown a performance improvement of a
non-visual replay of around 13.5%. This probably varies quite a bit, but
it should be somewhere between 5-20%. Memory usage has also been
improved. Check the forum thread for details.

Thanks to everyone from the team who helped with this directly or
indirectly (review, finding and fixing issues, the required C++11
upgrade, the new autobuilder etc.)! Also thanks to the SpiderMonkey
developers who helped on the #jsapi channel or elsewhere!

Fixes #2462, #2415, #2428, #2684, #1374
Refs #2973, #2669

This was SVN commit r16214.
2015-01-24 14:46:52 +00:00

390 lines
9.5 KiB
C++

/* Copyright (C) 2014 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 "ParamNode.h"
#include "lib/utf8.h"
#include "ps/CLogger.h"
#include "ps/CStr.h"
#include "ps/Filesystem.h"
#include "ps/XML/Xeromyces.h"
#include <sstream>
#include <boost/algorithm/string.hpp>
#include <boost/algorithm/string/join.hpp> // this isn't in string.hpp in old Boosts
static CParamNode g_NullNode(false);
CParamNode::CParamNode(bool isOk) :
m_IsOk(isOk)
{
}
void CParamNode::LoadXML(CParamNode& ret, const XMBFile& xmb, const wchar_t* sourceIdentifier /*= NULL*/)
{
ret.ApplyLayer(xmb, xmb.GetRoot(), sourceIdentifier);
}
void CParamNode::LoadXML(CParamNode& ret, const VfsPath& path)
{
CXeromyces xero;
PSRETURN ok = xero.Load(g_VFS, path);
if (ok != PSRETURN_OK)
return; // (Xeromyces already logged an error)
LoadXML(ret, xero, path.string().c_str());
}
PSRETURN CParamNode::LoadXMLString(CParamNode& ret, const char* xml, const wchar_t* sourceIdentifier /*=NULL*/)
{
CXeromyces xero;
PSRETURN ok = xero.LoadString(xml);
if (ok != PSRETURN_OK)
return ok;
ret.ApplyLayer(xero, xero.GetRoot(), sourceIdentifier);
return PSRETURN_OK;
}
void CParamNode::ApplyLayer(const XMBFile& xmb, const XMBElement& element, const wchar_t* sourceIdentifier /*= NULL*/)
{
ResetScriptVal();
std::string name = xmb.GetElementString(element.GetNodeName()); // TODO: is GetElementString inefficient?
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_datatype = xmb.GetAttributeID("datatype");
bool replacing = false;
{
XERO_ITER_ATTR(element, attr)
{
if (attr.Name == at_disable)
{
m_Childs.erase(name);
return;
}
else if (attr.Name == at_replace)
{
m_Childs.erase(name);
replacing = true;
}
}
}
{
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("[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& node = m_Childs[name];
if (!hasSetValue)
node.m_Value = value;
// Recurse through the element's children
XERO_ITER_EL(element, child)
{
node.ApplyLayer(xmb, child, sourceIdentifier);
}
// Add the element's attributes, prefixing names with "@"
XERO_ITER_ATTR(element, attr)
{
// Skip special attributes
if (attr.Name == at_replace) continue;
// Add any others
std::string attrName = xmb.GetAttributeString(attr.Name);
node.m_Childs["@" + attrName].m_Value = attr.Value.FromUTF8();
}
}
void CParamNode::CopyFilteredChildrenOfChild(const CParamNode& src, const char* name, const std::set<std::string>& permitted)
{
ResetScriptVal();
ChildrenMap::iterator dstChild = m_Childs.find(name);
ChildrenMap::const_iterator srcChild = src.m_Childs.find(name);
if (dstChild == m_Childs.end() || srcChild == src.m_Childs.end())
return; // error
ChildrenMap::const_iterator it = srcChild->second.m_Childs.begin();
for (; it != srcChild->second.m_Childs.end(); ++it)
if (permitted.count(it->first))
dstChild->second.m_Childs[it->first] = it->second;
}
const CParamNode& CParamNode::GetChild(const char* name) const
{
ChildrenMap::const_iterator it = m_Childs.find(name);
if (it == m_Childs.end())
return g_NullNode;
return it->second;
}
bool CParamNode::IsOk() const
{
return m_IsOk;
}
const std::wstring& CParamNode::ToString() const
{
return m_Value;
}
const std::string CParamNode::ToUTF8() const
{
return utf8_from_wstring(m_Value);
}
const CStrIntern CParamNode::ToUTF8Intern() const
{
return CStrIntern(utf8_from_wstring(m_Value));
}
int CParamNode::ToInt() const
{
int ret = 0;
std::wstringstream strm;
strm << m_Value;
strm >> ret;
return ret;
}
fixed CParamNode::ToFixed() const
{
return fixed::FromString(CStrW(m_Value));
}
float CParamNode::ToFloat() const
{
float ret = 0;
std::wstringstream strm;
strm << m_Value;
strm >> ret;
return ret;
}
bool CParamNode::ToBool() const
{
if (m_Value == L"true")
return true;
else
return false;
}
const CParamNode::ChildrenMap& CParamNode::GetChildren() const
{
return m_Childs;
}
std::wstring CParamNode::EscapeXMLString(const std::wstring& str)
{
std::wstring ret;
ret.reserve(str.size());
for (size_t i = 0; i < str.size(); ++i)
{
wchar_t c = str[i];
switch (c)
{
case '<': ret += L"&lt;"; break;
case '>': ret += L"&gt;"; break;
case '&': ret += L"&amp;"; break;
case '"': ret += L"&quot;"; break;
case '\t': ret += L"&#9;"; break;
case '\n': ret += L"&#10;"; break;
case '\r': ret += L"&#13;"; break;
default:
if ((0x20 <= c && c <= 0xD7FF) || (0xE000 <= c && c <= 0xFFFD))
ret += c;
else
ret += 0xFFFD;
}
}
return ret;
}
std::wstring CParamNode::ToXML() const
{
std::wstringstream strm;
ToXML(strm);
return strm.str();
}
void CParamNode::ToXML(std::wostream& strm) const
{
strm << m_Value;
ChildrenMap::const_iterator it = m_Childs.begin();
for (; it != m_Childs.end(); ++it)
{
// Skip attributes here (they were handled when the caller output the tag)
if (it->first.length() && it->first[0] == '@')
continue;
std::wstring name (it->first.begin(), it->first.end());
strm << L"<" << name;
// Output the child's attributes first
ChildrenMap::const_iterator cit = it->second.m_Childs.begin();
for (; cit != it->second.m_Childs.end(); ++cit)
{
if (cit->first.length() && cit->first[0] == '@')
{
std::wstring attrname (cit->first.begin()+1, cit->first.end());
strm << L" " << attrname << L"=\"" << EscapeXMLString(cit->second.m_Value) << L"\"";
}
}
strm << L">";
it->second.ToXML(strm);
strm << L"</" << name << ">";
}
}
void CParamNode::ToJSVal(JSContext* cx, bool cacheValue, JS::MutableHandleValue ret) const
{
if (cacheValue && m_ScriptVal != NULL)
{
ret.set(*m_ScriptVal);
return;
}
ConstructJSVal(cx, ret);
if (cacheValue)
m_ScriptVal.reset(new JS::PersistentRootedValue(cx, ret));
}
void CParamNode::ConstructJSVal(JSContext* cx, JS::MutableHandleValue ret) const
{
JSAutoRequest rq(cx);
if (m_Childs.empty())
{
// Empty node - map to undefined
if (m_Value.empty())
{
ret.setUndefined();
return;
}
// Just a string
utf16string text(m_Value.begin(), m_Value.end());
JS::RootedString str(cx, JS_InternUCStringN(cx, reinterpret_cast<const jschar*>(text.data()), text.length()));
if (str)
{
ret.setString(str);
return;
}
// TODO: report error
ret.setUndefined();
return;
}
// Got child nodes - convert this node into a hash-table-style object:
JS::RootedObject obj(cx, JS_NewObject(cx, nullptr, JS::NullPtr(), JS::NullPtr()));
if (!obj)
{
ret.setUndefined();
return; // TODO: report error
}
JS::RootedValue childVal(cx);
for (std::map<std::string, CParamNode>::const_iterator it = m_Childs.begin(); it != m_Childs.end(); ++it)
{
it->second.ConstructJSVal(cx, &childVal);
if (!JS_SetProperty(cx, obj, it->first.c_str(), childVal))
{
ret.setUndefined();
return; // TODO: report error
}
}
// If the node has a string too, add that as an extra property
if (!m_Value.empty())
{
utf16string text(m_Value.begin(), m_Value.end());
JS::RootedString str(cx, JS_InternUCStringN(cx, reinterpret_cast<const jschar*>(text.data()), text.length()));
if (!str)
{
ret.setUndefined();
return; // TODO: report error
}
JS::RootedValue childVal(cx, JS::StringValue(str));
if (!JS_SetProperty(cx, obj, "_string", childVal))
{
ret.setUndefined();
return; // TODO: report error
}
}
ret.setObject(*obj);
}
void CParamNode::ResetScriptVal()
{
m_ScriptVal = NULL;
}