Git Product home page Git Product logo

future-extensions's People

Contributors

byte-warlock avatar cmesonarana avatar digibawb avatar digibob avatar ft-arnout avatar muit avatar rr2do2 avatar valentingalea avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

future-extensions's Issues

Crash when stopping PIE during execution of continuation

Hi!

First of all, awesome library, exactly what I needed to make my game more async with nice error handling!

I have a problem though: When I start my game, I load some data from an API and then do the game logic based on the API results. The problem is, that if I stop the game during this process, the game crashes. Do I need to tell Unreal somehow that there is still a lambda/promise pending so that it waits until the execution of the continuation is done and then shuts down?

Question/Help: Installation, Usage and Wrapping UE4 delegates

Hello, thank you for this amazing extension, UE4 lacks a good async API.

Engine Version: 4.25.1 (Not compiled from source)

I'm having issues trying to use the extension, I've installed the Plugin on the projects folder, re-generated the project files (VSCode), but when I try to include the #include "FutureExtensions.h" header the compiles does not find it. If I don't include this header file I get a lot of errors regarding the SD namespace.
I've also updated the .uproject and added and enabled both Automatron and SDFutureExtensions plugins.

To get rid of these issues I include the extension directly on the source folder as a module.
I've also updated the project.build.cs file:

        PublicDependencyModuleNames.AddRange(new string[] {
            "Core",
            "CoreUObject",
            "Engine",
            "InputCore",
            "UMG",
            "AIModule",
            /* Temporarily added GameplayTasks to workaround 4.12 compilation bug. */
            "GameplayTasks",
            "NavigationSystem",
            "Automatron"
        });

        PrivateDependencyModuleNames.AddRange(new string[] {
            "Debug",
            "LoadingScreen",
            "OnlineSubsystem",
            "OnlineSubsystemNull",
            "OnlineSubsystemSteam",
            "SDFutureExtensions"
        });

I was able to use the extension as a module, but now I'm facing sharedPtr errors regarding the Wrapping UE4 delegates example.
The code I'm trying to use inside a UObject class SSessionService.cpp:

#include "World/SSessionService.h"
#include "GameFramework/PlayerController.h"
#include "GameFramework/PlayerState.h"
#include "Engine/LocalPlayer.h"
#include "OnlineSubsystem.h"
#include "OnlineSubsystemUtils.h"
#include "OnlineSessionSettings.h"
#include "FutureExtensions.h"

// Class Functions Members...

SD::TExpectedFuture<TArray<FOnlineSessionSearchResult>> FindSessionsAsync(ULocalPlayer *ForPlayer, const FName SessionName, TSharedPtr<FOnlineSessionSearch> FindSessionsSettings)
{
    checkf(ForPlayer, TEXT("Invalid ULocalPlayer instance"));

    //IOnlineSubsystem *OnlineSub = IOnlineSubsystem::Get();
    IOnlineSubsystem *OnlineSub = Online::GetSubsystem(ForPlayer->GetWorld());
    checkf(OnlineSub, TEXT("Failed to retrieve OnlineSubsystem"));

    IOnlineSessionPtr SessionPtr = OnlineSub->GetSessionInterface();
    checkf(SessionPtr, TEXT("Failed to retrieve IOnlineSession interface"));

    //Create a TExpectedPromise that wraps an array of search results, i.e. the same thing that the FindSession API delegate returns.
    //This is wrapped in a TSharedPtr as it's lifetime needs to be associated with the lambda delegate that sets it.
    
// ========== SSessionService.cpp(307) - This Promise declarations is generating the TSharedPtr Errors ==========/
    TSharedPtr<SD::TExpectedPromise<TArray<FOnlineSessionSearchResult>>> Promise = MakeShared<SD::TExpectedPromise<TArray<FOnlineSessionSearchResult>>>();

    auto OnComplete = FOnFindSessionsCompleteDelegate::CreateLambda([Promise, FindSessionsSettings](bool Success) {
        if (Success)
        {
            Promise->SetValue(FindSessionsSettings->SearchResults);
        }
        else
        {
            Promise->SetValue(SD::Error(-1, TEXT("Session search failed")));
        }
    });

    //Again our DelegateHandle is wrapped in a TSharedPtr as it's lifetime needs to be associated with the continuation attached to the TExpectedPromise above.
    TSharedPtr<FDelegateHandle> DelegateHandle = MakeShareable(new FDelegateHandle());
    *DelegateHandle = SessionPtr->AddOnFindSessionsCompleteDelegate_Handle(OnComplete);

    if (!SessionPtr->FindSessions(*ForPlayer->GetPreferredUniqueNetId(), FindSessionsSettings.ToSharedRef()))
    {
        Promise->SetValue(SD::Error(-1, FString::Printf(TEXT("Failed to find '%s' sessions."), *(SessionName.ToString()))));
    }

    TWeakPtr<IOnlineSession, ESPMode::ThreadSafe> SessionInterfaceWeak = SessionPtr;
    return Promise->GetFuture().Then([DelegateHandle, SessionInterfaceWeak](SD::TExpected<TArray<FOnlineSessionSearchResult>> ExpectedResults) {
                                   IOnlineSessionPtr SessionInterface = SessionInterfaceWeak.Pin();
                                   if (SessionInterface.IsValid())
                                   {
                                       SessionInterface->ClearOnFindSessionsCompleteDelegate_Handle(*DelegateHandle);
                                   }

                                   return ExpectedResults;
                               })
        .Then([](TArray<FOnlineSessionSearchResult> Results) {
            return Results;
        });
}

