FindSearch yields no search results even though the delegate finds sessions

I’ve been using [eXi’s sessions guide][1] to try getting multiplayer working. I’ve rigged his code up to a number of Blueprint nodes that I’m using in UMG based off of the Multiplayer Shootout’s session widgets.

I’m pretty sure I can host games alright, but whenever I try to find the games and use resulting SearchResults array to generate a bunch of SessionRow widgets, nothing happens. The messages in the delegate function shows me that any sessions I’ve created have been found and recognized. However, none of my widgets are spawned. I’ve made sure I can spawn these widgets, so it’s not a problem there.

Screenies/snippets below:

Host and Find session code from gameinstance.h:

bool HostSession(TSharedPtr<const FUniqueNetId> UserId, FName SessionName, bool bIsLAN, bool bIsPresence, int32 MaxNumPlayers);
	
	/* Delegate called when session created */
	FOnCreateSessionCompleteDelegate OnCreateSessionCompleteDelegate;
	/* Delegate called when session started */
	FOnStartSessionCompleteDelegate OnStartSessionCompleteDelegate;

	/** Handles to registered delegates for creating/starting a session */
	FDelegateHandle OnCreateSessionCompleteDelegateHandle;
	FDelegateHandle OnStartSessionCompleteDelegateHandle;

	TSharedPtr<class FOnlineSessionSettings> SessionSettings;


	virtual void OnCreateSessionComplete(FName SessionName, bool bWasSuccessful);


	void OnStartOnlineGameComplete(FName SessionName, bool bWasSuccessful);


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

	/** Delegate for searching for sessions */
	FOnFindSessionsCompleteDelegate OnFindSessionsCompleteDelegate;

	/** Handle to registered delegate for searching a session */
	FDelegateHandle OnFindSessionsCompleteDelegateHandle;

	TSharedPtr<class FOnlineSessionSearch> SessionSearch;


	void OnFindSessionsComplete(bool bWasSuccessful);

*snip*

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

	UFUNCTION(BlueprintCallable, Category = "Network|Test")
		TArray<FSessionSearchWrapperStruct> FindOnlineGames(bool isLan);

and the .cpp:

bool UBaseGameInstance_Network::HostSession(TSharedPtr<const FUniqueNetId> UserId, FName SessionName, bool bIsLAN, bool bIsPresence, int32 MaxNumPlayers)
{
	// Get the Online Subsystem to work with
	IOnlineSubsystem* const OnlineSub = IOnlineSubsystem::Get();

	if (OnlineSub)
	{
		// Get the Session Interface, so we can call the "CreateSession" function on it
		IOnlineSessionPtr Sessions = OnlineSub->GetSessionInterface();

		if (Sessions.IsValid() && UserId.IsValid())
		{
			/*
			Fill in all the Session Settings that we want to use.

			There are more with SessionSettings.Set(...);
			For example the Map or the GameMode/Type.
			*/
			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);

			// Set the delegate to the Handle of the SessionInterface
			OnCreateSessionCompleteDelegateHandle = Sessions->AddOnCreateSessionCompleteDelegate_Handle(OnCreateSessionCompleteDelegate);

			// Our delegate should get called when this is complete (doesn't need to be successful!)
			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));

	// Get the OnlineSubsystem so we can get the Session Interface
	IOnlineSubsystem* OnlineSub = IOnlineSubsystem::Get();
	if (OnlineSub)
	{
		// Get the Session Interface to call the StartSession function
		IOnlineSessionPtr Sessions = OnlineSub->GetSessionInterface();

		if (Sessions.IsValid())
		{
			// Clear the SessionComplete delegate handle, since we finished this call
			Sessions->ClearOnCreateSessionCompleteDelegate_Handle(OnCreateSessionCompleteDelegateHandle);
			if (bWasSuccessful)
			{
				// Set the StartSession delegate handle
				OnStartSessionCompleteDelegateHandle = Sessions->AddOnStartSessionCompleteDelegate_Handle(OnStartSessionCompleteDelegate);

				// Our StartSessionComplete delegate should get called after this
				Sessions->StartSession(SessionName);
			}
		}

	}
}

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

	// Get the Online Subsystem so we can get the Session Interface
	IOnlineSubsystem* OnlineSub = IOnlineSubsystem::Get();
	if (OnlineSub)
	{
		// Get the Session Interface to clear the Delegate
		IOnlineSessionPtr Sessions = OnlineSub->GetSessionInterface();
		if (Sessions.IsValid())
		{
			// Clear the delegate, since we are done with this call
			Sessions->ClearOnStartSessionCompleteDelegate_Handle(OnStartSessionCompleteDelegateHandle);
		}
	}

	// If the start was successful, we can open a NewMap if we want. Make sure to use "listen" as a parameter!
	if (bWasSuccessful)
	{
		UGameplayStatics::OpenLevel(GetWorld(), "NewMap", true, "listen");
	}
}


