Files
UnrealEngine/Engine/Source/Programs/NetworkPredictionTests/Private/WorldManagerTests_Interpolation.cpp
2025-05-18 13:04:45 +08: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);
}
}
}
}