1 /* Copyright (C) 2015 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/Game.h"
26 #include "ps/Profile.h"
27 #include "ps/Filesystem.h"
28 #include "ps/XML/Xeromyces.h"
29 #include "simulation2/Simulation2.h"
30 #include "simulation2/components/ICmpTerrain.h"
31 #include "simulation2/components/ICmpVisual.h"
32 
33 
operator <(const CObjectManager::ObjectKey & a) const34 bool CObjectManager::ObjectKey::operator< (const CObjectManager::ObjectKey& a) const
35 {
36 	if (ActorName < a.ActorName)
37 		return true;
38 	else if (ActorName > a.ActorName)
39 		return false;
40 	else
41 		return ActorVariation < a.ActorVariation;
42 }
43 
ReloadChangedFileCB(void * param,const VfsPath & path)44 static Status ReloadChangedFileCB(void* param, const VfsPath& path)
45 {
46 	return static_cast<CObjectManager*>(param)->ReloadChangedFile(path);
47 }
48 
CObjectManager(CMeshManager & meshManager,CSkeletonAnimManager & skeletonAnimManager,CSimulation2 & simulation)49 CObjectManager::CObjectManager(CMeshManager& meshManager, CSkeletonAnimManager& skeletonAnimManager, CSimulation2& simulation)
50 : m_MeshManager(meshManager), m_SkeletonAnimManager(skeletonAnimManager), m_Simulation(simulation)
51 {
52 	RegisterFileReloadFunc(ReloadChangedFileCB, this);
53 
54 	if (!CXeromyces::AddValidator(g_VFS, "actor", "art/actors/actor.rng"))
55 		LOGERROR("CObjectManager: failed to load actor grammar file 'art/actors/actor.rng'");
56 }
57 
~CObjectManager()58 CObjectManager::~CObjectManager()
59 {
60 	UnloadObjects();
61 
62 	UnregisterFileReloadFunc(ReloadChangedFileCB, this);
63 }
64 
65 
FindObjectBase(const CStrW & objectname)66 CObjectBase* CObjectManager::FindObjectBase(const CStrW& objectname)
67 {
68 	ENSURE(!objectname.empty());
69 
70 	// See if the base type has been loaded yet:
71 
72 	std::map<CStrW, CObjectBase*>::iterator it = m_ObjectBases.find(objectname);
73 	if (it != m_ObjectBases.end())
74 		return it->second;
75 
76 	// Not already loaded, so try to load it:
77 
78 	CObjectBase* obj = new CObjectBase(*this);
79 
80 	VfsPath pathname = VfsPath("art/actors/") / objectname;
81 
82 	if (obj->Load(pathname))
83 	{
84 		m_ObjectBases[objectname] = obj;
85 		return obj;
86 	}
87 	else
88 		delete obj;
89 
90 	LOGERROR("CObjectManager::FindObjectBase(): Cannot find object '%s'", utf8_from_wstring(objectname));
91 
92 	return 0;
93 }
94 
FindObject(const CStrW & objname)95 CObjectEntry* CObjectManager::FindObject(const CStrW& objname)
96 {
97 	std::vector<std::set<CStr> > selections; // TODO - should this really be empty?
98 	return FindObjectVariation(objname, selections);
99 }
100 
FindObjectVariation(const CStrW & objname,const std::vector<std::set<CStr>> & selections)101 CObjectEntry* CObjectManager::FindObjectVariation(const CStrW& objname, const std::vector<std::set<CStr> >& selections)
102 {
103 	CObjectBase* base = FindObjectBase(objname);
104 
105 	if (! base)
106 		return NULL;
107 
108 	return FindObjectVariation(base, selections);
109 }
110 
FindObjectVariation(CObjectBase * base,const std::vector<std::set<CStr>> & selections)111 CObjectEntry* CObjectManager::FindObjectVariation(CObjectBase* base, const std::vector<std::set<CStr> >& selections)
112 {
113 	PROFILE("object variation loading");
114 
115 	// Look to see whether this particular variation has already been loaded
116 
117 	std::vector<u8> choices = base->CalculateVariationKey(selections);
118 	ObjectKey key (base->m_Pathname.string(), choices);
119 
120 	std::map<ObjectKey, CObjectEntry*>::iterator it = m_Objects.find(key);
121 	if (it != m_Objects.end() && !it->second->m_Outdated)
122 		return it->second;
123 
124 	// If it hasn't been loaded, load it now
125 
126 	// TODO: If there was an existing ObjectEntry, but it's outdated (due to hotloading),
127 	// we'll get a memory leak when replacing its entry in m_Objects. The problem is
128 	// some CUnits might still have a pointer to the old ObjectEntry so we probably can't
129 	// just delete it now. Probably we need to redesign the caching/hotloading system so it
130 	// makes more sense (e.g. use shared_ptr); for now I'll just leak, to avoid making the logic
131 	// more complex than it is already is, since this only matters for the rare case of hotloading.
132 
133 	CObjectEntry* obj = new CObjectEntry(base, m_Simulation); // TODO: type ?
134 
135 	// TODO (for some efficiency): use the pre-calculated choices for this object,
136 	// which has already worked out what to do for props, instead of passing the
137 	// selections into BuildVariation and having it recalculate the props' choices.
138 
139 	if (! obj->BuildVariation(selections, choices, *this))
140 	{
141 		DeleteObject(obj);
142 		return NULL;
143 	}
144 
145 	m_Objects[key] = obj;
146 
147 	return obj;
148 }
149 
GetTerrain()150 CTerrain* CObjectManager::GetTerrain()
151 {
152 	CmpPtr<ICmpTerrain> cmpTerrain(m_Simulation, SYSTEM_ENTITY);
153 	if (!cmpTerrain)
154 		return NULL;
155 	return cmpTerrain->GetCTerrain();
156 }
157 
DeleteObject(CObjectEntry * entry)158 void CObjectManager::DeleteObject(CObjectEntry* entry)
159 {
160 	std::function<bool(const std::pair<ObjectKey, CObjectEntry*>&)> second_equals =
161 		[&entry](const std::pair<ObjectKey, CObjectEntry*>& a) { return a.second == entry; };
162 
163 	std::map<ObjectKey, CObjectEntry*>::iterator it = m_Objects.begin();
164 	while (m_Objects.end() != (it = find_if(it, m_Objects.end(), second_equals)))
165 		it = m_Objects.erase(it);
166 
167 	delete entry;
168 }
169 
170 
UnloadObjects()171 void CObjectManager::UnloadObjects()
172 {
173 	for (const std::pair<ObjectKey, CObjectEntry*>& p : m_Objects)
174 		delete p.second;
175 	m_Objects.clear();
176 
177 	for (const std::pair<CStrW, CObjectBase*>& p : m_ObjectBases)
178 		delete p.second;
179 	m_ObjectBases.clear();
180 }
181 
ReloadChangedFile(const VfsPath & path)182 Status CObjectManager::ReloadChangedFile(const VfsPath& path)
183 {
184 	// Mark old entries as outdated so we don't reload them from the cache
185 	for (std::map<ObjectKey, CObjectEntry*>::iterator it = m_Objects.begin(); it != m_Objects.end(); ++it)
186 		if (it->second->m_Base->UsesFile(path))
187 			it->second->m_Outdated = true;
188 
189 	// Reload actors that use a changed object
190 	for (std::map<CStrW, CObjectBase*>::iterator it = m_ObjectBases.begin(); it != m_ObjectBases.end(); ++it)
191 	{
192 		if (it->second->UsesFile(path))
193 		{
194 			it->second->Reload();
195 
196 			// Slightly ugly hack: The graphics system doesn't preserve enough information to regenerate the
197 			// object with all correct variations, and we don't want to waste space storing it just for the
198 			// rare occurrence of hotloading, so we'll tell the component (which does preserve the information)
199 			// to do the reloading itself
200 			const CSimulation2::InterfaceListUnordered& cmps = m_Simulation.GetEntitiesWithInterfaceUnordered(IID_Visual);
201 			for (CSimulation2::InterfaceListUnordered::const_iterator eit = cmps.begin(); eit != cmps.end(); ++eit)
202 				static_cast<ICmpVisual*>(eit->second)->Hotload(it->first);
203 		}
204 	}
205 
206 	return INFO::OK;
207 }
208