Download

[4.3] Error in garbage collection in my inventory manager when running PIE

I have an inventory manager set up to start handing my character’s inventory and currently don’t call anything in the class except for the creation of the inventory slots which has a pointer to the item actors.

I am getting an error and it triggers a break point when I am in the editor running my game in the PIE.

I am using the 4.3 preview so this could be an issue with the 4.3 preview. I have had this inventory manager code in my 4.2 game and I am able to play the game for much longer at a time with no errors.

I just got through setting up a temporary day/night system in blueprints when I noticed the error. I got an error before when trying to play with the new level streaming features and thought it was due to that without actually taking a look at the error. After getting the error multiple times in a row, I knew it was not a coincidence and I am having trouble narrowing down the cause.

Below is the error.


[2014.07.15-03.51.38:890][340]LogUObjectBase:Error: 'this' pointer is invalid.
D:\BuildFarm\buildmachine_++depot+UE4-Releases+4.3\Engine\Source\Runtime\CoreUObject\Private\UObject\GarbageCollection.cpp(273): Fatal error:
Invalid object in GC: 0x0000000000000005, ReferencingObject: InventoryManager /Game/Maps/UEDPIE_0_ExileMap.ExileMap:PersistentLevel.InventoryManager_0, ReferencingProperty: ObjectProperty /Script/Exile.InventoryManager:InventorySlot.Item

Below is the code I currently have for my InventoryManager.

InventoryManager.h





#pragma once

#include "GameFramework/Actor.h"
#include "Item.h"
#include "InventoryManager.generated.h"

USTRUCT()
struct FInventorySlot
{
	GENERATED_USTRUCT_BODY()

	UPROPERTY()
	FName SlotName;

	UPROPERTY()
	AItem* Item;

	FInventorySlot()
	{
		SlotName = "";
	}

	FInventorySlot(FName Name)
	{
		SlotName = Name;
	}
};

USTRUCT()
struct FInventoryRow
{
	GENERATED_USTRUCT_BODY()

	UPROPERTY()
	TArray<FInventorySlot> Columns;

	void AddNewColumn()
	{
		Columns.Add(FInventorySlot(""));
	}

	FInventoryRow()
	{

	}
};

USTRUCT()
struct FInventorySpace
{
	GENERATED_USTRUCT_BODY()

	UPROPERTY()
	TArray<FInventoryRow> Rows;

	void AddNewRow()
	{
		Rows.Add(FInventoryRow());
	}

	void AddUninitialized(const int32 ColCount, const int32 RowCount)
	{
		//Add Columns
		for (int32 r = 0; r < RowCount; r++)
		{
			AddNewRow();

			for (int32 c = 0; c < ColCount; c++)
			{
				Rows[r].AddNewColumn();
			}
		}
	}

	void Clear()
	{
		if (Rows.Num() <= 0) return;
		//~~~~~~~~~~~~~~~

		//Destroy any walls
		const int32 RowTotal = Rows.Num();
		const int32 ColTotal = Rows[0].Columns.Num();

		for (int32 r = 0; r < RowTotal; r++)
		{
			for (int32 c = 0; c < ColTotal; c++)
			{
				if (Rows[r].Columns[c].Item && Rows[r].Columns[c].Item->IsValidLowLevel())
				{
					Rows[r].Columns[c].Item->Destroy();
				}
			}
		}

		//Empty
		for (int32 r = 0; r < Rows.Num(); r++)
		{
			Rows[r].Columns.Empty();
		}
		Rows.Empty();
	}

	FInventorySpace()
	{

	}
};

/**
 * 
 */
UCLASS()
class EXILE_API AInventoryManager : public AActor
{
	GENERATED_UCLASS_BODY()

	// The main inventory space in a grid (player backpack,lootable items)
	UPROPERTY()
	FInventorySpace Inventory;

	// Array of Item Slots for the Hotbar used by Players
	UPROPERTY()
	TArray<FInventorySlot> Hotbar;

	// Array of Inventory Slots for the Equipment used by players and ai
	UPROPERTY()
	TArray<FInventorySlot> Equipment;

	UFUNCTION()
	void CreateInventory(int32 ColumnCount, int32 RowCount);

	UFUNCTION()
	void CreateHotbarSlots(int32 Count);

	UFUNCTION()
	void CreateEqupmentSlots();

	UFUNCTION()
	int32 GetInventorySize();
	
	// Move an item from one inventory slot to another
	UFUNCTION()
	bool MoveItem(FInventorySlot& Origin, FInventorySlot& Destination, AItem* Item);

	UFUNCTION()
	bool AddItemToInventory(AItem* Item);

	UFUNCTION()
	bool IsInventoryFull();

