record demos as packets

This commit is contained in:
2023-05-21 00:48:03 +03:00
parent 3a59bad850
commit 280be62caa
10 changed files with 579 additions and 255 deletions

View File

@@ -84,6 +84,11 @@ namespace Game
Level.SceneLoaded -= OnLevelLoaded;
//Scripting.LateUpdate -= OnLateUpdatePre;
Scripting.LateFixedUpdate -= OnLateUpdatePre;
foreach (var player in Level.GetActors<PlayerActor>())
{
FlaxEngine.Object.Destroy(player);
}
}
private static PlayerFrame GetPlayerFrame(uint playerIndex, ulong playerFrameIndex)
@@ -109,8 +114,8 @@ namespace Game
try
{
NetworkManager.IsServer = NetworkManager.server != null;
NetworkManager.IsClient = NetworkManager.client != null && NetworkManager.server == null;
NetworkManager.IsLocalClient = NetworkManager.client != null && NetworkManager.server != null;
NetworkManager.IsClient = (NetworkManager.client != null || NetworkManager.IsDemoPlaying) && NetworkManager.server == null;
NetworkManager.IsLocalClient = (NetworkManager.client != null || NetworkManager.IsDemoPlaying) && NetworkManager.server != null;
OnLateUpdate();
}
finally
@@ -213,9 +218,9 @@ namespace Game
clientWorldState.playerFrameHistory.Add(playerId, playerFrames);
}
var playerFrameHistory = clientWorldState.playerFrameHistory[playerId];
var playerFrame = playerFrameHistory[clientWorldState.frame % 120];
var playerFrame = playerFrameHistory[ClientFrame % 120];
playerFrame.frame = clientWorldState.frame;
playerFrame.frame = ClientFrame;
playerFrame.position = playerActor.Position;
}
@@ -341,7 +346,7 @@ namespace Game
new Float3(networkEvent.Message.ReadSingle(), networkEvent.Message.ReadSingle(), networkEvent.Message.ReadSingle()));
//if (NetworkManager.IsClient)
players[playerId].GetScript<PlayerMovement>().input.frame = clientWorldState.frame;
players[playerId].GetScript<PlayerMovement>().input.frame = ClientFrame;
break;
}
case GameModeMessageType.PlayerInput:
@@ -547,7 +552,7 @@ namespace Game
players.Add(playerId, null);
players[playerId] = playerActor;
PlayerInput playerInput = playerActor.GetScript<PlayerMovement>().input;
playerInput.frame = (NetworkManager.IsServer) ? serverWorldState.frame : clientWorldState.frame;
playerInput.frame = (NetworkManager.IsServer) ? serverWorldState.frame : ClientFrame;
if (NetworkManager.IsServer)
{
serverWorldState.actors.Add(playerActor);

View File

@@ -82,23 +82,25 @@ namespace Game
public static void Cleanup()
{
Scripting.Exit -= Cleanup;
Scripting.FixedUpdate -= OnDemoUpdate;
Scripting.FixedUpdate -= OnServerNetworkUpdate;
Level.ActorSpawned -= OnServerActorSpawned;
Scripting.FixedUpdate -= OnClientNetworkUpdate;
Level.ActorSpawned -= OnClientActorSpawned;
if (server != null)
{
Scripting.FixedUpdate -= OnServerUpdate;
Scripting.Exit -= Cleanup;
Level.ActorSpawned -= OnServerActorSpawned;
NetworkPeer.ShutdownPeer(server);
server = null;
}
if (client != null)
{
Scripting.FixedUpdate -= OnClientUpdate;
Scripting.Exit -= Cleanup;
Level.ActorSpawned -= OnClientActorSpawned;
NetworkPeer.ShutdownPeer(client);
client = null;
}
LocalPlayerClientId = 0;
#if FLAX_EDITOR
@@ -107,6 +109,8 @@ namespace Game
#endif
GameModeManager.Cleanup(); // FIXME
StopRecording();
initialized = false;
}
@@ -147,5 +151,7 @@ namespace Game
break;
}
}
}
}

View File

