Replicated

** [Drag Down] Performance Enhancement of Network Object Pooling ** 본문

언리얼 엔진/Drag Down

** [Drag Down] Performance Enhancement of Network Object Pooling **

라구넹 2025. 4. 16. 20:02

생각해보니 이전 네트워크 오브젝트 풀링은 결함이 있다

AActor* UDDNetworkObjectPoolingSubsystem::GetPooledObject(TSubclassOf<AActor> ActorClass, const FVector& SpawnLocation, const FRotator& SpawnRotation)
{
    if (!PooledActorsMap.Contains(ActorClass))
    {
        UE_LOG(LogTemp, Warning, TEXT("No Actor Pool : %s"), *ActorClass->GetName());
        return nullptr;
    }

    FActorArrayWrapper& ActorPool = PooledActorsMap[ActorClass];

    for (AActor* Actor : ActorPool.ActorArray)
    {
        if (Actor && Actor->IsHidden())
        {
            Actor->SetActorLocationAndRotation(SpawnLocation, SpawnRotation);

            IDDPoolable* PoolableActor = Cast<IDDPoolable>(Actor);
            if (PoolableActor)
            {
                PoolableActor->OnRetrievedFromPool();
            }

            return Actor;
        }
    }

일단 Actor 찾으려고 O(n) 순회한다

 

AActor* UDDNetworkObjectPoolingSubsystem::GetPooledObject(TSubclassOf<AActor> ActorClass, const FVector& SpawnLocation, const FRotator& SpawnRotation)
{
    if (!PooledActorsMap.Contains(ActorClass))
    {
        UE_LOG(LogTemp, Warning, TEXT("No Actor Pool : %s"), *ActorClass->GetName());
        return nullptr;
    }

    FActorArrayWrapper& ActorPool = PooledActorsMap[ActorClass];
    int32 PoolSize = ActorPool.ActorArray.Num();

    // Like a stack
    if ( PoolSize > 0 )
    {
        AActor* Actor = ActorPool.ActorArray.Last();
        if (Actor == nullptr) return nullptr;
        ActorPool.ActorArray.RemoveAt(PoolSize - 1);
        ActorPool.ActorSet.Remove(Actor);

        Actor->SetActorLocationAndRotation(SpawnLocation, SpawnRotation);

        IDDPoolable* PoolableActor = Cast<IDDPoolable>(Actor);
        if (PoolableActor)
        {
            PoolableActor->OnRetrievedFromPool();
        }

        return Actor;
    }

    // No Actor in Pool -> Spawn
    UWorld* World = GetWorld();
    if (World)
    {
        FActorSpawnParameters SpawnParams;
        SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;

        AActor* Actor = World->SpawnActor<AActor>(ActorClass, SpawnLocation, SpawnRotation, SpawnParams);

        IDDPoolable* PoolableActor = Cast<IDDPoolable>(Actor);
        if (PoolableActor)
        {
            PoolableActor->OnRetrievedFromPool();
        }

        return Actor;
    }

    return nullptr;
}

스택처럼 동작하도록 바꿨다

 

void UDDNetworkObjectPoolingSubsystem::ReturnPooledObject(AActor* Actor)
{
    if (!Actor)
    {
        UE_LOG(LogDD, Warning, TEXT("No Actor"));
        return;
    }

    TSubclassOf<AActor> ActorClass = Actor->GetClass();

    if (!PooledActorsMap.Contains(ActorClass))
    {
        UE_LOG(LogDD, Warning, TEXT("No Pool: %s"), *ActorClass->GetName());
        return;
    }

    IDDPoolable* PoolableActor = Cast<IDDPoolable>(Actor);
    if (PoolableActor)
    {
        PoolableActor->OnReturnedToPool();
    }

    FActorArrayWrapper& ActorPool = PooledActorsMap[ActorClass];

    if (!ActorPool.ActorArray.Contains(Actor))
    {
        //UE_LOG(LogTemp, Log, TEXT("Pool Actor : %s"), *ActorClass->GetName());
        ActorPool.ActorArray.Emplace(Actor);
    }
}

풀에 리턴할 때도 TArray의 Contains 써서 O(n)이다

 

USTRUCT()
struct FActorArrayWrapper
{
	GENERATED_BODY()

	UPROPERTY()
	TArray< TObjectPtr<AActor> > ActorArray;
	// for performance, ActorArray's Contains -> O(n), ActorSet's Contains -> O(1)
	TSet< TObjectPtr<AActor> > ActorSet;
};

래퍼 클래스에 TSet 추가하고

 

if (!ActorPool.ActorSet.Contains(Actor))
{
    //UE_LOG(LogTemp, Log, TEXT("Pool Actor : %s"), *ActorClass->GetName());
    ActorPool.ActorArray.Emplace(Actor);
    ActorPool.ActorSet.Add(Actor);
}

셋으로 Contains 검사로 해결