TArray memory not freed in editor only actor

Hi all,

I have created an editor only actor that can be placed into the editor at design time. This actor holds a TArray of floats that is not marked with UPROPERTY. I add some test data to the array in the OnConstruction() function which is added successfully. The RAM usage goes up at this point because of all that data. If I then delete the actor from the level/editor then the memory is never freed.

What’s strange is that if I try to empty the array inside the actors destructor it says the array is already “empty” but yet the memory is still being used as reported by Windows task manager. This memory never reduces even after some time.

Anyone know why?

Thanks.

Hi! TArray has some tricky memory management inside, you can try call Empty() method to free memory used by TArray allocator…

I have tried emptying the TArray in the destructor of the actor but when debugging in Visual Studio it states that the array is empty but the memory amount being used in Windows task manager still reports a significant amount of usage.

The memory before adding the actor in the editor to a level is around 600mb and after adding it, it is 1400mb. If I then delete the actor from the level the RAM stays at about 1400mb.

Here is the code that is causing the problem:

MyMainActor.h

struct FMyDataStruct
{
	TArray<float> MyArray;

	FMyDataStruct()
	{
		//Constructor
	}

	~FMyDataStruct()
	{
		//Destructor
		MyArray.Empty();
	}
};

UCLASS()
class TESTPROJECT_API AMyMainActor : public AActor
{
	GENERATED_BODY()

public:
	AMyMainActor();
	~AMyMainActor();

protected:
	virtual void BeginPlay() override;
	void OnConstruction(const FTransform& Transform) override;

public:
	virtual void Tick(float DeltaTime) override;

	void MyFunc();

private:
	TArray<FMyDataStruct> MyStructArray;
};

MyMainActor.cpp

AMyMainActor::AMyMainActor()
{
	PrimaryActorTick.bCanEverTick = true;
}

AMyMainActor::~AMyMainActor()
{
	MyStructArray.Empty();
}

void AMyMainActor::BeginPlay()
{
	Super::BeginPlay();
}

void AMyMainActor::OnConstruction(const FTransform& Transform)
{
	if (!IsTemplate(RF_Transient))
	{
		MyFunc();
	}
}

void AMyMainActor::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);
}

void AMyMainActor::MyFunc()
{
	for (int32 Counter1 = 0; Counter1 < 10; Counter1++)
	{
		FMyDataStruct MyStruct;

		for (int32 Counter2 = 0; Counter2 < 10000000; Counter2++)
		{
			MyStruct.MyArray.Add(1.f);
		}

		MyStructArray.Add(MyStruct);
	}
}

Please, can anyone help me understand this.

Thank you.

Also…

I have tried:

  • Using USTRUCT on the FMyDataStruct
  • Using UPROPERTY on the MyStructArray
  • Using UFUNCTION on the MyFunc

Hi! I try the snippet code you are using and have some ideas what is there. The thing is that the engine use Actor config as the base(aka Template) to create level. So Actor is constructed by this base(aka Template) for this Actor class. The items that were added in OnConstruction method are related to this base(aka template) for you actor. if you try to add some elements at runtime and end the game - you’ll see that they will be cleaned up, because they are not part of base.

Hi Kehel,
Thanks for looking at it.

I am struggling to understand the concept of ‘Template’. I added a check in the OnConstruction for

IsTemplate(RF_Transient) == false

but the MyStructArray is empty in the actors destructor even though the RAM is still in use.

If I change the code to

IsTemplate(RF_Transient) == true

then the MyStructArray is full of data and can be emptied in the actors destructor. This then empties the RAM usage.

What I am not understanding is: I thought IsTemplate(RF_Transient) == true means it IS the base/template version of the actor in which case I don’t want it running MyFunc in that case? I’m unsure. But if I set it to check for False then MyFunc is run in some other version of the actor in which case the MyStructArray can no longer be emptied in the actors destructor and therefore the RAM is never cleared out.

I am confused about why there are different versions of the actor and why the TArray is only available in certain ones… :confused:

If anyone can help me understand or direct me to a page that describes it then I would appreciate it.

Thank you.

Ok, so I have found this page that talks about the Class Default Object (CDO) in Unreal.

Unreal - Class Default Object (CDO)

It seems like Kehel18 said, when an actor is dragged into the level the CDO is created and its OnConstruction function is run. Then the instance of the actor is created and its OnConstruction function is run. To test for which version is which inside the OnConstruction function use:

