Simplify update logic, update when race parts are changed.

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

@ -24,22 +24,22 @@ namespace CSMWorld
this, SLOT(handleBodyPartChanged(const QModelIndex&, const QModelIndex&))); this, SLOT(handleBodyPartChanged(const QModelIndex&, const QModelIndex&)));
} }
const ActorAdapter::ActorPartMap* ActorAdapter::getActorPartMap(const std::string& refId) const ActorAdapter::ActorPartMap* ActorAdapter::getActorParts(const std::string& refId, bool create)
{ {
auto it = mActorPartMaps.find(refId); auto it = mCachedActors.find(refId);
if (it != mActorPartMaps.end()) if (it != mCachedActors.end())
{ {
return &it->second; return &it->second.parts;
} }
else else if (create)
{ {
updateActor(refId); updateActor(refId);
it = mActorPartMaps.find(refId); return getActorParts(refId, false);
if (it != mActorPartMaps.end()) }
return &it->second; else
{
return nullptr;
} }
return nullptr;
} }
void ActorAdapter::handleReferenceableChanged(const QModelIndex& topLeft, const QModelIndex& botRight) void ActorAdapter::handleReferenceableChanged(const QModelIndex& topLeft, const QModelIndex& botRight)
@ -57,17 +57,13 @@ namespace CSMWorld
{ {
// Update the cached npc or creature // Update the cached npc or creature
std::string refId = mReferenceables.getId(row); std::string refId = mReferenceables.getId(row);
if (mActorPartMaps.find(refId) != mActorPartMaps.end()) if (mCachedActors.find(refId) != mCachedActors.end())
updateActor(refId); updateActor(refId);
} }
else if (type == CSMWorld::UniversalId::Type_Armor) else if (type == CSMWorld::UniversalId::Type_Armor || type == CSMWorld::UniversalId::Type_Clothing)
{ {
// TODO update everything? std::string refId = mReferenceables.getId(row);
// store all items referenced when creating map and check against that here updateActorsWithDependency(refId);
}
else if (type == CSMWorld::UniversalId::Type_Clothing)
{
// TODO update everything?
} }
} }
} }
@ -79,14 +75,28 @@ namespace CSMWorld
for (int row = rowStart; row <= rowEnd; ++row) for (int row = rowStart; row <= rowEnd; ++row)
{ {
std::string raceId = mRaces.getId(row); std::string raceId = mRaces.getId(row);
updateNpcsWithRace(raceId); updateActorsWithDependency(raceId);
} }
} }
void ActorAdapter::handleBodyPartChanged(const QModelIndex& topLeft, const QModelIndex& botRight) void ActorAdapter::handleBodyPartChanged(const QModelIndex& topLeft, const QModelIndex& botRight)
{ {
// TODO int rowStart = getHighestIndex(topLeft).row();
Log(Debug::Info) << "Body Part Changed (" << topLeft.row() << ", " << topLeft.column() << ") (" << botRight.row() << ", " << botRight.column() << ")"; int rowEnd = getHighestIndex(botRight).row();
for (int row = rowStart; row <= rowEnd; ++row)
{
// Manually update race specified by part
auto& record = mBodyParts.getRecord(row);
if (!record.isDeleted())
{
updateRace(record.get().mRace);
}
// Update entries with a tracked dependency
std::string partId = mBodyParts.getId(row);
updateRacesWithDependency(partId);
updateActorsWithDependency(partId);
}
} }
QModelIndex ActorAdapter::getHighestIndex(QModelIndex index) const QModelIndex ActorAdapter::getHighestIndex(QModelIndex index) const
@ -96,47 +106,66 @@ namespace CSMWorld
return index; return index;
} }
ActorAdapter::RacePartMap& ActorAdapter::getOrCreateRacePartMap(const std::string& raceId, bool isFemale) bool ActorAdapter::is1stPersonPart(const std::string& name) const
{ {
auto key = std::make_pair(raceId, isFemale); return name.size() >= 4 && name.find(".1st", name.size() - 4) != std::string::npos;
auto it = mRacePartMaps.find(key); }
if (it != mRacePartMaps.end())
ActorAdapter::RaceData& ActorAdapter::getRaceData(const std::string& raceId)
{
auto it = mCachedRaces.find(raceId);
if (it != mCachedRaces.end())
{ {
return it->second; return it->second;
} }
else else
{ {
// Create and find result // Create and find result
updateRaceParts(raceId); updateRace(raceId);
return mRacePartMaps.find(key)->second; return mCachedRaces.find(raceId)->second;
} }
} }
void ActorAdapter::updateRaceParts(const std::string& raceId) void ActorAdapter::updateRace(const std::string& raceId)
{ {
// Convenience function to determine if part is for 1st person view // Retrieve or create cache entry
auto is1stPersonPart = [](std::string name) { auto raceDataIt = mCachedRaces.find(raceId);
return name.size() >= 4 && name.find(".1st", name.size() - 4) != std::string::npos; if (raceDataIt == mCachedRaces.end())
}; {
auto result = mCachedRaces.emplace(raceId, RaceData());
raceDataIt = result.first;
}
auto& raceData = raceDataIt->second;
raceData.femaleParts.clear();
raceData.maleParts.clear();
raceData.dependencies.clear();
RacePartMap maleMap, femaleMap; // Construct entry
for (int i = 0; i < mBodyParts.getSize(); ++i) for (int i = 0; i < mBodyParts.getSize(); ++i)
{ {
auto& record = mBodyParts.getRecord(i); auto& record = mBodyParts.getRecord(i);
if (!record.isDeleted() && record.get().mRace == raceId && record.get().mData.mType == ESM::BodyPart::MT_Skin && !is1stPersonPart(record.get().mId)) if (!record.isDeleted() && record.get().mRace == raceId)
{ {
auto& part = record.get(); auto& part = record.get();
auto type = (ESM::BodyPart::MeshPart) part.mData.mPart;
// Note: Prefer the first part encountered for duplicates. emplace() does not overwrite // Part could affect race data
if (part.mData.mFlags & ESM::BodyPart::BPF_Female) raceData.dependencies.emplace(part.mId, true);
femaleMap.emplace(type, part.mId);
else // Add base types
maleMap.emplace(type, part.mId); 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);
}
} }
} }
mRacePartMaps[std::make_pair(raceId, true)] = femaleMap; updateActorsWithDependency(raceId);
mRacePartMaps[std::make_pair(raceId, false)] = maleMap;
} }
void ActorAdapter::updateActor(const std::string& refId) void ActorAdapter::updateActor(const std::string& refId)
@ -156,17 +185,28 @@ namespace CSMWorld
void ActorAdapter::updateNpc(const std::string& refId) void ActorAdapter::updateNpc(const std::string& refId)
{ {
auto& record = mReferenceables.getRecord(refId); auto& record = mReferenceables.getRecord(refId);
// Retrieve record if possible
if (record.isDeleted()) if (record.isDeleted())
{ {
mActorPartMaps.erase(refId); mCachedActors.erase(refId);
emit actorChanged(refId);
return; return;
} }
auto& npc = dynamic_cast<const Record<ESM::NPC>&>(record).get(); auto& npc = dynamic_cast<const Record<ESM::NPC>&>(record).get();
auto& femaleRacePartMap = getOrCreateRacePartMap(npc.mRace, true);
auto& maleRacePartMap = getOrCreateRacePartMap(npc.mRace, false);
ActorPartMap npcMap; // Create holder for cached data
auto actorIt = mCachedActors.find(refId);
if (actorIt == mCachedActors.end())
{
auto result = mCachedActors.emplace(refId, ActorData());
actorIt = result.first;
}
auto& actorData = actorIt->second;
// Reset old data
actorData.parts.clear();
actorData.dependencies.clear();
// Look at the npc's inventory first // Look at the npc's inventory first
for (auto& item : npc.mInventory.mList) for (auto& item : npc.mInventory.mList)
@ -174,7 +214,7 @@ namespace CSMWorld
if (item.mCount > 0) if (item.mCount > 0)
{ {
std::string itemId = item.mItem.toString(); std::string itemId = item.mItem.toString();
// Handle armor, weapons, and clothing // Handle armor and clothing
int index = mReferenceables.searchId(itemId); int index = mReferenceables.searchId(itemId);
if (index != -1 && !mReferenceables.getRecord(index).isDeleted()) if (index != -1 && !mReferenceables.getRecord(index).isDeleted())
{ {
@ -184,6 +224,10 @@ namespace CSMWorld
int recordType = mReferenceables.getData(index, typeColumn).toInt(); int recordType = mReferenceables.getData(index, typeColumn).toInt();
if (recordType == CSMWorld::UniversalId::Type_Armor) 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(); auto& armor = dynamic_cast<const Record<ESM::Armor>&>(itemRecord).get();
for (auto& part : armor.mParts.mParts) for (auto& part : armor.mParts.mParts)
{ {
@ -194,11 +238,18 @@ namespace CSMWorld
bodyPartId = part.mMale; bodyPartId = part.mMale;
if (!bodyPartId.empty()) if (!bodyPartId.empty())
npcMap.emplace(static_cast<ESM::PartReferenceType>(part.mPart), bodyPartId); {
actorData.parts.emplace(static_cast<ESM::PartReferenceType>(part.mPart), bodyPartId);
actorData.dependencies.emplace(bodyPartId, true);
}
} }
} }
else if (recordType == CSMWorld::UniversalId::Type_Clothing) 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(); auto& clothing = dynamic_cast<const Record<ESM::Clothing>&>(itemRecord).get();
for (auto& part : clothing.mParts.mParts) for (auto& part : clothing.mParts.mParts)
{ {
@ -209,26 +260,37 @@ namespace CSMWorld
bodyPartId = part.mMale; bodyPartId = part.mMale;
if (!bodyPartId.empty()) if (!bodyPartId.empty())
npcMap.emplace(static_cast<ESM::PartReferenceType>(part.mPart), bodyPartId); {
actorData.parts.emplace(static_cast<ESM::PartReferenceType>(part.mPart), bodyPartId);
actorData.dependencies.emplace(bodyPartId, true);
}
} }
} }
} }
} }
} }
// Fill in the rest with body parts // Lookup cached race parts
auto& raceData = getRaceData(npc.mRace);
// Changes to race could affect the actor
actorData.dependencies.emplace(npc.mRace, true);
// Fill in the rest with race specific body parts
for (int i = 0; i < ESM::PRT_Count; ++i) for (int i = 0; i < ESM::PRT_Count; ++i)
{ {
auto type = static_cast<ESM::PartReferenceType>(i); auto type = static_cast<ESM::PartReferenceType>(i);
if (npcMap.find(type) == npcMap.end()) if (actorData.parts.find(type) == actorData.parts.end())
{ {
switch (type) switch (type)
{ {
case ESM::PRT_Head: case ESM::PRT_Head:
npcMap.emplace(type, npc.mHead); actorData.parts.emplace(type, npc.mHead);
actorData.dependencies.emplace(npc.mHead, true);
break; break;
case ESM::PRT_Hair: case ESM::PRT_Hair:
npcMap.emplace(type, npc.mHair); actorData.parts.emplace(type, npc.mHair);
actorData.dependencies.emplace(npc.mHair, true);
break; break;
case ESM::PRT_Skirt: case ESM::PRT_Skirt:
case ESM::PRT_Shield: case ESM::PRT_Shield:
@ -243,48 +305,57 @@ namespace CSMWorld
// Check female map if applicable // Check female map if applicable
if (!npc.isMale()) if (!npc.isMale())
{ {
auto partIt = femaleRacePartMap.find(ESM::getMeshPart(type)); auto partIt = raceData.femaleParts.find(ESM::getMeshPart(type));
if (partIt != femaleRacePartMap.end()) if (partIt != raceData.femaleParts.end())
bodyPartId = partIt->second; bodyPartId = partIt->second;
} }
// Check male map next // Check male map next
if (bodyPartId.empty() || npc.isMale()) if (bodyPartId.empty() || npc.isMale())
{ {
auto partIt = maleRacePartMap.find(ESM::getMeshPart(type)); auto partIt = raceData.maleParts.find(ESM::getMeshPart(type));
if (partIt != maleRacePartMap.end()) if (partIt != raceData.maleParts.end())
bodyPartId = partIt->second; bodyPartId = partIt->second;
} }
// Add to map // Add to map
if (!bodyPartId.empty()) if (!bodyPartId.empty())
{ {
npcMap.emplace(type, bodyPartId); actorData.parts.emplace(type, bodyPartId);
actorData.dependencies.emplace(bodyPartId, true);
} }
} }
} }
} }
} }
mActorPartMaps[refId] = npcMap; // Signal change to actor
emit actorChanged(refId); emit actorChanged(refId);
} }
void ActorAdapter::updateCreature(const std::string& refId) void ActorAdapter::updateCreature(const std::string& refId)
{ {
// Signal change to actor
emit actorChanged(refId); emit actorChanged(refId);
} }
void ActorAdapter::updateNpcsWithRace(const std::string& raceId) void ActorAdapter::updateActorsWithDependency(const std::string& id)
{ {
for (auto it : mActorPartMaps) for (auto it : mCachedActors)
{ {
auto& refId = it.first; auto& deps = it.second.dependencies;
auto& npc = dynamic_cast<const Record<ESM::NPC>&>(mReferenceables.getRecord(refId)).get(); if (deps.find(id) != deps.end())
if (npc.mRace == raceId) updateActor(it.first);
{ }
updateNpc(refId); }
}
void ActorAdapter::updateRacesWithDependency(const std::string& id)
{
for (auto it : mCachedRaces)
{
auto& deps = it.second.dependencies;
if (deps.find(id) != deps.end())
updateRace(it.first);
} }
} }
} }