void UBaseGameInstance_Network::FindSessions(TSharedPtr<const FUniqueNetId> UserId, FName SessionName, bool bIsLAN, bool bIsPresence)
{
	// Get the OnlineSubsystem we want to work with
	IOnlineSubsystem* OnlineSub = IOnlineSubsystem::Get();

	if (OnlineSub)
	{
		// Get the SessionInterface from our OnlineSubsystem
		IOnlineSessionPtr Sessions = OnlineSub->GetSessionInterface();

		if (Sessions.IsValid() && UserId.IsValid())
		{
			/*
			Fill in all the SearchSettings, like if we are searching for a LAN game and how many results we want to have!
			*/
			SessionSearch = MakeShareable(new FOnlineSessionSearch());

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

			// We only want to set this Query Setting if "bIsPresence" is true
			if (bIsPresence)
			{
				SessionSearch->QuerySettings.Set(SEARCH_PRESENCE, bIsPresence, EOnlineComparisonOp::Equals);
			}

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

			// Set the Delegate to the Delegate Handle of the FindSession function
			OnFindSessionsCompleteDelegateHandle = Sessions->AddOnFindSessionsCompleteDelegate_Handle(OnFindSessionsCompleteDelegate);

			// Finally call the SessionInterface function. The Delegate gets called once this is finished
			Sessions->FindSessions(*UserId, SearchSettingsRef);
		}
	}
	else
	{
		// If something goes wrong, just call the Delegate Function directly with "false".
		OnFindSessionsComplete(false);
	}
}

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

	// Get OnlineSubsystem we want to work with
	IOnlineSubsystem* const OnlineSub = IOnlineSubsystem::Get();
	if (OnlineSub)
	{
		// Get SessionInterface of the OnlineSubsystem
		IOnlineSessionPtr Sessions = OnlineSub->GetSessionInterface();
		if (Sessions.IsValid())
		{
			// Clear the Delegate handle, since we finished this call
			Sessions->ClearOnFindSessionsCompleteDelegate_Handle(OnFindSessionsCompleteDelegateHandle);

			// Just debugging the Number of Search results. Can be displayed in UMG or something later on
			GEngine->AddOnScreenDebugMessage(-1, 10.f, FColor::Red, FString::Printf(TEXT("Num Search Results: %d"), SessionSearch->SearchResults.Num()));

			// If we have found at least 1 session, we just going to debug them. You could add them to a list of UMG Widgets, like it is done in the BP version!
			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++)
				{
					// OwningUserName is just the SessionName for now. I guess you can create your own Host Settings class and GameSession Class and add a proper GameServer Name here.
					// This is something you can't do in Blueprint for example!
					GEngine->AddOnScreenDebugMessage(-1, 10.f, FColor::Red, FString::Printf(TEXT("Session Number: %d | Sessionname: %s "), SearchIdx + 1, *(SessionSearch->SearchResults[SearchIdx].Session.OwningUserName)));

				}}}}}

*snip*

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);
}

TArray<FSessionSearchWrapperStruct> UBaseGameInstance_Network::FindOnlineGames(bool isLan)
{
	ULocalPlayer* const Player = GetFirstGamePlayer();
	TArray<FSessionSearchWrapperStruct> results;

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

	for (int i = 0; i < SessionSearch->SearchResults.Num(); i++)
	{
		FSessionSearchWrapperStruct tempStruct;
		tempStruct.searchResult = SessionSearch->SearchResults[i];
		results.Add(tempStruct);
	}

	return results;
}

and screenshots of the place where I’m calling the code:

One thing I’ve noticed is that whenever I call this macro, it finishes and then, about five seconds after I get the message that it’s finished, I get the message from the FindSessions delegate listing all the sessions that have been found. I’m not sure if that affects anything since nothing really happens in the delegate function, but that might be some kind of problem.

EDIT: Here’s the FSessionSearchWrapper code. It’s a pretty simple struct but figured I should post it just in case it’s causing problems.