Compile errors:

Building 4 actions with 8 processes...
  [1/4] SSessionService.cpp
  E:\Epic\UE_4.25\Engine\Source\Runtime\Core\Public\Templates/SharedPointer.h(523): error C2338: You cannot use a TSharedPtr of one mode with a type which inherits TSharedFromThis of another mode.
  E:\Epic\UE_4.25\Engine\Source\Runtime\Core\Public\Templates/SharedPointer.h(519): note: while compiling class template member function 'TSharedRef<ObjectType,0>::TSharedRef(ObjectType *,SharedPointerInternals::FReferenceControllerBase *)'
          with
          [
              ObjectType=SD::TExpectedPromise<TArray<FOnlineSessionSearchResult,FDefaultAllocator>>
          ]
  E:\Epic\UE_4.25\Engine\Source\Runtime\Core\Public\Templates/SharedPointer.h(135): note: see reference to function template instantiation 'TSharedRef<ObjectType,0>::TSharedRef(ObjectType *,SharedPointerInternals::FReferenceControllerBase *)' being compiled
          with
          [
              ObjectType=SD::TExpectedPromise<TArray<FOnlineSessionSearchResult,FDefaultAllocator>>
          ]
  E:\Projects\Unreal\SurvivalGame\Source\SurvivalGame\Private\World\SSessionService.cpp(307): note: see reference to class template instantiation 'TSharedRef<ObjectType,0>' being compiled
          with
          [
              ObjectType=SD::TExpectedPromise<TArray<FOnlineSessionSearchResult,FDefaultAllocator>>
          ]
The terminal process "cmd.exe /d /c Engine\Build\BatchFiles\Build.bat SurvivalGameEditor Win64 Development E:\Projects\Unreal\SurvivalGame\SurvivalGame.uproject -waitmutex" terminated with exit code: 6.

What I'm doing wrong/missing?
I'm able to declare the promise as a raw pointer:
auto Promise = new SD::TExpectedPromise<TArray<FOnlineSessionSearchResult>>();
So, when to delete the promise pointer?

Here is my implementation so far, missing the Create/Join Session Delegates:

#pragma once

#include "CoreMinimal.h"
#include "UObject/NoExportTypes.h"
#include "Interfaces/OnlineSessionInterface.h"
#include "Interfaces/OnlineIdentityInterface.h"
#include "OnlineSessionSettings.h"
#include "FutureExtensions.h"
#include "SSessionService.generated.h"

// Forward Declarations
class APlayerController;
class APlayerState;
class FUniqueNetId;
class IOnlineSubsystem;

UCLASS()
class SURVIVALGAME_API USSessionService : public UObject
{
	GENERATED_BODY()
public:
	USSessionService();

	virtual void BeginDestroy() override;

	bool InitOnlineSubSystem();
	const FName &GetCurrentSessionName() const;
	const FUniqueNetId *GetUniqueNetID(APlayerController *PlayerController);
	const FUniqueNetId *GetUniqueNetIDFromPlayerState(APlayerState *PlayerState);
	const FName &GetSessionNameSettingsKey() const;
	bool IsValidSession() const;