	// Package entire inventory (Inventory + Hotbar + Equipment) into a single Inventory.
	// Used for creating a Lootable Actor from this inventory when a player or ai is killed and drops a pack of items.
	UFUNCTION()
	AInventoryManager* CreateDroppedInventory(int32 ColumnCount);
	
};


InventoryManager.cpp





#include "Exile.h"
#include "InventoryManager.h"


AInventoryManager::AInventoryManager(const class FPostConstructInitializeProperties& PCIP)
	: Super(PCIP)
{

}


void AInventoryManager::CreateInventory(int32 ColumnCount, int32 RowCount)
{
	Inventory.Clear();
	Inventory.AddUninitialized(ColumnCount, RowCount);
}

void AInventoryManager::CreateHotbarSlots(int32 Count)
{
	for (int32 i = 0; i < Count; i++)
	{
		Hotbar.Add(FInventorySlot(""));
	}
}

void AInventoryManager::CreateEqupmentSlots()
{
	// Add Head Item Slot
	Equipment.Add(FInventorySlot("Head"));

	// Add Chest Item Slot
	Equipment.Add(FInventorySlot("Chest"));

	// Add Legs Item Slot
	Equipment.Add(FInventorySlot("Legs"));

	// Add Feet Item Slot
	Equipment.Add(FInventorySlot("Feet"));
}

int32 AInventoryManager::GetInventorySize()
{
	if (Inventory.Rows.Num() <= 0)
	{
		return 0;
	}

	return Inventory.Rows.Num() * Inventory.Rows[0].Columns.Num();
}

bool AInventoryManager::MoveItem(FInventorySlot& Origin, FInventorySlot& Destination, AItem* Item)
{
	AItem* Temp = nullptr;

	// If Destination already has an item in the spot, store it into a temporary variable
	if (Destination.Item && Destination.Item->IsValidLowLevel())
	{
		Temp = Destination.Item;
	}

	// Assign the item pointer to the new item for the destination inventory slot
	Destination.Item = Item;

	// Assign the item pointer to the temp for the origin inventory slot
	Origin.Item = Temp;

	return true;
}

bool AInventoryManager::AddItemToInventory(AItem* Item)
{
	bool bAdded = false;

	if (IsInventoryFull())
	{
		return false;
	}

	if (Inventory.Rows.Num() > 0)
	{
		int32 InvRowCount = Inventory.Rows.Num();
		int32 InvColumnCount = Inventory.Rows[0].Columns.Num();
		// Count how many filled inventory slots there are
		for (int32 r = 0; r < InvRowCount; r++)
		{
			for (int32 c = 0; c < InvColumnCount; c++)
			{
				if (!Inventory.Rows[r].Columns[c].Item || !Inventory.Rows[r].Columns[c].Item->IsValidLowLevel())
				{
					Inventory.Rows[r].Columns[c].Item = Item;
					bAdded = true;
					break;
				}
			}

			if (bAdded)
			{
				break;
			}
		}
	}

	return bAdded;
}

AInventoryManager* AInventoryManager::CreateDroppedInventory(int32 ColumnCount)
{
	AInventoryManager* Inv;

	FActorSpawnParameters SpawnParams;
	SpawnParams.bNoCollisionFail = true;

	Inv = GetWorld()->SpawnActor<AInventoryManager>(AInventoryManager::StaticClass(), SpawnParams);

	int32 InventorySize = GetInventorySize() + Hotbar.Num() + Equipment.Num();

	// Calculate number of rows needed to make sure it can hold all the items if all slots are full
	int32 RowCount = FMath::Ceil(float(InventorySize) / float(ColumnCount));

	Inv->CreateInventory(ColumnCount, RowCount);

	// Add Hotbar items to the new inventory
	for (int32 i = 0; i < Equipment.Num(); i++)
	{
		if (Equipment*.Item && Equipment*.Item->IsValidLowLevel())
		{
			Inv->AddItemToInventory(Equipment*.Item);

			// Clear the item pointer so it only exists in the new inventory
			Equipment*.Item = nullptr;
		}
	}

	// Add Hotbar items to the new inventory
	for (int32 i = 0; i < Hotbar.Num(); i++)
	{
		if (Hotbar*.Item && Hotbar*.Item->IsValidLowLevel())
		{
			Inv->AddItemToInventory(Hotbar*.Item);

			// Clear the item pointer so it only exists in the new inventory
			Hotbar*.Item = nullptr;
		}
	}

	if (Inventory.Rows.Num() > 0)
	{
		int32 InvRowCount = Inventory.Rows.Num();
		int32 InvColumnCount = Inventory.Rows[0].Columns.Num();
		// Add Inventory items to the new inventory
		for (int32 r = 0; r < InvRowCount; r++)
		{
			for (int32 c = 0; c < InvColumnCount; c++)
			{
				if (Inventory.Rows[r].Columns[c].Item && Inventory.Rows[r].Columns[c].Item->IsValidLowLevel())
				{
					Inv->AddItemToInventory(Inventory.Rows[r].Columns[c].Item);

					// Clear the item pointer so it only exists in the new inventory
					Inventory.Rows[r].Columns[c].Item = nullptr;
				}
			}
		}
	}

	return Inv;
}

