Skip to content

Commit

Permalink
feat: implement Flakkari4Unity library
Browse files Browse the repository at this point in the history
  • Loading branch information
MasterLaplace committed Nov 18, 2024
1 parent f7d8292 commit ceb142b
Show file tree
Hide file tree
Showing 5 changed files with 907 additions and 0 deletions.
127 changes: 127 additions & 0 deletions Libraries/Flakkari4Unity/Examples/NetworkClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
using System;
using System.Net;
using System.Net.Sockets;
using UnityEngine;

using Flk_API = Flakkari4Unity.API;
using CurrentProtocol = Flakkari4Unity.Protocol.V1;
using System.Collections.Generic;

public class NetworkClient : MonoBehaviour
{
private UdpClient udpClient;
private IPEndPoint serverEndpoint;
private readonly float keepAliveInterval = 3;
[SerializeField] private string serverIP = "127.0.0.1";
[SerializeField] private int serverPort = 54000;
[SerializeField] private string gameName = "R-Type";
[SerializeField] private bool enable;

public NetworkClient(string serverIP, int serverPort, string gameName)
{
this.serverIP = serverIP;
this.serverPort = serverPort;
this.gameName = gameName;
enable = true;
}

void Start()
{
if (!enable)
{
Destroy(this);
return;
}

udpClient = new UdpClient();
serverEndpoint = new IPEndPoint(IPAddress.Parse(serverIP), serverPort);

udpClient.BeginReceive(OnReceive, null);

byte[] packet = Flk_API.APIClient.ReqConnect(gameName);
udpClient.Send(packet, packet.Length, serverEndpoint);
InvokeRepeating(nameof(ReqKeepAlive), keepAliveInterval, keepAliveInterval);
}

public bool Enable
{
get => enable;
set => enable = value;
}

internal void Send(byte[] packet)
{
if (udpClient != null)
{
udpClient.Send(packet, packet.Length, serverEndpoint);
}
else
{
Debug.LogError("UDP client is not initialized.");
}
}

private void ReqKeepAlive()
{
byte[] packet = Flk_API.APIClient.ReqKeepAlive();
udpClient.Send(packet, packet.Length, serverEndpoint);
}

private void OnReceive(IAsyncResult result)
{
if (!enable)
return;

try
{
byte[] receivedData = udpClient.EndReceive(result, ref serverEndpoint);
Flk_API.APIClient.Reply(receivedData, out List<CurrentProtocol.CommandId> commandId, out List<uint> sequenceNumber, out List<byte[]> payload);

for (int i = 0; i < commandId.Count; i++)
{
switch (commandId[i])
{
case CurrentProtocol.CommandId.REP_CONNECT:
Debug.Log("REP_CONNECT message received from the server.");
break;
case CurrentProtocol.CommandId.REP_HEARTBEAT:
Debug.Log("REP_HEARTBEAT message received from the server.");
break;
case CurrentProtocol.CommandId.REQ_ENTITY_SPAWN:
Debug.Log("REQ_ENTITY_SPAWN message received from the server.");
break;
case CurrentProtocol.CommandId.REQ_ENTITY_UPDATE:
Debug.Log("REQ_ENTITY_UPDATE message received from the server.");
break;
case CurrentProtocol.CommandId.REQ_ENTITY_DESTROY:
Debug.Log("REQ_ENTITY_DESTROY message received from the server.");
break;
case CurrentProtocol.CommandId.REP_USER_UPDATES:
Debug.Log("REP_USER_UPDATES message received from the server.");
break;
case CurrentProtocol.CommandId.REP_USER_UPDATE:
Debug.Log("REP_USER_UPDATE message received from the server.");
break;
default:
Debug.LogWarning("Unknown command ID received from the server.");
break;
}
}
}
catch (Exception e)
{
Debug.LogError("Error in OnReceive: " + e.Message);
}
udpClient.BeginReceive(OnReceive, null);
}

private void OnApplicationQuit()
{
if (!enable)
return;
enable = false;

udpClient.Close();
Debug.Log("UDP client closed.");
}
}
115 changes: 115 additions & 0 deletions Libraries/Flakkari4Unity/Source/API.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using UnityEngine;

using CurrentProtocol = Flakkari4Unity.Protocol.V1;