	SD::TExpectedFuture<bool> CreateSession(FString DisplayName, bool bIsLanSession, int32 NumPublicConnections, bool bShouldAdvertise, bool bUsesStats, bool bAllowJoinInProgress, bool bAllowInvites);
	void StartSession();
	SD::TExpectedFuture<TArray<FOnlineSessionSearchResult>> FindSession(bool bIsLanQuery, float TimeoutInSeconds);
	void JoinSession(FOnlineSessionSearchResult &OnlineSessionSearchResult);
	SD::TExpectedFuture<bool> DestroySession();

private:
	IOnlineSubsystem *OnlineSubsystem;
	IOnlineSessionPtr SessionInterface;
	IOnlineIdentityPtr IdentityInterface;
	TSharedPtr<FOnlineSessionSearch> SessionSearch;

	SD::TExpectedPromise<bool> *CreateSessionPromise;
	SD::TExpectedPromise<bool> *DestroySessionPromise;
	SD::TExpectedPromise<TArray<FOnlineSessionSearchResult>> *FindSessionPromise;

	FName CurrentSessionName;
	FName SessionNameSettingsKey;
	bool bSessionCreated;
};
#include "World/SSessionService.h"
#include "GameFramework/PlayerController.h"
#include "GameFramework/PlayerState.h"
#include "Engine/LocalPlayer.h"
#include "OnlineSubsystem.h"
#include "OnlineSubsystemUtils.h"

SD::TExpectedFuture<bool> CreateSessionAsync(IOnlineSessionPtr SessionInterface, FName &SessionName, FOnlineSessionSettings &SessionSettings, SD::TExpectedPromise<bool> *CreateSessionPromise, bool &bSessionCreatedRef)
{
    auto OnComplete = FOnCreateSessionCompleteDelegate::CreateLambda([CreateSessionPromise](FName CreatedSessionName, bool Success) {
        if (Success)
        {
            CreateSessionPromise->SetValue(Success);
        }
        else
        {
            CreateSessionPromise->SetValue(SD::Error(-1, TEXT("Session search failed")));
        }
    });

    //Again our DelegateHandle is wrapped in a TSharedPtr as it's lifetime needs to be associated with the continuation attached to the TExpectedPromise above.
    TSharedPtr<FDelegateHandle> DelegateHandle = MakeShareable(new FDelegateHandle());
    *DelegateHandle = SessionInterface->AddOnCreateSessionCompleteDelegate_Handle(OnComplete);

    if (!SessionInterface->CreateSession(0, SessionName, SessionSettings))
    {
        CreateSessionPromise->SetValue(SD::Error(-1, FString::Printf(TEXT("Failed to find sessions."))));
    }

    TWeakPtr<IOnlineSession, ESPMode::ThreadSafe> SessionInterfaceWeak = SessionInterface;
    return CreateSessionPromise->GetFuture().Then([DelegateHandle, SessionInterfaceWeak, &bSessionCreatedRef](bool Result) {
        IOnlineSessionPtr SessionInterfaceSharedPtr = SessionInterfaceWeak.Pin();
        if (SessionInterfaceSharedPtr.IsValid())
        {
            SessionInterfaceSharedPtr->ClearOnCreateSessionCompleteDelegate_Handle(*DelegateHandle);
        }

        bSessionCreatedRef = Result;

        return Result;
    });
}

