278 lines
8.4 KiB
C#
278 lines
8.4 KiB
C#
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();
|
|
//clientWorldStateManager = new WorldStateManager(isServer: true);
|
|
World.InitServer();
|
|
}
|
|
|
|
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.Update += 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, 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();
|
|
}
|
|
} |