SessionSearchWrapper.h:

#define LOCTEXT_NAMESPACE "sessionsearchwrapper"

#pragma once

#include "Object.h"
#include "SessionSearchWrapper.generated.h"

/**
 * 
 */

USTRUCT(BlueprintType)
struct MEGARISK_API FSessionSearchWrapperStruct
{
	GENERATED_USTRUCT_BODY()

		//UPROPERTY(BlueprintReadWrite, Category = "sessionstuff")
		FOnlineSessionSearchResult searchResult;

	UPROPERTY(BlueprintReadWrite, Category = "sessionstuff")
		FName ServerName;
};

#undef LOCTEXT_NAMESPACE

The .cpp is empty.

bumpbumpbump

Hi MrShmid,

Looking at the tutorial you mentioned, it looks like the FindOnlineGames function doesn’t return a value. This implies that you’ve changed this function for your use case. Can you supply the code you have for it?

Thanks,

Yeah, I’ve got the code for that function on line 186 of the .cpp code I posted. I am returning an array of structs. Speaking of which, I forgot to post the code to those.

It looks kind of weird since the code boxes they have are too small and cause word-wrapping in lots of places.

Hey, look at that! I completely missed it before. Thanks for pointing it out!

So I’m having trouble confirming this from the documentation, but judging from what you’re seeing and what the tutorial says about several of the other OnlineSubsystem functions, I’m guessing that the FindSessions function is asynchronous.

That is, I suspect that FindSessions starts the search for sessions but the search may take an indeterminate amount of time to complete before calling the completion delegate. Until the delegate is called, the list of results may contain no entries, invalid entries, or even misleading entries – you will only know they are correct once the delegate is called. This means that when you are immediately using the list, the results have not yet been returned (which is why you seen no results).

Based on that, I’m betting you need to separate your code into 2 parts: The first part is the code that starts the search. Presumably, it will perform all the setup for the search and finish with a call to FindSessions or FindOnlineGames (or another similar function). The second part, then, would be the main logic for iterating over the results, creating your widgets, and adding them to the display. This would need to happen in the completion delegate (or a function overriding it).

Does this help? I think I can provide an example if you like, I just don’t have it ready yet.

I’ll try this when I can. I’m not familiar with using UMG in C++ so I’ll have to look that up.

You’re certainly welcome to look up the UMG C++ interface – It’s a good thing to know. That said, I don’t think it should be needed.

It looks to me like you’ll need to make void OnFindSessionsComplete(bool bWasSuccessful); into either a BlueprintImplementableEvent or a BlueprintNativeEvent. Then you can override that function in the blueprints to update the UMG UI. Specifically, move the blueprint nodes after your branch into the newly added event and only run them if the bWasSuccessful parameter is true. Does this make more sense? Would an example be helpful?

Ok sure, I’d like an example. This is my first time using BlueprintNativeEvent and it doesn’t like me.

Everything was ok at first, but I realized I needed more parameters since I was running a bunch of extra code. Then I got the following errors:

>D:\Documents\Unreal Projects\MegaRisk\Source\MegaRisk\BaseGameInstance_Network.cpp(31): error C2665: 'TBaseDelegate<TTypeWrapper<void>,bool>::CreateUObject': none of the 2 overloads could convert all the argument types
1>  e:\programs\epic games\4.10\engine\source\runtime\core\public\delegates\DelegateSignatureImpl_Variadics.inl(221): note: could be 'TBaseDelegate<void,bool> TBaseDelegate<TTypeWrapper<void>,bool>::CreateUObject<UBaseGameInstance_Network,>(UserClass *,void (__cdecl UBaseGameInstance_Network::* )(bool) const)'
1>          with
1>          [
1>              UserClass=UBaseGameInstance_Network
1>          ]
1>  e:\programs\epic games\4.10\engine\source\runtime\core\public\delegates\DelegateSignatureImpl_Variadics.inl(216): note: or       'TBaseDelegate<void,bool> TBaseDelegate<TTypeWrapper<void>,bool>::CreateUObject<UBaseGameInstance_Network,>(UserClass *,void (__cdecl UBaseGameInstance_Network::* )(bool))'
1>          with
1>          [
1>              UserClass=UBaseGameInstance_Network
1>          ]
1>  D:\Documents\Unreal Projects\MegaRisk\Source\MegaRisk\BaseGameInstance_Network.cpp(31): note: while trying to match the argument list '(UBaseGameInstance_Network *const , void (__cdecl UBaseGameInstance_Network::* )(TArray<FSessionSearchWrapperStruct,FDefaultAllocator>,bool,UUserWidget *))'