SD::TExpectedFuture<bool> DestroySessionAsync(IOnlineSessionPtr SessionInterface, FName &SessionName, SD::TExpectedPromise<bool> *DestroySessionPromise, bool &bSessionCreatedRef)
{
    auto OnComplete = FOnDestroySessionCompleteDelegate::CreateLambda([DestroySessionPromise](FName DestroyedSessionName, bool Success) {
        if (Success)
        {
            DestroySessionPromise->SetValue(Success);
        }
        else
        {
            DestroySessionPromise->SetValue(SD::Error(-1, TEXT("Failed to destroy session")));
        }
    });

    //Again our DelegateHandle is wrapped in a TSharedPtr as it's lifetime needs to be associated with the continuation attached to the TExpectedPromise above.
    TSharedPtr<FDelegateHandle> DelegateHandle = MakeShareable(new FDelegateHandle());
    *DelegateHandle = SessionInterface->AddOnCreateSessionCompleteDelegate_Handle(OnComplete);

    if (!SessionInterface->DestroySession(SessionName))
    {
        DestroySessionPromise->SetValue(SD::Error(-1, FString::Printf(TEXT("Failed to destroy session"))));
    }

    TWeakPtr<IOnlineSession, ESPMode::ThreadSafe> SessionInterfaceWeak = SessionInterface;
    return DestroySessionPromise->GetFuture().Then([DelegateHandle, SessionInterfaceWeak, &bSessionCreatedRef](bool Result) {
        IOnlineSessionPtr SessionInterfaceSharedPtr = SessionInterfaceWeak.Pin();
        if (SessionInterfaceSharedPtr.IsValid())
        {
            SessionInterfaceSharedPtr->ClearOnDestroySessionCompleteDelegate_Handle(*DelegateHandle);
        }

        bSessionCreatedRef = !Result;

        return Result;
    });
}

SD::TExpectedFuture<TArray<FOnlineSessionSearchResult>> FindSessionsAsync(IOnlineSessionPtr SessionInterface, TSharedPtr<FOnlineSessionSearch> FindSessionsSettings, SD::TExpectedPromise<TArray<FOnlineSessionSearchResult>> *FindSessionPromise)
{
    if (FindSessionPromise == nullptr)
    {
        FindSessionPromise = new SD::TExpectedPromise<TArray<FOnlineSessionSearchResult>>();
    }
    auto OnComplete = FOnFindSessionsCompleteDelegate::CreateLambda([FindSessionPromise, FindSessionsSettings](bool Success) {
        if (Success)
        {
            FindSessionPromise->SetValue(FindSessionsSettings->SearchResults);
        }
        else
        {
            FindSessionPromise->SetValue(SD::Error(-1, TEXT("Session search failed")));
        }
    });

    //Again our DelegateHandle is wrapped in a TSharedPtr as it's lifetime needs to be associated with the continuation attached to the TExpectedPromise above.
    TSharedPtr<FDelegateHandle> DelegateHandle = MakeShareable(new FDelegateHandle());
    *DelegateHandle = SessionInterface->AddOnFindSessionsCompleteDelegate_Handle(OnComplete);

    if (!SessionInterface->FindSessions(0, FindSessionsSettings.ToSharedRef()))
    {
        FindSessionPromise->SetValue(SD::Error(-1, FString::Printf(TEXT("Failed to find sessions."))));
    }

    TWeakPtr<IOnlineSession, ESPMode::ThreadSafe> SessionInterfaceWeak = SessionInterface;
    return FindSessionPromise->GetFuture().Then([DelegateHandle, SessionInterfaceWeak](SD::TExpected<TArray<FOnlineSessionSearchResult>> ExpectedResults) {
        IOnlineSessionPtr SessionInterfaceSharedPtr = SessionInterfaceWeak.Pin();
        if (SessionInterfaceSharedPtr.IsValid())
        {
            SessionInterfaceSharedPtr->ClearOnFindSessionsCompleteDelegate_Handle(*DelegateHandle);
        }

        return ExpectedResults;
    });
}

USSessionService::USSessionService()
{
    OnlineSubsystem = nullptr;

    CurrentSessionName = TEXT("NONE");
    SessionNameSettingsKey = TEXT("SessionNameKey");
    bSessionCreated = false;

    CreateSessionPromise = nullptr;
    FindSessionPromise = nullptr;
    DestroySessionPromise = nullptr;
}

void USSessionService::BeginDestroy()
{
    Super::BeginDestroy();

    if (CreateSessionPromise)
    {
        delete CreateSessionPromise;
        CreateSessionPromise = nullptr;
    }

    if (FindSessionPromise)
    {
        delete FindSessionPromise;
        FindSessionPromise = nullptr;
    }

    if (DestroySessionPromise)
    {
        delete DestroySessionPromise;
        DestroySessionPromise = nullptr;
    }
}

