Reorganize ActorAdapter data, use weak cache for sharing

pull/541/head
Kyle Cooley 6 years ago committed by Andrei Kortunov
parent 2a9ebac572
commit 031502b2ab

@ -11,94 +11,333 @@
namespace CSMWorld
{
ActorAdapter::ActorAdapter(CSMWorld::Data& data)
const std::string& ActorAdapter::RaceData::getId() const
{
return mId;
}
bool ActorAdapter::RaceData::handlesPart(ESM::PartReferenceType type) const
{
switch (type)
{
case ESM::PRT_Skirt:
case ESM::PRT_Shield:
case ESM::PRT_RPauldron:
case ESM::PRT_LPauldron:
case ESM::PRT_Weapon:
return false;
default:
return true;
}
}
const std::string& ActorAdapter::RaceData::getFemalePart(ESM::PartReferenceType index) const
{
return mFemaleParts[ESM::getMeshPart(index)];
}
const std::string& ActorAdapter::RaceData::getMalePart(ESM::PartReferenceType index) const
{
return mMaleParts[ESM::getMeshPart(index)];
}
bool ActorAdapter::RaceData::hasDependency(const std::string& id) const
{
return mDependencies.find(id) != mDependencies.end();
}
void ActorAdapter::RaceData::setFemalePart(ESM::BodyPart::MeshPart index, const std::string& partId)
{
mFemaleParts[index] = partId;
addOtherDependency(partId);
}
void ActorAdapter::RaceData::setMalePart(ESM::BodyPart::MeshPart index, const std::string& partId)
{
mMaleParts[index] = partId;
addOtherDependency(partId);
}
void ActorAdapter::RaceData::addOtherDependency(const std::string& id)
{
if (!id.empty()) mDependencies.emplace(id);
}
void ActorAdapter::RaceData::reset(const std::string& id)
{
mId = id;
for (auto& str : mFemaleParts)
str.clear();
for (auto& str : mMaleParts)
str.clear();
mDependencies.clear();
// Mark self as a dependency
addOtherDependency(id);
}
const std::string& ActorAdapter::ActorData::getId() const
{
return mId;
}
bool ActorAdapter::ActorData::isFemale() const
{
return mFemale;
}
const std::string& ActorAdapter::ActorData::getPart(ESM::PartReferenceType index) const
{
if (mParts[index].empty() && mRaceData && mRaceData->handlesPart(index))
{
return mFemale ? mRaceData->getFemalePart(index) : mRaceData->getMalePart(index);
}
return mParts[index];
}
bool ActorAdapter::ActorData::hasDependency(const std::string& id) const
{
return mDependencies.find(id) != mDependencies.end();
}
void ActorAdapter::ActorData::setPart(ESM::PartReferenceType index, const std::string& partId)
{
mParts[index] = partId;
addOtherDependency(partId);
}
void ActorAdapter::ActorData::addOtherDependency(const std::string& id)
{
if (!id.empty()) mDependencies.emplace(id);
}
void ActorAdapter::ActorData::reset(const std::string& id, bool isFemale, RaceDataPtr raceData)
{
mId = id;
mFemale = isFemale;
mRaceData = raceData;
for (auto& str : mParts)
str.clear();
mDependencies.clear();
// Mark self and race as a dependency
addOtherDependency(id);
if (raceData) addOtherDependency(raceData->getId());
}
ActorAdapter::ActorAdapter(Data& data)
: mReferenceables(data.getReferenceables())
, mRaces(data.getRaces())
, mBodyParts(data.getBodyParts())
{
connect(data.getTableModel(UniversalId::Type_Referenceable), SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&)),
// Setup qt slots and signals
QAbstractItemModel* refModel = data.getTableModel(UniversalId::Type_Referenceable);
connect(refModel, SIGNAL(rowsInserted(const QModelIndex&, int, int)),
this, SLOT(handleReferenceablesInserted(const QModelIndex&, int, int)));
connect(refModel, SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&)),
this, SLOT(handleReferenceableChanged(const QModelIndex&, const QModelIndex&)));
connect(data.getTableModel(UniversalId::Type_Race), SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&)),
connect(refModel, SIGNAL(rowsAboutToBeRemoved(const QModelIndex&, int, int)),
this, SLOT(handleReferenceablesAboutToBeRemoved(const QModelIndex&, int, int)));
QAbstractItemModel* raceModel = data.getTableModel(UniversalId::Type_Race);
connect(raceModel, SIGNAL(rowsInserted(const QModelIndex&, int, int)),
this, SLOT(handleRacesAboutToBeRemoved(const QModelIndex&, int, int)));
connect(raceModel, SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&)),
this, SLOT(handleRaceChanged(const QModelIndex&, const QModelIndex&)));
connect(data.getTableModel(UniversalId::Type_BodyPart), SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&)),
connect(raceModel, SIGNAL(rowsAboutToBeRemoved(const QModelIndex&, int, int)),
this, SLOT(handleRacesAboutToBeRemoved(const QModelIndex&, int, int)));
QAbstractItemModel* partModel = data.getTableModel(UniversalId::Type_BodyPart);
connect(partModel, SIGNAL(rowsInserted(const QModelIndex&, int, int)),
this, SLOT(handleBodyPartsInserted(const QModelIndex&, int, int)));
connect(partModel, SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&)),
this, SLOT(handleBodyPartChanged(const QModelIndex&, const QModelIndex&)));
connect(partModel, SIGNAL(rowsAboutToBeRemoved(const QModelIndex&, int, int)),
this, SLOT(handleBodyPartsAboutToBeRemoved(const QModelIndex&, int, int)));
}
const ActorAdapter::ActorPartMap* ActorAdapter::getActorParts(const std::string& refId, bool create)
ActorAdapter::ActorDataPtr ActorAdapter::getActorData(const std::string& id)
{
auto it = mCachedActors.find(refId);
if (it != mCachedActors.end())
{
return &it->second.parts;
}
else if (create)
// Return cached actor data if it exists
ActorDataPtr data = mCachedActors.get(id);
if (data)
{
updateActor(refId);
return getActorParts(refId, false);
return data;
}
else
// Create the actor data
data.reset(new ActorData());
setupActor(id, data);
mCachedActors.insert(id, data);
return data;
}
void ActorAdapter::handleReferenceablesInserted(const QModelIndex& parent, int start, int end)
{
// Only rows added at the top level are pertinent. Others are caught by dataChanged handler.
if (!parent.isValid())
{
return nullptr;
for (int row = start; row <= end; ++row)
{
std::string refId = mReferenceables.getId(row);
markDirtyDependency(refId);
}
}
// Update affected
updateDirty();
}
void ActorAdapter::handleReferenceableChanged(const QModelIndex& topLeft, const QModelIndex& botRight)
{
// Setup
const int TypeColumn = mReferenceables.findColumnIndex(CSMWorld::Columns::ColumnId_RecordType);
int rowStart = getHighestIndex(topLeft).row();
int rowEnd = getHighestIndex(botRight).row();
int start = getHighestIndex(topLeft).row();
int end = getHighestIndex(botRight).row();
// Handle each record
for (int row = rowStart; row <= rowEnd; ++row)
for (int row = start; row <= end; ++row)
{
int type = mReferenceables.getData(row, TypeColumn).toInt();
if (type == CSMWorld::UniversalId::Type_Creature || type == CSMWorld::UniversalId::Type_Npc)
std::string refId = mReferenceables.getId(row);
markDirtyDependency(refId);
}
// Update affected
updateDirty();
}
void ActorAdapter::handleReferenceablesAboutToBeRemoved(const QModelIndex& parent, int start, int end)
{
// Only rows at the top are pertinent.
if (!parent.isValid())
{
for (int row = start; row <= end; ++row)
{
// Update the cached npc or creature
std::string refId = mReferenceables.getId(row);
if (mCachedActors.find(refId) != mCachedActors.end())
updateActor(refId);
markDirtyDependency(refId);
}
else if (type == CSMWorld::UniversalId::Type_Armor || type == CSMWorld::UniversalId::Type_Clothing)
}
}
void ActorAdapter::handleReferenceablesRemoved(const QModelIndex& parent, int start, int end)
{
// Changes specified in handleReferenceablesAboutToBeRemoved
updateDirty();
}
void ActorAdapter::handleRacesInserted(const QModelIndex& parent, int start, int end)
{
// Only rows added at the top are pertinent.
if (!parent.isValid())
{
for (int row = start; row <= end; ++row)
{
std::string refId = mReferenceables.getId(row);
updateActorsWithDependency(refId);
std::string raceId = mReferenceables.getId(row);
markDirtyDependency(raceId);
}
}
// Update affected
updateDirty();
}
void ActorAdapter::handleRaceChanged(const QModelIndex& topLeft, const QModelIndex& botRight)
{
int rowStart = getHighestIndex(topLeft).row();
int rowEnd = getHighestIndex(botRight).row();
for (int row = rowStart; row <= rowEnd; ++row)
int start = getHighestIndex(topLeft).row();
int end = getHighestIndex(botRight).row();
for (int row = start; row <= end; ++row)
{
std::string raceId = mRaces.getId(row);
updateActorsWithDependency(raceId);
markDirtyDependency(raceId);
}
// Update affected
updateDirty();
}
void ActorAdapter::handleRacesAboutToBeRemoved(const QModelIndex& parent, int start, int end)
{
// Only changes at the top are pertinent.
if (!parent.isValid())
{
for (int row = start; row <= end; ++row)
{
std::string raceId = mRaces.getId(row);
markDirtyDependency(raceId);
}
}
}
void ActorAdapter::handleRacesRemoved(const QModelIndex& parent, int start, int end)
{
// Changes specified in handleRacesAboutToBeRemoved
updateDirty();
}
void ActorAdapter::handleBodyPartsInserted(const QModelIndex& parent, int start, int end)
{
// Only rows added at the top are pertinent.
if (!parent.isValid())
{
for (int row = start; row <= end; ++row)
{
// Race specified by part may need update
auto& record = mBodyParts.getRecord(row);
if (!record.isDeleted())
{
markDirtyDependency(record.get().mRace);
}
std::string partId = mBodyParts.getId(row);
markDirtyDependency(partId);
}
}
// Update affected
updateDirty();
}
void ActorAdapter::handleBodyPartChanged(const QModelIndex& topLeft, const QModelIndex& botRight)
{
int rowStart = getHighestIndex(topLeft).row();
int rowEnd = getHighestIndex(botRight).row();
for (int row = rowStart; row <= rowEnd; ++row)
{
// Manually update race specified by part
// Race specified by part may need update
auto& record = mBodyParts.getRecord(row);
if (!record.isDeleted())
{
updateRace(record.get().mRace);
markDirtyDependency(record.get().mRace);
}
// Update entries with a tracked dependency
std::string partId = mBodyParts.getId(row);
updateRacesWithDependency(partId);
updateActorsWithDependency(partId);
markDirtyDependency(partId);
}
// Update affected
updateDirty();
}
void ActorAdapter::handleBodyPartsAboutToBeRemoved(const QModelIndex& parent, int start, int end)
{
// Only changes at the top are pertinent.
if (!parent.isValid())
{
for (int row = start; row <= end; ++row)
{
std::string partId = mBodyParts.getId(row);
markDirtyDependency(partId);
}
}
}
void ActorAdapter::handleBodyPartsRemoved(const QModelIndex& parent, int start, int end)
{
// Changes specified in handleBodyPartsAboutToBeRemoved
updateDirty();
}
QModelIndex ActorAdapter::getHighestIndex(QModelIndex index) const
{
while (index.parent().isValid())
@ -111,251 +350,223 @@ namespace CSMWorld
return name.size() >= 4 && name.find(".1st", name.size() - 4) != std::string::npos;
}
ActorAdapter::RaceData& ActorAdapter::getRaceData(const std::string& raceId)
ActorAdapter::RaceDataPtr ActorAdapter::getRaceData(const std::string& id)
{
// Return cached race data if it exists
RaceDataPtr data = mCachedRaces.get(id);
if (data) return data;
// Create the race data
data.reset(new RaceData());
setupRace(id, data);
mCachedRaces.insert(id, data);
return data;
}
void ActorAdapter::setupActor(const std::string& id, ActorDataPtr data)
{
auto it = mCachedRaces.find(raceId);
if (it != mCachedRaces.end())
int index = mReferenceables.searchId(id);
if (index == -1)
{
return it->second;
// Record does not exist
data->reset(id);
emit actorChanged(id);
return;
}
auto& record = mReferenceables.getRecord(index);
if (record.isDeleted())
{
// Record is deleted and therefore not accessible
data->reset(id);
emit actorChanged(id);
return;
}
const int TypeColumn = mReferenceables.findColumnIndex(Columns::ColumnId_RecordType);
int type = mReferenceables.getData(index, TypeColumn).toInt();
if (type == UniversalId::Type_Creature)
{
// Valid creature record
setupCreature(id, data);
emit actorChanged(id);
}
else if (type == UniversalId::Type_Npc)
{
// Valid npc record
setupNpc(id, data);
emit actorChanged(id);
}
else
{
// Create and find result
updateRace(raceId);
return mCachedRaces.find(raceId)->second;
// Wrong record type
data->reset(id);
emit actorChanged(id);
}
}
void ActorAdapter::updateRace(const std::string& raceId)
void ActorAdapter::setupRace(const std::string& id, RaceDataPtr data)
{
// Retrieve or create cache entry
auto raceDataIt = mCachedRaces.find(raceId);
if (raceDataIt == mCachedRaces.end())
// Common setup
data->reset(id);
int index = mRaces.searchId(id);
if (index == -1)
{
auto result = mCachedRaces.emplace(raceId, RaceData());
raceDataIt = result.first;
// Record does not exist
return;
}
auto& raceRecord = mRaces.getRecord(index);
if (raceRecord.isDeleted())
{
// Record is deleted, so not accessible
return;
}
auto& raceData = raceDataIt->second;
raceData.femaleParts.clear();
raceData.maleParts.clear();
raceData.dependencies.clear();
// TODO move stuff in actor related to race here
// Construct entry
// Setup body parts
for (int i = 0; i < mBodyParts.getSize(); ++i)
{
auto& record = mBodyParts.getRecord(i);
if (!record.isDeleted() && record.get().mRace == raceId)
{
auto& part = record.get();
std::string partId = mBodyParts.getId(i);
auto& partRecord = mBodyParts.getRecord(i);
// Part could affect race data
raceData.dependencies.emplace(part.mId, true);
if (partRecord.isDeleted())
{
// Record is deleted, so not accessible.
continue;
}
// Add base types
if (part.mData.mType == ESM::BodyPart::MT_Skin && !is1stPersonPart(part.mId))
{
auto type = (ESM::BodyPart::MeshPart) part.mData.mPart;
// Note: Prefer the first part encountered for duplicates. emplace() does not overwrite
if (part.mData.mFlags & ESM::BodyPart::BPF_Female)
raceData.femaleParts.emplace(type, part.mId);
else
raceData.maleParts.emplace(type, part.mId);
}
auto& part = partRecord.get();
if (part.mRace == id && part.mData.mType == ESM::BodyPart::MT_Skin && !is1stPersonPart(part.mId))
{
auto type = (ESM::BodyPart::MeshPart) part.mData.mPart;
bool female = part.mData.mFlags & ESM::BodyPart::BPF_Female;
if (female) data->setFemalePart(type, part.mId);
else data->setMalePart(type, part.mId);
}
}
updateActorsWithDependency(raceId);
}
void ActorAdapter::updateActor(const std::string& refId)
void ActorAdapter::setupNpc(const std::string& id, ActorDataPtr data)
{
int index = mReferenceables.searchId(refId);
if (index != -1)
// Common setup, record is known to exist and is not deleted
int index = mReferenceables.searchId(id);
auto& npc = dynamic_cast<const Record<ESM::NPC>&>(mReferenceables.getRecord(index)).get();
RaceDataPtr raceData = getRaceData(npc.mRace);
data->reset(id, !npc.isMale(), raceData);
// Add inventory items
for (auto& item : npc.mInventory.mList)
{
int typeColumn = mReferenceables.findColumnIndex(CSMWorld::Columns::ColumnId_RecordType);
int recordType = mReferenceables.getData(index, typeColumn).toInt();
if (recordType == CSMWorld::UniversalId::Type_Creature)
updateCreature(refId);
else if (recordType == CSMWorld::UniversalId::Type_Npc)
updateNpc(refId);
if (item.mCount <= 0) continue;
std::string itemId = item.mItem.toString();
addNpcItem(itemId, data);
}
// Add head and hair
data->setPart(ESM::PRT_Head, npc.mHead);
data->setPart(ESM::PRT_Hair, npc.mHair);
}
void ActorAdapter::updateNpc(const std::string& refId)
void ActorAdapter::addNpcItem(const std::string& itemId, ActorDataPtr data)
{
auto& record = mReferenceables.getRecord(refId);
// Retrieve record if possible
if (record.isDeleted())
int index = mReferenceables.searchId(itemId);
if (index == -1)
{
mCachedActors.erase(refId);
emit actorChanged(refId);
// Item does not exist yet
data->addOtherDependency(itemId);
return;
}
auto& npc = dynamic_cast<const Record<ESM::NPC>&>(record).get();
// Create holder for cached data
auto actorIt = mCachedActors.find(refId);
if (actorIt == mCachedActors.end())
auto& record = mReferenceables.getRecord(index);
if (record.isDeleted())
{
auto result = mCachedActors.emplace(refId, ActorData());
actorIt = result.first;
// Item cannot be accessed yet
data->addOtherDependency(itemId);
return;
}
auto& actorData = actorIt->second;
// Reset old data
actorData.parts.clear();
actorData.dependencies.clear();
// Look at the npc's inventory first
for (auto& item : npc.mInventory.mList)
{
if (item.mCount > 0)
// Convenience function to add a parts list to actor data
auto addParts = [&](const ESM::PartReferenceList& list) {
for (auto& part : list.mParts)
{
std::string itemId = item.mItem.toString();
// Handle armor and clothing
int index = mReferenceables.searchId(itemId);
if (index != -1 && !mReferenceables.getRecord(index).isDeleted())
{
auto& itemRecord = mReferenceables.getRecord(index);
int typeColumn = mReferenceables.findColumnIndex(CSMWorld::Columns::ColumnId_RecordType);
int recordType = mReferenceables.getData(index, typeColumn).toInt();
if (recordType == CSMWorld::UniversalId::Type_Armor)
{
// Changes here could affect the actor
actorData.dependencies.emplace(itemId, true);
// Add any parts if there is room
auto& armor = dynamic_cast<const Record<ESM::Armor>&>(itemRecord).get();
for (auto& part : armor.mParts.mParts)
{
std::string bodyPartId;
if (!npc.isMale())
bodyPartId = part.mFemale;
if (bodyPartId.empty())
bodyPartId = part.mMale;
if (!bodyPartId.empty())
{
actorData.parts.emplace(static_cast<ESM::PartReferenceType>(part.mPart), bodyPartId);
actorData.dependencies.emplace(bodyPartId, true);
}
}
}
else if (recordType == CSMWorld::UniversalId::Type_Clothing)
{
// Changes here could affect the actor
actorData.dependencies.emplace(itemId, true);
// Add any parts if there is room
auto& clothing = dynamic_cast<const Record<ESM::Clothing>&>(itemRecord).get();
for (auto& part : clothing.mParts.mParts)
{
std::string bodyPartId;
if (!npc.isMale())
bodyPartId = part.mFemale;
if (bodyPartId.empty())
bodyPartId = part.mMale;
if (!bodyPartId.empty())
{
actorData.parts.emplace(static_cast<ESM::PartReferenceType>(part.mPart), bodyPartId);
actorData.dependencies.emplace(bodyPartId, true);
}
}
}
}
}
}
std::string partId;
auto partType = (ESM::PartReferenceType) part.mPart;
// Lookup cached race parts
auto& raceData = getRaceData(npc.mRace);
if (data->isFemale())
partId = part.mFemale;
if (partId.empty())
partId = part.mMale;
// Changes to race could affect the actor
actorData.dependencies.emplace(npc.mRace, true);
if (!partId.empty()) data->setPart(partType, partId);
}
};
// Fill in the rest with race specific body parts
for (int i = 0; i < ESM::PRT_Count; ++i)
int TypeColumn = mReferenceables.findColumnIndex(Columns::ColumnId_RecordType);
int type = mReferenceables.getData(index, TypeColumn).toInt();
if (type == UniversalId::Type_Armor)
{
auto type = static_cast<ESM::PartReferenceType>(i);
if (actorData.parts.find(type) == actorData.parts.end())
{
switch (type)
{
case ESM::PRT_Head:
actorData.parts.emplace(type, npc.mHead);
actorData.dependencies.emplace(npc.mHead, true);
break;
case ESM::PRT_Hair:
actorData.parts.emplace(type, npc.mHair);
actorData.dependencies.emplace(npc.mHair, true);
break;
case ESM::PRT_Skirt:
case ESM::PRT_Shield:
case ESM::PRT_RPauldron:
case ESM::PRT_LPauldron:
case ESM::PRT_Weapon:
// No body part associated
break;
default:
{
std::string bodyPartId;
// Check female map if applicable
if (!npc.isMale())
{
auto partIt = raceData.femaleParts.find(ESM::getMeshPart(type));
if (partIt != raceData.femaleParts.end())
bodyPartId = partIt->second;
}
// Check male map next
if (bodyPartId.empty() || npc.isMale())
{
auto partIt = raceData.maleParts.find(ESM::getMeshPart(type));
if (partIt != raceData.maleParts.end())
bodyPartId = partIt->second;
}
// Add to map
if (!bodyPartId.empty())
{
actorData.parts.emplace(type, bodyPartId);
actorData.dependencies.emplace(bodyPartId, true);
}
}
}
}
auto& armor = dynamic_cast<const Record<ESM::Armor>&>(record).get();
addParts(armor.mParts);
// Changing parts could affect what is picked for rendering
data->addOtherDependency(itemId);
}
else if (type == UniversalId::Type_Clothing)
{
auto& clothing = dynamic_cast<const Record<ESM::Clothing>&>(record).get();
addParts(clothing.mParts);
// Signal change to actor
emit actorChanged(refId);
// Changing parts could affect what is picked for rendering
data->addOtherDependency(itemId);
}
}
void ActorAdapter::updateCreature(const std::string& refId)
void ActorAdapter::setupCreature(const std::string& id, ActorDataPtr data)
{
// Signal change to actor
emit actorChanged(refId);
data->reset(id);
// TODO move stuff from Actor here
}
void ActorAdapter::updateActorsWithDependency(const std::string& id)
void ActorAdapter::markDirtyDependency(const std::string& dep)
{
for (auto it : mCachedActors)
for (auto raceIt : mCachedRaces)
{
if (raceIt->hasDependency(dep))
mDirtyRaces.emplace(raceIt->getId());
}
for (auto actorIt : mCachedActors)
{
auto& deps = it.second.dependencies;
if (deps.find(id) != deps.end())
updateActor(it.first);
if (actorIt->hasDependency(dep))
mDirtyActors.emplace(actorIt->getId());
}
}
void ActorAdapter::updateRacesWithDependency(const std::string& id)
void ActorAdapter::updateDirty()
{
for (auto it : mCachedRaces)
// Handle races before actors, since actors are dependent on race
for (auto& race : mDirtyRaces)
{
auto& deps = it.second.dependencies;
if (deps.find(id) != deps.end())
updateRace(it.first);
RaceDataPtr data = mCachedRaces.get(race);
if (data)
{
setupRace(race, data);
}
}
mDirtyRaces.clear();
for (auto& actor : mDirtyActors)
{
ActorDataPtr data = mCachedActors.get(actor);
if (data)
{
setupActor(actor, data);
}
}
mDirtyActors.clear();
}
}

