How to use C++ Session Search Results with Blueprints?

I’m using eXi’s Session Multiplayer guide and I’m having trouble making the FindSessions function work with UMG/Blueprints. I made some SessionList and SessionsRow UMG widgets like in the Multiplayer Shootout and was going to use them along with the C++ session functions. Specifically, I was going to grab every session found in FindSessions and create a SessionRow based around it. However, I have one major problem:

It seems like I can’t use the FOnlineSessionSearchResult objects used in FindSessions in a UFunction. Every time I try to use that class as a function type or a parameter, I get this error when I compile:

1>  D:/Documents/Unreal Projects/MegaRisk/Source/MegaRisk/BaseGameInstance_Network.h(130) : Unrecognized type 'FOnlineSessionSearchResult' - type must be a UCLASS, USTRUCT or UENUM
1>Error : Failed to generate code for MegaRiskEditor - error code: OtherCompilationError (5)
1>  UnrealHeaderTool failed for target 'MegaRiskEditor' (platform: Win64, module info: D:\Documents\Unreal Projects\MegaRisk\Intermediate\Build\Win64\MegaRiskEditor\Development\UnrealHeaderTool.manifest).
1>C:\Program Files (x86)\MSBuild\Microsoft.Cpp\v4.0\V140\Microsoft.MakeFile.Targets(37,5): error MSB3073: The command ""E:\Programs\Epic Games\4.10\Engine\Build\BatchFiles\Build.bat" MegaRiskEditor Win64 Development "D:\Documents\Unreal Projects\MegaRisk\MegaRisk.uproject" -rocket -waitmutex" exited with code -1.

So, I was wondering if there’s any way to use C++ session functions with Blueprints. I’ve been searching through the ShooterGame demo but it looks like they use C++ UI, which is something I’m trying to avoid.

Is there anyway to use these things with Blueprints/UMG at all?

Here’s some of the code:

BaseGameInstance_Network.h

//Host Session Stuff

TSharedPtr<class FOnlineSessionSettings> SessionSettings;

void FindSessions(TSharedPtr<const FUniqueNetId> UserId, FName SessionName, bool bIsLAN, bool bIsPresence);

	FOnFindSessionsCompleteDelegate OnFindSessionsCompleteDelegate;

	FDelegateHandle OnFindSessionsCompleteDelegateHandle;

	TSharedPtr<class FOnlineSessionSearch> SessionSearch;

	void OnFindSessionsComplete(bool bWasSuccessful);

//Join Session and Delete Session stuff, all exactly as it is in the tutorial

public:

//Blueprint implementation
UFUNCTION(BlueprintCallable, Category = "Network|Test")
	void StartOnlineGame(FName sessionName, bool isLan, int32 maxPlayerNum);

	UFUNCTION(BlueprintCallable, Category = "Network|Test")
		void FindOnlineGames(TArray<FOnlineSessionSearchResult> &results, bool isLan);

	UFUNCTION(BlueprintCallable, Category = "Network|Test")
		void JoinOnlineGame(FName sessionName);

	UFUNCTION(BlueprintCallable, Category = "Network|Test")
		void DestroySessionAndLeaveGame(FName sessionName);

BaseGameInstance_Network.cpp

//Everything but the UFunctions are same as in tut, but I'll put the HostSession and FindSession stuff here anyway

bool UBaseGameInstance_Network::HostSession(TSharedPtr<const FUniqueNetId> UserId, FName SessionName, bool bIsLAN, bool bIsPresence, int32 MaxNumPlayers) {
	IOnlineSubsystem* const OnlineSub = IOnlineSubsystem::Get();

	if (OnlineSub){
		IOnlineSessionPtr Sessions = OnlineSub->GetSessionInterface();

		if (Sessions.IsValid() && UserId.IsValid()){
			SessionSettings = MakeShareable(new FOnlineSessionSettings());

			SessionSettings->bIsLANMatch = bIsLAN;
			SessionSettings->bUsesPresence = bIsPresence;
			SessionSettings->NumPublicConnections = MaxNumPlayers;
			SessionSettings->NumPrivateConnections = 0;
			SessionSettings->bAllowInvites = true;
			SessionSettings->bAllowJoinInProgress = true;
			SessionSettings->bShouldAdvertise = true;
			SessionSettings->bAllowJoinViaPresence = true;
			SessionSettings->bAllowJoinViaPresenceFriendsOnly = false;

			SessionSettings->Set(SETTING_MAPNAME, FString("NewMap"), EOnlineDataAdvertisementType::ViaOnlineService);

			OnCreateSessionCompleteDelegateHandle = Sessions->AddOnCreateSessionCompleteDelegate_Handle(OnCreateSessionCompleteDelegate);

			return Sessions->CreateSession(*UserId, SessionName, *SessionSettings);
		}
	}
	else
	{    GEngine->AddOnScreenDebugMessage(-1, 10.f, FColor::Red, TEXT("No OnlineSubsytem found!"));  	}

	return false;
}

