Skip to content

Commit

Permalink
Handled deletion of temp actors.
Browse files Browse the repository at this point in the history
replacements are now tracked by actor formIDs. Less memory consumption, better handling of deleted actors.
  • Loading branch information
adya committed Jul 18, 2024
1 parent 50858bd commit ac65d98
Show file tree
Hide file tree
Showing 3 changed files with 43 additions and 18 deletions.
9 changes: 7 additions & 2 deletions SPID/include/OutfitManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

namespace Outfits
{
class Manager : public ISingleton<Manager>
class Manager :
public ISingleton<Manager>,
public RE::BSTEventSink<RE::TESFormDeleteEvent>
{
public:
static void Register();
Expand All @@ -23,6 +25,9 @@ namespace Outfits
/// </summary>
void UseOriginalOutfit(RE::Actor*);

protected:
RE::BSEventNotifyControl ProcessEvent(const RE::TESFormDeleteEvent* a_event, RE::BSTEventSource<RE::TESFormDeleteEvent>*) override;

private:
static void Load(SKSE::SerializationInterface*);
static void Save(SKSE::SerializationInterface*);
Expand Down Expand Up @@ -50,7 +55,7 @@ namespace Outfits

friend fmt::formatter<Outfits::Manager::OutfitReplacement>;

std::unordered_map<RE::Actor*, OutfitReplacement> replacements;
std::unordered_map<RE::FormID, OutfitReplacement> replacements;
};
}

Expand Down
2 changes: 1 addition & 1 deletion SPID/src/DistributeManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ namespace Distribute::Event
{
if (const auto scripts = RE::ScriptEventSourceHolder::GetSingleton()) {
scripts->AddEventSink<RE::TESFormDeleteEvent>(GetSingleton());
logger::info("Registered for {}", typeid(RE::TESFormDeleteEvent).name());
logger::info("Registered Distribution Manager for {}", typeid(RE::TESFormDeleteEvent).name());
}
}

Expand Down
50 changes: 35 additions & 15 deletions SPID/src/OutfitManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -95,13 +95,13 @@ namespace Outfits
return true;
}

bool Save(SKSE::SerializationInterface* a_interface, const RE::Actor* actor, const RE::BGSOutfit* original, const RE::BGSOutfit* distributed)
bool Save(SKSE::SerializationInterface* a_interface, const RE::FormID actorFormId, const RE::BGSOutfit* original, const RE::BGSOutfit* distributed)
{
if (!a_interface->OpenRecord(recordType, serializationVersion)) {
return false;
}

return details::Write(a_interface, actor->formID) &&
return details::Write(a_interface, actorFormId) &&
details::Write(a_interface, original->formID) &&
details::Write(a_interface, distributed ? distributed->formID : 0);
}
Expand All @@ -114,6 +114,19 @@ namespace Outfits
serializationInterface->SetSaveCallback(Save);
serializationInterface->SetLoadCallback(Load);
serializationInterface->SetRevertCallback(Revert);

if (const auto scripts = RE::ScriptEventSourceHolder::GetSingleton()) {
scripts->AddEventSink<RE::TESFormDeleteEvent>(GetSingleton());
logger::info("Registered Outfit Manager for {}", typeid(RE::TESFormDeleteEvent).name());
}
}

RE::BSEventNotifyControl Manager::ProcessEvent(const RE::TESFormDeleteEvent* a_event, RE::BSTEventSource<RE::TESFormDeleteEvent>*)
{
if (a_event && a_event->formID != 0) {
replacements.erase(a_event->formID);
}
return RE::BSEventNotifyControl::kContinue;
}

bool CanEquipOutfit(const RE::Actor* actor, RE::BGSOutfit* outfit)
Expand Down Expand Up @@ -141,7 +154,7 @@ namespace Outfits
auto* npc = actor->GetActorBase();
auto defaultOutfit = npc->defaultOutfit;

