splash-damage / future-extensions

Unreal Engine plugin for async task programming
BSD 3-Clause "New" or "Revised" License
175 stars 26 forks source link

Question/Help: Installation, Usage and Wrapping UE4 delegates #1

Closed gustavomassa closed 3 years ago

gustavomassa commented 4 years ago

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.

ragnarula commented 4 years ago

Thanks for taking the time to check out this plugin @gustavomassa :)

The compiler error reads "You cannot use a TSharedPtr of one mode with a type which inherits TSharedFromThis of another mode."

This leads me to believe the issue you are having is because you are trying to create the shared pointer with the wrong ESPMode. TExpectedPromise extends from TSharedFromThis with ESPMode::ThreadSafe, however the default ESPMode for TSHaredPtr is ESPMode::NotThreadSafe. ESPMode is specified as the second template argument to TSharedPtr.

https://github.com/splash-damage/future-extensions/blob/180feac3fbba2f9fd6bb5f4e6cdcbe5be934a3da/Source/SDFutureExtensions/Public/ExpectedFuture.h#L410

Changing the line in question to TSharedPtr<SD::TExpectedPromise<TArray<FOnlineSessionSearchResult>>, ESPMode::ThreadSafe> Promise = MakeShared<SD::TExpectedPromise<TArray<FOnlineSessionSearchResult>>>(); or auto Promise = MakeShared<SD::TExpectedPromise<TArray<FOnlineSessionSearchResult>>>(); should fix the problem.

gustavomassa commented 4 years ago

Hey @ragnarula thank you for the help!

Using ThreadSafe worked perfectly! auto Promise = MakeShared<SD::TExpectedPromise<bool>, ESPMode::ThreadSafe>();

Sorry to bother you again, I just have 2 more questions:

  1. I'm trying to use the Get() for sync blocking, but it never returns, it blocks the entire thread, the idea is to use something like the Await, waiting for the promise to resolve on a sync way, any ideas?

  2. I'm returning the SD::Error from the promise, it works perfectly, but I'm not able to print the ErrorInfo correctly, it prints the address memory instead of the value.

    if (SessionService)
    {
        SessionService->CreateSession(TEXT("Survival Server"), false, 10, false, false, false, false).Then([this](SD::TExpected<bool> bSuccess) {
            if (bSuccess.IsCompleted() && bSuccess.GetValue() == true)
            {
                UE_LOG(LogTemp, Error, TEXT("Server Running with name: Survival Server"));
            }
            else if (bSuccess.IsError())
            {
                UE_LOG(LogTemp, Error, TEXT("Failed to Create Server Session: %s"), bSuccess.GetError().Get().GetErrorInfo().Get());
            }
        });
    }

I'm returning the error like this:

    auto Promise = MakeShared<SD::TExpectedPromise<bool>, ESPMode::ThreadSafe>();
    if (!SessionInterface.IsValid())
    {
        Promise->SetValue(SD::Error(-1, TEXT("Session invalid!")));
        return Promise->GetFuture();
    }

Thank you!

ragnarula commented 4 years ago

Glad I could help :)

  1. Rather than calling Get() which you rightly pointed out will block until it completes, use Then() to schedule a continuation. Within this continuation you can do any work you would otherwise have done after getting the result of Get(). Be mindful of how you capture references within the lambda passed to Then(), making sure to use TWeakPtr/TWeakObjectPtr where appropriate.

  2. GetErrorInfo() returns a TSharedPtr<FString>. Calling Get() will give you a FString* which is a pointer, this is probably the memory address it's printing. I think you would need to dereference this once to get the FString, then dereference it again to get the TCHAR* (or adding GetErrorInfo()->GetData() should also work I think).

gustavomassa commented 4 years ago

Thank you again!

Be mindful of how you capture references within the lambda passed to Then(), making sure to use TWeakPtr/TWeakObjectPtr where appropriate.

As you mentioned about references passed to lambdas, the rule if the same when passing this? Should I also use TWeakPtr/TWeakObjectPtr when passing "this"? Also, if I'm passing "this", is safe to use engine functions like the GetFirstLocalPlayerController inside the lambda context?

GetErrorInfo() returns a TSharedPtr. Calling Get() will give you a FString which is a pointer, this is probably the memory address it's printing. I think you would need to dereference this once to get the FString, then dereference it again to get the TCHAR (or adding GetErrorInfo()->GetData() should also work I think).

GetErrorInfo()->GetData() does not work :(, here is how I'm getting the error message:

            else if (Result.IsError())
            {
                FString *ErrorMessage = Result.GetError().Get().GetErrorInfo().Get();
                if (ErrorMessage != nullptr)
                {
                    UE_LOG(LogTemp, Error, TEXT("Failed to InitServer: %s"), ErrorMessage->GetCharArray().GetData());
                }
            }

Same for Completed Future Expected values, I have to call GetValue twice because of the TOptional:

            if (Result.IsCompleted() && Result.GetValue().IsSet())
            {
                // Get results and save it
                OnlineSessionSearchResult = Result.GetValue().GetValue();
            }

Sorry about all the questions, but I'm new to UE4, being studying it for about 2 months.