diff --git a/.gitignore b/.gitignore index 20526e9..dd49250 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ UserSettings *.csproj *.sln *.DotSettings +*.user .idea/ # some random downloaded assets Assets/imported/ diff --git a/Assets/Entities/Mortal.cs b/Assets/Entities/Mortal.cs index 568787f..3fccdee 100644 --- a/Assets/Entities/Mortal.cs +++ b/Assets/Entities/Mortal.cs @@ -27,7 +27,7 @@ namespace FastArena public void Kill() { - Destroy(this.gameObject); + Destroy(gameObject); } } } \ No newline at end of file diff --git a/Assets/Network/Client.cs b/Assets/Network/Client.cs index 8165b45..c991134 100644 --- a/Assets/Network/Client.cs +++ b/Assets/Network/Client.cs @@ -2,37 +2,105 @@ using System.Collections.Generic; using System.Net; using System.Net.Sockets; +using System.Runtime.InteropServices; +using System.Threading; +using FastArena.Network.Packets; using UnityEngine; using Object = UnityEngine.Object; -namespace FastArena +namespace FastArena.Network { + public enum ClientStatus + { + Initialized, Connected, Disconnected + } + + public class ServerConnection + { + public IPAddress Ip; + public short PortFirst; + public Guid SessionId; + public byte[] ServerKey; + } + public static class Client { private static TcpClient _tcp; private static UdpClient _udp; + private static byte[] _clientKey; + private static ServerConnection _serverConnection; + + public static ClientStatus Status { get; private set; } = ClientStatus.Initialized; public static void SendUserAction() { - throw new System.NotImplementedException(); + throw new NotImplementedException(); } public static void Connect(IPAddress ip, short portFirst) { _tcp = new TcpClient(); _udp = new UdpClient(); + _serverConnection = new ServerConnection() + { + Ip = ip, PortFirst = portFirst + }; + _udp.Connect(ip, portFirst + 1); _tcp.BeginConnect(ip, portFirst, TCPConnectCallback, null); - _udp.BeginSend(StructBinaryConverter.GetBytes(new ConnectionRequestUDP())) } + private static void ReadExactly(this TcpClient tcp, byte[] buffer, int offset, int exactSize, int timeoutMs = 5000) + { + int msPassed = 0; + while (tcp.Available < exactSize) + { + Thread.Sleep(1); + msPassed += 1; + if (msPassed > timeoutMs) + { + throw new TimeoutException( + $"couldn't get {exactSize} bytes from socket, {tcp.Available} avaliable"); + } + } + + int readN = _tcp.GetStream().Read(buffer, offset, exactSize); + if (readN != exactSize) + throw new Exception($"expected to read {exactSize} bytes, got {readN}"); + } + private static void TCPConnectCallback(IAsyncResult ar) { _tcp.EndConnect(ar); + + // receive first response from server + byte[] buffer = new byte[4096]; + _tcp.ReadExactly(buffer, 0, Marshal.SizeOf()); + //TODO: implement decryption + var firstResponse = StructBinaryConverter.ReadStruct(buffer); + firstResponse.ValidateResponseCode(); + _serverConnection.SessionId = firstResponse.sessionId; + _serverConnection.ServerKey = firstResponse.serverKey; + + // connect udp socket associated with current session + byte[] byteMessage = StructBinaryConverter.GetBytes(new ConnectionRequestUDP(_serverConnection.SessionId)); + //TODO: implement encryption + _udp.Send(byteMessage, byteMessage.Length); + + // receive second response from server + IPEndPoint remoteEndpoint = new IPEndPoint(IPAddress.None, 0); + byteMessage = _udp.Receive(ref remoteEndpoint); + //TODO: implement decryption + var secondResponse = StructBinaryConverter.ReadStruct(byteMessage); + secondResponse.ValidateResponseCode(); + + //TODO: start tcp read loop + //TODO: start udp read loop } - private static Dictionary _syncTransforms = new(); + + private static readonly Dictionary _syncTransforms = new Dictionary(); - private static void CreateGameObject(int id, ref CreateObjectPacket packet) + private static void CreateGameObject(ref CreateObjectPacket packet) { var prefab = Resources.Load(packet.prefabPath, typeof(GameObject)); var go = (GameObject)Object.Instantiate(prefab); @@ -44,9 +112,9 @@ namespace FastArena } } - private static void UpdateObjectTransform(int id, ref TransformUpdatePacket packet) + private static void UpdateObjectTransform(ref TransformUpdatePacket packet) { - if(!_syncTransforms.TryGetValue(id, out var component)) + if(!_syncTransforms.TryGetValue(packet.gameObjectId, out var component)) return; component.UpdateTransform(ref packet); } diff --git a/Assets/Network/GameObjectId.cs b/Assets/Network/GameObjectId.cs new file mode 100644 index 0000000..19029ac --- /dev/null +++ b/Assets/Network/GameObjectId.cs @@ -0,0 +1,17 @@ +using System; +using UnityEngine; + +namespace FastArena.Network +{ + public class GameObjectId : MonoBehaviour + { + public int Id { get; private set; } = -1; + + internal void _SetId(int packetGameObjectId) + { + if (Id != -1) + throw new Exception($"GameObjectId is already set to {Id}"); + Id = packetGameObjectId; + } + } +} \ No newline at end of file diff --git a/Assets/Network/GameObjectId.cs.meta b/Assets/Network/GameObjectId.cs.meta new file mode 100644 index 0000000..5e4297c --- /dev/null +++ b/Assets/Network/GameObjectId.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: a0274d4d086949a880c5e0b4a9e5cf20 +timeCreated: 1752248822 \ No newline at end of file diff --git a/Assets/Network/Packets/ConnectionPackets.cs b/Assets/Network/Packets/ConnectionPackets.cs index 5e83d62..b0d26ec 100644 --- a/Assets/Network/Packets/ConnectionPackets.cs +++ b/Assets/Network/Packets/ConnectionPackets.cs @@ -1,21 +1,75 @@ -using System.Runtime.InteropServices; -using System.Security.Cryptography; -using UnityEngine; +using System; +using System.Runtime.InteropServices; -namespace FastArena +namespace FastArena.Network.Packets { - internal struct Hash256 - [StructLayout(LayoutKind.Sequential)] internal readonly struct ConnectionRequestTCP { internal readonly PacketHeader header; - private SHA256 _sha256; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 256)] + internal readonly byte[] clientKey; - public ConnectionRequestTCP() + public ConnectionRequestTCP(byte[] clientKey) { header = new PacketHeader(PacketType.ConnectionRequestTCP); - + this.clientKey = clientKey; + } + } + + [StructLayout(LayoutKind.Sequential)] + internal readonly struct ConnectionResponseTCP + { + internal readonly PacketHeader header; + internal readonly StatusCode statusCode; + internal readonly Guid sessionId; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 256)] + internal readonly byte[] serverKey; + + public ConnectionResponseTCP(StatusCode statusCode, Guid sessionId, byte[] serverKey) + { + header = new PacketHeader(PacketType.ConnectionResponseTCP); + this.statusCode = statusCode; + this.sessionId = sessionId; + this.serverKey = serverKey; + } + + public void ValidateResponseCode() + { + if (statusCode != StatusCode.OK) + throw new StatusCodeException(statusCode); + } + } + + [StructLayout(LayoutKind.Sequential)] + internal readonly struct ConnectionRequestUDP + { + internal readonly PacketHeader header; + internal readonly Guid sessionId; + + public ConnectionRequestUDP(Guid sessionId) + { + header = new PacketHeader(PacketType.ConnectionRequestUDP); + this.sessionId = sessionId; + } + } + + [StructLayout(LayoutKind.Sequential)] + internal readonly struct ConnectionResponseUDP + { + internal readonly PacketHeader header; + internal readonly StatusCode statusCode; + + public ConnectionResponseUDP(StatusCode statusCode) + { + header = new PacketHeader(PacketType.ConnectionResponseUDP); + this.statusCode = statusCode; + } + + public void ValidateResponseCode() + { + if (statusCode != StatusCode.OK) + throw new StatusCodeException(statusCode); } } } \ No newline at end of file diff --git a/Assets/Network/Packets/CreateObjectPacket.cs b/Assets/Network/Packets/CreateObjectPacket.cs index 8ba7ef4..2d911e6 100644 --- a/Assets/Network/Packets/CreateObjectPacket.cs +++ b/Assets/Network/Packets/CreateObjectPacket.cs @@ -1,18 +1,19 @@ using System.Runtime.InteropServices; -namespace FastArena; - -[StructLayout(LayoutKind.Sequential)] -internal readonly struct CreateObjectPacket +namespace FastArena.Network.Packets { - internal readonly PacketHeader header; - internal readonly int gameObjectId; - internal readonly string prefabPath; - - internal CreateObjectPacket(int _gameObjectId, string _prefabPath) + [StructLayout(LayoutKind.Sequential)] + internal readonly struct CreateObjectPacket { - header = new PacketHeader(PacketType.CreateObject); - gameObjectId = _gameObjectId; - prefabPath = _prefabPath; + internal readonly PacketHeader header; + internal readonly int gameObjectId; + internal readonly string prefabPath; + + internal CreateObjectPacket(int _gameObjectId, string _prefabPath) + { + header = new PacketHeader(PacketType.CreateObject); + gameObjectId = _gameObjectId; + prefabPath = _prefabPath; + } } } \ No newline at end of file diff --git a/Assets/Network/Packets/PacketHeader.cs b/Assets/Network/Packets/PacketHeader.cs index 8b52847..7b7b79a 100644 --- a/Assets/Network/Packets/PacketHeader.cs +++ b/Assets/Network/Packets/PacketHeader.cs @@ -1,6 +1,6 @@ using System.Runtime.InteropServices; -namespace FastArena +namespace FastArena.Network.Packets { internal enum PacketType : ushort { diff --git a/Assets/Network/Packets/PacketParser.cs b/Assets/Network/Packets/PacketParser.cs index 11424e1..352874f 100644 --- a/Assets/Network/Packets/PacketParser.cs +++ b/Assets/Network/Packets/PacketParser.cs @@ -1,15 +1,16 @@ -namespace FastArena; - -internal static class PacketParser +namespace FastArena.Network.Packets { - internal static PacketType ReadHeader(byte[] data) + internal static class PacketParser { - var h = StructBinaryConverter.ReadStruct(data); - return h.magic == PacketHeader.MAGIC_CONST ? h.type : PacketType.Invalid; - } + internal static PacketType ReadHeader(byte[] data) + { + var h = StructBinaryConverter.ReadStruct(data); + return h.magic == PacketHeader.MAGIC_CONST ? h.type : PacketType.Invalid; + } - internal static T ReadPacket(byte[] data) where T : unmanaged - { - return StructBinaryConverter.ReadStruct(data); + internal static T ReadPacket(byte[] data) where T : unmanaged + { + return StructBinaryConverter.ReadStruct(data); + } } } \ No newline at end of file diff --git a/Assets/Network/Packets/StructBinaryConverter.cs b/Assets/Network/Packets/StructBinaryConverter.cs new file mode 100644 index 0000000..6364c40 --- /dev/null +++ b/Assets/Network/Packets/StructBinaryConverter.cs @@ -0,0 +1,46 @@ +using System.Runtime.InteropServices; + +namespace FastArena.Network.Packets +{ + public static class StructBinaryConverter + { + public static byte[] GetBytes(T s) where T : struct + { + return GetBytes(ref s); + } + + public static byte[] GetBytes(ref T s) where T : struct + { + int size = Marshal.SizeOf(); + // TODO: implement array pool + byte[] buffer = new byte[size]; + // tel GC to not move the buffer in memory + var handle = GCHandle.Alloc(buffer, GCHandleType.Pinned); + try + { + Marshal.StructureToPtr(s, handle.AddrOfPinnedObject(), false); + } + finally + { + handle.Free(); + } + return buffer; + } + + public static T ReadStruct(byte[] buffer) where T : struct + { + T s; + // tel GC to not move the buffer in memory + var handle = GCHandle.Alloc(buffer, GCHandleType.Pinned); + try + { + s = (T)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(T)); + } + finally + { + handle.Free(); + } + return s; + } + } +} \ No newline at end of file diff --git a/Assets/Network/StructBinaryConverter.cs.meta b/Assets/Network/Packets/StructBinaryConverter.cs.meta similarity index 100% rename from Assets/Network/StructBinaryConverter.cs.meta rename to Assets/Network/Packets/StructBinaryConverter.cs.meta diff --git a/Assets/Network/Packets/TransformUpdatePacket.cs b/Assets/Network/Packets/TransformUpdatePacket.cs index 8951f4c..031e061 100644 --- a/Assets/Network/Packets/TransformUpdatePacket.cs +++ b/Assets/Network/Packets/TransformUpdatePacket.cs @@ -1,21 +1,22 @@ using System.Runtime.InteropServices; using UnityEngine; -namespace FastArena; - -[StructLayout(LayoutKind.Sequential)] -internal readonly struct TransformUpdatePacket +namespace FastArena.Network.Packets { - internal readonly PacketHeader header; - internal readonly int gameObjectId; - internal readonly Vector3 position; - internal readonly Quaternion rotation; - - internal TransformUpdatePacket(int _gameObjectId, Transform transform) + [StructLayout(LayoutKind.Sequential)] + internal readonly struct TransformUpdatePacket { - header = new PacketHeader(PacketType.TransformUpdate); - gameObjectId = _gameObjectId; - position = transform.position; - rotation = transform.rotation; + internal readonly PacketHeader header; + internal readonly int gameObjectId; + internal readonly Vector3 position; + internal readonly Quaternion rotation; + + internal TransformUpdatePacket(int _gameObjectId, Transform transform) + { + header = new PacketHeader(PacketType.TransformUpdate); + gameObjectId = _gameObjectId; + position = transform.position; + rotation = transform.rotation; + } } } \ No newline at end of file diff --git a/Assets/Network/Server.cs b/Assets/Network/Server.cs index ca50c77..c551ee2 100644 --- a/Assets/Network/Server.cs +++ b/Assets/Network/Server.cs @@ -2,9 +2,10 @@ using System.Collections.Generic; using System.Net; using System.Net.Sockets; +using FastArena.Network.Packets; using UnityEngine; -namespace FastArena +namespace FastArena.Network { public class ServerConfig { @@ -33,7 +34,7 @@ namespace FastArena } } - public static List connections = new List(); + public static readonly List Connections = new List(); public static void Start(ServerConfig config) { @@ -49,7 +50,7 @@ namespace FastArena { var tcpClient = _tcp.EndAcceptTcpClient(ar); var connection = new ClientConnection(tcpClient); - connections.Add(connection); + Connections.Add(connection); } private static void UdpReceiveCallback(IAsyncResult ar) @@ -83,7 +84,7 @@ namespace FastArena public static void SendTransformUpdate(int id, Transform transform) { byte[] data = StructBinaryConverter.GetBytes(new TransformUpdatePacket(id, transform)); - foreach(var p in connections) + foreach(var p in Connections) { UdpSendTo(data, p); } diff --git a/Assets/Network/StatusCode.cs b/Assets/Network/StatusCode.cs new file mode 100644 index 0000000..dbf69f2 --- /dev/null +++ b/Assets/Network/StatusCode.cs @@ -0,0 +1,22 @@ +using System; + +namespace FastArena.Network +{ + public enum StatusCode + { + OK, + ServerError, + AccessNotAllowed + } + + public class StatusCodeException : Exception + { + public StatusCode Code; + + public StatusCodeException(StatusCode code) : + base($"Status code {(int)code} ({code})") + { + Code = code; + } + } +} \ No newline at end of file diff --git a/Assets/Network/StatusCode.cs.meta b/Assets/Network/StatusCode.cs.meta new file mode 100644 index 0000000..5c3fe62 --- /dev/null +++ b/Assets/Network/StatusCode.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 6ef3bf843e27438cbaae508f73654ec6 +timeCreated: 1752246290 \ No newline at end of file diff --git a/Assets/Network/StructBinaryConverter.cs b/Assets/Network/StructBinaryConverter.cs deleted file mode 100644 index 2ab33a9..0000000 --- a/Assets/Network/StructBinaryConverter.cs +++ /dev/null @@ -1,55 +0,0 @@ -using System; -using System.Runtime.InteropServices; - -namespace FastArena -{ - public static class StructBinaryConverter - { - public static byte[] GetBytes(T s) where T : unmanaged - { - return GetBytes(ref s); - } - - public static byte[] GetBytes(ref T s) where T : unmanaged - { - int size = Marshal.SizeOf(typeof(T)); - byte[] buffer = System.Buffers.ArrayPool.Shared.Rent(Marshal.SizeOf()); - - var handle = GCHandle.Alloc(buffer, GCHandleType.Pinned); - - unsafe - { - fixed (T* s_ptr = &s) - { - Marshal.Copy((IntPtr)s_ptr, buffer, 0, size); - Marshal.StructureToPtr(); - } - } - try - { - Marshal.StructureToPtr(msg_struct, handle.AddrOfPinnedObject(), false); - return buffer; - } - finally - { - handle.Free(); - ArrayPool.Shared.Return(buffer); - } - - } - - public static T ReadStruct(byte[] buffer) where T : unmanaged - { - GCHandle handle = GCHandle.Alloc(buffer, GCHandleType.Pinned); - try - { - T s = (T)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(T)); - return s; - } - finally - { - handle.Free(); - } - } - } -} \ No newline at end of file diff --git a/Assets/Network/SyncTransform.cs b/Assets/Network/SyncTransform.cs index 0b92f9a..c906a57 100644 --- a/Assets/Network/SyncTransform.cs +++ b/Assets/Network/SyncTransform.cs @@ -1,20 +1,8 @@ -using System; +using FastArena.Network.Packets; using UnityEngine; -namespace FastArena +namespace FastArena.Network { - public class GameObjectId : MonoBehaviour - { - public int Id { get; private set; } = -1; - - internal void _SetId(int packetGameObjectId) - { - if (Id != -1) - throw new Exception($"GameObjectId is already set to {Id}"); - Id = packetGameObjectId; - } - } - /// /// Component fetching GameObject's Transform from the server every tick. /// diff --git a/Packages/manifest.json b/Packages/manifest.json index 4597d37..d454f1e 100644 --- a/Packages/manifest.json +++ b/Packages/manifest.json @@ -1,7 +1,7 @@ { "dependencies": { - "com.unity.ide.rider": "3.0.28", - "com.unity.ide.visualstudio": "2.0.22", + "com.unity.ide.rider": "3.0.36", + "com.unity.ide.visualstudio": "2.0.23", "com.unity.ide.vscode": "1.2.5", "com.unity.textmeshpro": "2.1.6", "com.unity.ugui": "1.0.0", diff --git a/Packages/packages-lock.json b/Packages/packages-lock.json index f44d5a4..a58cc8c 100644 --- a/Packages/packages-lock.json +++ b/Packages/packages-lock.json @@ -8,7 +8,7 @@ "url": "https://packages.unity.com" }, "com.unity.ide.rider": { - "version": "3.0.28", + "version": "3.0.36", "depth": 0, "source": "registry", "dependencies": { @@ -17,7 +17,7 @@ "url": "https://packages.unity.com" }, "com.unity.ide.visualstudio": { - "version": "2.0.22", + "version": "2.0.23", "depth": 0, "source": "registry", "dependencies": {