@@ -49,79 +49,96 @@ namespace Game
return false;
}
Scripting.FixedUpdate += OnClientUpdate;
Scripting.FixedUpdate += OnClientNetworkUpdate;
if (!listenServer)
{
Scripting.Exit += Cleanup;
Level.ActorSpawned += OnClientActorSpawned;
//Level.ActorSpawned += OnClientActorSpawned;
}
string demoPath = System.IO.Path.Combine(AssetManager.DemoPath, $"{DateTimeOffset.Now.UtcTicks}_v2.gdem");
RecordDemo(demoPath);
return true;
}
private static void OnClientUpdate()
private static void OnClientNetworkUpdate()
{
using Utilities.ScopeProfiler _ = Utilities.ProfileScope("NetworkManager_OnClientUpdate");
using var _ = Utilities.ProfileScope("NetworkManager_OnClientNetworkUpdate");
while (client.PopEvent(out NetworkEvent networkEvent))
switch (networkEvent.EventType)
{
RecordMessage(ref networkEvent);
try
{
case NetworkEventType.Connected:
{
LocalPlayerClientId = networkEvent.Sender.ConnectionId;
Console.Print("Connected to server, ConnectionId: " + networkEvent.Sender.ConnectionId);
break;
}
case NetworkEventType.Disconnected:
{
Console.Print("Disconnected from server, timeout.");
LocalPlayerClientId = 0;
break;
}
case NetworkEventType.Timeout:
{
Console.Print("Disconnected from server, connection closed.");
LocalPlayerClientId = 0;
break;
}
case NetworkEventType.Message:
{
try
{
IsLocalClient = server != null;
IsClient = true;
OnNetworkMessage(ref networkEvent);
if (networkEvent.Message.Position > 0 &&
networkEvent.Message.Position < networkEvent.Message.Length)
{
string err =
$"Network message was not fully read: {networkEvent.Message.Position} / {networkEvent.Message.Length}.";
networkEvent.Message.Position = 0;
byte[] messageBytes = new byte[networkEvent.Message.Length];
unsafe
{
fixed (byte* messageBytePtr = &messageBytes[0])
networkEvent.Message.ReadBytes(messageBytePtr, (int)networkEvent.Message.Length);
}
string messageBytesStr = string.Join(", ",
messageBytes.Select(x => "0x" + ((int)x).ToString("X2")));
Console.PrintError(err + $"Message dump: {messageBytesStr}");
}
}
finally
{
IsLocalClient = false;
IsClient = false;
client.RecycleMessage(networkEvent.Message);
}
break;
}
default:
throw new ArgumentOutOfRangeException();
IsLocalClient = server != null;
IsClient = true;
OnClientReadMessage(ref networkEvent);
}
finally
{
IsLocalClient = false;
IsClient = false;
if (networkEvent.EventType == NetworkEventType.Message)
client.RecycleMessage(networkEvent.Message);
}
}
}
private static void OnClientReadMessage(ref NetworkEvent networkEvent)
{
using var _ = Utilities.ProfileScope("NetworkManager_OnClientReadMessage");
switch (networkEvent.EventType)
{
case NetworkEventType.Connected:
{
LocalPlayerClientId = networkEvent.Sender.ConnectionId;
Console.Print("Connected to server, ConnectionId: " + networkEvent.Sender.ConnectionId);
break;
}
case NetworkEventType.Disconnected:
{
Console.Print("Disconnected from server, timeout.");
LocalPlayerClientId = 0;
break;
}
case NetworkEventType.Timeout:
{
Console.Print("Disconnected from server, connection closed.");
LocalPlayerClientId = 0;
break;
}
case NetworkEventType.Message:
{
OnNetworkMessage(ref networkEvent);
if (networkEvent.Message.Position > 0 &&
networkEvent.Message.Position < networkEvent.Message.Length)
{
string err =
$"Network message was not fully read: {networkEvent.Message.Position} / {networkEvent.Message.Length}.\n";
networkEvent.Message.Position = 0;
byte[] messageBytes = new byte[networkEvent.Message.Length];
unsafe
{
fixed (byte* messageBytePtr = &messageBytes[0])
networkEvent.Message.ReadBytes(messageBytePtr, (int)networkEvent.Message.Length);
}
//string messageBytesStr = string.Join(", ", messageBytes.Select(x => "0x" + ((int)x).ToString("X2")));
string messageBytesStr = string.Join("", messageBytes.Select(x => ((int)x).ToString("X2")));
Console.PrintError(err + $"Message dump: {messageBytesStr}");
}
break;
}
default:
throw new ArgumentOutOfRangeException();
}
}
private static void OnClientActorSpawned(Actor actor)

