-
Notifications
You must be signed in to change notification settings - Fork 15
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Take From Pool now spreads out the creation of large pools of UObject…
…s and Actors over multiple frames to avoid any hitches.
- Loading branch information
Showing
19 changed files
with
1,237 additions
and
211 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
[/Script/PoolManager.PoolManagerSettings] | ||
SpawnObjectsPerFrame=5 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
; This section lists additional files which will be packaged along with your plugin. Paths should be listed relative to the root plugin directory, and | ||
; may include "...", "*", and "?" wildcards to match directories, files, and individual characters respectively. | ||
|
||
[FilterPlugin] | ||
/Config/BasePoolManager.ini |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
106 changes: 106 additions & 0 deletions
106
Source/PoolManager/Private/Factories/PoolFactory_Actor.cpp
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
// Copyright (c) Yevhenii Selivanov | ||
|
||
#include "Factories/PoolFactory_Actor.h" | ||
//--- | ||
#include "Engine/World.h" | ||
#include "GameFramework/Actor.h" | ||
//--- | ||
#include UE_INLINE_GENERATED_CPP_BY_NAME(PoolFactory_Actor) | ||
|
||
// It's almost farthest possible location where deactivated actors are placed | ||
#define VECTOR_HALF_WORLD_MAX FVector(HALF_WORLD_MAX - HALF_WORLD_MAX * THRESH_VECTOR_NORMALIZED) | ||
|
||
// Is overridden to handle Actors-inherited classes | ||
const UClass* UPoolFactory_Actor::GetObjectClass_Implementation() const | ||
{ | ||
return AActor::StaticClass(); | ||
} | ||
|
||
/********************************************************************************************* | ||
* Creation | ||
********************************************************************************************* */ | ||
|
||
// Is overridden to spawn actors using its engine's Spawn Actor method | ||
UObject* UPoolFactory_Actor::SpawnNow_Implementation(const FSpawnRequest& Request) | ||
{ | ||
// Super is not called to Spawn Actor instead of NewObject | ||
|
||
UWorld* World = GetWorld(); | ||
checkf(World, TEXT("ERROR: [%i] %s:\n'World' is null!"), __LINE__, *FString(__FUNCTION__)); | ||
|
||
const TSubclassOf<AActor> ClassToSpawn = const_cast<UClass*>(Request.Class.Get()); | ||
FActorSpawnParameters SpawnParameters; | ||
SpawnParameters.OverrideLevel = World->PersistentLevel; // Always keep new objects on Persistent level | ||
SpawnParameters.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn; | ||
SpawnParameters.bDeferConstruction = true; // Delay construction to add it to the pool first | ||
AActor* NewActor = World->SpawnActor(ClassToSpawn, &Request.Transform, SpawnParameters); | ||
checkf(NewActor, TEXT("ERROR: [%i] %s:\n'NewActor' was not spawned!"), __LINE__, *FString(__FUNCTION__)); | ||
|
||
if (Request.Callbacks.OnPreConstructed != nullptr) | ||
{ | ||
Request.Callbacks.OnPreConstructed(NewActor); | ||
} | ||
|
||
if (AActor* SpawnedActor = Cast<AActor>(NewActor)) | ||
{ | ||
// Call construction script since it was delayed before to add it to the pool first | ||
SpawnedActor->FinishSpawning(Request.Transform); | ||
} | ||
|
||
if (Request.Callbacks.OnPostSpawned != nullptr) | ||
{ | ||
Request.Callbacks.OnPostSpawned(NewActor); | ||
} | ||
|
||
return NewActor; | ||
} | ||
|
||
/********************************************************************************************* | ||
* Destruction | ||
********************************************************************************************* */ | ||
|
||
// Is overridden to destroy given actor using its engine's Destroy Actor method | ||
void UPoolFactory_Actor::Destroy_Implementation(UObject* Object) | ||
{ | ||
// Super is not called to Destroy Actor instead of ConditionalBeginDestroy | ||
|
||
AActor* Actor = CastChecked<AActor>(Object); | ||
checkf(IsValid(Actor), TEXT("ERROR: [%i] %s:\n'IsValid(Actor)' is null!"), __LINE__, *FString(__FUNCTION__)); | ||
Actor->Destroy(); | ||
} | ||
|
||
/********************************************************************************************* | ||
* Pool | ||
********************************************************************************************* */ | ||
|
||
// Is overridden to set transform to the actor before taking the object from its pool | ||
void UPoolFactory_Actor::OnTakeFromPool_Implementation(UObject* Object, const FTransform& Transform) | ||
{ | ||
Super::OnTakeFromPool_Implementation(Object, Transform); | ||
|
||
AActor* Actor = CastChecked<AActor>(Object); | ||
Actor->SetActorTransform(Transform); | ||
} | ||
|
||
// Is overridden to reset transform to the actor before returning the object to its pool | ||
void UPoolFactory_Actor::OnReturnToPool_Implementation(UObject* Object) | ||
{ | ||
Super::OnReturnToPool_Implementation(Object); | ||
|
||
// SetCollisionEnabled is not replicated, client collides with hidden actor, so move it far away | ||
AActor* Actor = CastChecked<AActor>(Object); | ||
Actor->SetActorLocation(VECTOR_HALF_WORLD_MAX); | ||
} | ||
|
||
// Is overridden to change visibility, collision, ticking, etc. according new state | ||
void UPoolFactory_Actor::OnChangedStateInPool_Implementation(EPoolObjectState NewState, UObject* InObject) | ||
{ | ||
Super::OnChangedStateInPool_Implementation(NewState, InObject); | ||
|
||
AActor* Actor = CastChecked<AActor>(InObject); | ||
const bool bActivate = NewState == EPoolObjectState::Active; | ||
|
||
Actor->SetActorHiddenInGame(!bActivate); | ||
Actor->SetActorEnableCollision(bActivate); | ||
Actor->SetActorTickEnabled(bActivate); | ||
} |
77 changes: 77 additions & 0 deletions
77
Source/PoolManager/Private/Factories/PoolFactory_UObject.cpp
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
// Copyright (c) Yevhenii Selivanov | ||
|
||
#include "Factories/PoolFactory_UObject.h" | ||
//--- | ||
#include "Data/PoolManagerSettings.h" | ||
//--- | ||
#include UE_INLINE_GENERATED_CPP_BY_NAME(PoolFactory_UObject) | ||
|
||
// Method to queue object spawn requests | ||
void UPoolFactory_UObject::RequestSpawn_Implementation(const FSpawnRequest& Request) | ||
{ | ||
// Add request to queue | ||
SpawnQueue.Enqueue(Request); | ||
|
||
// If this is the first object in the queue, schedule the OnNextTickProcessSpawn to be called on the next frame | ||
// Creating UObjects on separate threads is not thread-safe and leads to problems with garbage collection, | ||
// so we will create them on the game thread, but defer to next frame to avoid hitches | ||
if (++SpawnQueueSize == 1) | ||
{ | ||
const UWorld* World = GetWorld(); | ||
checkf(World, TEXT("ERROR: [%i] %s:\n'World' is null!"), __LINE__, *FString(__FUNCTION__)); | ||
|
||
World->GetTimerManager().SetTimerForNextTick(this, &ThisClass::OnNextTickProcessSpawn); | ||
} | ||
} | ||
|
||
// Method to immediately spawn requested object | ||
UObject* UPoolFactory_UObject::SpawnNow_Implementation(const FSpawnRequest& Request) | ||
{ | ||
UObject* CreatedObject = NewObject<UObject>(GetOuter(), Request.Class); | ||
|
||
if (Request.Callbacks.OnPreConstructed != nullptr) | ||
{ | ||
Request.Callbacks.OnPreConstructed(CreatedObject); | ||
} | ||
|
||
if (Request.Callbacks.OnPostSpawned != nullptr) | ||
{ | ||
Request.Callbacks.OnPostSpawned(CreatedObject); | ||
} | ||
|
||
return CreatedObject; | ||
} | ||
|
||
// Is called on next frame to process a chunk of the spawn queue | ||
void UPoolFactory_UObject::OnNextTickProcessSpawn_Implementation() | ||
{ | ||
int32 ObjectsPerFrame = UPoolManagerSettings::Get().GetSpawnObjectsPerFrame(); | ||
if (!ensureMsgf(ObjectsPerFrame >= 1, TEXT("ASSERT: [%i] %s:\n'ObjectsPerFrame' is less than 1, set the config!"), __LINE__, *FString(__FUNCTION__))) | ||
{ | ||
ObjectsPerFrame = 1; | ||
} | ||
|
||
for (int32 Index = 0; Index < FMath::Min(ObjectsPerFrame, SpawnQueueSize); ++Index) | ||
{ | ||
FSpawnRequest OutRequest; | ||
SpawnQueue.Dequeue(OutRequest); | ||
SpawnNow(OutRequest); | ||
--SpawnQueueSize; | ||
} | ||
|
||
// If there are more actors to spawn, schedule this function to be called again on the next frame | ||
// Is deferred to next frame instead of doing it on other threads since spawning actors is not thread-safe operation | ||
if (!SpawnQueue.IsEmpty()) | ||
{ | ||
const UWorld* World = GetWorld(); | ||
checkf(World, TEXT("ERROR: [%i] %s:\n'World' is null!"), __LINE__, *FString(__FUNCTION__)); | ||
World->GetTimerManager().SetTimerForNextTick(this, &ThisClass::OnNextTickProcessSpawn); | ||
} | ||
} | ||
|
||
// Method to destroy given object | ||
void UPoolFactory_UObject::Destroy_Implementation(UObject* Object) | ||
{ | ||
checkf(IsValid(Object), TEXT("ERROR: [%i] %s:\n'IsValid(Object)' is not valid!"), __LINE__, *FString(__FUNCTION__)); | ||
Object->ConditionalBeginDestroy(); | ||
} |
Oops, something went wrong.