diff --git a/Binaries/Win64/UnrealEditor-PoolManager.dll b/Binaries/Win64/UnrealEditor-PoolManager.dll index ccf82e7..a6b78e5 100644 Binary files a/Binaries/Win64/UnrealEditor-PoolManager.dll and b/Binaries/Win64/UnrealEditor-PoolManager.dll differ diff --git a/Binaries/Win64/UnrealEditor-PoolManager.pdb b/Binaries/Win64/UnrealEditor-PoolManager.pdb index f394e08..ddefcf0 100644 Binary files a/Binaries/Win64/UnrealEditor-PoolManager.pdb and b/Binaries/Win64/UnrealEditor-PoolManager.pdb differ diff --git a/Binaries/Win64/UnrealEditor.modules b/Binaries/Win64/UnrealEditor.modules index fa7bc86..b4498a9 100644 --- a/Binaries/Win64/UnrealEditor.modules +++ b/Binaries/Win64/UnrealEditor.modules @@ -1,7 +1,8 @@ { - "BuildId": "25360045", + "BuildId": "27405482", "Modules": { - "PoolManager": "UnrealEditor-PoolManager.dll" + "PoolManager": "UnrealEditor-PoolManager.dll", + "PoolManagerEditor": "UnrealEditor-PoolManagerEditor.dll" } } \ No newline at end of file diff --git a/Config/BasePoolManager.ini b/Config/BasePoolManager.ini new file mode 100644 index 0000000..b0efec8 --- /dev/null +++ b/Config/BasePoolManager.ini @@ -0,0 +1,4 @@ +[/Script/PoolManager.PoolManagerSettings] +SpawnObjectsPerFrame=5 ++PoolFactories=/Script/PoolManager.PoolFactory_UObject ++PoolFactories=/Script/PoolManager.PoolFactory_Actor diff --git a/Config/FilterPlugin.ini b/Config/FilterPlugin.ini new file mode 100644 index 0000000..a796e29 --- /dev/null +++ b/Config/FilterPlugin.ini @@ -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 diff --git a/PoolManager.uplugin b/PoolManager.uplugin index cd4ee8d..2a4bdff 100644 --- a/PoolManager.uplugin +++ b/PoolManager.uplugin @@ -10,7 +10,7 @@ "DocsURL": "", "MarketplaceURL": "", "SupportURL": "mailto:janseliw@gmail.com", - "EngineVersion": "5.2.0", + "EngineVersion": "5.3.0", "EnabledByDefault": true, "CanContainContent": false, "IsBetaVersion": false, @@ -20,6 +20,11 @@ "Name": "PoolManager", "Type": "Runtime", "LoadingPhase": "EarliestPossible" + }, + { + "Name": "PoolManagerEditor", + "Type": "UncookedOnly", + "LoadingPhase": "EarliestPossible" } ] } \ No newline at end of file diff --git a/README.md b/README.md index 6c6a830..442a6a9 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Creating and destroying objects, like projectiles or explosions, can be slow and The Pool Manager alleviates these problems by maintaining a pool of objects. Instead of creating and destroying objects all the time, the Pool Manager keeps these objects for reuse. This strategy improves the smoothness of the game. -![PoolManager](https://github.com/JanSeliv/PoolManager/assets/20540872/b3df793b-059b-4bf1-a04f-d06289fad5b5) +![PoolManager](https://github.com/JanSeliv/PoolManager/assets/20540872/0af55b33-732c-435d-a5b3-2d7e36cdebf2) ## 📚 Documentation @@ -19,12 +19,21 @@ Check out our [Release](https://github.com/JanSeliv/PoolManager/releases) page f Also, explore this [game project repository](https://github.com/JanSeliv/Bomber) to view the Pool Manager in action. ## 📅 Changelog +#### 2023-11-25 +- Updated to **Unreal Engine 5.3**. +- Introduced **Factories** to handle differences in pools by object archetypes (e.g.: uobjects, actors, components, widgets etc.). +- **Take From Pool** now spreads out the creation of large pools of UObjects and Actors over multiple frames to avoid any hitches. + ![image](https://github.com/JanSeliv/PoolManager/assets/20540872/10bdf24f-d078-4dd8-96bf-de5d92421bc8) #### 2023-05-28 - 🎉 Initial public release on Unreal Engine 5.2 ## 📫 Feedback & Contribution -This is an open-source project and we encourage you to contribute. If you encounter any bugs or if you have any feature requests, please file an issue in the GitHub repository. +Feedback and contributions from the community are highly appreciated! + +If you'd like to contribute, please fork the project and create a pull request targeting the `develop` branch. + +If you've found a bug or have an idea for a new feature, please open a new issue on GitHub. Thank you! ## 📜 License diff --git a/Source/PoolManager/PoolManager.Build.cs b/Source/PoolManager/PoolManager.Build.cs index 64686ea..8e810ba 100644 --- a/Source/PoolManager/PoolManager.Build.cs +++ b/Source/PoolManager/PoolManager.Build.cs @@ -8,10 +8,12 @@ public PoolManager(ReadOnlyTargetRules Target) : base(Target) { PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; CppStandard = CppStandardVersion.Latest; + bEnableNonInlinedGenCppWarnings = true; PublicDependencyModuleNames.AddRange(new[] { "Core" + , "DeveloperSettings" // Created UPoolManagerSettings } ); diff --git a/Source/PoolManager/Private/Data/PoolManagerSettings.cpp b/Source/PoolManager/Private/Data/PoolManagerSettings.cpp new file mode 100644 index 0000000..eb6eee5 --- /dev/null +++ b/Source/PoolManager/Private/Data/PoolManagerSettings.cpp @@ -0,0 +1,22 @@ +// Copyright (c) Yevhenii Selivanov + +#include "Data/PoolManagerSettings.h" +//--- +#include UE_INLINE_GENERATED_CPP_BY_NAME(PoolManagerSettings) + +// Returns all Pool Factories that will be used by the Pool Manager +void UPoolManagerSettings::GetPoolFactories(TArray& OutBlueprintPoolFactories) const +{ + if (!OutBlueprintPoolFactories.IsEmpty()) + { + OutBlueprintPoolFactories.Empty(); + } + + for (const TSoftClassPtr& It : PoolFactories) + { + if (UClass* PoolFactoryClass = It.LoadSynchronous()) + { + OutBlueprintPoolFactories.Emplace(PoolFactoryClass); + } + } +} diff --git a/Source/PoolManager/Private/Factories/PoolFactory_Actor.cpp b/Source/PoolManager/Private/Factories/PoolFactory_Actor.cpp new file mode 100644 index 0000000..0c8c021 --- /dev/null +++ b/Source/PoolManager/Private/Factories/PoolFactory_Actor.cpp @@ -0,0 +1,114 @@ +// 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 ClassToSpawn = const_cast(Request.Class.Get()); + checkf(ClassToSpawn, TEXT("ERROR: [%i] %s:\n'ClassToSpawn' is null!"), __LINE__, *FString(__FUNCTION__)); + + 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 + SpawnParameters.bNoFail = true; // Do not fail if spawn fails + AActor* NewActor = World->SpawnActor(ClassToSpawn, &Request.Transform, SpawnParameters); + checkf(NewActor, TEXT("ERROR: [%i] %s:\n'NewActor' was not spawned!"), __LINE__, *FString(__FUNCTION__)); + + FPoolObjectData PoolObjectData; + PoolObjectData.bIsActive = true; + PoolObjectData.PoolObject = NewActor; + PoolObjectData.Handle = Request.Handle; + + if (Request.Callbacks.OnPreRegistered != nullptr) + { + Request.Callbacks.OnPreRegistered(PoolObjectData); + } + + if (AActor* SpawnedActor = Cast(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(PoolObjectData); + } + + 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(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(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(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(InObject); + const bool bActivate = NewState == EPoolObjectState::Active; + + Actor->SetActorHiddenInGame(!bActivate); + Actor->SetActorEnableCollision(bActivate); + Actor->SetActorTickEnabled(bActivate); +} diff --git a/Source/PoolManager/Private/Factories/PoolFactory_UObject.cpp b/Source/PoolManager/Private/Factories/PoolFactory_UObject.cpp new file mode 100644 index 0000000..d340d38 --- /dev/null +++ b/Source/PoolManager/Private/Factories/PoolFactory_UObject.cpp @@ -0,0 +1,123 @@ +// 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) +{ + if (!ensureMsgf(Request.IsValid(), TEXT("ASSERT: [%i] %s:\n'Request' is not valid and can't be processed!"), __LINE__, *FString(__FUNCTION__))) + { + return; + } + + // Add request to queue + SpawnQueueInternal.Emplace(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 (SpawnQueueInternal.Num() == 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(GetOuter(), Request.Class); + + FPoolObjectData PoolObjectData; + PoolObjectData.bIsActive = true; + PoolObjectData.PoolObject = CreatedObject; + PoolObjectData.Handle = Request.Handle; + + if (Request.Callbacks.OnPreRegistered != nullptr) + { + Request.Callbacks.OnPreRegistered(PoolObjectData); + } + + if (Request.Callbacks.OnPostSpawned != nullptr) + { + Request.Callbacks.OnPostSpawned(PoolObjectData); + } + + return CreatedObject; +} + +// Removes the first spawn request from the queue and returns it +bool UPoolFactory_UObject::DequeueSpawnRequest(FSpawnRequest& OutRequest) +{ + if (!SpawnQueueInternal.IsValidIndex(0)) + { + return false; + } + + // Copy and remove first request from the queue (without Swap to keep order) + OutRequest = SpawnQueueInternal[0]; + SpawnQueueInternal.RemoveAt(0); + + return OutRequest.IsValid(); +} + +// Alternative method to remove specific spawn request from the queue and returns it. +bool UPoolFactory_UObject::DequeueSpawnRequestByHandle(const FPoolObjectHandle& Handle, FSpawnRequest& OutRequest) +{ + const int32 Idx = SpawnQueueInternal.IndexOfByPredicate([&Handle](const FSpawnRequest& Request) + { + return Request.Handle == Handle; + }); + + if (!ensureMsgf(SpawnQueueInternal.IsValidIndex(Idx), TEXT("ASSERT: [%i] %s:\nHandle is not found within Spawn Requests, can't dequeue it: %s"), __LINE__, *FString(__FUNCTION__), *Handle.GetHash().ToString())) + { + return false; + } + + // Copy and remove first request from the queue (without Swap to keep order) + OutRequest = SpawnQueueInternal[Idx]; + SpawnQueueInternal.RemoveAt(Idx); + + return OutRequest.IsValid(); +} + +// 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, SpawnQueueInternal.Num()); ++Index) + { + FSpawnRequest OutRequest; + if (DequeueSpawnRequest(OutRequest)) + { + SpawnNow(OutRequest); + } + } + + // 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 (!SpawnQueueInternal.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(); +} diff --git a/Source/PoolManager/Private/PoolManagerSubsystem.cpp b/Source/PoolManager/Private/PoolManagerSubsystem.cpp index 73c234d..56cc024 100644 --- a/Source/PoolManager/Private/PoolManagerSubsystem.cpp +++ b/Source/PoolManager/Private/PoolManagerSubsystem.cpp @@ -2,8 +2,10 @@ #include "PoolManagerSubsystem.h" //--- +#include "Factories/PoolFactory_UObject.h" +#include "Data/PoolManagerSettings.h" +//--- #include "Engine/World.h" -#include "GameFramework/Actor.h" //--- #if WITH_EDITOR #include "Editor.h" @@ -11,8 +13,9 @@ //--- #include UE_INLINE_GENERATED_CPP_BY_NAME(PoolManagerSubsystem) -// 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) +/********************************************************************************************* + * Static Getters + ********************************************************************************************* */ // Returns the pointer to your Pool Manager UPoolManagerSubsystem* UPoolManagerSubsystem::GetPoolManagerByClass(TSubclassOf OptionalClass/* = nullptr*/, const UObject* OptionalWorldContext/* = nullptr*/) @@ -49,166 +52,321 @@ UPoolManagerSubsystem* UPoolManagerSubsystem::GetPoolManagerByClass(TSubclassOf< return FoundPoolManager; } -// Get the object from a pool by specified class -UObject* UPoolManagerSubsystem::TakeFromPool_Implementation(const UClass* ClassInPool, const FTransform& Transform) +/********************************************************************************************* + * Main API + ********************************************************************************************* */ + +// Async version of TakeFromPool() that returns the object by specified class +void UPoolManagerSubsystem::BPTakeFromPool(const UClass* ObjectClass, const FTransform& Transform, const FOnTakenFromPool& Completed) { - if (!ensureMsgf(ClassInPool, TEXT("%s: 'ClassInPool' is not specified"), *FString(__FUNCTION__))) + const FPoolObjectData* ObjectData = TakeFromPoolOrNull(ObjectClass, Transform); + if (ObjectData) { - return nullptr; + // Found in pool + Completed.ExecuteIfBound(ObjectData->PoolObject); + return; } - // Try to get free object from the pool and return - if (UObject* PoolObject = GetFreeObjectInPool(ClassInPool)) + FSpawnRequest Request; + Request.Class = ObjectClass; + Request.Transform = Transform; + Request.Callbacks.OnPostSpawned = [Completed](const FPoolObjectData& ObjectData) + { + Completed.ExecuteIfBound(ObjectData.PoolObject); + }; + CreateNewObjectInPool(Request); +} + +// Is code async version of TakeFromPool() that calls callback functions when the object is ready +FPoolObjectHandle UPoolManagerSubsystem::TakeFromPool(const UClass* ObjectClass, const FTransform& Transform/* = FTransform::Identity*/, const FOnSpawnCallback& Completed/* = nullptr*/) +{ + const FPoolObjectData* ObjectData = TakeFromPoolOrNull(ObjectClass, Transform); + if (ObjectData) { - if (AActor* Actor = Cast(PoolObject)) + if (Completed != nullptr) { - Actor->SetActorTransform(Transform); + Completed(*ObjectData); } - SetActive(true, PoolObject); + return ObjectData->Handle; + } + + FSpawnRequest Request; + Request.Class = ObjectClass; + Request.Transform = Transform; + Request.Callbacks.OnPostSpawned = Completed; + return CreateNewObjectInPool(Request); +} + +// Is internal function to find object in pool or return null +const FPoolObjectData* UPoolManagerSubsystem::TakeFromPoolOrNull(const UClass* ObjectClass, const FTransform& Transform) +{ + if (!ensureMsgf(ObjectClass, TEXT("%s: 'ObjectClass' is not specified"), *FString(__FUNCTION__))) + { + return nullptr; + } - return PoolObject; + FPoolContainer* Pool = FindPool(ObjectClass); + if (!Pool) + { + // Pool is not registered that is ok for this function, so it returns null + // Outer will create new object and register it in pool + return nullptr; } - // Since there is no free object in the pool, create a new one - return CreateNewObjectInPool(ClassInPool, Transform, EPoolObjectState::Active); + // Try to find first object contained in the Pool by its class that is inactive and ready to be taken from pool + const FPoolObjectData* FoundData = nullptr; + for (const FPoolObjectData& DataIt : Pool->PoolObjects) + { + if (DataIt.IsFree()) + { + FoundData = &DataIt; + break; + } + } + + if (!FoundData) + { + // No free objects in pool + return nullptr; + } + + UObject& InObject = FoundData->GetChecked(); + + Pool->GetFactoryChecked().OnTakeFromPool(&InObject, Transform); + + SetObjectStateInPool(EPoolObjectState::Active, InObject, *Pool); + + return FoundData; } // Returns the specified object to the pool and deactivates it if the object was taken from the pool before -void UPoolManagerSubsystem::ReturnToPool_Implementation(UObject* Object) +bool UPoolManagerSubsystem::ReturnToPool_Implementation(UObject* Object) { - SetActive(false, Object); + if (!ensureMsgf(Object, TEXT("ASSERT: [%i] %s:\n'Object' is null!"), __LINE__, *FString(__FUNCTION__))) + { + return false; + } + + FPoolContainer& Pool = FindPoolOrAdd(Object->GetClass()); + Pool.GetFactoryChecked().OnReturnToPool(Object); + + SetObjectStateInPool(EPoolObjectState::Inactive, *Object, Pool); + + return true; } -// Adds specified object as is to the pool by its class to be handled by the Pool Manager -bool UPoolManagerSubsystem::RegisterObjectInPool_Implementation(UObject* Object, EPoolObjectState PoolObjectState/* = EPoolObjectState::Inactive*/) +// Alternative to ReturnToPool() to return object to the pool by its handle +bool UPoolManagerSubsystem::ReturnToPool(const FPoolObjectHandle& Handle) { - if (!Object) + if (!ensureMsgf(Handle.IsValid(), TEXT("ASSERT: [%i] %s:\n'Handle' is not valid!"), __LINE__, *FString(__FUNCTION__))) { return false; } - const UClass* ActorClass = Object->GetClass(); - FPoolContainer* Pool = FindPool(ActorClass); - if (!Pool) + const FPoolContainer& Pool = FindPoolOrAdd(Handle.GetObjectClass()); + if (const FPoolObjectData* ObjectData = Pool.FindInPool(Handle)) { - const int32 PoolIndex = PoolsInternal.Emplace(FPoolContainer(ActorClass)); - Pool = &PoolsInternal[PoolIndex]; + const bool bSucceed = ReturnToPool(ObjectData->PoolObject); + return ensureMsgf(bSucceed, TEXT("ASSERT: [%i] %s:\nFailed to return object to the Pool by given object!"), __LINE__, *FString(__FUNCTION__)); } - if (!ensureMsgf(Pool, TEXT("%s: 'Pool' is not valid"), *FString(__FUNCTION__))) + // It's exclusive feature of Handles: + // cancel spawn request if object returns to pool faster than it is spawned + FSpawnRequest OutRequest; + const bool bSucceed = Pool.GetFactoryChecked().DequeueSpawnRequestByHandle(Handle, OutRequest); + return ensureMsgf(bSucceed, TEXT("ASSERT: [%i] %s:\nGiven Handle is not known by Pool Manager and is not even in spawning queue!"), __LINE__, *FString(__FUNCTION__)); +} + +/********************************************************************************************* + * Advanced + ********************************************************************************************* */ + +// Adds specified object as is to the pool by its class to be handled by the Pool Manager +bool UPoolManagerSubsystem::RegisterObjectInPool_Implementation(const FPoolObjectData& InData) +{ + if (!ensureMsgf(InData.PoolObject, TEXT("ASSERT: [%i] %s:\n'PoolObject' is not valid, can't registed it in the Pool!"), __LINE__, *FString(__FUNCTION__))) { return false; } - if (Pool->FindInPool(Object)) + const UClass* ObjectClass = InData.PoolObject.GetClass(); + FPoolContainer& Pool = FindPoolOrAdd(ObjectClass); + + if (Pool.FindInPool(*InData.PoolObject)) { // Already contains in pool return false; } - FPoolObjectData PoolObject(Object); - - if (const AActor* Actor = Cast(Object)) + FPoolObjectData Data = InData; + if (!Data.Handle.IsValid()) { - // Decide by its location should it be activated or not if only state is not specified - switch (PoolObjectState) - { - case EPoolObjectState::None: - PoolObject.bIsActive = !Actor->GetActorLocation().Equals(VECTOR_HALF_WORLD_MAX); - break; - case EPoolObjectState::Active: - PoolObject.bIsActive = true; - break; - case EPoolObjectState::Inactive: - PoolObject.bIsActive = false; - break; - default: - checkf(false, TEXT("%s: Invalid plugin enumeration type. Need to add a handle for that case here"), *FString(__FUNCTION__)); - break; - } + // Hash can be unset that is fine, generate new one + Data.Handle = FPoolObjectHandle::NewHandle(*ObjectClass); } - Pool->PoolObjects.Emplace(PoolObject); + Pool.PoolObjects.Emplace(Data); - SetActive(PoolObject.bIsActive, Object); + SetObjectStateInPool(Data.GetState(), *Data.PoolObject, Pool); return true; } // Always creates new object and adds it to the pool by its class -UObject* UPoolManagerSubsystem::CreateNewObjectInPool_Implementation(const UClass* ObjectClass, const FTransform& Transform, EPoolObjectState PoolObjectState) +FPoolObjectHandle UPoolManagerSubsystem::CreateNewObjectInPool_Implementation(const FSpawnRequest& InRequest) { - UWorld* World = GetWorld(); - if (!ensureMsgf(World, TEXT("%s: 'World' is not valid"), *FString(__FUNCTION__))) + if (!ensureMsgf(InRequest.Class, TEXT("ASSERT: [%i] %s:\n'Class' is not null in the Spawn Request!"), __LINE__, *FString(__FUNCTION__))) { - return nullptr; + return FPoolObjectHandle::EmptyHandle; } - FPoolContainer* Pool = FindPool(ObjectClass); - if (!Pool) + FSpawnRequest Request = InRequest; + if (!Request.Handle.IsValid()) + { + // Hash can be unset that is fine, generate new one + Request.Handle = FPoolObjectHandle::NewHandle(*Request.Class); + } + + // Always register new object in pool once it is spawned + const TWeakObjectPtr WeakThis(this); + Request.Callbacks.OnPreRegistered = [WeakThis](const FPoolObjectData& ObjectData) + { + if (UPoolManagerSubsystem* PoolManager = WeakThis.Get()) + { + PoolManager->RegisterObjectInPool(ObjectData); + } + }; + + const FPoolContainer& Pool = FindPoolOrAdd(Request.Class); + Pool.GetFactoryChecked().RequestSpawn(Request); + + return Request.Handle; +} + +/********************************************************************************************* + * Advanced - Factories + ********************************************************************************************* */ + +// Registers new factory to be used by the Pool Manager when dealing with objects of specific class and its children +void UPoolManagerSubsystem::AddFactory(TSubclassOf FactoryClass) +{ + const UClass* ObjectClass = GetObjectClassByFactory(FactoryClass); + if (!ensureMsgf(ObjectClass, TEXT("ASSERT: [%i] %s:\n'ObjectClass' is not set for next factory: %s"), __LINE__, *FString(__FUNCTION__), *FactoryClass->GetName()) + || AllFactoriesInternal.Contains(ObjectClass)) + { + return; + } + + UPoolFactory_UObject* NewFactory = NewObject(this, FactoryClass); + AllFactoriesInternal.Emplace(ObjectClass, NewFactory); +} + +// Removes factory from the Pool Manager by its class +void UPoolManagerSubsystem::RemoveFactory(TSubclassOf FactoryClass) +{ + const UClass* ObjectClass = GetObjectClassByFactory(FactoryClass); + if (!ensureMsgf(ObjectClass, TEXT("ASSERT: [%i] %s:\n'ObjectClass' is not set for next factory: %s"), __LINE__, *FString(__FUNCTION__), *FactoryClass->GetName())) { - const int32 PoolIndex = PoolsInternal.Emplace(FPoolContainer(ObjectClass)); - Pool = &PoolsInternal[PoolIndex]; + return; } - UObject* CreatedObject; - if (ObjectClass->IsChildOf()) + const TObjectPtr* FactoryPtr = AllFactoriesInternal.Find(ObjectClass); + if (!ensureMsgf(FactoryPtr, TEXT("ASSERT: [%i] %s:\nFactory is not found for next class: %s"), __LINE__, *FString(__FUNCTION__), *ObjectClass->GetName())) { - UClass* ClassToSpawn = const_cast(ObjectClass); - 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 - CreatedObject = World->SpawnActor(ClassToSpawn, &Transform, SpawnParameters); + return; + } + + UPoolFactory_UObject* Factory = *FactoryPtr; + if (IsValid(Factory)) + { + Factory->ConditionalBeginDestroy(); + } + + AllFactoriesInternal.Remove(ObjectClass); +} + +// Traverses the class hierarchy to find the closest registered factory for a given object type or its ancestors +UPoolFactory_UObject* UPoolManagerSubsystem::FindPoolFactoryChecked(const UClass* ObjectClass) const +{ + checkf(ObjectClass, TEXT("ERROR: [%i] %s:\n'ObjectClass' is null!"), __LINE__, *FString(__FUNCTION__)); + + const TObjectPtr* FoundFactory = nullptr; + const UClass* CurrentClass = ObjectClass; + + // This loop will keep traversing up the hierarchy until a registered factory is found or the root is reached + while (CurrentClass != nullptr) + { + FoundFactory = AllFactoriesInternal.Find(CurrentClass); + if (FoundFactory) + { + break; // Exit the loop if a factory is found + } + CurrentClass = CurrentClass->GetSuperClass(); // Otherwise, move up the class hierarchy } - else + + checkf(FoundFactory, TEXT("ERROR: [%i] %s:\n'FoundFactory' is null for next object class: %s"), __LINE__, *FString(__FUNCTION__), *GetNameSafe(ObjectClass)); + return *FoundFactory; +} + +// Returns default class of object that is handled by given factory +const UClass* UPoolManagerSubsystem::GetObjectClassByFactory(const TSubclassOf& FactoryClass) +{ + if (!ensureMsgf(FactoryClass, TEXT("ASSERT: [%i] %s:\n'FactoryClass' is null!"), __LINE__, *FString(__FUNCTION__))) { - CreatedObject = NewObject(World, ObjectClass); + return nullptr; } - checkf(CreatedObject, TEXT("CRITICAL ERROR: %s: 'CreatedObject' is not valid"), *FString(__FUNCTION__)) + const UPoolFactory_UObject* FactoryCDO = CastChecked(FactoryClass->GetDefaultObject()); + return FactoryCDO->GetObjectClass(); +} - FPoolObjectData PoolObjectData; - PoolObjectData.PoolObject = CreatedObject; - // Set activity here instead of calling UPoolManagerSubsystem::SetActive since new object never was inactivated before to switch the state - PoolObjectData.bIsActive = PoolObjectState == EPoolObjectState::Active; - Pool->PoolObjects.Emplace(MoveTemp(PoolObjectData)); +// Creates all possible Pool Factories to be used by the Pool Manager when dealing with objects +void UPoolManagerSubsystem::InitializeAllFactories() +{ + TArray AllPoolFactories; + UPoolManagerSettings::Get().GetPoolFactories(/*out*/AllPoolFactories); + for (UClass* FactoryClass : AllPoolFactories) + { + AddFactory(FactoryClass); + } +} - if (AActor* SpawnedActor = Cast(CreatedObject)) +// Destroys all Pool Factories that are used by the Pool Manager when dealing with objects +void UPoolManagerSubsystem::ClearAllFactories() +{ + for (const TTuple, TObjectPtr>& FactoryIt : AllFactoriesInternal) { - // Call construction script since it was delayed before to add it to the pool first - SpawnedActor->FinishSpawning(Transform); + if (IsValid(FactoryIt.Value)) + { + FactoryIt.Value->ConditionalBeginDestroy(); + } } - return CreatedObject; + AllFactoriesInternal.Empty(); } +/********************************************************************************************* + * Empty Pool + ********************************************************************************************* */ + // Destroy all object of a pool by a given class -void UPoolManagerSubsystem::EmptyPool_Implementation(const UClass* ClassInPool) +void UPoolManagerSubsystem::EmptyPool_Implementation(const UClass* ObjectClass) { - FPoolContainer* Pool = FindPool(ClassInPool); + FPoolContainer* Pool = FindPool(ObjectClass); if (!ensureMsgf(Pool, TEXT("%s: 'Pool' is not valid"), *FString(__FUNCTION__))) { return; } + UPoolFactory_UObject& Factory = Pool->GetFactoryChecked(); TArray& PoolObjects = Pool->PoolObjects; for (int32 Index = PoolObjects.Num() - 1; Index >= 0; --Index) { UObject* ObjectIt = PoolObjects.IsValidIndex(Index) ? PoolObjects[Index].Get() : nullptr; - if (!IsValid(ObjectIt)) - { - continue; - } - - if (AActor* Actor = Cast(ObjectIt)) - { - Actor->Destroy(); - } - else + if (IsValid(ObjectIt)) { - ObjectIt->ConditionalBeginDestroy(); + Factory.Destroy(ObjectIt); } } @@ -221,8 +379,8 @@ void UPoolManagerSubsystem::EmptyAllPools_Implementation() const int32 PoolsNum = PoolsInternal.Num(); for (int32 Index = PoolsNum - 1; Index >= 0; --Index) { - const UClass* ClassInPool = PoolsInternal.IsValidIndex(Index) ? PoolsInternal[Index].ClassInPool : nullptr; - EmptyPool(ClassInPool); + const UClass* ObjectClass = PoolsInternal.IsValidIndex(Index) ? PoolsInternal[Index].ObjectClass : nullptr; + EmptyPool(ObjectClass); } PoolsInternal.Empty(); @@ -239,7 +397,10 @@ void UPoolManagerSubsystem::EmptyAllByPredicate(const TFunctionRef& PoolObjectsRef = PoolsInternal[PoolIndex].PoolObjects; + FPoolContainer& PoolIt = PoolsInternal[PoolIndex]; + UPoolFactory_UObject& Factory = PoolIt.GetFactoryChecked(); + TArray& PoolObjectsRef = PoolIt.PoolObjects; + const int32 ObjectsNum = PoolObjectsRef.Num(); for (int32 ObjectIndex = ObjectsNum - 1; ObjectIndex >= 0; --ObjectIndex) { @@ -250,63 +411,23 @@ void UPoolManagerSubsystem::EmptyAllByPredicate(const TFunctionRef(ObjectIt)) - { - Actor->Destroy(); - } - else - { - ObjectIt->ConditionalBeginDestroy(); - } + Factory.Destroy(ObjectIt); PoolObjectsRef.RemoveAt(ObjectIndex); } } } -// Activates or deactivates the object if such object is handled by the Pool Manager -void UPoolManagerSubsystem::SetActive(bool bShouldActivate, UObject* Object) -{ - const UWorld* World = Object ? Object->GetWorld() : nullptr; - if (!World) - { - return; - } - - const UClass* ClassInPool = Object ? Object->GetClass() : nullptr; - FPoolContainer* Pool = FindPool(ClassInPool); - FPoolObjectData* PoolObject = Pool ? Pool->FindInPool(Object) : nullptr; - if (!PoolObject - || !PoolObject->IsValid()) - { - return; - } - - PoolObject->bIsActive = bShouldActivate; - - AActor* Actor = PoolObject->Get(); - if (!Actor) - { - return; - } - - if (!bShouldActivate) - { - // SetCollisionEnabled is not replicated, client collides with hidden actor, so move it - Actor->SetActorLocation(VECTOR_HALF_WORLD_MAX); - } - - Actor->SetActorHiddenInGame(!bShouldActivate); - Actor->SetActorEnableCollision(bShouldActivate); - Actor->SetActorTickEnabled(bShouldActivate); -} +/********************************************************************************************* + * Getters + ********************************************************************************************* */ // Returns current state of specified object EPoolObjectState UPoolManagerSubsystem::GetPoolObjectState_Implementation(const UObject* Object) const { - const UClass* ClassInPool = Object ? Object->GetClass() : nullptr; - const FPoolContainer* Pool = FindPool(ClassInPool); - const FPoolObjectData* PoolObject = Pool ? Pool->FindInPool(Object) : nullptr; + const UClass* ObjectClass = Object ? Object->GetClass() : nullptr; + const FPoolContainer* Pool = FindPool(ObjectClass); + const FPoolObjectData* PoolObject = Pool ? Pool->FindInPool(*Object) : nullptr; if (!PoolObject || !PoolObject->IsValid()) @@ -315,7 +436,7 @@ EPoolObjectState UPoolManagerSubsystem::GetPoolObjectState_Implementation(const return EPoolObjectState::None; } - return PoolObject->IsActive() ? EPoolObjectState::Active : EPoolObjectState::Inactive; + return PoolObject->GetState(); } // Returns true is specified object is handled by Pool Manager @@ -324,9 +445,9 @@ bool UPoolManagerSubsystem::ContainsObjectInPool_Implementation(const UObject* O return GetPoolObjectState(Object) != EPoolObjectState::None; } -bool UPoolManagerSubsystem::ContainsClassInPool_Implementation(const UClass* ClassInPool) const +bool UPoolManagerSubsystem::ContainsClassInPool_Implementation(const UClass* ObjectClass) const { - return FindPool(ClassInPool) != nullptr; + return FindPool(ObjectClass) != nullptr; } // Returns true if specified object is handled by the Pool Manager and was taken from its pool @@ -341,26 +462,24 @@ bool UPoolManagerSubsystem::IsFreeObjectInPool_Implementation(const UObject* Obj return GetPoolObjectState(Object) == EPoolObjectState::Inactive; } -// Returns first object contained in the Pool by its class that is inactive and ready to be taken from pool -UObject* UPoolManagerSubsystem::GetFreeObjectInPool_Implementation(const UClass* ObjectClass) const +// Returns number of free objects in pool by specified class +int32 UPoolManagerSubsystem::GetFreeObjectsNum_Implementation(const UClass* ObjectClass) const { const FPoolContainer* Pool = FindPool(ObjectClass); if (!Pool) { - return nullptr; + return 0; } - // Try to find ready object to return + int32 FreeObjectsNum = 0; for (const FPoolObjectData& PoolObjectIt : Pool->PoolObjects) { if (PoolObjectIt.IsFree()) { - return PoolObjectIt.Get(); + ++FreeObjectsNum; } } - - // There is no free object to be taken - return nullptr; + return FreeObjectsNum; } // Returns true if object is known by Pool Manager @@ -369,11 +488,54 @@ bool UPoolManagerSubsystem::IsRegistered_Implementation(const UObject* Object) c return GetPoolObjectState(Object) != EPoolObjectState::None; } +// Returns number of registered objects in pool by specified class +int32 UPoolManagerSubsystem::GetRegisteredObjectsNum_Implementation(const UClass* ObjectClass) const +{ + const FPoolContainer* Pool = FindPool(ObjectClass); + if (!Pool) + { + return 0; + } + + int32 RegisteredObjectsNum = 0; + for (const FPoolObjectData& PoolObjectIt : Pool->PoolObjects) + { + if (PoolObjectIt.IsValid()) + { + ++RegisteredObjectsNum; + } + } + + return RegisteredObjectsNum; +} + +// Returns the object associated with given handle +UObject* UPoolManagerSubsystem::FindPoolObjectByHandle(const FPoolObjectHandle& Handle) const +{ + const FPoolContainer* Pool = FindPool(Handle.GetObjectClass()); + const FPoolObjectData* ObjectData = Pool ? Pool->FindInPool(Handle) : nullptr; + return ObjectData ? ObjectData->PoolObject : nullptr; +} + +// Returns handle associated with given object +const FPoolObjectHandle& UPoolManagerSubsystem::FindPoolHandleByObject(const UObject* Object) const +{ + const FPoolContainer* Pool = Object ? FindPool(Object->GetClass()) : nullptr; + const FPoolObjectData* ObjectData = Pool ? Pool->FindInPool(*Object) : nullptr; + return ObjectData ? ObjectData->Handle : FPoolObjectHandle::EmptyHandle; +} + +/********************************************************************************************* + * Protected methods + ********************************************************************************************* */ + // Is called on initialization of the Pool Manager instance void UPoolManagerSubsystem::Initialize(FSubsystemCollectionBase& Collection) { Super::Initialize(Collection); + InitializeAllFactories(); + #if WITH_EDITOR if (GEditor && !GEditor->IsPlaySessionInProgress() // Is Editor and not in PIE @@ -381,8 +543,8 @@ void UPoolManagerSubsystem::Initialize(FSubsystemCollectionBase& Collection) { // Editor pool manager instance has different lifetime than PIE pool manager instance, // So, to prevent memory leaks, clear all pools on switching levels in Editor - TWeakObjectPtr WeakPoolManager(this); - auto OnWorldDestroyed = [WeakPoolManager](const UWorld* World) + const TWeakObjectPtr WeakThis(this); + auto OnWorldDestroyed = [WeakThis](const UWorld* World) { if (!World || !World->IsEditorWorld() || !GEditor || GEditor->IsPlaySessionInProgress()) @@ -391,7 +553,7 @@ void UPoolManagerSubsystem::Initialize(FSubsystemCollectionBase& Collection) return; } - if (UPoolManagerSubsystem* PoolManager = WeakPoolManager.Get()) + if (UPoolManagerSubsystem* PoolManager = WeakThis.Get()) { PoolManager->EmptyAllPools(); } @@ -402,16 +564,54 @@ void UPoolManagerSubsystem::Initialize(FSubsystemCollectionBase& Collection) #endif // WITH_EDITOR } +// Is called on deinitialization of the Pool Manager instance +void UPoolManagerSubsystem::Deinitialize() +{ + Super::Deinitialize(); + + ClearAllFactories(); +} + // Returns the pointer to found pool by specified class -FPoolContainer* UPoolManagerSubsystem::FindPool(const UClass* ClassInPool) +FPoolContainer& UPoolManagerSubsystem::FindPoolOrAdd(const UClass* ObjectClass) { - if (!ClassInPool) + checkf(ObjectClass, TEXT("ERROR: [%i] %s:\n'ObjectClass' is null!"), __LINE__, *FString(__FUNCTION__)); + + if (FPoolContainer* Pool = FindPool(ObjectClass)) + { + return *Pool; + } + + FPoolContainer& Pool = PoolsInternal.AddDefaulted_GetRef(); + Pool.ObjectClass = ObjectClass; + Pool.Factory = FindPoolFactoryChecked(ObjectClass); + return Pool; +} + +// Returns the pointer to found pool by specified class +FPoolContainer* UPoolManagerSubsystem::FindPool(const UClass* ObjectClass) +{ + if (!ensureMsgf(ObjectClass, TEXT("ASSERT: [%i] %s:\n'ObjectClass' is null!"), __LINE__, *FString(__FUNCTION__))) { return nullptr; } - return PoolsInternal.FindByPredicate([ClassInPool](const FPoolContainer& It) + return PoolsInternal.FindByPredicate([ObjectClass](const FPoolContainer& It) { - return It.ClassInPool == ClassInPool; + return It.ObjectClass == ObjectClass; }); } + +// Activates or deactivates the object if such object is handled by the Pool Manager +void UPoolManagerSubsystem::SetObjectStateInPool(EPoolObjectState NewState, UObject& InObject, FPoolContainer& InPool) +{ + FPoolObjectData* PoolObject = InPool.FindInPool(InObject); + if (!ensureMsgf(PoolObject && PoolObject->IsValid(), TEXT("ASSERT: [%i] %s:\n'PoolObject' is not registered in given pool for class: %s"), __LINE__, *FString(__FUNCTION__), *GetNameSafe(InPool.ObjectClass))) + { + return; + } + + PoolObject->bIsActive = NewState == EPoolObjectState::Active; + + InPool.GetFactoryChecked().OnChangedStateInPool(NewState, &InObject); +} diff --git a/Source/PoolManager/Private/PoolManagerTypes.cpp b/Source/PoolManager/Private/PoolManagerTypes.cpp index 3fbb79a..e7c909d 100644 --- a/Source/PoolManager/Private/PoolManagerTypes.cpp +++ b/Source/PoolManager/Private/PoolManagerTypes.cpp @@ -4,34 +4,68 @@ //--- #include UE_INLINE_GENERATED_CPP_BY_NAME(PoolManagerTypes) +// Empty pool object handle +const FPoolObjectHandle FPoolObjectHandle::EmptyHandle = FPoolObjectHandle(); + // Empty pool object data const FPoolObjectData FPoolObjectData::EmptyObject = FPoolObjectData(); // Empty pool data container const FPoolContainer FPoolContainer::EmptyPool = FPoolContainer(); +// Generates a new handle for the specified object class +FPoolObjectHandle FPoolObjectHandle::NewHandle(const UClass& InObjectClass) +{ + FPoolObjectHandle Handle; + Handle.ObjectClass = &InObjectClass; + Handle.Hash = FGuid::NewGuid(); + return Handle; +} + // Parameterized constructor that takes object to keep FPoolObjectData::FPoolObjectData(UObject* InPoolObject) { PoolObject = InPoolObject; } +// Returns the state of the object in the pool +EPoolObjectState FPoolObjectData::GetState() const +{ + return bIsActive ? EPoolObjectState::Active : EPoolObjectState::Inactive; +} + // Parameterized constructor that takes a class of the pool FPoolContainer::FPoolContainer(const UClass* InClass) { - ClassInPool = InClass; + ObjectClass = InClass; } // Returns the pointer to the Pool element by specified object -FPoolObjectData* FPoolContainer::FindInPool(const UObject* Object) +FPoolObjectData* FPoolContainer::FindInPool(const UObject& Object) +{ + return PoolObjects.FindByPredicate([&Object](const FPoolObjectData& It) + { + return It.PoolObject == &Object; + }); +} + +// Returns the pointer to the Pool element by specified handle +FPoolObjectData* FPoolContainer::FindInPool(const FPoolObjectHandle& Handle) { - if (!Object) + if (!ensureMsgf(Handle.IsValid(), TEXT("ASSERT: [%i] %s:\n'Handle' is not valid!"), __LINE__, *FString(__FUNCTION__))) { return nullptr; } - - return PoolObjects.FindByPredicate([Object](const FPoolObjectData& It) + + return PoolObjects.FindByPredicate([&Handle](const FPoolObjectData& PoolObjectIt) { - return It.PoolObject == Object; + return PoolObjectIt.Handle == Handle; }); } + +// Returns factory or crashes as critical error if it is not set +UPoolFactory_UObject& FPoolContainer::GetFactoryChecked() const +{ + checkf(Factory, TEXT("ERROR: [%i] %s:\n'Factory' is null!"), __LINE__, *FString(__FUNCTION__)); + return *Factory; +} diff --git a/Source/PoolManager/Public/Data/PoolManagerSettings.h b/Source/PoolManager/Public/Data/PoolManagerSettings.h new file mode 100644 index 0000000..b03b650 --- /dev/null +++ b/Source/PoolManager/Public/Data/PoolManagerSettings.h @@ -0,0 +1,50 @@ +// Copyright (c) Yevhenii Selivanov + +#pragma once + +#include "Engine/DeveloperSettings.h" +//--- +#include "PoolManagerSettings.generated.h" + +class UPoolFactory_UObject; + +/** + * Contains common settings data of the Pool Manager plugin. + * Is set up in 'Project Settings' -> "Plugins" -> "Pool Manager". + */ +UCLASS(Config = "PoolManager", DefaultConfig, meta = (DisplayName = "Pool Manager")) +class POOLMANAGER_API UPoolManagerSettings : public UDeveloperSettings +{ + GENERATED_BODY() + +public: + /** Returns Project Settings data of the Pool Manager plugin. */ + static const FORCEINLINE UPoolManagerSettings& Get() { return *GetDefault(); } + + /** Returns Project Settings data of the Pool Manager plugin. */ + UFUNCTION(BlueprintPure, Category = "Pool Manager") + static const FORCEINLINE UPoolManagerSettings* GetPoolManagerSettings() { return &Get(); } + + /** Gets the settings container name for the settings, either Project or Editor */ + virtual FName GetContainerName() const override { return TEXT("Project"); } + + /** Gets the category for the settings, some high level grouping like, Editor, Engine, Game...etc. */ + virtual FName GetCategoryName() const override { return TEXT("Plugins"); } + + /** Returns a limit of how many actors to spawn per frame. */ + UFUNCTION(BlueprintPure, Category = "Pool Manager") + int32 GetSpawnObjectsPerFrame() const { return SpawnObjectsPerFrame; } + + /** Returns all Pool Factories that will be used by the Pool Manager. */ + UFUNCTION(BlueprintPure, Category = "Pool Manager") + void GetPoolFactories(TArray& OutBlueprintPoolFactories) const; + +protected: + /** Set a limit of how many actors to spawn per frame. */ + UPROPERTY(Config, EditDefaultsOnly, BlueprintReadOnly, Category = "Pool Manager", meta = (BlueprintProtected = "true")) + int32 SpawnObjectsPerFrame; + + /** All Pool Factories that will be used by the Pool Manager. */ + UPROPERTY(Config, EditDefaultsOnly, BlueprintReadOnly, Category = "Pool Manager", meta = (BlueprintProtected = "true")) + TArray> PoolFactories; +}; diff --git a/Source/PoolManager/Public/Factories/PoolFactory_Actor.h b/Source/PoolManager/Public/Factories/PoolFactory_Actor.h new file mode 100644 index 0000000..400b00f --- /dev/null +++ b/Source/PoolManager/Public/Factories/PoolFactory_Actor.h @@ -0,0 +1,50 @@ +// Copyright (c) Yevhenii Selivanov + +#pragma once + +#include "PoolFactory_UObject.h" +//--- +#include "PoolFactory_Actor.generated.h" + +/** + * Is responsible for managing actors, it handles such differences in actors as: + * Creation: call SpawnActor. + * Destruction: call DestroyActor. + * Pool: change visibility, collision, ticking, etc. + */ +UCLASS() +class POOLMANAGER_API UPoolFactory_Actor : public UPoolFactory_UObject +{ + GENERATED_BODY() + +public: + /** Is overridden to handle Actors-inherited classes. */ + virtual const UClass* GetObjectClass_Implementation() const override; + + /********************************************************************************************* + * Creation + ********************************************************************************************* */ +public: + /** Is overridden to spawn actors using its engine's Spawn Actor method. */ + virtual UObject* SpawnNow_Implementation(const FSpawnRequest& Request) override; + + /********************************************************************************************* + * Destruction + ********************************************************************************************* */ +public: + /** Is overridden to destroy given actor using its engine's Destroy Actor method. */ + virtual void Destroy_Implementation(UObject* Object) override; + + /********************************************************************************************* + * Pool + ********************************************************************************************* */ +public: + /** Is overridden to set transform to the actor before taking the object from its pool. */ + virtual void OnTakeFromPool_Implementation(UObject* Object, const FTransform& Transform) override; + + /** Is overridden to reset transform to the actor before returning the object to its pool. */ + virtual void OnReturnToPool_Implementation(UObject* Object) override; + + /** Is overridden to change visibility, collision, ticking, etc. according new state. */ + virtual void OnChangedStateInPool_Implementation(EPoolObjectState NewState, UObject* InObject) override; +}; diff --git a/Source/PoolManager/Public/Factories/PoolFactory_UObject.h b/Source/PoolManager/Public/Factories/PoolFactory_UObject.h new file mode 100644 index 0000000..7f5383a --- /dev/null +++ b/Source/PoolManager/Public/Factories/PoolFactory_UObject.h @@ -0,0 +1,103 @@ +// Copyright (c) Yevhenii Selivanov + +#pragma once + +#include "UObject/Object.h" +//--- +#include "PoolManagerTypes.h" +//--- +#include "PoolFactory_UObject.generated.h" + +/** + * Each factory implements specific logic of creating and managing objects of its class and its children. + * Factories are designed to handle such differences as: + * Creation: UObjects call NewObject; Actors call SpawnActor, Components call NewObject+RegisterComponent, Widgets call CreateWidget etc. + * Destruction: UObjects call ConditionalBeginDestroy; Actors call DestroyActor, Components call DestroyComponent, Widgets call RemoveFromParent etc. + * Pool: Actors and Scene Components are changing visibility, collision, ticking, etc. UObjects and Widgets are not. + * + * To create new factory: + * 1. Inherit from this/child class + * 2. Add it to the 'Project Settings' -> "Plugins" -> "Pool Manager" -> "Pool Factories" + * 3. Override GetObjectClass() method. + */ +UCLASS(Blueprintable, BlueprintType) +class POOLMANAGER_API UPoolFactory_UObject : public UObject +{ + GENERATED_BODY() + +public: + /** Returns the class of object that this factory will create and manage. + * Has to be overridden by child classes if it wants to handle logic for specific class and its children. + * E.g: UObject for UPoolFactory_UObject, AActor for UPoolFactory_Actor, UWidget for UPoolFactory_Widget etc. */ + UFUNCTION(BlueprintNativeEvent, BlueprintPure, Category = "Pool Factory") + const UClass* GetObjectClass() const; + virtual FORCEINLINE const UClass* GetObjectClass_Implementation() const { return UObject::StaticClass(); } + + /********************************************************************************************* + * Creation + ********************************************************************************************* */ +public: + /** Method to queue object spawn requests. */ + UFUNCTION(BlueprintNativeEvent, Blueprintable, Category = "Pool Factory", meta = (AutoCreateRefTerm = "Request")) + void RequestSpawn(const FSpawnRequest& Request); + virtual void RequestSpawn_Implementation(const FSpawnRequest& Request); + + /** Method to immediately spawn requested object. */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "Pool Factory", meta = (AutoCreateRefTerm = "Request")) + UObject* SpawnNow(const FSpawnRequest& Request); + virtual UObject* SpawnNow_Implementation(const FSpawnRequest& Request); + + /** Removes the first spawn request from the queue and returns it. */ + UFUNCTION(BlueprintCallable, Category = "Pool Factory") + virtual bool DequeueSpawnRequest(FSpawnRequest& OutRequest); + + /** Alternative method to remove specific spawn request from the queue and returns it. */ + UFUNCTION(BlueprintCallable, Category = "Pool Factory") + virtual bool DequeueSpawnRequestByHandle(const FPoolObjectHandle& Handle, FSpawnRequest& OutRequest); + + /** Returns true if the spawn queue is empty, so there are no spawn request at current moment. */ + UFUNCTION(BlueprintPure, Category = "Pool Factory") + virtual FORCEINLINE bool IsSpawnQueueEmpty() const { return SpawnQueueInternal.IsEmpty(); } + +protected: + /** Is called on next frame to process a chunk of the spawn queue. */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "Pool Factory", meta = (BlueprintProtected)) + void OnNextTickProcessSpawn(); + virtual void OnNextTickProcessSpawn_Implementation(); + + /********************************************************************************************* + * Destruction + ********************************************************************************************* */ +public: + /** Method to destroy given object. */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "Pool Factory") + void Destroy(UObject* Object); + virtual void Destroy_Implementation(UObject* Object); + + /********************************************************************************************* + * Pool + ********************************************************************************************* */ +public: + /** Is called right before taking the object from its pool. */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "Pool Factory", meta = (AutoCreateRefTerm = "Transform")) + void OnTakeFromPool(UObject* Object, const FTransform& Transform); + virtual void OnTakeFromPool_Implementation(UObject* Object, const FTransform& Transform) {} + + /** Is called right before returning the object back to its pool. */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "Pool Factory") + void OnReturnToPool(UObject* Object); + virtual void OnReturnToPool_Implementation(UObject* Object) {} + + /** Is called when activates the object to take it from pool or deactivate when is returned back. */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "Pool Factory") + void OnChangedStateInPool(EPoolObjectState NewState, UObject* InObject); + virtual void OnChangedStateInPool_Implementation(EPoolObjectState NewState, UObject* InObject) {} + + /********************************************************************************************* + * Data + ********************************************************************************************* */ +protected: + /** All request to spawn. */ + UPROPERTY(VisibleInstanceOnly, BlueprintReadWrite, Transient, Category = "Pool Factory", meta = (BlueprintProtected, DisplayName = "Spawn Queue")) + TArray SpawnQueueInternal; +}; diff --git a/Source/PoolManager/Public/PoolManagerSubsystem.h b/Source/PoolManager/Public/PoolManagerSubsystem.h index 9f781bb..2e2e973 100644 --- a/Source/PoolManager/Public/PoolManagerSubsystem.h +++ b/Source/PoolManager/Public/PoolManagerSubsystem.h @@ -23,8 +23,14 @@ * Objects are taken from and returned to the Pool Manager when not in use, which makes them 'inactive'. * In case of actors, they are moved outside the game level, hidden, and they don't interact with anything or use up resources. * - * Tips: - * - You can use the Pool Manager in the Editor before you start the game. + * Code architecture: + * - Pool Manager is a subsystem that is created automatically including all children. + * - It stores and manages only the data (Pools and objects). + * - It does not manage any specific logic for handling objects, only base pooling logic related to data. + * - Pool Factories are used to handle specific logic about objects behavior (creation, destruction, visibility etc). + * + * Optional features: + * - Pool Manager is available even before starting the game that allows to preconstruct your actors on the level. * - You can implement your own Pool Manager by inheriting from this class and overriding the functions. */ UCLASS(BlueprintType, Blueprintable) @@ -37,46 +43,78 @@ class POOLMANAGER_API UPoolManagerSubsystem : public UWorldSubsystem ********************************************************************************************* */ public: /** Returns the Pool Manager, is checked and wil crash if can't be obtained. - * UPoolManagerSubsystem::Get(). with no parameters can be used in most cases if there is no specific set up. - * @tparam T is optional, put your child class if you implemented your own Pull Manager in code. - * @param OptionalClass is optional, specify the class if you implemented your own Pool Manager in blueprints. - * @param OptionalWorldContext is optional, can be null in most cases, could be useful to avoid obtaining the automatically. */ + * Is useful in code, in most cases can be used with no parameters to obtain default Pool Manager. + * E.g: + * - UPoolManagerSubsystem::Get().TakeFromPool... + * - UPoolManagerSubsystem::Get().TakeFromPool... + * - UPoolManagerSubsystem::Get(CustomBlueprintPoolManager).TakeFromPool... + * @tparam T is optional, put your child class if you implemented your own Pull Manager in code. + * @param OptionalClass is optional, specify the class if you implemented your own Pool Manager in blueprints. + * @param OptionalWorldContext is optional, can be null in most cases, could be useful to avoid obtaining the automatically. */ template static FORCEINLINE T& Get(TSubclassOf OptionalClass = T::StaticClass(), const UObject* OptionalWorldContext = nullptr) { return *CastChecked(GetPoolManagerByClass(OptionalClass, OptionalWorldContext)); } /** Returns the pointer to the Pool Manager. + * Is useful for blueprints to obtain **default** Pool Manager. * @param OptionalWorldContext is optional parameter and hidden in blueprints, can be null in most cases, could be useful to avoid obtaining the world automatically. */ UFUNCTION(BlueprintPure, meta = (WorldContext = "OptionalWorldContext")) static UPoolManagerSubsystem* GetPoolManager(const UObject* OptionalWorldContext = nullptr) { return GetPoolManagerByClass(StaticClass(), OptionalWorldContext); } /** Returns the pointer to custom Pool Manager by given class. - * @param OptionalClass is optional, specify the class if you implemented your own Pool Manager. - * @param OptionalWorldContext is optional parameter and hidden in blueprints, can be null in most cases, could be useful to avoid obtaining the world automatically. */ + * Is useful for blueprints to obtain your **custom** Pool Manager. + * @param OptionalClass is optional, specify the class if you implemented your own Pool Manager. + * @param OptionalWorldContext is optional parameter and hidden in blueprints, can be null in most cases, could be useful to avoid obtaining the world automatically. */ UFUNCTION(BlueprintPure, meta = (WorldContext = "OptionalWorldContext", DeterminesOutputType = "OptionalClass", BlueprintAutocast)) static UPoolManagerSubsystem* GetPoolManagerByClass(TSubclassOf OptionalClass = nullptr, const UObject* OptionalWorldContext = nullptr); /********************************************************************************************* - * Main + * Main API * * Use TakeFromPool() to get it instead of creating by your own. * Use ReturnToPool() to return it back to the pool instead of destroying by your own. ********************************************************************************************* */ public: - /** Get the object from a pool by specified class. + DECLARE_DYNAMIC_DELEGATE_OneParam(FOnTakenFromPool, UObject*, Object); + + /** Get the object from a pool by specified class, where output is async that returns the object when is ready. * It creates new object if there no free objects contained in pool or does not exist any. - * @return Activated object requested from the pool. */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "Pool Manager", meta = (AutoCreateRefTerm = "Transform")) - UObject* TakeFromPool(const UClass* ClassInPool, const FTransform& Transform); - virtual UObject* TakeFromPool_Implementation(const UClass* ClassInPool, const FTransform& Transform); + * @param ObjectClass The class of object to get from the pool. + * @param Transform The transform to set for the object (if actor). + * @param Completed The callback function that is called when the object is ready. + * @return if any is found and free, activates and returns object from the pool, otherwise async spawns new one next frames and register in the pool. + * @warning 'SpawnObjectsPerFrame' affects how fast new objects are created, it can be changed in'Project Settings' -> "Plugins" -> "Pool Manager". + * @warning Is custom blueprint node implemented in K2Node_TakeFromPool.h, so can't be overridden and accessible on graph (not inside functions). + * @warning Node's output will node work in for/while loop in blueprints because of Completed delegate: to achieve loop connect output exec to input exec. */ + UFUNCTION(BlueprintCallable, Category = "Pool Manager", meta = (BlueprintInternalUseOnly = "true")) + void BPTakeFromPool(const UClass* ObjectClass, const FTransform& Transform, const FOnTakenFromPool& Completed); + + /** Is code-overridable version of TakeFromPool() that calls callback functions when the object is ready. + * Can be overridden by child code classes. + * Is useful in code with blueprint classes, e.g: TakeFromPool(SomeBlueprintClass); + * @return Handle to the object with the Hash associated with the object, is indirect since the object could be not ready yet. */ + virtual FPoolObjectHandle TakeFromPool(const UClass* ObjectClass, const FTransform& Transform = FTransform::Identity, const FOnSpawnCallback& Completed = nullptr); - /** The templated alternative to get the object from a pool by specified class. */ + /** A templated alternative to get the object from a pool by class in template. + * Is useful in code with code classes, e.g: TakeFromPool(); */ template - FORCEINLINE T* TakeFromPool(const UClass* ClassInPool = T::StaticClass(), const FTransform& Transform = FTransform::Identity) { return Cast(TakeFromPool(ClassInPool, Transform)); } + FPoolObjectHandle TakeFromPool(const FTransform& Transform = FTransform::Identity, const FOnSpawnCallback& Completed = nullptr) { return TakeFromPool(T::StaticClass(), Transform, Completed); } - /** Returns the specified object to the pool and deactivates it if the object was taken from the pool before. */ + /** Is alternative version of TakeFromPool() to find object in pool or return null. */ + virtual const FPoolObjectData* TakeFromPoolOrNull(const UClass* ObjectClass, const FTransform& Transform); + +public: + /** Returns the specified object to the pool and deactivates it if the object was taken from the pool before. + * @param Object The object to return to the pool. + * @return true if pool was found and returned successfully, otherwise false. */ UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "Pool Manager", meta = (DefaultToSelf = "Object")) - void ReturnToPool(UObject* Object); - virtual void ReturnToPool_Implementation(UObject* Object); + bool ReturnToPool(UObject* Object); + virtual bool ReturnToPool_Implementation(UObject* Object); + + /** Alternative to ReturnToPool() to return object to the pool by its handle. + * Is useful in case when you don't have a reference to the object but have its handle. + * @param Handle The handle associated with the object to return to the pool. + * @return true if handle was found and return successfully, otherwise false. */ + virtual bool ReturnToPool(const FPoolObjectHandle& Handle); /********************************************************************************************* * Advanced @@ -85,21 +123,62 @@ class POOLMANAGER_API UPoolManagerSubsystem : public UWorldSubsystem ********************************************************************************************* */ public: /** Adds specified object as is to the pool by its class to be handled by the Pool Manager. - * It's designed to be used only on already existed objects unknown for the Pool Manager. */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "Pool Manager", meta = (DefaultToSelf = "Object")) - bool RegisterObjectInPool(UObject* Object = nullptr, EPoolObjectState PoolObjectState = EPoolObjectState::Inactive); - virtual bool RegisterObjectInPool_Implementation(UObject* Object = nullptr, EPoolObjectState PoolObjectState = EPoolObjectState::Inactive); + * Should not be used directly in most cases since is called automatically. + * Could be useful to add already existed objects (spawned by outer code) to the pool. + * It's designed to be used only on already existed objects unknown for the Pool Manager. + * @param InData The data with the object to register in the pool. + * @return true if registered successfully, otherwise false. */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "Pool Manager", meta = (AutoCreateRefTerm = "InData")) + bool RegisterObjectInPool(const FPoolObjectData& InData); + virtual bool RegisterObjectInPool_Implementation(const FPoolObjectData& InData); /** Always creates new object and adds it to the pool by its class. - * Use carefully if only there is no free objects contained in pool. */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "Pool Manager", meta = (DefaultToSelf = "Object")) - UObject* CreateNewObjectInPool(const UClass* ObjectClass, const FTransform& Transform, EPoolObjectState PoolObjectState = EPoolObjectState::Active); - virtual UObject* CreateNewObjectInPool_Implementation(const UClass* ObjectClass, const FTransform& Transform, EPoolObjectState PoolObjectState = EPoolObjectState::Active); + * Use carefully if only there is no free objects contained in pool. + * @param InRequest The request to spawn new object. + * @return Handle to the object with the hash associated with object to be spawned next frames. */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "Pool Manager", meta = (AutoCreateRefTerm = "InRequest")) + FPoolObjectHandle CreateNewObjectInPool(const FSpawnRequest& InRequest); + virtual FPoolObjectHandle CreateNewObjectInPool_Implementation(const FSpawnRequest& InRequest); + + /********************************************************************************************* + * Advanced - Factories + ********************************************************************************************* */ +public: + /** Registers new factory to be used by the Pool Manager when dealing with objects of specific class and its children. + * In most cases, you don't need to use this function since factories are registered automatically. + * Could be useful to register factory from different plugin or module. */ + UFUNCTION(BlueprintCallable, Category = "Pool Manager") + virtual void AddFactory(TSubclassOf FactoryClass); + + /** Removes factory from the Pool Manager by its class. + * In most cases, you don't need to use this function since factories are removed with destroying the Pool Manager. + * Could be useful to remove factory in runtime when some class is not needed anymore. */ + UFUNCTION(BlueprintCallable, Category = "Pool Manager") + virtual void RemoveFactory(TSubclassOf FactoryClass); + + /** Traverses the class hierarchy to find the closest registered factory for a given object type or its ancestors. */ + UFUNCTION(BlueprintPure, Category = "Pool Manager") + UPoolFactory_UObject* FindPoolFactoryChecked(const UClass* ObjectClass) const; + /** Returns default class of object that is handled by given factory. */ + UFUNCTION(BlueprintPure, Category = "Pool Manager") + static const UClass* GetObjectClassByFactory(const TSubclassOf& FactoryClass); + +protected: + /** Creates all possible Pool Factories to be used by the Pool Manager when dealing with objects. */ + virtual void InitializeAllFactories(); + + /** Destroys all Pool Factories that are used by the Pool Manager when dealing with objects. */ + virtual void ClearAllFactories(); + + /********************************************************************************************* + * Empty Pool + ********************************************************************************************* */ +public: /** Destroy all object of a pool by a given class. */ UFUNCTION(BlueprintNativeEvent, BlueprintCallable) - void EmptyPool(const UClass* ClassInPool); - virtual void EmptyPool_Implementation(const UClass* ClassInPool); + void EmptyPool(const UClass* ObjectClass); + virtual void EmptyPool_Implementation(const UClass* ObjectClass); /** Destroy all objects in all pools that are handled by the Pool Manager. */ UFUNCTION(BlueprintNativeEvent, BlueprintCallable) @@ -124,9 +203,9 @@ class POOLMANAGER_API UPoolManagerSubsystem : public UWorldSubsystem virtual bool ContainsObjectInPool_Implementation(const UObject* Object) const; /** Returns true is specified class is handled by Pool Manager. */ - UFUNCTION(BlueprintNativeEvent, BlueprintPure, Category = "Pool Manager", meta = (DefaultToSelf = "Object")) - bool ContainsClassInPool(const UClass* ClassInPool) const; - virtual bool ContainsClassInPool_Implementation(const UClass* ClassInPool) const; + UFUNCTION(BlueprintNativeEvent, BlueprintPure, Category = "Pool Manager") + bool ContainsClassInPool(const UClass* ObjectClass) const; + virtual bool ContainsClassInPool_Implementation(const UClass* ObjectClass) const; /** Returns true if specified object is handled by the Pool Manager and was taken from its pool. */ UFUNCTION(BlueprintNativeEvent, BlueprintPure, Category = "Pool Manager", meta = (DefaultToSelf = "Object")) @@ -138,18 +217,31 @@ class POOLMANAGER_API UPoolManagerSubsystem : public UWorldSubsystem bool IsFreeObjectInPool(const UObject* Object) const; virtual bool IsFreeObjectInPool_Implementation(const UObject* Object) const; - /** Returns first object contained in the Pool by its class that is inactive and ready to be taken from pool. - * Return null if there no free objects contained in pool or does not exist any. - * Consider to use TakeFromPool() instead to create new object if there no free objects. */ - UFUNCTION(BlueprintNativeEvent, BlueprintPure, Category = "Pool Manager", meta = (DefaultToSelf = "Object")) - UObject* GetFreeObjectInPool(const UClass* ObjectClass) const; - virtual UObject* GetFreeObjectInPool_Implementation(const UClass* ObjectClass) const; + /** Returns number of free objects in pool by specified class. */ + UFUNCTION(BlueprintNativeEvent, BlueprintPure, Category = "Pool Manager") + int32 GetFreeObjectsNum(const UClass* ObjectClass) const; + virtual int32 GetFreeObjectsNum_Implementation(const UClass* ObjectClass) const; /** Returns true if object is known by Pool Manager. */ UFUNCTION(BlueprintNativeEvent, BlueprintPure, Category = "Pool Manager", meta = (DefaultToSelf = "Object")) bool IsRegistered(const UObject* Object) const; virtual bool IsRegistered_Implementation(const UObject* Object) const; + /** Returns number of registered objects in pool by specified class. */ + UFUNCTION(BlueprintNativeEvent, BlueprintPure, Category = "Pool Manager") + int32 GetRegisteredObjectsNum(const UClass* ObjectClass) const; + virtual int32 GetRegisteredObjectsNum_Implementation(const UClass* ObjectClass) const; + + /** Returns the object associated with given handle. + * Can be null if not found or object is in spawning queue. */ + UFUNCTION(BlueprintPure, Category = "Pool Manager") + UObject* FindPoolObjectByHandle(const FPoolObjectHandle& Handle) const; + + /** Returns handle associated with given object. + * Can be invalid (FPoolObjectHandle::EmptyHandle) if not found. */ + UFUNCTION(BlueprintPure, Category = "Pool Manager", meta = (DefaultToSelf = "Object")) + const FPoolObjectHandle& FindPoolHandleByObject(const UObject* Object) const; + /********************************************************************************************* * Protected properties ********************************************************************************************* */ @@ -158,6 +250,11 @@ class POOLMANAGER_API UPoolManagerSubsystem : public UWorldSubsystem UPROPERTY(BlueprintReadWrite, Transient, Category = "Pool Manager", meta = (BlueprintProtected, DisplayName = "Pools")) TArray PoolsInternal; + /** Map to store registered factories against the class types they handle. + * @see UPoolFactory_UObject's description. */ + UPROPERTY(BlueprintReadWrite, Transient, Category = "Pool Manager", meta = (BlueprintProtected, DisplayName = "All Factories")) + TMap, TObjectPtr> AllFactoriesInternal; + /********************************************************************************************* * Protected methods ********************************************************************************************* */ @@ -165,11 +262,19 @@ class POOLMANAGER_API UPoolManagerSubsystem : public UWorldSubsystem /** Is called on initialization of the Pool Manager instance. */ virtual void Initialize(FSubsystemCollectionBase& Collection) override; + /** Is called on deinitialization of the Pool Manager instance. */ + virtual void Deinitialize() override; + /** Returns the pointer to found pool by specified class. */ - virtual FPoolContainer* FindPool(const UClass* ClassInPool); - const FORCEINLINE FPoolContainer* FindPool(const UClass* ClassInPool) const { return const_cast(this)->FindPool(ClassInPool); } + virtual FPoolContainer& FindPoolOrAdd(const UClass* ObjectClass); + virtual FPoolContainer* FindPool(const UClass* ObjectClass); + const FORCEINLINE FPoolContainer* FindPool(const UClass* ObjectClass) const { return const_cast(this)->FindPool(ObjectClass); } - /** Activates or deactivates the object if such object is handled by the Pool Manager. */ - UFUNCTION(BlueprintCallable, Category = "Pool Manager", meta = (BlueprintProtected, DefaultToSelf = "Object")) - virtual void SetActive(bool bShouldActivate, UObject* Object); + /** Activates or deactivates the object if such object is handled by the Pool Manager. + * Is called when the object is taken from, registered or returned to the pool. + * @param NewState If true, the object will be activated, otherwise deactivated. + * @param InObject The object to activate or deactivate. + * @param InPool The pool that contains the object. + * @warning Do not call it directly, use TakeFromPool() or ReturnToPool() instead. */ + virtual void SetObjectStateInPool(EPoolObjectState NewState, UObject& InObject, UPARAM(ref) FPoolContainer& InPool); }; diff --git a/Source/PoolManager/Public/PoolManagerTypes.h b/Source/PoolManager/Public/PoolManagerTypes.h index ce96a45..9eb415d 100644 --- a/Source/PoolManager/Public/PoolManagerTypes.h +++ b/Source/PoolManager/Public/PoolManagerTypes.h @@ -4,6 +4,8 @@ #include "UObject/Object.h" //--- +#include "Misc/Guid.h" +//--- #include "PoolManagerTypes.generated.h" /** @@ -20,6 +22,53 @@ enum class EPoolObjectState : uint8 Active }; +/** + * A handle for managing pool object indirectly. + * - Provides a unique identifier, Hash, with associated object in the pool. + * - Enables tracking and control of objects within the Pool Manager system. + * - Useful in scenarios where object is requested from the pool and the handle is obtained immediately, + * even if the object spawning is delayed to a later frame or different thread. + */ +USTRUCT(BlueprintType) +struct POOLMANAGER_API FPoolObjectHandle +{ + GENERATED_BODY() + + /* Default constructor of empty handle. */ + FPoolObjectHandle() = default; + + /** Empty pool object handle. */ + static const FPoolObjectHandle EmptyHandle; + + /** Generates a new handle for the specified object class. */ + static FPoolObjectHandle NewHandle(const UClass& InObjectClass); + + /** Returns true if Hash is generated. */ + FORCEINLINE bool IsValid() const { return ObjectClass && Hash.IsValid(); } + + /** Empties the handle. */ + void Invalidate() { *this = EmptyHandle; } + + friend POOLMANAGER_API uint32 GetTypeHash(const FPoolObjectHandle& InHandle) { return GetTypeHash(InHandle.Hash); } + friend POOLMANAGER_API bool operator==(const FPoolObjectHandle& Lhs, const FPoolObjectHandle& Rhs) { return Lhs.Hash == Rhs.Hash; } + + /********************************************************************************************* + * Fields + * Is private to prevent direct access to the fields, use NewHandle() instead. + ********************************************************************************************* */ +public: + const UClass* GetObjectClass() const { return ObjectClass; } + const FGuid& GetHash() const { return Hash; } + +private: + /** Class of the object in the pool. */ + UPROPERTY(Transient) + const UClass* ObjectClass = nullptr; + + /** Generated hash for the object. */ + FGuid Hash; +}; + /** * Contains the data that describe specific object in a pool. */ @@ -38,21 +87,28 @@ struct POOLMANAGER_API FPoolObjectData explicit FPoolObjectData(UObject* InPoolObject); /** Is true whenever the object is taken from the pool. */ - UPROPERTY(EditAnywhere, BlueprintReadWrite) + UPROPERTY(EditAnywhere, BlueprintReadWrite, Transient) bool bIsActive = false; /** The object that is handled by the pool. */ - UPROPERTY(EditAnywhere, BlueprintReadWrite) + UPROPERTY(EditAnywhere, BlueprintReadWrite, Transient) TObjectPtr PoolObject = nullptr; + /** The handle associated with this pool object for management within the Pool Manager system. */ + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Transient) + FPoolObjectHandle Handle = FPoolObjectHandle::EmptyHandle; + /** Returns true if the object is taken from the pool. */ FORCEINLINE bool IsActive() const { return bIsActive && IsValid(); } + /** Returns the state of the object in the pool. */ + EPoolObjectState GetState() const; + /** Returns true if handled object is inactive and ready to be taken from pool. */ FORCEINLINE bool IsFree() const { return !bIsActive && IsValid(); } /** Returns true if the object is created. */ - FORCEINLINE bool IsValid() const { return PoolObject != nullptr; } + FORCEINLINE bool IsValid() const { return PoolObject && Handle.IsValid(); } /** conversion to "bool" returning true if pool object is valid. */ FORCEINLINE operator bool() const { return IsValid(); } @@ -61,6 +117,10 @@ struct POOLMANAGER_API FPoolObjectData template FORCEINLINE T* Get() const { return Cast(PoolObject.Get()); } + /** Element access, crash if object is not valid. */ + template + FORCEINLINE T& GetChecked() const { return *CastChecked(PoolObject.Get()); } + /** Element access. */ FORCEINLINE UObject* operator->() const { return PoolObject.Get(); } }; @@ -83,17 +143,70 @@ struct POOLMANAGER_API FPoolContainer explicit FPoolContainer(const UClass* InClass); /** Class of all objects in this pool. */ - UPROPERTY(EditAnywhere, BlueprintReadWrite) - TObjectPtr ClassInPool = nullptr; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Transient) + TObjectPtr ObjectClass = nullptr; + + /** Factory that manages objects for this pool. */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Transient) + TObjectPtr Factory = nullptr; /** All objects in this pool that are handled by the Pool Manager. */ - UPROPERTY(EditAnywhere, BlueprintReadWrite) + UPROPERTY(EditAnywhere, BlueprintReadWrite, Transient) TArray PoolObjects; /** Returns the pointer to the Pool element by specified object. */ - FPoolObjectData* FindInPool(const UObject* Object); - const FORCEINLINE FPoolObjectData* FindInPool(const UObject* Object) const { return const_cast(this)->FindInPool(Object); } + FPoolObjectData* FindInPool(const UObject& Object); + const FORCEINLINE FPoolObjectData* FindInPool(const UObject& Object) const { return const_cast(this)->FindInPool(Object); } + + /** Returns the pointer to the Pool element by specified handle. */ + FPoolObjectData* FindInPool(const FPoolObjectHandle& Handle); + const FORCEINLINE FPoolObjectData* FindInPool(const FPoolObjectHandle& Handle) const { return const_cast(this)->FindInPool(Handle); } + + /** Returns factory or crashes as critical error if it is not set. */ + UPoolFactory_UObject& GetFactoryChecked() const; /** Returns true if the class is set for the Pool. */ - FORCEINLINE bool IsValid() const { return ClassInPool != nullptr; } + FORCEINLINE bool IsValid() const { return ObjectClass != nullptr; } +}; + +typedef TFunction FOnSpawnCallback; + +/** + * Contains the functions that are called when the object is spawned. + */ +struct POOLMANAGER_API FSpawnCallbacks +{ + /** Returns complete object data before registration in the Pool. */ + FOnSpawnCallback OnPreRegistered = nullptr; + + /** Returns already spawned and registered object. */ + FOnSpawnCallback OnPostSpawned = nullptr; +}; + +/** + * Define a structure to hold the necessary information for spawning an object. + */ +USTRUCT(BlueprintType) +struct POOLMANAGER_API FSpawnRequest +{ + GENERATED_BODY() + + /** Class of the object to spawn. */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Transient) + TObjectPtr Class = nullptr; + + /** Transform of the object to spawn. */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Transient) + FTransform Transform = FTransform::Identity; + + /** The handle associated with spawning pool object for management within the Pool Manager system. + * Is generated automatically if not set. */ + UPROPERTY(BlueprintReadOnly, Transient) + FPoolObjectHandle Handle = FPoolObjectHandle::EmptyHandle; + + /** Contains the functions that are called when the object is spawned. */ + FSpawnCallbacks Callbacks; + + /** Returns true if this spawn request can be processed. */ + FORCEINLINE bool IsValid() const { return Class && Handle.IsValid(); } }; diff --git a/Source/PoolManagerEditor/PoolManagerEditor.Build.cs b/Source/PoolManagerEditor/PoolManagerEditor.Build.cs new file mode 100644 index 0000000..3788ca1 --- /dev/null +++ b/Source/PoolManagerEditor/PoolManagerEditor.Build.cs @@ -0,0 +1,30 @@ +// Copyright (c) Yevhenii Selivanov. + +using UnrealBuildTool; + +public class PoolManagerEditor : ModuleRules +{ + public PoolManagerEditor(ReadOnlyTargetRules Target) : base(Target) + { + PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; + CppStandard = CppStandardVersion.Latest; + bEnableNonInlinedGenCppWarnings = true; + + PublicDependencyModuleNames.AddRange(new[] + { + "Core" + , "BlueprintGraph" // Created UK2Node_TakeFromPool + } + ); + + PrivateDependencyModuleNames.AddRange(new[] + { + "CoreUObject", "Engine", "Slate", "SlateCore" // Core + , "UnrealEd" + , "KismetCompiler" + // My modules + , "PoolManager" + } + ); + } +} \ No newline at end of file diff --git a/Source/PoolManagerEditor/Private/K2Node_TakeFromPool.cpp b/Source/PoolManagerEditor/Private/K2Node_TakeFromPool.cpp new file mode 100644 index 0000000..585043e --- /dev/null +++ b/Source/PoolManagerEditor/Private/K2Node_TakeFromPool.cpp @@ -0,0 +1,308 @@ +// Copyright (c) Yevhenii Selivanov + +#include "K2Node_TakeFromPool.h" +//--- +#include "PoolManagerSubsystem.h" +//--- +#include "BlueprintActionDatabaseRegistrar.h" +#include "BlueprintNodeSpawner.h" +#include "K2Node_AssignmentStatement.h" +#include "K2Node_CallFunction.h" +#include "K2Node_CustomEvent.h" +#include "K2Node_ExecutionSequence.h" +#include "K2Node_TemporaryVariable.h" +#include "KismetCompiler.h" +//--- +#include UE_INLINE_GENERATED_CPP_BY_NAME(K2Node_TakeFromPool) + +#define LOCTEXT_NAMESPACE "K2Node_TakeFromPool" + +void UK2Node_TakeFromPool::AllocateDefaultPins() +{ + CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Exec, UEdGraphSchema_K2::PN_Execute); + CreatePin(EGPD_Output, UEdGraphSchema_K2::PC_Exec, UEdGraphSchema_K2::PN_Completed); + + UEdGraphPin* TargetPin = CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Object, UPoolManagerSubsystem::StaticClass(), UEdGraphSchema_K2::PN_Self); + checkf(TargetPin, TEXT("ERROR: [%i] %s:\n'TargetPin' is null!"), __LINE__, *FString(__FUNCTION__)); + TargetPin->PinFriendlyName = LOCTEXT("Target", "Target"); + TargetPin->bDefaultValueIsIgnored = true; + + CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Class, UObject::StaticClass(), ClassInputName); + CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Struct, TBaseStructure::Get(), TransformInputName); + CreatePin(EGPD_Output, UEdGraphSchema_K2::PC_Object, UObject::StaticClass(), ObjectOutputName); +} + +void UK2Node_TakeFromPool::ReallocatePinsDuringReconstruction(TArray& OldPins) +{ + Super::ReallocatePinsDuringReconstruction(OldPins); + + UEdGraphPin* OldThenPin = nullptr; + const UEdGraphPin* OldCompletedPin = nullptr; + + for (UEdGraphPin* CurrentPin : OldPins) + { + if (CurrentPin->PinName == UEdGraphSchema_K2::PN_Then) + { + OldThenPin = CurrentPin; + } + else if (CurrentPin->PinName == UEdGraphSchema_K2::PN_Completed) + { + OldCompletedPin = CurrentPin; + } + } + + if (OldThenPin && !OldCompletedPin) + { + // This is an old node from when Completed was called then, rename the node to Completed and allow normal rewire to take place + OldThenPin->PinName = UEdGraphSchema_K2::PN_Completed; + } +} + +void UK2Node_TakeFromPool::ExpandNode(class FKismetCompilerContext& CompilerContext, UEdGraph* SourceGraph) +{ + Super::ExpandNode(CompilerContext, SourceGraph); + const UEdGraphSchema_K2* Schema = CompilerContext.GetSchema(); + check(Schema); + bool bIsErrorFree = true; + + // Sequence node, defaults to two output pins + UK2Node_ExecutionSequence* SequenceNode = CompilerContext.SpawnIntermediateNode(this, SourceGraph); + SequenceNode->AllocateDefaultPins(); + + // connect to input exe + { + UEdGraphPin* InputExePin = GetExecPin(); + UEdGraphPin* SequenceInputExePin = SequenceNode->GetExecPin(); + bIsErrorFree &= InputExePin && SequenceInputExePin && CompilerContext.MovePinLinksToIntermediate(*InputExePin, *SequenceInputExePin).CanSafeConnect(); + } + + // Create TakeFromPool function call + UK2Node_CallFunction* CallTakeFromPoolNode = CompilerContext.SpawnIntermediateNode(this, SourceGraph); + CallTakeFromPoolNode->FunctionReference.SetExternalMember(NativeFunctionName(), UPoolManagerSubsystem::StaticClass()); + CallTakeFromPoolNode->AllocateDefaultPins(); + + // connect load to first sequence pin + { + UEdGraphPin* CallFunctionInputExePin = CallTakeFromPoolNode->GetExecPin(); + UEdGraphPin* SequenceFirstExePin = SequenceNode->GetThenPinGivenIndex(0); + bIsErrorFree &= SequenceFirstExePin && CallFunctionInputExePin && Schema->TryCreateConnection(CallFunctionInputExePin, SequenceFirstExePin); + } + + // Create Local Variable + UK2Node_TemporaryVariable* TempVarOutput = CompilerContext.SpawnInternalVariable(this, UEdGraphSchema_K2::PC_Object, NAME_None, UObject::StaticClass()); + + // Create assign node + UK2Node_AssignmentStatement* AssignNode = CompilerContext.SpawnIntermediateNode(this, SourceGraph); + AssignNode->AllocateDefaultPins(); + + UEdGraphPin* LoadedObjectVariablePin = TempVarOutput->GetVariablePin(); + + // connect local variable to assign node + { + UEdGraphPin* AssignLHSPPin = AssignNode->GetVariablePin(); + bIsErrorFree &= AssignLHSPPin && LoadedObjectVariablePin && Schema->TryCreateConnection(AssignLHSPPin, LoadedObjectVariablePin); + } + + // connect local variable to output + { + UEdGraphPin* OutputObjectPinPin = FindPin(ObjectOutputName); + bIsErrorFree &= LoadedObjectVariablePin && OutputObjectPinPin && CompilerContext.MovePinLinksToIntermediate(*OutputObjectPinPin, *LoadedObjectVariablePin).CanSafeConnect(); + } + + // connect to Pool Manager input from self + UEdGraphPin* CallPoolManagerPin = CallTakeFromPoolNode->FindPin(UEdGraphSchema_K2::PN_Self); + { + UEdGraphPin* PoolManagerPin = FindPin(UEdGraphSchema_K2::PN_Self); + ensure(CallPoolManagerPin); + + if (PoolManagerPin && CallPoolManagerPin) + { + if (PoolManagerPin->LinkedTo.Num() > 0) + { + bIsErrorFree &= CompilerContext.MovePinLinksToIntermediate(*PoolManagerPin, *CallPoolManagerPin).CanSafeConnect(); + } + else + { + // Copy literal value + CallPoolManagerPin->DefaultValue = PoolManagerPin->DefaultValue; + } + } + else + { + bIsErrorFree = false; + } + } + + // connect to class input + UEdGraphPin* CallClassPin = CallTakeFromPoolNode->FindPin(ClassInputName); + { + UEdGraphPin* ClassPin = FindPin(ClassInputName); + ensure(CallClassPin); + + if (ClassPin && CallClassPin) + { + if (ClassPin->LinkedTo.Num() > 0) + { + bIsErrorFree &= CompilerContext.MovePinLinksToIntermediate(*ClassPin, *CallClassPin).CanSafeConnect(); + } + else + { + // Copy literal value + CallClassPin->DefaultValue = ClassPin->DefaultValue; + } + } + else + { + bIsErrorFree = false; + } + } + + // connect to transform input + UEdGraphPin* CallTransformPin = CallTakeFromPoolNode->FindPin(TransformInputName); + { + UEdGraphPin* TransformPin = FindPin(TransformInputName); + ensure(CallTransformPin); + + if (TransformPin && CallTransformPin) + { + if (TransformPin->LinkedTo.Num() > 0) + { + bIsErrorFree &= CompilerContext.MovePinLinksToIntermediate(*TransformPin, *CallTransformPin).CanSafeConnect(); + } + else + { + // Copy literal value + CallTransformPin->DefaultValue = TransformPin->DefaultValue; + } + } + else + { + bIsErrorFree = false; + } + } + + // Create Completed delegate parameter + const FName DelegateCompletedParamName(TEXT("Completed")); + UK2Node_CustomEvent* CompletedEventNode = CompilerContext.SpawnIntermediateEventNode(this, CallClassPin, SourceGraph); + CompletedEventNode->CustomFunctionName = *FString::Printf(TEXT("Completed_%s"), *CompilerContext.GetGuid(this)); + CompletedEventNode->AllocateDefaultPins(); + { + UFunction* TakeFromPoolFunction = CallTakeFromPoolNode->GetTargetFunction(); + FDelegateProperty* CompletedDelegateProperty = TakeFromPoolFunction ? FindFProperty(TakeFromPoolFunction, DelegateCompletedParamName) : nullptr; + UFunction* CompletedSignature = CompletedDelegateProperty ? CompletedDelegateProperty->SignatureFunction : nullptr; + ensure(CompletedSignature); + for (TFieldIterator PropIt(CompletedSignature); PropIt && PropIt->PropertyFlags & CPF_Parm; ++PropIt) + { + const FProperty* Param = *PropIt; + if (!Param->HasAnyPropertyFlags(CPF_OutParm) || Param->HasAnyPropertyFlags(CPF_ReferenceParm)) + { + FEdGraphPinType PinType; + bIsErrorFree &= Schema->ConvertPropertyToPinType(Param, /*out*/ PinType); + bIsErrorFree &= nullptr != CompletedEventNode->CreateUserDefinedPin(Param->GetFName(), PinType, EGPD_Output); + } + } + } + + // connect delegate + { + UEdGraphPin* CallFunctionDelegatePin = CallTakeFromPoolNode->FindPin(DelegateCompletedParamName); + ensure(CallFunctionDelegatePin); + UEdGraphPin* EventDelegatePin = CompletedEventNode->FindPin(UK2Node_CustomEvent::DelegateOutputName); + bIsErrorFree &= CallFunctionDelegatePin && EventDelegatePin && Schema->TryCreateConnection(CallFunctionDelegatePin, EventDelegatePin); + } + + // connect loaded object from event to assign + { + UEdGraphPin* LoadedAssetEventPin = CompletedEventNode->FindPin(TEXT("Object")); + ensure(LoadedAssetEventPin); + UEdGraphPin* AssignRHSPPin = AssignNode->GetValuePin(); + bIsErrorFree &= AssignRHSPPin && LoadedAssetEventPin && Schema->TryCreateConnection(LoadedAssetEventPin, AssignRHSPPin); + } + + // connect assign exec input to event output + { + UEdGraphPin* CompletedEventOutputPin = CompletedEventNode->FindPin(UEdGraphSchema_K2::PN_Then); + UEdGraphPin* AssignInputExePin = AssignNode->GetExecPin(); + bIsErrorFree &= AssignInputExePin && CompletedEventOutputPin && Schema->TryCreateConnection(AssignInputExePin, CompletedEventOutputPin); + } + + // connect assign exec output to output + { + UEdGraphPin* OutputCompletedPin = FindPin(UEdGraphSchema_K2::PN_Completed); + UEdGraphPin* AssignOutputExePin = AssignNode->GetThenPin(); + bIsErrorFree &= OutputCompletedPin && AssignOutputExePin && CompilerContext.MovePinLinksToIntermediate(*OutputCompletedPin, *AssignOutputExePin).CanSafeConnect(); + } + + if (!bIsErrorFree) + { + CompilerContext.MessageLog.Error(*LOCTEXT("InternalConnectionError", "K2Node_TakeFromPool: Internal connection error. @@").ToString(), this); + } + + BreakAllNodeLinks(); +} + +FText UK2Node_TakeFromPool::GetTooltipText() const +{ + return FText(LOCTEXT("UK2Node_TakeFromPoolGetTooltipText", + "Get the object from a pool by specified class, where output is async that returns the object when is ready. " + "It creates new object if there no free objects contained in pool or does not exist any. " + "If any is found and free, activates and returns object from the pool, otherwise async spawns new one next frames and register in the pool. " + "'SpawnObjectsPerFrame' affects how fast new objects are created, it can be changed in 'Project Settings' -> 'Plugins' -> 'Pool Manager'. " + "Is custom blueprint node implemented in K2Node_TakeFromPool.h, so can't be overridden and accessible on graph (not inside functions). " + "Node's output will not work in for/while loop in blueprints because of Completed delegate: to achieve loop connect output exec to input exec." + )); +} + +FText UK2Node_TakeFromPool::GetNodeTitle(ENodeTitleType::Type TitleType) const +{ + return FText(LOCTEXT("UK2Node_TakeFromPoolGetNodeTitle", "Take From Pool")); +} + +bool UK2Node_TakeFromPool::IsCompatibleWithGraph(const UEdGraph* TargetGraph) const +{ + bool bIsCompatible = false; + // Can only place events in ubergraphs and macros (other code will help prevent macros with latents from ending up in functions), and basicasync task creates an event node: + const EGraphType GraphType = TargetGraph->GetSchema()->GetGraphType(TargetGraph); + if (GraphType == EGraphType::GT_Ubergraph || GraphType == EGraphType::GT_Macro) + { + bIsCompatible = true; + } + return bIsCompatible && Super::IsCompatibleWithGraph(TargetGraph); +} + +FName UK2Node_TakeFromPool::GetCornerIcon() const +{ + return TEXT("Graph.Latent.LatentIcon"); +} + +void UK2Node_TakeFromPool::GetMenuActions(FBlueprintActionDatabaseRegistrar& ActionRegistrar) const +{ + // actions get registered under specific object-keys; the idea is that + // actions might have to be updated (or deleted) if their object-key is + // mutated (or removed)... here we use the node's class (so if the node + // type disappears, then the action should go with it) + const UClass* ActionKey = GetClass(); + // to keep from needlessly instantiating a UBlueprintNodeSpawner, first + // check to make sure that the registrar is looking for actions of this type + // (could be regenerating actions for a specific asset, and therefore the + // registrar would only accept actions corresponding to that asset) + if (ActionRegistrar.IsOpenForRegistration(ActionKey)) + { + UBlueprintNodeSpawner* NodeSpawner = UBlueprintNodeSpawner::Create(GetClass()); + check(NodeSpawner != nullptr); + + ActionRegistrar.AddBlueprintAction(ActionKey, NodeSpawner); + } +} + +FText UK2Node_TakeFromPool::GetMenuCategory() const +{ + return FText(LOCTEXT("UK2Node_TakeFromPoolGetMenuCategory", "Pool Manager")); +} + +FName UK2Node_TakeFromPool::NativeFunctionName() const +{ + return GET_FUNCTION_NAME_CHECKED(UPoolManagerSubsystem, BPTakeFromPool); +} + +#undef LOCTEXT_NAMESPACE diff --git a/Source/PoolManagerEditor/Private/PoolManagerEditorModule.cpp b/Source/PoolManagerEditor/Private/PoolManagerEditorModule.cpp new file mode 100644 index 0000000..fd18641 --- /dev/null +++ b/Source/PoolManagerEditor/Private/PoolManagerEditorModule.cpp @@ -0,0 +1,20 @@ +// Copyright (c) Yevhenii Selivanov. + +#include "PoolManagerEditorModule.h" + +#define LOCTEXT_NAMESPACE "FPoolManagerEditorModule" + +void FPoolManagerEditorModule::StartupModule() +{ + // This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module +} + +void FPoolManagerEditorModule::ShutdownModule() +{ + // This function may be called during shutdown to clean up your module. For modules that support dynamic reloading, + // we call this function before unloading the module. +} + +#undef LOCTEXT_NAMESPACE + +IMPLEMENT_MODULE(FPoolManagerEditorModule, PoolManagerEditor) \ No newline at end of file diff --git a/Source/PoolManagerEditor/Public/K2Node_TakeFromPool.h b/Source/PoolManagerEditor/Public/K2Node_TakeFromPool.h new file mode 100644 index 0000000..fb4919e --- /dev/null +++ b/Source/PoolManagerEditor/Public/K2Node_TakeFromPool.h @@ -0,0 +1,39 @@ +// Copyright (c) Yevhenii Selivanov + +#pragma once + +#include "K2Node.h" +//--- +#include "K2Node_TakeFromPool.generated.h" + +UCLASS(MinimalAPI) +class UK2Node_TakeFromPool : public UK2Node +{ + GENERATED_BODY() + +public: + static inline const FName ClassInputName = TEXT("ObjectClass"); + static inline const FName TransformInputName = TEXT("Transform"); + static inline const FName ObjectOutputName = TEXT("Object"); + + // UEdGraphNode interface + virtual void AllocateDefaultPins() override; + virtual FText GetTooltipText() const override; + virtual FText GetNodeTitle(ENodeTitleType::Type TitleType) const override; + virtual bool IsCompatibleWithGraph(const UEdGraph* TargetGraph) const override; + // End of UEdGraphNode interface + + // UK2Node interface + virtual bool IsNodeSafeToIgnore() const override { return true; } + virtual bool IsNodePure() const override { return false; } + virtual void ExpandNode(class FKismetCompilerContext& CompilerContext, UEdGraph* SourceGraph) override; + virtual FName GetCornerIcon() const override; + virtual void GetMenuActions(FBlueprintActionDatabaseRegistrar& ActionRegistrar) const override; + virtual FText GetMenuCategory() const override; + virtual bool NodeCausesStructuralBlueprintChange() const override { return true; } + virtual void ReallocatePinsDuringReconstruction(TArray& OldPins) override; + // End of UK2Node interface + +protected: + virtual FName NativeFunctionName() const; +}; diff --git a/Source/PoolManagerEditor/Public/PoolManagerEditorModule.h b/Source/PoolManagerEditor/Public/PoolManagerEditorModule.h new file mode 100644 index 0000000..0741823 --- /dev/null +++ b/Source/PoolManagerEditor/Public/PoolManagerEditorModule.h @@ -0,0 +1,25 @@ +// Copyright (c) Yevhenii Selivanov. + +#pragma once + +#include "Modules/ModuleInterface.h" +//--- +#include "Modules/ModuleManager.h" + +class POOLMANAGEREDITOR_API FPoolManagerEditorModule : public IModuleInterface +{ +public: + /** + * Called right after the module DLL has been loaded and the module object has been created. + * Load dependent modules here, and they will be guaranteed to be available during ShutdownModule. + */ + virtual void StartupModule() override; + + /** + * Called before the module is unloaded, right before the module object is destroyed. + * During normal shutdown, this is called in reverse order that modules finish StartupModule(). + * This means that, as long as a module references dependent modules in it's StartupModule(), it + * can safely reference those dependencies in ShutdownModule() as well. + */ + virtual void ShutdownModule() override; +};