networking progress

This commit is contained in:
2023-04-06 21:12:04 +03:00
parent 59ef64fe3b
commit 7228c51dd7
32 changed files with 1084 additions and 261 deletions

View File

@@ -4965,3 +4965,9 @@
"origin" "160 40 504"
"targetname" "light_target1"
}
// entity 74
{
"classname" "info_player_deathmatch"
"origin" "-496 -304 152"
"angle" "180"
}

View File

@@ -9,19 +9,6 @@
"V": {},
"IsActive": false,
"Name": "PlayerPrefab",
"Transform": {
"Translation": {
"X": 73.19660949707031,
"Y": -33.01502227783203,
"Z": -139.43125915527345
},
"Orientation": {
"X": 0.0,
"Y": 1.0,
"Z": 0.0,
"W": -4.371138828673793e-8
}
},
"StaticFlags": 0,
"OverrideMass": true,
"Mass": 10.0

View File

@@ -1,7 +1,7 @@
{
"ID": "3c7bc3854d42f9b1b0fea9ba0d7fa8e9",
"TypeName": "FlaxEditor.Content.Settings.GameSettings",
"EngineBuild": 6335,
"EngineBuild": 6340,
"Data": {
"ProductName": "Goake",
"CompanyName": "GoaLitiuM",

View File

@@ -49,13 +49,13 @@
"ParentID": "ff6b6db54b5aa08e7286ef86246149ef",
"Transform": {
"Translation": {
"X": 1841.0,
"Y": 972.0,
"X": 1355.0,
"Y": 791.0,
"Z": 0.0
}
},
"Data": {
"Text": "1302 tris\n 131 drawcalls\n434fps2\n1921fps"
"Text": "13022 tris\n 102 drawcalls\n238fps2\n240fps"
}
},
{

View File

@@ -1,7 +1,7 @@
{
"ID": "af2e52554f7faed7b4937181dd22d166",
"TypeName": "FlaxEditor.Content.Settings.BuildSettings",
"EngineBuild": 6332,
"EngineBuild": 6340,
"Data": {
"MaxAssetsPerPackage": 4096,
"MaxPackageSizeMB": 1024,
@@ -9,6 +9,7 @@
"ForDistribution": false,
"SkipPackaging": false,
"AdditionalAssets": [],
"AdditionalScenes": null,
"AdditionalAssetFolders": [
"Content/Materials",
"Content/Textures",
@@ -17,6 +18,7 @@
],
"ShadersNoOptimize": false,
"ShadersGenerateDebugData": false,
"SkipDotnetPackaging": true,
"Presets": [
{
"Name": "Preset 3",

View File

@@ -7,6 +7,6 @@ StaticallyLinkedBinaryModuleInitializer StaticallyLinkedBinaryModuleGame(GetBina
extern "C" BinaryModule* GetBinaryModuleGame()
{
static NativeBinaryModule module("Game", MAssemblyOptions());
static NativeBinaryModule module("Game");
return &module;
}

View File

@@ -3,12 +3,13 @@ using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using FlaxEditor;
using FlaxEngine;
namespace Game
{
public class ConsoleLine
public struct ConsoleLine
{
public string content;
@@ -75,7 +76,7 @@ namespace Game
public static string LinePrefix => instance.LinePrefix;
public static IReadOnlyCollection<ConsoleLine> Lines => instance.Lines;
public static ReadOnlySpan<ConsoleLine> Lines => instance.Lines;
public static void Init()
{
@@ -220,7 +221,7 @@ namespace Game
public int DebugVerbosity { get; set; } = 1;
public string LinePrefix { get; internal set; } = "]";
public IReadOnlyCollection<ConsoleLine> Lines => consoleLines.AsReadOnly();
public ReadOnlySpan<ConsoleLine> Lines => CollectionsMarshal.AsSpan(consoleLines);
public void Dispose()
{
@@ -380,23 +381,14 @@ namespace Game
{
debugLastLine = text;
foreach (string line in text.Split('\n'))
{
ConsoleLine lineEntry = new ConsoleLine(line);
consoleLines.Add(lineEntry);
OnPrint?.Invoke(text);
}
PrintLine(text);
}
// Echoes warning text to Console
public void PrintWarning(string text)
{
foreach (string line in text.Split('\n'))
{
ConsoleLine lineEntry = new ConsoleLine(line);
consoleLines.Add(lineEntry);
OnPrint?.Invoke(text);
}
PrintLine(text);
}
// Echoes error text to Console
@@ -404,12 +396,7 @@ namespace Game
[DebuggerHidden]
public void PrintError(string text)
{
foreach (string line in text.Split('\n'))
{
ConsoleLine lineEntry = new ConsoleLine(line);
consoleLines.Add(lineEntry);
OnPrint?.Invoke(text);
}
PrintLine(text);
/*if (Debugger.IsAttached)
Debugger.Break();
@@ -417,6 +404,26 @@ namespace Game
throw new Exception(text);
}
private void PrintLine(string text)
{
if (text.IndexOf('\n') != -1)
{
// Avoid generating extra garbage in single-line cases
foreach (string line in text.Split('\n'))
{
ConsoleLine lineEntry = new ConsoleLine(line);
consoleLines.Add(lineEntry);
OnPrint?.Invoke(text);
}
}
else
{
ConsoleLine lineEntry = new ConsoleLine(text);
consoleLines.Add(lineEntry);
OnPrint?.Invoke(text);
}
}
public void PrintDebug(int verbosity, bool noRepeat, string text)
{
if (DebugVerbosity < verbosity)
@@ -428,12 +435,7 @@ namespace Game
debugLastLine = text;
foreach (string line in text.Split('\n'))
{
ConsoleLine lineEntry = new ConsoleLine(line);
consoleLines.Add(lineEntry);
OnPrint?.Invoke(text);
}
PrintLine(text);
}
// Opens the Console

View File

@@ -127,7 +127,7 @@ namespace Game
}
private void CalculateVisibleLines(IReadOnlyCollection<ConsoleLine> lines, out int firstVisibleLine,
private void CalculateVisibleLines(ReadOnlySpan<ConsoleLine> lines, out int firstVisibleLine,
out int lastVisibleLine, out LineInfo[] wrappedLines)
{
wrappedLines = null;
@@ -142,18 +142,21 @@ namespace Game
int lineMaxChars = (int)(Width / fontWidth);
int lineMaxLines = GetHeightInLines();
int numLines = 0;
int lineIndex = lines.Count;
var lineInfos = new List<LineInfo>(lineMaxLines + 1);
int linesSkipped = 0;
foreach (string line in lines.Reverse())
//foreach (string line in lines.Reverse())
int lineIndex = lines.Length - 1;
for (; lineIndex >= 0; lineIndex--)
{
lineIndex--;
//lineIndex--;
if (linesSkipped < ScrollOffset)
{
linesSkipped++;
continue;
}
string line = lines[lineIndex];
int numChars = 0;
int startIndex = lineInfos.Count;
while (numChars < line.Length)
@@ -207,7 +210,7 @@ namespace Game
Profiler.BeginEvent("ConsoleContentTextBoxDraw_FetchLines");
var lines = Console.Lines;
Profiler.EndEvent();
if (lines.Count > 0)
if (lines.Length > 0)
{
Profiler.BeginEvent("ConsoleContentTextBoxDraw_Lines");
@@ -253,7 +256,7 @@ namespace Game
foreach (LineInfo li in wrappedLines)
{
int lineIndex = li.lineIndex;
string fullLine = lines.ElementAt(lineIndex);
string fullLine = lines[lineIndex];
string line = fullLine.Substring(li.lineOffset, li.lineLength);
int leftChar = selectionLeftChar;
@@ -330,7 +333,7 @@ namespace Game
foreach (LineInfo li in wrappedLines)
{
int lineIndex = li.lineIndex;
string line = lines.ElementAt(lineIndex).content.Substring(li.lineOffset, li.lineLength);
string line = lines[lineIndex].content.Substring(li.lineOffset, li.lineLength);
Render2D.DrawText(font, line, TextColor, ref layout);
layout.Bounds.Y += lineHeight;
}
@@ -405,7 +408,7 @@ namespace Game
return false;
hitLine = wrappedLines[hitWrappedLine].lineIndex;
string line = lines.ElementAt(hitLine).content.Substring(wrappedLines[hitWrappedLine].lineOffset,
string line = lines[hitLine].content.Substring(wrappedLines[hitWrappedLine].lineOffset,
wrappedLines[hitWrappedLine].lineLength);
layout.Bounds.Y = top + hitWrappedLine * lineHeight;
@@ -447,7 +450,7 @@ namespace Game
ScrollOffset += GetHeightInLines() / 2;
// should count the wrapped line count here over Console.Lines.Count
//var maxOffset = Console.Lines.Count - GetHeightInLines();
int maxOffset = Console.Lines.Count - 1;
int maxOffset = Console.Lines.Length - 1;
if (ScrollOffset > maxOffset)
ScrollOffset = maxOffset;
}
@@ -477,7 +480,7 @@ namespace Game
else if (delta > 0)
{
ScrollOffset += ScrollMouseLines;
int maxOffset = Console.Lines.Count - GetHeightInLines();
int maxOffset = Console.Lines.Length - GetHeightInLines();
if (ScrollOffset > maxOffset)
ScrollOffset = maxOffset;
}
@@ -497,7 +500,7 @@ namespace Game
ret = true;
}
if (button == MouseButton.Left && Console.Lines.Count > 0)
if (button == MouseButton.Left && Console.Lines.Length > 0)
{
bool selectionStarted = !selectionActive;
Focus();
@@ -614,7 +617,7 @@ namespace Game
selectedText.AppendLine();
lastLineIndex = lineIndex;
string fullLine = lines.ElementAt(lineIndex);
string fullLine = lines[lineIndex];
string line = fullLine.Substring(li.lineOffset, li.lineLength);
int leftChar = selectionLeftChar;

View File

@@ -25,6 +25,7 @@ namespace Game
//AssetManager.Init(); // TODO: move these elsewhere
#if !FLAX_EDITOR
Level.SceneLoading += OnSceneLoading;
Level.SceneLoaded += OnSceneLoaded;
#endif
}
@@ -32,6 +33,7 @@ namespace Game
{
#if !FLAX_EDITOR
Level.SceneLoading -= OnSceneLoading;
Level.SceneLoaded -= OnSceneLoaded;
#endif
}
@@ -41,6 +43,19 @@ namespace Game
LoadConfig();
}
private void OnSceneLoaded(Scene scene, Guid guid)
{
Level.SceneLoaded -= OnSceneLoaded;
#if !FLAX_EDITOR
//GameMode.Connect();
//GameMode.StartServer(true);
//NetworkManager.StartServer();
GameModeManager.Init();
NetworkManager.ConnectServer();
#endif
}
private void LoadConfig()
{
Console.Print("Loading config file (GamePlugin)");
@@ -101,12 +116,16 @@ namespace Game
{
//FlaxEditor.Editor.Instance.PlayModeBegin -= Instance_PlayModeBegin;
LoadConfig();
GameMode.StartServer(true);
//GameMode.Connect();
GameModeManager.Init();
NetworkManager.StartServer();
//GameMode.StartServer(true);
}
private void OnPlayModeEnd()
{
GameMode.StopServer();
//GameMode.StopServer();
NetworkManager.Cleanup();
}
public override void Deinitialize()

View File

@@ -30,6 +30,8 @@ namespace Game
private UIControl rootControl;
private int fontHeight;
public override void OnStart()
{
consoleInputEvent = new InputEvent("Console");
@@ -37,8 +39,7 @@ namespace Game
FontReference fontReference = new FontReference(ConsoleFont, ConsoleFontSize);
Font fontRaw = fontReference.GetFont();
int fontHeight = (int)(fontRaw.Height / Platform.DpiScale);
fontHeight = (int)(fontRaw.Height / Platform.DpiScale);
// root actor which holds all the elements
//var rootContainerControl = new ContainerControl(new Rectangle(0, 0, screenSize.X, screenSize.Y));
ContainerControl rootContainerControl = new ContainerControl(new Rectangle());
@@ -222,6 +223,7 @@ namespace Game
Debug.Logger.LogHandler.SendLog += OnSendLog;
Debug.Logger.LogHandler.SendExceptionLog += OnSendExceptionLog;
Console.OnOpen += OnConsoleOpen;
Console.OnClose += OnConsoleClose;
Console.OnPrint += OnPrint;
@@ -254,7 +256,7 @@ namespace Game
}
else
{
Console.Print("[EXCEP] " + exception.Message);
Console.Print("[EXCEP] " + exception.ToString());
}
}
@@ -374,7 +376,6 @@ namespace Game
}
else if (!Console.IsOpen)
{
int fontHeight = (int)(consoleNotifyBox.FontHeight / Platform.DpiScale);
if (location.Y < -conHeight * ConsoleHeight + fontHeight)
{
consoleNotifyBox.Visible = true;
@@ -386,8 +387,7 @@ namespace Game
public void OnPrint(string text)
{
int fontHeight = (int)(consoleNotifyBox.FontHeight / Platform.DpiScale);
consoleNotifyBox.Height = Math.Min(ConsoleNotifyLines, Console.Lines.Count) * fontHeight;
consoleNotifyBox.Height = Math.Min(ConsoleNotifyLines, Console.Lines.Length) * fontHeight;
}
public void SetInput(string text)

View File

@@ -7,6 +7,7 @@ using NVIDIA;
#endif
using FlaxEditor.Content.Settings;
using FlaxEngine;
using FlaxEngine.Networking;
namespace Game
{
@@ -353,11 +354,12 @@ namespace Game
/*aoSettings.OverrideFlags = (aoSettings.OverrideFlags & ~AmbientOcclusionSettingsOverride.Enabled) |
(boolValue
? AmbientOcclusionSettingsOverride.Enabled
: 0 & AmbientOcclusionSettingsOverride.Enabled);
*/
: 0 & AmbientOcclusionSettingsOverride.Enabled);*/
aoSettings.Enabled = boolValue;
postProcessSettings.AmbientOcclusion = aoSettings;
Graphics.PostProcessSettings = postProcessSettings;
}
}
@@ -608,17 +610,45 @@ namespace Game
Scripting.Update += TimeDemoOnUpdate;
}
[ConsoleVariable("net_fakelag")]
public static string NetFakeLag
{
get
{
var driver = NetworkManager.server != null ? (NetworkManager.ServerNetworkDriver as NetworkLagDriver) : (NetworkManager.ClientNetworkDriver as NetworkLagDriver);
if (driver == null)
return 0.ToString();
return ((int)driver.Lag).ToString();
}
set
{
var driver = NetworkManager.server != null ? (NetworkManager.ServerNetworkDriver as NetworkLagDriver) : (NetworkManager.ClientNetworkDriver as NetworkLagDriver);
if (driver == null)
return;
int intValue = 0;
if (int.TryParse(value, out intValue))
{ }
else if (float.TryParse(value, out float valueFloat))
intValue = (int)valueFloat;
intValue = Math.Clamp(intValue, 0, 2000);
driver.Lag = intValue;
}
}
[ConsoleCommand("map")]
public static void MapCommand()
{
//NetworkManager.StartServer(true);
GameMode.StartServer(true);
NetworkManager.StartServer();
}
[ConsoleCommand("connect")]
public static void ConnectCommand()
{
GameMode.Connect();
//GameMode.Connect();
NetworkManager.ConnectServer();
}
[ConsoleSubsystemInitializer]

View File

@@ -35,8 +35,8 @@ public class Game : GameModule
options.PublicDependencies.Add("Networking");
options.PrivateDependencies.Add("FidelityFXFSR");
//options.ScriptingAPI.FileReferences.Add(Path.Combine(Globals.EngineRoot, "Source", "Platforms", "DotNet", "Newtonsoft.Json.dll"));
//options.ScriptingAPI.FileReferences.Add(Path.Combine(Globals.EngineRoot, "Source", "Platforms", "DotNet", "Newtonsoft.Json.dll"));
#if COMPILE_WITH_DLSS
DLSS.ConditionalImport(options, options.PrivateDependencies);

View File

@@ -1,9 +1,10 @@
#if false
#if true
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using FlaxEngine;
using FlaxEngine.Assertions;
using FlaxEngine.Networking;
using Console = Game.Console;
@@ -14,7 +15,8 @@ namespace Game
WelcomePlayer,
SpawnPlayer,
PlayerInput,
PlayerPosition, // debug
PlayerPosition, // world update
PlayerSnapshot, // send world state delta to client since last client's acknowledged frame
}
@@ -43,32 +45,42 @@ namespace Game
private static Dictionary<uint, PlayerActor> players;
private static Dictionary<uint, NetworkConnection> playerConnections;
private static bool spawned = false;
private static bool welcomed = false;
private static WorldState worldState;
private static PlayerFrame[] localPlayerFrameHistory;
private static WorldState serverWorldState;
private static WorldState clientWorldState;
private static ulong lastReceivedServerFrame = 0;
public static ulong ServerFrame => /*NetworkManager.server != null ? serverWorldState.frame :*/ lastReceivedServerFrame;
public static ulong ClientFrame => clientWorldState.frame;
public static void Init()
{
welcomed = false;
lastReceivedServerFrame = 0;
players = new Dictionary<uint, PlayerActor>();
playerConnections = new Dictionary<uint, NetworkConnection>();
localPlayerFrameHistory = new PlayerFrame[120];
worldState = new WorldState();
serverWorldState = new WorldState();
clientWorldState = new WorldState();
NetworkManager.OnMessage += OnMessage;
Level.SceneLoaded += OnLevelLoaded;
Scripting.LateUpdate += OnLateUpdatePre;
//Scripting.LateUpdate += OnLateUpdatePre;
Scripting.LateFixedUpdate += OnLateUpdatePre;
}
public static void Cleanup()
{
NetworkManager.OnMessage -= OnMessage;
Level.SceneLoaded -= OnLevelLoaded;
Scripting.LateUpdate -= OnLateUpdatePre;
//Scripting.LateUpdate -= OnLateUpdatePre;
Scripting.LateFixedUpdate -= OnLateUpdatePre;
}
private static PlayerFrame GetPlayerFrame(uint playerIndex, ulong playerFrameIndex)
{
WorldState worldState = NetworkManager.server != null ? serverWorldState : clientWorldState;
PlayerFrame[] playerFrames = worldState.playerFrameHistory[playerIndex];
PlayerFrame playerFrame = playerFrames[playerFrameIndex % 120];
@@ -80,7 +92,7 @@ namespace Game
public static void OnLevelLoaded(Scene scene, Guid assetGuid)
{
worldState.frame = 0;
serverWorldState.frame = 0;
Console.Print("level loaded");
}
@@ -90,12 +102,14 @@ namespace Game
{
NetworkManager.IsServer = NetworkManager.server != null;
NetworkManager.IsClient = NetworkManager.client != null && NetworkManager.server == null;
NetworkManager.IsLocalClient = NetworkManager.client != null && NetworkManager.server != null;
OnLateUpdate();
}
finally
{
NetworkManager.IsServer = false;
NetworkManager.IsClient = false;
NetworkManager.IsLocalClient = false;
}
}
@@ -108,10 +122,10 @@ namespace Game
var playerId = kv.Key;
var playerActor = kv.Value;
var playerFrames = worldState.playerFrameHistory[playerId];
var playerFrame = playerFrames[worldState.frame % 120];
var playerFrames = serverWorldState.playerFrameHistory[playerId];
var playerFrame = playerFrames[serverWorldState.frame % 120];
playerFrame.frame = worldState.frame;
playerFrame.frame = serverWorldState.frame;
playerFrame.position = playerActor.Position;
}
@@ -120,41 +134,79 @@ namespace Game
var playerId = kv.Key;
foreach (KeyValuePair<uint, PlayerActor> kv2 in players)
{
if (kv2.Key == playerId)
continue;
//if (kv2.Key == playerId)
// continue;
var otherPlayerActor = kv2.Value;
// TODO: relevancy checks here etc.
PlayerMovement playerMovement = otherPlayerActor.GetScript<PlayerMovement>();
PlayerActorState actorState = playerMovement.input.GetCurrentActorState();
PlayerInputState inputState = playerMovement.input.GetCurrentInputState();
{
NetworkMessage message = NetworkManager.ServerBeginSendMessage();
message.WriteByte((byte)GameModeMessageType.PlayerPosition);
message.WriteUInt64(worldState.frame);
message.WriteUInt64(serverWorldState.frame);
message.WriteUInt32(kv2.Key);
message.WriteSingle(otherPlayerActor.Position.X);
message.WriteSingle(otherPlayerActor.Position.Y);
message.WriteSingle(otherPlayerActor.Position.Z);
message.WriteSingle(actorState.position.X);
message.WriteSingle(actorState.position.Y);
message.WriteSingle(actorState.position.Z);
message.WriteSingle(actorState.velocity.X);
message.WriteSingle(actorState.velocity.Y);
message.WriteSingle(actorState.velocity.Z);
message.WriteSingle(actorState.orientation.X);
message.WriteSingle(actorState.orientation.Y);
message.WriteSingle(actorState.orientation.Z);
message.WriteSingle(actorState.orientation.W);
message.WriteSingle(actorState.viewAngles.X);
message.WriteSingle(actorState.viewAngles.Y);
message.WriteSingle(actorState.viewAngles.Z);
//inputState.frame
message.WriteSingle(inputState.viewDeltaX);
message.WriteSingle(inputState.viewDeltaY);
message.WriteSingle(inputState.moveForward);
message.WriteSingle(inputState.moveRight);
message.WriteBoolean(inputState.attacking);
message.WriteBoolean(inputState.jumping);
NetworkManager.ServerEndSendMessage(ref message, playerConnections[playerId]);
}
}
}
}
if (NetworkManager.IsClient)
if (NetworkManager.IsClient || NetworkManager.IsLocalClient)
{
if (!spawned)
return;
//if (!welcomed)
// return;
PlayerActor playerActor = Level.GetActors<PlayerActor>().First(x =>
if (welcomed)
foreach (PlayerActor playerActor in Level.GetActors<PlayerActor>())
{
var playerId = playerActor.PlayerId;
var playerFrameHistory = clientWorldState.playerFrameHistory[playerId];
var playerFrame = playerFrameHistory[clientWorldState.frame % 120];
playerFrame.frame = clientWorldState.frame;
playerFrame.position = playerActor.Position;
}
clientWorldState.frame++;
/*PlayerActor playerActor = Level.GetActors<PlayerActor>().FirstOrDefault(x =>
x.GetScript<PlayerMovement>().PlayerId == NetworkManager.LocalPlayerClientId);
var playerFrame = localPlayerFrameHistory[worldState.frame % 120];
if (playerActor == null)
return;*/
playerFrame.frame = worldState.frame;
playerFrame.position = playerActor.Position;
//var playerFrame = localPlayerFrameHistory[serverWorldState.frame % 120];
{
//playerFrame.frame = serverWorldState.frame;
//playerFrame.position = playerActor.Position;
/*{
NetworkMessage message = NetworkManager.ClientBeginSendMessage();
message.WriteByte((byte)GameModeMessageType.PlayerPosition);
message.WriteUInt64(worldState.frame);
@@ -163,10 +215,14 @@ namespace Game
message.WriteSingle(playerActor.Position.Y);
message.WriteSingle(playerActor.Position.Z);
NetworkManager.ClientEndSendMessage(ref message);
}
}*/
}
else if (NetworkManager.IsLocalClient)
{
}
worldState.frame++;
serverWorldState.frame++;
/*foreach (KeyValuePair<uint,PlayerActor> kv in players)
{
@@ -205,11 +261,12 @@ namespace Game
{
case GameModeMessageType.WelcomePlayer:
{
if (NetworkManager.IsClient)
welcomed = true;
if (NetworkManager.IsClient || NetworkManager.IsLocalClient)
{
worldState.frame = networkEvent.Message.ReadUInt64();
var serverFrame = networkEvent.Message.ReadUInt64();
int numActors = (int)networkEvent.Message.ReadUInt32();
for (int i=0; i<numActors; i++)
for (int i = 0; i < numActors; i++)
{
uint playerId = networkEvent.Message.ReadUInt32();
Float3 playerPosition;
@@ -218,15 +275,24 @@ namespace Game
playerPosition.Z = networkEvent.Message.ReadSingle();
SpawnPlayer(playerId, playerPosition, new Float3(0));
var playerFrames = new PlayerFrame[120];
for (int j = 0; j < playerFrames.Length; j++)
playerFrames[j] = new PlayerFrame();
clientWorldState.playerFrameHistory.Add(playerId, playerFrames);
}
Console.Print("received welcome: frame " + worldState.frame);
Console.Print("received welcome: frame " + serverWorldState.frame);
players.Add(NetworkManager.LocalPlayerClientId, null);
if (!NetworkManager.IsLocalClient)
serverWorldState.frame = serverFrame;
//lastReceivedServerFrame = serverFrame;
clientWorldState.frame += serverFrame;
if (!players.ContainsKey(NetworkManager.LocalPlayerClientId)) // listen server
players.Add(NetworkManager.LocalPlayerClientId, null);
//playerConnections.Add(NetworkManager.LocalPlayerClientId, connection);
localPlayerFrameHistory = new PlayerFrame[120];
for (int i = 0; i < localPlayerFrameHistory.Length; i++)
localPlayerFrameHistory[i] = new PlayerFrame();
}
break;
}
@@ -234,9 +300,21 @@ namespace Game
{
uint playerId = networkEvent.Message.ReadUInt32();
ulong playerFrameIndex = networkEvent.Message.ReadUInt64();
if (!clientWorldState.playerFrameHistory.ContainsKey(playerId))
{
var playerFrames = new PlayerFrame[120];
for (int j = 0; j < playerFrames.Length; j++)
playerFrames[j] = new PlayerFrame();
clientWorldState.playerFrameHistory.Add(playerId, playerFrames);
}
SpawnPlayer(playerId,
new Float3(networkEvent.Message.ReadSingle(), networkEvent.Message.ReadSingle(), networkEvent.Message.ReadSingle()),
new Float3(networkEvent.Message.ReadSingle(), networkEvent.Message.ReadSingle(), networkEvent.Message.ReadSingle()));
//if (NetworkManager.IsClient)
players[playerId].GetScript<PlayerMovement>().input.frame = clientWorldState.frame;
break;
}
case GameModeMessageType.PlayerInput:
@@ -257,21 +335,49 @@ namespace Game
case GameModeMessageType.PlayerPosition:
{
uint playerId = networkEvent.Sender.ConnectionId;
Float3 reportedPosition;
PlayerInputState inputState = default; //?
PlayerActorState actorState;
ulong reportedFrame = networkEvent.Message.ReadUInt64();
uint reportedPlayerId = networkEvent.Message.ReadUInt32();
reportedPosition.X = networkEvent.Message.ReadSingle();
reportedPosition.Y = networkEvent.Message.ReadSingle();
reportedPosition.Z = networkEvent.Message.ReadSingle();
actorState.position.X = networkEvent.Message.ReadSingle();
actorState.position.Y = networkEvent.Message.ReadSingle();
actorState.position.Z = networkEvent.Message.ReadSingle();
actorState.velocity.X = networkEvent.Message.ReadSingle();
actorState.velocity.Y = networkEvent.Message.ReadSingle();
actorState.velocity.Z = networkEvent.Message.ReadSingle();
actorState.orientation.X = networkEvent.Message.ReadSingle();
actorState.orientation.Y = networkEvent.Message.ReadSingle();
actorState.orientation.Z = networkEvent.Message.ReadSingle();
actorState.orientation.W = networkEvent.Message.ReadSingle();
actorState.viewAngles.X = networkEvent.Message.ReadSingle();
actorState.viewAngles.Y = networkEvent.Message.ReadSingle();
actorState.viewAngles.Z = networkEvent.Message.ReadSingle();
if (NetworkManager.IsLocalClient && !NetworkManager.IsServer)
inputState.frame = reportedFrame;
inputState.viewDeltaX = networkEvent.Message.ReadSingle();
inputState.viewDeltaY = networkEvent.Message.ReadSingle();
inputState.moveForward = networkEvent.Message.ReadSingle();
inputState.moveRight = networkEvent.Message.ReadSingle();
inputState.attacking = networkEvent.Message.ReadBoolean();
inputState.jumping = networkEvent.Message.ReadBoolean();
//Assert.IsTrue(reportedFrame >= lastReceivedServerFrame);
if (reportedFrame < lastReceivedServerFrame)
{
Console.Print($"packet wrong order, received {lastReceivedServerFrame}, new {reportedFrame}");
break;
}
/*if (NetworkManager.IsLocalClient && !NetworkManager.IsServer)
{
}
else if (NetworkManager.IsServer)
else*/ if (NetworkManager.IsServer)
{
PlayerFrame playerFrame = GetPlayerFrame(playerId, reportedFrame);
Assert.Fail();
/*PlayerFrame playerFrame = GetPlayerFrame(playerId, reportedFrame);
PlayerActor playerActor = players[playerId];
if (playerFrame == null)
Console.Print("frame is in the past, unable to verify frame");
@@ -285,12 +391,8 @@ namespace Game
{
NetworkMessage message = NetworkManager.ServerBeginSendMessage();
message.WriteByte((byte)GameModeMessageType.PlayerPosition);
/*message.WriteUInt64(reportedFrame);
message.WriteUInt32(playerId);
message.WriteSingle(playerFramePosition.X);
message.WriteSingle(playerFramePosition.Y);
message.WriteSingle(playerFramePosition.Z);*/
message.WriteUInt64(worldState.frame);
message.WriteUInt64(serverWorldState.frame);
message.WriteUInt32(playerId);
message.WriteSingle(playerActor.Position.X);
message.WriteSingle(playerActor.Position.Y);
@@ -298,16 +400,26 @@ namespace Game
NetworkManager.ServerEndSendMessage(ref message, playerConnections[playerId]);
}
}
}
}*/
}
else if (NetworkManager.IsClient)
{
Console.Print($"we drifted, corrected. client frame: {worldState.frame}, server frame: {reportedFrame}");
lastReceivedServerFrame = reportedFrame;
//Console.Print($"we drifted, corrected. client frame: {serverWorldState.frame}, server frame: {reportedFrame}");
PlayerActor playerActor = Level.GetActors<PlayerActor>().FirstOrDefault(x =>
x.GetScript<PlayerMovement>().PlayerId == reportedPlayerId);
if (playerActor != null)
playerActor.SetPosition(reportedPosition);
{
PlayerInput playerInput = playerActor.GetScript<PlayerMovement>().input;
playerInput.SetState(reportedFrame, ref inputState, ref actorState);
{
}
//playerActor.SetPosition(reportedPosition);
}
}
break;
@@ -322,14 +434,14 @@ namespace Game
public static bool OnClientConnecting(NetworkConnection connection)
{
if (connection.ConnectionId != NetworkManager.LocalPlayerClientId)
//if (connection.ConnectionId != NetworkManager.LocalPlayerClientId)
{
Console.Print("sending welcome: frame " + worldState.frame);
Console.Print("sending welcome: frame " + serverWorldState.frame);
NetworkMessage message = NetworkManager.ServerBeginSendMessage();
message.WriteByte((byte)GameModeMessageType.WelcomePlayer);
message.WriteUInt64(worldState.frame);
message.WriteUInt32((uint)worldState.actors.Count);
foreach (PlayerActor playerActor in worldState.actors)
message.WriteUInt64(serverWorldState.frame);
message.WriteUInt32((uint)serverWorldState.actors.Count);
foreach (PlayerActor playerActor in serverWorldState.actors)
{
message.WriteUInt32(playerActor.GetScript<PlayerMovement>().PlayerId);
message.WriteSingle(playerActor.Position.X);
@@ -343,12 +455,16 @@ namespace Game
public static bool OnClientConnected(NetworkConnection connection)
{
uint playerId = connection.ConnectionId;
if (NetworkManager.LocalPlayerClientId == 0)
NetworkManager.LocalPlayerClientId = playerId;
var spawns = Level.GetActors<Actor>().Where(x => x.Name.StartsWith("PlayerSpawn_")).ToArray();
Console.Print($"found {spawns.Length} spawns");
var randomSpawn = spawns.First();
uint playerId = connection.ConnectionId;
Float3 position = randomSpawn.Position + new Float3(0f, 4.1f, 0f);
Float3 eulerAngles = randomSpawn.Orientation.EulerAngles;
@@ -357,7 +473,7 @@ namespace Game
var playerFrames = new PlayerFrame[120];
for (int i = 0; i < playerFrames.Length; i++)
playerFrames[i] = new PlayerFrame();
worldState.playerFrameHistory.Add(playerId, playerFrames);
serverWorldState.playerFrameHistory.Add(playerId, playerFrames);
SpawnPlayer(playerId, position, eulerAngles);
@@ -365,7 +481,7 @@ namespace Game
NetworkMessage message = NetworkManager.ServerBeginSendMessage();
message.WriteByte((byte)GameModeMessageType.SpawnPlayer);
message.WriteUInt32(playerId);
message.WriteUInt64(worldState.frame);
message.WriteUInt64(serverWorldState.frame);
message.WriteSingle(position.X);
message.WriteSingle(position.Y);
message.WriteSingle(position.Z);
@@ -383,7 +499,7 @@ namespace Game
if (NetworkManager.IsLocalClient)
return; // Handled by listenserver
spawned = true;
//spawned = true;
string prefabPath = Path.Combine(AssetManager.ContentPath, "Common");
var playerPrefab = Content.Load<Prefab>(Path.Combine(prefabPath, "PlayerPrefab.prefab"));
@@ -391,15 +507,16 @@ namespace Game
Console.PrintError("GameModeManager: Failed to find PlayerPrefab");
PlayerActor playerActor = PrefabManager.SpawnPrefab(playerPrefab).As<PlayerActor>();
playerActor.Initialize(playerId);
playerActor.Teleport(position, eulerAngles);
playerActor.Initialize(playerId, position, eulerAngles);
//playerActor.Teleport(position, eulerAngles);
if (!players.ContainsKey(playerId))
players.Add(playerId, null);
players[playerId] = playerActor;
PlayerInput playerInput = playerActor.GetScript<PlayerMovement>().input;
if (NetworkManager.IsServer)
worldState.actors.Add(playerActor);
serverWorldState.actors.Add(playerActor);
}
private static void UpdatePlayerInput(uint playerId, PlayerInputState inputState)

View File

@@ -1,4 +1,5 @@
using FlaxEditor.Content.Settings;
#if false
using FlaxEditor.Content.Settings;
using FlaxEngine;
using FlaxEngine.Networking;
using System;
@@ -88,6 +89,8 @@ namespace Game
currentGameMode.Start();
Console.Open();
return true;
}
@@ -122,9 +125,23 @@ namespace Game
private static void NetworkManager_ClientConnected(NetworkClient networkClient)
{
Console.Print("new client connected");
try
{
Console.Print("new client connected");
currentGameMode.OnPlayerSpawn(networkClient.ClientId);
foreach (var (playerId, playerActor) in currentGameMode.players)
{
NetworkReplicator.SpawnObject(playerActor, new[] { networkClient.ClientId });
playerActor.Initialize(playerId, playerActor.Position, playerActor.GetRotation());
}
currentGameMode.OnPlayerSpawn(networkClient.ClientId);
}
catch (Exception e)
{
Console.Print(e.ToString());
throw;
}
}
private static void NetworkManager_ClientConnecting(ref NetworkClientConnectionData arg0)
@@ -184,11 +201,18 @@ namespace Game
public void OnPlayerSpawn(uint clientId)
{
// Get random spawn
// Get a random spawn
var spawns = Level.GetActors<Actor>().Where(x => x.Name.StartsWith("PlayerSpawn_")).ToArray();
Console.Print($"found {spawns.Length} spawns");
var randomSpawn = spawns.First();
var randomSpawn = spawns.FirstOrDefault();
if (players.Count > 0)
randomSpawn = spawns.LastOrDefault();
if (randomSpawn == null)
{
Console.Print("No spawns found for player");
return;
}
Console.Print($"found {spawns.Length} spawns");
Float3 spawnPosition = randomSpawn.Position + new Float3(0f, 4.1f, 0f);
Float3 spawnAngles = randomSpawn.Orientation.EulerAngles;
@@ -201,12 +225,13 @@ namespace Game
PlayerActor playerActor = PrefabManager.SpawnPrefab(playerPrefab).As<PlayerActor>();
NetworkReplicator.AddObject(playerActor);
playerActor.Initialize(clientId);
playerActor.Initialize(clientId, spawnPosition, spawnAngles);
playerActor.Teleport(spawnPosition, spawnAngles);
//playerActor.Teleport(spawnPosition, spawnAngles);
//NetworkReplicator.SetObjectOwnership(playerActor, clientId);
NetworkReplicator.SpawnObject(playerActor);
players.Add(clientId, playerActor);
//playerActor.Initialize(clientId);
//playerActor.hai = 345;
@@ -232,4 +257,5 @@ namespace Game
return Float3.Zero;
}
}
}
}
#endif

View File

@@ -0,0 +1,150 @@
using System;
using System.Linq;
using FlaxEditor;
using FlaxEngine;
using FlaxEngine.Networking;
using Console = Game.Console;
namespace Game
{
[AttributeUsage(AttributeTargets.Class)]
public class NetworkPredictedAttribute : Attribute
{
}
// TODO: insert code to update variables with this attribute?
// rename to NetworkReplicatedAttribute?
[AttributeUsage(AttributeTargets.Class)]
public class NetworkedAttribute : Attribute
{
}
// NetworkMulticastAttribute: calls methods marked with this in all clients
public enum NetworkMessageType : byte
{
Handshake = 1,
Message
}
public static partial class NetworkManager
{
public delegate bool OnMessageDecl(ref NetworkEvent networkEvent);
private static bool initialized;
public static NetworkPeer server;
public static NetworkPeer client;
private static readonly ushort ServerPort = 59183;
private static string ServerAddress;
private static readonly ushort MTU = 1500;
private static readonly ushort MaximumClients = 32;
public static OnMessageDecl OnMessage;
public static bool IsServer = false;
public static bool IsClient = false;
public static bool IsLocalClient = false; // Context dependant, true when message is handled by local client
public static void Init()
{
if (initialized)
return;
/*if (Engine.CommandLine.Contains("-server"))
{
StartServer();
ServerAddress = "localhost";
ConnectServer();
}
else if (Engine.CommandLine.Contains("-client"))
{
ServerAddress = "localhost";
ConnectServer();
}
//#if FLAX_EDITOR
else
{
StartServer();
ServerAddress = "localhost";
ConnectServer();
}*/
//#endif
initialized = true;
#if FLAX_EDITOR
Editor.Instance.PlayModeEnd += Cleanup;
#endif
GameModeManager.Init(); // FIXME
}
public static void Cleanup()
{
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
Editor.Instance.PlayModeEnd -= Cleanup;
GameModeManager.Cleanup(); // FIXME
#endif
initialized = false;
}
private static void OnNetworkMessage(ref NetworkEvent networkEvent)
{
byte messageTypeByte = networkEvent.Message.ReadByte();
if (!Enum.IsDefined(typeof(NetworkMessageType), messageTypeByte))
{
Console.PrintError($"Unsupported message type received from client: {messageTypeByte}");
return;
}
NetworkMessageType messageType = (NetworkMessageType)messageTypeByte;
switch (messageType)
{
case NetworkMessageType.Handshake:
{
string message = networkEvent.Message.ReadString();
Console.Print($"Received handshake from {networkEvent.Sender.ConnectionId}, msg: " + message);
break;
}
case NetworkMessageType.Message:
{
if (OnMessage != null)
foreach (OnMessageDecl func in OnMessage.GetInvocationList()
.Cast<OnMessageDecl>().ToArray())
{
bool ret = func.Invoke(ref networkEvent);
if (ret)
break;
}
break;
}
default:
Console.PrintError($"Unsupported message type received from client: {messageTypeByte}");
break;
}
}
}
}

View File

@@ -0,0 +1,128 @@
using System;
using System.Linq;
using FlaxEngine;
using FlaxEngine.Networking;
using Console = Game.Console;
using Object = FlaxEngine.Object;
namespace Game
{
public static partial class NetworkManager
{
public static uint LocalPlayerClientId { get; /*private*/ set; }
public static INetworkDriver ClientNetworkDriver { get; set; }
public static bool ConnectServer(string serverAddress = "localhost", bool listenServer = false)
{
if (!listenServer)
Cleanup();
ServerAddress = serverAddress;
//var driver = Object.New(typeof(ENetDriver));
//ClientNetworkDriver = null;
NetworkLagDriver driver = Object.New<NetworkLagDriver>();
driver.Lag = 0f;
ClientNetworkDriver = driver;
client = NetworkPeer.CreatePeer(new NetworkConfig
{
NetworkDriver = driver,
ConnectionsLimit = MaximumClients,
MessagePoolSize = 2048,
MessageSize = MTU,
Address = ServerAddress == "localhost" ? "127.0.0.1" : ServerAddress,
Port = ServerPort
});
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 += OnClientUpdate;
if (!listenServer)
{
Scripting.Exit += Cleanup;
Level.ActorSpawned += OnClientActorSpawned;
}
return true;
}
private static void OnClientUpdate()
{
using Utilities.ScopeProfiler _ = Utilities.ProfileScope("NetworkManager_OnClientUpdate");
while (client.PopEvent(out NetworkEvent networkEvent))
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:
{
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();
}
}
private static void OnClientActorSpawned(Actor actor)
{
}
}
}

View File

@@ -0,0 +1,196 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using FlaxEngine;
using FlaxEngine.Networking;
using Console = Game.Console;
using Object = FlaxEngine.Object;
namespace Game
{
public static partial class NetworkManager
{
private static List<NetworkConnection> ConnectedClients;
private static List<Type> NetworkedTypes;
public static INetworkDriver ServerNetworkDriver { get; set; }
public static bool StartServer(bool listenServer = true)
{
ConnectedClients = new List<NetworkConnection>(MaximumClients);
//var driver = Object.New(typeof(ENetDriver));
//ServerNetworkDriver = null;
NetworkLagDriver driver = Object.New<NetworkLagDriver>();
driver.Lag = 200f;
ServerNetworkDriver = driver;
server = NetworkPeer.CreatePeer(new NetworkConfig
{
NetworkDriver = driver,
ConnectionsLimit = MaximumClients,
MessagePoolSize = 2048,
MessageSize = MTU,
Address = "any",
Port = ServerPort
});
if (!server.Listen())
{
Console.PrintError("Failed to start the server.");
return false;
}
Scripting.FixedUpdate += OnServerUpdate;
Scripting.Exit += Cleanup;
Level.ActorSpawned += OnServerActorSpawned;
NetworkedTypes = new List<Type>();
#if false
var assemblies = Utils.GetAssemblies();
foreach (Assembly assembly in assemblies)
{
// Skip common assemblies
string assemblyName = assembly.GetName().Name;
if (assemblyName == "System" ||
assemblyName.StartsWith("System.") ||
assemblyName.StartsWith("Mono.") ||
assemblyName == "mscorlib" ||
assemblyName == "Newtonsoft.Json" ||
assemblyName == "Snippets" ||
assemblyName == "netstandard" ||
assemblyName == "Anonymously Hosted DynamicMethods Assembly" ||
assemblyName.StartsWith("FlaxEngine.") ||
assemblyName.StartsWith("JetBrains.") ||
assemblyName.StartsWith("Microsoft.") ||
assemblyName.StartsWith("nunit."))
continue;
foreach (Type type in assembly.GetTypes())
if (type.GetCustomAttributes().Any(x => x is NetworkedAttribute))
NetworkedTypes.Add(type);
}
foreach (Type type in NetworkedTypes)
Console.Print("tracking networked type: " + type.Name);
#endif
if (listenServer)
return ConnectServer(listenServer: true);
return true;
}
public static NetworkMessage ServerBeginSendMessage()
{
NetworkMessage message = server.BeginSendMessage();
message.WriteByte((byte)NetworkMessageType.Message);
return message;
}
public static void ServerEndSendMessage(ref NetworkMessage message, NetworkConnection connection, NetworkChannelType channelType = NetworkChannelType.Reliable)
{
server.EndSendMessage(channelType, message, connection);
}
public static NetworkMessage ClientBeginSendMessage()
{
NetworkMessage message = client.BeginSendMessage();
message.WriteByte((byte)NetworkMessageType.Message);
return message;
}
public static void ClientEndSendMessage(ref NetworkMessage message, NetworkChannelType channelType = NetworkChannelType.Reliable)
{
client.EndSendMessage(channelType, message);
}
private static void OnServerUpdate()
{
using Utilities.ScopeProfiler _ = Utilities.ProfileScope("NetworkManager_OnServerUpdate");
while (server.PopEvent(out NetworkEvent networkEvent))
switch (networkEvent.EventType)
{
case NetworkEventType.Connected:
{
Console.Print($"Client({networkEvent.Sender.ConnectionId}) is trying to connect");
try
{
IsServer = true;
if (GameModeManager.OnClientConnecting(networkEvent.Sender))
{
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");
}
finally
{
IsServer = false;
}
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)
{
//Console.Print($"actor spawned: {actor.Name} ({actor.TypeName})");
}
}
}

View File

@@ -0,0 +1,50 @@
using System.Collections.Generic;
using FlaxEngine;
using FlaxEngine.Assertions;
namespace Game;
public static class NetworkWorld
{
private class WorldState
{
public ulong frame;
public List<PlayerActor> actors;
public Dictionary<PlayerActor, PlayerLagCompStates> playerFrameHistory;
public WorldState()
{
actors = new List<PlayerActor>();
playerFrameHistory = new Dictionary<PlayerActor, PlayerLagCompStates>();
}
}
private struct PlayerLagCompState
{
public Float3 position;
}
private class PlayerLagCompStates
{
public ulong oldestFrame;
private PlayerLagCompState[] states;
public PlayerLagCompStates(int frames)
{
states = new PlayerLagCompState[frames];
}
public PlayerLagCompState GetState(ulong frame)
{
Assert.IsTrue(frame >= oldestFrame);
ulong index = frame % (ulong)states.Length;
return states[index];
}
public void SetState(PlayerLagCompState state, ulong frame)
{
ulong index = frame % (ulong)states.Length;
states[index] = state;
}
}
}

View File

@@ -135,7 +135,7 @@ namespace Game
outVertices = finalPoints.ToArray();
//verts = new QuickHull().QuickHull2(points);
//outVertices = verts.ToArray();
//outVertices = verts.ToArray(); frf f
}
private MapEntity root;

View File

@@ -35,12 +35,16 @@ namespace Game
}
#endif
public class PlayerActor : RigidBody
public class SomeActor : EmptyActor
{
}
public class PlayerActor : RigidBody//, INetworkSerializable
{
private PlayerMovement playerMovement;
private RigidBody playerRigidBody;
[NetworkReplicated]
//[NetworkReplicated]
public uint PlayerId = uint.MaxValue;
/*public PlayerActor()
@@ -65,12 +69,17 @@ namespace Game
base.OnBeginPlay();
playerMovement = FindScript<PlayerMovement>();
playerRigidBody = FindActor<RigidBody>();
//playerRigidBody = FindActor<RigidBody>();
//Console.Print("OnBeginPlay playerid: " + PlayerId.ToString());
//playerMovement.input = new PlayerInputNetwork();
}
public override void OnEnable()
{
// Trigger OnEnable manually, does not seem 8to propagate when parent gets enabled/disabled
// Trigger OnEnable manually, does not seem to propagate when parent gets enabled/disabled
playerMovement.Enabled = true;
//NetworkReplicator.AddObject(this);
}
@@ -81,29 +90,37 @@ namespace Game
//NetworkReplicator.RemoveObject(this);
}
[NetworkRpc(client: true)]
public void Initialize(uint playerId)
//[NetworkRpc(client: true)]
public void Initialize(uint playerId, Float3 newPosition, Float3 eulerAngles)
{
if (PlayerId == playerId) // FIXME
return;
FindActor("PlayerModel").IsActive = true;
IsActive = true;
PlayerId = playerId;
playerMovement.SetInput(playerId);
if (playerId == NetworkManager.LocalClientId)
if (playerId == NetworkManager.LocalPlayerClientId)
{
FindActor("CameraHolder").IsActive = true;
//FindActor("ViewModelHolder").IsActive = true;
FindActor("PlayerModel").IsActive = false;
}
else
FindActor("PlayerModel").IsActive = true;
IsActive = true;
SetPosition(newPosition);
SetRotation(eulerAngles);
//else
// FindActor("PlayerModel").IsActive = true;
//IsActive = true;
}
[NetworkRpc(server: true)]
public void UpdateNetworkInput(ulong frame, Float4 viewDeltaXYMoveForwardRight, bool attacking, bool jumping)
//[NetworkRpc(server: true)]
public void UpdateNetworkInput(PlayerInputState inputState/*, Float4 viewDeltaXYMoveForwardRight, bool attacking, bool jumping*/)
{
if (playerMovement.input is not PlayerInputNetwork playerInputNetwork)
return;
PlayerInputState inputState = new PlayerInputState(frame, viewDeltaXYMoveForwardRight.X, viewDeltaXYMoveForwardRight.Y, viewDeltaXYMoveForwardRight.Z, viewDeltaXYMoveForwardRight.W, attacking, jumping);
//PlayerInputState inputState = new PlayerInputState(frame, viewDeltaXYMoveForwardRight.X, viewDeltaXYMoveForwardRight.Y, viewDeltaXYMoveForwardRight.Z, viewDeltaXYMoveForwardRight.W, attacking, jumping);
playerInputNetwork.currentState.input = inputState;
}
@@ -117,7 +134,12 @@ namespace Game
playerMovement.ResetRotation(eulerAngles);
}
[NetworkRpc(client: true)]
public Float3 GetRotation()
{
return playerMovement.viewAngles;
}
//[NetworkRpc(client: true)]
public void Teleport(Float3 newPosition, Float3 eulerAngles)
{
SetPosition(newPosition);

View File

@@ -87,6 +87,11 @@ namespace Game
public const byte DemoVer = 1;
public PlayerState currentState;
public ulong frame;
//public ulong oldestFrame;
private PlayerState[] states = new PlayerState[120];
public virtual bool Predict => false;
public virtual void OnUpdate()
{
@@ -98,12 +103,43 @@ namespace Game
public virtual void OnEndFrame()
{
states[frame % 120] = currentState;
/*ulong oldest = ulong.MaxValue;
for (int i = 0; i < 120; i++)
oldest = states[i].input.frame < oldest ? states[i].input.frame : oldest;
oldestFrame = oldest;*/
frame++;
}
public virtual void RecordCurrentActorState(PlayerActorState actorState)
{
}
public bool GetState(ulong frame, out PlayerInputState inputState, out PlayerActorState actorState)
{
int frameIndex = (int)frame % 120;
if (states[frameIndex].input.frame != frame)
{
inputState = default;
actorState = default;
return false;
}
inputState = states[frameIndex].input;
actorState = states[frameIndex].actor;
return true;
}
public void SetState(ulong frame, ref PlayerInputState inputState, ref PlayerActorState actorState)
{
int frameIndex = (int)frame % 120;
states[frameIndex].input = inputState;
states[frameIndex].input.frame = frame;
states[frameIndex].actor = actorState;
}
public PlayerInputState GetCurrentInputState()
{
return currentState.input;

View File

@@ -1,8 +1,10 @@
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 Console = Game.Console;
@@ -26,49 +28,46 @@ namespace Game
if (!File.Exists(demoPath))
return;
int expectedPlayerInputStateSize = Marshal.SizeOf(typeof(PlayerInputState));
int expectedPlayerInputStateSize = Unsafe.SizeOf<PlayerInputState>();
FileStream fileStream = File.OpenRead(demoPath);
GZipStream stream = new GZipStream(fileStream, CompressionMode.Decompress);
Stopwatch sw = Stopwatch.StartNew();
using FileStream fileStream = File.OpenRead(demoPath);
using GZipStream stream = new GZipStream(fileStream, CompressionMode.Decompress);
int ver = stream.ReadByte();
int inputStateSize = stream.ReadByte();
if (ver != DemoVer && inputStateSize != expectedPlayerInputStateSize)
if (ver != DemoVer || inputStateSize != expectedPlayerInputStateSize)
{
Console.Print("demover mismatch: version " + ver + " != " + DemoVer + ", inputStateSize " +
inputStateSize + " != " + Marshal.SizeOf(typeof(PlayerInputState)));
inputStateSize + " != " + Unsafe.SizeOf<PlayerInputState>());
stream.Close();
return;
}
// TODO: bench and compare to unsafe ptr version of this:
// https://stackoverflow.com/questions/17549123/c-sharp-performance-using-unsafe-pointers-instead-of-intptr-and-marshal/29836312#29836312
T RawDeserialize<T>(byte[] rawData, int position)
{
int rawsize = Marshal.SizeOf(typeof(T));
if (rawsize > rawData.Length - position)
throw new ArgumentException("Not enough data to fill struct. Array length from position: " +
(rawData.Length - position) + ", Struct length: " + rawsize);
IntPtr buffer = Marshal.AllocHGlobal(rawsize);
Marshal.Copy(rawData, position, buffer, rawsize);
T retobj = (T)Marshal.PtrToStructure(buffer, typeof(T));
Marshal.FreeHGlobal(buffer);
return retobj;
}
Span<byte> b = stackalloc byte[expectedPlayerInputStateSize];
while (true)
{
byte[] b = new byte[expectedPlayerInputStateSize];
int readBytes = stream.Read(b, 0, b.Length);
if (readBytes < expectedPlayerInputStateSize)
break;
int bytesLeftInBuffer = expectedPlayerInputStateSize;
do
{
int readBytes = stream.Read(b.Slice(expectedPlayerInputStateSize - bytesLeftInBuffer, bytesLeftInBuffer));
if (readBytes == 0)
break;
bytesLeftInBuffer -= readBytes;
} while (bytesLeftInBuffer > 0);
buffer.Add(RawDeserialize<PlayerInputState>(b, 0));
if (bytesLeftInBuffer > 0)
break; // EOF;
buffer.Add(MemoryMarshal.Read<PlayerInputState>(b));
}
sw.Stop();
bufferEnumerable = buffer.GetEnumerator();
Console.Print("demo numstates: " + buffer.Count);
Console.Print($"Demo parse time {sw.Elapsed.TotalMilliseconds}ms, frames: {buffer.Count} ");
OnEndFrame(); // advances to first frame
}

View File

@@ -1,7 +1,9 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.IO.Compression;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using FlaxEngine;
using FlaxEngine.Networking;
@@ -12,16 +14,19 @@ namespace Game
public class PlayerInputLocal : PlayerInput
{
protected List<PlayerInputState> buffer = new List<PlayerInputState>();
protected GZipStream demoFileStream;
protected FileStream demoFileStream2;
protected GZipStream demoStream;
protected FileStream demoFileStream;
private PlayerActor playerActor;
//public bool IsNetworked => NetworkManager.client != null;
public bool IsNetworked => NetworkManager.client != null;
private long flushedFrames = 0;
/*public PlayerInputLocal()
{
}*/
public override bool Predict => true;
public PlayerInputLocal(PlayerActor playerActor, string demoPath)
{
this.playerActor = playerActor;
@@ -29,15 +34,13 @@ namespace Game
if (!demoFolder.Exists)
Directory.CreateDirectory(demoFolder.FullName);
demoFileStream2 = File.Open(demoPath, FileMode.Create, FileAccess.Write);
demoFileStream = new GZipStream(demoFileStream2, CompressionMode.Compress);
//stream.Position = 0;
//stream.SetLength(0);
demoFileStream.WriteByte(DemoVer);
demoFileStream.WriteByte((byte)Marshal.SizeOf(typeof(PlayerInputState)));
demoFileStream = File.Open(demoPath, FileMode.Create, FileAccess.Write);
demoStream = new GZipStream(demoFileStream, CompressionMode.Compress);
demoStream.WriteByte(DemoVer);
demoStream.WriteByte((byte)Unsafe.SizeOf<PlayerInputState>());
}
public bool IsRecording => demoFileStream != null;
public bool IsRecording => demoStream != null;
public override void OnUpdate()
{
@@ -82,11 +85,12 @@ namespace Game
if (playerActor != null)
{
playerActor.UpdateNetworkInput(currentState.input.frame, new Float4(currentState.input.viewDeltaX, currentState.input.viewDeltaY, currentState.input.moveForward, currentState.input.moveRight), currentState.input.attacking, currentState.input.jumping);
//playerActor.UpdateNetworkInput(currentState.input.frame, new Float4(currentState.input.viewDeltaX, currentState.input.viewDeltaY, currentState.input.moveForward, currentState.input.moveRight), currentState.input.attacking, currentState.input.jumping);
}
//playerActor.UpdateNetworkInput(currentState.input.frame, currentState.input.viewDeltaX, currentState.input.viewDeltaY, currentState.input.moveForward, currentState.input.moveRight, currentState.input.attacking, currentState.input.jumping, currentState.input.verificationPosition, currentState.input.verificationVelocity, currentState.input.verificationViewAngles, currentState.input.verificationOrientation);
/*if (IsNetworked)
if (IsNetworked)
{
var message = NetworkManager.ClientBeginSendMessage();
message.WriteByte((byte)GameModeMessageType.PlayerInput);
@@ -98,13 +102,13 @@ namespace Game
message.WriteBoolean(currentState.input.attacking);
message.WriteBoolean(currentState.input.jumping);
NetworkManager.ClientEndSendMessage(ref message);
}*/
}
// Reset anything accumulatable here
currentState.input.viewDeltaX = 0;
currentState.input.viewDeltaY = 0;
frame++;
base.OnEndFrame();
}
public override void RecordCurrentActorState(PlayerActorState actorState)
@@ -122,24 +126,21 @@ namespace Game
if (!IsRecording)
return;
byte[] RawSerialize(object anything)
Stopwatch sw = Stopwatch.StartNew();
Span<byte> bytes = stackalloc byte[Unsafe.SizeOf<PlayerInputState>()];
foreach (ref PlayerInputState state in CollectionsMarshal.AsSpan(buffer))
{
int rawSize = Marshal.SizeOf(anything);
IntPtr buffer = Marshal.AllocHGlobal(rawSize);
Marshal.StructureToPtr(anything, buffer, false);
byte[] rawDatas = new byte[rawSize];
Marshal.Copy(buffer, rawDatas, 0, rawSize);
Marshal.FreeHGlobal(buffer);
return rawDatas;
}
foreach (PlayerInputState state in buffer)
{
byte[] bytes = RawSerialize(state);
demoFileStream.Write(bytes, 0, bytes.Length * sizeof(byte));
MemoryMarshal.Write(bytes, ref state);
demoStream.Write(bytes);
}
sw.Stop();
flushedFrames += buffer.Count;
buffer.Clear();
FlaxEngine.Debug.Write(LogType.Info, $"Wrote demo in {sw.Elapsed.TotalMilliseconds}ms, frames: {flushedFrames}");
}
public void StopRecording()
@@ -148,10 +149,10 @@ namespace Game
return;
FlushDemo();
demoStream.Close();
demoStream = null;
demoFileStream.Close();
demoFileStream = null;
Debug.Write(LogType.Info, "demo, wrote states: " + buffer.Count);
}
}
}

