netcode progress, improved handling of missing frames

This commit is contained in:
2025-09-07 18:45:24 +03:00
parent 0d324c0ff0
commit 95de571eb5
9 changed files with 259 additions and 46 deletions

View File

@@ -24,14 +24,17 @@ public static partial class NetworkManager
//var driver = Object.New(typeof(ENetDriver));
//ClientNetworkDriver = null;
NetworkLagDriver driver = Object.New<NetworkLagDriver>();
driver.Lag = 0f;
//INetworkDriver driver = Object.New<ENetDriver>();
INetworkDriver driver = Object.New<NetworkLagDriver>();
if (driver is NetworkLagDriver networkLagDriver)
networkLagDriver.Lag = 50.0f;
ClientNetworkDriver = driver;
client = NetworkPeer.CreatePeer(new NetworkConfig
{
NetworkDriver = driver,
NetworkDriver = (Object)driver,
ConnectionsLimit = MaximumClients,
MessagePoolSize = 2048,
MessageSize = MTU,

View File

@@ -22,14 +22,15 @@ public static partial class NetworkManager
public static bool StartServer(bool listenServer = true)
{
Time.Synchronize();
Cleanup();
ConnectedClients = new List<NetworkConnection>(MaximumClients);
//INetworkDriver driver = Object.New<ENetDriver>();
NetworkLagDriver driver = Object.New<NetworkLagDriver>();
INetworkDriver driver = Object.New<ENetDriver>();
//NetworkLagDriver driver = Object.New<NetworkLagDriver>();
if (driver is NetworkLagDriver networkLagDriver)
networkLagDriver.Lag = 200f;
networkLagDriver.Lag = 50.0f;//200f;
ServerNetworkDriver = driver;

View File

@@ -24,7 +24,7 @@ public enum GameModeMessageType2 : byte
public class PlayerFrame
{
public ulong frame;
public ulong frame => inputState.Frame;
public Float3 position;
public PlayerInputState2 inputState;
public PlayerMovementState movementState;
@@ -40,7 +40,7 @@ public interface IClientInfo
public class World
{
public bool IsServer => this is ServerWorld;
public bool IsClient => !IsServer;
public bool IsClient => this is ClientWorld;
public ulong Frame { get; protected set; }
public ulong ServerFrame { get; protected set; } // Last received frame from server
public float GameTime { get; protected set; } // The join time
@@ -102,17 +102,17 @@ public class World
void OnUpdate()
{
Console.Print("server Update");
//Console.Print("server Update");
}
void OnUpdateLate()
{
Console.Print("server LateUpdate");
//Console.Print("server LateUpdate");
}
void OnFixedUpdate()
{
Console.Print("server FixedUpdate");
//Console.Print("server FixedUpdate");
}
protected void CreateScene(string sceneNamePrefix, string sceneGuid)
@@ -224,14 +224,17 @@ public class World
virtual public bool IsLocalPlayer(uint playerId) => false;
virtual public ulong GetLastProcessedFrame(uint playerId) => 0;
virtual public ulong GetLastReceivedFrame(uint playerId) => 0;
public PlayerFrame GetPlayerFrame(uint playerId, ulong frame)
{
IClientInfo player = GetClient(playerId);
PlayerFrame playerFrame = player.FrameHistory[frame % 120];
if (playerFrame.frame != frame)
return null;
return playerFrame;
if (playerFrame.inputState.Frame == frame || playerFrame.movementState.frame == frame)
return playerFrame;
return null;
}
public bool HasPlayerFrame(uint playerId, ulong frame) => GetPlayerFrame(playerId, frame) != null;
@@ -248,8 +251,9 @@ public class World
IClientInfo player = GetClient(playerId);
PlayerFrame playerFrame = player.FrameHistory[frame % 120];
if (playerFrame.frame == frame)
/*if (playerFrame.frame == frame)
{
var old = playerFrame.inputState with { Frame = inputState.Frame };
playerFrame.inputState = inputState with
{
ViewDelta = playerFrame.inputState.ViewDelta + inputState.ViewDelta,
@@ -258,11 +262,23 @@ public class World
Attack = playerFrame.inputState.Attack || inputState.Attack,
Jump = playerFrame.inputState.Jump || inputState.Jump,
};
if (old != playerFrame.inputState)
old = old;
}
else
else*/
playerFrame.inputState = inputState;
playerFrame.inputState.Frame = frame;
//playerFrame.movementState = movementState;
//playerFrame.frame = frame;
}
virtual public void UpdatePlayerState(uint playerId, ulong frame, PlayerMovementState movementState)
{
IClientInfo player = GetClient(playerId);
PlayerFrame playerFrame = player.FrameHistory[frame % 120];
playerFrame.movementState = movementState;
playerFrame.frame = frame;
playerFrame.movementState.frame = frame;
}
}
@@ -275,12 +291,17 @@ file class ServerWorld : World
public PlayerFrame[] FrameHistory { get; set; } = new PlayerFrame[120];
public ulong LastReceivedFrame = 0;
public ulong LastSentDeltaFrame = 0; // TODO: Accumulate deltas since this frame
public ulong LastProcessedFrame = 0; // In case player is lagged behind, these frames should be caught up during next simulation
public int SendRate = 0; // How many updates the client wants to receive per second
public ClientInfo()
{
for (int i = 0; i < FrameHistory.Length; i++)
{
FrameHistory[i] = new PlayerFrame();
FrameHistory[i].inputState.Frame = ulong.MaxValue;
FrameHistory[i].movementState.frame = ulong.MaxValue;
}
}
}
@@ -306,8 +327,14 @@ file class ServerWorld : World
protected override void OnLateUpdate()
{
Console.Print("server LateFixedUpdate");
//Console.Print("server LateFixedUpdate");
base.OnLateUpdate();
foreach (var client in _clients.Values)
{
// We assume all the scripts have processed the frames during this cycle
client.LastProcessedFrame = client.LastReceivedFrame;
}
}
public bool OnMessage(ref NetworkEvent networkEvent)
@@ -369,9 +396,10 @@ file class ServerWorld : World
{
PlayerInputState2 inputState; //?
ulong receivedFrame = networkEvent.Message.ReadUInt64();
uint reportedPlayerId = networkEvent.Message.ReadUInt32();
inputState.Frame = receivedFrame;
ulong receivedFrame = inputState.Frame = networkEvent.Message.ReadUInt64();
uint playerId = networkEvent.Message.ReadUInt32();
float time = networkEvent.Message.ReadSingle();
float delay = Time.TimeSinceStartup - time;
inputState.ViewDelta.X = networkEvent.Message.ReadSingle();
inputState.ViewDelta.Y = networkEvent.Message.ReadSingle();
inputState.MoveForward = networkEvent.Message.ReadSingle();
@@ -379,22 +407,42 @@ file class ServerWorld : World
inputState.Attack = networkEvent.Message.ReadBoolean();
inputState.Jump = networkEvent.Message.ReadBoolean();
Console.Print($"server receive client frame {receivedFrame}, current server frame {Frame}");
//Console.Print("recv: " + receivedFrame.ToString());
ClientInfo player = GetClient(playerId) as ClientInfo;
if (receivedFrame <= player.LastReceivedFrame)
{
if (receivedFrame < player.LastProcessedFrame)
{
Console.PrintWarning($"Dropping late frame from client: {receivedFrame}, last processed: {player.LastProcessedFrame}");
break;
}
else
{
//Console.PrintWarning($"Out-of-order frame received from client: {receivedFrame}, latest: {player.LastReceivedFrame}");
}
}
player.LastReceivedFrame = Math.Max(receivedFrame, player.LastReceivedFrame);
//if (player.LastProcessedFrame == 0)
// player.LastProcessedFrame = receivedFrame - 1;
//Console.Print($"server receive client frame {receivedFrame}, current server frame {Frame}");
var asdf = receivedFrame;
receivedFrame = Frame;
//receivedFrame = Frame; // TODO: receive all frames from players, simulate all unprocessed frames in next simulation cycle?
if (inputState.MoveForward != 0)
receivedFrame = receivedFrame;
//Console.Print($"delay: {delay * 1000}ms");
// Sanity check
if (GetPlayerInputState(reportedPlayerId, receivedFrame, out var prevInputState))
if (GetPlayerInputState(playerId, receivedFrame, out var prevInputState))
{
Console.PrintWarning($"Duplicate frame received from client: {asdf}");
Console.PrintWarning($"Duplicate frame received from client: {asdf}, delay: {delay*1000}ms");
//break;
}
PlayerMovementState movementState = default;
UpdatePlayerInputState(reportedPlayerId, receivedFrame, inputState, movementState);
UpdatePlayerInputState(playerId, receivedFrame, inputState, default);
break;
}
default:
@@ -454,12 +502,24 @@ file class ServerWorld : World
}
}
public override ulong GetLastProcessedFrame(uint playerId)
{
ClientInfo player = GetClient(playerId) as ClientInfo;
return player.LastProcessedFrame;
}
public override ulong GetLastReceivedFrame(uint playerId)
{
ClientInfo player = GetClient(playerId) as ClientInfo;
return player.LastReceivedFrame;
}
public override void UpdatePlayerInputState(uint playerId, ulong frame, PlayerInputState2 inputState, PlayerMovementState movementState)
{
base.UpdatePlayerInputState(playerId, frame, inputState, movementState);
ClientInfo player = GetClient(playerId) as ClientInfo;
player.LastReceivedFrame = frame; // Dropped frames should be ignored?
//ClientInfo player = GetClient(playerId) as ClientInfo;
//player.LastReceivedFrame = frame; // Dropped frames should be ignored?
}
protected override PlayerActor SpawnPlayer(uint playerId, Float3 position, Vector3 eulerAngles)
@@ -526,7 +586,7 @@ file class ClientWorld : World
public bool Jump;*/
Console.Print($"client send {Frame}");
//Console.Print($"client send {Frame}");
UpdatePlayerInputState(player.PlayerId, Frame, inputState, movementState);
//Console.Print($"cframe: {Frame}");
@@ -534,12 +594,15 @@ file class ClientWorld : World
message.WriteByte((byte)GameModeMessageType2.PlayerInput);
message.WriteUInt64(Frame);
message.WriteUInt32(player.PlayerId);
message.WriteSingle(Time.TimeSinceStartup);
message.WriteVector2(inputState.ViewDelta);
message.WriteSingle(inputState.MoveForward);
message.WriteSingle(inputState.MoveRight);
message.WriteBoolean(inputState.Attack);
message.WriteBoolean(inputState.Jump);
NetworkManager.ClientEndSendMessage(ref message);
//Console.Print("send: " + Frame.ToString());
}
base.OnLateUpdate();
}
@@ -644,6 +707,18 @@ file class ClientWorld : World
public override bool IsLocalPlayer(uint playerId) => playerId == _localPlayerId;
public override ulong GetLastProcessedFrame(uint playerId)
{
//ClientInfo player = GetClient(playerId) as ClientInfo;
return 0;
}
public override ulong GetLastReceivedFrame(uint playerId)
{
//ClientInfo player = GetClient(playerId) as ClientInfo;
return 0;
}
public void OnClientConnected(NetworkConnection connection)
{
uint playerId = connection.ConnectionId;