bool AInventoryManager::IsInventoryFull()
{
	int32 InventorySize = GetInventorySize();
	int32 FilledSlotCount = 0;

	if (Inventory.Rows.Num() > 0)
	{
		int32 InvRowCount = Inventory.Rows.Num();
		int32 InvColumnCount = Inventory.Rows[0].Columns.Num();
		// Count how many filled inventory slots there are
		for (int32 r = 0; r < InvRowCount; r++)
		{
			for (int32 c = 0; c < InvColumnCount; c++)
			{
				if (Inventory.Rows[r].Columns[c].Item && Inventory.Rows[r].Columns[c].Item->IsValidLowLevel())
				{
					FilledSlotCount++;
				}
			}
		}
	}

	return (FilledSlotCount >= InventorySize);
}


If someone could help me figure out what is wrong in my code, that would be helpful.

If you need to see the code for how I am spawning the Inventory Manager, just let me know.

Thanks

Hello.

I had the same problem a while ago. You have to reference your items in the inventory manager via AddReferencedObjects otherwise they’ll get garbage collected.
Little bit more information in Marc’s answer here https://answers.unrealengine.com/questions/40794/how-to-implement-tickable-uobject.html

You must use AddReferencedObjects if you are using an array that don’t hold the UObject like TMAP but TArray should keep your UObject from garbage collector.

But for TARRAY to work, it must be declare as UPROPERTY.

This is the “work as design” rule but I found an issue in 4.2.1 that might be also in 4.3, where UObject that are inserted during the Class Construtor (of the parent) are garbagged, whereas if they are added in PostInitComponents, evertything is fine.

Good luck with this and keep us updated.

Thanks for your input and I will look into the AddReferencedObjects. What is weird is I haven not gotten any items implemented yet so its always an empty pointer.

Since I wasn’t sure if the pointer was a nullptr on initialize I decided to modify the FInventorySlot struct to



USTRUCT()
struct FInventorySlot
{
	GENERATED_USTRUCT_BODY()

	UPROPERTY()
	FName SlotName;

	UPROPERTY()
	AItem* Item;

	FInventorySlot()
	{
		SlotName = "";
		Item = nullptr;
	}

	FInventorySlot(FName Name)
	{
		SlotName = Name;
		Item = nullptr;
	}
};

After initializing the pointers to be nullptr I am not getting any errors and can run the game much longer compared to if I remove the initialization of the item pointer. I am not sure this is the correct way to go about it as I need to learn how to use AddReferencedObjects as I know I will need that either now or later down the road.

If anyone knows a tutorial or good examples on how to use AddReferencedObjects that would be helpful.

Thanks again.

Your pointer was pointing at some garbage since you didn’t set it to nullptr or initialize it. So it were crashing since GC was not able to access it. I’m not sure if GC would ever crash now.

As Marc suggested, InteractiveFoliage actor implements AddReferencedObjects and it’s quite clear how to override on your own, but here’s snippet from my function used in project.


void APlayerCharacter::AddReferencedObjects(UObject* InThis, FReferenceCollector& Collector)
{
	APlayerCharacter* This = CastChecked<APlayerCharacter>(InThis);

	Collector.AddReferencedObject(This->Inventory);

	for(TMap<FString, UCharacterAttribute*>::TIterator It(This->Attributes); It; ++It)
	{
		Collector.AddReferencedObject(It->Value);
	}

	Super::AddReferencedObjects(InThis, Collector);
}

It adds inventory to referenced objects and iterates over character attributes and adds them too.

P.S. Just noticed i could rewrite for-loop, thanks, would not check this function any time soon if not the question :smiley:

Thanks BiggestSmile,

I saw how to go about implementing the function but wasn’t sure if it was needed in the Character or Inventory class. With the code you provided it has helped me out and now I just need to implement it.

Thanks again for everyone’s help.

I actually think your inventory should override this function, since the example i provided references attributes which are within character class.

Of course it’s possible to reference items from inventory in your character class, but logically it would be better to keep it inside inventory class. :slight_smile:

Yea, I was thinking the same thing right as I sent the reply. I noticed your inventory was inside your character class where mine is a separate actor. It usually is a good idea to keep it inside the same class as it would get very complicated once I start adding in actors with their own inventory manager and having it done in the character and other actor classes would just be redundant when it can all be done in the inventory actor class. I just need to figure out how to store a reference to the active slot on the hotbar for my game :stuck_out_tongue: Not sure if you can have a null reference as I can’t do a pointer that I know of to the structs. One hurdle down and many more to go :smiley:

Thanks again.