Compare commits

...

34 Commits

Author SHA1 Message Date
edmand46 7ddd52bf9d feat: added websocket support 2022-10-23 18:50:05 +04:00
edmand46 ecf3631163 Merge branch 'retry'
# Conflicts:
#	Ragon/Sources/Game/GameRoom.cs
2022-10-23 18:49:32 +04:00
edmand46 8a149eb3b4 fixed: added catch error websocket 2022-10-23 18:45:37 +04:00
edmand46 14ae5e8189 fixed: temporary scheduler 2022-10-22 22:58:15 +04:00
edmand46 b7e8327ca8 feat: websocket 2022-10-22 21:34:35 +04:00
edmand46 b9e79af9d8 fixed: player joined opcode 2022-10-21 22:19:21 +04:00
edmand46 545ec02ecc chore: update version 2022-10-21 00:05:01 +04:00
edmand46 73feb77169 fixed: wrong peerId in restored events 2022-10-21 00:04:33 +04:00
edmand46 cbcf1773aa wip 2022-10-16 18:27:40 +04:00
edmand46 5519ae7679 wip 2022-10-16 18:00:32 +04:00
edmand46 1558b5eefb wip 2022-10-16 17:14:32 +04:00
edmand46 aaa0e4a317 wip 2022-10-16 16:50:22 +04:00
edmand46 ff712dc094 wip 2022-10-16 16:22:27 +04:00
edmand46 c2c75cb513 fixed: double authorization request 2022-10-15 18:48:59 +04:00
edmand46 9a22566f79 fixed: additional data missed 2022-10-15 18:48:48 +04:00
edmand46 783d2ce922 chore: bump version 2022-10-02 16:13:53 +04:00
edmand46 5771ec738b fixed: ownership changing 2022-10-02 16:00:34 +04:00
edmand46 85081e1da7 fix: replication entity event 2022-09-11 15:05:32 +04:00
edmand46 dbaa5d9d92 chore: renaming enums 2022-09-10 10:25:45 +04:00
edmand46 54786c065d wip 2022-09-06 22:39:52 +04:00
edmand46 0b3a0dd1ac refactor: spawning entities with properties 2022-09-04 14:43:17 +04:00
edmand46 72ff37e94a bump version 2022-08-30 01:54:50 +04:00
edmand46 75dab9d16f fixed: on scene entities load 2022-08-30 01:53:47 +04:00
edmand46 25b0e54d13 fixed: changed flow of joining 2022-08-29 01:45:47 +04:00
edmand46 e4f664b557 refactor: renamed plugin api 2022-08-28 19:31:07 +04:00
edmand46 174a4c4422 bump version 2022-08-28 11:51:14 +04:00
edmand46 a51d7c73cd fixed: mistype 2022-08-28 11:46:30 +04:00
edmand46 957622a170 fix: on player leave logic 2022-08-28 00:18:26 +04:00
edmand46 4a07424293 feat: added checks for size of payload 2022-08-27 11:21:51 +04:00
edmand46 568a3282c3 fix: restoring properties 2022-08-27 10:51:57 +04:00
edmand46 6a71fe5fe0 feat: restore properties 2022-08-25 23:12:33 +04:00
edmand46 082e183989 chore: move copyright on top of license 2022-08-24 00:06:37 +04:00
edmand46 51c0482a40 fix: leave player logic 2022-08-21 00:15:07 +04:00
edmand46 9e16c53cad fix: gameloop optimization 2022-08-20 22:05:43 +04:00
48 changed files with 1270 additions and 1050 deletions
+3 -2
View File
@@ -2,7 +2,8 @@ namespace Ragon.Common
{ {
public enum RagonAuthority: byte public enum RagonAuthority: byte
{ {
OWNER_ONLY, None,
ALL, OwnerOnly,
All,
} }
} }
+1 -8
View File
@@ -5,7 +5,6 @@ namespace Ragon.Common
AUTHORIZE, AUTHORIZE,
AUTHORIZED_SUCCESS, AUTHORIZED_SUCCESS,
AUTHORIZED_FAILED, AUTHORIZED_FAILED,
JOIN_OR_CREATE_ROOM, JOIN_OR_CREATE_ROOM,
CREATE_ROOM, CREATE_ROOM,
JOIN_ROOM, JOIN_ROOM,
@@ -13,20 +12,14 @@ namespace Ragon.Common
OWNERSHIP_CHANGED, OWNERSHIP_CHANGED,
JOIN_SUCCESS, JOIN_SUCCESS,
JOIN_FAILED, JOIN_FAILED,
LOAD_SCENE, LOAD_SCENE,
SCENE_IS_LOADED, SCENE_LOADED,
PLAYER_JOINED, PLAYER_JOINED,
PLAYER_LEAVED, PLAYER_LEAVED,
CREATE_ENTITY, CREATE_ENTITY,
CREATE_SCENE_ENTITY,
DESTROY_ENTITY, DESTROY_ENTITY,
SNAPSHOT, SNAPSHOT,
REPLICATE_ENTITY_STATE, REPLICATE_ENTITY_STATE,
REPLICATE_ENTITY_EVENT, REPLICATE_ENTITY_EVENT,
REPLICATE_EVENT,
} }
} }
@@ -0,0 +1,10 @@
namespace Ragon.Common
{
public enum RagonReplicationMode: byte
{
Local,
Server,
LocalAndServer,
Buffered,
}
}
+1
View File
@@ -27,6 +27,7 @@ namespace Ragon.Common
private byte[] _data; private byte[] _data;
private int _offset; private int _offset;
private int _size; private int _size;
public int Lenght => _offset; public int Lenght => _offset;
public int Size => _size - _offset; public int Size => _size - _offset;
+3 -3
View File
@@ -2,8 +2,8 @@ namespace Ragon.Common
{ {
public enum RagonTarget: byte public enum RagonTarget: byte
{ {
OWNER, Owner,
EXCEPT_OWNER, ExceptOwner,
ALL, All,
} }
} }
+1
View File
@@ -1,4 +1,5 @@
using System; using System;
using System.Threading;
using Game.Source; using Game.Source;
using Ragon.Core; using Ragon.Core;
@@ -4,10 +4,10 @@ using Ragon.Core;
namespace Game.Source; namespace Game.Source;
public class AuthorizationProviderByKey: IAuthorizationProvider public class ApplicationHandlerByKey: IApplicationHandler
{ {
private Configuration _configuration; private Configuration _configuration;
public AuthorizationProviderByKey(Configuration configuration) public ApplicationHandlerByKey(Configuration configuration)
{ {
_configuration = configuration; _configuration = configuration;
} }
@@ -26,4 +26,19 @@ public class AuthorizationProviderByKey: IAuthorizationProvider
reject(0); reject(0);
} }
} }
public void OnCustomEvent(ushort peerId, ReadOnlySpan<byte> payload)
{
}
public void OnJoin(ushort peerId)
{
}
public void OnLeave(ushort peerId)
{
}
} }
@@ -1,4 +1,5 @@
using Ragon.Core; using NLog.Fluent;
using Ragon.Core;
namespace Game.Source namespace Game.Source
{ {
@@ -17,12 +18,24 @@ namespace Game.Source
public override void OnPlayerJoined(Player player) public override void OnPlayerJoined(Player player)
{ {
// _logger.Info($"Player({player.PlayerName}) joined to Room({GameRoom.Id})"); // Logger.Info($"Player({player.PlayerName}) joined to Room({Room.Id})");
} }
public override void OnPlayerLeaved(Player player) public override void OnPlayerLeaved(Player player)
{ {
// _logger.Info($"Player({player.PlayerName}) left from Room({GameRoom.Id})"); // Logger.Info($"Player({player.PlayerName}) left from Room({Room.Id})");
}
public override bool OnEntityCreated(Player player, Entity entity)
{
return false;
// Logger.Info($"Player({player.PlayerName}) create entity {entity.EntityId}:{entity.EntityType}");
}
public override bool OnEntityDestroyed(Player player, Entity entity)
{
return false;
// Logger.Info($"Player({player.PlayerName}) destroy entity {entity.EntityId}:{entity.EntityType}");
} }
} }
} }
@@ -10,9 +10,9 @@ namespace Game.Source
return new SimplePlugin(); return new SimplePlugin();
} }
public IAuthorizationProvider CreateAuthorizationProvider(Configuration configuration) public IApplicationHandler CreateAuthorizationProvider(Configuration configuration)
{ {
return new AuthorizationProviderByKey(configuration); return new ApplicationHandlerByKey(configuration);
} }
} }
} }
+1 -1
View File
@@ -2,7 +2,7 @@
"key": "defaultkey", "key": "defaultkey",
"protocol": "1.0.0", "protocol": "1.0.0",
"statisticsInterval": 5, "statisticsInterval": 5,
"sendRate": 30, "sendRate": 50,
"port": 4444, "port": 4444,
"skipTimeout": 60, "skipTimeout": 60,
"reconnectTimeout": 300, "reconnectTimeout": 300,
+1 -1
View File
@@ -101,7 +101,7 @@ namespace Stress
simulationClient.InRoom = true; simulationClient.InRoom = true;
ragonSerializer.Clear(); ragonSerializer.Clear();
ragonSerializer.WriteOperation(RagonOperation.SCENE_IS_LOADED); ragonSerializer.WriteOperation(RagonOperation.SCENE_LOADED);
var sendData = ragonSerializer.ToArray(); var sendData = ragonSerializer.ToArray();
var packet = new Packet(); var packet = new Packet();
+112 -11
View File
@@ -1,31 +1,132 @@
using ENet; using System;
using System.Threading;
using Ragon.Common;
using NLog; using NLog;
namespace Ragon.Core namespace Ragon.Core
{ {
public class Application public class Application : IEventHandler
{ {
private readonly Logger _logger = LogManager.GetCurrentClassLogger(); private readonly RoomManager _roomManager;
private readonly GameThread _gameThread; private readonly Thread _thread;
private readonly Lobby _lobby;
private readonly ISocketServer _socketServer;
private readonly Dispatcher _dispatcher;
private readonly ILogger _logger = LogManager.GetCurrentClassLogger();
private readonly float _deltaTime = 0.0f;
private readonly Configuration _configuration;
private readonly RagonSerializer _serializer;
public ISocketServer SocketServer => _socketServer;
public Dispatcher Dispatcher => _dispatcher;
public Application(PluginFactory factory, Configuration configuration) public Application(PluginFactory factory, Configuration configuration)
{ {
_gameThread = new GameThread(factory, configuration); var authorizationProvider = factory.CreateAuthorizationProvider(configuration);
_configuration = configuration;
_serializer = new RagonSerializer();
var dispatcher = new Dispatcher();
_dispatcher = dispatcher;
_socketServer = new ENetServer(this);
_deltaTime = 1000.0f / configuration.SendRate;
_roomManager = new RoomManager(factory, this);
_lobby = new Lobby(authorizationProvider, _roomManager, this);
_thread = new Thread(Execute);
_thread.Name = "Game Thread";
_thread.IsBackground = true;
} }
public void Start() public void Start()
{ {
Library.Initialize(); var strings = _configuration.Protocol.Split(".");
_gameThread.Start(); if (strings.Length < 3)
_logger.Info("Started"); {
_logger.Error("Wrong protocol passed to connect method");
return;
}
var parts = new uint[] {0, 0, 0};
for (int i = 0; i < parts.Length; i++)
{
if (!uint.TryParse(strings[i], out var v))
{
_logger.Error("Wrong protocol");
return;
}
parts[i] = v;
}
uint encoded = (parts[0] << 16) | (parts[1] << 8) | parts[2];
_socketServer.Start(_configuration.Port, _configuration.MaxConnections, encoded);
_thread.Start();
} }
public void Stop() public void Stop()
{ {
_gameThread.Stop(); _socketServer.Stop();
Library.Deinitialize(); _thread.Interrupt();
_logger.Info("Stopped"); }
private void Execute()
{
while (true)
{
_socketServer.Process();
_dispatcher.Process();
_roomManager.Tick(_deltaTime);
//
Thread.Sleep((int) _deltaTime);
}
}
public void OnConnected(ushort peerId)
{
_logger.Trace("Connected " + peerId);
}
public void OnDisconnected(ushort peerId)
{
_logger.Trace("Disconnected " + peerId);
var player = _lobby.AuthorizationManager.GetPlayer(peerId);
if (player != null)
_roomManager.Left(player, Array.Empty<byte>());
_lobby.OnDisconnected(peerId);
}
public void OnData(ushort peerId, byte[] data)
{
try
{
_serializer.Clear();
_serializer.FromArray(data);
var operation = _serializer.ReadOperation();
if (_roomManager.RoomsBySocket.TryGetValue(peerId, out var room))
room.ProcessEvent(peerId, operation, _serializer);
_lobby.ProcessEvent(peerId, operation, _serializer);
}
catch (Exception exception)
{
_logger.Error(exception);
}
}
public void OnTimeout(ushort peerId)
{
var player = _lobby.AuthorizationManager.GetPlayer(peerId);
if (player != null)
_roomManager.Left(player, Array.Empty<byte>());
_lobby.OnDisconnected(peerId);
} }
} }
} }
@@ -1,21 +1,21 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using NLog;
using NLog.Targets;
using Ragon.Common; using Ragon.Common;
namespace Ragon.Core; namespace Ragon.Core;
public class AuthorizationManager : IAuthorizationManager public class AuthorizationManager
{ {
private IAuthorizationProvider _provider; private Logger _logger = LogManager.GetCurrentClassLogger();
private IGameThread _gameThread; private IApplicationHandler _provider;
private Application _gameThread;
private Lobby _lobby; private Lobby _lobby;
private RagonSerializer _serializer; private RagonSerializer _serializer;
private readonly Dictionary<uint, Player> _playersByPeers; private readonly Dictionary<uint, Player> _playersByPeers;
private readonly Dictionary<string, Player> _playersByIds; private readonly Dictionary<string, Player> _playersByIds;
public AuthorizationManager(IAuthorizationProvider provider, IGameThread gameThread, Lobby lobby, RagonSerializer serializer) public AuthorizationManager(IApplicationHandler provider, Application gameThread, Lobby lobby, RagonSerializer serializer)
{ {
_serializer = serializer; _serializer = serializer;
_lobby = lobby; _lobby = lobby;
@@ -25,16 +25,22 @@ public class AuthorizationManager : IAuthorizationManager
_playersByPeers = new Dictionary<uint, Player>(); _playersByPeers = new Dictionary<uint, Player>();
} }
public void OnAuthorization(uint peerId, string key, string name) public void OnAuthorization(ushort peerId, string key, string name, ReadOnlySpan<byte> additionalData)
{ {
var dispatcher = _gameThread.ThreadDispatcher; if (_playersByPeers.ContainsKey(peerId))
{
_logger.Warn($"Connection already authorized {peerId}");
return;
}
_provider.OnAuthorizationRequest(key, name, Array.Empty<byte>(), var dispatcher = _gameThread.Dispatcher;
_provider.OnAuthorizationRequest(key, name, additionalData.ToArray(),
(playerId, playerName) => { dispatcher.Dispatch(() => Accepted(peerId, playerId, playerName)); }, (playerId, playerName) => { dispatcher.Dispatch(() => Accepted(peerId, playerId, playerName)); },
(errorCode) => { dispatcher.Dispatch(() => Rejected(peerId, errorCode)); }); (errorCode) => { dispatcher.Dispatch(() => Rejected(peerId, errorCode)); });
} }
public void Accepted(uint peerId, string playerId, string playerName) public void Accepted(ushort peerId, string playerId, string playerName)
{ {
_serializer.Clear(); _serializer.Clear();
_serializer.WriteOperation(RagonOperation.AUTHORIZED_SUCCESS); _serializer.WriteOperation(RagonOperation.AUTHORIZED_SUCCESS);
@@ -48,34 +54,34 @@ public class AuthorizationManager : IAuthorizationManager
PeerId = peerId, PeerId = peerId,
IsLoaded = false, IsLoaded = false,
Entities = new List<Entity>(), Entities = new List<Entity>(),
EntitiesIds = new List<int>(), EntitiesIds = new List<ushort>(),
}; };
_playersByIds.Add(playerId, player); _playersByIds.Add(playerId, player);
_playersByPeers.Add(peerId, player); _playersByPeers.Add(peerId, player);
var sendData = _serializer.ToArray(); var sendData = _serializer.ToArray();
_gameThread.Server.Send(peerId, sendData, DeliveryType.Reliable); _gameThread.SocketServer.Send(peerId, sendData, DeliveryType.Reliable);
} }
public void Rejected(uint peerId, uint code) public void Rejected(ushort peerId, uint code)
{ {
_serializer.Clear(); _serializer.Clear();
_serializer.WriteOperation(RagonOperation.AUTHORIZED_FAILED); _serializer.WriteOperation(RagonOperation.AUTHORIZED_FAILED);
_serializer.WriteInt((int) code); _serializer.WriteInt((int) code);
var sendData = _serializer.ToArray(); var sendData = _serializer.ToArray();
_gameThread.Server.Send(peerId, sendData, DeliveryType.Reliable); _gameThread.SocketServer.Send(peerId, sendData, DeliveryType.Reliable);
_gameThread.Server.Disconnect(peerId, 0); _gameThread.SocketServer.Disconnect(peerId, 0);
} }
public void Cleanup(uint peerId) public void Cleanup(ushort peerId)
{ {
if (_playersByPeers.Remove(peerId, out var player)) if (_playersByPeers.Remove(peerId, out var player))
_playersByIds.Remove(player.Id); _playersByIds.Remove(player.Id);
} }
public Player? GetPlayer(uint peerId) public Player? GetPlayer(ushort peerId)
{ {
if (_playersByPeers.TryGetValue(peerId, out var player)) if (_playersByPeers.TryGetValue(peerId, out var player))
return player; return player;
@@ -1,6 +0,0 @@
namespace Ragon.Core;
public interface IAuthorizationManager
{
}
+1 -1
View File
@@ -13,7 +13,7 @@ namespace Ragon.Core
{ {
_logger.Info("Configure application..."); _logger.Info("Configure application...");
var filePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "config.json"); var filePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "config.json");
var configuration = ConfigurationLoader.Load(filePath); var configuration = Configuration.Load(filePath);
var app = new Application(factory, configuration); var app = new Application(factory, configuration);
return app; return app;
} }
@@ -2,14 +2,25 @@
using System.IO; using System.IO;
using Newtonsoft.Json; using Newtonsoft.Json;
using NLog; using NLog;
using Logger = NLog.Logger;
namespace Ragon.Core namespace Ragon.Core
{ {
public static class ConfigurationLoader [Serializable]
public struct Configuration
{ {
public string Key;
public string Protocol;
public int StatisticsInterval;
public ushort SendRate;
public ushort Port;
public int SkipTimeout;
public int ReconnectTimeout;
public int MaxConnections;
public int MaxPlayersPerRoom;
public int MaxRooms;
private static readonly Logger _logger = LogManager.GetCurrentClassLogger(); private static readonly Logger _logger = LogManager.GetCurrentClassLogger();
private static readonly string _serverVersion = "1.0.9-rc"; private static readonly string _serverVersion = "1.0.22-rc";
private static void CopyrightInfo() private static void CopyrightInfo()
{ {
@@ -18,11 +29,10 @@ namespace Ragon.Core
_logger.Info($"OS: {Environment.OSVersion}"); _logger.Info($"OS: {Environment.OSVersion}");
_logger.Info($"Processors: {Environment.ProcessorCount}"); _logger.Info($"Processors: {Environment.ProcessorCount}");
_logger.Info($"Runtime Version: {Environment.Version}"); _logger.Info($"Runtime Version: {Environment.Version}");
_logger.Info("=================================="); _logger.Info("==================================");
_logger.Info("= ="); _logger.Info("| |");
_logger.Info($"={"Ragon".PadBoth(32)}="); _logger.Info("| Ragon |");
_logger.Info("= ="); _logger.Info("| |");
_logger.Info("=================================="); _logger.Info("==================================");
} }
@@ -1,19 +0,0 @@
using System;
namespace Ragon.Core
{
[Serializable]
public struct Configuration
{
public string Key;
public string Protocol;
public int StatisticsInterval;
public ushort SendRate;
public ushort Port;
public int SkipTimeout;
public int ReconnectTimeout;
public int MaxConnections;
public int MaxPlayersPerRoom;
public int MaxRooms;
}
}
@@ -3,7 +3,7 @@ using System.Collections.Generic;
namespace Ragon.Core; namespace Ragon.Core;
public class Dispatcher: IDispatcher, IDispatcherInternal public class Dispatcher
{ {
public Queue<Action> _actions = new Queue<Action>(); public Queue<Action> _actions = new Queue<Action>();
+227 -5
View File
@@ -1,26 +1,248 @@
using System;
using System.Collections.Generic;
using NLog;
using Ragon.Common; using Ragon.Common;
namespace Ragon.Core; namespace Ragon.Core;
public class Entity public class Entity
{ {
private ILogger _logger = LogManager.GetCurrentClassLogger();
private GameRoom _room;
private static ushort _idGenerator = 0; private static ushort _idGenerator = 0;
public ushort EntityId { get; private set; } public ushort EntityId { get; private set; }
public ushort StaticId { get; private set; } public ushort StaticId { get; private set; }
public ushort EntityType { get; private set; } public ushort EntityType { get; private set; }
public ushort OwnerId { get; private set; } public ushort OwnerId { get; private set; }
public byte[] Payload { get; private set; }
public RagonAuthority Authority { get; private set; } public RagonAuthority Authority { get; private set; }
public EntityProperty[] Properties { get; private set; }
public byte[] Payload { get; set; }
public Entity(ushort ownerId, ushort entityType, ushort staticId, RagonAuthority stateAuthority, RagonAuthority eventAuthority, int props) private List<EntityProperty> _properties;
private List<EntityEvent> _bufferedEvents;
public Entity(GameRoom room, ushort ownerId, ushort entityType, ushort staticId, RagonAuthority eventAuthority)
{ {
OwnerId = ownerId; OwnerId = ownerId;
StaticId = staticId; StaticId = staticId;
EntityType = entityType; EntityType = entityType;
EntityId = _idGenerator++; EntityId = _idGenerator++;
Properties = new EntityProperty[props]; Payload = Array.Empty<byte>();
Payload = new byte[1024];
Authority = eventAuthority; Authority = eventAuthority;
_room = room;
_properties = new List<EntityProperty>();
_bufferedEvents = new List<EntityEvent>();
}
public void SetPayload(byte[] payload)
{
Payload = payload;
}
public void SetOwner(ushort ownerId)
{
OwnerId = ownerId;
}
public void AddProperty(EntityProperty property)
{
_properties.Add(property);
}
public void ReplicateEvent(ushort peerId, ushort eventId, ReadOnlySpan<byte> payload, RagonReplicationMode eventMode, RagonTarget targetMode)
{
if (Authority == RagonAuthority.OwnerOnly && OwnerId != peerId)
{
_logger.Warn($"Player have not enought authority for event with Id {eventId}");
return;
}
if (eventMode == RagonReplicationMode.Buffered && targetMode != RagonTarget.Owner)
{
var bufferedEvent = new EntityEvent()
{
EventData = payload.ToArray(),
Target = targetMode,
EventId = eventId,
PeerId = peerId,
};
_bufferedEvents.Add(bufferedEvent);
}
var serializer = _room.GetSharedSerializer();
serializer.Clear();
serializer.WriteOperation(RagonOperation.REPLICATE_ENTITY_EVENT);
serializer.WriteUShort(eventId);
serializer.WriteUShort(peerId);
serializer.WriteByte((byte) eventMode);
serializer.WriteUShort(EntityId);
serializer.WriteData(ref payload);
var sendData = serializer.ToArray();
Send(targetMode, sendData);
}
public void ReadState(uint peerId, RagonSerializer serializer)
{
if (OwnerId != peerId)
{
_logger.Warn($"Not owner can't change properties of object {EntityId}");
return;
}
for (var i = 0; i < _properties.Count; i++)
{
if (serializer.ReadBool())
{
var property = _properties[i];
var size = property.Size;
if (!property.IsFixed)
size = serializer.ReadUShort();
if (size > property.Capacity)
{
_logger.Warn($"Property {i} payload too large, size: {size}");
continue;
}
var propertyPayload = serializer.ReadData(size);
property.Write(ref propertyPayload);
property.Size = size;
}
}
}
public void WriteProperties(RagonSerializer serializer)
{
serializer.WriteUShort(EntityId);
for (int propertyIndex = 0; propertyIndex < _properties.Count; propertyIndex++)
{
var property = _properties[propertyIndex];
if (property.IsDirty)
{
serializer.WriteBool(true);
var span = serializer.GetWritableData(property.Size);
var data = property.Read();
data.CopyTo(span);
property.Clear();
}
else
{
serializer.WriteBool(false);
}
}
}
public void WriteSnapshot(RagonSerializer serializer)
{
for (int propertyIndex = 0; propertyIndex < _properties.Count; propertyIndex++)
{
var property = _properties[propertyIndex];
var hasPayload = property.IsFixed || property.Size > 0 && !property.IsFixed;
if (hasPayload)
{
serializer.WriteBool(true);
var span = serializer.GetWritableData(property.Size);
var data = property.Read();
data.CopyTo(span);
}
else
{
serializer.WriteBool(false);
}
}
}
public void RestoreBufferedEvents(ushort peerId)
{
var serializer = _room.GetSharedSerializer();
foreach (var bufferedEvent in _bufferedEvents)
{
serializer.Clear();
serializer.WriteOperation(RagonOperation.REPLICATE_ENTITY_EVENT);
serializer.WriteUShort(bufferedEvent.EventId);
serializer.WriteUShort(bufferedEvent.PeerId);
serializer.WriteByte((byte) RagonReplicationMode.Server);
serializer.WriteUShort(EntityId);
ReadOnlySpan<byte> data = bufferedEvent.EventData.AsSpan();
serializer.WriteData(ref data);
var sendData = serializer.ToArray();
_room.Send(peerId, sendData, DeliveryType.Reliable);
}
}
public void Create()
{
var serializer = _room.GetSharedSerializer();
serializer.Clear();
serializer.WriteOperation(RagonOperation.CREATE_ENTITY);
serializer.WriteUShort(EntityType);
serializer.WriteUShort(EntityId);
serializer.WriteUShort(OwnerId);
ReadOnlySpan<byte> entityPayload = Payload.AsSpan();
serializer.WriteUShort((ushort) entityPayload.Length);
serializer.WriteData(ref entityPayload);
var sendData = serializer.ToArray();
_room.BroadcastToReady(sendData, DeliveryType.Reliable);
}
public void Destroy(ReadOnlySpan<byte> payload)
{
var serializer = _room.GetSharedSerializer();
serializer.Clear();
serializer.WriteOperation(RagonOperation.DESTROY_ENTITY);
serializer.WriteInt(EntityId);
serializer.WriteUShort((ushort) payload.Length);
serializer.WriteData(ref payload);
var sendData = serializer.ToArray();
_room.BroadcastToReady(sendData, DeliveryType.Reliable);
}
void Send(RagonTarget targetMode, byte[] sendData)
{
switch (targetMode)
{
case RagonTarget.Owner:
{
_room.Send(OwnerId, sendData, DeliveryType.Reliable);
break;
}
case RagonTarget.ExceptOwner:
{
_room.BroadcastToReady(sendData, new [] { OwnerId }, DeliveryType.Reliable);
break;
}
case RagonTarget.All:
{
_room.BroadcastToReady(sendData, DeliveryType.Reliable);
break;
}
}
}
public void ProcessEvent(Player player, RagonSerializer reader)
{
var eventId = reader.ReadUShort();
var eventMode = (RagonReplicationMode) reader.ReadByte();
var targetMode = (RagonTarget) reader.ReadByte();
var payloadData = reader.ReadData(reader.Size);
Span<byte> payloadRaw = stackalloc byte[reader.Size];
ReadOnlySpan<byte> payload = payloadRaw;
payloadData.CopyTo(payloadRaw);
if (_room.Plugin.InternalHandle(player.PeerId, EntityId, eventId, ref payload))
return;
ReplicateEvent(player.PeerId, eventId, payload, eventMode, targetMode);
} }
} }
+11
View File
@@ -0,0 +1,11 @@
using Ragon.Common;
namespace Ragon.Core;
public class EntityEvent
{
public ushort PeerId { get; set; }
public ushort EventId { get; set; }
public byte[] EventData { get; set; }
public RagonTarget Target { set; get; }
}
+4 -2
View File
@@ -6,17 +6,19 @@ namespace Ragon.Core;
public class EntityProperty public class EntityProperty
{ {
public int Size { get; set; } public int Size { get; set; }
public int Capacity { get; set; }
public bool IsDirty { get; private set; } public bool IsDirty { get; private set; }
public bool IsFixed { get; private set; } public bool IsFixed { get; private set; }
private byte[] _data; private byte[] _data;
public EntityProperty(int size, bool isFixed) public EntityProperty(int size, bool isFixed)
{ {
_data = new byte[512]; Capacity = 512;
Size = size; Size = size;
IsFixed = isFixed; IsFixed = isFixed;
IsDirty = true; IsDirty = true;
_data = new byte[Capacity];
} }
public ReadOnlySpan<byte> Read() public ReadOnlySpan<byte> Read()
@@ -1,14 +0,0 @@
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);
}
}
}
@@ -1,14 +0,0 @@
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;
}
}
}
-555
View File
@@ -1,555 +0,0 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using NLog;
using Ragon.Common;
namespace Ragon.Core
{
public class GameRoom : IGameRoom
{
public int PlayersMin { get; private set; }
public int PlayersMax { get; private set; }
public int PlayersCount => _players.Count;
public int EntitiesCount => _entities.Count;
public string Id { get; private set; }
public string Map { get; private set; }
private ILogger _logger = LogManager.GetCurrentClassLogger();
private Dictionary<uint, Player> _players = new();
private Dictionary<int, Entity> _entities = new();
private uint _owner;
private readonly IScheduler _scheduler;
private readonly IGameThread _gameThread;
private readonly PluginBase _plugin;
private readonly RagonSerializer _serializer = new(512);
// Cache
private uint[] _readyPlayers = Array.Empty<uint>();
private uint[] _allPlayers = Array.Empty<uint>();
private Entity[] _entitiesAll = Array.Empty<Entity>();
private HashSet<Entity> _entitiesDirtySet = new HashSet<Entity>();
private List<Entity> _entitiesDirty = new List<Entity>();
private List<uint> _peersCache = new List<uint>();
public GameRoom(IGameThread gameThread, PluginBase pluginBase, string roomId, string map, int min, int max)
{
_gameThread = gameThread;
_plugin = pluginBase;
_scheduler = new Scheduler();
Map = map;
PlayersMin = min;
PlayersMax = max;
Id = roomId;
_plugin.Attach(this);
}
public void Joined(Player player, ReadOnlySpan<byte> payload)
{
if (_players.Count == 0)
{
_owner = player.PeerId;
}
{
_serializer.Clear();
_serializer.WriteOperation(RagonOperation.PLAYER_JOINED);
_serializer.WriteUShort((ushort) player.PeerId);
_serializer.WriteString(player.Id);
_serializer.WriteString(player.PlayerName);
var sendData = _serializer.ToArray();
Broadcast(_readyPlayers, sendData, DeliveryType.Reliable);
}
_players.Add(player.PeerId, player);
_allPlayers = _players.Select(p => p.Key).ToArray();
{
_serializer.Clear();
_serializer.WriteOperation(RagonOperation.JOIN_SUCCESS);
_serializer.WriteString(Id);
_serializer.WriteString(player.Id);
_serializer.WriteString(GetOwner().Id);
_serializer.WriteUShort((ushort) PlayersMin);
_serializer.WriteUShort((ushort) PlayersMax);
var sendData = _serializer.ToArray();
Send(player.PeerId, sendData, DeliveryType.Reliable);
}
{
_serializer.Clear();
_serializer.WriteOperation(RagonOperation.LOAD_SCENE);
_serializer.WriteString(Map);
var sendData = _serializer.ToArray();
Send(player.PeerId, sendData, DeliveryType.Reliable);
}
}
public void Leave(uint peerId)
{
if (_players.Remove(peerId, out var player))
{
_allPlayers = _players.Select(p => p.Key).ToArray();
_readyPlayers = _players.Where(p => p.Value.IsLoaded).Select(p => p.Key).ToArray();
{
_plugin.OnPlayerLeaved(player);
_serializer.Clear();
_serializer.WriteOperation(RagonOperation.PLAYER_LEAVED);
_serializer.WriteString(player.Id);
_serializer.WriteUShort((ushort) player.EntitiesIds.Count);
foreach (var entityId in player.EntitiesIds)
{
_serializer.WriteInt(entityId);
_entities.Remove(entityId);
}
var sendData = _serializer.ToArray();
Broadcast(_readyPlayers, sendData);
}
if (_allPlayers.Length > 0)
{
var nextOwnerId = _allPlayers[0];
_owner = nextOwnerId;
var nextOwner = _players[nextOwnerId];
_serializer.Clear();
_serializer.WriteOperation(RagonOperation.OWNERSHIP_CHANGED);
_serializer.WriteString(nextOwner.Id);
var sendData = _serializer.ToArray();
Broadcast(_readyPlayers, sendData);
}
_entitiesAll = _entities.Values.ToArray();
}
}
public void ProcessEvent(ushort peerId, RagonOperation operation, ReadOnlySpan<byte> payloadRawData)
{
_serializer.Clear();
_serializer.FromSpan(ref payloadRawData);
switch (operation)
{
case RagonOperation.REPLICATE_ENTITY_STATE:
{
var entitiesCount = _serializer.ReadUShort();
for (var entityIndex = 0; entityIndex < entitiesCount; entityIndex++)
{
var entityId = _serializer.ReadUShort();
if (_entities.TryGetValue(entityId, out var ent))
{
if (ent.OwnerId != peerId)
{
_logger.Warn($"Not owner can't change properties of object {entityId}");
return;
}
for (var i = 0; i < ent.Properties.Length; i++)
{
if (_serializer.ReadBool())
{
var property = ent.Properties[i];
if (!property.IsFixed)
property.Size = _serializer.ReadUShort();
var propertyPayload = _serializer.ReadData(property.Size);
property.Write(ref propertyPayload);
}
}
if (_entitiesDirtySet.Add(ent))
_entitiesDirty.Add(ent);
}
else
{
_logger.Error($"Entity with Id {entityId} not found, replication interrupted");
break;
}
}
break;
}
case RagonOperation.REPLICATE_ENTITY_EVENT:
{
var evntId = _serializer.ReadUShort();
var evntMode = _serializer.ReadByte();
var targetMode = (RagonTarget) _serializer.ReadByte();
var entityId = _serializer.ReadUShort();
if (!_entities.TryGetValue(entityId, out var ent))
return;
if (ent.Authority == RagonAuthority.OWNER_ONLY && ent.OwnerId != peerId)
return;
Span<byte> payloadRaw = stackalloc byte[_serializer.Size];
var payloadData = _serializer.ReadData(_serializer.Size);
payloadData.CopyTo(payloadRaw);
ReadOnlySpan<byte> payload = payloadRaw;
if (_plugin.InternalHandle(peerId, entityId, evntId, ref payload))
return;
_serializer.Clear();
_serializer.WriteOperation(RagonOperation.REPLICATE_ENTITY_EVENT);
_serializer.WriteUShort(evntId);
_serializer.WriteUShort((ushort) peerId);
_serializer.WriteByte(evntMode);
_serializer.WriteUShort(entityId);
_serializer.WriteData(ref payload);
var sendData = _serializer.ToArray();
switch (targetMode)
{
case RagonTarget.OWNER:
{
Send(ent.OwnerId, sendData, DeliveryType.Reliable);
break;
}
case RagonTarget.EXCEPT_OWNER:
{
_peersCache.Clear();
foreach (var playerPeerId in _readyPlayers)
if (playerPeerId != ent.OwnerId)
_peersCache.Add(playerPeerId);
Broadcast(_peersCache.ToArray(), sendData, DeliveryType.Reliable);
break;
}
case RagonTarget.ALL:
{
Broadcast(_readyPlayers, sendData, DeliveryType.Reliable);
break;
}
}
break;
}
case RagonOperation.LOAD_SCENE:
{
var sceneName = _serializer.ReadString();
_readyPlayers = Array.Empty<uint>();
_entitiesAll = Array.Empty<Entity>();
_entities.Clear();
_serializer.Clear();
_serializer.WriteOperation(RagonOperation.LOAD_SCENE);
_serializer.WriteString(sceneName);
var sendData = _serializer.ToArray();
Broadcast(_allPlayers, sendData, DeliveryType.Reliable);
break;
}
case RagonOperation.REPLICATE_EVENT:
{
var evntId = _serializer.ReadUShort();
var evntMode = _serializer.ReadByte();
var targetMode = (RagonTarget) _serializer.ReadByte();
Span<byte> payloadRaw = stackalloc byte[_serializer.Size];
var payloadData = _serializer.ReadData(_serializer.Size);
payloadData.CopyTo(payloadRaw);
ReadOnlySpan<byte> payload = payloadRaw;
if (_plugin.InternalHandle(peerId, evntId, ref payload))
return;
_serializer.Clear();
_serializer.WriteOperation(RagonOperation.REPLICATE_EVENT);
_serializer.WriteUShort((ushort) peerId);
_serializer.WriteByte(evntMode);
_serializer.WriteUShort(evntId);
_serializer.WriteData(ref payload);
var sendData = _serializer.ToArray();
switch (targetMode)
{
case RagonTarget.OWNER:
{
Send(_owner, sendData, DeliveryType.Reliable);
break;
}
case RagonTarget.EXCEPT_OWNER:
{
_peersCache.Clear();
foreach (var playerPeerId in _readyPlayers)
if (playerPeerId != _owner)
_peersCache.Add(playerPeerId);
Broadcast(_peersCache.ToArray(), sendData, DeliveryType.Reliable);
break;
}
case RagonTarget.ALL:
{
Broadcast(_readyPlayers, sendData, DeliveryType.Reliable);
break;
}
}
break;
}
case RagonOperation.CREATE_SCENE_ENTITY:
{
var entityType = _serializer.ReadUShort();
var staticId = _serializer.ReadUShort();
var propertiesCount = _serializer.ReadUShort();
var entity = new Entity(peerId, entityType, staticId, RagonAuthority.ALL, RagonAuthority.OWNER_ONLY, propertiesCount);
for (var i = 0; i < propertiesCount; i++)
{
var propertyType = _serializer.ReadBool();
var propertySize = _serializer.ReadUShort();
entity.Properties[i] = new EntityProperty(propertySize, propertyType);
}
{
var entityPayload = _serializer.ReadData(_serializer.Size);
entity.Payload = entityPayload.ToArray();
}
var player = _players[peerId];
player.Entities.Add(entity);
player.EntitiesIds.Add(entity.EntityId);
var ownerId = (ushort) peerId;
_entities.Add(entity.EntityId, entity);
_entitiesAll = _entities.Values.ToArray();
_plugin.OnEntityCreated(player, entity);
_serializer.Clear();
_serializer.WriteOperation(RagonOperation.CREATE_SCENE_ENTITY);
_serializer.WriteUShort(entityType);
_serializer.WriteUShort(entity.EntityId);
_serializer.WriteUShort(staticId);
_serializer.WriteUShort(ownerId);
{
ReadOnlySpan<byte> entityPayload = entity.Payload.AsSpan();
_serializer.WriteData(ref entityPayload);
}
var sendData = _serializer.ToArray();
Broadcast(_readyPlayers, sendData, DeliveryType.Reliable);
break;
}
case RagonOperation.CREATE_ENTITY:
{
var entityType = _serializer.ReadUShort();
var propertiesCount = _serializer.ReadUShort();
var entity = new Entity(peerId, entityType, 0, RagonAuthority.ALL, RagonAuthority.ALL, propertiesCount);
// _logger.Trace("Created entity with properties: " + propertiesCount);
for (var i = 0; i < propertiesCount; i++)
{
var propertyType = _serializer.ReadBool();
var propertySize = _serializer.ReadUShort();
entity.Properties[i] = new EntityProperty(propertySize, propertyType);
// _logger.Trace($"Property: {i} Size: {propertySize} IsFixed: {propertyType}");
}
{
var entityPayload = _serializer.ReadData(_serializer.Size);
entity.Payload = entityPayload.ToArray();
}
var player = _players[peerId];
player.Entities.Add(entity);
player.EntitiesIds.Add(entity.EntityId);
var ownerId = (ushort) peerId;
_entities.Add(entity.EntityId, entity);
_entitiesAll = _entities.Values.ToArray();
_plugin.OnEntityCreated(player, entity);
_serializer.Clear();
_serializer.WriteOperation(RagonOperation.CREATE_ENTITY);
_serializer.WriteUShort(entityType);
_serializer.WriteUShort(entity.EntityId);
_serializer.WriteUShort(ownerId);
{
ReadOnlySpan<byte> entityPayload = entity.Payload.AsSpan();
_serializer.WriteData(ref entityPayload);
}
var sendData = _serializer.ToArray();
Broadcast(_readyPlayers, sendData, DeliveryType.Reliable);
break;
}
case RagonOperation.DESTROY_ENTITY:
{
var entityId = _serializer.ReadInt();
if (_entities.TryGetValue(entityId, out var entity))
{
if (entity.Authority == RagonAuthority.OWNER_ONLY && entity.OwnerId != peerId)
return;
var player = _players[peerId];
var destroyPayload = _serializer.ReadData(_serializer.Size);
player.Entities.Remove(entity);
player.EntitiesIds.Remove(entity.EntityId);
_entities.Remove(entityId);
_entitiesAll = _entities.Values.ToArray();
_plugin.OnEntityDestroyed(player, entity);
_serializer.Clear();
_serializer.WriteOperation(RagonOperation.DESTROY_ENTITY);
_serializer.WriteInt(entityId);
_serializer.WriteData(ref destroyPayload);
var sendData = _serializer.ToArray();
Broadcast(_readyPlayers, sendData, DeliveryType.Reliable);
}
break;
}
case RagonOperation.SCENE_IS_LOADED:
{
_serializer.Clear();
_serializer.WriteOperation(RagonOperation.SNAPSHOT);
_serializer.WriteUShort((ushort) _allPlayers.Length);
foreach (var playerPeerId in _allPlayers)
{
_serializer.WriteString(_players[playerPeerId].Id);
_serializer.WriteUShort((ushort) playerPeerId);
_serializer.WriteString(_players[playerPeerId].PlayerName);
}
var dynamicEntities = _entitiesAll.Where(e => e.StaticId == 0).ToArray();
_serializer.WriteUShort((ushort) dynamicEntities.Length);
foreach (var entity in dynamicEntities)
{
ReadOnlySpan<byte> payload = entity.Payload.AsSpan();
_serializer.WriteUShort(entity.EntityType);
_serializer.WriteUShort(entity.EntityId);
_serializer.WriteUShort((ushort) entity.OwnerId);
_serializer.WriteUShort((ushort) payload.Length);
_serializer.WriteData(ref payload);
}
var staticCount = _entitiesAll.Where(e => e.StaticId != 0).ToArray();
_serializer.WriteUShort((ushort) staticCount.Length);
foreach (var entity in staticCount)
{
ReadOnlySpan<byte> payload = entity.Payload.AsSpan();
_serializer.WriteUShort(entity.EntityType);
_serializer.WriteUShort(entity.EntityId);
_serializer.WriteUShort(entity.StaticId);
_serializer.WriteUShort(entity.OwnerId);
_serializer.WriteUShort((ushort) payload.Length);
_serializer.WriteData(ref payload);
}
var sendData = _serializer.ToArray();
Send(peerId, sendData, DeliveryType.Reliable);
_players[peerId].IsLoaded = true;
_readyPlayers = _players.Where(p => p.Value.IsLoaded).Select(p => p.Key).ToArray();
_plugin.OnPlayerJoined(_players[peerId]);
break;
}
}
}
public void Tick(float deltaTime)
{
_scheduler.Tick(deltaTime);
if (_entitiesDirty.Count > 0)
{
_serializer.Clear();
_serializer.WriteOperation(RagonOperation.REPLICATE_ENTITY_STATE);
_serializer.WriteUShort((ushort) _entitiesDirty.Count);
for (var entityIndex = 0; entityIndex < _entitiesDirty.Count; entityIndex++)
{
var entity = _entitiesDirty[entityIndex];
_serializer.WriteUShort(entity.EntityId);
for (int propertyIndex = 0; propertyIndex < entity.Properties.Length; propertyIndex++)
{
var property = entity.Properties[propertyIndex];
if (property.IsDirty)
{
_serializer.WriteBool(true);
var span = _serializer.GetWritableData(property.Size);
var data = property.Read();
data.CopyTo(span);
property.Clear();
}
else
{
_serializer.WriteBool(false);
}
}
}
_entitiesDirty.Clear();
_entitiesDirtySet.Clear();
var sendData = _serializer.ToArray();
Broadcast(_readyPlayers, sendData);
}
}
public void Start()
{
_plugin.OnStart();
}
public void Stop()
{
foreach (var peerId in _allPlayers)
_gameThread.Server.Disconnect(peerId, 0);
_plugin.OnStop();
_plugin.Detach();
}
public Player GetPlayerById(uint peerId) => _players[peerId];
public Entity GetEntityById(int entityId) => _entities[entityId];
public Player GetOwner() => _players[_owner];
public IDispatcher GetThreadDispatcher() => _gameThread.ThreadDispatcher;
public IScheduler GetScheduler() => _scheduler;
public void Send(uint peerId, byte[] rawData, DeliveryType deliveryType = DeliveryType.Unreliable)
{
_gameThread.Server.Send(peerId, rawData, deliveryType);
}
public void Broadcast(uint[] peersIds, byte[] rawData, DeliveryType deliveryType = DeliveryType.Unreliable)
{
_gameThread.Server.Broadcast(peersIds, rawData, deliveryType);
}
public void Broadcast(byte[] rawData, DeliveryType deliveryType = DeliveryType.Unreliable)
{
_gameThread.Server.Broadcast(_allPlayers, rawData, deliveryType);
}
}
}
-161
View File
@@ -1,161 +0,0 @@
using System;
using System.Diagnostics;
using System.Linq;
using System.Security.Cryptography;
using System.Threading;
using Ragon.Common;
using ENet;
using NLog;
namespace Ragon.Core
{
public class GameThread : IGameThread, IHandler
{
private readonly RoomManager _roomManager;
private readonly Thread _thread;
private readonly Stopwatch _gameLoopTimer;
private readonly Stopwatch _statisticsTimer;
private readonly Stopwatch _serverTimer;
private readonly Stopwatch _logicTimer;
private readonly Lobby _lobby;
private readonly ILogger _logger = LogManager.GetCurrentClassLogger();
private readonly float _deltaTime = 0.0f;
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;
ThreadDispatcher = dispatcher;
Server = new ENetServer(this);
_deltaTime = 1000.0f / configuration.SendRate;
_roomManager = new RoomManager(factory, this);
_lobby = new Lobby(authorizationProvider, _roomManager, this);
_gameLoopTimer = new Stopwatch();
_statisticsTimer = new Stopwatch();
_serverTimer = new Stopwatch();
_logicTimer = new Stopwatch();
_thread = new Thread(Execute);
_thread.Name = "Game Thread";
_thread.IsBackground = true;
}
public void Start()
{
var strings = _configuration.Protocol.Split(".");
if (strings.Length < 3)
{
_logger.Error("Wrong protocol passed to connect method");
return;
}
var parts = new uint[] {0, 0, 0};
for (int i = 0; i < parts.Length; i++)
{
if (!uint.TryParse(strings[i], out var v))
{
_logger.Error("Wrong protocol");
return;
}
parts[i] = v;
}
uint encoded = (parts[0] << 16) | (parts[1] << 8) | parts[2];
Server.Start(_configuration.Port, _configuration.MaxConnections, encoded);
_gameLoopTimer.Start();
_statisticsTimer.Start();
_logicTimer.Start();
_serverTimer.Start();
_thread.Start();
}
public void Stop()
{
Server.Stop();
_gameLoopTimer.Stop();
_statisticsTimer.Stop();
_thread.Interrupt();
}
private void Execute()
{
while (true)
{
_logicTimer.Restart();
_serverTimer.Restart();
Server.Process();
_dispatcherInternal.Process();
var elapsedMilliseconds = _gameLoopTimer.ElapsedMilliseconds;
if (elapsedMilliseconds > _deltaTime)
{
_roomManager.Tick(elapsedMilliseconds / 1000.0f);
_gameLoopTimer.Restart();
continue;
}
if (_statisticsTimer.Elapsed.Seconds > _configuration.StatisticsInterval && _roomManager.RoomsBySocket.Count > 0)
{
var rooms = _roomManager.Rooms.Count;
var clients = _roomManager.RoomsBySocket.Count;
var entities = _roomManager.Rooms.Select(r => r.EntitiesCount).Sum();
_logger.Trace($"Rooms: {rooms} Clients: {clients} Entities: {entities}");
_statisticsTimer.Restart();
}
}
}
public void OnEvent(Event evnt)
{
if (evnt.Type == EventType.Timeout || evnt.Type == EventType.Disconnect)
{
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 == EventType.Receive)
{
try
{
var peerId = (ushort) evnt.Peer.ID;
var dataRaw = new byte[evnt.Packet.Length];
evnt.Packet.CopyTo(dataRaw);
var data = new ReadOnlySpan<byte>(dataRaw);
var operation = (RagonOperation) data[0];
var payload = data.Slice(1, data.Length - 1);
if (_roomManager.RoomsBySocket.TryGetValue(peerId, out var room))
room.ProcessEvent(peerId, operation, payload);
_lobby.ProcessEvent(peerId, operation, payload);
}
catch (Exception exception)
{
_logger.Error(exception);
}
}
}
}
}
-20
View File
@@ -1,20 +0,0 @@
namespace Ragon.Core;
public interface IGameRoom
{
public string Id { get; }
public string Map { get; }
public int PlayersMin { get; }
public int PlayersMax { get; }
public int PlayersCount { get; }
public Player GetPlayerById(uint peerId);
public Entity GetEntityById(int entityId);
public Player GetOwner();
public IDispatcher GetThreadDispatcher();
public IScheduler GetScheduler();
public void Send(uint peerId, byte[] rawData, DeliveryType deliveryType = DeliveryType.Unreliable);
public void Broadcast(uint[] peersIds, byte[] rawData, DeliveryType deliveryType = DeliveryType.Unreliable);
public void Broadcast(byte[] rawData, DeliveryType deliveryType = DeliveryType.Unreliable);
}
-9
View File
@@ -1,9 +0,0 @@
using Ragon.Common;
namespace Ragon.Core;
public interface IGameThread
{
public IDispatcher ThreadDispatcher { get; }
public ISocketServer Server { get; }
}
+503
View File
@@ -0,0 +1,503 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using NLog;
using Ragon.Common;
namespace Ragon.Core
{
public class GameRoom
{
public int PlayersMin { get; private set; }
public int PlayersMax { get; private set; }
public int PlayersCount => _players.Count;
public int EntitiesCount => _entities.Count;
public string Id { get; private set; }
public string Map { get; private set; }
public PluginBase Plugin => _plugin;
private ILogger _logger = LogManager.GetCurrentClassLogger();
private Dictionary<ushort, Player> _players = new();
private Dictionary<int, Entity> _entities = new();
private ushort _owner;
private readonly ISocketServer _socketServer;
private readonly Scheduler _scheduler;
private readonly Application _application;
private readonly PluginBase _plugin;
private readonly RagonSerializer _writer = new(512);
// Cache
private ushort[] _readyPlayers = Array.Empty<ushort>();
private ushort[] _allPlayers = Array.Empty<ushort>();
private Entity[] _entitiesAll = Array.Empty<Entity>();
private HashSet<Entity> _entitiesDirtySet = new HashSet<Entity>();
private List<Entity> _entitiesDirty = new List<Entity>();
private List<ushort> _peersCache = new List<ushort>();
private List<ushort> _awaitingPeers = new List<ushort>();
public Player GetPlayerByPeer(ushort peerId) => _players[peerId];
public Player GetPlayerById(string id) => _players.Values.FirstOrDefault(p => p.Id == id)!;
public Entity GetEntityById(int entityId) => _entities[entityId];
public Player GetOwner() => _players[_owner];
public RagonSerializer GetSharedSerializer() => _writer;
public GameRoom(Application application, PluginBase pluginBase, string roomId, string map, int min, int max)
{
_application = application;
_socketServer = application.SocketServer;
_plugin = pluginBase;
_scheduler = new Scheduler();
Map = map;
PlayersMin = min;
PlayersMax = max;
Id = roomId;
_plugin.Attach(this);
}
public void AddPlayer(Player player, ReadOnlySpan<byte> payload)
{
if (_players.Count == 0)
{
_owner = player.PeerId;
}
_players.Add(player.PeerId, player);
_allPlayers = _players.Select(p => p.Key).ToArray();
AcceptPlayer(player);
}
public void RemovePlayer(ushort peerId)
{
if (_players.Remove(peerId, out var player))
{
_allPlayers = _players.Select(p => p.Key).ToArray();
_readyPlayers = _players.Where(p => p.Value.IsLoaded).Select(p => p.Key).ToArray();
_plugin.OnPlayerLeaved(player);
BroadcastLeaved(player);
if (_allPlayers.Length > 0 && player.PeerId == _owner)
{
var nextOwnerId = _allPlayers[0];
var nextOwner = _players[nextOwnerId];
_owner = nextOwnerId;
BroadcastNewOwner(player, nextOwner);
}
_entitiesAll = _entities.Values.ToArray();
}
}
public void ProcessEvent(ushort peerId, RagonOperation operation, RagonSerializer reader)
{
switch (operation)
{
case RagonOperation.LOAD_SCENE:
{
var sceneName = reader.ReadString();
BroadcastNewScene(sceneName);
break;
}
case RagonOperation.SCENE_LOADED:
{
var player = _players[peerId];
if (peerId == _owner)
{
var statics = reader.ReadUShort();
for (var staticIndex = 0; staticIndex < statics; staticIndex++)
{
var entityType = reader.ReadUShort();
var entityAuthority = (RagonAuthority) reader.ReadByte();
var staticId = reader.ReadUShort();
var propertiesCount = reader.ReadUShort();
var entity = new Entity(this, player.PeerId, entityType, staticId, entityAuthority);
for (var propertyIndex = 0; propertyIndex < propertiesCount; propertyIndex++)
{
var propertyType = reader.ReadBool();
var propertySize = reader.ReadUShort();
entity.AddProperty(new EntityProperty(propertySize, propertyType));
}
player.AttachEntity(entity);
AttachEntity(player, entity);
}
_entitiesAll = _entities.Values.ToArray();
_logger.Trace($"Scene entities: {statics}");
_awaitingPeers.Add(peerId);
foreach (var peer in _awaitingPeers)
{
var joinedPlayer = _players[peer];
joinedPlayer.IsLoaded = true;
_plugin.OnPlayerJoined(joinedPlayer);
_logger.Trace($"[{_owner}][{peer}] Player {joinedPlayer.Id} restored");
BroadcastJoined(player);
}
_readyPlayers = _players.Where(p => p.Value.IsLoaded).Select(p => p.Key).ToArray();
BroadcastSnapshot(_awaitingPeers.ToArray());
_awaitingPeers.Clear();
}
else if (GetOwner().IsLoaded)
{
_logger.Trace($"[{_owner}][{peerId}] Player {player.Id} restored instantly");
player.IsLoaded = true;
BroadcastJoined(player);
_readyPlayers = _players.Where(p => p.Value.IsLoaded).Select(p => p.Key).ToArray();
_plugin.OnPlayerJoined(player);
BroadcastSnapshot(new[] {peerId});
foreach (var (key, value) in _entities)
value.RestoreBufferedEvents(peerId);
}
else
{
_logger.Trace($"[{_owner}][{peerId}] Player {player.Id} waiting");
_awaitingPeers.Add(peerId);
}
break;
}
case RagonOperation.REPLICATE_ENTITY_STATE:
{
var entitiesCount = reader.ReadUShort();
for (var entityIndex = 0; entityIndex < entitiesCount; entityIndex++)
{
var entityId = reader.ReadUShort();
if (_entities.TryGetValue(entityId, out var entity))
{
entity.ReadState(peerId, reader);
if (_entitiesDirtySet.Add(entity))
_entitiesDirty.Add(entity);
}
else
{
_logger.Error($"Entity with Id {entityId} not found, replication interrupted");
break;
}
}
break;
}
case RagonOperation.REPLICATE_ENTITY_EVENT:
{
var entityId = reader.ReadUShort();
if (!_entities.TryGetValue(entityId, out var ent))
{
_logger.Warn($"Entity not found for event with Id {entityId}");
return;
}
var player = _players[peerId];
ent.ProcessEvent(player, reader);
break;
}
case RagonOperation.CREATE_ENTITY:
{
var entityType = reader.ReadUShort();
var eventAuthority = (RagonAuthority) reader.ReadByte();
var propertiesCount = reader.ReadUShort();
_logger.Trace($"[{peerId}] Create Entity {entityType}");
var player = _players[peerId];
var entity = new Entity(this, player.PeerId, entityType, 0, eventAuthority);
for (var i = 0; i < propertiesCount; i++)
{
var propertyType = reader.ReadBool();
var propertySize = reader.ReadUShort();
entity.AddProperty(new EntityProperty(propertySize, propertyType));
}
var entityPayload = reader.ReadData(reader.Size);
entity.SetPayload(entityPayload.ToArray());
if (_plugin.OnEntityCreated(player, entity))
return;
player.AttachEntity(entity);
AttachEntity(player, entity);
entity.Create();
break;
}
case RagonOperation.DESTROY_ENTITY:
{
var entityId = reader.ReadInt();
if (_entities.TryGetValue(entityId, out var entity))
{
var player = _players[peerId];
var payload = reader.ReadData(reader.Size);
DetachEntity(player, entity, payload);
}
break;
}
}
}
public void Tick(float deltaTime)
{
_scheduler.Tick(deltaTime);
BroadcastState();
}
public void OnStart()
{
_plugin.OnStart();
}
public void OnStop()
{
foreach (var peerId in _allPlayers)
_application.SocketServer.Disconnect(peerId, 0);
_plugin.OnStop();
_plugin.Detach();
_scheduler.Dispose();
}
public void AttachEntity(Player player, Entity entity)
{
_entities.Add(entity.EntityId, entity);
_entitiesAll = _entities.Values.ToArray();
}
public void DetachEntity(Player player, Entity entity, ReadOnlySpan<byte> payload)
{
if (entity.Authority == RagonAuthority.OwnerOnly && entity.OwnerId != player.PeerId)
return;
if (_plugin.OnEntityDestroyed(player, entity))
return;
player.DetachEntity(entity);
entity.Destroy(payload);
_entities.Remove(entity.EntityId);
_entitiesAll = _entities.Values.ToArray();
}
void BroadcastNewOwner(Player prev, Player next)
{
var entitiesToUpdate = prev.Entities.Where(e => e.StaticId > 0).ToArray();
_writer.Clear();
_writer.WriteOperation(RagonOperation.OWNERSHIP_CHANGED);
_writer.WriteString(next.Id);
_writer.WriteUShort((ushort) entitiesToUpdate.Length);
foreach (var entity in entitiesToUpdate)
{
_writer.WriteUShort(entity.EntityId);
entity.SetOwner((ushort) next.PeerId);
}
BroadcastToReady(_writer, DeliveryType.Reliable);
}
void BroadcastJoined(Player player)
{
_writer.Clear();
_writer.WriteOperation(RagonOperation.PLAYER_JOINED);
_writer.WriteUShort(player.PeerId);
_writer.WriteString(player.Id);
_writer.WriteString(player.PlayerName);
BroadcastToReady(_writer, new [] { player.PeerId }, DeliveryType.Reliable);
}
void BroadcastLeaved(Player player)
{
_writer.Clear();
_writer.WriteOperation(RagonOperation.PLAYER_LEAVED);
_writer.WriteString(player.Id);
var entitiesToDelete = player.Entities.Where(e => e.StaticId == 0).ToArray();
_writer.WriteUShort((ushort) entitiesToDelete.Length);
foreach (var entity in entitiesToDelete)
{
_writer.WriteUShort(entity.EntityId);
_entities.Remove(entity.EntityId);
}
BroadcastToReady(_writer, DeliveryType.Reliable);
}
void BroadcastSnapshot(ushort[] peersIds)
{
_writer.Clear();
_writer.WriteOperation(RagonOperation.SNAPSHOT);
_writer.WriteUShort((ushort) _readyPlayers.Length);
foreach (var playerPeerId in _readyPlayers)
{
_writer.WriteUShort(playerPeerId);
_writer.WriteString(_players[playerPeerId].Id);
_writer.WriteString(_players[playerPeerId].PlayerName);
}
var dynamicEntities = _entitiesAll.Where(e => e.StaticId == 0).ToArray();
var dynamicEntitiesCount = (ushort) dynamicEntities.Length;
_writer.WriteUShort(dynamicEntitiesCount);
foreach (var entity in dynamicEntities)
{
ReadOnlySpan<byte> payload = entity.Payload.AsSpan();
_writer.WriteUShort(entity.EntityType);
_writer.WriteUShort(entity.EntityId);
_writer.WriteUShort(entity.OwnerId);
_writer.WriteUShort((ushort) payload.Length);
_writer.WriteData(ref payload);
entity.WriteSnapshot(_writer);
}
var staticEntities = _entitiesAll.Where(e => e.StaticId != 0).ToArray();
var staticEntitiesCount = (ushort) staticEntities.Length;
_writer.WriteUShort(staticEntitiesCount);
foreach (var entity in staticEntities)
{
ReadOnlySpan<byte> payload = entity.Payload.AsSpan();
_writer.WriteUShort(entity.EntityType);
_writer.WriteUShort(entity.EntityId);
_writer.WriteUShort(entity.StaticId);
_writer.WriteUShort(entity.OwnerId);
_writer.WriteUShort((ushort) payload.Length);
_writer.WriteData(ref payload);
entity.WriteSnapshot(_writer);
}
var sendData = _writer.ToArray();
Broadcast(peersIds, sendData, DeliveryType.Reliable);
}
void BroadcastState()
{
var entities = (ushort) _entitiesDirty.Count;
if (entities > 0)
{
_writer.Clear();
_writer.WriteOperation(RagonOperation.REPLICATE_ENTITY_STATE);
_writer.WriteUShort(entities);
foreach (var entity in _entitiesDirty)
entity.WriteProperties(_writer);
_entitiesDirty.Clear();
_entitiesDirtySet.Clear();
BroadcastToReady(_writer, DeliveryType.Reliable);
}
}
void AcceptPlayer(Player player)
{
_writer.Clear();
_writer.WriteOperation(RagonOperation.JOIN_SUCCESS);
_writer.WriteString(Id);
_writer.WriteString(player.Id);
_writer.WriteString(GetOwner().Id);
_writer.WriteUShort((ushort) PlayersMin);
_writer.WriteUShort((ushort) PlayersMax);
_writer.WriteString(Map);
Send(player.PeerId, _writer, DeliveryType.Reliable);
}
void BroadcastNewScene(string sceneName)
{
_readyPlayers = Array.Empty<ushort>();
_entitiesAll = Array.Empty<Entity>();
_entities.Clear();
_writer.Clear();
_writer.WriteOperation(RagonOperation.LOAD_SCENE);
_writer.WriteString(sceneName);
BroadcastToAll(_writer, DeliveryType.Reliable);
}
public void Send(ushort peerId, byte[] rawData, DeliveryType deliveryType = DeliveryType.Unreliable)
{
_socketServer.Send(peerId, rawData, deliveryType);
}
public void Send(ushort peerId, RagonSerializer writer, DeliveryType deliveryType = DeliveryType.Unreliable)
{
var sendData = writer.ToArray();
_socketServer.Send(peerId, sendData, deliveryType);
}
public void Broadcast(ushort[] peersIds, byte[] rawData, DeliveryType deliveryType = DeliveryType.Unreliable)
{
_socketServer.Broadcast(peersIds, rawData, deliveryType);
}
public void BroadcastToAll(byte[] rawData, DeliveryType deliveryType = DeliveryType.Unreliable)
{
_socketServer.Broadcast(_allPlayers, rawData, deliveryType);
}
public void BroadcastToAll(RagonSerializer writer, DeliveryType deliveryType = DeliveryType.Unreliable)
{
var sendData = writer.ToArray();
_socketServer.Broadcast(_allPlayers, sendData, deliveryType);
}
public void BroadcastToReady(byte[] rawData, DeliveryType deliveryType = DeliveryType.Unreliable)
{
_socketServer.Broadcast(_readyPlayers, rawData, deliveryType);
}
public void BroadcastToReady(RagonSerializer writer, DeliveryType deliveryType = DeliveryType.Unreliable)
{
var sendData = writer.ToArray();
_socketServer.Broadcast(_readyPlayers, sendData, deliveryType);
}
public void BroadcastToReady(byte[] rawData, ushort[] excludePeersIds, DeliveryType deliveryType = DeliveryType.Unreliable)
{
_peersCache.Clear();
foreach (var playerPeerId in _readyPlayers)
{
foreach (var excludePeersId in excludePeersIds)
{
if (playerPeerId != excludePeersId)
{
_peersCache.Add(playerPeerId);
}
}
}
var peersIds = _peersCache.ToArray();
_socketServer.Broadcast(peersIds, rawData, deliveryType);
}
public void BroadcastToReady(RagonSerializer writer, ushort[] excludePeersIds, DeliveryType deliveryType = DeliveryType.Unreliable)
{
var sendData = writer.ToArray();
BroadcastToReady(sendData, excludePeersIds, deliveryType);
}
}
}
@@ -3,7 +3,10 @@ using System.Threading.Tasks;
namespace Ragon.Core; namespace Ragon.Core;
public interface IAuthorizationProvider public interface IApplicationHandler
{ {
Task OnAuthorizationRequest(string key, string playerName, byte[] additionalData, Action<string, string> Accept, Action<uint> Reject); Task OnAuthorizationRequest(string key, string playerName, byte[] additionalData, Action<string, string> Accept, Action<uint> Reject);
public void OnCustomEvent(ushort peerId, ReadOnlySpan<byte> payload);
public void OnJoin(ushort peerId);
public void OnLeave(ushort peerId);
} }
@@ -5,38 +5,42 @@ using Ragon.Common;
namespace Ragon.Core; namespace Ragon.Core;
public class Lobby : ILobby public class Lobby
{ {
private readonly ILogger _logger = LogManager.GetCurrentClassLogger(); private readonly ILogger _logger = LogManager.GetCurrentClassLogger();
private readonly RagonSerializer _serializer; private readonly Application _application;
private readonly RagonSerializer _writer;
private readonly RoomManager _roomManager; private readonly RoomManager _roomManager;
private readonly AuthorizationManager _authorizationManager; private readonly AuthorizationManager _authorizationManager;
private readonly IGameThread _gameThread;
public AuthorizationManager AuthorizationManager => _authorizationManager; public AuthorizationManager AuthorizationManager => _authorizationManager;
public Lobby(IAuthorizationProvider provider, RoomManager manager, IGameThread gameThread) public Lobby(IApplicationHandler provider, RoomManager manager, Application application)
{ {
_roomManager = manager; _roomManager = manager;
_gameThread = gameThread; _application = application;
_serializer = new RagonSerializer(); _writer = new RagonSerializer();
_authorizationManager = new AuthorizationManager(provider, gameThread, this, _serializer); _authorizationManager = new AuthorizationManager(provider, application, this, _writer);
} }
public void ProcessEvent(ushort peerId, RagonOperation op, ReadOnlySpan<byte> payload) public void ProcessEvent(ushort peerId, RagonOperation op, RagonSerializer reader)
{ {
_serializer.Clear(); var player = _authorizationManager.GetPlayer(peerId);
_serializer.FromSpan(ref payload);
if (op == RagonOperation.AUTHORIZE) if (op == RagonOperation.AUTHORIZE)
{ {
var key = _serializer.ReadString(); if (player != null)
var playerName = _serializer.ReadString(); {
_authorizationManager.OnAuthorization(peerId, key, playerName); _logger.Warn("Player already authorized");
return;
}
var key = reader.ReadString();
var playerName = reader.ReadString();
var additionalData = reader.ReadData(reader.Size);
_authorizationManager.OnAuthorization(peerId, key, playerName, additionalData);
return; return;
} }
var player = _authorizationManager.GetPlayer(peerId);
if (player == null) if (player == null)
{ {
_logger.Warn($"Peer not authorized {peerId} trying to {op}"); _logger.Warn($"Peer not authorized {peerId} trying to {op}");
@@ -47,15 +51,15 @@ public class Lobby : ILobby
{ {
case RagonOperation.JOIN_ROOM: case RagonOperation.JOIN_ROOM:
{ {
var roomId = _serializer.ReadString(); var roomId = reader.ReadString();
var exists = _roomManager.Rooms.Any(r => r.Id == roomId); var exists = _roomManager.Rooms.Any(r => r.Id == roomId);
if (!exists) if (!exists)
{ {
_serializer.Clear(); _writer.Clear();
_serializer.WriteOperation(RagonOperation.JOIN_FAILED); _writer.WriteOperation(RagonOperation.JOIN_FAILED);
_serializer.WriteString($"Room with id {roomId} not exists"); _writer.WriteString($"Room with id {roomId} not exists");
var sendData = _serializer.ToArray(); var sendData = _writer.ToArray();
_gameThread.Server.Send(peerId, sendData, DeliveryType.Reliable); _application.SocketServer.Send(peerId, sendData, DeliveryType.Reliable);
return; return;
} }
@@ -68,25 +72,25 @@ public class Lobby : ILobby
case RagonOperation.CREATE_ROOM: case RagonOperation.CREATE_ROOM:
{ {
var roomId = Guid.NewGuid().ToString(); var roomId = Guid.NewGuid().ToString();
var custom = _serializer.ReadBool(); var custom = reader.ReadBool();
if (custom) if (custom)
{ {
roomId = _serializer.ReadString(); roomId = reader.ReadString();
var exists = _roomManager.Rooms.Any(r => r.Id == roomId); var exists = _roomManager.Rooms.Any(r => r.Id == roomId);
if (exists) if (exists)
{ {
_serializer.Clear(); _writer.Clear();
_serializer.WriteOperation(RagonOperation.JOIN_FAILED); _writer.WriteOperation(RagonOperation.JOIN_FAILED);
_serializer.WriteString($"Room with id {roomId} already exists"); _writer.WriteString($"Room with id {roomId} already exists");
var sendData = _serializer.ToArray(); var sendData = _writer.ToArray();
_gameThread.Server.Send(peerId, sendData, DeliveryType.Reliable); _application.SocketServer.Send(peerId, sendData, DeliveryType.Reliable);
return; return;
} }
} }
var roomProperties = new RagonRoomParameters(); var roomProperties = new RagonRoomParameters();
roomProperties.Deserialize(_serializer); roomProperties.Deserialize(reader);
if (_roomManager.RoomsBySocket.ContainsKey(peerId)) if (_roomManager.RoomsBySocket.ContainsKey(peerId))
_roomManager.Left(player, Array.Empty<byte>()); _roomManager.Left(player, Array.Empty<byte>());
@@ -98,7 +102,7 @@ public class Lobby : ILobby
{ {
var roomId = Guid.NewGuid().ToString(); var roomId = Guid.NewGuid().ToString();
var roomProperties = new RagonRoomParameters(); var roomProperties = new RagonRoomParameters();
roomProperties.Deserialize(_serializer); roomProperties.Deserialize(reader);
if (_roomManager.RoomsBySocket.ContainsKey(peerId)) if (_roomManager.RoomsBySocket.ContainsKey(peerId))
_roomManager.Left(player, Array.Empty<byte>()); _roomManager.Left(player, Array.Empty<byte>());
@@ -114,7 +118,7 @@ public class Lobby : ILobby
} }
} }
public void OnDisconnected(uint peerId) public void OnDisconnected(ushort peerId)
{ {
_authorizationManager.Cleanup(peerId); _authorizationManager.Cleanup(peerId);
} }
-6
View File
@@ -1,6 +0,0 @@
namespace Ragon.Core;
public interface ILobby
{
}
+28
View File
@@ -0,0 +1,28 @@
using System;
using System.Collections.Generic;
namespace Ragon.Core
{
public class Player
{
public string Id { get; set; }
public string PlayerName { get; set; }
public ushort PeerId { get; set; }
public bool IsLoaded { get; set; }
public List<Entity> Entities;
public List<ushort> EntitiesIds;
public void AttachEntity(Entity entity)
{
Entities.Add(entity);
EntitiesIds.Add((entity.EntityId));
}
public void DetachEntity(Entity entity)
{
Entities.Remove(entity);
EntitiesIds.Remove(entity.EntityId);
}
}
}
-16
View File
@@ -1,16 +0,0 @@
using System;
using System.Collections.Generic;
namespace Ragon.Core
{
public class Player
{
public string Id { get; set; }
public uint PeerId { get; set; }
public string PlayerName { get; set; }
public bool IsLoaded { get; set; }
public List<Entity> Entities;
public List<int> EntitiesIds;
}
}
+1 -3
View File
@@ -3,8 +3,6 @@ namespace Ragon.Core
public interface PluginFactory public interface PluginFactory
{ {
public PluginBase CreatePlugin(string map); public PluginBase CreatePlugin(string map);
public IAuthorizationProvider CreateAuthorizationProvider(Configuration configuration); public IApplicationHandler CreateAuthorizationProvider(Configuration configuration);
} }
} }
+16 -61
View File
@@ -8,21 +8,20 @@ namespace Ragon.Core
public class PluginBase public class PluginBase
{ {
private delegate void SubscribeDelegate(Player player, ref ReadOnlySpan<byte> data); private delegate void SubscribeDelegate(Player player, ref ReadOnlySpan<byte> data);
private delegate void SubscribeEntityDelegate(Player player, Entity entity, ref ReadOnlySpan<byte> data); private delegate void SubscribeEntityDelegate(Player player, Entity entity, ref ReadOnlySpan<byte> data);
private Dictionary<ushort, SubscribeDelegate> _globalEvents = new(); private Dictionary<ushort, SubscribeDelegate> _globalEvents = new();
private Dictionary<int, Dictionary<ushort, SubscribeEntityDelegate>> _entityEvents = new(); private Dictionary<int, Dictionary<ushort, SubscribeEntityDelegate>> _entityEvents = new();
private readonly RagonSerializer _serializer = new(); private readonly RagonSerializer _serializer = new();
protected IGameRoom GameRoom { get; private set; } = null!; protected GameRoom Room { get; private set; } = null!;
protected ILogger Logger = null!; protected ILogger Logger = null!;
public void Attach(GameRoom gameRoom) public void Attach(GameRoom gameRoom)
{ {
Logger = LogManager.GetLogger($"Plugin<{GetType().Name}>"); Logger = LogManager.GetLogger($"Plugin<{GetType().Name}>");
GameRoom = gameRoom; Room = gameRoom;
_globalEvents.Clear(); _globalEvents.Clear();
_entityEvents.Clear(); _entityEvents.Clear();
@@ -34,7 +33,7 @@ namespace Ragon.Core
_entityEvents.Clear(); _entityEvents.Clear();
} }
public void Subscribe<T>(ushort evntCode, Action<Player, T> action) where T : IRagonSerializable, new() public void OnEvent<T>(ushort evntCode, Action<Player, T> action) where T : IRagonSerializable, new()
{ {
if (_globalEvents.ContainsKey(evntCode)) if (_globalEvents.ContainsKey(evntCode))
{ {
@@ -58,7 +57,7 @@ namespace Ragon.Core
}); });
} }
public void Subscribe(ushort evntCode, Action<Player> action) public void OnEvent(ushort evntCode, Action<Player> action)
{ {
if (_globalEvents.ContainsKey(evntCode)) if (_globalEvents.ContainsKey(evntCode))
{ {
@@ -69,7 +68,7 @@ namespace Ragon.Core
_globalEvents.Add(evntCode, (Player player, ref ReadOnlySpan<byte> raw) => { action.Invoke(player); }); _globalEvents.Add(evntCode, (Player player, ref ReadOnlySpan<byte> raw) => { action.Invoke(player); });
} }
public void Subscribe<T>(Entity entity, ushort evntCode, Action<Player, Entity, T> action) where T : IRagonSerializable, new() public void OnEvent<T>(Entity entity, ushort evntCode, Action<Player, Entity, T> action) where T : IRagonSerializable, new()
{ {
if (_entityEvents.ContainsKey(entity.EntityId)) if (_entityEvents.ContainsKey(entity.EntityId))
{ {
@@ -116,7 +115,7 @@ namespace Ragon.Core
} }
} }
public void Subscribe(Entity entity, ushort evntCode, Action<Player, Entity> action) public void OnEvent(Entity entity, ushort evntCode, Action<Player, Entity> action)
{ {
if (_entityEvents.ContainsKey(entity.EntityId)) if (_entityEvents.ContainsKey(entity.EntityId))
{ {
@@ -150,9 +149,10 @@ namespace Ragon.Core
if (!_entityEvents[entityId].ContainsKey(evntCode)) if (!_entityEvents[entityId].ContainsKey(evntCode))
return false; return false;
var player = GameRoom.GetPlayerById(peerId); // var player = Room.GetPlayerById(peerId);
var entity = GameRoom.GetEntityById(entityId); var entity = Room.GetEntityById(entityId);
_entityEvents[entityId][evntCode].Invoke(player, entity, ref payload);
// _entityEvents[entityId][evntCode].Invoke(player, entity, ref payload);
return true; return true;
} }
@@ -161,61 +161,14 @@ namespace Ragon.Core
{ {
if (_globalEvents.ContainsKey(evntCode)) if (_globalEvents.ContainsKey(evntCode))
{ {
var player = GameRoom.GetPlayerById(peerId); // var player = Room.GetPlayerById(peerId);
_globalEvents[evntCode].Invoke(player, ref payload); // _globalEvents[evntCode].Invoke(player, ref payload);
return true; return true;
} }
return false; return false;
} }
public void SendEvent(Player player, uint eventCode, IRagonSerializable payload)
{
_serializer.Clear();
_serializer.WriteOperation(RagonOperation.REPLICATE_EVENT);
payload.Serialize(_serializer);
var sendData = _serializer.ToArray();
GameRoom.Send(player.PeerId, sendData);
}
public void BroadcastEvent(ushort eventCode, IRagonSerializable payload)
{
_serializer.Clear();
_serializer.WriteOperation(RagonOperation.REPLICATE_EVENT);
payload.Serialize(_serializer);
var sendData = _serializer.ToArray();
GameRoom.Broadcast(sendData, DeliveryType.Reliable);
}
public void SendEntityEvent(Player player, Entity entity, IRagonSerializable payload)
{
_serializer.Clear();
_serializer.WriteOperation(RagonOperation.REPLICATE_ENTITY_EVENT);
_serializer.WriteInt(entity.EntityId);
payload.Serialize(_serializer);
var sendData = _serializer.ToArray();
GameRoom.Send(player.PeerId, sendData, DeliveryType.Reliable);
}
public void BroadcastEntityEvent(Entity entity, IRagonSerializable payload)
{
_serializer.Clear();
_serializer.WriteOperation(RagonOperation.REPLICATE_ENTITY_EVENT);
_serializer.WriteInt(entity.EntityId);
payload.Serialize(_serializer);
var sendData = _serializer.ToArray();
GameRoom.Broadcast(sendData);
}
#region VIRTUAL #region VIRTUAL
public virtual void OnPlayerJoined(Player player) public virtual void OnPlayerJoined(Player player)
@@ -230,12 +183,14 @@ namespace Ragon.Core
{ {
} }
public virtual void OnEntityCreated(Player creator, Entity entity) public virtual bool OnEntityCreated(Player player, Entity entity)
{ {
return false;
} }
public virtual void OnEntityDestroyed(Player destoyer, Entity entity) public virtual bool OnEntityDestroyed(Player player, Entity entity)
{ {
return false;
} }
public virtual void OnStart() public virtual void OnStart()
@@ -8,7 +8,7 @@ namespace Ragon.Core;
public class RoomManager public class RoomManager
{ {
private readonly IGameThread _gameThread; private readonly Application _gameThread;
private readonly PluginFactory _factory; private readonly PluginFactory _factory;
private readonly Logger _logger = LogManager.GetCurrentClassLogger(); private readonly Logger _logger = LogManager.GetCurrentClassLogger();
private readonly List<GameRoom> _rooms = new(); private readonly List<GameRoom> _rooms = new();
@@ -17,7 +17,7 @@ public class RoomManager
public IReadOnlyDictionary<uint, GameRoom> RoomsBySocket => _roomsBySocket; public IReadOnlyDictionary<uint, GameRoom> RoomsBySocket => _roomsBySocket;
public IReadOnlyList<GameRoom> Rooms => _rooms; public IReadOnlyList<GameRoom> Rooms => _rooms;
public RoomManager(PluginFactory factory, IGameThread gameThread) public RoomManager(PluginFactory factory, Application gameThread)
{ {
_gameThread = gameThread; _gameThread = gameThread;
_factory = factory; _factory = factory;
@@ -34,7 +34,7 @@ public class RoomManager
{ {
if (existRoom.Id == roomId && existRoom.PlayersCount < existRoom.PlayersMax) if (existRoom.Id == roomId && existRoom.PlayersCount < existRoom.PlayersMax)
{ {
existRoom.Joined(player, payload); existRoom.AddPlayer(player, payload);
_roomsBySocket.Add(player.PeerId, existRoom); _roomsBySocket.Add(player.PeerId, existRoom);
return; return;
} }
@@ -55,8 +55,8 @@ public class RoomManager
throw new NullReferenceException($"Plugin for map {map} is null"); throw new NullReferenceException($"Plugin for map {map} is null");
var room = new GameRoom(_gameThread, plugin, roomId, map, min, max); var room = new GameRoom(_gameThread, plugin, roomId, map, min, max);
room.Joined(creator, payload); room.AddPlayer(creator, payload);
room.Start(); room.OnStart();
_roomsBySocket.Add(creator.PeerId, room); _roomsBySocket.Add(creator.PeerId, room);
_rooms.Add(room); _rooms.Add(room);
@@ -76,7 +76,7 @@ public class RoomManager
{ {
_logger.Trace($"Player ({player.PlayerName}|{player.Id}) joined to room with Id {roomId}"); _logger.Trace($"Player ({player.PlayerName}|{player.Id}) joined to room with Id {roomId}");
existRoom.Joined(player, payload); existRoom.AddPlayer(player, payload);
_roomsBySocket.Add(player.PeerId, existRoom); _roomsBySocket.Add(player.PeerId, existRoom);
return; return;
} }
@@ -90,8 +90,8 @@ public class RoomManager
throw new NullReferenceException($"Plugin for map {map} is null"); throw new NullReferenceException($"Plugin for map {map} is null");
var room = new GameRoom(_gameThread, plugin, roomId, map, min, max); var room = new GameRoom(_gameThread, plugin, roomId, map, min, max);
room.Joined(player, payload); room.AddPlayer(player, payload);
room.Start(); room.OnStart();
_roomsBySocket.Add(player.PeerId, room); _roomsBySocket.Add(player.PeerId, room);
_rooms.Add(room); _rooms.Add(room);
@@ -102,15 +102,15 @@ public class RoomManager
if (_roomsBySocket.Remove(player.PeerId, out var room)) if (_roomsBySocket.Remove(player.PeerId, out var room))
{ {
_logger.Trace($"Player ({player.PlayerName}|{player.Id}) left room with Id {room.Id}"); _logger.Trace($"Player ({player.PlayerName}|{player.Id}) left room with Id {room.Id}");
room.Leave(player.PeerId); room.RemovePlayer(player.PeerId);
if (room.PlayersCount < room.PlayersMin) if (room.PlayersCount < room.PlayersMin)
{ {
_logger.Trace($"Room with Id {room.Id} destroyed"); _logger.Trace($"Room with Id {room.Id} destroyed");
room.Stop(); room.OnStop();
_rooms.Remove(room); _rooms.Remove(room);
} }
_gameThread.Server.Send(player.PeerId, new byte[] {(byte) RagonOperation.LEAVE_ROOM}, DeliveryType.Reliable); _gameThread.SocketServer.Send(player.PeerId, new byte[] {(byte) RagonOperation.LEAVE_ROOM}, DeliveryType.Reliable);
} }
} }
@@ -1,10 +1,10 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel;
namespace Ragon.Core namespace Ragon.Core
{ {
public class Scheduler: IScheduler public class Scheduler: IDisposable
{ {
List<SchedulerTask> _scheduledTasks; List<SchedulerTask> _scheduledTasks;
+16 -11
View File
@@ -14,12 +14,12 @@ namespace Ragon.Core
private Address _address; private Address _address;
private Event _netEvent; private Event _netEvent;
private Peer[] _peers; private Peer[] _peers;
private IHandler _handler; private IEventHandler _eventHandler;
private Stopwatch _timer; private Stopwatch _timer;
public ENetServer(IHandler handler) public ENetServer(IEventHandler eventHandler)
{ {
_handler = handler; _eventHandler = eventHandler;
_timer = Stopwatch.StartNew(); _timer = Stopwatch.StartNew();
_peers = Array.Empty<Peer>(); _peers = Array.Empty<Peer>();
_host = new Host(); _host = new Host();
@@ -39,7 +39,7 @@ namespace Ragon.Core
_logger.Info($"Protocol: {protocolDecoded}"); _logger.Info($"Protocol: {protocolDecoded}");
} }
public void Broadcast(uint[] peersIds, byte[] data, DeliveryType type) public void Broadcast(ushort[] peersIds, byte[] data, DeliveryType type)
{ {
var newPacket = new Packet(); var newPacket = new Packet();
var packetFlags = PacketFlags.Instant; var packetFlags = PacketFlags.Instant;
@@ -61,7 +61,7 @@ namespace Ragon.Core
_peers[peerId].Send(channel, ref newPacket); _peers[peerId].Send(channel, ref newPacket);
} }
public void Send(uint peerId, byte[] data, DeliveryType type) public void Send(ushort peerId, byte[] data, DeliveryType type)
{ {
var newPacket = new Packet(); var newPacket = new Packet();
var packetFlags = PacketFlags.Instant; var packetFlags = PacketFlags.Instant;
@@ -82,7 +82,7 @@ namespace Ragon.Core
_peers[peerId].Send(channel, ref newPacket); _peers[peerId].Send(channel, ref newPacket);
} }
public void Disconnect(uint peerId, uint errorCode) public void Disconnect(ushort peerId, uint errorCode)
{ {
_peers[peerId].Reset(); _peers[peerId].Reset();
} }
@@ -94,7 +94,7 @@ namespace Ragon.Core
{ {
if (_host.CheckEvents(out _netEvent) <= 0) if (_host.CheckEvents(out _netEvent) <= 0)
{ {
if (_host.Service(15, out _netEvent) <= 0) if (_host.Service(0, out _netEvent) <= 0)
break; break;
polled = true; polled = true;
@@ -115,23 +115,28 @@ namespace Ragon.Core
// break; // break;
// } // }
_peers[_netEvent.Peer.ID] = _netEvent.Peer; _peers[_netEvent.Peer.ID] = _netEvent.Peer;
_handler.OnEvent(_netEvent); _eventHandler.OnConnected((ushort)_netEvent.Peer.ID);
break; break;
} }
case EventType.Disconnect: case EventType.Disconnect:
{ {
_handler.OnEvent(_netEvent); _eventHandler.OnDisconnected((ushort)_netEvent.Peer.ID);
break; break;
} }
case EventType.Timeout: case EventType.Timeout:
{ {
_handler.OnEvent(_netEvent); _eventHandler.OnTimeout((ushort)_netEvent.Peer.ID);
break; break;
} }
case EventType.Receive: case EventType.Receive:
{ {
_handler.OnEvent(_netEvent); var peerId = (ushort) _netEvent.Peer.ID;
var dataRaw = new byte[_netEvent.Packet.Length];
_netEvent.Packet.CopyTo(dataRaw);
_netEvent.Packet.Dispose(); _netEvent.Packet.Dispose();
_eventHandler.OnData(peerId, dataRaw);
break; break;
} }
} }
@@ -0,0 +1,7 @@
namespace Ragon.Core;
public class WebSocketPacket
{
public ushort PeerId;
public byte[] Data;
}
@@ -0,0 +1,123 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Net;
using System.Net.WebSockets;
using System.Threading;
using System.Threading.Tasks;
using NLog;
using Ragon.Common;
namespace Ragon.Core;
public class WebSocketServer : ISocketServer
{
private ushort _idSequencer = 0;
private ILogger _logger = LogManager.GetCurrentClassLogger();
private Dictionary<ushort, WebSocket> _webSockets = new Dictionary<ushort, WebSocket>();
private Queue<WebSocketPacket> _events;
private IEventHandler _eventHandler;
private WebSocketTaskScheduler _webSocketScheduler;
private TaskFactory _taskFactory;
private HttpListener _httpListener;
public WebSocketServer(IEventHandler eventHandler)
{
_eventHandler = eventHandler;
_events = new Queue<WebSocketPacket>(1024);
_webSocketScheduler = new WebSocketTaskScheduler();
_taskFactory = new TaskFactory(_webSocketScheduler);
}
async void StartAccept()
{
while (true)
{
var context = await _httpListener.GetContextAsync();
if (!context.Request.IsWebSocketRequest) continue;
var webSocketContext = await context.AcceptWebSocketAsync(null);
var webSocket = webSocketContext.WebSocket;
_idSequencer++;
_webSockets.Add(_idSequencer, webSocket);
_ = _taskFactory.StartNew(() => StartListen(webSocket, _idSequencer));
}
}
async void StartListen(WebSocket webSocket, ushort peerId)
{
_eventHandler.OnConnected(peerId);
var bytes = new byte[2048];
var buffer = new Memory<byte>(bytes);
while (webSocket.State == WebSocketState.Open)
{
try
{
var result = await webSocket.ReceiveAsync(buffer, CancellationToken.None);
var dataRaw = buffer.Slice(0, result.Count);
_eventHandler.OnData(peerId, dataRaw.ToArray());
}
catch (Exception ex)
{
break;
}
}
_eventHandler.OnDisconnected(peerId);
}
async void ProcessQueue()
{
while (_events.TryDequeue(out var evnt))
{
if (_webSockets.TryGetValue(evnt.PeerId, out var ws) && ws.State == WebSocketState.Open)
{
await ws.SendAsync(evnt.Data, WebSocketMessageType.Binary, WebSocketMessageFlags.EndOfMessage, CancellationToken.None);
}
}
}
public void Start(ushort port, int connections, uint protocol)
{
_httpListener = new HttpListener();
_httpListener.Prefixes.Add($"http://*:{port}/");
_httpListener.Start();
_taskFactory.StartNew(StartAccept);
var protocolDecoded = (protocol >> 16 & 0xFF) + "." + (protocol >> 8 & 0xFF) + "." + (protocol & 0xFF);
_logger.Info($"Network listening on http://*:{port}/");
_logger.Info($"Protocol: {protocolDecoded}");
}
public void Process()
{
_webSocketScheduler.Process();
ProcessQueue();
}
public void Stop()
{
_httpListener.Stop();
}
public void Send(ushort peerId, byte[] data, DeliveryType type)
{
_events.Enqueue(new WebSocketPacket() {PeerId = peerId, Data = data});
}
public void Broadcast(ushort[] peersIds, byte[] data, DeliveryType type)
{
foreach (var peerId in peersIds)
_events.Enqueue(new WebSocketPacket() {PeerId = peerId, Data = data});
}
public void Disconnect(ushort peerId, uint errorCode)
{
_webSockets[peerId].CloseAsync(WebSocketCloseStatus.NormalClosure, null, CancellationToken.None);
}
}
@@ -0,0 +1,51 @@
using System;
using System.Collections.Generic;
using System.Threading.Channels;
using System.Threading.Tasks;
using NLog.LayoutRenderers.Wrappers;
namespace Ragon.Core;
public class WebSocketTaskScheduler: TaskScheduler
{
private ChannelReader<Task> _reader;
private ChannelWriter<Task> _writer;
private Queue<Task> _pendingTasks;
public WebSocketTaskScheduler()
{
var channel = Channel.CreateUnbounded<Task>();
_pendingTasks = new Queue<Task>();
_reader = channel.Reader;
_writer = channel.Writer;
}
protected override IEnumerable<Task>? GetScheduledTasks()
{
throw new NotSupportedException();
}
protected override void QueueTask(Task task)
{
_writer.TryWrite(task);
}
protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued)
{
return false;
}
public void Process()
{
while (_reader.TryRead(out var task))
{
TryExecuteTask(task);
if (task.Status == TaskStatus.Running)
_pendingTasks.Enqueue(task);
}
while (_pendingTasks.TryDequeue(out var task))
_writer.TryWrite(task);
}
}
+11
View File
@@ -0,0 +1,11 @@
using ENet;
namespace Ragon.Core;
public interface IEventHandler
{
public void OnConnected(ushort peerId);
public void OnDisconnected(ushort peerId);
public void OnTimeout(ushort peerId);
public void OnData(ushort peerId, byte[] data);
}
-8
View File
@@ -1,8 +0,0 @@
using ENet;
namespace Ragon.Core;
public interface IHandler
{
public void OnEvent(Event evnt);
}
+3 -3
View File
@@ -5,7 +5,7 @@ public interface ISocketServer
public void Start(ushort port, int connections, uint protocol); public void Start(ushort port, int connections, uint protocol);
public void Process(); public void Process();
public void Stop(); public void Stop();
public void Send(uint peerId, byte[] data, DeliveryType type); public void Send(ushort peerId, byte[] data, DeliveryType type);
public void Broadcast(uint[] peersIds, byte[] data, DeliveryType type); public void Broadcast(ushort[] peersIds, byte[] data, DeliveryType type);
public void Disconnect(uint peerId, uint errorCode); public void Disconnect(ushort peerId, uint errorCode);
} }
-8
View File
@@ -1,8 +0,0 @@
using System;
namespace Ragon.Core;
public interface IDispatcher
{
public void Dispatch(Action action);
}
@@ -1,6 +0,0 @@
namespace Ragon.Core;
public interface IDispatcherInternal
{
public void Process();
}
-12
View File
@@ -1,12 +0,0 @@
using System;
namespace Ragon.Core;
public interface IScheduler
{
public SchedulerTask Schedule(Action<SchedulerTask> action, float interval, int count = 1);
public SchedulerTask ScheduleForever(Action<SchedulerTask> action, float interval);
public void StopSchedule(SchedulerTask schedulerTask);
public void Tick(float deltaTime);
}
+2 -2
View File
@@ -1,7 +1,7 @@
MIT License
Copyright (c) 2022 Eduard Kargin (theedison4@gmail.com) Copyright (c) 2022 Eduard Kargin (theedison4@gmail.com)
MIT License:
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights in the Software without restriction, including without limitation the rights