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 : }
|