View File

@@ -2,5 +2,12 @@
{
public class PlayerInputNetwork : PlayerInput
{
public override bool Predict => true;
public override void OnEndFrame()
{
currentState.input.frame = frame;
base.OnEndFrame();
}
}
}

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using FlaxEngine;
using FlaxEngine.Assertions;
using FlaxEngine.Networking;
@@ -79,11 +80,11 @@ namespace Game
private readonly InputEvent onExit = new InputEvent("Exit");
private readonly bool predicting = false;
private bool predicting = false;
private readonly List<PhysicsColliderActor> touchingActors = new List<PhysicsColliderActor>();
public int currentInputFrame;
private int currentInputFrame2;
//public int currentInputFrame;
//private int currentInputFrame2;
private Float3 currentVelocity;
public PlayerInput input;
@@ -92,7 +93,7 @@ namespace Game
private bool jumped;
private int lastInputFrame;
//private int lastInputFrame;
private float lastJumped = -1f;
private float lastLanded = -1f;
private int numJumps;
@@ -105,7 +106,7 @@ namespace Game
private Actor rootActor;
private float startupTime;
private Float3 viewAngles;
public Float3 viewAngles;
private Float3 viewAnglesLastFrame;
[Limit(0, 9000)]
@@ -114,7 +115,7 @@ namespace Game
private static Float3 Gravity { get; } = new Float3(0, -800.0f, 0f);
//private Float3 safePosition;
//private Float3 safePosition;444 rg
[NetworkReplicated]
public uint PlayerId = 0;
@@ -173,7 +174,7 @@ namespace Game
Assert.IsTrue(playerId != uint.MaxValue);
PlayerId = playerId;
if (PlayerId == NetworkManager.LocalClientId)//if (NetworkReplicator.GetObjectRole(this.Parent) == NetworkObjectRole.OwnedAuthoritative)// if (playerId == NetworkManager.LocalPlayerClientId)
if (PlayerId == NetworkManager.LocalPlayerClientId)//if (NetworkReplicator.GetObjectRole(this.Parent) == NetworkObjectRole.OwnedAuthoritative)// if (playerId == NetworkManager.LocalPlayerClientId)
{
Console.Print("local player?: " + playerId.ToString());
string demoPath = System.IO.Path.Combine(AssetManager.DemoPath, $"{DateTimeOffset.Now.UtcTicks}.gdem");
@@ -290,24 +291,26 @@ namespace Game
viewPitch = viewPitch,
viewRoll = viewRoll
});*/
currentInputFrame2++;
//currentInputFrame2++;
}
private ulong lastPredictedFrame = 0;
public override void OnFixedUpdate()
{
if (input is PlayerInputDemo)
input.OnUpdate();
PlayerInputDemo demoInput = input as PlayerInputDemo;
if (demoInput != null)
demoInput.OnUpdate();
float deltadif = Time.DeltaTime - 1.0f / Time.PhysicsFPS;
//if (Math.Abs(deltadif) > 0.0001f)
// Console.Print("drift: " + deltadif);
float timeDeltaDiff = Time.DeltaTime - 1.0f / Time.PhysicsFPS;
if (Math.Abs(timeDeltaDiff) > 0.0001f)
Console.Print("Time.DeltaTime is not stable: " + timeDeltaDiff);
input.OnFixedUpdate();
PlayerInputState inputState = input.GetCurrentInputState();
if (input is PlayerInputDemo)
if (demoInput != null && demoInput.IsPlaying)
{
ApplyInputToCamera(inputState, false);
ApplyInputToCamera(inputState, true);
// Verify view angles first
if (demoDeltasVerify)
@@ -316,43 +319,75 @@ namespace Game
float viewAnglesDelta = (viewAngles - verifAngles).Length;
if (viewAnglesDelta > 0.00001)
{
Console.PrintError($"Demo verification failed, view angles delta: {viewAnglesDelta}, viewAngles:{viewAngles}, verif:{verifAngles}");
Console.Print($"Demo verification failed, view angles delta: {viewAnglesDelta}, viewAngles:{viewAngles}, verif:{verifAngles}");
if (demoDeltasCorrect)
SetCameraEulerAngles(verifAngles, false);
}
}
}
SimulatePlayerMovement(inputState);
SimulatePlayerMovement(inputState);
if (input is PlayerInputDemo && demoDeltasVerify)
{
// verify
float positionDelta = (Actor.Position - inputState.verificationPosition).Length;
if (positionDelta > 0.00001)
Console.PrintError("Demo verification failed, position delta: " + positionDelta);
float velocityDelta = (currentVelocity - inputState.verificationVelocity).Length;
if (velocityDelta > 0.00001)
Console.PrintError("Demo verification failed, velocity delta: " + velocityDelta);
float orientationDelta = (rootActor.Orientation - inputState.verificationOrientation).Length;
if (orientationDelta > 0.00001)
if (demoDeltasVerify)
{
Console.PrintError("Demo verification failed, orientation delta: " + orientationDelta);
if (demoDeltasCorrect)
// verify
float positionDelta = (Actor.Position - inputState.verificationPosition).Length;
if (positionDelta > 0.00001)
Console.Print("Demo verification failed, position delta: " + positionDelta);
float velocityDelta = (currentVelocity - inputState.verificationVelocity).Length;
if (velocityDelta > 0.00001)
Console.Print("Demo verification failed, velocity delta: " + velocityDelta);
float orientationDelta = (rootActor.Orientation - inputState.verificationOrientation).Length;
if (orientationDelta > 0.00001)
{
Console.Print("Demo verification failed, orientation delta: " + orientationDelta);
if (demoDeltasCorrect)
{
}
}
//if (currentInputFrame == 0)
/*{
//Console.Print("repos: " + inputState.verificationPosition);
Actor.Position = inputState.verificationPosition;
currentVelocity = inputState.verificationVelocity;
rootActor.Orientation = inputState.verificationOrientation;
}*/
}
}
else
{
if (input.Predict && GameModeManager.ClientFrame > 0 && GameModeManager.ServerFrame > 0)
{
predicting = true;
for (ulong currentFrame = GameModeManager.ServerFrame; currentFrame < GameModeManager.ClientFrame - 1; currentFrame++)
{
if (!input.GetState(currentFrame, out var pastInputState, out var pastActorState))
{
Console.Print($"predict failure: {currentFrame}");
break;
}
if (currentFrame == GameModeManager.ServerFrame)
{
Actor.Position = pastActorState.position;
currentVelocity = pastActorState.velocity;
rootActor.Orientation = pastActorState.orientation;
viewAngles = new Float3(pastActorState.viewAngles.Y, pastActorState.viewAngles.X, pastActorState.viewAngles.Z);
}
SimulatePlayerMovement(pastInputState);
//Console.Print($"predicted: {currentFrame}");
}
predicting = false;
//Console.Print($"current: {inputState.frame}");
//if (GameModeManager.ClientFrame - GameModeManager.ServerFrame > 0)
// Console.Print($"current diff: {GameModeManager.ClientFrame - GameModeManager.ServerFrame}");
}
//if (currentInputFrame == 0)
/*{
//Console.Print("repos: " + inputState.verificationPosition);
Actor.Position = inputState.verificationPosition;
currentVelocity = inputState.verificationVelocity;
rootActor.Orientation = inputState.verificationOrientation;
}*/
SimulatePlayerMovement(inputState);
}
input.RecordCurrentActorState(new PlayerActorState
@@ -365,8 +400,8 @@ namespace Game
input.OnEndFrame();
lastInputFrame = currentInputFrame;
currentInputFrame++;
//lastInputFrame = currentInputFrame;
//currentInputFrame++;
viewAnglesLastFrame = viewAngles;
}

View File

@@ -28,6 +28,13 @@ public class GameTarget : GameProjectTarget
Modules.Add("Game");
}
public override void SetupTargetEnvironment(BuildOptions options)
{
base.SetupTargetEnvironment(options);
options.LinkEnv.UseFastPDBLinking = true;
}
public override string GetOutputFilePath(BuildOptions options, TargetOutputType? outputType = null)
{
if (!Environment.CommandLine.Contains("Cooker")) // Hacky way to detect if this is run during cooking