Help understanding GameInstance and GameSession classes in ShooterGame

I’ve got a working system set up that allows me to create, search for, join, and destroy listen servers. This was previously all being done on the GameInstance class. However while dissecting ShooterGame, I noticed that most of that functionality had been broken out to GameSession, with only a few functions on GameInstance to call things. This seemed odd to me given that the session class only exists as long as the session does, but nonetheless I decided to experiment and moved the necessary functions over there.

It compiles and runs fine, but as expected, I ran into a problem. Because the function that actually calls CreateSession() is on GameSession, we first need to get and cast to GameSession in order to call it. But because GameSession doesn’t exist until after CreateSession() is called, the cast returns null and the necessary functions can’t be called. This is of course a paradox, so either Epic’s code on ShooterGame is totally bonked, or (the much more likely probability) I’m missing something fundamental about how ShooterGame’s code actually works.

Can anyone offer any insight here? I’ve scoured a bunch of the other source files and I can’t for the life of me figure out how it can be accessing the session class apparently before it’s created. :thinking:

Here’s a snippet of the relevant code from my own project. Once again this compiles and runs just fine, but as I expected it doesn’t ever make it inside the conditional in the first block of code. Instead it prints out the second of the two custom debug macros, indicating that the session doesn’t yet exist.

(Also please note CreateServer() is just my own implementation, not to be confused with CreateSession() - probably going to rename these)

In E6KGameInstance.cpp

void UE6KGameInstance::CreateServer(FString ServerName, bool bIsPublic)
{
	AE6KGameSession* const GameSession = GetGameSession(); // NOTE: GetGameSession() casts to AE6KGameSession and then returns a pointer to it
	if (GameSession)
	{
		PRINT_STRING(Cyan, "GAME SESSION FOUND!!!") // NOTE: Custom macro
		GameSession->CreateServer(ServerName, bIsPublic);
		return;
	}

	PRINT_STRING(Red, "NO GAME SESSION FOUND!!!") // NOTE: Custom macro
}

Which then calls:

In E6KGameSession.cpp

void AE6KGameSession::CreateServer(FString ServerName, bool bIsPublic)
{
	if (!SessionInterface.IsValid()) return;

	// Sets up initial settings for creating a session
	const ULocalPlayer* LocalPlayer = GetWorld()->GetFirstLocalPlayerFromController();
	SessionSettings = MakeShareable(new FE6KOnlineSessionSettings(ServerName, CheckForLAN(), bIsPublic));

	// Creates new session
	if (LocalPlayer)
	{
		OnCreateSessionCompleteDelegateHandle = SessionInterface->AddOnCreateSessionCompleteDelegate_Handle(OnCreateSessionCompleteDelegate);
		SessionInterface->CreateSession(*LocalPlayer->GetPreferredUniqueNetId(), NAME_GameSession, *SessionSettings);
	}
}

*Before you reply! Once again, I understand why this code isn’t working. I want to understand how and why the equivalent classes on ShooterGame are set up similarly and yet I assume run just fine.

Solved it!

If anyone comes across this thread in the future with the same question, what I think is actually going on here is the engine is quite happy to reference the code in a custom GameSession class even if a session hasn’t been created yet (after all, it’s not like the file doesn’t exist).

But in order for it to do that, you need to actually tell the engine to use your custom GameSession class. Sounds obvious, but an easy thing to overlook when you’re in the weeds with everything else. How exactly you do that is a bit unusual - simple, but like so many things in Unreal, basically not documented anywhere. Open up your custom game MODE class and add the following functions:

In YourGameMode.h

/** Returns game session class to use */
	virtual TSubclassOf<AGameSession> GetGameSessionClass() const override;

In YourGameMode.cpp

#include "GameFramework/YourGameSession.h"

...

TSubclassOf<AGameSession> AYourGameModeBase::GetGameSessionClass() const
{
	return AYourGameSession::StaticClass();
}

The issue with GameSession is that it only exists on the server. So IMO it should only handle session specific server side logic such as registering players with the session when they login. For stuff like create session, join session, etc. the OnlineSession class is the way to go. It is spawned and managed by the GameInstance.