View File

@@ -0,0 +1,278 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using FlaxEngine;
using FlaxEngine.Networking;
using Console = Game.Console;
using Object = FlaxEngine.Object;
namespace Game
{
public static unsafe partial class NetworkManager
{
public const byte DemoVer = 2;
private struct DemoNetworkEventHeader
{
public NetworkEventType EventType;
public uint SenderId;
public uint MessageId;
public uint Length;
}
private static List<NetworkEvent> buffer;
private static IEnumerator<NetworkEvent> bufferEnumerable;
private static GZipStream demoStream; // record
private static FileStream demoFileStream;
public static bool IsRecording => demoStream != null;
private static long flushedFrames = 0;
public static bool IsDemoPlaying => bufferEnumerable != null;
public static bool PlayDemo(string demoName)
{
{
Cleanup();
GameModeManager.Init();
}
if (!ReadDemo(demoName))
{
Console.Print("Failed to read demo.");
return false;
}
/*if (client == null)
{
Console.Print("Failed to create NetworkPeer.");
return false;
}
if (!client.Connect())
{
Console.Print("Failed to connect to the server.");
return false;
}*/
Scripting.FixedUpdate += OnDemoUpdate;
//if (!listenServer)
{
Scripting.Exit += Cleanup;
//Level.ActorSpawned += OnClientActorSpawned;
}
return true;
}
public static void RecordDemo(string demoPath)
{
if (IsRecording)
StopRecording();
buffer = new List<NetworkEvent>();
var demoFolder = Directory.GetParent(demoPath);
if (!demoFolder.Exists)
Directory.CreateDirectory(demoFolder.FullName);
demoFileStream = File.Open(demoPath, FileMode.Create, FileAccess.Write);
demoStream = new GZipStream(demoFileStream, CompressionMode.Compress);
demoStream.WriteByte(DemoVer);
}
private static void RecordMessage(ref NetworkEvent networkEvent)
{
if (!IsRecording)
return;
NetworkEvent recordedEvent = networkEvent;
recordedEvent.Message.Buffer = (byte*)NativeMemory.Alloc(recordedEvent.Message.Length);
recordedEvent.Sender.ConnectionId = networkEvent.Sender.ConnectionId;
NativeMemory.Copy(networkEvent.Message.Buffer, recordedEvent.Message.Buffer, recordedEvent.Message.Length);
buffer.Add(recordedEvent);
}
public static void FlushDemo()
{
if (!IsRecording)
return;
Stopwatch sw = Stopwatch.StartNew();
Span<byte> bytes = stackalloc byte[Unsafe.SizeOf<DemoNetworkEventHeader>()];
foreach (ref NetworkEvent networkEvent in CollectionsMarshal.AsSpan(buffer))
{
DemoNetworkEventHeader header = new DemoNetworkEventHeader()
{
EventType = networkEvent.EventType,
SenderId = networkEvent.Sender.ConnectionId,
Length = networkEvent.Message.Length,
MessageId = networkEvent.Message.MessageId,
};
MemoryMarshal.Write(bytes, ref header);
demoStream.Write(bytes);
Span<byte> messageBytes = new Span<byte>(networkEvent.Message.Buffer, (int)networkEvent.Message.Length);
demoStream.Write(messageBytes);
// TODO: free networkEvent buffer
}
sw.Stop();
flushedFrames += buffer.Count;
buffer.Clear();
FlaxEngine.Debug.Write(LogType.Info, $"Wrote demo in {sw.Elapsed.TotalMilliseconds}ms, frames: {flushedFrames}");
}
public static void StopRecording()
{
if (!IsRecording)
return;
FlushDemo();
demoStream.Close();
demoStream = null;
demoFileStream.Close();
demoFileStream = null;
}
private static unsafe bool ReadDemo(string demoPath)
{
if (!File.Exists(demoPath))
return false;
buffer = new List<NetworkEvent>();
Stopwatch sw = Stopwatch.StartNew();
using FileStream fileStream = File.OpenRead(demoPath);
using GZipStream stream = new GZipStream(fileStream, CompressionMode.Decompress);
int ver = stream.ReadByte();
if (ver != DemoVer)
{
Console.Print($"Demo version mismatch, expected {DemoVer}, got {ver}");
stream.Close();
return false;
}
int headerSize = Unsafe.SizeOf<DemoNetworkEventHeader>();
Span<byte> headerBuffer = stackalloc byte[headerSize];
while (true)
{
{
int bytesLeftInBuffer = headerSize;
do
{
int readBytes = stream.Read(headerBuffer.Slice(headerSize - bytesLeftInBuffer, bytesLeftInBuffer));
if (readBytes == 0)
break;
bytesLeftInBuffer -= readBytes;
} while (bytesLeftInBuffer > 0);
if (bytesLeftInBuffer > 0)
break; // EOF;
}
DemoNetworkEventHeader header = MemoryMarshal.Read<DemoNetworkEventHeader>(headerBuffer);
//buffer.Add(new NetworkEvent());
//ref NetworkEvent networkEvent = ref buffer[buffer.Length]; // collectionmarshal
NetworkEvent networkEvent = new NetworkEvent();
networkEvent.EventType = header.EventType;
networkEvent.Message.Position = 0;
networkEvent.Message.MessageId = header.MessageId;
networkEvent.Message.Length = networkEvent.Message.BufferSize = header.Length;
networkEvent.Sender.ConnectionId = header.SenderId;
if (header.Length > 0)
{
networkEvent.Message.Buffer = (byte*)NativeMemory.Alloc(header.Length);
Span<byte> messageBufferSpan = new Span<byte>(networkEvent.Message.Buffer, (int)networkEvent.Message.BufferSize);
{
int bytesLeftInBuffer = (int)header.Length;
do
{
int readBytes = stream.Read(messageBufferSpan.Slice((int)header.Length - bytesLeftInBuffer, bytesLeftInBuffer));
if (readBytes == 0)
break;
bytesLeftInBuffer -= readBytes;
} while (bytesLeftInBuffer > 0);
if (bytesLeftInBuffer > 0)
break; // EOF;
}
}
buffer.Add(networkEvent);
}
sw.Stop();
bufferEnumerable = buffer.GetEnumerator();
Console.Print($"Demo parse time {sw.Elapsed.TotalMilliseconds}ms, frames: {buffer.Count} ");
DemoEndFrame(); // advances to first frame
return true;
}
private static void DemoEndFrame()
{
// TODO: check if the current state frame matches the current frame number before advancing
/*asdf++;
if (asdf < 8)
return;*/
if (bufferEnumerable == null || !bufferEnumerable.MoveNext())
{
if (buffer.Any())
{
bufferEnumerable.Dispose();
bufferEnumerable = null;
buffer.Clear();
Console.Print("Demo ended");
}
return;
}
//var actorState = currentState.actor;
//currentState.input = bufferEnumerable.Current;
//frame++;
//currentState.actor = actorState;
}
private static void OnDemoUpdate()
{
if (!IsDemoPlaying)
return;
using Utilities.ScopeProfiler _ = Utilities.ProfileScope("NetworkManager_OnDemoUpdate");
NetworkEvent demoEvent = bufferEnumerable.Current; // ref?
// TODO: change/randomize Sender.ConnectionId?
try
{
IsLocalClient = server != null;
IsClient = true;
OnClientReadMessage(ref demoEvent);
}
finally
{
IsLocalClient = false;
IsClient = false;
//TODO: recycle event?
}
DemoEndFrame();
}
}
}