if(HasAnyFlags(RF_ClassDefaultObject) == false)
{
//Do Something
}

If I run the MyFunc function inside the IF statement it adds the data to MyStructArray.

What I cannot figure out is the deletion/destruction side of the actor.

If you delete the actor in the level editor then the class/actors destructor is run immediately but the MyStructArray is empty for some reason.

If you then close the editor window it runs the destructor again (even though it no longer appears in the level editor) and this time the MyStructArray is populated with data and is therefore emptied and its RAM is released.

Do you have Discord account?

Hey Kehel, no I don’t have discord. :slight_smile:

I have found a couple of interesting pages to do with similar explainations of this issue.

Unreal Object Handling

Destructor Issue

Unreal CDO

I still struggling with getting the actor to clear out the MyStructArray when deleting the actor from the editor so that I see a RAM decrease.

I just make a simple example proj to look at outers and flags and so on

OnConstruction can be run several times by object. It is running when opening level or placed actor in level. While destructor is called when level is unloaded. A template object is fully determined by having outer with flags in param.

Hi Kehel,

Thanks for your help.

Using the HasAnyFlags() distinguishes between the ghost/CDO and instance of my actor and if I implement the Destroyed() event I have been able to see TArrays being emptied there correctly with a release of memory.

I could do with your opinion on a slightly different angle to this, TArrays not releasing their memory.

It’s to do with a TArray that exists in the main actor but is filled inside a separate FRunnable thread. If you pass the array by reference to the thread using “FMyTask MyTask = new FMyTask(MyMainArray, etc…)” it fills the array correctly but when the actor is deleted and the Destroyed() function is run, the MyMainArray array shows that data is in there and using Empty() shows that the array is then empty when debugging in visual studio but the RAM is not released in that scenario.

Do you have any ideas why that would be?

Thank you.

Hi! Can you write it in more details? When is this task executed? During play?

Hi Kehel,

Please see:

https://forums.unrealengine.com/development-discussion/c-gameplay-programming/1819171-memory-not-freed-using-frunnable-and-tarray

Do you have any ideas why the TArray is not being emptied in RAM when the actor is deleted using the code in the link?

By the way, TArray is not allright when used by several threads, I think that is the case. See Is TArray Thread-Safe? - C++ - Unreal Engine Forums

By the way, do you log actor pointers in Fill routine and empty routine? Are they same? And one more - your code is in Tick function. Is your default object Ticking? And is this a special case for something?

I added some UE_LOG to the code and the array is filled in the task loop then when the task exits it reports zero items in the array and there is nothing in the main array in the actor but yet 600mb of ram is still in use :confused:

It seems that the reference is not correct inside the task and therefore does not change the array passed to it, just its own version of it some how.

The ram shoots up from a base line in the editor of 570mb in the editor with no actor to 2800mb with the tasks loop running. Then when the loop is done is goes down to 1400mb and sits there forever, even after the actor is deleted.

Yes, the running in tick function is for a building/updating facility that the actor will do.

I also added a HasAnyTags(RF_CreateDefaultObject) if statement check to disable the Tick if that is true.

This TArray is only meant to be accessed by this thread only. Plus I also suffered this problem when I created/filled the array in the task and then passed the result back by value to the main thread :frowning:

​In the callback function that I was using that the thread calls when it is done was setting the MyMainArray TArray to be a copy of the Results items, which that results code was not even in use inside the thread.

After that was removed the array now does not suddenly change to zero items when the task runs its callback function.

So the TArray is emptied but still says that the ram is sitting at 1300mb when it should be 600mb when the actor is deleted.

I tracked down this extra 700mb to be the one float I was adding to the test struct array I was adding inside the loop that fills the array in the first place inside the thread.

I was under the impression that running the Empty() function on a TArray would invoke the destructor of the struct for each element in the main TArray which would in turn empty its own float array as the code shows.

That does not seem to be the case as the 700mb from that one float value multiplied up by the number of structs is still in memory and never released.

As of yet I don’t know why… :slight_smile:

By the way do you try to change in you FRunnable this code

FMyDataStruct MyDataStruct;
DataArrayRef->Add(MyDataStruct);

to this code

DataArrayRef->Emplace();

Something strange is about Add(). Methods AddZeroed and AddDefault are allright!