bool USSessionService::InitOnlineSubSystem()
{
    OnlineSubsystem = IOnlineSubsystem::Get();
    if (!OnlineSubsystem)
    {
        UE_LOG(LogTemp, Error, TEXT("%s: Failed to find OnlineSubsystem!"), *GetName());
        return false;
    }

    UE_LOG(LogTemp, Display, TEXT("%s: Using %s OnlineSubsystem"), *GetName(), *OnlineSubsystem->GetSubsystemName().ToString());

    SessionInterface = OnlineSubsystem->GetSessionInterface();
    if (!SessionInterface.IsValid())
    {
        UE_LOG(LogTemp, Error, TEXT("%s: SessionInterface is INVALID"), *GetName());
        return false;
    }

    return true;
}

const FName &USSessionService::GetCurrentSessionName() const
{
    return CurrentSessionName;
}

bool USSessionService::IsValidSession() const
{
    if (OnlineSubsystem && SessionInterface.IsValid())
    {
        if (!CurrentSessionName.IsEqual(TEXT("NONE"), ENameCase::CaseSensitive, false) && bSessionCreated)
        {
            return true;
        }
    }
    return false;
}

const FName &USSessionService::GetSessionNameSettingsKey() const
{
    return SessionNameSettingsKey;
}

const FUniqueNetId *USSessionService::GetUniqueNetID(APlayerController *PlayerController)
{
    if (!PlayerController)
    {
        UE_LOG(LogTemp, Error, TEXT("%s: PlayerController parameter is NULL!"), *GetName());
        return nullptr;
    }

    if (APlayerState *PlayerState = (PlayerController != NULL) ? PlayerController->PlayerState : NULL)
    {
        const FUniqueNetId *UniqueNetId = PlayerState->GetUniqueId().GetUniqueNetId().Get();
        if (!UniqueNetId || !UniqueNetId->IsValid())
        {
            UE_LOG(LogTemp, Error, TEXT("%s: UniqueNetID is NULL/Invalid!"), *GetName());
            return nullptr;
        }
        return UniqueNetId;
    }
    return nullptr;
}

const FUniqueNetId *USSessionService::GetUniqueNetIDFromPlayerState(APlayerState *PlayerState)
{
    if (!PlayerState)
    {
        UE_LOG(LogTemp, Error, TEXT("%s: PlayerState parameter is NULL!"), *GetName());
        return nullptr;
    }

    const FUniqueNetId *UniqueNetId = PlayerState->GetUniqueId().GetUniqueNetId().Get();
    if (!UniqueNetId || !UniqueNetId->IsValid())
    {
        UE_LOG(LogTemp, Error, TEXT("%s: UniqueNetID is NULL/Invalid!"), *GetName());
        return nullptr;
    }

    return UniqueNetId;
}

SD::TExpectedFuture<bool> USSessionService::CreateSession(FString DisplayName, bool bIsLanSession, int32 NumPublicConnections, bool bShouldAdvertise, bool bUsesStats, bool bAllowJoinInProgress, bool bAllowInvites)
{
    // Free any previous results
    if (CreateSessionPromise)
    {
        delete CreateSessionPromise;
        CreateSessionPromise = nullptr;
    }

    // Realocate
    CreateSessionPromise = new SD::TExpectedPromise<bool>();

    if (SessionInterface.IsValid())
    {
        CurrentSessionName = TEXT("GameSession");
        FOnlineSessionSettings SessionSettings;
        //TODO: Set the correct BuildUniqueId
        SessionSettings.BuildUniqueId = 554768;
        // Configurable Settings
        SessionSettings.bIsLANMatch = bIsLanSession;
        SessionSettings.NumPublicConnections = NumPublicConnections;
        SessionSettings.NumPrivateConnections = 0;
        SessionSettings.bShouldAdvertise = bShouldAdvertise;
        SessionSettings.bUsesStats = bUsesStats;
        SessionSettings.bAllowJoinInProgress = bAllowJoinInProgress;
        SessionSettings.bAllowInvites = bAllowInvites;

        // Dedicated Server Settings
        SessionSettings.bUsesPresence = false;
        SessionSettings.bAllowJoinViaPresence = false;
        SessionSettings.bAllowJoinViaPresenceFriendsOnly = false;
        SessionSettings.bIsDedicated = true;
        SessionSettings.bAntiCheatProtected = false;

        if (bShouldAdvertise)
        {
            SessionSettings.Set<FString>(SessionNameSettingsKey, DisplayName, EOnlineDataAdvertisementType::ViaOnlineServiceAndPing);
        }
        else
        {
            SessionSettings.Set<FString>(SessionNameSettingsKey, DisplayName, EOnlineDataAdvertisementType::ViaPingOnly);
        }

        return CreateSessionAsync(SessionInterface, CurrentSessionName, SessionSettings, CreateSessionPromise, bSessionCreated);

        //TODO: Use the Correct Player Num
        //SessionInterface->CreateSession(0, CurrentSessionName, SessionSettings);
    }

    CreateSessionPromise->SetValue(SD::Error(-1, FString::Printf(TEXT("Failed to create session"))));
    return CreateSessionPromise->GetFuture();
}

