LCOV - code coverage report
Current view: top level - source/ps - TemplateLoader.cpp (source / functions) Hit Total Coverage
Test: 0 A.D. test coverage report Lines: 46 85 54.1 %
Date: 2023-01-19 00:18:29 Functions: 5 10 50.0 %

          Line data    Source code
       1             : /* Copyright (C) 2023 Wildfire Games.
       2             :  * This file is part of 0 A.D.
       3             :  *
       4             :  * 0 A.D. is free software: you can redistribute it and/or modify
       5             :  * it under the terms of the GNU General Public License as published by
       6             :  * the Free Software Foundation, either version 2 of the License, or
       7             :  * (at your option) any later version.
       8             :  *
       9             :  * 0 A.D. is distributed in the hope that it will be useful,
      10             :  * but WITHOUT ANY WARRANTY; without even the implied warranty of
      11             :  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      12             :  * GNU General Public License for more details.
      13             :  *
      14             :  * You should have received a copy of the GNU General Public License
      15             :  * along with 0 A.D.  If not, see <http://www.gnu.org/licenses/>.
      16             :  */
      17             : 
      18             : #include "precompiled.h"
      19             : 
      20             : #include "TemplateLoader.h"
      21             : 
      22             : #include "lib/utf8.h"
      23             : #include "ps/CLogger.h"
      24             : #include "ps/Filesystem.h"
      25             : #include "ps/XML/Xeromyces.h"
      26             : 
      27             : static const wchar_t TEMPLATE_ROOT[] = L"simulation/templates/";
      28             : static const wchar_t ACTOR_ROOT[] = L"art/actors/";
      29             : 
      30           1 : static CParamNode NULL_NODE(false);
      31             : 
      32         356 : bool CTemplateLoader::LoadTemplateFile(CParamNode& node, std::string_view templateName, bool compositing, int depth)
      33             : {
      34             :     // Handle special case "actor|foo", which does not load 'foo' at all, just uses the name.
      35         356 :     if (templateName.compare(0, 6, "actor|") == 0)
      36             :     {
      37           4 :         ConstructTemplateActor(templateName.substr(6), node);
      38           4 :         return true;
      39             :     }
      40             :     // Handle infinite loops more gracefully than running out of stack space and crashing
      41         352 :     if (depth > 100)
      42             :     {
      43           3 :         LOGERROR("Probable infinite inheritance loop in entity template '%s'", templateName);
      44           3 :         return false;
      45             :     }
      46             : 
      47         349 :     size_t pos = templateName.find_first_of('|');
      48         349 :     if (pos != std::string::npos)
      49             :     {
      50             :         // 'foo|bar' pattern: 'bar' is treated as the parent of 'foo'.
      51           2 :         if (!LoadTemplateFile(node, templateName.substr(pos + 1), false, depth + 1))
      52           1 :             return false;
      53           1 :         if (!LoadTemplateFile(node, templateName.substr(0, pos), true, depth + 1))
      54           0 :             return false;
      55           1 :         return true;
      56             :     }
      57             : 
      58             :     // Load the data we need to apply on the node. This data may contain special modifiers,
      59             :     // such as filters, merges, multiplying the parent values, etc. Applying it to paramnode is destructive.
      60             :     // Find the XML file to load - by default, this assumes the files reside in 'special/filter'.
      61             :     // If not found there, it will be searched for in 'mixins/', then from the root.
      62             :     // The reason for this order is that filters are used at runtime, mixins at load time.
      63         694 :     std::wstring wtempName = wstring_from_utf8(std::string(templateName) + ".xml");
      64         694 :     VfsPath path = VfsPath(TEMPLATE_ROOT) / L"special" / L"filter" / wtempName;
      65         347 :     if (!VfsFileExists(path))
      66         346 :         path = VfsPath(TEMPLATE_ROOT) / L"mixins" / wtempName;
      67         347 :     if (!VfsFileExists(path))
      68         346 :         path = VfsPath(TEMPLATE_ROOT) / wtempName;
      69             : 
      70         694 :     CXeromyces xero;
      71         347 :     PSRETURN ok = xero.Load(g_VFS, path);
      72         347 :     if (ok != PSRETURN_OK)
      73          10 :         return false; // (Xeromyces already logged an error with the full filename)
      74             : 
      75             :     // If the layer defines an explicit parent, we must load that and apply it before ourselves.
      76         337 :     int attr_parent = xero.GetAttributeID("parent");
      77         674 :     CStr parentName = xero.GetRoot().GetAttributes().GetNamedItem(attr_parent);
      78         337 :     if (!parentName.empty() && !LoadTemplateFile(node, parentName, compositing, depth + 1))
      79         306 :         return false;
      80             : 
      81             :     // Load the new file into the template data (overriding parent values).
      82             :     // TODO: error handling.
      83          31 :     CParamNode::LoadXML(node, xero);
      84          31 :     return true;
      85             : }
      86             : 
      87           0 : static Status AddToTemplates(const VfsPath& pathname, const CFileInfo& UNUSED(fileInfo), const uintptr_t cbData)
      88             : {
      89           0 :     std::vector<std::string>& templates = *(std::vector<std::string>*)cbData;
      90             : 
      91             :     // Strip the .xml extension
      92           0 :     VfsPath pathstem = pathname.ChangeExtension(L"");
      93             :     // Strip the root from the path
      94           0 :     std::string name = pathstem.string8().substr(ARRAY_SIZE(TEMPLATE_ROOT)-1);
      95             : 
      96             :     // We want to ignore template_*.xml templates, since they may be incompleat and should never be loaded on their own.
      97           0 :     if (name.substr(0, 9) == "template_")
      98           0 :         return INFO::OK;
      99             : 
     100             :     // Also ignore some subfolders.
     101           0 :     if (name.substr(0, 7) == "mixins/")
     102           0 :         return INFO::OK;
     103             : 
     104           0 :     templates.push_back(name);
     105           0 :     return INFO::OK;
     106             : }
     107             : 
     108           0 : static Status AddActorToTemplates(const VfsPath& pathname, const CFileInfo& UNUSED(fileInfo), const uintptr_t cbData)
     109             : {
     110           0 :     std::vector<std::string>& templates = *(std::vector<std::string>*)cbData;
     111             : 
     112             :     // Strip the root from the path
     113           0 :     std::wstring name = pathname.string().substr(ARRAY_SIZE(ACTOR_ROOT)-1);
     114             : 
     115           0 :     templates.push_back("actor|" + std::string(name.begin(), name.end()));
     116           0 :     return INFO::OK;
     117             : }
     118             : 
     119           0 : bool CTemplateLoader::TemplateExists(const std::string& templateName) const
     120             : {
     121           0 :     size_t pos = templateName.rfind('|');
     122           0 :     std::string baseName(pos != std::string::npos ? templateName.substr(pos+1) : templateName);
     123           0 :     return VfsFileExists(VfsPath(TEMPLATE_ROOT) / wstring_from_utf8(baseName + ".xml"));
     124             : }
     125             : 
     126           0 : std::vector<std::string> CTemplateLoader::FindTemplates(const std::string& path, bool includeSubdirectories, ETemplatesType templatesType) const
     127             : {
     128           0 :     std::vector<std::string> templates;
     129             : 
     130           0 :     if (templatesType != SIMULATION_TEMPLATES && templatesType != ACTOR_TEMPLATES && templatesType != ALL_TEMPLATES)
     131             :     {
     132           0 :         LOGERROR("Undefined template type (valid: all, simulation, actor)");
     133           0 :         return templates;
     134             :     }
     135             : 
     136           0 :     size_t flags = includeSubdirectories ? vfs::DIR_RECURSIVE : 0;
     137             : 
     138           0 :     if (templatesType == SIMULATION_TEMPLATES || templatesType == ALL_TEMPLATES)
     139           0 :         WARN_IF_ERR(vfs::ForEachFile(g_VFS, VfsPath(TEMPLATE_ROOT) / path, AddToTemplates, (uintptr_t)&templates, L"*.xml", flags));
     140             : 
     141           0 :     if (templatesType == ACTOR_TEMPLATES || templatesType == ALL_TEMPLATES)
     142           0 :         WARN_IF_ERR(vfs::ForEachFile(g_VFS, VfsPath(ACTOR_ROOT) / path, AddActorToTemplates, (uintptr_t)&templates, L"*.xml", flags));
     143             : 
     144           0 :     return templates;
     145             : }
     146             : 
     147           0 : std::vector<std::string> CTemplateLoader::FindTemplatesWithRootName(const std::string& path, bool includeSubdirectories, ETemplatesType templatesType, const std::string& rootName)
     148             : {
     149           0 :     std::vector<std::string> templates = FindTemplates(path, includeSubdirectories, templatesType);
     150           0 :     std::vector<std::string> templatesWithCorrectRootName;
     151             : 
     152           0 :     for (const std::string& templateName : templates)
     153             :     {
     154           0 :         const CParamNode& templateData = GetTemplateFileData(templateName);
     155           0 :         if (templateData.GetOnlyChild().GetName() == rootName)
     156           0 :             templatesWithCorrectRootName.push_back(templateName);
     157             :     }
     158             : 
     159           0 :     return templatesWithCorrectRootName;
     160             : }
     161             : 
     162          45 : const CParamNode& CTemplateLoader::GetTemplateFileData(const std::string& templateName)
     163             : {
     164          45 :     if (std::unordered_map<std::string, CParamNode>::const_iterator it = m_TemplateFileData.find(templateName); it != m_TemplateFileData.end())
     165           5 :         return it->second;
     166             : 
     167          80 :     CParamNode ret;
     168          40 :     if (!LoadTemplateFile(ret, templateName, false, 0))
     169             :     {
     170          13 :         LOGERROR("Failed to load entity template '%s'", templateName.c_str());
     171          13 :         return NULL_NODE;
     172             :     }
     173          27 :     return m_TemplateFileData.insert_or_assign(templateName, ret).first->second;
     174             : }
     175             : 
     176           4 : void CTemplateLoader::ConstructTemplateActor(std::string_view actorName, CParamNode& out)
     177             : {
     178             :     // Copy the actor template
     179           4 :     out = GetTemplateFileData("special/actor");
     180             : 
     181             :     // Initialize the actor's name and make it an Atlas selectable entity.
     182           8 :     std::string source(actorName);
     183           8 :     std::wstring actorNameW = wstring_from_utf8(source);
     184           4 :     source = "<Entity>"
     185           8 :                "<VisualActor><Actor>" + source + "</Actor><ActorOnly/></VisualActor>"
     186             :                // Arbitrary-sized Footprint definition to make actors' selection outlines show up in Atlas.
     187             :                "<Footprint><Circle radius='2.0'/><Height>1.0</Height></Footprint>"
     188             :                  "<Selectable>"
     189             :                    "<EditorOnly/>"
     190             :                    "<Overlay><Texture><MainTexture>128x128/ellipse.png</MainTexture><MainTextureMask>128x128/ellipse_mask.png</MainTextureMask></Texture></Overlay>"
     191             :                  "</Selectable>"
     192             :                "</Entity>";
     193             :     // We'll assume that actorName is valid XML, otherwise this will fail and report the error anyways.
     194           4 :     CParamNode::LoadXMLString(out, source.c_str(), actorNameW.c_str());
     195           7 : }

Generated by: LCOV version 1.13