if (!allowOverwrites && replacements.find(actor) != replacements.end()) {
if (!allowOverwrites && replacements.find(actor->formID) != replacements.end()) {
return true;
}

Expand All @@ -154,10 +167,10 @@ namespace Outfits

actor->SetDefaultOutfit(outfit, false); // Having true here causes infinite loading. It seems that it works either way.

if (auto previous = replacements.find(actor); previous != replacements.end()) {
if (auto previous = replacements.find(actor->formID); previous != replacements.end()) {
previous->second.distributed = outfit;
} else if (defaultOutfit) {
replacements.try_emplace(actor, defaultOutfit, outfit);
replacements.try_emplace(actor->formID, defaultOutfit, outfit);
}

return true;
Expand All @@ -166,10 +179,10 @@ namespace Outfits
void Manager::UseOriginalOutfit(RE::Actor* actor)
{
if (auto npc = actor->GetActorBase(); npc && npc->defaultOutfit) {
if (replacements.find(actor) != replacements.end()) {
if (replacements.find(actor->formID) != replacements.end()) {
logger::warn("Overwriting replacement for {}", *actor);
}
replacements.try_emplace(actor, npc->defaultOutfit);
replacements.try_emplace(actor->formID, npc->defaultOutfit);
}
}

Expand Down Expand Up @@ -207,7 +220,9 @@ namespace Outfits

logger::info("Cached {} Outfit Replacements", newReplacements.size());
for (const auto& pair : newReplacements) {
logger::info("\t{}", *pair.first);
if (const auto actor = RE::TESForm::LookupByID<RE::Actor>(pair.first); actor) {
logger::info("\t{}", *actor);
}
logger::info("\t\t{}", pair.second);
}

Expand All @@ -216,7 +231,7 @@ namespace Outfits
const auto& actor = it.first;
const auto& replacement = it.second;

if (auto newIt = newReplacements.find(actor); newIt != newReplacements.end()) {
if (auto newIt = newReplacements.find(actor->formID); newIt != newReplacements.end()) {
if (newIt->second.UsesOriginalOutfit()) { // If new replacement uses original outfit
if (!replacement.UsesOriginalOutfit() && replacement.distributed == actor->GetActorBase()->defaultOutfit) { // but previous one doesn't and NPC still wears the distributed outfit
#ifndef NDEBUG
Expand All @@ -232,7 +247,7 @@ namespace Outfits
}

} else { // If there is no new distribution, we want to keep the old one, assuming that whatever outfit is stored in this replacement is what NPC still wears in this save file
newReplacements[actor] = replacement;
newReplacements[actor->formID] = replacement;
}
}

Expand All @@ -245,18 +260,23 @@ namespace Outfits
{
logger::info("{:*^30}", "SAVING");

auto outfits = Manager::GetSingleton()->replacements;
auto replacements = Manager::GetSingleton()->replacements;

logger::info("Saving {} distributed outfits...", outfits.size());
logger::info("Saving {} distributed outfits...", replacements.size());

std::uint32_t savedCount = 0;
for (const auto& pair : outfits) {
for (const auto& pair : replacements) {
if (!Data::Save(interface, pair.first, pair.second.original, pair.second.distributed)) {
logger::error("Failed to save Outfit Replacement ({}) for {}", pair.second, *pair.first);
if (const auto actor = RE::TESForm::LookupByID<RE::Actor>(pair.first); actor) {
logger::error("Failed to save Outfit Replacement ({}) for {}", pair.second, *actor);
}

continue;
}
#ifndef NDEBUG
logger::info("\tSaved Outfit Replacement ({}) for actor {}", pair.second, *pair.first);
if (const auto actor = RE::TESForm::LookupByID<RE::Actor>(pair.first); actor) {
logger::info("\tSaved Outfit Replacement ({}) for actor {}", pair.second, *actor);
}
#endif
++savedCount;
}
Expand Down

0 comments on commit ac65d98

Please sign in to comment.