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 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()) : mReferenceables(data.getReferenceables())
, mRaces(data.getRaces()) , mRaces(data.getRaces())
, mBodyParts(data.getBodyParts()) , 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&))); 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&))); 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&))); 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); // Return cached actor data if it exists
if (it != mCachedActors.end()) ActorDataPtr data = mCachedActors.get(id);
{ if (data)
return &it->second.parts;
}
else if (create)
{ {
updateActor(refId); return data;
return getActorParts(refId, false);
} }
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) void ActorAdapter::handleReferenceableChanged(const QModelIndex& topLeft, const QModelIndex& botRight)
{ {
// Setup int start = getHighestIndex(topLeft).row();
const int TypeColumn = mReferenceables.findColumnIndex(CSMWorld::Columns::ColumnId_RecordType); int end = getHighestIndex(botRight).row();
int rowStart = getHighestIndex(topLeft).row();
int rowEnd = getHighestIndex(botRight).row();
// Handle each record // Handle each record
for (int row = rowStart; row <= rowEnd; ++row) for (int row = start; row <= end; ++row)
{ {
int type = mReferenceables.getData(row, TypeColumn).toInt(); std::string refId = mReferenceables.getId(row);
if (type == CSMWorld::UniversalId::Type_Creature || type == CSMWorld::UniversalId::Type_Npc) 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); std::string refId = mReferenceables.getId(row);
if (mCachedActors.find(refId) != mCachedActors.end()) markDirtyDependency(refId);
updateActor(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); std::string raceId = mReferenceables.getId(row);
updateActorsWithDependency(refId); markDirtyDependency(raceId);
} }
} }
// Update affected
updateDirty();
} }
void ActorAdapter::handleRaceChanged(const QModelIndex& topLeft, const QModelIndex& botRight) void ActorAdapter::handleRaceChanged(const QModelIndex& topLeft, const QModelIndex& botRight)
{ {
int rowStart = getHighestIndex(topLeft).row(); int start = getHighestIndex(topLeft).row();
int rowEnd = getHighestIndex(botRight).row(); int end = getHighestIndex(botRight).row();
for (int row = rowStart; row <= rowEnd; ++row) for (int row = start; row <= end; ++row)
{ {
std::string raceId = mRaces.getId(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) void ActorAdapter::handleBodyPartChanged(const QModelIndex& topLeft, const QModelIndex& botRight)
{ {
int rowStart = getHighestIndex(topLeft).row(); int rowStart = getHighestIndex(topLeft).row();
int rowEnd = getHighestIndex(botRight).row(); int rowEnd = getHighestIndex(botRight).row();
for (int row = rowStart; row <= rowEnd; ++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); auto& record = mBodyParts.getRecord(row);
if (!record.isDeleted()) if (!record.isDeleted())
{ {
updateRace(record.get().mRace); markDirtyDependency(record.get().mRace);
} }
// Update entries with a tracked dependency // Update entries with a tracked dependency
std::string partId = mBodyParts.getId(row); std::string partId = mBodyParts.getId(row);
updateRacesWithDependency(partId); markDirtyDependency(partId);
updateActorsWithDependency(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 QModelIndex ActorAdapter::getHighestIndex(QModelIndex index) const
{ {
while (index.parent().isValid()) while (index.parent().isValid())
@ -111,251 +350,223 @@ namespace CSMWorld
return name.size() >= 4 && name.find(".1st", name.size() - 4) != std::string::npos; 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); int index = mReferenceables.searchId(id);
if (it != mCachedRaces.end()) 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 else
{ {
// Create and find result // Wrong record type
updateRace(raceId); data->reset(id);
return mCachedRaces.find(raceId)->second; emit actorChanged(id);
} }
} }
void ActorAdapter::updateRace(const std::string& raceId) void ActorAdapter::setupRace(const std::string& id, RaceDataPtr data)
{ {
// Retrieve or create cache entry // Common setup
auto raceDataIt = mCachedRaces.find(raceId); data->reset(id);
if (raceDataIt == mCachedRaces.end())
int index = mRaces.searchId(id);
if (index == -1)
{ {
auto result = mCachedRaces.emplace(raceId, RaceData()); // Record does not exist
raceDataIt = result.first; return;
}
auto& raceRecord = mRaces.getRecord(index);
if (raceRecord.isDeleted())
{
// Record is deleted, so not accessible
return;
} }
auto& raceData = raceDataIt->second; // TODO move stuff in actor related to race here
raceData.femaleParts.clear();
raceData.maleParts.clear();
raceData.dependencies.clear();
// Construct entry // Setup body parts
for (int i = 0; i < mBodyParts.getSize(); ++i) for (int i = 0; i < mBodyParts.getSize(); ++i)
{ {
auto& record = mBodyParts.getRecord(i); std::string partId = mBodyParts.getId(i);
if (!record.isDeleted() && record.get().mRace == raceId) auto& partRecord = mBodyParts.getRecord(i);
{
auto& part = record.get();
// Part could affect race data if (partRecord.isDeleted())
raceData.dependencies.emplace(part.mId, true); {
// Record is deleted, so not accessible.
continue;
}
// Add base types auto& part = partRecord.get();
if (part.mData.mType == ESM::BodyPart::MT_Skin && !is1stPersonPart(part.mId)) if (part.mRace == id && part.mData.mType == ESM::BodyPart::MT_Skin && !is1stPersonPart(part.mId))
{ {
auto type = (ESM::BodyPart::MeshPart) part.mData.mPart; auto type = (ESM::BodyPart::MeshPart) part.mData.mPart;
// Note: Prefer the first part encountered for duplicates. emplace() does not overwrite bool female = part.mData.mFlags & ESM::BodyPart::BPF_Female;
if (part.mData.mFlags & ESM::BodyPart::BPF_Female) if (female) data->setFemalePart(type, part.mId);
raceData.femaleParts.emplace(type, part.mId); else data->setMalePart(type, part.mId);
else
raceData.maleParts.emplace(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); // Common setup, record is known to exist and is not deleted
if (index != -1) 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); if (item.mCount <= 0) continue;
int recordType = mReferenceables.getData(index, typeColumn).toInt(); std::string itemId = item.mItem.toString();
if (recordType == CSMWorld::UniversalId::Type_Creature) addNpcItem(itemId, data);
updateCreature(refId);
else if (recordType == CSMWorld::UniversalId::Type_Npc)
updateNpc(refId);
} }
// 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); int index = mReferenceables.searchId(itemId);
if (index == -1)
// Retrieve record if possible
if (record.isDeleted())
{ {
mCachedActors.erase(refId); // Item does not exist yet
emit actorChanged(refId); data->addOtherDependency(itemId);
return; return;
} }
auto& npc = dynamic_cast<const Record<ESM::NPC>&>(record).get();
// Create holder for cached data auto& record = mReferenceables.getRecord(index);
auto actorIt = mCachedActors.find(refId); if (record.isDeleted())
if (actorIt == mCachedActors.end())
{ {
auto result = mCachedActors.emplace(refId, ActorData()); // Item cannot be accessed yet
actorIt = result.first; data->addOtherDependency(itemId);
return;
} }
auto& actorData = actorIt->second;
// Reset old data
actorData.parts.clear();
actorData.dependencies.clear();
// Look at the npc's inventory first // Convenience function to add a parts list to actor data
for (auto& item : npc.mInventory.mList) auto addParts = [&](const ESM::PartReferenceList& list) {
{ for (auto& part : list.mParts)
if (item.mCount > 0)
{ {
std::string itemId = item.mItem.toString(); std::string partId;
// Handle armor and clothing auto partType = (ESM::PartReferenceType) part.mPart;
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);
}
}
}
}
}
}
// Lookup cached race parts if (data->isFemale())
auto& raceData = getRaceData(npc.mRace); partId = part.mFemale;
if (partId.empty())
partId = part.mMale;
// Changes to race could affect the actor if (!partId.empty()) data->setPart(partType, partId);
actorData.dependencies.emplace(npc.mRace, true); }
};
// Fill in the rest with race specific body parts int TypeColumn = mReferenceables.findColumnIndex(Columns::ColumnId_RecordType);
for (int i = 0; i < ESM::PRT_Count; ++i) int type = mReferenceables.getData(index, TypeColumn).toInt();
if (type == UniversalId::Type_Armor)
{ {
auto type = static_cast<ESM::PartReferenceType>(i); auto& armor = dynamic_cast<const Record<ESM::Armor>&>(record).get();
if (actorData.parts.find(type) == actorData.parts.end()) addParts(armor.mParts);
{
switch (type) // Changing parts could affect what is picked for rendering
{ data->addOtherDependency(itemId);
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);
}
}
}
}
} }
else if (type == UniversalId::Type_Clothing)
{
auto& clothing = dynamic_cast<const Record<ESM::Clothing>&>(record).get();
addParts(clothing.mParts);
// Signal change to actor // Changing parts could affect what is picked for rendering
emit actorChanged(refId); data->addOtherDependency(itemId);
}
} }
void ActorAdapter::updateCreature(const std::string& refId) void ActorAdapter::setupCreature(const std::string& id, ActorDataPtr data)
{ {
// Signal change to actor data->reset(id);
emit actorChanged(refId);
// 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 (actorIt->hasDependency(dep))
if (deps.find(id) != deps.end()) mDirtyActors.emplace(actorIt->getId());
updateActor(it.first);
} }
} }
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; RaceDataPtr data = mCachedRaces.get(race);
if (deps.find(id) != deps.end()) if (data)
updateRace(it.first); {
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 #ifndef CSM_WOLRD_ACTORADAPTER_H
#define CSM_WOLRD_ACTORADAPTER_H #define CSM_WOLRD_ACTORADAPTER_H
#include <functional> #include <array>
#include <unordered_map> #include <unordered_set>
#include <utility>
#include <QObject> #include <QObject>
#include <QModelIndex> #include <QModelIndex>
#include <components/cache/weakcache.hpp>
#include <components/esm/loadarmo.hpp> #include <components/esm/loadarmo.hpp>
#include <components/esm/loadbody.hpp> #include <components/esm/loadbody.hpp>
@ -17,88 +17,147 @@
namespace ESM namespace ESM
{ {
struct Race; struct Race;
enum PartReferenceType;
} }
namespace CSMWorld namespace CSMWorld
{ {
class Data; class Data;
/// Quick and dirty hashing functor. /// Adapts multiple collections to provide the data needed to render
struct StringBoolPairHash /// 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>(); public:
return stringHash(value.first) + value.second; /// 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 private:
{ bool handles(ESM::PartReferenceType type) const;
Q_OBJECT 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: 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 private:
using ActorPartMap = std::unordered_map<ESM::PartReferenceType, std::string>; 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&); public slots:
void handleRaceChanged(const QModelIndex&, const QModelIndex&);
void handleBodyPartChanged(const QModelIndex&, const QModelIndex&);
private: void handleReferenceablesInserted(const QModelIndex&, int, int);
// Maps mesh part type to 'body part' id void handleReferenceableChanged(const QModelIndex&, const QModelIndex&);
using RacePartMap = std::unordered_map<ESM::BodyPart::MeshPart, std::string>; void handleReferenceablesAboutToBeRemoved(const QModelIndex&, int, int);
// Stores ids that are referenced by the actor. Data part is meaningless. void handleReferenceablesRemoved(const QModelIndex&, int, int);
using DependencyMap = std::unordered_map<std::string, bool>;
void handleRacesInserted(const QModelIndex&, int, int);
struct ActorData void handleRaceChanged(const QModelIndex&, const QModelIndex&);
{ void handleRacesAboutToBeRemoved(const QModelIndex&, int, int);
ActorPartMap parts; void handleRacesRemoved(const QModelIndex&, int, int);
DependencyMap dependencies;
}; void handleBodyPartsInserted(const QModelIndex&, int, int);
void handleBodyPartChanged(const QModelIndex&, const QModelIndex&);
struct RaceData void handleBodyPartsAboutToBeRemoved(const QModelIndex&, int, int);
{ void handleBodyPartsRemoved(const QModelIndex&, int, int);
RacePartMap femaleParts;
RacePartMap maleParts; private:
DependencyMap dependencies;
}; ActorAdapter(const ActorAdapter&) = delete;
ActorAdapter& operator=(const ActorAdapter&) = delete;
ActorAdapter(const ActorAdapter&) = delete;
ActorAdapter& operator=(const ActorAdapter&) = delete; QModelIndex getHighestIndex(QModelIndex) const;
bool is1stPersonPart(const std::string& id) const;
QModelIndex getHighestIndex(QModelIndex) const;
bool is1stPersonPart(const std::string& id) const; RaceDataPtr getRaceData(const std::string& raceId);
RaceData& getRaceData(const std::string& raceId); void setupActor(const std::string& id, ActorDataPtr data);
void setupRace(const std::string& id, RaceDataPtr data);
void updateRace(const std::string& raceId);
void updateActor(const std::string& refId); void setupNpc(const std::string& id, ActorDataPtr data);
void updateNpc(const std::string& refId); void addNpcItem(const std::string& itemId, ActorDataPtr data);
void updateCreature(const std::string& refId);
void setupCreature(const std::string& id, ActorDataPtr data);
void updateActorsWithDependency(const std::string& id);
void updateRacesWithDependency(const std::string& id); void markDirtyDependency(const std::string& dependency);
void updateDirty();
RefIdCollection& mReferenceables;
IdCollection<ESM::Race>& mRaces; RefIdCollection& mReferenceables;
IdCollection<ESM::BodyPart>& mBodyParts; IdCollection<ESM::Race>& mRaces;
IdCollection<ESM::BodyPart>& mBodyParts;
// Key: referenceable id
std::unordered_map<std::string, ActorData> mCachedActors; cache::WeakCache<std::string, ActorData> mCachedActors; // Key: referenceable id
// Key: race id cache::WeakCache<std::string, RaceData> mCachedRaces; // Key: race id
std::unordered_map<std::string, RaceData> mCachedRaces;
StringSet mDirtyActors; // Actors that need updating
StringSet mDirtyRaces; // Races that need updating
}; };
} }

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

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

@ -150,6 +150,10 @@ add_component_dir (fallback
fallback validate fallback validate
) )
add_component_dir(cache
weakcache
)
if(NOT WIN32 AND NOT ANDROID) if(NOT WIN32 AND NOT ANDROID)
add_component_dir (crashcatcher add_component_dir (crashcatcher
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