@ -1,13 +1,13 @@
#ifndef CSM_WOLRD_ACTORADAPTER_H
#define CSM_WOLRD_ACTORADAPTER_H
#include <functional>
#include <unordered_map>
#include <utility>
#include <array>
#include <unordered_set>
#include <QObject>
#include <QModelIndex>
#include <components/cache/weakcache.hpp>
#include <components/esm/loadarmo.hpp>
#include <components/esm/loadbody.hpp>
@ -17,88 +17,147 @@
namespace ESM
{
struct Race;
enum PartReferenceType;
}
namespace CSMWorld
{
class Data;
/// Quick and dirty hashing functor.
struct StringBoolPairHash
/// Adapts multiple collections to provide the data needed to render
/// an npc or creature.
class ActorAdapter : public QObject
{
size_t operator()(const std::pair<std::string, bool>& value) const noexcept
Q_OBJECT
public:
/// A list indexed by ESM::PartReferenceType
using ActorPartList = std::array<std::string, ESM::PRT_Count>;
/// A list indexed by ESM::BodyPart::MeshPart
using RacePartList = std::array<std::string, ESM::BodyPart::MP_Count>;
/// Tracks unique strings
using StringSet = std::unordered_set<std::string>;
/// Contains base race data shared between actors
class RaceData
{
auto stringHash = std::hash<std::string>();
return stringHash(value.first) + value.second;
}
};
public:
/// Retrieves the id of the race represented
const std::string& getId() const;
/// Checks if a part could exist for the given type
bool handlesPart(ESM::PartReferenceType type) const;
/// Retrieves the associated body part
const std::string& getFemalePart(ESM::PartReferenceType index) const;
/// Retrieves the associated body part
const std::string& getMalePart(ESM::PartReferenceType index) const;
/// Checks if the race has a data dependency
bool hasDependency(const std::string& id) const;
/// Sets the associated part if it's empty and marks a dependency
void setFemalePart(ESM::BodyPart::MeshPart partIndex, const std::string& partId);
/// Sets the associated part if it's empty and marks a dependency
void setMalePart(ESM::BodyPart::MeshPart partIndex, const std::string& partId);
/// Marks an additional dependency
void addOtherDependency(const std::string& id);
/// Clears parts and dependencies
void reset(const std::string& raceId);
class ActorAdapter : public QObject
{
Q_OBJECT
private:
bool handles(ESM::PartReferenceType type) const;
std::string mId;
RacePartList mFemaleParts;
RacePartList mMaleParts;
StringSet mDependencies;
};
using RaceDataPtr = std::shared_ptr<RaceData>;
/// Contains all the data needed to render an actor. Tracks dependencies
/// so that pertinent data changes can be checked.
class ActorData
{
public:
/// Retrieves the id of the actor represented
const std::string& getId() const;
/// Checks if the actor is female
bool isFemale() const;
/// Retrieves the associated actor part
const std::string& getPart(ESM::PartReferenceType index) const;
/// Checks if the actor has a data dependency
bool hasDependency(const std::string& id) const;
/// Sets the actor part used and marks a dependency
void setPart(ESM::PartReferenceType partIndex, const std::string& partId);
/// Marks an additional dependency for the actor
void addOtherDependency(const std::string& id);
/// Clears race, parts, and dependencies
void reset(const std::string& actorId, bool female=true, RaceDataPtr raceData=nullptr);
// Maps body part type to 'body part' id
using ActorPartMap = std::unordered_map<ESM::PartReferenceType, std::string>;
private:
std::string mId;
bool mFemale;
RaceDataPtr mRaceData;
ActorPartList mParts;
StringSet mDependencies;
};
using ActorDataPtr = std::shared_ptr<ActorData>;
ActorAdapter(CSMWorld::Data& data);
const ActorPartMap* getActorParts(const std::string& refId, bool create=true);
ActorAdapter(Data& data);
signals:
/// Obtains the shared data for a given actor
ActorDataPtr getActorData(const std::string& refId);
void actorChanged(const std::string& refId);
signals:
public slots:
void actorChanged(const std::string& refId);
void handleReferenceableChanged(const QModelIndex&, const QModelIndex&);
void handleRaceChanged(const QModelIndex&, const QModelIndex&);
void handleBodyPartChanged(const QModelIndex&, const QModelIndex&);
public slots:
private:
// Maps mesh part type to 'body part' id
using RacePartMap = std::unordered_map<ESM::BodyPart::MeshPart, std::string>;
// Stores ids that are referenced by the actor. Data part is meaningless.
using DependencyMap = std::unordered_map<std::string, bool>;
struct ActorData
{
ActorPartMap parts;
DependencyMap dependencies;
};
struct RaceData
{
RacePartMap femaleParts;
RacePartMap maleParts;
DependencyMap dependencies;
};
ActorAdapter(const ActorAdapter&) = delete;
ActorAdapter& operator=(const ActorAdapter&) = delete;
QModelIndex getHighestIndex(QModelIndex) const;
bool is1stPersonPart(const std::string& id) const;
RaceData& getRaceData(const std::string& raceId);
void updateRace(const std::string& raceId);
void updateActor(const std::string& refId);
void updateNpc(const std::string& refId);
void updateCreature(const std::string& refId);
void updateActorsWithDependency(const std::string& id);
void updateRacesWithDependency(const std::string& id);
RefIdCollection& mReferenceables;
IdCollection<ESM::Race>& mRaces;
IdCollection<ESM::BodyPart>& mBodyParts;
// Key: referenceable id
std::unordered_map<std::string, ActorData> mCachedActors;
// Key: race id
std::unordered_map<std::string, RaceData> mCachedRaces;
void handleReferenceablesInserted(const QModelIndex&, int, int);
void handleReferenceableChanged(const QModelIndex&, const QModelIndex&);
void handleReferenceablesAboutToBeRemoved(const QModelIndex&, int, int);
void handleReferenceablesRemoved(const QModelIndex&, int, int);
void handleRacesInserted(const QModelIndex&, int, int);
void handleRaceChanged(const QModelIndex&, const QModelIndex&);
void handleRacesAboutToBeRemoved(const QModelIndex&, int, int);
void handleRacesRemoved(const QModelIndex&, int, int);
void handleBodyPartsInserted(const QModelIndex&, int, int);
void handleBodyPartChanged(const QModelIndex&, const QModelIndex&);
void handleBodyPartsAboutToBeRemoved(const QModelIndex&, int, int);
void handleBodyPartsRemoved(const QModelIndex&, int, int);
private:
ActorAdapter(const ActorAdapter&) = delete;
ActorAdapter& operator=(const ActorAdapter&) = delete;
QModelIndex getHighestIndex(QModelIndex) const;
bool is1stPersonPart(const std::string& id) const;
RaceDataPtr getRaceData(const std::string& raceId);
void setupActor(const std::string& id, ActorDataPtr data);
void setupRace(const std::string& id, RaceDataPtr data);
void setupNpc(const std::string& id, ActorDataPtr data);
void addNpcItem(const std::string& itemId, ActorDataPtr data);
void setupCreature(const std::string& id, ActorDataPtr data);
void markDirtyDependency(const std::string& dependency);
void updateDirty();
RefIdCollection& mReferenceables;
IdCollection<ESM::Race>& mRaces;
IdCollection<ESM::BodyPart>& mBodyParts;
cache::WeakCache<std::string, ActorData> mCachedActors; // Key: referenceable id
cache::WeakCache<std::string, RaceData> mCachedRaces; // Key: race id
StringSet mDirtyActors; // Actors that need updating
StringSet mDirtyRaces; // Races that need updating
};
}