namespace Flakkari4Unity.API
{
public class APIClient : MonoBehaviour
{
/// <summary>
/// Creates a REQ_CONNECT message to connect to the server.
/// </summary>
/// <param name="gameName">The name of the game to connect to.</param>
/// <returns>A byte array representing the serialized REQ_CONNECT message.</returns>
public static byte[] ReqConnect(string gameName)
{
Debug.Log("REQ_CONNECT message sent to the server.");
return CurrentProtocol.Packet.Serialize(
CurrentProtocol.Priority.HIGH,
CurrentProtocol.CommandId.REQ_CONNECT,
System.Text.Encoding.UTF8.GetBytes(gameName)
);
}

/// <summary>
/// Creates a REQ_HEARTBEAT message to keep the connection alive.
/// </summary>
/// <returns>A byte array representing the serialized REQ_HEARTBEAT message.</returns>
/// <remarks>
/// This message is sent to the server at regular intervals to keep the connection alive.
/// </remarks>
public static byte[] ReqKeepAlive()
{
// Debug.Log("REQ_HEARTBEAT message sent to the server.");
return CurrentProtocol.Packet.Serialize(
CurrentProtocol.Priority.LOW,
CurrentProtocol.CommandId.REQ_HEARTBEAT
);
}

public static byte[] ReqUserUpdates(List<CurrentProtocol.Event> events, Dictionary<CurrentProtocol.EventId, float> axisEvents)
{
byte[] eventCountBytes = BitConverter.GetBytes((ushort)events.Count);
byte[] serializedEvents = CurrentProtocol.Event.Serialize(events);
byte[] axisEventCountBytes = BitConverter.GetBytes((ushort)axisEvents.Count);
byte[] serializedAxisEvents = CurrentProtocol.Event.Serialize(axisEvents);

byte[] payload = ConcatByteArrays(eventCountBytes, serializedEvents);
payload = ConcatByteArrays(payload, axisEventCountBytes);
payload = ConcatByteArrays(payload, serializedAxisEvents);

Debug.Log("REQ_USER_UPDATE message sent to the server.");
return CurrentProtocol.Packet.Serialize(
CurrentProtocol.Priority.HIGH,
CurrentProtocol.CommandId.REQ_USER_UPDATES,
payload
);
}

public static byte[] ReqUserUpdate(CurrentProtocol.EventId id, CurrentProtocol.EventState state)
{
CurrentProtocol.Event _event = new()
{
id = id,
state = state
};

Debug.Log("REQ_USER_UPDATE message sent to the server.");
return CurrentProtocol.Packet.Serialize(
CurrentProtocol.Priority.HIGH,
CurrentProtocol.CommandId.REQ_USER_UPDATE,
CurrentProtocol.Event.Serialize(_event)
);
}

/// <summary>
/// Processes the received data and extracts the command ID, sequence number, and message.
/// </summary>
/// <param name="receivedData">The byte array containing the received data.</param>
/// <param name="commandIds">The extracted command IDs from the received data.</param>
/// <param name="sequenceNumbers">The extracted sequence numbers from the received data.</param>
/// <param name="payloads">The extracted payloads from the received data.</param>
public static void Reply(byte[] receivedData, out List<CurrentProtocol.CommandId> commandIds, out List<uint> sequenceNumbers, out List<byte[]> payloads)
{
commandIds = new List<CurrentProtocol.CommandId>();
sequenceNumbers = new List<uint>();
payloads = new List<byte[]>();

while (receivedData.Length > 0)
{
CurrentProtocol.Packet.Deserialize(receivedData, out CurrentProtocol.CommandId commandId, out uint sequenceNumber, out byte[] payload);

Debug.Log($"[RECEIVED] CommandId: {commandId}, delay: {sequenceNumber - (uint)DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()} ms, payload: {payload?.Length} bytes");

commandIds.Add(commandId);
sequenceNumbers.Add(sequenceNumber);
payloads.Add(payload);

int headerSize = Marshal.SizeOf<CurrentProtocol.Header>();
receivedData = receivedData.Skip(headerSize + (payload?.Length ?? 0)).ToArray();
}
}

private static byte[] ConcatByteArrays(byte[] first, byte[] second)
{
byte[] result = new byte[first.Length + second.Length];
Buffer.BlockCopy(first, 0, result, 0, first.Length);
Buffer.BlockCopy(second, 0, result, first.Length, second.Length);
return result;
}
}
} // namespace Flakkari4Unity.API
8 changes: 8 additions & 0 deletions Libraries/Flakkari4Unity/Source/Entity.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Entity : MonoBehaviour
{
public ulong Id;
}
Loading

0 comments on commit ceb142b

Please sign in to comment.