void USSessionService::StartSession()
{
    if (IsValidSession())
    {
        SessionInterface->StartSession(CurrentSessionName);
    }
}

SD::TExpectedFuture<bool> USSessionService::DestroySession()
{
    // Free any previous results
    if (DestroySessionPromise)
    {
        delete DestroySessionPromise;
        DestroySessionPromise = nullptr;
    }

    // Realocate
    DestroySessionPromise = new SD::TExpectedPromise<bool>();

    if (SessionInterface.IsValid())
    {
        bSessionCreated = false;
        CurrentSessionName = TEXT("NONE");

        auto ExistingSession = SessionInterface->GetNamedSession(CurrentSessionName);
        if (ExistingSession != nullptr)
        {
            return DestroySessionAsync(SessionInterface, CurrentSessionName, DestroySessionPromise, bSessionCreated);
            //SessionInterface->DestroySession(CurrentSessionName);
        }

        DestroySessionPromise->SetValue(true);
        return DestroySessionPromise->GetFuture();
    }

    DestroySessionPromise->SetValue(SD::Error(-1, FString::Printf(TEXT("Failed to destroy session"))));
    return DestroySessionPromise->GetFuture();
}

SD::TExpectedFuture<TArray<FOnlineSessionSearchResult>> USSessionService::FindSession(bool bIsLanQuery, float TimeoutInSeconds)
{
    // Free any previous results
    if (FindSessionPromise)
    {
        delete FindSessionPromise;
        FindSessionPromise = nullptr;
    }

    // Allocate again
    //FindSessionPromise = new SD::TExpectedPromise<TArray<FOnlineSessionSearchResult>>();

    SessionSearch = MakeShareable(new FOnlineSessionSearch());
    if (SessionSearch.IsValid())
    {
        SessionSearch->bIsLanQuery = bIsLanQuery;
        SessionSearch->TimeoutInSeconds = TimeoutInSeconds;
        SessionSearch->MaxSearchResults = 100;
        SessionSearch->QuerySettings.Set<bool>(SEARCH_DEDICATED_ONLY, true, EOnlineComparisonOp::Equals);
        //TODO Pass the Correct Player Number
        //SessionInterface->FindSessions(0, SessionSearch.ToSharedRef());

        return FindSessionsAsync(SessionInterface, SessionSearch, FindSessionPromise);
    }

    FindSessionPromise->SetValue(SD::Error(-1, FString::Printf(TEXT("Failed to find sessions"))));
    return FindSessionPromise->GetFuture();
}

void USSessionService::JoinSession(FOnlineSessionSearchResult &OnlineSessionSearchResult)
{
    if (!SessionInterface.IsValid() || !SessionSearch.IsValid())
    {
        return;
    }
    SessionInterface->JoinSession(0, CurrentSessionName, OnlineSessionSearchResult);
}

I'm using class member variable pointers to control the memory allocation, I changed the logic on the CreateSession to create the pointer on the function scope.

Execution policy ThreadPool test failures in UE5 EA

I've noticed a couple of tests are failing with the latest version in a UE5 Early Access project, but I'm unsure if it's related to project settings or potential engine changes in UE5. The two tests in question are in FFutureTestSpec_ExecutionPolicy:

