[Gameplay] Reduce loom cost
[0ad.git] / source / graphics / ObjectManager.cpp
blobf3acf11fe97c575bbeac5e7a21d25e36849356a4
1 /* Copyright (C) 2021 Wildfire Games.
2 * This file is part of 0 A.D.
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.
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.
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/>.
18 #include "precompiled.h"
20 #include "ObjectManager.h"
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"
34 bool CObjectManager::ObjectKey::operator< (const CObjectManager::ObjectKey& a) const
36 if (ObjectBaseIdentifier < a.ObjectBaseIdentifier)
37 return true;
38 else if (ObjectBaseIdentifier > a.ObjectBaseIdentifier)
39 return false;
40 else
41 return ActorVariation < a.ActorVariation;
44 static Status ReloadChangedFileCB(void* param, const VfsPath& path)
46 return static_cast<CObjectManager*>(param)->ReloadChangedFile(path);
49 CObjectManager::CObjectManager(CMeshManager& meshManager, CSkeletonAnimManager& skeletonAnimManager, CSimulation2& simulation)
50 : m_MeshManager(meshManager), m_SkeletonAnimManager(skeletonAnimManager), m_Simulation(simulation)
52 RegisterFileReloadFunc(ReloadChangedFileCB, this);
54 m_QualityHook = std::make_unique<CConfigDBHook>(g_ConfigDB.RegisterHookAndCall("max_actor_quality", [this]() { ActorQualityChanged(); }));
55 m_VariantDiversityHook = std::make_unique<CConfigDBHook>(g_ConfigDB.RegisterHookAndCall("variant_diversity", [this]() { VariantDiversityChanged(); }));
57 if (!CXeromyces::AddValidator(g_VFS, "actor", "art/actors/actor.rng"))
58 LOGERROR("CObjectManager: failed to load actor grammar file 'art/actors/actor.rng'");
61 CObjectManager::~CObjectManager()
63 UnloadObjects();
65 UnregisterFileReloadFunc(ReloadChangedFileCB, this);
68 std::pair<bool, CActorDef&> CObjectManager::FindActorDef(const CStrW& actorName)
70 ENSURE(!actorName.empty());
72 decltype(m_ActorDefs)::iterator it = m_ActorDefs.find(actorName);
73 if (it != m_ActorDefs.end() && !it->second.outdated)
74 return { true, *it->second.obj };
76 std::unique_ptr<CActorDef> actor = std::make_unique<CActorDef>(*this);
78 VfsPath pathname = VfsPath("art/actors/") / actorName;
80 bool success = true;
81 if (!actor->Load(pathname))
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 LOGERROR("CObjectManager::FindActorDef(): Cannot find actor '%s'", utf8_from_wstring(actorName));
87 actor->LoadErrorPlaceholder(pathname);
88 success = false;
91 return { success, *m_ActorDefs.insert_or_assign(actorName, std::move(actor)).first->second.obj };
94 CObjectEntry* CObjectManager::FindObjectVariation(const CActorDef* actor, const std::vector<std::set<CStr>>& selections, uint32_t seed)
96 if (!actor)
97 return nullptr;
99 const std::shared_ptr<CObjectBase>& base = actor->GetBase(m_QualityLevel);
101 std::vector<const std::set<CStr>*> completeSelections;
102 for (const std::set<CStr>& selectionSet : selections)
103 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 std::set<CStr> highQualitySelections = actor->GetBase(255)->CalculateRandomRemainingSelections(seed, selections);
107 completeSelections.emplace_back(&highQualitySelections);
108 // We don't have to pass the high-quality selections here because they have higher priority anyways.
109 std::set<CStr> remainingSelections = base->CalculateRandomRemainingSelections(seed, selections);
110 completeSelections.emplace_back(&remainingSelections);
111 return FindObjectVariation(base, completeSelections);
114 CObjectEntry* CObjectManager::FindObjectVariation(const std::shared_ptr<CObjectBase>& base, const std::vector<const std::set<CStr>*>& completeSelections)
116 PROFILE2("FindObjectVariation");
118 // Look to see whether this particular variation has already been loaded
119 std::vector<u8> choices = base->CalculateVariationKey(completeSelections);
120 ObjectKey key (base->GetIdentifier(), choices);
121 decltype(m_Objects)::iterator it = m_Objects.find(key);
122 if (it != m_Objects.end() && !it->second.outdated)
123 return it->second.obj.get();
125 // If it hasn't been loaded, load it now.
127 std::unique_ptr<CObjectEntry> obj = std::make_unique<CObjectEntry>(base, m_Simulation);
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.
133 if (!obj->BuildVariation(completeSelections, choices, *this))
134 return nullptr;
136 return m_Objects.insert_or_assign(key, std::move(obj)).first->second.obj.get();
139 CTerrain* CObjectManager::GetTerrain()
141 CmpPtr<ICmpTerrain> cmpTerrain(m_Simulation, SYSTEM_ENTITY);
142 if (!cmpTerrain)
143 return NULL;
144 return cmpTerrain->GetCTerrain();
147 CObjectManager::VariantDiversity CObjectManager::GetVariantDiversity() const
149 return m_VariantDiversity;
152 void CObjectManager::UnloadObjects()
154 m_Objects.clear();
155 m_ActorDefs.clear();
158 Status CObjectManager::ReloadChangedFile(const VfsPath& path)
160 bool changed = false;
162 // Mark old entries as outdated so we don't reload them from the cache
163 for (std::pair<const ObjectKey, Hotloadable<CObjectEntry>>& object : m_Objects)
164 if (!object.second.outdated && object.second.obj->m_Base->UsesFile(path))
166 object.second.outdated = true;
167 changed = true;
170 const CSimulation2::InterfaceListUnordered& cmps = m_Simulation.GetEntitiesWithInterfaceUnordered(IID_Visual);
172 // Reload actors that use a changed object
173 for (std::pair<const CStrW, Hotloadable<CActorDef>>& actor : m_ActorDefs)
175 if (!actor.second.outdated && actor.second.obj->UsesFile(path))
177 actor.second.outdated = true;
178 changed = true;
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 for (CSimulation2::InterfaceListUnordered::const_iterator eit = cmps.begin(); eit != cmps.end(); ++eit)
185 static_cast<ICmpVisual*>(eit->second)->Hotload(actor.first);
189 if (changed)
190 // Trigger an interpolate call - needed because the game may be paused & if so, models disappear.
191 m_Simulation.Interpolate(0.f, 0.f, 0.f);
193 return INFO::OK;
196 void CObjectManager::ActorQualityChanged()
198 int quality;
199 CFG_GET_VAL("max_actor_quality", quality);
200 if (quality == m_QualityLevel)
201 return;
203 m_QualityLevel = quality > 255 ? 255 : quality < 0 ? 0 : quality;
205 // No need to reload entries or actors, but we do need to reload all units.
206 const CSimulation2::InterfaceListUnordered& cmps = m_Simulation.GetEntitiesWithInterfaceUnordered(IID_Visual);
207 for (CSimulation2::InterfaceListUnordered::const_iterator eit = cmps.begin(); eit != cmps.end(); ++eit)
208 static_cast<ICmpVisual*>(eit->second)->Hotload();
210 // Trigger an interpolate call - needed because the game is generally paused & models disappear otherwise.
211 m_Simulation.Interpolate(0.f, 0.f, 0.f);
214 void CObjectManager::VariantDiversityChanged()
216 CStr value;
217 CFG_GET_VAL("variant_diversity", value);
218 VariantDiversity variantDiversity = VariantDiversity::FULL;
219 if (value == "none")
220 variantDiversity = VariantDiversity::NONE;
221 else if (value == "limited")
222 variantDiversity = VariantDiversity::LIMITED;
223 // Otherwise assume full.
225 if (variantDiversity == m_VariantDiversity)
226 return;
228 m_VariantDiversity = variantDiversity;
230 // Mark old entries as outdated so we don't reload them from the cache.
231 for (std::pair<const ObjectKey, Hotloadable<CObjectEntry>>& object : m_Objects)
232 object.second.outdated = true;
234 // Reload actors.
235 for (std::pair<const CStrW, Hotloadable<CActorDef>>& actor : m_ActorDefs)
236 actor.second.outdated = true;
238 // Reload visual actor components.
239 const CSimulation2::InterfaceListUnordered& cmps = m_Simulation.GetEntitiesWithInterfaceUnordered(IID_Visual);
240 for (CSimulation2::InterfaceListUnordered::const_iterator eit = cmps.begin(); eit != cmps.end(); ++eit)
241 static_cast<ICmpVisual*>(eit->second)->Hotload();
243 // Trigger an interpolate call - needed because the game is generally paused & models disappear otherwise.
244 m_Simulation.Interpolate(0.f, 0.f, 0.f);