mirror of
https://gitea.wildfiregames.com/0ad/0ad.git
synced 2026-09-21 20:06:40 +00:00
XMB Improvements, parse JS into XMB, make strings more efficient.
XMB format is bumped to 4, invalidating all cached files. The differences are: - element/attribute names are stored after the elements themselves, and not before. This allows writing XMB data in one pass instead of two. - names themselves becomes offsets (instead of arbitrary integers), making getting the string from the int name much more efficient. XMBFile is renamed to XMBData to clarify that it does not, in fact, refer to a file on disk. XMBData::GetElementString is also changed to return a const char*, thus not creating an std::string. A string_view version is added where convenient. The XML->XMB and JS->XMB conversion functions and the corresponding storage are moved to `ps/XMB`, since that format doesn't particularly relate to XML. CXeromyces becomes lighter and more focused as a result. The XML->XMB conversion also benefits from the above streamlining. Note that in a few cases, string_view gets printed to CLogger via data(), which is generally not legal, but we know that the strings are null-terminated here. Our libfmt (version 4) doesn't support string_view, that would be v5. Differential Revision: https://code.wildfiregames.com/D3909 This was SVN commit r25375.
This commit is contained in:
+14
-205
@@ -28,6 +28,7 @@
|
||||
#include "ps/CacheLoader.h"
|
||||
#include "ps/CLogger.h"
|
||||
#include "ps/Filesystem.h"
|
||||
|
||||
#include "RelaxNG.h"
|
||||
#include "Xeromyces.h"
|
||||
|
||||
@@ -120,13 +121,17 @@ PSRETURN CXeromyces::Load(const PIVFS& vfs, const VfsPath& filename, const std::
|
||||
validatorGrammarHash = GetValidator(validatorName).GetGrammarHash();
|
||||
}
|
||||
VfsPath xmbPath;
|
||||
Status ret = cacheLoader.TryLoadingCached(filename, validatorGrammarHash, XMBVersion, xmbPath);
|
||||
Status ret = cacheLoader.TryLoadingCached(filename, validatorGrammarHash, XMBStorage::XMBVersion, xmbPath);
|
||||
|
||||
if (ret == INFO::OK)
|
||||
{
|
||||
// Found a cached XMB - load it
|
||||
if (ReadXMBFile(vfs, xmbPath))
|
||||
if (m_Data.ReadFromFile(vfs, xmbPath))
|
||||
{
|
||||
if(!Initialise(m_Data))
|
||||
return PSRETURN_Xeromyces_XMLParseError;
|
||||
return PSRETURN_OK;
|
||||
}
|
||||
// If this fails then we'll continue and (re)create the loose cache -
|
||||
// this failure legitimately happens due to partially-written XMB files.
|
||||
}
|
||||
@@ -184,43 +189,20 @@ PSRETURN CXeromyces::ConvertFile(const PIVFS& vfs, const VfsPath& filename, cons
|
||||
}
|
||||
}
|
||||
|
||||
WriteBuffer writeBuffer;
|
||||
CreateXMB(doc, writeBuffer);
|
||||
|
||||
m_Data.LoadXMLDoc(doc);
|
||||
xmlFreeDoc(doc);
|
||||
|
||||
// Save the file to disk, so it can be loaded quickly next time.
|
||||
// Don't save if invalid, because we want the syntax error every program start.
|
||||
vfs->CreateFile(xmbPath, writeBuffer.Data(), writeBuffer.Size());
|
||||
vfs->CreateFile(xmbPath, m_Data.m_Buffer, m_Data.m_Size);
|
||||
|
||||
m_XMBBuffer = writeBuffer.Data(); // add a reference
|
||||
|
||||
// Set up the XMBFile
|
||||
const bool ok = Initialise((const char*)m_XMBBuffer.get());
|
||||
// Set up the XMBData
|
||||
const bool ok = Initialise(m_Data);
|
||||
ENSURE(ok);
|
||||
|
||||
return PSRETURN_OK;
|
||||
}
|
||||
|
||||
bool CXeromyces::ReadXMBFile(const PIVFS& vfs, const VfsPath& filename)
|
||||
{
|
||||
size_t size;
|
||||
if(vfs->LoadFile(filename, m_XMBBuffer, size) < 0)
|
||||
return false;
|
||||
// if the game crashes during loading, (e.g. due to driver bugs),
|
||||
// it sometimes leaves empty XMB files in the cache.
|
||||
// reporting failure will cause our caller to re-generate the XMB.
|
||||
if(size == 0)
|
||||
return false;
|
||||
ENSURE(size >= 4); // make sure it's at least got the initial header
|
||||
|
||||
// Set up the XMBFile
|
||||
if(!Initialise((const char*)m_XMBBuffer.get()))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
PSRETURN CXeromyces::LoadString(const char* xml, const std::string& validatorName /* = "" */)
|
||||
{
|
||||
ENSURE(g_XeromycesStarted);
|
||||
@@ -242,185 +224,12 @@ PSRETURN CXeromyces::LoadString(const char* xml, const std::string& validatorNam
|
||||
}
|
||||
}
|
||||
|
||||
WriteBuffer writeBuffer;
|
||||
CreateXMB(doc, writeBuffer);
|
||||
|
||||
m_Data.LoadXMLDoc(doc);
|
||||
xmlFreeDoc(doc);
|
||||
|
||||
m_XMBBuffer = writeBuffer.Data(); // add a reference
|
||||
|
||||
// Set up the XMBFile
|
||||
const bool ok = Initialise((const char*)m_XMBBuffer.get());
|
||||
// Set up the XMBData
|
||||
const bool ok = Initialise(m_Data);
|
||||
ENSURE(ok);
|
||||
|
||||
return PSRETURN_OK;
|
||||
}
|
||||
|
||||
|
||||
static void FindNames(const xmlNodePtr node, std::set<std::string>& elementNames, std::set<std::string>& attributeNames)
|
||||
{
|
||||
elementNames.insert((const char*)node->name);
|
||||
|
||||
for (xmlAttrPtr attr = node->properties; attr; attr = attr->next)
|
||||
attributeNames.insert((const char*)attr->name);
|
||||
|
||||
for (xmlNodePtr child = node->children; child; child = child->next)
|
||||
if (child->type == XML_ELEMENT_NODE)
|
||||
FindNames(child, elementNames, attributeNames);
|
||||
}
|
||||
|
||||
static void OutputElement(const xmlNodePtr node, WriteBuffer& writeBuffer,
|
||||
std::map<std::string, u32>& elementIDs,
|
||||
std::map<std::string, u32>& attributeIDs
|
||||
)
|
||||
{
|
||||
// Filled in later with the length of the element
|
||||
size_t posLength = writeBuffer.Size();
|
||||
writeBuffer.Append("????", 4);
|
||||
|
||||
writeBuffer.Append(&elementIDs[(const char*)node->name], 4);
|
||||
|
||||
u32 attrCount = 0;
|
||||
for (xmlAttrPtr attr = node->properties; attr; attr = attr->next)
|
||||
++attrCount;
|
||||
writeBuffer.Append(&attrCount, 4);
|
||||
|
||||
u32 childCount = 0;
|
||||
for (xmlNodePtr child = node->children; child; child = child->next)
|
||||
if (child->type == XML_ELEMENT_NODE)
|
||||
++childCount;
|
||||
writeBuffer.Append(&childCount, 4);
|
||||
|
||||
// Filled in later with the offset to the list of child elements
|
||||
size_t posChildrenOffset = writeBuffer.Size();
|
||||
writeBuffer.Append("????", 4);
|
||||
|
||||
|
||||
// Trim excess whitespace in the entity's text, while counting
|
||||
// the number of newlines trimmed (so that JS error reporting
|
||||
// can give the correct line number within the script)
|
||||
|
||||
std::string whitespace = " \t\r\n";
|
||||
std::string text;
|
||||
for (xmlNodePtr child = node->children; child; child = child->next)
|
||||
{
|
||||
if (child->type == XML_TEXT_NODE)
|
||||
{
|
||||
xmlChar* content = xmlNodeGetContent(child);
|
||||
text += std::string((const char*)content);
|
||||
xmlFree(content);
|
||||
}
|
||||
}
|
||||
|
||||
u32 linenum = xmlGetLineNo(node);
|
||||
|
||||
// Find the start of the non-whitespace section
|
||||
size_t first = text.find_first_not_of(whitespace);
|
||||
|
||||
if (first == text.npos)
|
||||
// Entirely whitespace - easy to handle
|
||||
text = "";
|
||||
|
||||
else
|
||||
{
|
||||
// Count the number of \n being cut off,
|
||||
// and add them to the line number
|
||||
std::string trimmed (text.begin(), text.begin()+first);
|
||||
linenum += std::count(trimmed.begin(), trimmed.end(), '\n');
|
||||
|
||||
// Find the end of the non-whitespace section,
|
||||
// and trim off everything else
|
||||
size_t last = text.find_last_not_of(whitespace);
|
||||
text = text.substr(first, 1+last-first);
|
||||
}
|
||||
|
||||
|
||||
// Output text, prefixed by length in bytes
|
||||
if (text.length() == 0)
|
||||
{
|
||||
// No text; don't write much
|
||||
writeBuffer.Append("\0\0\0\0", 4);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Write length and line number and null-terminated text
|
||||
u32 nodeLen = u32(4 + text.length()+1);
|
||||
writeBuffer.Append(&nodeLen, 4);
|
||||
writeBuffer.Append(&linenum, 4);
|
||||
writeBuffer.Append((void*)text.c_str(), nodeLen-4);
|
||||
}
|
||||
|
||||
// Output attributes
|
||||
for (xmlAttrPtr attr = node->properties; attr; attr = attr->next)
|
||||
{
|
||||
writeBuffer.Append(&attributeIDs[(const char*)attr->name], 4);
|
||||
|
||||
xmlChar* value = xmlNodeGetContent(attr->children);
|
||||
u32 attrLen = u32(xmlStrlen(value)+1);
|
||||
writeBuffer.Append(&attrLen, 4);
|
||||
writeBuffer.Append((void*)value, attrLen);
|
||||
xmlFree(value);
|
||||
}
|
||||
|
||||
// Go back and fill in the child-element offset
|
||||
u32 childrenOffset = (u32)(writeBuffer.Size() - (posChildrenOffset+4));
|
||||
writeBuffer.Overwrite(&childrenOffset, 4, posChildrenOffset);
|
||||
|
||||
// Output all child elements
|
||||
for (xmlNodePtr child = node->children; child; child = child->next)
|
||||
if (child->type == XML_ELEMENT_NODE)
|
||||
OutputElement(child, writeBuffer, elementIDs, attributeIDs);
|
||||
|
||||
// Go back and fill in the length
|
||||
u32 length = (u32)(writeBuffer.Size() - posLength);
|
||||
writeBuffer.Overwrite(&length, 4, posLength);
|
||||
}
|
||||
|
||||
PSRETURN CXeromyces::CreateXMB(const xmlDocPtr doc, WriteBuffer& writeBuffer)
|
||||
{
|
||||
// Header
|
||||
writeBuffer.Append(UnfinishedHeaderMagicStr, 4);
|
||||
// Version
|
||||
writeBuffer.Append(&XMBVersion, 4);
|
||||
|
||||
u32 i;
|
||||
|
||||
// Find the unique element/attribute names
|
||||
std::set<std::string> elementNames;
|
||||
std::set<std::string> attributeNames;
|
||||
FindNames(xmlDocGetRootElement(doc), elementNames, attributeNames);
|
||||
|
||||
std::map<std::string, u32> elementIDs;
|
||||
std::map<std::string, u32> attributeIDs;
|
||||
|
||||
// Output element names
|
||||
i = 0;
|
||||
u32 elementCount = (u32)elementNames.size();
|
||||
writeBuffer.Append(&elementCount, 4);
|
||||
for (const std::string& n : elementNames)
|
||||
{
|
||||
u32 textLen = (u32)n.length()+1;
|
||||
writeBuffer.Append(&textLen, 4);
|
||||
writeBuffer.Append((void*)n.c_str(), textLen);
|
||||
elementIDs[n] = i++;
|
||||
}
|
||||
|
||||
// Output attribute names
|
||||
i = 0;
|
||||
u32 attributeCount = (u32)attributeNames.size();
|
||||
writeBuffer.Append(&attributeCount, 4);
|
||||
for (const std::string& n : attributeNames)
|
||||
{
|
||||
u32 textLen = (u32)n.length()+1;
|
||||
writeBuffer.Append(&textLen, 4);
|
||||
writeBuffer.Append((void*)n.c_str(), textLen);
|
||||
attributeIDs[n] = i++;
|
||||
}
|
||||
|
||||
OutputElement(xmlDocGetRootElement(doc), writeBuffer, elementIDs, attributeIDs);
|
||||
|
||||
// file is now valid, so insert correct magic string
|
||||
writeBuffer.Overwrite(HeaderMagicStr, 4, 0);
|
||||
|
||||
return PSRETURN_OK;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user