LatentIt("Can inline thread in next Then", [this](const auto& Done)
{
SD::Async([]()
{
return SD::MakeReadyExpected(FTaskGraphInterface::Get().GetCurrentThreadIfKnown());
}, SD::FExpectedFutureOptionsBuilder()
.SetExecutionPolicy(SD::EExpectedFutureExecutionPolicy::ThreadPool)
.Build())
.Then([this, Done](SD::TExpected<ENamedThreads::Type> Expected)
{
TestTrue("Async function is completed", Expected.IsCompleted());
TestEqual("Execution thread", *Expected, ENamedThreads::AnyThread);
Done.Execute();
});
});
LatentIt("Can schedule Then on a worker thread", [this](const auto& Done)
{
SD::Async([]()
{
return SD::MakeReadyExpected();
})
.Then([](SD::TExpected<void> Expected)
{
return SD::MakeReadyExpected(FTaskGraphInterface::Get().GetCurrentThreadIfKnown());
}, SD::FExpectedFutureOptionsBuilder()
.SetExecutionPolicy(SD::EExpectedFutureExecutionPolicy::ThreadPool)
.Build())
.Then([this, Done](SD::TExpected<ENamedThreads::Type> Expected)
{
TestTrue("Async function is completed", Expected.IsCompleted());
TestEqual("Execution thread", *Expected, ENamedThreads::AnyThread);
Done.Execute();
});
});

FTaskGraphInterface::Get().GetCurrentThreadIfKnown() is returning ENamedThreads::AnyBackgroundThreadNormalTask rather than ENamedThreads::AnyThread, and failing the TestEqual... cases.

This can be fixed by changing those lines to:

TestTrue("Executed on the thread pool", (*Expected & ENamedThreads::AnyThread) == ENamedThreads::AnyThread);

But I just wanted to double check that the behaviour was similar on your end to make sure it's not a project setup issue.

Memory Leak in TExpectedPromiseState

Firstly, thanks for creating this library! It's made working with promises in Unreal so much more ergonomic.

While investigating a linear memory leak in our dedicated server, I found what appears to be a memory leak in TExpectedPromiseState, where the CompletionTask is seemingly never freed, meaning every future we create results in an unfreed allocation. The task is created here:

: CompletionTask(TGraphTask<FNullGraphTask>::CreateTask().ConstructAndHold(TStatId(), ENamedThreads::AnyThread))

One can validate it is never destroyed by substituting FNullGraphTask with a derived class that faults the process upon destruction of the task:

	class FFailingNullGraphTask : public FNullGraphTask
	{
	public:
		FFailingNullGraphTask(const TStatId& StatId, ENamedThreads::Type InDesiredThread)
			: FNullGraphTask(StatId, InDesiredThread)
		{
		}

		~FFailingNullGraphTask()
		{
			*(int*)nullptr = 0;
		}
	};

The destructor is never run, nor is the task ever freed which results in the prepared task residing in memory forever. The allocation is fairly small, however over time it very quickly adds up to several megabytes (we make use of promises for all of our HTTP requests).

It can be indepedently verified using the MALLOC_LEAKDETECTION=1 flag, as referenced here.

Unfortunately, I'm not familiar enough with the internals of the library to propose a fix, despite my best efforts ๐Ÿ˜„

If it's useful, here is the unit test I wrote to identify the leak:

struct FAsyncCommandHelper
{
	bool bDispatched = false;
	int CompletePromises = 0;
	int NumPromises = 0;
};

DEFINE_LATENT_AUTOMATION_COMMAND_ONE_PARAMETER(FPromiseLeakTest, FAsyncCommandHelper, AsyncHelper);

bool FPromiseLeakTest::Update()
{
	if (!AsyncHelper.bDispatched)
	{
		FMallocLeakReporter::Get().Start();

		for (int i = 0; i < 256; i++)
		{
			SD::Async([this]
			{
				AsyncHelper.CompletePromises++;
			});

			AsyncHelper.NumPromises++;
		}

		AsyncHelper.bDispatched = true;
	}

	bool bIsDone = AsyncHelper.CompletePromises == AsyncHelper.NumPromises;
	if (bIsDone)
	{
		FMallocLeakReporter::Get().Stop();
		FMallocLeakReporter::Get().WriteReports();
	}

	return bIsDone;
}

Custom Error Types

Curious, how hard would it be to allow for this library to support custom error types? One of the biggest advantages of using a type for error handling vs. traditionally supporting exception based promise handling is that you can actually parse and typecheck your errors.

As it stands, the custom error type is fairly opinionated: requiring an error code, error context code (which must be integers), and only being flexible on the error info as a string.

