How would you pass a class type for implementing a spawning factory for object pooling?

The code below works for a very simple implementation of object pooling, but it’s limited to spawning the type statically defined at compile time.

The problems is that I’m having an impossible time figuring out how to make the object pool more flexible/generic. It seems logical to be able to pass the type of any arbitrary subclass of AActor to a function and have it be able to create a container of the arbitrary subclass type, AND additionally spawn the AActors of the same arbitrary subclass type.

One work around would be to create a simple AActor container and cast the objects to their correct type at runtime, but it seems counterintuitive to be casting each actor to it’s appropriate type at runtime when I know exactly what the type will be at compile time; my gut is telling me that this is a problem best solved at compile time.

Another workaround is to create a series of overloaded functions that take a different “dummy pointer” of the subclass type I want spawned and then return with a container of the same subclass type. My gut is again telling me that this is a very error prone and bloated/redundant/brute-forced approach to solving this problem and that there has to be a more elegant solution.

I’m new to UE4 and C++ and I understand that C++ is statically typed, but would this be an application for virtual functions since MyPoolingManager won’t know the type at compile time, but me the programmer knows exactly what it will expect, or am I totally off base with that idea/concept?

Any help is greatly appreciated and thanks in advance!

This is mostly boilerplate code, so stripped out all but important bits, and MyActor is just an empty/generic class.

MyCharacter.h

#pragma once
#include "GameFramework/Character.h"
#include "MyPoolingManager.h"
#include "MyCharacter.generated.h"

UCLASS()
class TESTPROJECT_API AMyCharacter : public ACharacter
{
	GENERATED_BODY()
public:
	AMyCharacter();

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Object Pool")
		AMyPoolingManager* MyActorPool;
//...
};

MyCharacter.cpp

#include "TestProject.h"
#include "MyCharacter.h"
//...
void AMyCharacter::BeginPlay()
{
	Super::BeginPlay();
	
	MyActorPool = ()->SpawnActor<AMyPoolingManager>(AMyPoolingManager::StaticClass());
	MyActorPool->CreatePool(200);
}
//...

MyPoolingManager.h

#pragma once
#include "GameFramework/Actor.h"
#include "MyPoolingManager.generated.h"

USTRUCT(Blueprintable)
struct FSimplePoolStruct
{
	GENERATED_USTRUCT_BODY()
public:
	UPROPERTY(EditAnywhere, Category = "Object Pool")
		TArray<AActor*> PoolArray;

	UPROPERTY(EditAnywhere, Category = "Object Pool")
		int32 Next; // Hold the index of the next available actor

	FSimplePoolStruct() {
		PoolArray.Empty();
		Next = 0;
	}
};

UCLASS()
class TESTPROJECT_API AMyPoolingManager : public AActor
{
	GENERATED_BODY()
	
public:	
	AMyPoolingManager();

	//...

	UFUNCTION()
		void CreatePool(int32 PoolSize);
	
	UFUNCTION()
		AActor* GetActor();

	UPROPERTY(EditAnywhere, Category = "Object Pool")
		FSimplePoolStruct MyPool;
};

MyPoolingManager.cpp

#include "TestProject.h"
#include "MyActor.h"
#include "MyPoolingManager.h"

//...
void AMyPoolingManager::CreatePool(int32 PoolSize)
{
	UWorld* const World = ();
	if (World) {
		MyPool.PoolArray.Reserve(20); // Do the memory allocation up front to avoid multiple allocations while iterating through the loop
		for (int32 i = 0; i < 20; i++){
			MyActor* NewActor = World->SpawnActor<MyActor>(MyActor::StaticClass());
			MyPool.PoolArray.Add(NewActor);
		}
	}
}

AActor* AMyPoolingManager::GetActor(){
	return MyPool.PoolArray[MyPool.Next];
	MyPool.Next++;
}

So you want something like SpawnActor is doing? It’s called templates, but keep in mind templates are not supported by UE4 reflection system

I don’t know what you mean when you say “like SpawnActor is doing.”, because SpawnActor isn’t doing what I need? I’m confused…?

It’s really hard to find any sort of information on pooling, let alone specific to UE4. Outside of the UE4 framework object pooling seems like a great idea and “the right thing to do”, but when you throw in the complexities of how everything interacts within the framework I can begin to see your point.

Yes this was a very simplified version of code for AnswerHub purposes (I even forgot to zero out next when it reached the upperbound on the array as you pointed out), but I thank you for your thorough reply and you’ve given me a lot to think about.

Thanks!

Could you please elaborate a little on what you meant when you said the following,

If other objects in your game world
hold references to the pooled objects,
you are increasing the strain on the
garbage collector.

Advice time! Let me know if you have more questions or if you need help with some specific problems.

