This commit is contained in:
2022-06-25 11:08:50 +04:00
parent 679608bc48
commit 1e41b9f2eb
31 changed files with 152 additions and 645 deletions
+1 -1
View File
@@ -3,7 +3,7 @@
<PropertyGroup>
<LangVersion>10</LangVersion>
<Nullable>enable</Nullable>
<TargetFrameworks>net6.0;netstandard2.1</TargetFrameworks>
<TargetFramework>net6.0</TargetFramework>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
+1 -1
View File
@@ -11,7 +11,7 @@ namespace Ragon.Core
{
private readonly Logger _logger = LogManager.GetCurrentClassLogger();
private readonly GameThread _gameThread;
private readonly ENetServer _netServer;
public Application(PluginFactory factory, Configuration configuration)
{
_gameThread = new GameThread(factory, configuration);
@@ -27,7 +27,7 @@ public class AuthorizationManager : IAuthorizationManager
public void OnAuthorization(uint peerId, string key, string name, byte protocol)
{
var dispatcher = _gameThread.Dispatcher;
var dispatcher = _gameThread.ThreadDispatcher;
_provider.OnAuthorizationRequest(key, name, protocol, Array.Empty<byte>(),
(playerId, playerName) =>
{
@@ -77,9 +77,12 @@ public class AuthorizationManager : IAuthorizationManager
_playersByIds.Remove(player.Id);
}
public Player GetPlayer(uint peerId)
public Player? GetPlayer(uint peerId)
{
return _playersByPeers[peerId];
if (_playersByPeers.TryGetValue(peerId, out var player))
return player;
return null;
}
public Player GetPlayer(string playerId)
+7 -8
View File
@@ -2,17 +2,16 @@
namespace Ragon.Core
{
[Serializable]
public struct Server
{
public ushort Port;
}
[Serializable]
public struct Configuration
{
public string Key;
public ushort TickRate;
public Server Server;
public int StatisticsInterval;
public ushort SendRate;
public ushort Port;
public int SkipTimeout;
public int MaxConnections;
public int MaxPlayersPerRoom;
public int MaxRooms;
}
}
@@ -1,6 +0,0 @@
namespace Ragon.Core;
public interface Receiver<T>
{
public bool Receive(out T data);
}
@@ -1,6 +0,0 @@
namespace Ragon.Core;
public interface ISender<T>
{
public void Send(T data);
}
-15
View File
@@ -1,15 +0,0 @@
using System;
using System.Diagnostics;
namespace Ragon.Core;
public class DispatcherTask
{
public Action Action;
public Action Callback;
public void Execute()
{
Action?.Invoke();
}
}
@@ -1,130 +0,0 @@
using System.Collections.Generic;
using System.Threading;
namespace Ragon.Core.Core.Utils;
public class SynchronizedCache
{
private ReaderWriterLockSlim cacheLock = new ReaderWriterLockSlim();
private Dictionary<int, string> innerCache = new Dictionary<int, string>();
public int Count
{ get { return innerCache.Count; } }
public string Read(int key)
{
cacheLock.EnterReadLock();
try
{
return innerCache[key];
}
finally
{
cacheLock.ExitReadLock();
}
}
public void Add(int key, string value)
{
cacheLock.EnterWriteLock();
try
{
innerCache.Add(key, value);
}
finally
{
cacheLock.ExitWriteLock();
}
}
public bool AddWithTimeout(int key, string value, int timeout)
{
if (cacheLock.TryEnterWriteLock(timeout))
{
try
{
innerCache.Add(key, value);
}
finally
{
cacheLock.ExitWriteLock();
}
return true;
}
else
{
return false;
}
}
public AddOrUpdateStatus AddOrUpdate(int key, string value)
{
cacheLock.EnterUpgradeableReadLock();
try
{
string result = null;
if (innerCache.TryGetValue(key, out result))
{
if (result == value)
{
return AddOrUpdateStatus.Unchanged;
}
else
{
cacheLock.EnterWriteLock();
try
{
innerCache[key] = value;
}
finally
{
cacheLock.ExitWriteLock();
}
return AddOrUpdateStatus.Updated;
}
}
else
{
cacheLock.EnterWriteLock();
try
{
innerCache.Add(key, value);
}
finally
{
cacheLock.ExitWriteLock();
}
return AddOrUpdateStatus.Added;
}
}
finally
{
cacheLock.ExitUpgradeableReadLock();
}
}
public void Delete(int key)
{
cacheLock.EnterWriteLock();
try
{
innerCache.Remove(key);
}
finally
{
cacheLock.ExitWriteLock();
}
}
public enum AddOrUpdateStatus
{
Added,
Updated,
Unchanged
};
~SynchronizedCache()
{
if (cacheLock != null) cacheLock.Dispose();
}
}
-12
View File
@@ -1,12 +0,0 @@
using System;
namespace Ragon.Core
{
public struct SocketEvent
{
public EventType Type;
public DeliveryType Delivery;
public byte[] Data;
public uint PeerId;
}
}
-10
View File
@@ -1,10 +0,0 @@
namespace Ragon.Core
{
public enum EventType
{
CONNECTED,
DISCONNECTED,
TIMEOUT,
DATA,
}
}
+6 -8
View File
@@ -19,7 +19,6 @@ namespace Ragon.Core
private Dictionary<uint, Player> _players = new();
private Dictionary<int, Entity> _entities = new();
private uint _owner;
private uint _ticks;
private readonly PluginBase _plugin;
private readonly IGameThread _gameThread;
@@ -40,7 +39,6 @@ namespace Ragon.Core
PlayersMax = max;
Id = Guid.NewGuid().ToString();
_logger.Info($"Room created with plugin: {_plugin.GetType().Name}");
_plugin.Attach(this);
}
@@ -119,7 +117,9 @@ namespace Ragon.Core
{
var newRoomOwnerId = _allPlayers[0];
var newRoomOwner = _players[newRoomOwnerId];
_owner = newRoomOwnerId;
{
_plugin.OnOwnershipChanged(newRoomOwner);
@@ -323,8 +323,7 @@ namespace Ragon.Core
public void Tick(float deltaTime)
{
_ticks++;
_plugin.OnTick(_ticks, deltaTime);
_plugin.OnTick(deltaTime);
foreach (var entity in _entitiesAll)
{
@@ -347,15 +346,12 @@ namespace Ragon.Core
public void Start()
{
_logger.Info("Room started");
_plugin.OnStart();
}
public void Stop()
{
_logger.Info("Room stopped");
_plugin.OnStop();
_plugin.Detach();
}
@@ -365,6 +361,8 @@ namespace Ragon.Core
public Player GetOwner() => _players[_owner];
public IDispatcher GetThreadDispatcher() => _gameThread.ThreadDispatcher;
public void Send(uint peerId, byte[] rawData, DeliveryType deliveryType = DeliveryType.Unreliable)
{
_gameThread.Server.Send(peerId, rawData, deliveryType);
+33 -41
View File
@@ -9,37 +9,37 @@ namespace Ragon.Core
{
public class GameThread : IGameThread, IHandler
{
private readonly Dictionary<uint, GameRoom> _socketByRooms;
private readonly RoomManager _roomManager;
private readonly ISocketServer _server;
private readonly Thread _thread;
private readonly Server _serverConfiguration;
private readonly Stopwatch _gameLoopTimer;
private readonly Lobby _lobby;
private readonly ILogger _logger = LogManager.GetCurrentClassLogger();
private readonly float _deltaTime = 0.0f;
private readonly Stopwatch _packetsTimer;
private int _packets = 0;
public IDispatcher Dispatcher { get; private set; }
private readonly Stopwatch _statisticsTimer;
private readonly Configuration _configuration;
private readonly IDispatcherInternal _dispatcherInternal;
public IDispatcher ThreadDispatcher { get; private set; }
public ISocketServer Server { get; private set; }
public GameThread(PluginFactory factory, Configuration configuration)
{
_configuration = configuration;
var authorizationProvider = factory.CreateAuthorizationProvider(configuration);
var dispatcher = new Dispatcher();
_dispatcherInternal = dispatcher;
Dispatcher = new Dispatcher();
ThreadDispatcher = dispatcher;
Server = new ENetServer(this);
_serverConfiguration = configuration.Server;
_deltaTime = 1000.0f / configuration.TickRate;
_deltaTime = 1000.0f / configuration.SendRate;
_roomManager = new RoomManager(factory, this);
_lobby = new Lobby(authorizationProvider, _roomManager, this);
_gameLoopTimer = new Stopwatch();
_packetsTimer = new Stopwatch();
_socketByRooms = new Dictionary<uint, GameRoom>();
_statisticsTimer = new Stopwatch();
_thread = new Thread(Execute);
_thread.Name = "Game Thread";
@@ -48,19 +48,19 @@ namespace Ragon.Core
public void Start()
{
Server.Start(_serverConfiguration.Port);
Server.Start(_configuration.Port, _configuration.MaxConnections);
_gameLoopTimer.Start();
_packetsTimer.Start();
_statisticsTimer.Start();
_thread.Start();
}
public void Stop()
{
Server.Stop();
_gameLoopTimer.Stop();
_packetsTimer.Stop();
_statisticsTimer.Stop();
_thread.Interrupt();
}
@@ -69,8 +69,9 @@ namespace Ragon.Core
while (true)
{
Server.Process();
Dispatcher.Process();
_dispatcherInternal.Process();
var elapsedMilliseconds = _gameLoopTimer.ElapsedMilliseconds;
if (elapsedMilliseconds > _deltaTime)
{
@@ -79,47 +80,38 @@ namespace Ragon.Core
continue;
}
if (_packetsTimer.Elapsed.Seconds > 1)
if (_statisticsTimer.Elapsed.Seconds > _configuration.StatisticsInterval && _roomManager.RoomsBySocket.Count > 0)
{
_logger.Trace($"Clients: {_socketByRooms.Keys.Count} Packets: {_packets} per sec");
_packetsTimer.Restart();
_packets = 0;
_logger.Trace($"Rooms: {_roomManager.Rooms.Count} Clients: {_roomManager.RoomsBySocket.Count}");
_statisticsTimer.Restart();
}
Thread.Sleep(15);
}
}
public void Attach(uint peerId, GameRoom room)
{
_socketByRooms.Add(peerId, room);
}
public void Detach(uint peerId)
{
_socketByRooms.Remove(peerId);
}
public void OnEvent(Event evnt)
{
if (evnt.Type == ENet.EventType.Timeout || evnt.Type == ENet.EventType.Disconnect)
if (evnt.Type == EventType.Timeout || evnt.Type == EventType.Disconnect)
{
if (_socketByRooms.Remove(evnt.Peer.ID, out var room))
room.Leave(evnt.Peer.ID);
var player = _lobby.AuthorizationManager.GetPlayer(evnt.Peer.ID);
if (player != null)
_roomManager.Left(player, Array.Empty<byte>());
_lobby.OnDisconnected(evnt.Peer.ID);
}
if (evnt.Type == ENet.EventType.Receive)
if (evnt.Type == EventType.Receive)
{
_packets += 1;
try
{
var peerId = evnt.Peer.ID;
var dataRaw = new byte[evnt.Packet.Length];
evnt.Packet.CopyTo(dataRaw);
var data = new ReadOnlySpan<byte>(dataRaw);
if (_socketByRooms.TryGetValue(peerId, out var room))
if (_roomManager.RoomsBySocket.TryGetValue(peerId, out var room))
{
room.ProcessEvent(peerId, data);
}
+1
View File
@@ -11,6 +11,7 @@ public interface IGameRoom
public Player GetPlayerById(uint peerId);
public Entity GetEntityById(int entityId);
public Player GetOwner();
public IDispatcher GetThreadDispatcher();
public void Send(uint peerId, byte[] rawData, DeliveryType deliveryType = DeliveryType.Unreliable);
public void Broadcast(uint[] peersIds, byte[] rawData, DeliveryType deliveryType = DeliveryType.Unreliable);
+1 -3
View File
@@ -4,8 +4,6 @@ namespace Ragon.Core;
public interface IGameThread
{
public void Attach(uint peerId, GameRoom room);
public void Detach(uint peerId);
public IDispatcher Dispatcher { get; }
public IDispatcher ThreadDispatcher { get; }
public ISocketServer Server { get; }
}
+6
View File
@@ -0,0 +1,6 @@
namespace Ragon.Core;
public interface ILobby
{
}
+16 -11
View File
@@ -1,19 +1,21 @@
using System;
using System.Collections.Generic;
using Ragon.Common;
namespace Ragon.Core;
public class Lobby
public class Lobby : ILobby
{
private readonly RagonSerializer _serializer;
private readonly AuthorizationManager _authorizationManager;
private readonly RoomManager _roomManager;
public AuthorizationManager AuthorizationManager { get; private set; }
public Lobby(IAuthorizationProvider provider, RoomManager manager, IGameThread gameThread)
{
_roomManager = manager;
_serializer = new RagonSerializer();
_authorizationManager = new AuthorizationManager(provider, gameThread, this, _serializer);
AuthorizationManager = new AuthorizationManager(provider, gameThread, this, _serializer);
}
public void ProcessEvent(uint peerId, ReadOnlySpan<byte> data)
@@ -31,14 +33,15 @@ public class Lobby
var key = _serializer.ReadString();
var playerName = _serializer.ReadString();
var protocol = _serializer.ReadByte();
_authorizationManager.OnAuthorization(peerId, key, playerName, protocol);
AuthorizationManager.OnAuthorization(peerId, key, playerName, protocol);
break;
}
case RagonOperation.JOIN_ROOM:
{
var roomId = _serializer.ReadString();
var player = _authorizationManager.GetPlayer(peerId);
_roomManager.Join(player, roomId, Array.Empty<byte>());
var player = AuthorizationManager.GetPlayer(peerId);
if (player != null)
_roomManager.Join(player, roomId, Array.Empty<byte>());
break;
}
case RagonOperation.JOIN_OR_CREATE_ROOM:
@@ -46,14 +49,16 @@ public class Lobby
var map = _serializer.ReadString();
var min = _serializer.ReadInt();
var max = _serializer.ReadInt();
var player = _authorizationManager.GetPlayer(peerId);
_roomManager.JoinOrCreate(player, map, min, max, Array.Empty<byte>());
var player = AuthorizationManager.GetPlayer(peerId);
if (player != null)
_roomManager.JoinOrCreate(player, map, min, max, Array.Empty<byte>());
break;
}
case RagonOperation.LEAVE_ROOM:
{
var player = _authorizationManager.GetPlayer(peerId);
_roomManager.Left(player, Array.Empty<byte>());
var player = AuthorizationManager.GetPlayer(peerId);
if (player != null)
_roomManager.Left(player, Array.Empty<byte>());
break;
}
}
@@ -61,6 +66,6 @@ public class Lobby
public void OnDisconnected(uint peerId)
{
_authorizationManager.Cleanup(peerId);
AuthorizationManager.Cleanup(peerId);
}
}
+11 -11
View File
@@ -18,12 +18,12 @@ namespace Ragon.Core
private readonly BitBuffer _buffer = new();
private readonly RagonSerializer _serializer = new();
protected IGameRoom GameRoom { get; private set; }
protected ILogger _logger;
protected IGameRoom GameRoom { get; private set; } = null!;
protected ILogger Logger = null!;
public void Attach(GameRoom gameRoom)
{
_logger = LogManager.GetLogger($"Plugin<{GetType().Name}>");
Logger = LogManager.GetLogger($"Plugin<{GetType().Name}>");
GameRoom = gameRoom;
@@ -41,7 +41,7 @@ namespace Ragon.Core
{
if (_globalEvents.ContainsKey(evntCode))
{
_logger.Warn($"Event subscriber already added {evntCode}");
Logger.Warn($"Event subscriber already added {evntCode}");
return;
}
@@ -50,7 +50,7 @@ namespace Ragon.Core
{
if (raw.Length == 0)
{
_logger.Warn($"Payload is empty for event {evntCode}");
Logger.Warn($"Payload is empty for event {evntCode}");
return;
}
@@ -65,7 +65,7 @@ namespace Ragon.Core
{
if (_globalEvents.ContainsKey(evntCode))
{
_logger.Warn($"Event subscriber already added {evntCode}");
Logger.Warn($"Event subscriber already added {evntCode}");
return;
}
@@ -78,7 +78,7 @@ namespace Ragon.Core
{
if (_entityEvents[entity.EntityId].ContainsKey(evntCode))
{
_logger.Warn($"Event subscriber already added {evntCode} for {entity.EntityId}");
Logger.Warn($"Event subscriber already added {evntCode} for {entity.EntityId}");
return;
}
@@ -87,7 +87,7 @@ namespace Ragon.Core
{
if (raw.Length == 0)
{
_logger.Warn($"Payload is empty for entity {ent.EntityId} event {evntCode}");
Logger.Warn($"Payload is empty for entity {ent.EntityId} event {evntCode}");
return;
}
@@ -107,7 +107,7 @@ namespace Ragon.Core
{
if (raw.Length == 0)
{
_logger.Warn($"Payload is empty for entity {ent.EntityId} event {evntCode}");
Logger.Warn($"Payload is empty for entity {ent.EntityId} event {evntCode}");
return;
}
@@ -125,7 +125,7 @@ namespace Ragon.Core
{
if (_entityEvents[entity.EntityId].ContainsKey(evntCode))
{
_logger.Warn($"Event subscriber already added {evntCode} for {entity.EntityId}");
Logger.Warn($"Event subscriber already added {evntCode} for {entity.EntityId}");
return;
}
@@ -265,7 +265,7 @@ namespace Ragon.Core
{
}
public virtual void OnTick(ulong ticks, float deltaTime)
public virtual void OnTick(float deltaTime)
{
}
+21 -10
View File
@@ -1,4 +1,3 @@
using System;
using System.Collections.Generic;
using System.Text;
@@ -12,14 +11,19 @@ public class RoomManager
private readonly IGameThread _gameThread;
private readonly PluginFactory _factory;
private readonly Logger _logger = LogManager.GetCurrentClassLogger();
private List<GameRoom> _rooms = new List<GameRoom>();
private readonly List<GameRoom> _rooms = new List<GameRoom>();
private readonly Dictionary<uint, GameRoom> _roomsBySocket;
public IReadOnlyDictionary<uint, GameRoom> RoomsBySocket => _roomsBySocket;
public IReadOnlyList<GameRoom> Rooms => _rooms;
public RoomManager(PluginFactory factory, IGameThread gameThread)
{
_gameThread = gameThread;
_factory = factory;
}
_roomsBySocket = new Dictionary<uint, GameRoom>();
}
public void Join(Player player, string roomId, byte[] payload)
{
if (_rooms.Count > 0)
@@ -29,7 +33,7 @@ public class RoomManager
if (existRoom.Id == roomId && existRoom.PlayersCount < existRoom.PlayersMax)
{
existRoom.Joined(player, payload);
_gameThread.Attach(player.PeerId, existRoom);
_roomsBySocket.Add(player.PeerId, existRoom);
break;
}
}
@@ -45,8 +49,7 @@ public class RoomManager
if (existRoom.Map == map && existRoom.PlayersCount < existRoom.PlayersMax)
{
existRoom.Joined(player, payload);
_gameThread.Attach(player.PeerId, existRoom);
_roomsBySocket.Add(player.PeerId, existRoom);
return;
}
}
@@ -56,17 +59,25 @@ public class RoomManager
if (plugin == null)
throw new NullReferenceException($"Plugin for map {map} is null");
var room = new GameRoom(_gameThread, plugin, map, min, max);
var room = new GameRoom(_gameThread, plugin, map, min, max);
room.Joined(player, payload);
room.Start();
_gameThread.Attach(player.PeerId, room);
_roomsBySocket.Add(player.PeerId, room);
_rooms.Add(room);
}
public void Left(Player player, byte[] payload)
{
if (_roomsBySocket.Remove(player.PeerId, out var room))
{
room.Leave(player.PeerId);
if (room.PlayersCount < room.PlayersMin)
{
room.Stop();
_rooms.Remove(room);
}
}
}
public void Tick(float deltaTime)
@@ -1,7 +1,4 @@
using System;
using System.Diagnostics;
using System.Threading;
using DisruptorUnity3d;
using ENet;
using NLog;
@@ -33,14 +30,14 @@ namespace Ragon.Core
_handler = handler;
}
public void Start(ushort port)
public void Start(ushort port, int connections)
{
_address = default;
_address.Port = port;
_peers = new Peer[2048];
_peers = new Peer[connections];
_host = new Host();
_host.Create(_address, 2048, 2, 0, 0, 1024 * 1024);
_host.Create(_address, connections, 2, 0, 0, 1024 * 1024);
Status = Status.Listening;
_logger.Info($"Network listening on {port}");
@@ -87,27 +84,27 @@ namespace Ragon.Core
switch (_netEvent.Type)
{
case ENet.EventType.None:
case EventType.None:
Console.WriteLine("None event");
break;
case ENet.EventType.Connect:
case EventType.Connect:
{
_peers[_netEvent.Peer.ID] = _netEvent.Peer;
_handler.OnEvent(_netEvent);
break;
}
case ENet.EventType.Disconnect:
case EventType.Disconnect:
{
_handler.OnEvent(_netEvent);
break;
}
case ENet.EventType.Timeout:
case EventType.Timeout:
{
_handler.OnEvent(_netEvent);
break;
}
case ENet.EventType.Receive:
case EventType.Receive:
{
_handler.OnEvent(_netEvent);
_netEvent.Packet.Dispose();
+1 -1
View File
@@ -2,7 +2,7 @@ namespace Ragon.Core;
public interface ISocketServer
{
public void Start(ushort port);
public void Start(ushort port, int connections);
public void Process();
public void Stop();
public void Send(uint peerId, byte[] data, DeliveryType type);
@@ -3,19 +3,20 @@ using System.Collections.Generic;
namespace Ragon.Core;
public class Dispatcher: IDispatcher
public class Dispatcher: IDispatcher, IDispatcherInternal
{
public Queue<DispatcherTask> _actions = new Queue<DispatcherTask>();
public Queue<Action> _actions = new Queue<Action>();
public void Dispatch(Action action)
{
lock (_actions)
_actions.Enqueue(new DispatcherTask() { Action = action });
_actions.Enqueue(action);
}
public void Process()
{
lock(_actions)
while(_actions.TryDequeue(out var action))
action.Execute();
action?.Invoke();
}
}
@@ -5,5 +5,4 @@ namespace Ragon.Core;
public interface IDispatcher
{
public void Dispatch(Action action);
public void Process();
}
@@ -0,0 +1,6 @@
namespace Ragon.Core;
public interface IDispatcherInternal
{
public void Process();
}