What would be the most flexible way to support custom error types beyond a string? I took a stab at looking at how hard it would be templatize the error, but it seems like this would actually be a massive change across the board.

Question: Performance with chained promises?

Curious for someone a little more experienced with Unreal, C++, and this library - how are chained promised handled in terms of performance?

If I have something like:

SD:Async([]{
  Blob b = CreateLargeBlob();
  return b;
})
.Then([](const Blob& b)
{
  // Do something with the blob
  return b;
})
.Then([](const Blob& b)
{
  // Do something with the blob
  return b;
})
.Then([](const Blob& b)
{
  // Do something with the blob
  return b;
})
...

Am I unnecessarily creating copies of Blob each time. Scanning the source code, it seems like the answer is possibly no, since from reading the source code, copy elision and the ability to construct a TExpected via a move "should" result in the initial blob being stored directedly in the TExpected wrapper. After this, a similar thing must be happening as blob is passed through the chain?

This is more for curiosity. Is it more performant to have chained handlers take the entire TExpected value from the previous future and pass it through or is unwrapping to a const ref and back enough?

Error when packaging for Linux

In file included from E:/Projects/Unreal/SurvivalGame/Source/SDFutureExtensions/Public\FutureExtensions.h:10:
ParallelExecutor.ExecuteActions:   E:\Projects\Unreal\SurvivalGame\Source\SDFutureExtensions\Public\ExpectedFuture.h(354,3): error: explicitly defaulted default constructor is implicitly deleted [-Werror,-Wdefaulted-function-deleted]
ParallelExecutor.ExecuteActions:                   TExpectedFuture() = default;
ParallelExecutor.ExecuteActions:                   ^
ParallelExecutor.ExecuteActions:   E:\Projects\Unreal\SurvivalGame\Source\SDFutureExtensions\Public\ExpectedFuture.h(341,32): note: default constructor of 'TExpectedFuture<void>' is implicitly deleted because base class 'SD::TExpectedFutureBase' has no default constructor
ParallelExecutor.ExecuteActions:           class TExpectedFuture<void> : public TExpectedFutureBase

Complaining about this constructor on line 354:

template <>
	class TExpectedFuture<void> : public TExpectedFutureBase
	{
	public:
		using ResultType = void;
		using ExpectedResultType = TExpected<void>;

		TExpectedFuture(TFuture<ExpectedResultType>&& Future, FGraphEventRef InPromiseCompletionEventRef,
						const FutureExecutionDetails::FExecutionDetails& InExecutionDetails)
			: TExpectedFutureBase(InPromiseCompletionEventRef)
			, InternalFuture(MoveTemp(Future))
			, ExecutionDetails(InExecutionDetails)
		{}

		TExpectedFuture() = default; // Line 354
		TExpectedFuture(TExpectedFuture<void>&&) = default;

Adding a default constructor to TExpectedFutureBase fixed the error:

	class TExpectedFutureBase
	{
	public:
		TExpectedFutureBase(FGraphEventRef InPromiseCompletionEventRef)
			: PromiseCompletionEventRef(InPromiseCompletionEventRef)
		{
		}

		TExpectedFutureBase() = default;

	protected:
		FGraphEventRef PromiseCompletionEventRef;
	};

Compile Error using Clang in FutureExtensionsTypeTraits.h

I get the following error when compiling using Clang:

T:/p4/project/main/MyGame/Plugins/Runtime/ThirdParty/SDFutureExtensions/Source\SDFutureExtensions/Private/FutureExtensionsTypeTraits.h(161,87): error: implicit instantiation of undefined template 'SD::TExpected<void>'
  auto ContinuationFunctorVoidParamTypeHelper(F Func, int, int, ...) -> decltype(Func(ToExpected()), ToExpected());
                                                                                      ^
T:/p4/project/main/MyGame/Plugins/Runtime/ThirdParty/SDFutureExtensions/Source\SDFutureExtensions/Private/FutureExtensionsTypeTraits.h(10,8): note: template is declared here
 class TExpected;
       ^
1 error generated.

It seems to be something with the forward declarations of the template types, so it seems to be resolved when adding the import of ExpectedResult.h directly in the file FutureExtensionsTypeTraits.h. There's a lot of really complex template work done in this library so I'm not sure if this is the safest or most correct change, so raising viz here for folks!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.