@ -26,6 +26,7 @@ namespace CSVRender
, mBaseNode(new osg::Group())
, mSkeleton(nullptr)
{
mActorData = mData.getActorAdapter()->getActorData(mId);
}
osg::Group* Actor::getBaseNode()
@ -60,7 +61,6 @@ namespace CSVRender
{
if (mId == refId)
{
Log(Debug::Info) << "Actor::actorChanged " << mId;
update();
}
}
@ -80,9 +80,6 @@ namespace CSVRender
mSkeleton->accept(removeTriBipVisitor);
removeTriBipVisitor.remove();
// Attach weapons
loadBodyParts(creature.mId);
// Post setup
mSkeleton->markDirty();
mSkeleton->setActive(SceneUtil::Skeleton::Active);
@ -141,12 +138,11 @@ namespace CSVRender
void Actor::loadBodyParts(const std::string& actorId)
{
auto actorAdapter = mData.getActorAdapter();
auto parts = actorAdapter->getActorParts(actorId);
if (parts)
for (int i = 0; i < ESM::PRT_Count; ++i)
{
for (auto& pair : *parts)
attachBodyPart(pair.first, getBodyPartMesh(pair.second));
auto type = (ESM::PartReferenceType) i;
std::string partId = mActorData->getPart(type);
attachBodyPart(type, getBodyPartMesh(partId));
}
}