1 - Virtual functions are unrelated to what you are trying to do. They are a mechanism for dispatching function calls polymorphically at runtime.

2 - Your pool code will crash once GetActor() has been called more times than the size of the pool.

3 - Your pooling code doesn’t accomplish anything useful. Spawning actors up-front without cause does not gain you anything in Unreal Engine 4. Even if this were because you simplified the definition for the purposes of asking on AnswerHub, you must understand that “pooling” does not gain you anything unless there is a specific reason to do so, and specific ways in which your Actors or UObjects can be restricted and detected to have been disposed of in the game world without having been destroyed. It must also be the case that simply destroying the object and recreating it has significantly higher overhead than performing the state reset on the object. If other objects in your game world hold references to the pooled objects, you are increasing the strain on the garbage collector. If your game supports saving, you must either save all of the pooled objects (wasteful, slow) or correctly destroy and rebuild them during serialization/deserializations and initialization (increases your code size and complexity, also potentially slow). If your pooled actors have ticking functions, you must correctly enable and disable ticking when they become needed and not needed. If they contain components that cause effects on the world, you must deregister and reregister the components at the correct time. At this point, it will not be much different than destroying old objects and creating new ones.

Pooling objects “because it’s the right thing to do” without thinking critically about it will likely result in various state and logic problems in your game code, in addition to increasing the startup time of your levels.

4 - What you are describing with restricting the types to the correct type at compile time is called parametric typing. C++ supports this through the template metaprogramming system. However, templated C++ code cannot be used as UPROPERTY-declared member fields in Unreal Engine unless you manually add the supporting code for it to the Unreal Header Tool, and have it detect and generate the correct code. This is not something that is done when programming a game. Therefore, your templated struct or class would not be able to hold GC-aware references to your Actors (or other game objects). You would have to use weak references (via the TWeakObjectPtr<> template class). However, this would not work, because the pool may be the only reference holder to your UObjects (until something tries to get an object from the pool), and since they would be weak references, your pooled objects would be collected by the GC system.

5 - It’s possible to pass the class to spawn dynamically and without templates using a UClass pointer. However, you cannot statically parameterize your types using a UClass pointer, because it is a value in C++, not a type.

In summary: you would need to pass a UClass pointer to your pooling manager to tell it what to spawn. It would hold generic UObject pointers in an array, and then yield them when requesters needed to fetch objects. You would need to cast to get the appropriate static type in C++. (It’s possible for you to store the references as basic UObject pointers and then use a templated function only to fetch and cast, however. But this is not really any more type-safe, because the requester could accidentally put in the wrong type to the templated function and the cast would then fail.)

But really, you probably don’t need to pool objects, unless you have already measured and determined that this is true. In which case, I could help you reduce the overhead of spawning and destroying your Actor classes.

Sure. Basically whenever you add a

UPROPERTY()
UObject* SomeObject;

or a

UPROPERTY()
TArray<UObject*> SomeObjects;

and also for each UObject that you have created, you are increasing the number of things that the garbage collector has to traverse whenever it performs a garbage collection.

So, if you have some pooling thing, and allocate 1000 objects up-front before they are used, or only have some fraction of them being used at any point in time, you still pay the full cost for the GC having to check them while your game is running.

Sure. Basically whenever you add a

UPROPERTY()
UObject* SomeObject;

or a

UPROPERTY()
TArray<UObject*> SomeObjects;

and also for each UObject that you have created, you are increasing the number of things that the garbage collector has to traverse whenever it performs a garbage collection.

So, if you have some pooling thing, and allocate 1000 objects up-front before they are used, or only have some fraction of them being used at any point in time, you still pay the full cost for the GC having to check them while your game is running.

What you’re describing is one of the main types of implementations of garbage collection. It’s named “generational garbage collection”. For many types of work, it has the highest average speed. However, it is probably the most complicated and difficult to implement, and also has a more unstable performance profile. Sometimes it will run very fast, but every once in a while it will have a large hitch. This isn’t so great for games.

I know UE4 does not have a generational garbage collector. I believe it uses mark-and-sweep, which has a slower average speed but is more predictable and simple to implement. Because C++ lets you manage memory yourself, if you need to deal with lots of things, you can handle allocations and whatnot yourself, which reduces the need to have a generational garbage collector. Generational garbage collectors are most useful in languages where you don’t have that option (C#, Java, etc.)

(By the way, one of the quirks of the UE4 AnswerHub is that any comment will remove the ‘answered’ status, so you have to re-mark it as answered after every comment. A bit annoying)

Ahhhhh… I see said the blind man. That makes perfect sense and is a very good point.

For my own edification, is there any way to optimize the GC by prioritizing certain objects over others for collection, literally putting them at the bottom of the stack possibly even skipping them if needed until a later less “busy” pass, or is an all or nothing process?