void UBaseGameInstance_Network::OnCreateSessionComplete(FName SessionName, bool bWasSuccessful){
	GEngine->AddOnScreenDebugMessage(-1, 10.f, FColor::Red, FString::Printf(TEXT("OnCreateSessionComplete %s, %d"), *SessionName.ToString(), bWasSuccessful));

	IOnlineSubsystem* OnlineSub = IOnlineSubsystem::Get();
	if (OnlineSub){
		IOnlineSessionPtr Sessions = OnlineSub->GetSessionInterface();

		if (Sessions.IsValid()){
			Sessions->ClearOnCreateSessionCompleteDelegate_Handle(OnCreateSessionCompleteDelegateHandle);
			if (bWasSuccessful){
				OnStartSessionCompleteDelegateHandle = Sessions->AddOnStartSessionCompleteDelegate_Handle(OnStartSessionCompleteDelegate);

				Sessions->StartSession(SessionName);
} //4 more lines of }'s here

void UBaseGameInstance_Network::OnStartOnlineGameComplete(FName SessionName, bool bWasSuccessful)
{
	GEngine->AddOnScreenDebugMessage(-1, 10.f, FColor::Red, FString::Printf(TEXT("OnStartSessionComplete %s, %d"), *SessionName.ToString(), bWasSuccessful));

	IOnlineSubsystem* OnlineSub = IOnlineSubsystem::Get();
	if (OnlineSub){
		IOnlineSessionPtr Sessions = OnlineSub->GetSessionInterface();
		if (Sessions.IsValid()){
			Sessions->ClearOnStartSessionCompleteDelegate_Handle(OnStartSessionCompleteDelegateHandle);
		}
	}

	if (bWasSuccessful)
	{   UGameplayStatics::OpenLevel(GetWorld(), "NewMap", true, "listen");   }
}


void UBaseGameInstance_Network::FindSessions(TSharedPtr<const FUniqueNetId> UserId, FName SessionName, bool bIsLAN, bool bIsPresence)
{
	IOnlineSubsystem* OnlineSub = IOnlineSubsystem::Get();

	if (OnlineSub){
		IOnlineSessionPtr Sessions = OnlineSub->GetSessionInterface();

		if (Sessions.IsValid() && UserId.IsValid()){
			SessionSearch = MakeShareable(new FOnlineSessionSearch());

			SessionSearch->bIsLanQuery = bIsLAN;
			SessionSearch->MaxSearchResults = 20;
			SessionSearch->PingBucketSize = 50;

			if (bIsPresence)
			{   SessionSearch->QuerySettings.Set(SEARCH_PRESENCE, bIsPresence, EOnlineComparisonOp::Equals);   }

			TSharedRef<FOnlineSessionSearch> SearchSettingsRef = SessionSearch.ToSharedRef();

			OnFindSessionsCompleteDelegateHandle = Sessions->AddOnFindSessionsCompleteDelegate_Handle(OnFindSessionsCompleteDelegate);

			Sessions->FindSessions(*UserId, SearchSettingsRef);
		}
	}
	else
	{   OnFindSessionsComplete(false);   }
}

void UBaseGameInstance_Network::OnFindSessionsComplete(bool bWasSuccessful){
	GEngine->AddOnScreenDebugMessage(-1, 10.f, FColor::Red, FString::Printf(TEXT("OFindSessionsComplete bSuccess: %d"), bWasSuccessful));

	IOnlineSubsystem* const OnlineSub = IOnlineSubsystem::Get();
	if (OnlineSub){
		IOnlineSessionPtr Sessions = OnlineSub->GetSessionInterface();
		if (Sessions.IsValid()){
			Sessions->ClearOnFindSessionsCompleteDelegate_Handle(OnFindSessionsCompleteDelegateHandle);

			GEngine->AddOnScreenDebugMessage(-1, 10.f, FColor::Red, FString::Printf(TEXT("Num Search Results: %d"), SessionSearch->SearchResults.Num()));

			if (SessionSearch->SearchResults.Num() > 0){
				// "SessionSearch->SearchResults" is an Array that contains all the information. You can access the Session in this and get a lot of information.
				// This can be customized later on with your own classes to add more information that can be set and displayed
				for (int32 SearchIdx = 0; SearchIdx < SessionSearch->SearchResults.Num(); SearchIdx++){
					GEngine->AddOnScreenDebugMessage(-1, 10.f, FColor::Red, FString::Printf(TEXT("Session Number: %d | Sessionname: %s "), SearchIdx + 1, *(SessionSearch->SearchResults[SearchIdx].Session.OwningUserName)));
} //Many more }'s here


//Blueprint implementation
void UBaseGameInstance_Network::StartOnlineGame(FName sessionName, bool isLan, int32 maxPlayerNum){
	// Creating a local player where we can get the UserID from
	ULocalPlayer* const Player = GetFirstGamePlayer();

	// Call our custom HostSession function. GameSessionName is a GameInstance variable
	HostSession(Player->GetPreferredUniqueNetId(), GameSessionName, isLan, true, maxPlayerNum);}

void UBaseGameInstance_Network::FindOnlineGames(TArray<FOnlineSessionSearchResult> &results, bool isLan){
	ULocalPlayer* const Player = GetFirstGamePlayer();

	FindSessions(Player->GetPreferredUniqueNetId(), GameSessionName, isLan, true);

	results = SessionSearch->SearchResults;
}

bump, multiplayer production is effectively stalled until I figure this out

So… there’s nothing I can do about this?

Yes you can make them work, I have a multiplayer game and they work. That error comes up when you try to use the FOnlineSessionSearchResult outside of C++, you should not make a UPROPERTY or UFUNCTION with it. You need to handle it all through C++.

I’m talking about this line for example:

 UFUNCTION(BlueprintCallable, Category = "Network|Test")
         void FindOnlineGames(TArray<FOnlineSessionSearchResult> &results, bool isLan);

You cannot do this, since FOnlineSessionSearchResult cannot be referred in blueprint

They actually have a wrapper struct so they can use it in blueprint

 USTRUCT(BlueprintType)
 struct FBlueprintSessionResult
 {
     GENERATED_USTRUCT_BODY()
 
     FOnlineSessionSearchResult OnlineResult;
 };

Note that this struct is only usable in blueprint and not in your code since the struct is defined in the engine files.

So if you wanna have c++ blueprint callable functions that implicitly handle FOnlineSessionSearchResult on blueprint you will have to create your own wrapper struct for it, so your functions can handle them in c++ but they are exposed in blueprint.

You can do something like this:

class.h:

USTRUCT(BlueprintType)
struct FMyCustomStruct
{
	GENERATED_USTRUCT_BODY()

	FOnlineSessionSearchResult mySearchResult;
};


class
{


public:
    UFUNCTION(BlueprintCallable,Category "Sessions")
    void joinGame(FMyCustomStruct searchResult);


}

class.cpp:

void class::joinGame(FMyCustomStruct searchResult)
{
    //Do something with the FOnlineSessionSearchResult
   searchResult.mySearchResult;


}

Hope I was helpful :slight_smile: !

Ah, a wrapper object! This seems to have solved my problem for now.

You should include this header:

#include "FindSessionsCallbackProxy.h"

which actually contains the struct FBlueprintSessionResult

 USTRUCT(BlueprintType)
  struct FBlueprintSessionResult
  {
      GENERATED_USTRUCT_BODY()
  
      FOnlineSessionSearchResult OnlineResult;
  };

and then use the struct in your methods (also passing the SearchResult from a blueprint).

Creating your own struct is useless, it is not a FBlueprintSessionResult, so you couldn’t pass a SearchResult from a blueprint.