Files
UnrealEngineUWP/Engine/Source/Programs/NetworkPredictionTests/Private/WorldManagerTests_Interpolation.cpp
Ryan Gerleve bf23eaa8ea Fix behavior, and related ensure, of simulated proxies controlled by the network prediction plugin when there's no autonomous proxy, such as when a client unpossesses a pawn and doesn't immediately possess another one.
Interpolation now uses the maximum of the LatestRecvFrame between autonomous and simulated proxies, instead of just autonomous proxies (if it's ever been set). Allows interpolation to continue while there's no local autonomous proxy.
Enabled the possession test by default now that it passes.

#jira UE-170936
#rb justin.hare
#preflight 6435b0422909bc56c8e76d81

[CL 24996399 by Ryan Gerleve in ue5-main branch]
2023-04-11 15:31:04 -04:00

227 lines
8.3 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "NetPredictionTestDriver.h"
#include "NetPredictionTestWorld.h"
#include "NetworkPredictionDriver.h"
#include "NetworkPredictionProxy.h"
#include <catch2/catch_test_macros.hpp>
namespace UE::Net::Private
{
struct FTestPossession
{
FNetPredictionTestWorld ServerWorld;
FNetPredictionTestWorld ClientWorld;
FNetPredictionTestObject Object1;
FNetPredictionTestObject Object2;
FTestPossession(UNetworkPredictionSettingsObject* Settings)
: ServerWorld(Settings, NM_DedicatedServer)
, ClientWorld(Settings, NM_Client)
, Object1(ServerWorld.WorldManager, ClientWorld.WorldManager, ROLE_AutonomousProxy)
, Object2(ServerWorld.WorldManager, ClientWorld.WorldManager, ROLE_SimulatedProxy)
{
}
void TickServer()
{
ServerWorld.PreTick(FixedFrameRate);
Object1.ServerObject.ReceiveServerRPCs();
Object2.ServerObject.ReceiveServerRPCs();
ServerWorld.Tick(FixedFrameRate);
Object1.ServerSend();
Object2.ServerSend();
}
void TickClient()
{
ClientWorld.PreTick(FixedFrameRate);
Object1.ClientReceive();
Object2.ClientReceive();
ClientWorld.Tick(FixedFrameRate);
}
private:
static constexpr int32 FixedFrameRate = 60;
};
// This case includes a repro for UE-170936, disabled until it's fixed.
TEST_CASE("NetworkPrediction interpolation mode, one simulated proxy", "[possession]")
{
UNetworkPredictionSettingsObject* Settings = NewObject<UNetworkPredictionSettingsObject>();
Settings->Settings.SimulatedProxyNetworkLOD = ENetworkLOD::Interpolated;
FTestPossession Worlds(Settings);
Worlds.Object1.ServerObject.DebugName = TEXT("Obj1Server");
Worlds.Object1.ClientObject.DebugName = TEXT("Obj1Client");
Worlds.Object2.ServerObject.DebugName = TEXT("Obj2Server");
Worlds.Object2.ClientObject.DebugName = TEXT("Obj2Client");
float ExpectedServerAutoCounterValue = 0.0f;
const FNetPredictionTestSyncState* ServerState = nullptr;
// Interpolated objects will buffer FNetworkPredictionSettings::FixedTickInterpolationBufferedMS
// frames worth of updates. For this test with the default of 100ms and fixed 60hz tick that's 6 frames.
constexpr int32 ExpectedInterpolateBufferFrames = 6;
SECTION("One simulated proxy")
{
// Run some frames to allow the client to buffer interpolation data
for (int i = 0; i < 20; ++i)
{
Worlds.TickServer();
ExpectedServerAutoCounterValue += 1.0f;
ServerState = Worlds.Object2.ServerObject.Proxy.ReadSyncState<FNetPredictionTestSyncState>();
CHECK(ServerState->AutoCounter == ExpectedServerAutoCounterValue);
CHECK(ServerState->InputCounter == 0.0f);
Worlds.TickClient();
}
// Client has now buffered and is behind the server by 6 frames.
const FNetPredictionTestSyncState* ClientState = Worlds.Object2.ClientObject.Proxy.ReadSyncState<FNetPredictionTestSyncState>();
CHECK(ClientState->AutoCounter == ExpectedServerAutoCounterValue - ExpectedInterpolateBufferFrames);
CHECK(ClientState->InputCounter == 0.0f);
// Run some more frames
for (int i = 0; i < 20; ++i)
{
Worlds.TickServer();
ExpectedServerAutoCounterValue += 1.0f;
ServerState = Worlds.Object2.ServerObject.Proxy.ReadSyncState<FNetPredictionTestSyncState>();
CHECK(ServerState->AutoCounter == ExpectedServerAutoCounterValue);
CHECK(ServerState->InputCounter == 0.0f);
Worlds.TickClient();
ClientState = Worlds.Object2.ClientObject.Proxy.ReadSyncState<FNetPredictionTestSyncState>();
CHECK(ClientState->AutoCounter == ExpectedServerAutoCounterValue - ExpectedInterpolateBufferFrames);
CHECK(ClientState->InputCounter == 0.0f);
}
}
SECTION("Changing possession")
{
// This section repros UE-170936
const FNetPredictionTestSyncState* ServerState1 = nullptr;
const FNetPredictionTestSyncState* ServerState2 = nullptr;
constexpr int32 ExpectedForwardPredictFrames = 6;
// Run some frames to allow the client to predict ahead (autonomous proxy)
// and buffer interpolation data (simulated proxy)
for (int i = 0; i < 20; ++i)
{
Worlds.TickServer();
ExpectedServerAutoCounterValue += 1.0f;
ServerState1 = Worlds.Object1.ServerObject.Proxy.ReadSyncState<FNetPredictionTestSyncState>();
CHECK(ServerState1->AutoCounter == ExpectedServerAutoCounterValue);
CHECK(ServerState1->InputCounter == 0.0f);
ServerState2 = Worlds.Object2.ServerObject.Proxy.ReadSyncState<FNetPredictionTestSyncState>();
CHECK(ServerState2->AutoCounter == ExpectedServerAutoCounterValue);
CHECK(ServerState2->InputCounter == 0.0f);
Worlds.TickClient();
}
const FNetPredictionTestSyncState* ClientState1 = Worlds.Object1.ClientObject.Proxy.ReadSyncState<FNetPredictionTestSyncState>();
const FNetPredictionTestSyncState* ClientState2 = Worlds.Object2.ClientObject.Proxy.ReadSyncState<FNetPredictionTestSyncState>();
// Auto proxy is ahead of the server by 6 frames.
CHECK(ClientState1->AutoCounter == ExpectedServerAutoCounterValue + ExpectedForwardPredictFrames);
CHECK(ClientState1->InputCounter == 0.0f);
// Simulated proxy is behind the server by 6 frames.
CHECK(ClientState2->AutoCounter == ExpectedServerAutoCounterValue - ExpectedInterpolateBufferFrames);
CHECK(ClientState2->InputCounter == 0.0f);
// "Unpossess" the current autonomous proxy on server & client
Worlds.Object1.ServerObject.Proxy.InitForNetworkRole(ROLE_Authority, false);
Worlds.Object1.ClientObject.Proxy.InitForNetworkRole(ROLE_SimulatedProxy, false);
float ExpectedClientAutoCounterValue = 21.0f;
// Continue running
for (int i = 0; i < 80; ++i)
{
Worlds.TickServer();
ExpectedServerAutoCounterValue += 1.0f;
ServerState1 = Worlds.Object1.ServerObject.Proxy.ReadSyncState<FNetPredictionTestSyncState>();
CHECK(ServerState1->AutoCounter == ExpectedServerAutoCounterValue);
CHECK(ServerState1->InputCounter == 0.0f);
ServerState2 = Worlds.Object2.ServerObject.Proxy.ReadSyncState<FNetPredictionTestSyncState>();
CHECK(ServerState2->AutoCounter == ExpectedServerAutoCounterValue);
CHECK(ServerState2->InputCounter == 0.0f);
Worlds.TickClient();
// Object1 went from autonomous proxy to simulated proxy
ClientState1 = Worlds.Object1.ClientObject.Proxy.ReadSyncState<FNetPredictionTestSyncState>();
// For the first few frames it's buffering interpolation data
if (i < ExpectedInterpolateBufferFrames)
{
CHECK(ExpectedClientAutoCounterValue == 21.0f);
}
else
{
CHECK(ClientState1->AutoCounter == ExpectedServerAutoCounterValue - ExpectedInterpolateBufferFrames);
}
CHECK(ClientState1->InputCounter == 0.0f);
// Object2 is still a simulated proxy
ClientState2 = Worlds.Object2.ClientObject.Proxy.ReadSyncState<FNetPredictionTestSyncState>();
CHECK(ClientState2->AutoCounter == ExpectedServerAutoCounterValue - ExpectedInterpolateBufferFrames);
CHECK(ClientState2->InputCounter == 0.0f);
}
// "Possess" the other object
Worlds.Object2.ServerObject.Proxy.InitForNetworkRole(ROLE_Authority, true);
Worlds.Object2.ClientObject.Proxy.InitForNetworkRole(ROLE_AutonomousProxy, true);
// Continue running
for (int i = 0; i < 80; ++i)
{
Worlds.TickServer();
ExpectedServerAutoCounterValue += 1.0f;
ServerState1 = Worlds.Object1.ServerObject.Proxy.ReadSyncState<FNetPredictionTestSyncState>();
CHECK(ServerState1->AutoCounter == ExpectedServerAutoCounterValue);
CHECK(ServerState1->InputCounter == 0.0f);
ServerState2 = Worlds.Object2.ServerObject.Proxy.ReadSyncState<FNetPredictionTestSyncState>();
CHECK(ServerState2->AutoCounter == ExpectedServerAutoCounterValue);
CHECK(ServerState2->InputCounter == 0.0f);
Worlds.TickClient();
// Object1 is still a simulated proxy
ClientState1 = Worlds.Object1.ClientObject.Proxy.ReadSyncState<FNetPredictionTestSyncState>();
CHECK(ClientState1->AutoCounter == ExpectedServerAutoCounterValue - ExpectedInterpolateBufferFrames);
CHECK(ClientState1->InputCounter == 0.0f);
// Object2 is now an autonomous proxy
ClientState2 = Worlds.Object2.ClientObject.Proxy.ReadSyncState<FNetPredictionTestSyncState>();
if (i == 0)
{
// For the first client tick after "possession" the client hasn't run forward yet
CHECK(ClientState2->AutoCounter == ExpectedServerAutoCounterValue);
}
else
{
// Now the client is a few frames ahead of the server
CHECK(ClientState2->AutoCounter == ExpectedServerAutoCounterValue + ExpectedForwardPredictFrames);
}
CHECK(ClientState2->InputCounter == 0.0f);
}
}
}
}