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: 42 79 53.2 %
Date: 2022-06-14 00:41:00 Functions: 3 9 33.3 %

          Line data    Source code
       1             : /* Copyright (C) 2022 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             : 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           9 :         LOGERROR("Probable infinite inheritance loop in entity template '%s'", std::string(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             :             return false;
      53           1 :         if (!LoadTemplateFile(node, templateName.substr(0, pos), true, depth + 1))
      54             :             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        1041 :     std::wstring wtempName = wstring_from_utf8(std::string(templateName) + ".xml");
      64        2429 :     VfsPath path = VfsPath(TEMPLATE_ROOT) / L"special" / L"filter" / wtempName;
      65         347 :     if (!VfsFileExists(path))
      66        2422 :         path = VfsPath(TEMPLATE_ROOT) / L"mixins" / wtempName;
      67         347 :     if (!VfsFileExists(path))
      68        1730 :         path = VfsPath(TEMPLATE_ROOT) / wtempName;
      69             : 
      70        1031 :     CXeromyces xero;
      71         694 :     PSRETURN ok = xero.Load(g_VFS, path);
      72         347 :     if (ok != PSRETURN_OK)
      73             :         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         674 :     if (!parentName.empty() && !LoadTemplateFile(node, parentName, compositing, depth + 1))
      79             :         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             :     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 should never be built in the editor
      97           0 :     if (name.substr(0, 9) == "template_")
      98             :         return INFO::OK;
      99             : 
     100             :     // Also ignore some subfolders.
     101           0 :     if (name.substr(0, 8) == "special/" || name.substr(0, 7) == "mixins/")
     102             :         return INFO::OK;
     103             : 
     104           0 :     templates.push_back(name);
     105             :     return INFO::OK;
     106             : }
     107             : 
     108           0 : static Status AddToTemplatesUnrestricted(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           0 :     VfsPath pathstem = pathname.ChangeExtension(L"");
     113           0 :     std::string name = pathstem.string8().substr(ARRAY_SIZE(TEMPLATE_ROOT)-1);
     114             : 
     115             :     // We want to ignore template_*.xml templates, since they may be incomplete.
     116           0 :     if (name.substr(0, 9) == "template_")
     117             :         return INFO::OK;
     118             : 
     119           0 :     templates.push_back(name);
     120             :     return INFO::OK;
     121             : }
     122             : 
     123           0 : static Status AddActorToTemplates(const VfsPath& pathname, const CFileInfo& UNUSED(fileInfo), const uintptr_t cbData)
     124             : {
     125           0 :     std::vector<std::string>& templates = *(std::vector<std::string>*)cbData;
     126             : 
     127             :     // Strip the root from the path
     128           0 :     std::wstring name = pathname.string().substr(ARRAY_SIZE(ACTOR_ROOT)-1);
     129             : 
     130           0 :     templates.push_back("actor|" + std::string(name.begin(), name.end()));
     131           0 :     return INFO::OK;
     132             : }
     133             : 
     134           0 : bool CTemplateLoader::TemplateExists(const std::string& templateName) const
     135             : {
     136           0 :     size_t pos = templateName.rfind('|');
     137           0 :     std::string baseName(pos != std::string::npos ? templateName.substr(pos+1) : templateName);
     138           0 :     return VfsFileExists(VfsPath(TEMPLATE_ROOT) / wstring_from_utf8(baseName + ".xml"));
     139             : }
     140             : 
     141           0 : std::vector<std::string> CTemplateLoader::FindTemplates(const std::string& path, bool includeSubdirectories, ETemplatesType templatesType) const
     142             : {
     143           0 :     std::vector<std::string> templates;
     144             : 
     145           0 :     if (templatesType != SIMULATION_TEMPLATES && templatesType != ACTOR_TEMPLATES && templatesType != ALL_TEMPLATES)
     146             :     {
     147           0 :         LOGERROR("Undefined template type (valid: all, simulation, actor)");
     148           0 :         return templates;
     149             :     }
     150             : 
     151           0 :     size_t flags = includeSubdirectories ? vfs::DIR_RECURSIVE : 0;
     152             : 
     153           0 :     if (templatesType == SIMULATION_TEMPLATES || templatesType == ALL_TEMPLATES)
     154           0 :         WARN_IF_ERR(vfs::ForEachFile(g_VFS, VfsPath(TEMPLATE_ROOT) / path, AddToTemplates, (uintptr_t)&templates, L"*.xml", flags));
     155             : 
     156           0 :     if (templatesType == ACTOR_TEMPLATES || templatesType == ALL_TEMPLATES)
     157           0 :         WARN_IF_ERR(vfs::ForEachFile(g_VFS, VfsPath(ACTOR_ROOT) / path, AddActorToTemplates, (uintptr_t)&templates, L"*.xml", flags));
     158             : 
     159             :     return templates;
     160             : }
     161             : 
     162           0 : std::vector<std::string> CTemplateLoader::FindTemplatesUnrestricted(const std::string& path, bool includeSubdirectories) const
     163             : {
     164           0 :     std::vector<std::string> templates;
     165             : 
     166           0 :     size_t flags = includeSubdirectories ? vfs::DIR_RECURSIVE : 0;
     167             : 
     168           0 :     WARN_IF_ERR(vfs::ForEachFile(g_VFS, VfsPath(TEMPLATE_ROOT) / path, AddToTemplatesUnrestricted, (uintptr_t)&templates, L"*.xml", flags));
     169             : 
     170           0 :     return templates;
     171             : }
     172             : 
     173          45 : const CParamNode& CTemplateLoader::GetTemplateFileData(const std::string& templateName)
     174             : {
     175          45 :     if (std::unordered_map<std::string, CParamNode>::const_iterator it = m_TemplateFileData.find(templateName); it != m_TemplateFileData.end())
     176          10 :         return it->second;
     177             : 
     178          80 :     CParamNode ret;
     179          80 :     if (!LoadTemplateFile(ret, templateName, false, 0))
     180             :     {
     181          39 :         LOGERROR("Failed to load entity template '%s'", templateName.c_str());
     182          13 :         return NULL_NODE;
     183             :     }
     184          27 :     return m_TemplateFileData.insert_or_assign(templateName, ret).first->second;
     185             : }
     186             : 
     187           4 : void CTemplateLoader::ConstructTemplateActor(std::string_view actorName, CParamNode& out)
     188             : {
     189             :     // Copy the actor template
     190           8 :     out = GetTemplateFileData("special/actor");
     191             : 
     192             :     // Initialize the actor's name and make it an Atlas selectable entity.
     193           8 :     std::string source(actorName);
     194          12 :     std::wstring actorNameW = wstring_from_utf8(source);
     195           4 :     source = "<Entity>"
     196           8 :                "<VisualActor><Actor>" + source + "</Actor><ActorOnly/></VisualActor>"
     197             :                // Arbitrary-sized Footprint definition to make actors' selection outlines show up in Atlas.
     198             :                "<Footprint><Circle radius='2.0'/><Height>1.0</Height></Footprint>"
     199             :                  "<Selectable>"
     200             :                    "<EditorOnly/>"
     201             :                    "<Overlay><Texture><MainTexture>128x128/ellipse.png</MainTexture><MainTextureMask>128x128/ellipse_mask.png</MainTextureMask></Texture></Overlay>"
     202             :                  "</Selectable>"
     203           4 :                "</Entity>";
     204             :     // We'll assume that actorName is valid XML, otherwise this will fail and report the error anyways.
     205          12 :     CParamNode::LoadXMLString(out, source.c_str(), actorNameW.c_str());
     206           4 : }

Generated by: LCOV version 1.13