Hi,
I am not able to clear the RAM used by a background task (FRunnable) and a TArray of my own structs. If I don’t use the background task it clears the memory.
See the code below:
MyStructs.h
#pragma once
struct FMyDataStruct
{
TArray<float> MyArray;
FMyDataStruct() {}
~FMyDataStruct()
{
MyArray.Empty();
}
};
struct FMyTaskResult
{
TArray<FMyDataStruct> MyItems;
FMyTaskResult() {}
~FMyTaskResult()
{
MyItems.Empty();
}
};
MyTask.h (A task class that loops the NumberOfItems and fills the main actors MyDataArray)
#pragma once
#include "Async/AsyncWork.h"
#include "Runtime/Core/Public/Async/Async.h"
#include "MyStructs.h"
#include "MyMainActor.h"
class FMyTask : public FRunnable
{
public:
FMyTask(int32 NumberOfItems, TArray<FMyDataStruct>& MyDataArrayRef, FMyTaskCallback& CompleteCallback)
: Thread(nullptr)
{
bKillThread = false;
TotalItems = NumberOfItems;
TaskCompleteCallback = CompleteCallback;
DataArrayRef = &MyDataArrayRef;
}
~FMyTask()
{
if (Thread)
{
delete Thread;
Thread = nullptr;
}
};
bool Init()
{
DataArrayRef->Empty();
return true;
}
uint32 Run()
{
for (int32 Index = 0; Index < TotalItems; Index++)
{
if (bKillThread) return 0;
FMyDataStruct MyDataStruct;
MyDataStruct.MyArray.Add(0);
DataArrayRef->Add(MyDataStruct);
}
return 0;
}
void Exit() override
{
AsyncTask(ENamedThreads::GameThread, [this]()
{
if (this != nullptr)
{
if (TaskCompleteCallback.IsBound())
{
TaskCompleteCallback.Execute(&Result);
}
}
});
}
void Stop() override
{
bKillThread = true;
}
//Start the thread
bool Startup()
{
if (Thread != nullptr)
{
return false;
}
if (FPlatformProcess::SupportsMultithreading())
{
//Create the new thread
Thread = FRunnableThread::Create(this, TEXT("FMyTask"), 0, TPri_AboveNormal);
}
return (Thread != nullptr);
}
//Shuts down the thread
void Shutdown()
{
Stop();
if (Thread)
{
Thread->WaitForCompletion();
}
}
private:
FRunnableThread* Thread; //Thread to run the worker on
FThreadSafeBool bKillThread;
int32 TotalItems;
TArray<FMyDataStruct>* DataArrayRef;
FMyTaskResult Result;
FMyTaskCallback TaskCompleteCallback;
};
MyMainActor.h
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "TimerManager.h"
#include "MyStructs.h"
DECLARE_DELEGATE_OneParam(FMyTaskCallback, FMyTaskResult*);
#include "MyTask.h"
#include "MyMainActor.generated.h"
UCLASS()
class TESTPROJECT1_API AMyMainActor : public AActor
{
GENERATED_BODY()
public:
AMyMainActor();
~AMyMainActor();
protected:
virtual void BeginPlay() override;
virtual void Destroyed() override;
public:
bool IsEditorOnly() const override;
bool ShouldTickIfViewportsOnly() const override;
void OnConstruction(const FTransform& Transform) override;
virtual void Tick(float DeltaTime) override;
void MyFunc();
void OnTimerTriggered();
void MyTaskCallback(FMyTaskResult* Result);
TArray<FMyDataStruct> MyMainArray;
private:
FTimerHandle MyTimer;
bool bBuild = false;
FMyTask* MyTaskWorker;
FMyTaskCallback MainTaskCallback;
};
MyMainActor.cpp
#pragma once
#include "MyMainActor.h"
AMyMainActor::AMyMainActor()
{
PrimaryActorTick.bCanEverTick = true;
PrimaryActorTick.TickInterval = 0.1f;
#if WITH_EDITOR
bRunConstructionScriptOnDrag = false;
#endif
}
AMyMainActor::~AMyMainActor()
{
//Class Destructor
}
void AMyMainActor::Destroyed()
{
MyMainArray.Empty();
}
void AMyMainActor::BeginPlay()
{
Super::BeginPlay();
}
void AMyMainActor::OnConstruction(const FTransform& Transform)
{
if (!HasAnyFlags(RF_ClassDefaultObject))
{
bBuild = true;
}
}
bool AMyMainActor::IsEditorOnly() const
{
return true;
}
bool AMyMainActor::ShouldTickIfViewportsOnly() const
{
return true;
}
void AMyMainActor::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
if (bBuild)
{
bBuild = false;
MyFunc();
}
}
void AMyMainActor::MyFunc()
{
FTimerManager& TimerManager = GetWorldTimerManager();
if (TimerManager.IsTimerActive(MyTimer))
TimerManager.ClearTimer(MyTimer);
TimerManager.SetTimer(MyTimer, this, &AMyMainActor::OnTimerTriggered, 5.f, true);
}
void AMyMainActor::OnTimerTriggered()
{
FTimerManager& TimerManager = GetWorldTimerManager();
TimerManager.ClearTimer(MyTimer);
//CODE 1: This part DOES empty the RAM when run
/*for (int32 Index = 0; Index < 100000000; Index++)
{
FMyDataStruct MyDataStruct;
MyDataStruct.MyArray.Add(0);
MyMainArray.Add(MyDataStruct);
}*/
//CODE 2: This does NOT empty the RAM when run
if (!MainTaskCallback.IsBound())
{
MainTaskCallback = FMyTaskCallback::CreateUObject(this, &AMyMainActor::MyTaskCallback);
}
MyTaskWorker = new FMyTask(100000000, MyMainArray, MainTaskCallback);
MyTaskWorker->Startup();
}
void AMyMainActor::MyTaskCallback(FMyTaskResult* Result)
{
MyMainArray = TArray<FMyDataStruct>(Result->MyItems);
Result->MyItems.Empty();
if (MyTaskWorker)
{
MyTaskWorker->Shutdown();
delete MyTaskWorker;
MyTaskWorker = nullptr;
}
}
I have included two code sections in the OnTimerTriggered(). They demonstrate that the code 1 does work and code 2 does not work.
When you delete the actor inside the editor window/level editor code 1 is emptying the RAM correctly but blocking the editor when its fills (intended) and code 2 does not empty the ram but running in the background.
What I want it to be able to run a background task that produces an array/works on an array then that array be completely emptied in RAM when that actor is deleted from the editor window.
This is an editor only actor. Not for running in a game.
Note: I know the Result is not returning anything useful but I did test entirely working on a TArray inside the task only with its own version then posting the “result” back to the main thread using the AsyncTask run on main thread code but that didn’t remove the RAM when the TArray was emptied either.
Thank you.