@ -41,12 +41,10 @@ namespace CSMWorld
// Maps body part type to 'body part' id // Maps body part type to 'body part' id
using ActorPartMap = std::unordered_map<ESM::PartReferenceType, std::string>; using ActorPartMap = std::unordered_map<ESM::PartReferenceType, std::string>;
// Maps mesh part type to 'body part' id
using RacePartMap = std::unordered_map<ESM::BodyPart::MeshPart, std::string>;
ActorAdapter(CSMWorld::Data& data); ActorAdapter(CSMWorld::Data& data);
const ActorPartMap* getActorPartMap(const std::string& refId); const ActorPartMap* getActorParts(const std::string& refId, bool create=true);
signals: signals:
@ -59,29 +57,48 @@ namespace CSMWorld
void handleBodyPartChanged(const QModelIndex&, const QModelIndex&); void handleBodyPartChanged(const QModelIndex&, const QModelIndex&);
private: 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(const ActorAdapter&) = delete;
ActorAdapter& operator=(const ActorAdapter&) = delete; ActorAdapter& operator=(const ActorAdapter&) = delete;
QModelIndex getHighestIndex(QModelIndex) const; QModelIndex getHighestIndex(QModelIndex) const;
bool is1stPersonPart(const std::string& id) const;
RacePartMap& getOrCreateRacePartMap(const std::string& raceId, bool isFemale); RaceData& getRaceData(const std::string& raceId);
void updateRaceParts(const std::string& raceId); void updateRace(const std::string& raceId);
void updateActor(const std::string& refId); void updateActor(const std::string& refId);
void updateNpc(const std::string& refId); void updateNpc(const std::string& refId);
void updateCreature(const std::string& refId); void updateCreature(const std::string& refId);
void updateNpcsWithRace(const std::string& raceId); void updateActorsWithDependency(const std::string& id);
void updateRacesWithDependency(const std::string& id);
RefIdCollection& mReferenceables; RefIdCollection& mReferenceables;
IdCollection<ESM::Race>& mRaces; IdCollection<ESM::Race>& mRaces;
IdCollection<ESM::BodyPart>& mBodyParts; IdCollection<ESM::BodyPart>& mBodyParts;
// Key: referenceable id // Key: referenceable id
std::unordered_map<std::string, ActorPartMap> mActorPartMaps; std::unordered_map<std::string, ActorData> mCachedActors;
// Key: race id, is female // Key: race id
std::unordered_map<std::pair<std::string, bool>, RacePartMap, StringBoolPairHash> mRacePartMaps; std::unordered_map<std::string, RaceData> mCachedRaces;
}; };
} }

@ -142,10 +142,10 @@ namespace CSVRender
void Actor::loadBodyParts(const std::string& actorId) void Actor::loadBodyParts(const std::string& actorId)
{ {
auto actorAdapter = mData.getActorAdapter(); auto actorAdapter = mData.getActorAdapter();
auto partMap = actorAdapter->getActorPartMap(actorId); auto parts = actorAdapter->getActorParts(actorId);
if (partMap) if (parts)
{ {
for (auto& pair : *partMap) for (auto& pair : *parts)
attachBodyPart(pair.first, getBodyPartMesh(pair.second)); attachBodyPart(pair.first, getBodyPartMesh(pair.second));
} }
} }

Loading…
Cancel
Save