From 6d60df5c0184f2e63d9f673e358509714d345fb3 Mon Sep 17 00:00:00 2001 From: Edmand46 Date: Sun, 8 May 2022 00:40:11 +0400 Subject: [PATCH] wip --- Game/Game.csproj | 6 +- Game/Source/Events/TestEvent.cs | 19 + Game/Source/GameFactory.cs | 2 + Game/Source/Modes/ExamplePlugin.cs | 45 ++- Game/config.json | 22 +- .../{IPacket.cs => RagonSerializable.cs} | 2 +- Ragon.Common/Ragon.Common.csproj | 4 +- Ragon/Sources/Application.cs | 7 +- .../Configuration/ConfigurationLoader.cs | 2 +- Ragon/Sources/ENetServer.cs | 13 +- Ragon/Sources/Entity/Entity.cs | 4 +- Ragon/Sources/Event/Event.cs | 2 +- Ragon/Sources/Plugin/PluginBase.cs | 329 +++++++++++------- Ragon/Sources/Plugin/PluginFactory.cs | 1 - Ragon/Sources/Rooms/Room.cs | 175 ++++++---- Ragon/Sources/Rooms/RoomManager.cs | 17 +- Ragon/Sources/Rooms/RoomThread.cs | 20 +- Ragon/Sources/Rooms/RoomThreadInfo.cs | 9 - readme.md | 48 ++- 19 files changed, 462 insertions(+), 265 deletions(-) create mode 100644 Game/Source/Events/TestEvent.cs rename Ragon.Common/Protocol/{IPacket.cs => RagonSerializable.cs} (80%) delete mode 100644 Ragon/Sources/Rooms/RoomThreadInfo.cs diff --git a/Game/Game.csproj b/Game/Game.csproj index 3984c7e..daa608d 100755 --- a/Game/Game.csproj +++ b/Game/Game.csproj @@ -6,10 +6,6 @@ Game - - - - Always @@ -20,7 +16,7 @@ - + diff --git a/Game/Source/Events/TestEvent.cs b/Game/Source/Events/TestEvent.cs new file mode 100644 index 0000000..1c8040d --- /dev/null +++ b/Game/Source/Events/TestEvent.cs @@ -0,0 +1,19 @@ +using NetStack.Serialization; +using Ragon.Common; + +namespace Game.Source.Events; + +public class TestEvent: IRagonSerializable +{ + public string TestData; + + public void Serialize(BitBuffer buffer) + { + buffer.AddString(TestData); + } + + public void Deserialize(BitBuffer buffer) + { + TestData = buffer.ReadString(); + } +} \ No newline at end of file diff --git a/Game/Source/GameFactory.cs b/Game/Source/GameFactory.cs index 258aef5..02d4111 100644 --- a/Game/Source/GameFactory.cs +++ b/Game/Source/GameFactory.cs @@ -1,3 +1,4 @@ +using System.Runtime.InteropServices; using Ragon.Core; namespace Game.Source @@ -7,6 +8,7 @@ namespace Game.Source public string PluginName { get; set; } = "ExamplePlugin"; public PluginBase CreatePlugin(string map) { + return new ExamplePlugin(); } diff --git a/Game/Source/Modes/ExamplePlugin.cs b/Game/Source/Modes/ExamplePlugin.cs index cb75baa..84e983c 100755 --- a/Game/Source/Modes/ExamplePlugin.cs +++ b/Game/Source/Modes/ExamplePlugin.cs @@ -1,20 +1,59 @@ -using NLog; +using System.Runtime.InteropServices; +using Game.Source.Events; +using NLog; using Ragon.Core; namespace Game.Source { public class ExamplePlugin: PluginBase { - private ILogger _logger = LogManager.GetCurrentClassLogger(); - public override void OnStart() { _logger.Info("Plugin started"); + + Subscribe(123, OnTestEvent); } public override void OnStop() { _logger.Info("Plugin stopped"); } + + private void OnTestEvent(Player player, TestEvent myEvent) + { + _logger.Info("Data " + myEvent.TestData); + } + + public override void OnPlayerJoined(Player player) + { + _logger.Info("Player joined " + player.PlayerName); + SendEvent(player, 123, new TestEvent() { TestData = "asdf"}); + + SendEvent(123, new TestEvent() + { + TestData = "Hello!", + }); + } + + public override void OnPlayerLeaved(Player player) + { + _logger.Info("Player leaved " + player.PlayerName); + } + + public override void OnEntityCreated(Player creator, Entity entity) + { + // entity. + Subscribe(entity, 123, OnEntityTestEvent); + } + + public override void OnEntityDestroyed(Player destoyer, Entity entity) + { + + } + + private void OnEntityTestEvent(Player arg1, int arg2, TestEvent arg3) + { + + } } } \ No newline at end of file diff --git a/Game/config.json b/Game/config.json index 79e2003..81eae9f 100755 --- a/Game/config.json +++ b/Game/config.json @@ -3,25 +3,5 @@ "server": { "port": 5000, "skipTimeout": 60 - }, - "blacklist": [ - "пидор", - "сука", - "хуидор", - "хуй", - "Hitler", - "Гитлер", - "Гей", - "Админ", - "admin", - "падла", - "уебок", - "собака", - "пидорас", - "мразь", - "ебасос", - "еблан", - "ебучий", - "дрочь" - ] + } } \ No newline at end of file diff --git a/Ragon.Common/Protocol/IPacket.cs b/Ragon.Common/Protocol/RagonSerializable.cs similarity index 80% rename from Ragon.Common/Protocol/IPacket.cs rename to Ragon.Common/Protocol/RagonSerializable.cs index a3be150..48c0e33 100644 --- a/Ragon.Common/Protocol/IPacket.cs +++ b/Ragon.Common/Protocol/RagonSerializable.cs @@ -2,7 +2,7 @@ using NetStack.Serialization; namespace Ragon.Common { - public interface IPacket + public interface IRagonSerializable { public void Serialize(BitBuffer buffer); public void Deserialize(BitBuffer buffer); diff --git a/Ragon.Common/Ragon.Common.csproj b/Ragon.Common/Ragon.Common.csproj index b25b27c..17c2955 100644 --- a/Ragon.Common/Ragon.Common.csproj +++ b/Ragon.Common/Ragon.Common.csproj @@ -8,14 +8,14 @@ - /Users/edmand46/UnityProjects/Ragon-Unity-SDK/Assets/RagonSDK/netstandard2.1/ + /Users/edmand46/UnityProjects/Ragon-Unity-SDK/Assets/RagonSDK/Plugins/ false true TRACE;NETSTACK_SPAN - /Users/edmand46/UnityProjects/Ragon-Unity-SDK/Assets/RagonSDK/netstandard2.1/ + /Users/edmand46/UnityProjects/Ragon-Unity-SDK/Assets/RagonSDK/Plugins/ TRACE;NETSTACK_SPAN diff --git a/Ragon/Sources/Application.cs b/Ragon/Sources/Application.cs index f8b5405..5ca7c86 100755 --- a/Ragon/Sources/Application.cs +++ b/Ragon/Sources/Application.cs @@ -14,6 +14,7 @@ namespace Ragon.Core private readonly Dictionary _roomThreadCounter = new(); private readonly Configuration _configuration; private readonly ENetServer _socketServer; + private int _roomThreadBalancer = 0; public Application(PluginFactory factory, Configuration configuration, int threadsCount) { @@ -45,9 +46,13 @@ namespace Ragon.Core { if (evnt.Type == EventType.CONNECTED) { - var roomThread = _roomThreads.First(); + if (_roomThreadBalancer >= _roomThreads.Count) + _roomThreadBalancer = 0; + + var roomThread = _roomThreads[_roomThreadBalancer]; _roomThreadCounter[roomThread] += 1; _socketByRoomThreads.Add(evnt.PeerId, roomThread); + _roomThreadBalancer++; } if (_socketByRoomThreads.TryGetValue(evnt.PeerId, out var existsRoomThread)) diff --git a/Ragon/Sources/Configuration/ConfigurationLoader.cs b/Ragon/Sources/Configuration/ConfigurationLoader.cs index eb3589d..85beb10 100755 --- a/Ragon/Sources/Configuration/ConfigurationLoader.cs +++ b/Ragon/Sources/Configuration/ConfigurationLoader.cs @@ -21,7 +21,7 @@ namespace Ragon.Core _logger.Info("=================================="); _logger.Info("= ="); - _logger.Info($"={"Yohoho Server".PadBoth(32)}="); + _logger.Info($"={"Ragon".PadBoth(32)}="); _logger.Info("= ="); _logger.Info("=================================="); } diff --git a/Ragon/Sources/ENetServer.cs b/Ragon/Sources/ENetServer.cs index 560928e..097cd09 100755 --- a/Ragon/Sources/ENetServer.cs +++ b/Ragon/Sources/ENetServer.cs @@ -28,8 +28,10 @@ namespace Ragon.Core private Address _address; private ENet.Event _netEvent; private Peer[] _peers; + private RingBuffer _receiveBuffer; private RingBuffer _sendBuffer; + public void WriteEvent(Event evnt) => _sendBuffer.Enqueue(evnt); public bool ReadEvent(out Event evnt) => _receiveBuffer.TryDequeue(out evnt); @@ -48,7 +50,7 @@ namespace Ragon.Core _receiveBuffer = new RingBuffer(8192 + 8192); Status = Status.Listening; - + _thread = new Thread(Execute); _thread.Name = "NetworkThread"; _thread.Start(); @@ -77,7 +79,7 @@ namespace Ragon.Core channel = 1; packetFlags = PacketFlags.Instant; } - + newPacket.Create(data.Data, data.Data.Length, packetFlags); _peers[data.PeerId].Send(channel, ref newPacket); } @@ -127,11 +129,12 @@ namespace Ragon.Core case ENet.EventType.Receive: { var data = new byte[_netEvent.Packet.Length]; - + _netEvent.Packet.CopyTo(data); _netEvent.Packet.Dispose(); - - var @event = new Event {PeerId = _netEvent.Peer.ID, Type = EventType.DATA, Data = data}; + + var @event = new Event {PeerId = _netEvent.Peer.ID, Type = EventType.DATA, Data = data }; + _receiveBuffer.Enqueue(@event); break; } diff --git a/Ragon/Sources/Entity/Entity.cs b/Ragon/Sources/Entity/Entity.cs index f5a88ca..8d19bb1 100644 --- a/Ragon/Sources/Entity/Entity.cs +++ b/Ragon/Sources/Entity/Entity.cs @@ -7,12 +7,14 @@ public class Entity private static int _idGenerator = 0; public int EntityId { get; private set; } public uint OwnerId { get; private set; } + public ushort EntityType { get; private set; } public byte[] State { get; set; } public Dictionary Properties { get; set; } - public Entity(uint ownerId) + public Entity(uint ownerId, ushort entityType) { OwnerId = ownerId; + EntityType = entityType; EntityId = _idGenerator++; } } \ No newline at end of file diff --git a/Ragon/Sources/Event/Event.cs b/Ragon/Sources/Event/Event.cs index ec7bdda..d275c7e 100644 --- a/Ragon/Sources/Event/Event.cs +++ b/Ragon/Sources/Event/Event.cs @@ -6,7 +6,7 @@ namespace Ragon.Core { public EventType Type; public DeliveryType Delivery; - public uint PeerId; public byte[] Data; + public uint PeerId; } } \ No newline at end of file diff --git a/Ragon/Sources/Plugin/PluginBase.cs b/Ragon/Sources/Plugin/PluginBase.cs index 99ef977..e736fcb 100755 --- a/Ragon/Sources/Plugin/PluginBase.cs +++ b/Ragon/Sources/Plugin/PluginBase.cs @@ -1,134 +1,213 @@ - -using System; +using System; using System.Collections.Generic; +using System.Runtime.CompilerServices; using NetStack.Serialization; +using NLog; using Ragon.Common; namespace Ragon.Core { - public class PluginBase + public class PluginBase: IDisposable + { + private delegate void SubscribeDelegate(Player player, ref ReadOnlySpan data); + private delegate void SubscribeEntityDelegate(Player player, Entity entity, ref ReadOnlySpan data); + + private Dictionary _globalEvents = new(); + private Dictionary> _entityEvents = new(); + private BitBuffer _buffer = new BitBuffer(8192); + + protected Room Room { get; private set; } + protected ILogger _logger; + + public void Attach(Room room) { - private delegate void SubscribeDelegate(Player player, ref ReadOnlySpan data); - private Dictionary _subscribes = new(); - private BitBuffer _buffer = new BitBuffer(1024); - - protected Room Room { get; private set; } - - public void Attach(Room room) => Room = room; - public void Detach() => _subscribes.Clear(); - - public void Subscribe(ushort evntCode, Action action) where T: IPacket, new() - { - var data = new T(); - _subscribes.Add(evntCode, (Player player, ref ReadOnlySpan raw) => - { - _buffer.Clear(); - _buffer.FromSpan(ref raw, raw.Length); - data.Deserialize(_buffer); - action.Invoke(player, data); - }); - } - - // public void Subscribe(RagonOperation operation, Action action) where T: IPacket, new() - // { - // var data = new T(); - // _subscribes.Add(evntCode, (Player player, ref ReadOnlySpan raw) => - // { - // _buffer.Clear(); - // _buffer.FromSpan(ref raw, raw.Length); - // data.Deserialize(_buffer); - // action.Invoke(player, data); - // }); - // } - public void UnsubscribeAll() - { - _subscribes.Clear(); - } - - - public bool InternalHandle(uint peerId, ushort evntCode, ref ReadOnlySpan payload) - { - if (_subscribes.ContainsKey(evntCode)) - { - var player = Room.GetPlayerByPeerId(peerId); - _subscribes[evntCode].Invoke(player, ref payload); - return true; - } - - return false; - } - - public void Send(Player player, RagonOperation operation, IPacket payload) - { - Send(player.PeerId, operation, payload); - } - - public void Broadcast(Player[] players, RagonOperation operation, IPacket payload) - { - var ids = new uint[players.Length]; - for (int i = 0; i < players.Length; i++) - ids[i] = players[i].PeerId; - - Broadcast(ids, operation, payload); - } - - public void Send(uint peerId, RagonOperation operation, IPacket payload) - { - _buffer.Clear(); - - payload.Serialize(_buffer); - - Span data = stackalloc byte[_buffer.Length + 2]; - Span bufferSpan = data.Slice(2, data.Length - 2); - - _buffer.ToSpan(ref bufferSpan); - - RagonHeader.WriteUShort((ushort) operation, ref data); - - Room.Send(peerId, data); - } - - - public void Broadcast(uint[] peersIds, RagonOperation operation, IPacket payload) - { - _buffer.Clear(); - payload.Serialize(_buffer); - - Span data = stackalloc byte[_buffer.Length + 2]; - Span bufferSpan = data.Slice(2, data.Length - 2); - - _buffer.ToSpan(ref bufferSpan); - - RagonHeader.WriteUShort((ushort) operation, ref data); - - Room.Broadcast(peersIds, data); - } - - #region VIRTUAL - - public virtual void OnRoomJoined() - { - - } - - public virtual void OnRoomLeaved() - { - - } - - public virtual void OnStart() - { - } - - public virtual void OnStop() - { - } - - public virtual void OnTick(ulong ticks, float deltaTime) - { - - } - - #endregion + _logger = LogManager.GetLogger($"Plugin<{GetType().Name}>"); + + Room = room; + + _globalEvents.Clear(); + _entityEvents.Clear(); } + public void Dispose() + { + _globalEvents.Clear(); + _entityEvents.Clear(); + } + + public void Subscribe(ushort evntCode, Action action) where T : IRagonSerializable, new() + { + if (_globalEvents.ContainsKey(evntCode)) + { + _logger.Warn($"Event subscriber already added {evntCode}"); + return; + } + + var data = new T(); + _globalEvents.Add(evntCode, (Player player, ref ReadOnlySpan raw) => + { + _buffer.Clear(); + _buffer.FromSpan(ref raw, raw.Length); + data.Deserialize(_buffer); + action.Invoke(player, data); + }); + } + + public void Subscribe(Entity entity, ushort evntCode, Action action) where T : IRagonSerializable, new() + { + if (_entityEvents.ContainsKey(evntCode)) + { + _logger.Warn($"Event subscriber already added {evntCode}"); + return; + } + + var data = new T(); + _entityEvents[entity.EntityId][evntCode] = (Player player, Entity ent, ref ReadOnlySpan raw) => + { + _buffer.Clear(); + _buffer.FromSpan(ref raw, raw.Length); + data.Deserialize(_buffer); + action.Invoke(player, ent.EntityId, data); + }; + } + + public void UnsubscribeAll() + { + _globalEvents.Clear(); + _entityEvents.Clear(); + } + + public bool InternalHandle(uint peerId, int entityId, ushort evntCode, ref ReadOnlySpan payload) + { + if (!_entityEvents.ContainsKey(entityId)) + return false; + + if (!_entityEvents[entityId].ContainsKey(evntCode)) + return false; + + var player = Room.GetPlayerById(peerId); + var entity = Room.GetEntityById(entityId); + _entityEvents[entityId][evntCode].Invoke(player, entity, ref payload); + + return true; + } + + public bool InternalHandle(uint peerId, ushort evntCode, ref ReadOnlySpan payload) + { + if (_globalEvents.ContainsKey(evntCode)) + { + var player = Room.GetPlayerById(peerId); + _globalEvents[evntCode].Invoke(player, ref payload); + return true; + } + + return false; + } + + public void SendEvent(Player player, uint eventCode, IRagonSerializable payload) + { + _buffer.Clear(); + payload.Serialize(_buffer); + + var sendData = new byte[_buffer.Length + 2]; + Span data = sendData.AsSpan(); + Span operationData = data.Slice(0, 2); + Span payloadData = data.Slice(2, data.Length - 2); + + _buffer.ToSpan(ref payloadData); + + RagonHeader.WriteUShort((ushort) RagonOperation.REPLICATE_EVENT, ref operationData); + + Room.Send(player.PeerId, sendData); + } + + public void SendEvent(ushort eventCode, IRagonSerializable payload) + { + _buffer.Clear(); + payload.Serialize(_buffer); + + var sendData = new byte[_buffer.Length + 4]; + Span data = sendData.AsSpan(); + Span operationData = data.Slice(0, 2); + Span eventCodeData = data.Slice(2, 2); + Span payloadData = data.Slice(4, _buffer.Length); + + RagonHeader.WriteUShort((ushort) RagonOperation.REPLICATE_EVENT,ref operationData); + RagonHeader.WriteUShort( eventCode, ref eventCodeData); + + _buffer.ToSpan(ref payloadData); + + Room.Broadcast(sendData); + } + + public void SendEntityEvent(Player player, Entity entity, IRagonSerializable payload) + { + _buffer.Clear(); + payload.Serialize(_buffer); + + var sendData = new byte[_buffer.Length + 6]; + Span data = sendData.AsSpan(); + Span operationData = data.Slice(0, 2); + Span entityData = data.Slice(2, 4); + + RagonHeader.WriteUShort((ushort) RagonOperation.REPLICATE_EVENT, ref operationData); + RagonHeader.WriteInt(entity.EntityId, ref entityData); + + Room.Send(player.PeerId, sendData); + } + + public void SendEntityEvent(Entity entity, IRagonSerializable payload) + { + _buffer.Clear(); + payload.Serialize(_buffer); + + var sendData = new byte[_buffer.Length + 6]; + Span data = sendData.AsSpan(); + Span operationData = data.Slice(0, 2); + Span entityData = data.Slice(2, 4); + + RagonHeader.WriteUShort((ushort) RagonOperation.REPLICATE_EVENT, ref operationData); + RagonHeader.WriteInt(entity.EntityId, ref entityData); + + Room.Broadcast(sendData); + } + + + #region VIRTUAL + + public virtual void OnPlayerJoined(Player player) + { + } + + public virtual void OnPlayerLeaved(Player player) + { + } + + public virtual void OnOwnerChanged(Player player) + { + + } + public virtual void OnEntityCreated(Player creator, Entity entity) + { + + } + + public virtual void OnEntityDestroyed(Player destoyer, Entity entity) + { + + } + + public virtual void OnStart() + { + } + + public virtual void OnStop() + { + } + + public virtual void OnTick(float deltaTime) + { + } + + #endregion + } } \ No newline at end of file diff --git a/Ragon/Sources/Plugin/PluginFactory.cs b/Ragon/Sources/Plugin/PluginFactory.cs index a264d15..faaa749 100644 --- a/Ragon/Sources/Plugin/PluginFactory.cs +++ b/Ragon/Sources/Plugin/PluginFactory.cs @@ -2,7 +2,6 @@ namespace Ragon.Core { public interface PluginFactory { - public string PluginName { get; set; } public PluginBase CreatePlugin(string map); public AuthorizationManager CreateManager(Configuration configuration); } diff --git a/Ragon/Sources/Rooms/Room.cs b/Ragon/Sources/Rooms/Room.cs index 0e40e78..1e0cbbb 100755 --- a/Ragon/Sources/Rooms/Room.cs +++ b/Ragon/Sources/Rooms/Room.cs @@ -1,7 +1,10 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using System.Text; +using NetStack.Serialization; using NLog; using Ragon.Common; @@ -13,8 +16,8 @@ namespace Ragon.Core public int PlayersMax { get; private set; } public int PlayersCount => _players.Count; public string Id { get; private set; } - public string Map { get; private set; } - + public string Map { get; private set; } + private ILogger _logger = LogManager.GetCurrentClassLogger(); private Dictionary _players = new(); private Dictionary _entities = new(); @@ -22,7 +25,6 @@ namespace Ragon.Core private readonly PluginBase _plugin; private readonly RoomThread _roomThread; - private ulong _ticks = 0; // Cache private uint[] _readyPlayers = Array.Empty(); @@ -38,8 +40,8 @@ namespace Ragon.Core PlayersMin = min; PlayersMax = max; Id = Guid.NewGuid().ToString(); - - _logger.Info("Room created"); + + _logger.Info($"Room created with plugin: {_plugin.GetType().Name}"); _plugin.Attach(this); } @@ -61,11 +63,13 @@ namespace Ragon.Core _players.Add(peerId, player); _allPlayers = _players.Select(p => p.Key).ToArray(); - + { var idRaw = Encoding.UTF8.GetBytes(Id).AsSpan(); + + var sendData = new byte[idRaw.Length + 18]; + var data = sendData.AsSpan(); - Span data = stackalloc byte[idRaw.Length + 18]; Span operationData = data.Slice(0, 2); Span peerData = data.Slice(2, 4); Span ownerData = data.Slice(4, 4); @@ -78,22 +82,24 @@ namespace Ragon.Core RagonHeader.WriteInt((int) _owner, ref ownerData); RagonHeader.WriteInt(PlayersMin, ref minData); RagonHeader.WriteInt(PlayersMax, ref maxData); - - idRaw.CopyTo(idData); - Send(peerId, data); + idRaw.CopyTo(idData); + + Send(peerId, sendData); } { var sceneRawData = Encoding.UTF8.GetBytes(Map).AsSpan(); - Span data = stackalloc byte[sceneRawData.Length + 2]; + var sendData = new byte[sceneRawData.Length + 2]; + var data = sendData.AsSpan(); + Span operationData = data.Slice(0, 2); Span sceneData = data.Slice(2, sceneRawData.Length); - + RagonHeader.WriteUShort((ushort) RagonOperation.LOAD_SCENE, ref operationData); sceneRawData.CopyTo(sceneData); - Send(peerId, data, DeliveryType.Reliable); + Send(peerId, sendData, DeliveryType.Reliable); } } @@ -103,15 +109,18 @@ namespace Ragon.Core { _allPlayers = _players.Select(p => p.Key).ToArray(); + _plugin.OnPlayerLeaved(player); + foreach (var entityId in player.EntitiesIds) { - Span entityData = stackalloc byte[6]; + var sendData = new byte[6]; + var entityData = sendData.AsSpan(); var operationData = entityData.Slice(0, 2); RagonHeader.WriteUShort((ushort) RagonOperation.DESTROY_ENTITY, ref operationData); RagonHeader.WriteInt(entityId, ref entityData); - Broadcast(_allPlayers, entityData); + Broadcast(_allPlayers, sendData); _entities.Remove(entityId); } @@ -126,12 +135,14 @@ namespace Ragon.Core { var entityData = rawData.Slice(2, 4); var entityId = RagonHeader.ReadInt(ref entityData); - if (_entities.TryGetValue(entityId, out var ent)) + if (_entities.TryGetValue(entityId, out var ent) && ent.OwnerId == peerId) { ent.State = rawData.Slice(6, rawData.Length - 6).ToArray(); - Span data = stackalloc byte[rawData.Length]; + var data = new byte[rawData.Length]; + rawData.CopyTo(data); + Broadcast(_readyPlayers, data); } @@ -141,7 +152,7 @@ namespace Ragon.Core { var entityData = rawData.Slice(2, 4); var entityId = RagonHeader.ReadInt(ref entityData); - if (_entities.TryGetValue(entityId, out var ent)) + if (_entities.TryGetValue(entityId, out var ent) && ent.OwnerId == peerId) { var propertyData = rawData.Slice(6, 4); var propertyId = RagonHeader.ReadInt(ref propertyData); @@ -153,25 +164,47 @@ namespace Ragon.Core else props.Add(propertyId, payload); - // Span sendData = MemoryMarshal.CreateSpan(ref MemoryMarshal.GetReference(rawData), rawData.Length); - - Span sendData = stackalloc byte[rawData.Length]; + var sendData = new byte[rawData.Length]; + rawData.CopyTo(sendData); + Broadcast(_readyPlayers, sendData, DeliveryType.Reliable); } break; } - case RagonOperation.REPLICATE_EVENT: case RagonOperation.REPLICATE_ENTITY_EVENT: { var evntCodeData = rawData.Slice(2, 2); + var entityIdData = rawData.Slice(4, 4); var evntId = RagonHeader.ReadUShort(ref evntCodeData); - - if (_plugin.InternalHandle(peerId, evntId, ref rawData)) return; - - Span data = stackalloc byte[rawData.Length]; + var entityId = RagonHeader.ReadInt(ref entityIdData); + if (_entities[entityId].OwnerId != peerId) + return; + + var payload = rawData.Slice(8, rawData.Length - 8); + if (_plugin.InternalHandle(peerId, entityId, evntId, ref payload)) + return; + + var data = new byte[rawData.Length]; + + rawData.CopyTo(data); + + Broadcast(_readyPlayers, data, DeliveryType.Reliable); + break; + } + case RagonOperation.REPLICATE_EVENT: + { + var evntCodeData = rawData.Slice(2, 2); + var evntId = RagonHeader.ReadUShort(ref evntCodeData); + + var payload = rawData.Slice(4, rawData.Length - 4); + if (_plugin.InternalHandle(peerId, evntId, ref payload)) + return; + + var data = new byte[rawData.Length]; + rawData.CopyTo(data); Broadcast(_readyPlayers, data, DeliveryType.Reliable); @@ -179,9 +212,12 @@ namespace Ragon.Core } case RagonOperation.CREATE_ENTITY: { - var entity = new Entity(peerId); - var entityPayload = rawData.Slice(2, rawData.Length - 2); - entity.State = entityPayload.ToArray(); + var typeData = rawData.Slice(2, 2); + var entityPayloadData = rawData.Slice(4, rawData.Length - 4); + + var entityType = RagonHeader.ReadUShort(ref typeData); + var entity = new Entity(peerId, entityType); + entity.State = entityPayloadData.ToArray(); entity.Properties = new Dictionary(); var player = _players[peerId]; @@ -191,15 +227,20 @@ namespace Ragon.Core _entities.Add(entity.EntityId, entity); _entitiesAll = _entities.Values.ToArray(); - Span data = stackalloc byte[entityPayload.Length + 10]; - var operationData = data.Slice(0, 2); - var entityData = data.Slice(2, 4); - var peerData = data.Slice(6, 4); - var payload = data.Slice(10, entityPayload.Length); + _plugin.OnEntityCreated(player, entity); - entityPayload.CopyTo(payload); + var data = new byte[entityPayloadData.Length + 12]; + var sendData = data.AsSpan(); + var operationData = sendData.Slice(0, 2); + var entityTypeData = sendData.Slice(2, 4); + var entityData = sendData.Slice(4, 4); + var peerData = sendData.Slice(8, 4); + var payload = sendData.Slice(12, entityPayloadData.Length); + + entityPayloadData.CopyTo(payload); RagonHeader.WriteUShort((ushort) RagonOperation.CREATE_ENTITY, ref operationData); + RagonHeader.WriteUShort(entityType, ref entityTypeData); RagonHeader.WriteInt(entity.EntityId, ref entityData); RagonHeader.WriteInt((int) peerId, ref peerData); @@ -222,9 +263,13 @@ namespace Ragon.Core _entities.Remove(entityId); _entitiesAll = _entities.Values.ToArray(); - Span sendData = stackalloc byte[rawData.Length]; + _plugin.OnEntityDestroyed(player, entity); + + var data = new byte[rawData.Length]; + Span sendData = data.AsSpan(); rawData.CopyTo(sendData); - Broadcast(_readyPlayers, sendData, DeliveryType.Reliable); + + Broadcast(_readyPlayers, data, DeliveryType.Reliable); } } @@ -236,8 +281,9 @@ namespace Ragon.Core foreach (var entity in _entities.Values) { var entityState = entity.State.AsSpan(); - - Span sendData = stackalloc byte[entity.State.Length + 10]; + var data = new byte[entity.State.Length + 10]; + + Span sendData = data.AsSpan(); Span operationData = sendData.Slice(0, 2); Span entityData = sendData.Slice(2, 4); Span ownerData = sendData.Slice(6, 4); @@ -246,10 +292,10 @@ namespace Ragon.Core RagonHeader.WriteUShort((ushort) RagonOperation.CREATE_ENTITY, ref operationData); RagonHeader.WriteInt(entity.EntityId, ref entityData); RagonHeader.WriteInt((int) entity.OwnerId, ref ownerData); - + entityState.CopyTo(entityStateData); - - Send(peerId, sendData, DeliveryType.Reliable); + + Send(peerId, data, DeliveryType.Reliable); } Send(peerId, RagonOperation.RESTORE_END); @@ -259,6 +305,8 @@ namespace Ragon.Core { _players[peerId].IsLoaded = true; _readyPlayers = _players.Where(p => p.Value.IsLoaded).Select(p => p.Key).ToArray(); + + _plugin.OnPlayerJoined(_players[peerId]); break; } } @@ -266,16 +314,7 @@ namespace Ragon.Core public void Tick(float deltaTime) { - _ticks++; - _plugin.OnTick(_ticks, deltaTime); - - // for (var i = 0; i < _entitiesAll.Length; i++) - // { - // var entity = _entities[i]; - // Span data = stackalloc byte[entity.State.Length]; - // rawData.CopyTo(data); - // Broadcast(_readyPlayers, data); - // } + _plugin.OnTick(deltaTime); } public void Start() @@ -290,66 +329,66 @@ namespace Ragon.Core _plugin.OnStop(); } + public void Dispose() { _logger.Info("Room destroyed"); - _plugin.Detach(); + _plugin.Dispose(); } - public Player GetPlayerByPeerId(uint peerId) => _players[peerId]; + public Player GetPlayerById(uint peerId) => _players[peerId]; + public Entity GetEntityById(int entityId) => _entities[entityId]; public Player GetOwner() => _players[_owner]; - + public void Send(uint peerId, RagonOperation operation, DeliveryType deliveryType = DeliveryType.Unreliable) { - Span data = stackalloc byte[2]; - RagonHeader.WriteUShort((ushort) operation, ref data); + var rawData = new byte[2]; + var rawDataSpan = new Span(rawData); + + RagonHeader.WriteUShort((ushort) operation, ref rawDataSpan); - var bytes = data.ToArray(); _roomThread.WriteOutEvent(new Event() { PeerId = peerId, - Data = bytes, + Data = rawData, Type = EventType.DATA, Delivery = deliveryType, }); } - public void Send(uint peerId, Span payload, DeliveryType deliveryType = DeliveryType.Unreliable) + public void Send(uint peerId, byte[] rawData, DeliveryType deliveryType = DeliveryType.Unreliable) { - var bytes = payload.ToArray(); _roomThread.WriteOutEvent(new Event() { PeerId = peerId, - Data = bytes, + Data = rawData, Type = EventType.DATA, Delivery = deliveryType, }); } - public void Broadcast(uint[] peersIds, Span rawData, DeliveryType deliveryType = DeliveryType.Unreliable) + public void Broadcast(uint[] peersIds, byte[] rawData, DeliveryType deliveryType = DeliveryType.Unreliable) { - var bytes = rawData.ToArray(); foreach (var peer in peersIds) { _roomThread.WriteOutEvent(new Event() { PeerId = peer, - Data = bytes, + Data = rawData, Type = EventType.DATA, Delivery = deliveryType, }); } } - public void Broadcast(Span rawData, DeliveryType deliveryType = DeliveryType.Unreliable) + public void Broadcast(byte[] rawData, DeliveryType deliveryType = DeliveryType.Unreliable) { - var bytes = rawData.ToArray(); foreach (var player in _players.Values.ToArray()) { _roomThread.WriteOutEvent(new Event() { PeerId = player.PeerId, - Data = bytes, + Data = rawData, Type = EventType.DATA, Delivery = deliveryType, }); diff --git a/Ragon/Sources/Rooms/RoomManager.cs b/Ragon/Sources/Rooms/RoomManager.cs index 5e0eec5..3d5cd3b 100644 --- a/Ragon/Sources/Rooms/RoomManager.cs +++ b/Ragon/Sources/Rooms/RoomManager.cs @@ -28,7 +28,7 @@ namespace Ragon.Core _peersByRoom = new Dictionary(); } - public void ProcessEvent(RagonOperation operation, uint peerId, byte[] payload) + public void ProcessEvent(RagonOperation operation, uint peerId, ReadOnlySpan payload) { switch (operation) { @@ -56,29 +56,30 @@ namespace Ragon.Core { if (_manager.OnAuthorize(peerId, ref payload)) { - Span data = stackalloc byte[2]; + var sendData = new byte[2]; + Span data = sendData.AsSpan(); + RagonHeader.WriteUShort((ushort) RagonOperation.AUTHORIZED_SUCCESS, ref data); - var bytes = data.ToArray(); _roomThread.WriteOutEvent(new Event() { Delivery = DeliveryType.Reliable, Type = EventType.DATA, - Data = bytes, + Data = sendData, PeerId = peerId, }); } else { - Span data = stackalloc byte[2]; + var sendData = new byte[2]; + var data = sendData.AsSpan(); RagonHeader.WriteUShort((ushort) RagonOperation.AUTHORIZED_FAILED, ref data); - var bytes = data.ToArray(); _roomThread.WriteOutEvent(new Event() { Delivery = DeliveryType.Reliable, Type = EventType.DATA, - Data = bytes, + Data = sendData, PeerId = peerId, }); @@ -133,7 +134,7 @@ namespace Ragon.Core return room; } - public Room Left(uint peerId, byte[] payload) + public Room Left(uint peerId, ReadOnlySpan payload) { _peersByRoom.Remove(peerId, out var room); diff --git a/Ragon/Sources/Rooms/RoomThread.cs b/Ragon/Sources/Rooms/RoomThread.cs index f378270..8063f98 100755 --- a/Ragon/Sources/Rooms/RoomThread.cs +++ b/Ragon/Sources/Rooms/RoomThread.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Threading; using DisruptorUnity3d; +using NLog; using Ragon.Common; namespace Ragon.Core @@ -13,6 +14,7 @@ namespace Ragon.Core private readonly Dictionary _socketByRooms; private readonly Thread _thread; private readonly Stopwatch _timer; + private readonly ILogger _logger = LogManager.GetCurrentClassLogger(); private RingBuffer _receiveBuffer = new RingBuffer(8192 + 8192); private RingBuffer _sendBuffer = new RingBuffer(8192 + 8192); @@ -60,7 +62,6 @@ namespace Ragon.Core { if (evnt.Type == EventType.DISCONNECTED || evnt.Type == EventType.TIMEOUT) { - if (_socketByRooms.ContainsKey(evnt.PeerId)) { _roomManager.Disconnected(evnt.PeerId); @@ -70,16 +71,23 @@ namespace Ragon.Core if (evnt.Type == EventType.DATA) { - ReadOnlySpan data = evnt.Data.AsSpan(); - var operation = (RagonOperation) RagonHeader.ReadUShort(ref data); + var data = new ReadOnlySpan(evnt.Data); + var operationData = data.Slice(0, 2); + var operation = (RagonOperation) RagonHeader.ReadUShort(ref operationData); if (_socketByRooms.TryGetValue(evnt.PeerId, out var room)) { - room.ProcessEvent(operation, evnt.PeerId, evnt.Data); + try + { + room.ProcessEvent(operation, evnt.PeerId, data); + } + catch (Exception exception) + { + _logger.Error(exception); + } } else { - var payload = new byte[evnt.Data.Length - 2]; - Array.Copy(evnt.Data, 2, payload, 0, evnt.Data.Length - 2); + var payload = data.Slice(2, data.Length - 2); _roomManager.ProcessEvent(operation, evnt.PeerId, payload); } } diff --git a/Ragon/Sources/Rooms/RoomThreadInfo.cs b/Ragon/Sources/Rooms/RoomThreadInfo.cs deleted file mode 100644 index 280e8e0..0000000 --- a/Ragon/Sources/Rooms/RoomThreadInfo.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Ragon.Core -{ - public struct RoomThreadInfo - { - public int PlayersCount; - public int PlayersMax; - public bool Available; - } -} \ No newline at end of file diff --git a/readme.md b/readme.md index 8159f87..70411b1 100755 --- a/readme.md +++ b/readme.md @@ -2,12 +2,46 @@

-Ragon - high perfomance room based game server with plugin based architecture. +## Ragon Server -Features: -- Room base architecture -- Efficient memory management -- Flexible plugin system -- No CCU limitations -- Fully multithreaded +Ragon is fully free high perfomance room based game server with plugin based architecture. + +Documentation +
+Get started + + +### Features: +- Free +- Flexiable API +- Support client authoritative +- Support server authoritative +- Room based architecture +- Extendable room logic via plugin +- Custom authorization +- No CCU limitations* +- Extentable rooms with plugin system +- Fully multi-threaded +- Engine agnostic +- Support any client architecture (OOP, ECS) +- UDP + +### Roadmap: +- Use native memory +- Reduce allocations +- Dashboard for monitoring entities and players in realtime +- Statistics for monitoring state of server, cpu, memory +- Docker support +- Add additional API to plugin system + +### Dependencies +* ENet-Sharp +* NetStack +* RingBuffer-Unity3D + +### License + + +### Tips +* Limited to 4095 CCU by library ENet-Sharp \ No newline at end of file