initial
This commit is contained in:
Executable
+19
@@ -0,0 +1,19 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<LangVersion>10</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<TargetFrameworks>net6.0;netstandard2.1</TargetFrameworks>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ENet-CSharp" Version="2.4.8" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
||||
<PackageReference Include="NLog" Version="5.0.0-rc2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Ragon.Common\Ragon.Common.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,2 @@
|
||||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=sources/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
||||
Executable
+83
@@ -0,0 +1,83 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using DisruptorUnity3d;
|
||||
using NetStack.Serialization;
|
||||
using NLog;
|
||||
using Ragon.Common.Protocol;
|
||||
|
||||
namespace Ragon.Core
|
||||
{
|
||||
public class Application : IDisposable
|
||||
{
|
||||
private readonly Logger _logger = LogManager.GetCurrentClassLogger();
|
||||
private readonly List<RoomThread> _roomThreads = new();
|
||||
private readonly Dictionary<uint, RoomThread> _socketByRoomThreads = new();
|
||||
private readonly Dictionary<RoomThread, int> _roomThreadCounter = new();
|
||||
|
||||
private readonly ENetServer _socketServer;
|
||||
public Application(PluginFactory factory, Configuration configuration, int threadsCount)
|
||||
{
|
||||
_socketServer = new ENetServer();
|
||||
|
||||
for (var i = 0; i < threadsCount; i++)
|
||||
{
|
||||
var roomThread = new RoomThread(factory);
|
||||
_roomThreadCounter.Add(roomThread, 0);
|
||||
_roomThreads.Add(roomThread);
|
||||
}
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
_socketServer.Start(5000);
|
||||
|
||||
foreach (var roomThread in _roomThreads)
|
||||
roomThread.Start();
|
||||
|
||||
while (true)
|
||||
{
|
||||
foreach (var roomThread in _roomThreads)
|
||||
while (roomThread.ReadOutEvent(out var evnt))
|
||||
_socketServer.WriteEvent(evnt);
|
||||
|
||||
while (_socketServer.ReadEvent(out var evnt))
|
||||
{
|
||||
if (evnt.Type == EventType.CONNECTED)
|
||||
{
|
||||
var roomThread = _roomThreads.First();
|
||||
_roomThreadCounter[roomThread] += 1;
|
||||
_socketByRoomThreads.Add(evnt.PeerId, roomThread);
|
||||
}
|
||||
|
||||
if (_socketByRoomThreads.TryGetValue(evnt.PeerId, out var existsRoomThread))
|
||||
existsRoomThread.WriteInEvent(evnt);
|
||||
|
||||
if (evnt.Type == EventType.DISCONNECTED)
|
||||
{
|
||||
_socketByRoomThreads.Remove(evnt.PeerId, out var roomThread);
|
||||
_roomThreadCounter[roomThread] =- 1;
|
||||
}
|
||||
|
||||
if (evnt.Type == EventType.TIMEOUT)
|
||||
{
|
||||
_socketByRoomThreads.Remove(evnt.PeerId, out var roomThread);
|
||||
_roomThreadCounter[roomThread] =- 1;
|
||||
}
|
||||
}
|
||||
|
||||
Thread.Sleep(1);
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
foreach (var roomThread in _roomThreads)
|
||||
roomThread.Dispose();
|
||||
|
||||
_roomThreads.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
Executable
+17
@@ -0,0 +1,17 @@
|
||||
using NLog;
|
||||
|
||||
namespace Ragon.Core
|
||||
{
|
||||
public class Bootstrap
|
||||
{
|
||||
private ILogger _logger = LogManager.GetCurrentClassLogger();
|
||||
|
||||
public void Configure(PluginFactory factory)
|
||||
{
|
||||
_logger.Info("Configure application...");
|
||||
var configuration = ConfigurationLoader.Load("config.json");
|
||||
var app = new Application(factory, configuration, 2);
|
||||
app.Start();
|
||||
}
|
||||
}
|
||||
}
|
||||
Executable
+7
@@ -0,0 +1,7 @@
|
||||
namespace Ragon.Core
|
||||
{
|
||||
public class Configuration
|
||||
{
|
||||
public string[] blacklist;
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using Newtonsoft.Json;
|
||||
using NLog;
|
||||
using Logger = NLog.Logger;
|
||||
|
||||
namespace Ragon.Core
|
||||
{
|
||||
public static class ConfigurationLoader
|
||||
{
|
||||
private static readonly Logger _logger = LogManager.GetCurrentClassLogger();
|
||||
private static readonly string _serverVersion = "2.0.0-preview";
|
||||
|
||||
private static void CopyrightInfo()
|
||||
{
|
||||
_logger.Info($"Server Version: {_serverVersion}");
|
||||
_logger.Info($"Machine Name: {Environment.MachineName}");
|
||||
_logger.Info($"OS: {Environment.OSVersion}");
|
||||
_logger.Info($"Processors: {Environment.ProcessorCount}");
|
||||
_logger.Info($"Runtime Version: {Environment.Version}");
|
||||
|
||||
_logger.Info("==================================");
|
||||
_logger.Info("= =");
|
||||
_logger.Info($"={"Yohoho Server".PadBoth(32)}=");
|
||||
_logger.Info("= =");
|
||||
_logger.Info("==================================");
|
||||
}
|
||||
|
||||
public static Configuration Load(string filePath)
|
||||
{
|
||||
CopyrightInfo();
|
||||
|
||||
var data = File.ReadAllText(filePath);
|
||||
var configuration = JsonConvert.DeserializeObject<Configuration>(data);
|
||||
return configuration;
|
||||
}
|
||||
}
|
||||
}
|
||||
Executable
+142
@@ -0,0 +1,142 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Threading;
|
||||
using DisruptorUnity3d;
|
||||
using ENet;
|
||||
using NLog;
|
||||
using NLog.LayoutRenderers.Wrappers;
|
||||
|
||||
namespace Ragon.Core
|
||||
{
|
||||
public enum Status
|
||||
{
|
||||
Stopped,
|
||||
Listening,
|
||||
Disconnecting,
|
||||
Connecting,
|
||||
Assigning,
|
||||
Connected
|
||||
}
|
||||
|
||||
public enum DeliveryType
|
||||
{
|
||||
UnreliableUnsequenced,
|
||||
UnreliableSequenced,
|
||||
UnreliableFragmented,
|
||||
ReliableSequenced
|
||||
}
|
||||
|
||||
|
||||
public class ENetServer : IDisposable
|
||||
{
|
||||
public Status Status { get; private set; }
|
||||
|
||||
private ILogger _logger = LogManager.GetCurrentClassLogger();
|
||||
private Thread _thread;
|
||||
private Host _host;
|
||||
private Address _address;
|
||||
private ENet.Event _netEvent;
|
||||
private Peer[] _peers;
|
||||
private RingBuffer<Event> _receiveBuffer;
|
||||
private RingBuffer<Event> _sendBuffer;
|
||||
public void WriteEvent(Event evnt) => _sendBuffer.Enqueue(evnt);
|
||||
public bool ReadEvent(out Event evnt) => _receiveBuffer.TryDequeue(out evnt);
|
||||
|
||||
public void Start(ushort port)
|
||||
{
|
||||
Library.Initialize();
|
||||
|
||||
_address = default;
|
||||
_address.Port = port;
|
||||
|
||||
_host = new Host();
|
||||
_host.Create(_address, 4095, 2, 0, 0, 1024 * 1024);
|
||||
|
||||
_peers = new Peer[4095];
|
||||
_sendBuffer = new RingBuffer<Event>(8192 + 8192);
|
||||
_receiveBuffer = new RingBuffer<Event>(8192 + 8192);
|
||||
|
||||
Status = Status.Listening;
|
||||
|
||||
_thread = new Thread(Execute);
|
||||
_thread.Name = "NetworkThread";
|
||||
_thread.Start();
|
||||
_logger.Info($"Socket Server Started at port {port}");
|
||||
}
|
||||
|
||||
private void Execute()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
while (_sendBuffer.TryDequeue(out var data))
|
||||
{
|
||||
var newPacket = new Packet();
|
||||
newPacket.Create(data.Data, data.Data.Length, PacketFlags.Reliable);
|
||||
_peers[data.PeerId].Send(0, ref newPacket);
|
||||
}
|
||||
|
||||
bool polled = false;
|
||||
while (!polled)
|
||||
{
|
||||
if (_host.CheckEvents(out _netEvent) <= 0)
|
||||
{
|
||||
if (_host.Service(16, out _netEvent) <= 0)
|
||||
break;
|
||||
|
||||
polled = true;
|
||||
}
|
||||
|
||||
switch (_netEvent.Type)
|
||||
{
|
||||
case ENet.EventType.None:
|
||||
Console.WriteLine("None event");
|
||||
break;
|
||||
|
||||
case ENet.EventType.Connect:
|
||||
{
|
||||
var @event = new Event {PeerId = _netEvent.Peer.ID, Type = EventType.CONNECTED};
|
||||
// Console.WriteLine("Client connected - ID: " + _netEvent.Peer.ID + ", IP: " + _netEvent.Peer.IP);
|
||||
_peers[_netEvent.Peer.ID] = _netEvent.Peer;
|
||||
_receiveBuffer.Enqueue(@event);
|
||||
break;
|
||||
}
|
||||
case ENet.EventType.Disconnect:
|
||||
{
|
||||
var @event = new Event {PeerId = _netEvent.Peer.ID, Type = EventType.DISCONNECTED};
|
||||
// Console.WriteLine("Client disconnected - ID: " + _netEvent.Peer.ID + ", IP: " + _netEvent.Peer.IP);
|
||||
_receiveBuffer.Enqueue(@event);
|
||||
break;
|
||||
}
|
||||
case ENet.EventType.Timeout:
|
||||
{
|
||||
// Console.WriteLine("Client timeout - ID: " + _netEvent.Peer.ID + ", IP: " + _netEvent.Peer.IP);
|
||||
var @event = new Event {PeerId = _netEvent.Peer.ID, Type = EventType.TIMEOUT};
|
||||
// Console.WriteLine("Client disconnected - ID: " + _netEvent.Peer.ID + ", IP: " + _netEvent.Peer.IP);
|
||||
_receiveBuffer.Enqueue(@event);
|
||||
break;
|
||||
}
|
||||
case ENet.EventType.Receive:
|
||||
{
|
||||
// Console.WriteLine("Packet received from - ID: " + _netEvent.Peer.ID + ", IP: " + _netEvent.Peer.IP + ", Channel ID: " + _netEvent.ChannelID + ", Data length: " + _netEvent.Packet.Length);
|
||||
var data = new byte[_netEvent.Packet.Length];
|
||||
_netEvent.Packet.CopyTo(data);
|
||||
_netEvent.Packet.Dispose();
|
||||
|
||||
var @event = new Event {PeerId = _netEvent.Peer.ID, Type = EventType.DATA, Data = data};
|
||||
_receiveBuffer.Enqueue(@event);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Library.Deinitialize();
|
||||
|
||||
_host?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Ragon.Core;
|
||||
|
||||
public class Entity
|
||||
{
|
||||
private static int _idGenerator = 0;
|
||||
public int EntityId { get; private set; }
|
||||
public uint OwnerId { get; private set; }
|
||||
public byte[] State { get; set; }
|
||||
public Dictionary<int, byte[]> Properties { get; set; }
|
||||
|
||||
public Entity(uint ownerId)
|
||||
{
|
||||
OwnerId = ownerId;
|
||||
EntityId = _idGenerator++;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace Ragon.Core
|
||||
{
|
||||
public struct Event
|
||||
{
|
||||
public EventType Type;
|
||||
public uint PeerId;
|
||||
public byte[] Data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace Ragon.Core
|
||||
{
|
||||
public enum EventType
|
||||
{
|
||||
CONNECTED,
|
||||
DISCONNECTED,
|
||||
TIMEOUT,
|
||||
DATA,
|
||||
}
|
||||
}
|
||||
Executable
+14
@@ -0,0 +1,14 @@
|
||||
using System;
|
||||
|
||||
namespace Ragon.Core
|
||||
{
|
||||
public static class StringExtensions
|
||||
{
|
||||
public static string PadBoth(this string str, int length)
|
||||
{
|
||||
int spaces = length - str.Length;
|
||||
int padLeft = spaces / 2 + str.Length;
|
||||
return str.PadLeft(padLeft).PadRight(length);
|
||||
}
|
||||
}
|
||||
}
|
||||
Executable
+14
@@ -0,0 +1,14 @@
|
||||
using System;
|
||||
|
||||
namespace Ragon.Core
|
||||
{
|
||||
public static class ValueExtensions
|
||||
{
|
||||
public static T Clamp<T>(this T val, T min, T max) where T : IComparable<T>
|
||||
{
|
||||
if (val.CompareTo(min) < 0) return min;
|
||||
else if (val.CompareTo(max) > 0) return max;
|
||||
else return val;
|
||||
}
|
||||
}
|
||||
}
|
||||
Executable
+15
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Ragon.Core
|
||||
{
|
||||
public class Player
|
||||
{
|
||||
public uint PeerId { get; set; }
|
||||
public string PlayerName { get; set; }
|
||||
public bool IsLoaded { get; set; }
|
||||
|
||||
public List<Entity> Entities;
|
||||
public List<int> EntitiesIds;
|
||||
}
|
||||
}
|
||||
Executable
+36
@@ -0,0 +1,36 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Ragon.Core
|
||||
{
|
||||
public class PluginBase
|
||||
{
|
||||
static class Storage<T>
|
||||
{
|
||||
public static Dictionary<Room, Dictionary<ushort, Action<T>>> Subscribes = new();
|
||||
}
|
||||
|
||||
|
||||
protected Room _room;
|
||||
// protected Dictionary<ushort, > _subscribes = new Dictionary<ushort,???>();
|
||||
public void Attach(Room room) => _room = room;
|
||||
|
||||
public void Subscribe<T>(ushort evntCode, Action<T> val)
|
||||
{
|
||||
Storage<T>.Subscribes.Add(_room, val);
|
||||
}
|
||||
public virtual void OnStart()
|
||||
{
|
||||
}
|
||||
|
||||
public virtual void OnStop()
|
||||
{
|
||||
}
|
||||
|
||||
public virtual void OnTick(ulong ticks, float deltaTime)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace Ragon.Core
|
||||
{
|
||||
public interface PluginFactory
|
||||
{
|
||||
public PluginBase CreatePlugin(string map);
|
||||
}
|
||||
}
|
||||
Executable
+344
@@ -0,0 +1,344 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using NetStack.Serialization;
|
||||
using NLog;
|
||||
using Ragon.Common.Protocol;
|
||||
|
||||
namespace Ragon.Core
|
||||
{
|
||||
public class Room : IDisposable
|
||||
{
|
||||
private ILogger _logger = LogManager.GetCurrentClassLogger();
|
||||
private Dictionary<uint, Player> _players = new();
|
||||
private Dictionary<int, Entity> _entities = new();
|
||||
private uint Owner;
|
||||
|
||||
private BitBuffer _buffer = new BitBuffer(8192);
|
||||
private byte[] _bytes = new byte[8192];
|
||||
|
||||
private readonly PluginBase _plugin;
|
||||
private readonly RoomThread _roomThread;
|
||||
private readonly string _map;
|
||||
private ulong _ticks = 0;
|
||||
|
||||
// Cache
|
||||
private uint[] _readyPlayers = Array.Empty<uint>();
|
||||
|
||||
public int Players => _players.Count;
|
||||
public int MaxPlayers { get; } = 0;
|
||||
|
||||
public Room(RoomThread roomThread, PluginBase pluginBase, string map)
|
||||
{
|
||||
_roomThread = roomThread;
|
||||
_plugin = pluginBase;
|
||||
_map = map;
|
||||
|
||||
_plugin.Attach(this);
|
||||
}
|
||||
|
||||
public void Joined(uint peerId, byte[] payload)
|
||||
{
|
||||
if (_players.Count == 0)
|
||||
{
|
||||
Owner = peerId;
|
||||
}
|
||||
|
||||
var player = new Player()
|
||||
{
|
||||
PlayerName = "Player " + peerId,
|
||||
PeerId = peerId,
|
||||
IsLoaded = false,
|
||||
Entities = new List<Entity>(),
|
||||
EntitiesIds = new List<int>(),
|
||||
};
|
||||
|
||||
_players.Add(peerId, player);
|
||||
_plugin.OnPlayerConnected(player);
|
||||
|
||||
var data = new byte[8];
|
||||
ProtocolHeader.WriteEntity((int) peerId, data, 0);
|
||||
ProtocolHeader.WriteEntity((int) Owner, data, 4);
|
||||
Send(peerId, RagonOperation.JOIN_ROOM, data);
|
||||
|
||||
var sceneRawData = Encoding.UTF8.GetBytes(_map);
|
||||
Send(peerId, RagonOperation.LOAD_SCENE, sceneRawData);
|
||||
}
|
||||
|
||||
public void Leave(uint peerId)
|
||||
{
|
||||
if (_players.Remove(peerId, out var player))
|
||||
{
|
||||
_plugin.OnPlayerDisconnected(player);
|
||||
foreach (var entityId in player.EntitiesIds)
|
||||
{
|
||||
var entityData = new byte[4];
|
||||
ProtocolHeader.WriteEntity(entityId, entityData, 0);
|
||||
Broadcast(_readyPlayers, RagonOperation.DESTROY_ENTITY, entityData);
|
||||
|
||||
_entities.Remove(entityId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void ProcessEvent(RagonOperation operation, uint peerId, byte[] rawData)
|
||||
{
|
||||
switch (operation)
|
||||
{
|
||||
case RagonOperation.REPLICATE_ENTITY_STATE:
|
||||
{
|
||||
var entityId = ProtocolHeader.ReadEntity(rawData, 2);
|
||||
if (_entities.TryGetValue(entityId, out var ent))
|
||||
{
|
||||
var data = new byte[rawData.Length - 6]; // opcode(ushort)(2) + entity(int)(4)
|
||||
Array.Copy(rawData, 6, data, 0, rawData.Length - 6);
|
||||
_entities[entityId].State = data;
|
||||
|
||||
Broadcast(_readyPlayers, rawData);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case RagonOperation.REPLICATE_ENTITY_PROPERTY:
|
||||
{
|
||||
var entityId = ProtocolHeader.ReadEntity(rawData, 2);
|
||||
if (_entities.TryGetValue(entityId, out var ent))
|
||||
{
|
||||
var propertyId = ProtocolHeader.ReadProperty(rawData, 6);
|
||||
var data = new byte[rawData.Length - 10]; // opcode(ushort)(2) + entity(int)(4) + propertyId(int)(4)
|
||||
Array.Copy(rawData, 10, data, 0, rawData.Length - 10);
|
||||
|
||||
var props = _entities[entityId].Properties;
|
||||
if (props.ContainsKey(propertyId))
|
||||
{
|
||||
props[propertyId] = data;
|
||||
}
|
||||
else
|
||||
{
|
||||
props.Add(propertyId, data);
|
||||
}
|
||||
|
||||
Broadcast(_readyPlayers, RagonOperation.REPLICATE_ENTITY_PROPERTY, rawData);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case RagonOperation.REPLICATE_EVENT:
|
||||
{
|
||||
|
||||
Broadcast(_readyPlayers, rawData);
|
||||
break;
|
||||
}
|
||||
case RagonOperation.CREATE_ENTITY:
|
||||
{
|
||||
var entity = new Entity(peerId);
|
||||
var data = new byte[rawData.Length - 2]; // opcode(ushort)(2)
|
||||
Array.Copy(rawData, 2, data, 0, rawData.Length - 2);
|
||||
|
||||
entity.State = data;
|
||||
entity.Properties = new Dictionary<int, byte[]>();
|
||||
|
||||
var player = _players[peerId];
|
||||
player.Entities.Add(entity);
|
||||
player.EntitiesIds.Add(entity.EntityId);
|
||||
|
||||
_entities.Add(entity.EntityId, entity);
|
||||
|
||||
var entityData = new byte[entity.State.Length + 8];
|
||||
ProtocolHeader.WriteEntity(entity.EntityId, entityData, 0);
|
||||
ProtocolHeader.WriteEntity((int) peerId, entityData, 4);
|
||||
|
||||
Array.Copy(entity.State, 0, entityData, 8, entity.State.Length);
|
||||
|
||||
_logger.Trace("Create entity Owner:" + peerId + " Id: " + entity.EntityId);
|
||||
|
||||
Broadcast(_readyPlayers, RagonOperation.CREATE_ENTITY, entityData);
|
||||
break;
|
||||
}
|
||||
case RagonOperation.DESTROY_ENTITY:
|
||||
{
|
||||
var entityId = ProtocolHeader.ReadEntity(rawData);
|
||||
if (_entities.TryGetValue(entityId, out var entity))
|
||||
{
|
||||
if (entity.OwnerId == peerId)
|
||||
{
|
||||
var player = _players[peerId];
|
||||
|
||||
player.Entities.Remove(entity);
|
||||
player.EntitiesIds.Remove(entity.EntityId);
|
||||
|
||||
_entities.Remove(entityId);
|
||||
|
||||
Broadcast(_readyPlayers, rawData);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case RagonOperation.SCENE_IS_LOADED:
|
||||
{
|
||||
Send(peerId, RagonOperation.RESTORE_BEGIN, Array.Empty<byte>());
|
||||
foreach (var entity in _entities.Values)
|
||||
{
|
||||
var entityData = new byte[entity.State.Length + 8];
|
||||
ProtocolHeader.WriteEntity(entity.EntityId, entityData, 0);
|
||||
ProtocolHeader.WriteEntity((int) entity.OwnerId, entityData, 4);
|
||||
|
||||
Array.Copy(entity.State, 0, entityData, 8, entity.State.Length);
|
||||
Send(peerId, RagonOperation.CREATE_ENTITY, entityData);
|
||||
}
|
||||
Send(peerId, RagonOperation.RESTORE_END, Array.Empty<byte>());
|
||||
break;
|
||||
}
|
||||
case RagonOperation.RESTORED:
|
||||
{
|
||||
_players[peerId].IsLoaded = true;
|
||||
_readyPlayers = _players.Where(p => p.Value.IsLoaded).Select(p => p.Key).ToArray();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Tick(float deltaTime)
|
||||
{
|
||||
_ticks++;
|
||||
|
||||
_plugin.OnTick(_ticks, deltaTime);
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
_plugin.OnStart();
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
_plugin.OnStop();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
|
||||
public void Send(uint peerId, RagonOperation operation, byte[] payload)
|
||||
{
|
||||
if (payload.Length > 0)
|
||||
{
|
||||
var data = new byte[payload.Length + 2];
|
||||
|
||||
Array.Copy(payload, 0, data, 2, payload.Length);
|
||||
ProtocolHeader.WriteOperation((ushort) operation, data);
|
||||
|
||||
_roomThread.WriteOutEvent(new Event()
|
||||
{
|
||||
PeerId = peerId,
|
||||
Data = data,
|
||||
Type = EventType.DATA,
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
var data = new byte[2];
|
||||
|
||||
ProtocolHeader.WriteOperation((ushort) operation, data);
|
||||
|
||||
_roomThread.WriteOutEvent(new Event()
|
||||
{
|
||||
PeerId = peerId,
|
||||
Data = data,
|
||||
Type = EventType.DATA,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public void Send(uint peerId, RagonOperation operation, IData payload)
|
||||
{
|
||||
_buffer.Clear();
|
||||
payload.Serialize(_buffer);
|
||||
_buffer.ToArray(_bytes);
|
||||
|
||||
var data = new byte[_buffer.Length + 2];
|
||||
|
||||
Array.Copy(_bytes, 0, data, 2, _buffer.Length);
|
||||
|
||||
ProtocolHeader.WriteOperation((ushort) operation, data);
|
||||
|
||||
_roomThread.WriteOutEvent(new Event()
|
||||
{
|
||||
PeerId = peerId,
|
||||
Data = data,
|
||||
Type = EventType.DATA,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public void Broadcast(uint[] peersIds, RagonOperation operation, IData payload)
|
||||
{
|
||||
_buffer.Clear();
|
||||
payload.Serialize(_buffer);
|
||||
_buffer.ToArray(_bytes);
|
||||
|
||||
var data = new byte[_buffer.Length + 2];
|
||||
|
||||
Array.Copy(_bytes, 0, data, 2, _buffer.Length);
|
||||
|
||||
ProtocolHeader.WriteOperation((ushort) operation, data);
|
||||
|
||||
foreach (var peer in peersIds)
|
||||
{
|
||||
_roomThread.WriteOutEvent(new Event()
|
||||
{
|
||||
PeerId = peer,
|
||||
Data = data,
|
||||
Type = EventType.DATA,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public void Broadcast(uint[] peersIds, RagonOperation operation, byte[] payload)
|
||||
{
|
||||
var data = new byte[payload.Length + 2];
|
||||
|
||||
Array.Copy(payload, 0, data, 2, payload.Length);
|
||||
|
||||
ProtocolHeader.WriteOperation((ushort) operation, data);
|
||||
|
||||
foreach (var peer in peersIds)
|
||||
{
|
||||
_roomThread.WriteOutEvent(new Event()
|
||||
{
|
||||
PeerId = peer,
|
||||
Data = data,
|
||||
Type = EventType.DATA,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public void Broadcast(uint[] peersIds, byte[] rawData)
|
||||
{
|
||||
foreach (var peer in peersIds)
|
||||
{
|
||||
_roomThread.WriteOutEvent(new Event()
|
||||
{
|
||||
PeerId = peer,
|
||||
Data = rawData,
|
||||
Type = EventType.DATA,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public void Broadcast(byte[] rawData)
|
||||
{
|
||||
foreach (var player in _players.Values.ToArray())
|
||||
{
|
||||
_roomThread.WriteOutEvent(new Event()
|
||||
{
|
||||
PeerId = player.PeerId,
|
||||
Data = rawData,
|
||||
Type = EventType.DATA,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using NetStack.Serialization;
|
||||
using NLog;
|
||||
using Ragon.Common.Protocol;
|
||||
|
||||
namespace Ragon.Core
|
||||
{
|
||||
public class RoomManager
|
||||
{
|
||||
private readonly Logger _logger = LogManager.GetCurrentClassLogger();
|
||||
private List<Room> _rooms;
|
||||
private Dictionary<uint, Room> _peersByRoom;
|
||||
private PluginFactory _factory;
|
||||
private RoomThread _roomThread;
|
||||
private BitBuffer _bitBuffer;
|
||||
|
||||
public Action<(uint, Room)> OnJoined;
|
||||
public Action<(uint, Room)> OnLeaved;
|
||||
|
||||
public RoomManager(RoomThread roomThread, PluginFactory factory)
|
||||
{
|
||||
_roomThread = roomThread;
|
||||
_factory = factory;
|
||||
_rooms = new List<Room>();
|
||||
_peersByRoom = new Dictionary<uint, Room>();
|
||||
_bitBuffer = new BitBuffer(1024);
|
||||
}
|
||||
|
||||
public void ProccessEvent(RagonOperation operation, uint peerId, byte[] payload)
|
||||
{
|
||||
switch (operation)
|
||||
{
|
||||
case RagonOperation.AUTHORIZE:
|
||||
{
|
||||
OnAuthorize(peerId, payload);
|
||||
break;
|
||||
}
|
||||
case RagonOperation.JOIN_ROOM:
|
||||
{
|
||||
var room = Join(peerId, payload);
|
||||
OnJoined?.Invoke((peerId, room));
|
||||
break;
|
||||
}
|
||||
case RagonOperation.LEAVE_ROOM:
|
||||
{
|
||||
var room = Left(peerId, payload);
|
||||
OnLeaved((peerId, room));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void OnAuthorize(uint peerId, byte[] payload)
|
||||
{
|
||||
_bitBuffer.Clear();
|
||||
// _bitBuffer.FromArray(payload, payload.Length);
|
||||
|
||||
// var authorizePacket = new AuthorationData();
|
||||
// authorizePacket.Deserialize(_bitBuffer);
|
||||
|
||||
var data = new byte[2];
|
||||
|
||||
ProtocolHeader.WriteOperation((ushort) RagonOperation.AUTHORIZED_SUCCESS, data);
|
||||
|
||||
_roomThread.WriteOutEvent(new Event()
|
||||
{
|
||||
Type = EventType.DATA,
|
||||
Data = data,
|
||||
PeerId = peerId,
|
||||
});
|
||||
}
|
||||
|
||||
public Room Join(uint peerId, byte[] payload)
|
||||
{
|
||||
var map = Encoding.UTF8.GetString(payload);
|
||||
|
||||
if (_rooms.Count > 0)
|
||||
{
|
||||
var existsRoom = _rooms[0];
|
||||
existsRoom.Joined(peerId, payload);
|
||||
_peersByRoom.Add(peerId, existsRoom);
|
||||
|
||||
return existsRoom;
|
||||
}
|
||||
|
||||
var plugin = _factory.CreatePlugin(map);
|
||||
if (plugin == null)
|
||||
throw new NullReferenceException($"Plugin for map {map} is null");
|
||||
|
||||
_logger.Info("Room created");
|
||||
|
||||
var room = new Room(_roomThread, plugin, map);
|
||||
room.Joined(peerId, payload);
|
||||
_peersByRoom.Add(peerId, room);
|
||||
|
||||
_rooms.Add(room);
|
||||
|
||||
return room;
|
||||
}
|
||||
|
||||
public Room Left(uint peerId, byte[] payload)
|
||||
{
|
||||
_peersByRoom.Remove(peerId, out var room);
|
||||
room?.Leave(peerId);
|
||||
|
||||
return room;
|
||||
}
|
||||
|
||||
public void Tick(float deltaTime)
|
||||
{
|
||||
foreach (Room room in _rooms)
|
||||
room.Tick(deltaTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
Executable
+103
@@ -0,0 +1,103 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Threading;
|
||||
using DisruptorUnity3d;
|
||||
using ENet;
|
||||
using NetStack.Serialization;
|
||||
using Ragon.Common.Protocol;
|
||||
|
||||
namespace Ragon.Core
|
||||
{
|
||||
public class RoomThread : IDisposable
|
||||
{
|
||||
private readonly RoomManager _roomManager;
|
||||
private readonly Dictionary<uint, Room> _socketByRooms;
|
||||
private readonly Thread _thread;
|
||||
private readonly Stopwatch _timer;
|
||||
|
||||
private RingBuffer<Event> _receiveBuffer = new RingBuffer<Event>(8192 + 8192);
|
||||
private RingBuffer<Event> _sendBuffer = new RingBuffer<Event>(8192 + 8192);
|
||||
|
||||
public bool ReadOutEvent(out Event evnt) => _sendBuffer.TryDequeue(out evnt);
|
||||
public void WriteOutEvent(Event evnt) => _sendBuffer.Enqueue(evnt);
|
||||
|
||||
public bool ReadIntEvent(out Event evnt) => _receiveBuffer.TryDequeue(out evnt);
|
||||
public void WriteInEvent(Event evnt) => _receiveBuffer.Enqueue(evnt);
|
||||
|
||||
public RoomThread(PluginFactory factory)
|
||||
{
|
||||
_thread = new Thread(Execute);
|
||||
_thread.IsBackground = true;
|
||||
_timer = new Stopwatch();
|
||||
_socketByRooms = new Dictionary<uint, Room>();
|
||||
|
||||
_roomManager = new RoomManager(this, factory);
|
||||
_roomManager.OnJoined += (tuple) => _socketByRooms.Add(tuple.Item1, tuple.Item2);
|
||||
_roomManager.OnLeaved += (tuple) => _socketByRooms.Remove(tuple.Item1);
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
_timer.Start();
|
||||
_thread.Start();
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
_thread.Interrupt();
|
||||
}
|
||||
|
||||
private void Execute()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
var deltaTime = _timer.ElapsedMilliseconds;
|
||||
if (deltaTime > 1000 / 60)
|
||||
{
|
||||
while (_receiveBuffer.TryDequeue(out var evnt))
|
||||
{
|
||||
if (evnt.Type == EventType.DISCONNECTED || evnt.Type == EventType.TIMEOUT)
|
||||
{
|
||||
|
||||
if (_socketByRooms.ContainsKey(evnt.PeerId))
|
||||
{
|
||||
_roomManager.Left(evnt.PeerId, Array.Empty<byte>());
|
||||
_socketByRooms.Remove(evnt.PeerId);
|
||||
}
|
||||
}
|
||||
|
||||
if (evnt.Type == EventType.DATA)
|
||||
{
|
||||
var operation = (RagonOperation) ProtocolHeader.ReadOperation(evnt.Data, 0);
|
||||
if (_socketByRooms.TryGetValue(evnt.PeerId, out var room))
|
||||
{
|
||||
room.ProcessEvent(operation, evnt.PeerId, evnt.Data);
|
||||
}
|
||||
else
|
||||
{
|
||||
var payload = new byte[evnt.Data.Length - 2];
|
||||
|
||||
Array.Copy(evnt.Data, 2, payload, 0, evnt.Data.Length - 2);
|
||||
|
||||
_roomManager.ProccessEvent(operation, evnt.PeerId, payload);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_roomManager.Tick(deltaTime / 1000.0f);
|
||||
|
||||
_timer.Restart();
|
||||
}
|
||||
else
|
||||
{
|
||||
Thread.Sleep(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace Ragon.Core
|
||||
{
|
||||
public struct RoomThreadInfo
|
||||
{
|
||||
public int PlayersCount;
|
||||
public int PlayersMax;
|
||||
public bool Available;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace Ragon.Core;
|
||||
|
||||
public class WebsocketServer
|
||||
{
|
||||
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
- Send states only on scene loaded
|
||||
- Add events global and by entity
|
||||
Reference in New Issue
Block a user