The GameMode ?Restart command is a pretty handy way to reset or retry a game mode without dumping clients out and forcing them back through a series of loading screens.
GetWorld()->ServerTravel("?Restart");
However I ran into an issue recently where I found that my dedicated server was rejecting incoming connections between ServerTravels due to the Experience not being loaded. My code was rejecting the clients because I had some dependencies on the experience as part of ApproveLogin. This is mostly an issue when using editor based builds because those builds will recompile blue prints leading to some delays in loading. When the clients are rejected it causes them to disconnect and get pushed to your default map which is fairly jarring.
The gist was I wanted a way to to fix this so that clients that were connected don’t get errors during connect and I figured Unreal must have some kind of way to solve this and I came across the code is IpNetDriver::TickDispatch()
const bool bAcceptingConnection = Notify != nullptr && Notify->NotifyAcceptingConnection() == EAcceptConnection::Accept;
Where NotifyAcceptingConnection has the following body
EAcceptConnection::Type UWorld::NotifyAcceptingConnection()
{
check(NetDriver);
if( NetDriver->ServerConnection )
{
// We are a client and we dont welcome incoming connections.
UE_LOG(LogNet, Log, TEXT("NotifyAcceptingConnection: Client refused") );
return EAcceptConnection::Reject;
}
else if( NextURL!=TEXT("") )
{
// Server is switching levels.
UE_LOG(LogNet, Log, TEXT("NotifyAcceptingConnection: Server %s refused"), *GetName() );
return EAcceptConnection::Ignore;
}
else
{
// Server is up and running.
UE_CLOG(!NetDriver->DDoS.CheckLogRestrictions(), LogNet, Verbose, TEXT("NotifyAcceptingConnection: Server %s accept"), *GetName());
return EAcceptConnection::Accept;
}
}
Note the section of code that returns the Ignore is pretty much what I want, I just want to extend it to cover some game mode specific criteria. But the Notify pointer in the IpNetDriver is hard coded to the World as part of the Initialization of the NetDriver.
Looking through the code I stumbled across AOnlineBeacon which actually implements a function that swaps out the Notify in the NetDriver temporarily to allow it to manage the functionality and I used this as a guide to change my application specific GameMode baseclass type to derive off of public FNetworkNotify and implement a function which simply swaps itself out temporarily for the Notify callback class until the experience is done loading and then it swaps it back.
void ACustomGameMode::PauseConnectRequests(bool bPause)
{
// The idea here is to follow a pattern seen by AOnlineBeacon where we temporarily take over
// the duties of the FNetworkNotify callback class that is normally done by UWorld.
//
// This allows us to return a value of Ignore from NotifyAcceptingConnection() until the
// experience has finished loading. This solves issues where we would otherwise reject clients
// during restart cases such as ServerTravel.
if (UWorld* World = GetWorld())
{
if (UNetDriver* NetDriver = World->GetNetDriver())
{
if (bPause)
{
UE_LOG(CustomGameModeLog, Warning, TEXT("Connect Requests Paused."));
NetDriver->Notify = this;
}
else
{
UE_LOG(CustomGameModeLog, Warning, TEXT("Connect Requests Resumed."));
NetDriver->Notify = World;
}
}
else
{
UE_LOG(CustomGameModeLog, Error, TEXT("Cannot pause/unpause connections, no valid net driver!"));
}
}
else
{
UE_LOG(CustomGameModeLog, Error, TEXT("Cannot pause/unpause connections, no valid World!"));
}
}
I ended up pausing the connections from BeginPlay() of my custom game mode if my criteria isn’t met, and then I eventually unpause them when my criteria is finally met.
This worked for my use case and allows the server to ignore incoming connection requests instead of accepting or rejecting them while I wait for some criteria to complete, then I can change the Notify back to allow connection processing to continue.
Hope this is helpful to others.