View File

@@ -44,7 +44,7 @@ namespace Game
return false;
}
Scripting.FixedUpdate += OnServerUpdate;
Scripting.FixedUpdate += OnServerNetworkUpdate;
Scripting.Exit += Cleanup;
Level.ActorSpawned += OnServerActorSpawned;
@@ -116,84 +116,91 @@ namespace Game
client.EndSendMessage(channelType, message);
}
private static void OnServerUpdate()
private static void OnServerNetworkUpdate()
{
using Utilities.ScopeProfiler _ = Utilities.ProfileScope("NetworkManager_OnServerUpdate");
using var _ = Utilities.ProfileScope("NetworkManager_OnServerNetworkUpdate");
while (server.PopEvent(out NetworkEvent networkEvent))
switch (networkEvent.EventType)
OnServerReadMessage(ref networkEvent);
}
private static void OnServerReadMessage(ref NetworkEvent networkEvent)
{
using var _ = Utilities.ProfileScope("NetworkManager_OnServerReadMessage");
switch (networkEvent.EventType)
{
case NetworkEventType.Connected:
{
case NetworkEventType.Connected:
Console.Print($"Client({networkEvent.Sender.ConnectionId}) is trying to connect");
try
{
Console.Print($"Client({networkEvent.Sender.ConnectionId}) is trying to connect");
try
IsServer = true;
if (GameModeManager.OnClientConnecting(networkEvent.Sender))
{
IsServer = true;
if (GameModeManager.OnClientConnecting(networkEvent.Sender))
{
ConnectedClients.Add(networkEvent.Sender);
Console.Print(
$"Client({networkEvent.Sender.ConnectionId}) connected. Total clients: {ConnectedClients.Count}");
ConnectedClients.Add(networkEvent.Sender);
Console.Print(
$"Client({networkEvent.Sender.ConnectionId}) connected. Total clients: {ConnectedClients.Count}");
GameModeManager.OnClientConnected(networkEvent.Sender);
}
else
Console.Print($"Client({networkEvent.Sender.ConnectionId}) connection refused");
GameModeManager.OnClientConnected(networkEvent.Sender);
}
finally
{
IsServer = false;
}
break;
else
Console.Print($"Client({networkEvent.Sender.ConnectionId}) connection refused");
}
case NetworkEventType.Disconnected:
case NetworkEventType.Timeout:
finally
{
Console.Print($"Client({networkEvent.Sender.ConnectionId}) disconnected!");
ConnectedClients.Remove(networkEvent.Sender);
Console.Print("Connected clients: " + ConnectedClients.Count);
break;
IsServer = false;
}
case NetworkEventType.Message:
{
try
{
IsServer = true;
OnNetworkMessage(ref networkEvent);
if (networkEvent.Message.Position > 0 &&
networkEvent.Message.Position < networkEvent.Message.Length)
{
string err =
$"Network message was not fully read: {networkEvent.Message.Position} / {networkEvent.Message.Length}.";
networkEvent.Message.Position = 0;
byte[] messageBytes = new byte[networkEvent.Message.Length];
unsafe
{
fixed (byte* messageBytePtr = &messageBytes[0])
networkEvent.Message.ReadBytes(messageBytePtr, (int)networkEvent.Message.Length);
}
string messageBytesStr = string.Join(", ",
messageBytes.Select(x => "0x" + ((int)x).ToString("X2")));
Console.PrintError(err + $"Message dump: {messageBytesStr}");
}
}
finally
{
IsServer = false;
server.RecycleMessage(networkEvent.Message);
}
break;
}
default:
throw new ArgumentOutOfRangeException();
break;
}
case NetworkEventType.Disconnected:
case NetworkEventType.Timeout:
{
Console.Print($"Client({networkEvent.Sender.ConnectionId}) disconnected!");
ConnectedClients.Remove(networkEvent.Sender);
Console.Print("Connected clients: " + ConnectedClients.Count);
break;
}
case NetworkEventType.Message:
{
try
{
IsServer = true;
OnNetworkMessage(ref networkEvent);
if (networkEvent.Message.Position > 0 &&
networkEvent.Message.Position < networkEvent.Message.Length)
{
string err =
$"Network message was not fully read: {networkEvent.Message.Position} / {networkEvent.Message.Length}.";
networkEvent.Message.Position = 0;
byte[] messageBytes = new byte[networkEvent.Message.Length];
unsafe
{
fixed (byte* messageBytePtr = &messageBytes[0])
networkEvent.Message.ReadBytes(messageBytePtr, (int)networkEvent.Message.Length);
}
string messageBytesStr = string.Join(", ",
messageBytes.Select(x => "0x" + ((int)x).ToString("X2")));
Console.PrintError(err + $"Message dump: {messageBytesStr}");
}
}
finally
{
IsServer = false;
server.RecycleMessage(networkEvent.Message);
}
break;
}
default:
throw new ArgumentOutOfRangeException();
}
}
private static void OnServerActorSpawned(Actor actor)