Dynamic Memory Allocation
Dynamic Memory Management and the Garbage Collection System.
Overview
Keep in mind that this tutorial in its present state reflects only my understanding and is not an Epic tutorial, you should examine the Unreal Engine Source to get a more complete understanding of Dynamic Memory Management and the Garbage Collection System.
Dynamic Load Object
Here are my static library functions for saving an object path and then loading an object dynamically from a path, I have a templated version of my dynamic load object function for your entertainment, to test this you could remove the word static and put it in any .h file of your choosing, like player controller.
Get Object's Path
// Get Path
static FORCEINLINE FName GetObjPath(const UObject* Obj)
{
if(!Obj)
{
return NAME_None;
}
//~
FStringAssetReference ThePath = FStringAssetReference(Obj);
if(!ThePath.IsValid()) return NAME_None;
// The Class FString Name For This Object
FString Str = Obj->GetClass()->GetDescription();
Str += "'";
Str += ThePath.ToString();
Str += "'";
return FName(*Str);
}
Why FName?
Because in my In-Game editor I only save the asset references to binary file, and use Dynamic Load Object during the loading of a level. So I do it for Save File size-reduction reasons.
FName is half the size of FString, 8 compared to 16 bytes.
Templates
Dynamic Load Object Function
//TEMPLATE Load Obj From Path
template
static FORCEINLINE ObjClass* LoadObjFromPath(const FName& Path)
{
if(Path == NAME_None) return NULL;
//~
return Cast(StaticLoadObject( ObjClass::StaticClass(), NULL, *Path.ToString()));
}
Usage
// Load PS From Path
static FORCEINLINE UParticleSystem* LoadPSFromPath(const FName& Path)
{
if(Path == NAME_None) return NULL;
//~
return LoadObjFromPath(Path);
}
// Load Material From Path
static FORCEINLINE UMaterialInterface* LoadMatFromPath(const FName& Path)
{
if(Path == NAME_None) return NULL;
//~
return LoadObjFromPath(Path);
}
// Load Static Mesh From Path
static FORCEINLINE UStaticMesh* LoadMeshFromPath(const FName& Path)
{
if(Path == NAME_None) return NULL;
//~
return LoadObjFromPath(Path);
}
()
Dynamic UObject Allocation
// In any class
UMyObjectClass* DynamicObj = NewObject(this);
this = Outer, if you are looking through the UE4 Source.
Preventing Garbage Collection
UE4 Garbage Collection only counts references to UObjects that are UPROPERTY()
To ensure that your spawned UObjects or objects created with NewObject are not Garbage Collected prematurely, you must have at least 1 reference to the UObject that is UPROPERTY()
.H
UPROPERTY()
UMyObjectClass* MyGCProtectedObj;
.CPP
MyGCProtectedObj = NewObject(this);
Implications of Not Using UPROPERTY()
If you do not use UPROPERTY() you can never rely on your dynamic UObject staying in existence!
Make sure you pay close attention to this if you are spawning UObjects or using NewObject())!
Using UObject Flag to Prevent Garbage Collection
You can also prevent an object from being garbage collected by setting the RF_RootSet flag.
YourObjectInstance->AddToRoot();
Counting UPROPERTY() References To Any Object
I have created a wiki that shows you how you can find out exactly who is referring to your UObject/AActor at any time!
Garbage Collection ~ Count References To Any Object
Destroying / Deallocating
Destroying Objects
You can destroy objects created during runtime using:
if(!MyObject) return;
if(!MyObject->IsValidLowLevel()) return;
MyObject->ConditionalBeginDestroy(); // Instantly clears UObject out of memory
MyObject = nullptr;
Destroying Actors
You can destroy AActor extending classes using
if(!TheCharacter) return;
if(!TheCharacter->IsValidLowLevel()) return;
TheCharacter->Destroy();
TheCharacter->ConditionalBeginDestroy(); // Essential extra step, in all my testing
IsValidLowLevel()
You must always check if a UObject or AActor is valid before deferencing the pointer to it!
If you are wanting to use Dynamic Allocation of UObjects,
you really should also use IsValidLowLevel()
// Get the Name of my Dynamic Object
if(!MyGCProtectedObj) return;
if(!MyGCProtectedObj->IsValidLowLevel()) return;
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// It is safe to dereference now :)
ClientMessage(MyGCProtectedObj->GetName());
IsValidLowLevel() checks the actual validity of the created UObject and can prevent crashes where a simple pointer check wouldn't, because the pointer is valid but is pointing to an incomplete/partially constructed UObject.
Weak Pointers
Quoting Epic Dev Matt
If you know that the lifetime of an object is managed elsewhere and you just want to observe it and not contribute any references to it, you can hold onto a TWeakObjectPtr to that object which will safely be nulled out when the object is destroyed.
TWeakObjectPtr MeshThatCouldBeNull;
Dynamic Memory Management
C++ operator new and delete
Although you can use Malloc and free as I explain below, I personally recommend that you use c++ operator new and delete!
This is because C++ operator new properly initializes the vtable (virtual function table) for any virtual functions in your data type, and also calls the constructor for your data type!
FYourDataType* NewDataPtr = new FYourDataType();
Every New Must Have a Delete
Please note you absolutely must pair every use of new with a delete to avoid memory leaks, as you are now doing your own memory management!
delete NewDataPtr;
UnrealMemory.h / FMemory::
Use the Memory.h functions!
Do not use c++ level memory functions!
UnrealMemory.h contains UE4 C++ versions of C++ memory management functions
that are maintained by Epic as the engine evolves.
Templated UE4 C++ Malloc Function
Here is my very own Templated Malloc function which I used to make my In-game Editor Undo system!
Video of my In-game Editor Undo system!
//The purpose of this template is to ensure no typos when malloc-ing
// types that are related and will pass static cast, but if there's a typo could be allocating
// insufficient amount of space
//the name is VStruct because I was mallocing USTRUCTS
template
FORCEINLINE DataType* VStructMalloc()
{
return static_cast(FMemory::Malloc(sizeof(DataType)));
}
Example Usage
FVictoryUndoDataCreateWall* NewUndo = VStructMalloc();
if(!NewUndo) return;
UE4 C++ Free
FMemory::Free(NewUndo);
If You Use Malloc You Must Use Free
Every Malloc must be paired with a Free when you are doing your own Dynamic Memory management, or you will have a Memory Leak.
FMemory::Malloc
FMemory::Free
UE4 new operator and running out of memory
From AnswerHub answer by Jamie Dale:
UObject and UStruct types overload operator new via one of the nested macros within GENERATED_UCLASS_BODY and GENERATED_USTRUCT_BODY. Slate widgets also override this operator, as do modules via REPLACEMENT_OPERATOR_NEW_AND_DELETE.
The module level replacement seems to catch all the allocations made within a module, even if you're not allocating a UObject, UStruct, or Slate widget.
Ultimately they call through to FMemory::Malloc, which will forward it onto whichever allocator is active (eg, FMallocTBB). If one of these allocators fails to perform an allocation, they will call an implementation specific OutOfMemory function to log a fatal error.
I tried allocating 0x7fffffffffffffff bytes. With a debugger attached, it broke into the debugger on the failed allocation; without a debugger attached, the application just quit.
How to Disable GC Verify To Avoid Hitches
If you want to disable GC in release builds to avoid hitches (if you are experiencing them)
In the commandline when you run the game:
-NoVerifyGC
AnswerHub Explanation by Epic Dev Robert
Summary
Now you know how to dynamically spawn / create UObjects and also prevent them from getting Garbage Collected!
You also know how to prevent your game from crashing constantly.
It's called IsValidLowLevel() !!!
You also have a very brief intro into Dynamic Memory Management which I plan to let you explore further by studying UnrealMemory.h directly.