Best practices and worfklow for testing network play?

I know I can run multiple PIE windows for testing network play, and this appears to be the fastest/easiest way to spin up a testing session when I’m working on network functionality. But I seem to get different results here versus what I get when packaging a standalone build and then running multiple instances of that, which is worrisome.

There are also some annoying workflow issues with the multi-PIE approach, in particular:

  • UE_LOG calls all go to the same output log so it’s impossible to tell who’s reporting what
  • GEngine->AddOnscreenDebugMessage calls all render on the first PIE instance regardless of who actually invoked them

So it’s making it kinda difficult to keep track of the individual states of my host and client(s).

Is there a better way to handle this in the multi-PIE case? Because packaging builds for network testing makes for a pretty slow turnaround and I’d like to minimize the frequency with which I need to do that.

(Obviously I would do final testing with standalone builds on physically distinct machines; I’m just trying to identify a good way to work day-to-day that allows quick iteration time and where I can validate network functionality on my own without needing to rope in other team members all the time.)

EDIT: Uggggggh topic title typo and no apparent ability to edit it :frowning:

It’s worth giving this a look:
Engine/Plugins/NetcodeUnitTest/NUTUnrealEngine4/Source/NUTUnrealEngine4/Classes/UnitTests/FTextCrash.h

This fires up a server instance and runs tests on the client. Although it appears to be broken in 4.17 and 4.18, you can take a crack at fixing it.

I ended up defining a new log macro:


#define UE_LOG_NET(CategoryName, Verbosity, Format, ...) \
{ \
    FString message = FString::Printf(Format, ##__VA_ARGS__); \
    FString netMode; \
    switch(GetWorld()->GetNetMode()) \
    { \
        case NM_Standalone: { netMode = TEXT("Standalone"); break; } \
        case NM_ListenServer: { netMode = TEXT("ListenServer"); break; } \
        case NM_DedicatedServer: { netMode = TEXT("DedicatedServer"); break; } \
        case NM_Client: { netMode = TEXT("Client"); break; } \
        default: { netMode = TEXT("Unknown"); break; } \
    } \
    UE_LOG(CategoryName, Verbosity, TEXT("%s] %s"), *netMode, *message); \
}


So if for example the client does this:


UE_LOG_NET(MyGame, Log, TEXT("Started the game!"));

What prints to the log looks like this:


[Client] Started the game!

No doubt this could be implemented more elegantly (the switch is gross) but it gets the job done, at least for your basic one-server/one-client setup. So far I haven’t needed to worry about identifying separate clients uniquely.