LCOV - code coverage report
Current view: top level - source/graphics - ObjectManager.cpp (source / functions) Hit Total Coverage
Test: 0 A.D. test coverage report Lines: 1 112 0.9 %
Date: 2023-01-19 00:18:29 Functions: 2 17 11.8 %

          Line data    Source code
       1             : /* Copyright (C) 2021 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 "ObjectManager.h"
      21             : 
      22             : #include "graphics/ObjectBase.h"
      23             : #include "graphics/ObjectEntry.h"
      24             : #include "ps/CLogger.h"
      25             : #include "ps/ConfigDB.h"
      26             : #include "ps/Game.h"
      27             : #include "ps/Profile.h"
      28             : #include "ps/Filesystem.h"
      29             : #include "ps/XML/Xeromyces.h"
      30             : #include "simulation2/Simulation2.h"
      31             : #include "simulation2/components/ICmpTerrain.h"
      32             : #include "simulation2/components/ICmpVisual.h"
      33             : 
      34           0 : bool CObjectManager::ObjectKey::operator< (const CObjectManager::ObjectKey& a) const
      35             : {
      36           0 :     if (ObjectBaseIdentifier < a.ObjectBaseIdentifier)
      37           0 :         return true;
      38           0 :     else if (ObjectBaseIdentifier > a.ObjectBaseIdentifier)
      39           0 :         return false;
      40             :     else
      41           0 :         return ActorVariation < a.ActorVariation;
      42             : }
      43             : 
      44           0 : static Status ReloadChangedFileCB(void* param, const VfsPath& path)
      45             : {
      46           0 :     return static_cast<CObjectManager*>(param)->ReloadChangedFile(path);
      47             : }
      48             : 
      49           0 : CObjectManager::CObjectManager(CMeshManager& meshManager, CSkeletonAnimManager& skeletonAnimManager, CSimulation2& simulation)
      50           0 : : m_MeshManager(meshManager), m_SkeletonAnimManager(skeletonAnimManager), m_Simulation(simulation)
      51             : {
      52           0 :     RegisterFileReloadFunc(ReloadChangedFileCB, this);
      53             : 
      54           0 :     m_QualityHook = std::make_unique<CConfigDBHook>(g_ConfigDB.RegisterHookAndCall("max_actor_quality", [this]() { ActorQualityChanged(); }));
      55           0 :     m_VariantDiversityHook = std::make_unique<CConfigDBHook>(g_ConfigDB.RegisterHookAndCall("variant_diversity", [this]() { VariantDiversityChanged(); }));
      56             : 
      57           0 :     if (!CXeromyces::AddValidator(g_VFS, "actor", "art/actors/actor.rng"))
      58           0 :         LOGERROR("CObjectManager: failed to load actor grammar file 'art/actors/actor.rng'");
      59           0 : }
      60             : 
      61           0 : CObjectManager::~CObjectManager()
      62             : {
      63           0 :     UnloadObjects();
      64             : 
      65           0 :     UnregisterFileReloadFunc(ReloadChangedFileCB, this);
      66           0 : }
      67             : 
      68           0 : std::pair<bool, CActorDef&> CObjectManager::FindActorDef(const CStrW& actorName)
      69             : {
      70           0 :     ENSURE(!actorName.empty());
      71             : 
      72           0 :     decltype(m_ActorDefs)::iterator it = m_ActorDefs.find(actorName);
      73           0 :     if (it != m_ActorDefs.end() && !it->second.outdated)
      74           0 :         return { true, *it->second.obj };
      75             : 
      76           0 :     std::unique_ptr<CActorDef> actor = std::make_unique<CActorDef>(*this);
      77             : 
      78           0 :     VfsPath pathname = VfsPath("art/actors/") / actorName;
      79             : 
      80           0 :     bool success = true;
      81           0 :     if (!actor->Load(pathname))
      82             :     {
      83             :         // In case of failure, load a placeholder - we want to have an actor around for hotloading.
      84             :         // (this will leave garbage actors in the object manager if loading files with typos in the name,
      85             :         // but that's unlikely to be a large memory problem).
      86           0 :         LOGERROR("CObjectManager::FindActorDef(): Cannot find actor '%s'", utf8_from_wstring(actorName));
      87           0 :         actor->LoadErrorPlaceholder(pathname);
      88           0 :         success = false;
      89             :     }
      90             : 
      91           0 :     return { success, *m_ActorDefs.insert_or_assign(actorName, std::move(actor)).first->second.obj };
      92             : }
      93             : 
      94           0 : CObjectEntry* CObjectManager::FindObjectVariation(const CActorDef* actor, const std::vector<std::set<CStr>>& selections, uint32_t seed)
      95             : {
      96           0 :     if (!actor)
      97           0 :         return nullptr;
      98             : 
      99           0 :     const std::shared_ptr<CObjectBase>& base = actor->GetBase(m_QualityLevel);
     100             : 
     101           0 :     std::vector<const std::set<CStr>*> completeSelections;
     102           0 :     for (const std::set<CStr>& selectionSet : selections)
     103           0 :         completeSelections.emplace_back(&selectionSet);
     104             :     // To maintain a consistent look between quality levels, first complete with the highest-quality variants.
     105             :     // then complete again at the required quality level (since not all variants may be available).
     106           0 :     std::set<CStr> highQualitySelections = actor->GetBase(255)->CalculateRandomRemainingSelections(seed, selections);
     107           0 :     completeSelections.emplace_back(&highQualitySelections);
     108             :     // We don't have to pass the high-quality selections here because they have higher priority anyways.
     109           0 :     std::set<CStr> remainingSelections = base->CalculateRandomRemainingSelections(seed, selections);
     110           0 :     completeSelections.emplace_back(&remainingSelections);
     111           0 :     return FindObjectVariation(base, completeSelections);
     112             : }
     113             : 
     114           0 : CObjectEntry* CObjectManager::FindObjectVariation(const std::shared_ptr<CObjectBase>& base, const std::vector<const std::set<CStr>*>& completeSelections)
     115             : {
     116           0 :     PROFILE2("FindObjectVariation");
     117             : 
     118             :     // Look to see whether this particular variation has already been loaded
     119           0 :     std::vector<u8> choices = base->CalculateVariationKey(completeSelections);
     120           0 :     ObjectKey key (base->GetIdentifier(), choices);
     121           0 :     decltype(m_Objects)::iterator it = m_Objects.find(key);
     122           0 :     if (it != m_Objects.end() && !it->second.outdated)
     123           0 :         return it->second.obj.get();
     124             : 
     125             :     // If it hasn't been loaded, load it now.
     126             : 
     127           0 :     std::unique_ptr<CObjectEntry> obj = std::make_unique<CObjectEntry>(base, m_Simulation);
     128             : 
     129             :     // TODO (for some efficiency): use the pre-calculated choices for this object,
     130             :     // which has already worked out what to do for props, instead of passing the
     131             :     // selections into BuildVariation and having it recalculate the props' choices.
     132             : 
     133           0 :     if (!obj->BuildVariation(completeSelections, choices, *this))
     134           0 :         return nullptr;
     135             : 
     136           0 :     return m_Objects.insert_or_assign(key, std::move(obj)).first->second.obj.get();
     137             : }
     138             : 
     139           0 : CTerrain* CObjectManager::GetTerrain()
     140             : {
     141           0 :     CmpPtr<ICmpTerrain> cmpTerrain(m_Simulation, SYSTEM_ENTITY);
     142           0 :     if (!cmpTerrain)
     143           0 :         return NULL;
     144           0 :     return cmpTerrain->GetCTerrain();
     145             : }
     146             : 
     147           0 : CObjectManager::VariantDiversity CObjectManager::GetVariantDiversity() const
     148             : {
     149           0 :     return m_VariantDiversity;
     150             : }
     151             : 
     152           0 : void CObjectManager::UnloadObjects()
     153             : {
     154           0 :     m_Objects.clear();
     155           0 :     m_ActorDefs.clear();
     156           0 : }
     157             : 
     158           0 : Status CObjectManager::ReloadChangedFile(const VfsPath& path)
     159             : {
     160           0 :     bool changed = false;
     161             : 
     162             :     // Mark old entries as outdated so we don't reload them from the cache
     163           0 :     for (std::pair<const ObjectKey, Hotloadable<CObjectEntry>>& object : m_Objects)
     164           0 :         if (!object.second.outdated && object.second.obj->m_Base->UsesFile(path))
     165             :         {
     166           0 :             object.second.outdated = true;
     167           0 :             changed = true;
     168             :         }
     169             : 
     170           0 :     const CSimulation2::InterfaceListUnordered& cmps = m_Simulation.GetEntitiesWithInterfaceUnordered(IID_Visual);
     171             : 
     172             :     // Reload actors that use a changed object
     173           0 :     for (std::pair<const CStrW, Hotloadable<CActorDef>>& actor : m_ActorDefs)
     174             :     {
     175           0 :         if (!actor.second.outdated && actor.second.obj->UsesFile(path))
     176             :         {
     177           0 :             actor.second.outdated = true;
     178           0 :             changed = true;
     179             : 
     180             :             // Slightly ugly hack: The graphics system doesn't preserve enough information to regenerate the
     181             :             // object with all correct variations, and we don't want to waste space storing it just for the
     182             :             // rare occurrence of hotloading, so we'll tell the component (which does preserve the information)
     183             :             // to do the reloading itself
     184           0 :             for (CSimulation2::InterfaceListUnordered::const_iterator eit = cmps.begin(); eit != cmps.end(); ++eit)
     185           0 :                 static_cast<ICmpVisual*>(eit->second)->Hotload(actor.first);
     186             :         }
     187             :     }
     188             : 
     189           0 :     if (changed)
     190             :         // Trigger an interpolate call - needed because the game may be paused & if so, models disappear.
     191           0 :         m_Simulation.Interpolate(0.f, 0.f, 0.f);
     192             : 
     193           0 :     return INFO::OK;
     194             : }
     195             : 
     196           0 : void CObjectManager::ActorQualityChanged()
     197             : {
     198             :     int quality;
     199           0 :     CFG_GET_VAL("max_actor_quality", quality);
     200           0 :     if (quality == m_QualityLevel)
     201           0 :         return;
     202             : 
     203           0 :     m_QualityLevel = quality > 255 ? 255 : quality < 0 ? 0 : quality;
     204             : 
     205             :     // No need to reload entries or actors, but we do need to reload all units.
     206           0 :     const CSimulation2::InterfaceListUnordered& cmps = m_Simulation.GetEntitiesWithInterfaceUnordered(IID_Visual);
     207           0 :     for (CSimulation2::InterfaceListUnordered::const_iterator eit = cmps.begin(); eit != cmps.end(); ++eit)
     208           0 :         static_cast<ICmpVisual*>(eit->second)->Hotload();
     209             : 
     210             :     // Trigger an interpolate call - needed because the game is generally paused & models disappear otherwise.
     211           0 :     m_Simulation.Interpolate(0.f, 0.f, 0.f);
     212             : }
     213             : 
     214           0 : void CObjectManager::VariantDiversityChanged()
     215             : {
     216           0 :     CStr value;
     217           0 :     CFG_GET_VAL("variant_diversity", value);
     218           0 :     VariantDiversity variantDiversity = VariantDiversity::FULL;
     219           0 :     if (value == "none")
     220           0 :         variantDiversity = VariantDiversity::NONE;
     221           0 :     else if (value == "limited")
     222           0 :         variantDiversity = VariantDiversity::LIMITED;
     223             :     // Otherwise assume full.
     224             : 
     225           0 :     if (variantDiversity == m_VariantDiversity)
     226           0 :         return;
     227             : 
     228           0 :     m_VariantDiversity = variantDiversity;
     229             : 
     230             :     // Mark old entries as outdated so we don't reload them from the cache.
     231           0 :     for (std::pair<const ObjectKey, Hotloadable<CObjectEntry>>& object : m_Objects)
     232           0 :         object.second.outdated = true;
     233             : 
     234             :     // Reload actors.
     235           0 :     for (std::pair<const CStrW, Hotloadable<CActorDef>>& actor : m_ActorDefs)
     236           0 :         actor.second.outdated = true;
     237             : 
     238             :     // Reload visual actor components.
     239           0 :     const CSimulation2::InterfaceListUnordered& cmps = m_Simulation.GetEntitiesWithInterfaceUnordered(IID_Visual);
     240           0 :     for (CSimulation2::InterfaceListUnordered::const_iterator eit = cmps.begin(); eit != cmps.end(); ++eit)
     241           0 :         static_cast<ICmpVisual*>(eit->second)->Hotload();
     242             : 
     243             :     // Trigger an interpolate call - needed because the game is generally paused & models disappear otherwise.
     244           0 :     m_Simulation.Interpolate(0.f, 0.f, 0.f);
     245           3 : }

Generated by: LCOV version 1.13