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

@@ -6,8 +6,8 @@
"UseVSync": false, "UseVSync": false,
"AAQuality": 3, "AAQuality": 3,
"SSRQuality": 3, "SSRQuality": 3,
"SSAOQuality": 0, "SSAOQuality": 3,
"VolumetricFogQuality": 3, "VolumetricFogQuality": 0,
"ShadowsQuality": 3, "ShadowsQuality": 3,
"ShadowMapsQuality": 3, "ShadowMapsQuality": 3,
"AllowCSMBlending": true, "AllowCSMBlending": true,

View File

@@ -1,7 +1,7 @@
{ {
"ID": "8ec53dba4c238bfbea1d62922e612a4d", "ID": "8ec53dba4c238bfbea1d62922e612a4d",
"TypeName": "FlaxEditor.Content.Settings.InputSettings", "TypeName": "FlaxEditor.Content.Settings.InputSettings",
"EngineBuild": 6335, "EngineBuild": 6705,
"Data": { "Data": {
"ActionMappings": [ "ActionMappings": [
{ {
@@ -84,6 +84,8 @@
"Gamepad": 0, "Gamepad": 0,
"PositiveButton": 0, "PositiveButton": 0,
"NegativeButton": 0, "NegativeButton": 0,
"GamepadPositiveButton": 0,
"GamepadNegativeButton": 0,
"DeadZone": 0.0, "DeadZone": 0.0,
"Sensitivity": 0.11, "Sensitivity": 0.11,
"Gravity": 1.0, "Gravity": 1.0,
@@ -96,6 +98,8 @@
"Gamepad": 0, "Gamepad": 0,
"PositiveButton": 0, "PositiveButton": 0,
"NegativeButton": 0, "NegativeButton": 0,
"GamepadPositiveButton": 0,
"GamepadNegativeButton": 0,
"DeadZone": 0.0, "DeadZone": 0.0,
"Sensitivity": 0.11, "Sensitivity": 0.11,
"Gravity": 1.0, "Gravity": 1.0,
@@ -108,9 +112,11 @@
"Gamepad": 0, "Gamepad": 0,
"PositiveButton": 68, "PositiveButton": 68,
"NegativeButton": 65, "NegativeButton": 65,
"GamepadPositiveButton": 0,
"GamepadNegativeButton": 0,
"DeadZone": 0.01, "DeadZone": 0.01,
"Sensitivity": 5.0, "Sensitivity": 0.0,
"Gravity": 5.0, "Gravity": 0.0,
"Scale": 1.0, "Scale": 1.0,
"Snap": true "Snap": true
}, },
@@ -120,9 +126,11 @@
"Gamepad": 0, "Gamepad": 0,
"PositiveButton": 87, "PositiveButton": 87,
"NegativeButton": 83, "NegativeButton": 83,
"GamepadPositiveButton": 0,
"GamepadNegativeButton": 0,
"DeadZone": 0.01, "DeadZone": 0.01,
"Sensitivity": 5.0, "Sensitivity": 0.0,
"Gravity": 5.0, "Gravity": 0.0,
"Scale": 1.0, "Scale": 1.0,
"Snap": true "Snap": true
}, },
@@ -132,6 +140,8 @@
"Gamepad": 0, "Gamepad": 0,
"PositiveButton": 39, "PositiveButton": 39,
"NegativeButton": 37, "NegativeButton": 37,
"GamepadPositiveButton": 0,
"GamepadNegativeButton": 0,
"DeadZone": 0.0, "DeadZone": 0.0,
"Sensitivity": 1.0, "Sensitivity": 1.0,
"Gravity": 800.0, "Gravity": 800.0,
@@ -144,6 +154,8 @@
"Gamepad": 0, "Gamepad": 0,
"PositiveButton": 38, "PositiveButton": 38,
"NegativeButton": 40, "NegativeButton": 40,
"GamepadPositiveButton": 0,
"GamepadNegativeButton": 0,
"DeadZone": 0.0, "DeadZone": 0.0,
"Sensitivity": 1.0, "Sensitivity": 1.0,
"Gravity": 800.0, "Gravity": 800.0,
@@ -156,6 +168,8 @@
"Gamepad": 0, "Gamepad": 0,
"PositiveButton": 0, "PositiveButton": 0,
"NegativeButton": 0, "NegativeButton": 0,
"GamepadPositiveButton": 0,
"GamepadNegativeButton": 0,
"DeadZone": 0.19, "DeadZone": 0.19,
"Sensitivity": 1.0, "Sensitivity": 1.0,
"Gravity": 1.0, "Gravity": 1.0,
@@ -168,6 +182,8 @@
"Gamepad": 0, "Gamepad": 0,
"PositiveButton": 0, "PositiveButton": 0,
"NegativeButton": 0, "NegativeButton": 0,
"GamepadPositiveButton": 0,
"GamepadNegativeButton": 0,
"DeadZone": 0.19, "DeadZone": 0.19,
"Sensitivity": 1.0, "Sensitivity": 1.0,
"Gravity": 1.0, "Gravity": 1.0,
@@ -180,6 +196,8 @@
"Gamepad": 0, "Gamepad": 0,
"PositiveButton": 0, "PositiveButton": 0,
"NegativeButton": 0, "NegativeButton": 0,
"GamepadPositiveButton": 0,
"GamepadNegativeButton": 0,
"DeadZone": 0.19, "DeadZone": 0.19,
"Sensitivity": 1.0, "Sensitivity": 1.0,
"Gravity": 1.0, "Gravity": 1.0,
@@ -192,6 +210,8 @@
"Gamepad": 0, "Gamepad": 0,
"PositiveButton": 0, "PositiveButton": 0,
"NegativeButton": 0, "NegativeButton": 0,
"GamepadPositiveButton": 0,
"GamepadNegativeButton": 0,
"DeadZone": 0.19, "DeadZone": 0.19,
"Sensitivity": 1.0, "Sensitivity": 1.0,
"Gravity": 1.0, "Gravity": 1.0,

View File

@@ -1,7 +1,7 @@
{ {
"ID": "4bd8a4cc460399b5f1975fbe0a668e3f", "ID": "4bd8a4cc460399b5f1975fbe0a668e3f",
"TypeName": "FlaxEditor.Content.Settings.PhysicsSettings", "TypeName": "FlaxEditor.Content.Settings.PhysicsSettings",
"EngineBuild": 6510, "EngineBuild": 6705,
"Data": { "Data": {
"DefaultGravity": { "DefaultGravity": {
"X": 0.0, "X": 0.0,
@@ -13,6 +13,7 @@
"RestitutionCombineMode": 0, "RestitutionCombineMode": 0,
"DisableCCD": false, "DisableCCD": false,
"BroadPhaseType": 3, "BroadPhaseType": 3,
"EnableEnhancedDeterminism": false,
"SolverType": 0, "SolverType": 0,
"MaxDeltaTime": 0.1, "MaxDeltaTime": 0.1,
"EnableSubstepping": false, "EnableSubstepping": false,

View File

@@ -137,7 +137,7 @@ public class CameraRender : PostProcessEffect
private bool lastRescale = false; private bool lastRescale = false;
public override void OnUpdate() public override void OnUpdate()
{ {
#if FLAX_EDITOR /*#if FLAX_EDITOR
if (Input.GetKeyDown(KeyboardKeys.F7)) if (Input.GetKeyDown(KeyboardKeys.F7))
{ {
PhysicsSettings physicsSettings = GameSettings.Load<PhysicsSettings>(); PhysicsSettings physicsSettings = GameSettings.Load<PhysicsSettings>();
@@ -145,7 +145,7 @@ public class CameraRender : PostProcessEffect
GameSettings.Save(physicsSettings); GameSettings.Save(physicsSettings);
//GameSettings.Apply(); //GameSettings.Apply();
} }
#endif #endif*/
if (lastEnabled != camera.IsActive) if (lastEnabled != camera.IsActive)
{ {

View File

@@ -24,14 +24,17 @@ public static partial class NetworkManager
//var driver = Object.New(typeof(ENetDriver)); //var driver = Object.New(typeof(ENetDriver));
//ClientNetworkDriver = null; //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; ClientNetworkDriver = driver;
client = NetworkPeer.CreatePeer(new NetworkConfig client = NetworkPeer.CreatePeer(new NetworkConfig
{ {
NetworkDriver = driver, NetworkDriver = (Object)driver,
ConnectionsLimit = MaximumClients, ConnectionsLimit = MaximumClients,
MessagePoolSize = 2048, MessagePoolSize = 2048,
MessageSize = MTU, MessageSize = MTU,

View File

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

View File

@@ -24,7 +24,7 @@ public enum GameModeMessageType2 : byte
public class PlayerFrame public class PlayerFrame
{ {
public ulong frame; public ulong frame => inputState.Frame;
public Float3 position; public Float3 position;
public PlayerInputState2 inputState; public PlayerInputState2 inputState;
public PlayerMovementState movementState; public PlayerMovementState movementState;
@@ -40,7 +40,7 @@ public interface IClientInfo
public class World public class World
{ {
public bool IsServer => this is ServerWorld; public bool IsServer => this is ServerWorld;
public bool IsClient => !IsServer; public bool IsClient => this is ClientWorld;
public ulong Frame { get; protected set; } public ulong Frame { get; protected set; }
public ulong ServerFrame { get; protected set; } // Last received frame from server public ulong ServerFrame { get; protected set; } // Last received frame from server
public float GameTime { get; protected set; } // The join time public float GameTime { get; protected set; } // The join time
@@ -102,17 +102,17 @@ public class World
void OnUpdate() void OnUpdate()
{ {
Console.Print("server Update"); //Console.Print("server Update");
} }
void OnUpdateLate() void OnUpdateLate()
{ {
Console.Print("server LateUpdate"); //Console.Print("server LateUpdate");
} }
void OnFixedUpdate() void OnFixedUpdate()
{ {
Console.Print("server FixedUpdate"); //Console.Print("server FixedUpdate");
} }
protected void CreateScene(string sceneNamePrefix, string sceneGuid) protected void CreateScene(string sceneNamePrefix, string sceneGuid)
@@ -224,14 +224,17 @@ public class World
virtual public bool IsLocalPlayer(uint playerId) => false; 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) public PlayerFrame GetPlayerFrame(uint playerId, ulong frame)
{ {
IClientInfo player = GetClient(playerId); IClientInfo player = GetClient(playerId);
PlayerFrame playerFrame = player.FrameHistory[frame % 120]; PlayerFrame playerFrame = player.FrameHistory[frame % 120];
if (playerFrame.frame != frame) if (playerFrame.inputState.Frame == frame || playerFrame.movementState.frame == frame)
return null; return playerFrame;
return null;
return playerFrame;
} }
public bool HasPlayerFrame(uint playerId, ulong frame) => GetPlayerFrame(playerId, frame) != null; public bool HasPlayerFrame(uint playerId, ulong frame) => GetPlayerFrame(playerId, frame) != null;
@@ -248,8 +251,9 @@ public class World
IClientInfo player = GetClient(playerId); IClientInfo player = GetClient(playerId);
PlayerFrame playerFrame = player.FrameHistory[frame % 120]; 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 playerFrame.inputState = inputState with
{ {
ViewDelta = playerFrame.inputState.ViewDelta + inputState.ViewDelta, ViewDelta = playerFrame.inputState.ViewDelta + inputState.ViewDelta,
@@ -258,11 +262,23 @@ public class World
Attack = playerFrame.inputState.Attack || inputState.Attack, Attack = playerFrame.inputState.Attack || inputState.Attack,
Jump = playerFrame.inputState.Jump || inputState.Jump, Jump = playerFrame.inputState.Jump || inputState.Jump,
}; };
if (old != playerFrame.inputState)
old = old;
} }
else else*/
playerFrame.inputState = inputState; 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.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 PlayerFrame[] FrameHistory { get; set; } = new PlayerFrame[120];
public ulong LastReceivedFrame = 0; public ulong LastReceivedFrame = 0;
public ulong LastSentDeltaFrame = 0; // TODO: Accumulate deltas since this frame 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 int SendRate = 0; // How many updates the client wants to receive per second
public ClientInfo() public ClientInfo()
{ {
for (int i = 0; i < FrameHistory.Length; i++) for (int i = 0; i < FrameHistory.Length; i++)
{
FrameHistory[i] = new PlayerFrame(); 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() protected override void OnLateUpdate()
{ {
Console.Print("server LateFixedUpdate"); //Console.Print("server LateFixedUpdate");
base.OnLateUpdate(); 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) public bool OnMessage(ref NetworkEvent networkEvent)
@@ -369,9 +396,10 @@ file class ServerWorld : World
{ {
PlayerInputState2 inputState; //? PlayerInputState2 inputState; //?
ulong receivedFrame = networkEvent.Message.ReadUInt64(); ulong receivedFrame = inputState.Frame = networkEvent.Message.ReadUInt64();
uint reportedPlayerId = networkEvent.Message.ReadUInt32(); uint playerId = networkEvent.Message.ReadUInt32();
inputState.Frame = receivedFrame; float time = networkEvent.Message.ReadSingle();
float delay = Time.TimeSinceStartup - time;
inputState.ViewDelta.X = networkEvent.Message.ReadSingle(); inputState.ViewDelta.X = networkEvent.Message.ReadSingle();
inputState.ViewDelta.Y = networkEvent.Message.ReadSingle(); inputState.ViewDelta.Y = networkEvent.Message.ReadSingle();
inputState.MoveForward = networkEvent.Message.ReadSingle(); inputState.MoveForward = networkEvent.Message.ReadSingle();
@@ -379,22 +407,42 @@ file class ServerWorld : World
inputState.Attack = networkEvent.Message.ReadBoolean(); inputState.Attack = networkEvent.Message.ReadBoolean();
inputState.Jump = 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; 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) if (inputState.MoveForward != 0)
receivedFrame = receivedFrame; receivedFrame = receivedFrame;
//Console.Print($"delay: {delay * 1000}ms");
// Sanity check // 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(playerId, receivedFrame, inputState, default);
UpdatePlayerInputState(reportedPlayerId, receivedFrame, inputState, movementState);
break; break;
} }
default: 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) public override void UpdatePlayerInputState(uint playerId, ulong frame, PlayerInputState2 inputState, PlayerMovementState movementState)
{ {
base.UpdatePlayerInputState(playerId, frame, inputState, movementState); base.UpdatePlayerInputState(playerId, frame, inputState, movementState);
ClientInfo player = GetClient(playerId) as ClientInfo; //ClientInfo player = GetClient(playerId) as ClientInfo;
player.LastReceivedFrame = frame; // Dropped frames should be ignored? //player.LastReceivedFrame = frame; // Dropped frames should be ignored?
} }
protected override PlayerActor SpawnPlayer(uint playerId, Float3 position, Vector3 eulerAngles) protected override PlayerActor SpawnPlayer(uint playerId, Float3 position, Vector3 eulerAngles)
@@ -526,7 +586,7 @@ file class ClientWorld : World
public bool Jump;*/ public bool Jump;*/
Console.Print($"client send {Frame}"); //Console.Print($"client send {Frame}");
UpdatePlayerInputState(player.PlayerId, Frame, inputState, movementState); UpdatePlayerInputState(player.PlayerId, Frame, inputState, movementState);
//Console.Print($"cframe: {Frame}"); //Console.Print($"cframe: {Frame}");
@@ -534,12 +594,15 @@ file class ClientWorld : World
message.WriteByte((byte)GameModeMessageType2.PlayerInput); message.WriteByte((byte)GameModeMessageType2.PlayerInput);
message.WriteUInt64(Frame); message.WriteUInt64(Frame);
message.WriteUInt32(player.PlayerId); message.WriteUInt32(player.PlayerId);
message.WriteSingle(Time.TimeSinceStartup);
message.WriteVector2(inputState.ViewDelta); message.WriteVector2(inputState.ViewDelta);
message.WriteSingle(inputState.MoveForward); message.WriteSingle(inputState.MoveForward);
message.WriteSingle(inputState.MoveRight); message.WriteSingle(inputState.MoveRight);
message.WriteBoolean(inputState.Attack); message.WriteBoolean(inputState.Attack);
message.WriteBoolean(inputState.Jump); message.WriteBoolean(inputState.Jump);
NetworkManager.ClientEndSendMessage(ref message); NetworkManager.ClientEndSendMessage(ref message);
//Console.Print("send: " + Frame.ToString());
} }
base.OnLateUpdate(); base.OnLateUpdate();
} }
@@ -644,6 +707,18 @@ file class ClientWorld : World
public override bool IsLocalPlayer(uint playerId) => playerId == _localPlayerId; 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) public void OnClientConnected(NetworkConnection connection)
{ {
uint playerId = connection.ConnectionId; uint playerId = connection.ConnectionId;

View File

@@ -1,15 +1,17 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq; using System.Linq;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Text; using System.Text;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using FlaxEngine; using FlaxEngine;
namespace Game; namespace Game;
[StructLayout(LayoutKind.Sequential)] [StructLayout(LayoutKind.Sequential)]
public struct PlayerInputState2 public struct PlayerInputState2 : IEquatable<PlayerInputState2>
{ {
public ulong Frame; public ulong Frame;
public Float2 ViewDelta; public Float2 ViewDelta;
@@ -17,6 +19,22 @@ public struct PlayerInputState2
public float MoveRight; public float MoveRight;
public bool Attack; public bool Attack;
public bool Jump; public bool Jump;
/*public override bool Equals([NotNullWhen(true)] object obj)
{
if (obj is not PlayerInputState2 other)
return false;
return Frame == other.Frame && ViewDelta == other.ViewDelta && MoveForward == other.MoveForward && MoveRight == other.MoveRight && Attack == other.Attack && Jump == other.Jump;
}*/
public bool Equals(PlayerInputState2 other)
{
return Frame == other.Frame && ViewDelta == other.ViewDelta && MoveForward == other.MoveForward && MoveRight == other.MoveRight && Attack == other.Attack && Jump == other.Jump;
}
public static bool operator ==(PlayerInputState2 left, PlayerInputState2 right) => left.Equals(right);
public static bool operator !=(PlayerInputState2 left, PlayerInputState2 right) => !left.Equals(right);
} }
public interface IPlayerInput public interface IPlayerInput
@@ -80,6 +98,9 @@ public class PlayerInput2 : IPlayerInput
_recordState.Attack |= Input.GetAction("Attack"); _recordState.Attack |= Input.GetAction("Attack");
_recordState.Jump |= Input.GetAction("Jump"); _recordState.Jump |= Input.GetAction("Jump");
_recordState.Frame = _frame; _recordState.Frame = _frame;
if (Input.GetKeyDown(KeyboardKeys.F))
Thread.Sleep(20);
} }
public PlayerInputState2 GetState() => _recordState; public PlayerInputState2 GetState() => _recordState;

View File

@@ -74,6 +74,7 @@ public struct PlayerMovementState
public int numJumps; public int numJumps;
public bool jumped; public bool jumped;
public bool onGround; public bool onGround;
public ulong frame;
public PlayerMovementState() public PlayerMovementState()
@@ -250,9 +251,20 @@ public class PlayerMovement : Script
viewAnglesLastFrame = viewAngles; viewAnglesLastFrame = viewAngles;
} }
private static int framedropped = 0;
public override void OnUpdate() public override void OnUpdate()
{ {
Console.Print("playerMovement OnUpdate"); if (World.IsServer && Mathf.Abs(Time.DeltaTime - (1.0f / Time.UpdateFPS)) > (1.0f / Time.UpdateFPS) * 0.999f)
{
Console.Print($"drop: {Time.DeltaTime*1000.0f}ms");
framedropped = 3;
}
else if (World.IsServer && framedropped > 0)
{
Console.Print("dropping...");
framedropped--;
}
//Console.Print($"{(World.IsClient ? "[cl] " : "[sv] ") + World.Frame.ToString()} playerMovement OnUpdate");
//input.OnUpdate(); //input.OnUpdate();
@@ -315,7 +327,7 @@ public class PlayerMovement : Script
public override void OnFixedUpdate() public override void OnFixedUpdate()
{ {
Console.Print("playerMovement OnFixedUpdate"); //Console.Print($"{(World.IsClient ? "[cl] " : "[sv] ") + World.Frame.ToString()} playerMovement OnFixedUpdate");
float timeDeltaDiff = Time.DeltaTime - 1.0f / Time.PhysicsFPS; float timeDeltaDiff = Time.DeltaTime - 1.0f / Time.PhysicsFPS;
if (Time.PhysicsFPS > 0 && Math.Abs(timeDeltaDiff) > 0.0001f) if (Time.PhysicsFPS > 0 && Math.Abs(timeDeltaDiff) > 0.0001f)
Console.Print("Time.DeltaTime is not stable: " + timeDeltaDiff); Console.Print("Time.DeltaTime is not stable: " + timeDeltaDiff);
@@ -413,13 +425,93 @@ public class PlayerMovement : Script
} }
} }
if (!predict) if (World.IsServer)
{
ulong currentFrame = World.GetLastProcessedFrame(PlayerId);
{
// Rewind to last confirmed state
var frameInfo = World.GetPlayerFrame(PlayerId, currentFrame);
if (frameInfo != null && frameInfo.movementState.frame == currentFrame)
{
// Reset all state to latest confirmed state from server
movementState.position = frameInfo.movementState.position;
movementState.currentVelocity = frameInfo.movementState.currentVelocity;
movementState.lastJumped = frameInfo.movementState.lastJumped;
movementState.numJumps = frameInfo.movementState.numJumps;
movementState.jumped = frameInfo.movementState.jumped;
Actor.Position = movementState.position;
SetCameraEulerAngles(frameInfo.movementState.viewAngles, true);
//ApplyInputToCamera(frameInfo.inputState, true);
}
else if (frameInfo == null)
{
// First frame, update the known state
movementState.viewAngles = viewAngles;
World.UpdatePlayerState(PlayerId, currentFrame, movementState);
}
currentFrame++;
}
//Console.Print("before:" + viewAngles.ToString());
ulong lastFrame = World.GetLastReceivedFrame(PlayerId);
PlayerFrame lastFrameInfo = null;
for (; currentFrame < lastFrame+1; currentFrame++)
{
var frameInfo = World.GetPlayerFrame(PlayerId, currentFrame);
if (frameInfo == null || frameInfo.inputState.Frame != currentFrame)
//if (!Input.GetState(currentFrame, out var pastInputState, out var pastActorState))
{
Console.Print($"frame {currentFrame} missing");
frameInfo = lastFrameInfo; // Use last known input
if (frameInfo == null)
{
// FIXME
frameInfo = new PlayerFrame()
{
inputState =
{
Frame = currentFrame,
},
movementState = movementState,
};
frameInfo.movementState.frame = currentFrame;
}
//continue;
//Console.Print($"not predicting");
//predict = false;
//break;
}
else
{
Input.SetFrame(currentFrame);
Input.UpdateState();
ApplyInputToCamera(frameInfo.inputState, true);
lastFrameInfo = frameInfo;
}
SimulatePlayerMovement(frameInfo.inputState, currentFrame);
movementState.viewAngles = viewAngles;
//Console.Print("tick: " + viewAngles.ToString());
//if (frameInfo.movementState.frame != currentFrame)
World.UpdatePlayerState(PlayerId, currentFrame, movementState);
//else
// currentFrame = currentFrame;
}
}
else if (!predict)
{ {
SetCameraEulerAngles(viewAnglesLastFrame, true); SetCameraEulerAngles(viewAnglesLastFrame, true);
ApplyInputToCamera(inputState, true); ApplyInputToCamera(inputState, true);
SimulatePlayerMovement(inputState, World.Frame); // MAYBE? SimulatePlayerMovement(inputState, World.Frame); // MAYBE?
} }
//if (!movementState.currentVelocity.IsZero)
// Console.Print($"{(World.IsClient ? "client" : "server")} {World.Frame} vel: {movementState.currentVelocity}, pos: {movementState.position}");
viewAnglesLastFrame = viewAngles; viewAnglesLastFrame = viewAngles;
#if false #if false