…I’ve traced this back to the line where the OnFindSessionsComplete delegate is bound to the delegate function. Here’s that line:

/** Bind function for FINDING a Session */
	OnFindSessionsCompleteDelegate = FOnFindSessionsCompleteDelegate::CreateUObject(this, &UBaseGameInstance_Network::OnFindSessionsComplete);

For reference, the function now looks like this:

UFUNCTION(BlueprintNativeEvent, Category = "Network")
	void OnFindSessionsComplete(TArray<FSessionSearchWrapperStruct> resultArray, bool bWasSuccessful, UUserWidget* sessionScreen);

and

void UBaseGameInstance_Network::OnFindSessionsComplete_Implementation(TArray<FSessionSearchWrapperStruct> resultArray, bool bWasSuccessful, UUserWidget* sessionScreen)

So that’s where I’m at now. I’m not quite sure how to solve this.

Hi MrShmid,

I’ve put together (and attached) an example project. It only follows eXi’s tutorial up to where it finds the sessions, since that’s where our problem is. Hopefully you’ll find the project self-explanatory, but I’ll try to explain here for completeness.

The important parts are:

  1. The networked game instance (from the tutorial; UNWGameInstance in the code in NWGameInstance.h/.cpp) has OnFindSessionsComplete(…) as a BlueprintNativeEvent. It also has a BlueprintCallable function, GetSessionSearchResults(…) that converts the C++ search results into a Blueprint-friendly list.
  2. NWGameInstance_BP is a Blueprint game instance (based on the C++ networked game instance) that has an Event Dispatcher, OnFindSessionsComplete_Delegate, that is used to notify another Blueprint that the search is complete. It also implements OnFindSessionsComplete(…) to call the event dispatcher with the results of the search.
  3. When finding online games, we first bind a custom event to the NWGameInstanceBP’s event dispatcher (so we’ll see the results when they arrive) and then call Find Online Games. (Note: I put the search code in the level blueprint for the NewMap level. You’ll want to put it in your GUI blueprint instead, but the ideas should all transfer over.)

This way, when the search is complete, it calls the OnFindSessionsComplete(…) function in the NWGameInstance_BP (since it overrides the one from UNWGameInstance). This then passes the proper information on to the proper place – in your case the GUI.

Does this make sense? Are there any parts I can help clarify?

Well all of the C++ makes sense, but I can’t get into the .uproject to look at the BP because it won’t compile. There wasn’t any Intermediate folder in your .zip so all of the .generated.h files broke on me.

Specifically, I get the error ‘#define syntax’ on all of the .generated’s and from there almost every line of code in the GameInstance.cpp has some sort of error.

Hi MrShmid,

Sorry for the confusion! I haven’t uploaded many projects recently and didn’t remember that folder was important.

This is the same project, this time including the Intermediate folder. Does this work better?

Still won’t compile. Seems the .generated’s are still missing. Could you post some screenshots of the GameInstance_BP graph instead?

I was able to put the map in my own project to look at the level BP, but the gameinstance won’t open because it can’t find a parent (even when I added the c++ files, the BP still wouldn’t work).

Certainly! Actually, I’ll just screenshot the level blueprint too, since that’s the only other important piece.

This is the blueprint from the game instance. I selected the Event Dispatcher on the left so you can see the signature in the Details panel on the right.

This is how I start the search: By first binding a custom event (shown next) to the event dispatcher, and then by calling Find Online Games. I do this in BeginPlay in the level, but you’ll want to do it at the appropriate point(s) in your GUI.

This is an example event to handle the search results. It detects whether or not the search was successful, and responds appropriately, either by printing how many sessions were found and iterating over them or by printing that no sessions were found. You’ll want to replace it with the code to display the results using your GUI.

(Note: To create the example event, I first made the Bind Event to OnFindSessionsComplete_Delegate node, and then dragged off the Event pin to a blank area of the Blueprint, and finally selected Add Custom Event… under Add Event. I bet there are other ways to do this, but this is the easiest one I know.)

's final comment solved the problem. If you’re here looking for an answer to a similar/identical problem, you should probably read the entire comment chain anyway.

That made everything work!
I had no idea how delegates(and a couple of other things) worked and the docs didn’t make much sense.

Thanks a lot!