initial commit

This commit is contained in:
2025-07-09 21:15:31 +03:00
commit 7b809a2c60
228 changed files with 504652 additions and 0 deletions

54
Assets/Network/Client.cs Normal file
View File

@@ -0,0 +1,54 @@
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using UnityEngine;
using Object = UnityEngine.Object;
namespace FastArena
{
public static class Client
{
private static TcpClient _tcp;
private static UdpClient _udp;
public static void SendUserAction()
{
throw new System.NotImplementedException();
}
public static void Connect(IPAddress ip, short portFirst)
{
_tcp = new TcpClient();
_udp = new UdpClient();
_tcp.BeginConnect(ip, portFirst, TCPConnectCallback, null);
_udp.BeginSend(StructBinaryConverter.GetBytes(new ConnectionRequestUDP()))
}
private static void TCPConnectCallback(IAsyncResult ar)
{
_tcp.EndConnect(ar);
}
private static Dictionary<int, SyncTransform> _syncTransforms = new();
private static void CreateGameObject(int id, ref CreateObjectPacket packet)
{
var prefab = Resources.Load(packet.prefabPath, typeof(GameObject));
var go = (GameObject)Object.Instantiate(prefab);
var idComponent = go.AddComponent<GameObjectId>();
idComponent._SetId(packet.gameObjectId);
if (go.TryGetComponent(out SyncTransform syncTransform))
{
_syncTransforms.Add(packet.gameObjectId, syncTransform);
}
}
private static void UpdateObjectTransform(int id, ref TransformUpdatePacket packet)
{
if(!_syncTransforms.TryGetValue(id, out var component))
return;
component.UpdateTransform(ref packet);
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 8c0ca1a557b3445bb080f944580f6393
timeCreated: 1719762842

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 8390671da7da4562881b1dc29107131f
timeCreated: 1752080612

View File

@@ -0,0 +1,21 @@
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using UnityEngine;
namespace FastArena
{
internal struct Hash256
[StructLayout(LayoutKind.Sequential)]
internal readonly struct ConnectionRequestTCP
{
internal readonly PacketHeader header;
private SHA256 _sha256;
public ConnectionRequestTCP()
{
header = new PacketHeader(PacketType.ConnectionRequestTCP);
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 973e6f96c4a74c8299406cfe25e29414
timeCreated: 1752080764

View File

@@ -0,0 +1,18 @@
using System.Runtime.InteropServices;
namespace FastArena;
[StructLayout(LayoutKind.Sequential)]
internal readonly struct CreateObjectPacket
{
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;
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: a0bc94f4527d493d88a018b32d36ae4f
timeCreated: 1752080638

View File

@@ -0,0 +1,32 @@
using System.Runtime.InteropServices;
namespace FastArena
{
internal enum PacketType : ushort
{
Invalid = 0, // not packet
ConnectionRequestTCP,
ConnectionRequestUDP,
ConnectionResponseTCP,
ConnectionResponseUDP,
TransformUpdate,
CreateObject
}
[StructLayout(LayoutKind.Sequential, Pack = 4)]
internal readonly struct PacketHeader
{
internal const ushort MAGIC_CONST = 0x6313;
internal readonly ushort magic;
internal readonly PacketType type;
internal PacketHeader(PacketType t)
{
magic = MAGIC_CONST;
type = t;
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: c5d47bd361a840ab95f8411ad0d2458a
timeCreated: 1719857658

View File

@@ -0,0 +1,15 @@
namespace FastArena;
internal static class PacketParser
{
internal static PacketType ReadHeader(byte[] data)
{
var h = StructBinaryConverter.ReadStruct<PacketHeader>(data);
return h.magic == PacketHeader.MAGIC_CONST ? h.type : PacketType.Invalid;
}
internal static T ReadPacket<T>(byte[] data) where T : unmanaged
{
return StructBinaryConverter.ReadStruct<T>(data);
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 6f0a0f3489a547308cf0e57b45bed6c6
timeCreated: 1752080621

View File

@@ -0,0 +1,21 @@
using System.Runtime.InteropServices;
using UnityEngine;
namespace FastArena;
[StructLayout(LayoutKind.Sequential)]
internal readonly struct TransformUpdatePacket
{
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;
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: b57b439f5bd74ca1a4fdbe3621fae51d
timeCreated: 1752080633

92
Assets/Network/Server.cs Normal file
View File

@@ -0,0 +1,92 @@
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using UnityEngine;
namespace FastArena
{
public class ServerConfig
{
public int PortFirst = 26782;
public int MaxPlayers = 20;
}
public static class Server
{
private static UdpClient _udp;
private static TcpListener _tcp;
public class ClientConnection
{
// public int id { get; private set; }
private TcpClient _tcp;
public ClientConnection(TcpClient tcp)
{
_tcp = tcp;
}
public IPEndPoint GetEndPoint()
{
return (IPEndPoint)_tcp.Client.RemoteEndPoint;
}
}
public static List<ClientConnection> connections = new List<ClientConnection>();
public static void Start(ServerConfig config)
{
_tcp = new TcpListener(IPAddress.Any, config.PortFirst);
_udp = new UdpClient(config.PortFirst + 1);
_tcp.Start();
_tcp.BeginAcceptTcpClient(TcpAcceptCallback, null);
_udp.BeginReceive(UdpReceiveCallback, null);
}
private static void TcpAcceptCallback(IAsyncResult ar)
{
var tcpClient = _tcp.EndAcceptTcpClient(ar);
var connection = new ClientConnection(tcpClient);
connections.Add(connection);
}
private static void UdpReceiveCallback(IAsyncResult ar)
{
IPEndPoint clientEndPoint = new IPEndPoint(IPAddress.None, 0);
byte[] data = _udp.EndReceive(ar, ref clientEndPoint);
// begin receiving new data immediately
_udp.BeginReceive(UdpReceiveCallback, null);
PacketType t = PacketParser.ReadHeader(data);
switch (t)
{
case PacketType.Invalid:
break;
default:
Debug.Log($"received unexpected packet of type {t}");
break;
}
}
private static void UdpSendTo(byte[] data, IPEndPoint cliendEnd)
{
_udp.BeginSend(data, data.Length, cliendEnd, null, null);
}
private static void UdpSendTo(byte[] data, ClientConnection client)
{
UdpSendTo(data, client.GetEndPoint());
}
public static void SendTransformUpdate(int id, Transform transform)
{
byte[] data = StructBinaryConverter.GetBytes(new TransformUpdatePacket(id, transform));
foreach(var p in connections)
{
UdpSendTo(data, p);
}
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 9e44812093d941899b0c6458f2218197
timeCreated: 1719762837

View File

@@ -0,0 +1,55 @@
using System;
using System.Runtime.InteropServices;
namespace FastArena
{
public static class StructBinaryConverter
{
public static byte[] GetBytes<T>(T s) where T : unmanaged
{
return GetBytes(ref s);
}
public static byte[] GetBytes<T>(ref T s) where T : unmanaged
{
int size = Marshal.SizeOf(typeof(T));
byte[] buffer = System.Buffers.ArrayPool<byte>.Shared.Rent(Marshal.SizeOf<T>());
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<byte>.Shared.Return(buffer);
}
}
public static T ReadStruct<T>(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();
}
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 55c4eabd2994422da052a6dd70a1bdf4
timeCreated: 1719866226

View File

@@ -0,0 +1,43 @@
using System;
using UnityEngine;
namespace FastArena
{
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;
}
}
/// <summary>
/// Component fetching GameObject's Transform from the server every tick.
/// </summary>
public class SyncTransform : MonoBehaviour
{
public GameObjectId IdRef { get; private set; }
private void Start()
{
IdRef = GetComponent<GameObjectId>();
}
#if UNITY_SERVER
private void FixedUpdate()
{
Server.SendTransformUpdate(IdRef.Id, transform);
}
#else
internal void UpdateTransform(ref TransformUpdatePacket data)
{
transform.position = data.position;
transform.rotation = data.rotation;
}
#endif
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a4a82b5feb6640b4f914a1743053b1e5
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: