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"
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
)
38 else if (ObjectBaseIdentifier
> a
.ObjectBaseIdentifier
)
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()
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
;
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
);
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
)
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))
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
);
144 return cmpTerrain
->GetCTerrain();
147 CObjectManager::VariantDiversity
CObjectManager::GetVariantDiversity() const
149 return m_VariantDiversity
;
152 void CObjectManager::UnloadObjects()
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;
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;
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
);
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
);
196 void CObjectManager::ActorQualityChanged()
199 CFG_GET_VAL("max_actor_quality", quality
);
200 if (quality
== m_QualityLevel
)
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()
217 CFG_GET_VAL("variant_diversity", value
);
218 VariantDiversity variantDiversity
= VariantDiversity::FULL
;
220 variantDiversity
= VariantDiversity::NONE
;
221 else if (value
== "limited")
222 variantDiversity
= VariantDiversity::LIMITED
;
223 // Otherwise assume full.
225 if (variantDiversity
== m_VariantDiversity
)
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;
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
);