@ -10,6 +10,8 @@
#include <components/esm/loadarmo.hpp>
#include <components/sceneutil/visitor.hpp>
#include "../../model/world/actoradapter.hpp"
namespace osg
{
class Group;
@ -63,6 +65,7 @@ namespace CSVRender
bool mInitialized;
int mType;
CSMWorld::Data& mData;
CSMWorld::ActorAdapter::ActorDataPtr mActorData;
osg::ref_ptr<osg::Group> mBaseNode;
SceneUtil::Skeleton* mSkeleton;

@ -150,6 +150,10 @@ add_component_dir (fallback
fallback validate
)
add_component_dir(cache
weakcache
)
if(NOT WIN32 AND NOT ANDROID)
add_component_dir (crashcatcher
crashcatcher

@ -0,0 +1,116 @@
#ifndef OPENMW_COMPONENTS_WEAKCACHE_HPP
#define OPENMW_COMPONENTS_WEAKCACHE_HPP
#include <memory>
#include <unordered_map>
namespace cache
{
/// \class WeakCache
/// Provides a container to weakly store pointers to shared data.
template <typename Key, typename T>
class WeakCache
{
public:
using WeakPtr = std::weak_ptr<T>;
using StrongPtr = std::shared_ptr<T>;
using Map = std::unordered_map<Key, WeakPtr>;
class iterator
{
public:
iterator(typename Map::iterator current, typename Map::iterator end);
iterator& operator++();
bool operator==(const iterator& other);
bool operator!=(const iterator& other);
StrongPtr operator*();
private:
typename Map::iterator mCurrent, mEnd;
StrongPtr mPtr;
};
/// Stores a weak pointer to the item.
void insert(Key key, StrongPtr value);
/// Retrieves the item associated with the key.
/// \return An item or null.
StrongPtr get(Key key);
iterator begin();
iterator end();
private:
Map mData;
};
template <typename Key, typename T>
WeakCache<Key, T>::iterator::iterator(typename Map::iterator current, typename Map::iterator end)
: mCurrent(current)
, mEnd(end)
{
// Move to 1st available valid item
for ( ; mCurrent != mEnd; ++mCurrent)
{
mPtr = mCurrent->second.lock();
if (mPtr) break;
}
}
template <typename Key, typename T>
typename WeakCache<Key, T>::iterator& WeakCache<Key, T>::iterator::operator++()
{
auto next = mCurrent;
++next;
return *this = iterator(next, mEnd);
}
template <typename Key, typename T>
bool WeakCache<Key, T>::iterator::operator==(const iterator& other)
{
return mCurrent == other.mCurrent;
}
template <typename Key, typename T>
bool WeakCache<Key, T>::iterator::operator!=(const iterator& other)
{
return !(*this == other);
}
template <typename Key, typename T>
typename WeakCache<Key, T>::StrongPtr WeakCache<Key, T>::iterator::operator*()
{
return mPtr;
}
template <typename Key, typename T>
void WeakCache<Key, T>::insert(Key key, StrongPtr value)
{
mData[key] = WeakPtr(value);
}
template <typename Key, typename T>
typename WeakCache<Key, T>::StrongPtr WeakCache<Key, T>::get(Key key)
{
auto searchIt = mData.find(key);
if (searchIt != mData.end())
return searchIt->second.lock();
else
return StrongPtr();
}
template <typename Key, typename T>
typename WeakCache<Key, T>::iterator WeakCache<Key, T>::begin()
{
return iterator(mData.begin(), mData.end());
}
template <typename Key, typename T>
typename WeakCache<Key, T>::iterator WeakCache<Key, T>::end()
{
return iterator(mData.end(), mData.end());
}
}
#endif
Loading…
Cancel
Save