Curing the Zombie Node Apocalypse

So in Resource Roulette I spawn a bunch of nodes, these nodes are spawned with the transient flag and their vital information is stored in a struct which is re-created every time the save is reloaded, vs serializing a bunch of actors to the savefile. However, somehow a large number of actors are being serialized to the savefile with incomplete information, so I need to ensure I 1. Properly cull these nodes in their entirety, to prevent them from being serialized 2. Find a way to search through and destroy any such nodes that currently exist in a save to improve load times I believe this is at least partially due to a conflict between Resource Roulette and Ficsit Farming / Refined Power, as the FF/RP node actors are serialized to savefile and RP/FF appears to spawn a large number of nodes on each load.
19 Replies
Beef
BeefOP•4w ago
No description
Beef
BeefOP•4w ago
My current "zombie curing" code is (partial method here as the rest is for collecting nodes) that runs when collecting the list of resource nodes (but runs only once in a savefile - the results are stored for the future so it doesn't run every time the game loads)
// Need to "cure" zombie nodes :\ I don't know how they get caused just yet
TArray<FName> ZombieNodeClassnames = {
"Desc_FF_Dirt_Fertilized_C",
"Desc_FF_Dirt_C",
"Desc_FF_Dirt_Wet_C",
"Desc_RP_Thorium_C"
};

for (TActorIterator<AFGResourceNode> It(World); It; ++It)
{
AFGResourceNode* ResourceNode = *It;

if (!UResourceRouletteUtility::IsValidAllInfiniteResourceNode(ResourceNode))
{
continue;
}
// Need to "cure" zombie nodes :\ I don't know how they get caused just yet
TArray<FName> ZombieNodeClassnames = {
"Desc_FF_Dirt_Fertilized_C",
"Desc_FF_Dirt_C",
"Desc_FF_Dirt_Wet_C",
"Desc_RP_Thorium_C"
};

for (TActorIterator<AFGResourceNode> It(World); It; ++It)
{
AFGResourceNode* ResourceNode = *It;

if (!UResourceRouletteUtility::IsValidAllInfiniteResourceNode(ResourceNode))
{
continue;
}
if (!ResourceNode->HasAnyFlags(RF_WasLoaded))
{
// Compare with our zombie node list
FName NodeClassName = ResourceNode->GetResourceClass()->GetFName();
if (ZombieNodeClassnames.Contains(NodeClassName))
{
// FResourceRouletteUtilityLog::Get().LogMessage(FString::Printf(TEXT("Killing zombie node: %s at location: %s"),
// *NodeClassName.ToString(),*ResourceNode->GetActorLocation().ToString()),ELogLevel::Warning);

TArray<UStaticMeshComponent*> MeshComponents;
ResourceNode->GetComponents(MeshComponents);
for (UStaticMeshComponent* MeshComponent : MeshComponents)
{
if (MeshComponent)
{
MeshComponent->SetVisibility(false);
MeshComponent->SetCollisionEnabled(ECollisionEnabled::NoCollision);
MeshComponent->DestroyComponent();
}
}
TArray<UDecalComponent*> DecalComponents;
for (UDecalComponent* DecalComponent : DecalComponents)
{
if (DecalComponent)
{
DecalComponent->SetVisibility(false);
DecalComponent->DestroyComponent();
}
}
// and then the actor
ResourceNode->SetFlags(EObjectFlags::RF_Transient);
if (AFGActorRepresentationManager* RepManager = AFGActorRepresentationManager::Get(GetWorld()))
{
RepManager->RemoveRepresentationOfActor(ResourceNode);
}
ResourceNode->Destroy();
continue;
}
}
if (!ResourceNode->HasAnyFlags(RF_WasLoaded))
{
// Compare with our zombie node list
FName NodeClassName = ResourceNode->GetResourceClass()->GetFName();
if (ZombieNodeClassnames.Contains(NodeClassName))
{
// FResourceRouletteUtilityLog::Get().LogMessage(FString::Printf(TEXT("Killing zombie node: %s at location: %s"),
// *NodeClassName.ToString(),*ResourceNode->GetActorLocation().ToString()),ELogLevel::Warning);

TArray<UStaticMeshComponent*> MeshComponents;
ResourceNode->GetComponents(MeshComponents);
for (UStaticMeshComponent* MeshComponent : MeshComponents)
{
if (MeshComponent)
{
MeshComponent->SetVisibility(false);
MeshComponent->SetCollisionEnabled(ECollisionEnabled::NoCollision);
MeshComponent->DestroyComponent();
}
}
TArray<UDecalComponent*> DecalComponents;
for (UDecalComponent* DecalComponent : DecalComponents)
{
if (DecalComponent)
{
DecalComponent->SetVisibility(false);
DecalComponent->DestroyComponent();
}
}
// and then the actor
ResourceNode->SetFlags(EObjectFlags::RF_Transient);
if (AFGActorRepresentationManager* RepManager = AFGActorRepresentationManager::Get(GetWorld()))
{
RepManager->RemoveRepresentationOfActor(ResourceNode);
}
ResourceNode->Destroy();
continue;
}
}
My next step will be to see how many "None" resource type nodes get created on each save/close/reload cycle
Rex
Rex•4w ago
FF/RP will spawn their nodes via a sublevel, so you have to either stop that from happening (not trivial) or always move the newly-created nodes Those won't be the only mods doing it ExampleMod now spawns resource nodes using a similar approach Currently only on ExampleLevel, but that's slated to change (people want to know how to add nodes to an existing level, typically the base game level)
Beef
BeefOP•4w ago
Interesting results
No description
No description
Beef
BeefOP•4w ago
So 27 "None" type resources are spawned on a new game, although this may just be a limitation of how I'm putting the output here, but after reloading/saving again there are now 459 of a different type of "None" resource in the save, that don't have either the WasLoaded or Transient flags
Rex
Rex•4w ago
If you add all the RP/FF nodes, does it get close to 459?
Beef
BeefOP•4w ago
459 implies it's an issue with my mod not a conflict here If I include the water/dam nodes, yes - there are 467 RP/FF nodes... but there are exactly 459 Vanilla nodes that I touch, so it appears that the vanilla nodes are the source of the issue and RP/FF isn't
Rex
Rex•4w ago
What happens without RP/FF? Also, why do you use the transient flag?
Beef
BeefOP•4w ago
^ transient flag prevents them being included in the savefile *Should prevent anyways :LUL:
Rex
Rex•4w ago
I don't think it works that way If anything you'll end up saving something, but not its contents Are the zombie nodes fully zeroed out?
Beef
BeefOP•4w ago
Yes it appears that the transient flag doesn't prevent them from being saved, so that's likely going to be the issue here
Beef
BeefOP•4w ago
there it is
No description
Beef
BeefOP•4w ago
Bah virtual functions \SML\SML\Mods\SML\Source\SML\Private\Patching\NativeHookManager.cpp] [Line: 61] Hooking function AFGResourceNodeBase::ShouldSave_Implementation failed: funchook failed: Too short instructions simple but less elegant solution will just be to destroy these nodes on load if we can't easily prevent them from being saved yeah that was way simpler, already fixed. loading 459 extra actors only to destroy them isn't nice, but takes significantly less time than loading 57742 of them :fonic:
Rex
Rex•4w ago
If you spawn your own actors you could consider spawning a subclass that overrides ShouldSave Does ShouldSave exist and can it be hooked?
Beef
BeefOP•4w ago
I'm not sure if it's pure vitual or if it's just too short, but since it's just "return true" it seems it doesn't matter as it's too short to be hooked anyways reading discord history
Rex
Rex•4w ago
Not ShouldSave_Implementation ShouldSave
Beef
BeefOP•4w ago
As far as the subclass - I know that the better solution here is to make a subclass but I'm so deep into the weeds of this mod that that's a non-trivial task to go back and rework that. A fair bit of logic depends on having vanilla classes. I think this is the better solution long-term, it was an oversight back when I started this mod unfortunately Can you hook an interface? ShouldSave() is part of IFGSaveInterface definition
class FACTORYGAME_API IFGSaveInterface
{
GENERATED_IINTERFACE_BODY()

....


/**
* Called before PreSaveGame, used to determine if the object want to be saved
*
* @return true if the object want to be saved
*/
UFUNCTION( BlueprintNativeEvent )
bool ShouldSave() const;
class FACTORYGAME_API IFGSaveInterface
{
GENERATED_IINTERFACE_BODY()

....


/**
* Called before PreSaveGame, used to determine if the object want to be saved
*
* @return true if the object want to be saved
*/
UFUNCTION( BlueprintNativeEvent )
bool ShouldSave() const;
Rex
Rex•4w ago
AIUI there's some code there that calls the C++ _Implementation function or a BP function if it exists
Beef
BeefOP•4w ago
I think the subclass and just overriding there is a better approach anyways, will be cleaner in the long run Just have to rework a fair bit of logic, but it's going to be better and simpler approach to maintain, and give more flexibility for future functionality as well So I'll get the shorter-term fix out tonight and work on migrating to subclasses in the future 🙂

Did you find this page helpful?