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