From f88aa728c31fa215d7b0dad8b1007d9ca073e3dc Mon Sep 17 00:00:00 2001 From: Edmand46 Date: Sat, 14 May 2022 10:36:21 +0400 Subject: [PATCH] feat: ticks, state authority, event authority --- .gitignore | 1 + Game/Source/Modes/ExamplePlugin.cs | 70 ---------- Game/config.json | 7 - Ragon.Common/Protocol/RagonAuthority.cs | 8 ++ Ragon.Common/Protocol/RagonOperation.cs | 1 - Ragon.Common/Ragon.Common.csproj | 4 +- Ragon.sln | 2 +- Ragon/Ragon.csproj | 2 - Ragon/Sources/Configuration/Configuration.cs | 3 +- Ragon/Sources/Entity/Entity.cs | 10 +- Ragon/Sources/Entity/EntityState.cs | 32 +++++ Ragon/Sources/Plugin/PluginBase.cs | 2 +- Ragon/Sources/Rooms/Room.cs | 127 +++++++++---------- Ragon/Sources/Rooms/RoomThread.cs | 83 ++++++------ SimpleServer/config.json | 8 ++ readme.md | 3 + 16 files changed, 163 insertions(+), 200 deletions(-) delete mode 100755 Game/Source/Modes/ExamplePlugin.cs delete mode 100755 Game/config.json create mode 100644 Ragon.Common/Protocol/RagonAuthority.cs create mode 100644 Ragon/Sources/Entity/EntityState.cs create mode 100755 SimpleServer/config.json diff --git a/.gitignore b/.gitignore index ec1bc2b..42c30e2 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ .vs obj bin +*.user \ No newline at end of file diff --git a/Game/Source/Modes/ExamplePlugin.cs b/Game/Source/Modes/ExamplePlugin.cs deleted file mode 100755 index 7f42945..0000000 --- a/Game/Source/Modes/ExamplePlugin.cs +++ /dev/null @@ -1,70 +0,0 @@ -using System.Runtime.InteropServices; -using Game.Source.Events; -using NLog; -using Ragon.Core; - -namespace Game.Source -{ - public class ExamplePlugin: PluginBase - { - public override void OnStart() - { - _logger.Info("Plugin started"); - - /*Subscribe(123, OnTestEvent); - */ - // Subscribe(500, OnTestEvent2);; - } - - private void OnTestEvent2(Player obj) - { - _logger.Info("Event without data"); - } - - 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, 500, OnEntityTestEvent); - } - - public override void OnEntityDestroyed(Player destoyer, Entity entity) - { - - } - - private void OnEntityTestEvent(Player player, Entity entity) - { - _logger.Info("Entity event with empty payload"); - } - } -} \ No newline at end of file diff --git a/Game/config.json b/Game/config.json deleted file mode 100755 index 81eae9f..0000000 --- a/Game/config.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "apiKey": "123", - "server": { - "port": 5000, - "skipTimeout": 60 - } -} \ No newline at end of file diff --git a/Ragon.Common/Protocol/RagonAuthority.cs b/Ragon.Common/Protocol/RagonAuthority.cs new file mode 100644 index 0000000..d892f55 --- /dev/null +++ b/Ragon.Common/Protocol/RagonAuthority.cs @@ -0,0 +1,8 @@ +namespace Ragon.Common +{ + public enum RagonAuthority: byte + { + OWNER_ONLY, + ALL, + } +} \ No newline at end of file diff --git a/Ragon.Common/Protocol/RagonOperation.cs b/Ragon.Common/Protocol/RagonOperation.cs index 46fa3de..7e7a720 100644 --- a/Ragon.Common/Protocol/RagonOperation.cs +++ b/Ragon.Common/Protocol/RagonOperation.cs @@ -23,7 +23,6 @@ namespace Ragon.Common RESTORED, REPLICATE_ENTITY_STATE, - REPLICATE_ENTITY_PROPERTY, REPLICATE_ENTITY_EVENT, REPLICATE_EVENT, } diff --git a/Ragon.Common/Ragon.Common.csproj b/Ragon.Common/Ragon.Common.csproj index 17c2955..1413a29 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/Plugins/ + false true TRACE;NETSTACK_SPAN - /Users/edmand46/UnityProjects/Ragon-Unity-SDK/Assets/RagonSDK/Plugins/ + TRACE;NETSTACK_SPAN diff --git a/Ragon.sln b/Ragon.sln index 6838f6b..6f2e392 100755 --- a/Ragon.sln +++ b/Ragon.sln @@ -2,7 +2,7 @@ Microsoft Visual Studio Solution File, Format Version 12.00 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ragon", "Ragon\Ragon.csproj", "{BABA1AF0-CF91-43F2-9577-53800068ACCF}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Game", "Game\Game.csproj", "{C2B87ADE-A122-4366-8EB7-24DAE11EBAF0}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SimpleServer", "SimpleServer\SimpleServer.csproj", "{C2B87ADE-A122-4366-8EB7-24DAE11EBAF0}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ragon.Common", "Ragon.Common\Ragon.Common.csproj", "{F478B2A2-36F4-43B9-9BB7-382A57C449B2}" EndProject diff --git a/Ragon/Ragon.csproj b/Ragon/Ragon.csproj index dede3de..79a3555 100755 --- a/Ragon/Ragon.csproj +++ b/Ragon/Ragon.csproj @@ -20,8 +20,6 @@ - - diff --git a/Ragon/Sources/Configuration/Configuration.cs b/Ragon/Sources/Configuration/Configuration.cs index 97541a4..ee48ce6 100755 --- a/Ragon/Sources/Configuration/Configuration.cs +++ b/Ragon/Sources/Configuration/Configuration.cs @@ -6,12 +6,13 @@ namespace Ragon.Core public struct Server { public ushort Port; + public ushort TickRate; } [Serializable] public struct Configuration { - public string ApiKey; + public string Key; public Server Server; } } \ No newline at end of file diff --git a/Ragon/Sources/Entity/Entity.cs b/Ragon/Sources/Entity/Entity.cs index 8d19bb1..061a275 100644 --- a/Ragon/Sources/Entity/Entity.cs +++ b/Ragon/Sources/Entity/Entity.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using Ragon.Common; namespace Ragon.Core; @@ -8,13 +8,15 @@ public class Entity 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 RagonAuthority Authority { get; private set; } + public EntityState State { get; private set; } - public Entity(uint ownerId, ushort entityType) + public Entity(uint ownerId, ushort entityType, RagonAuthority stateAuthority, RagonAuthority eventAuthority) { OwnerId = ownerId; EntityType = entityType; EntityId = _idGenerator++; + State = new EntityState(stateAuthority); + Authority = eventAuthority; } } \ No newline at end of file diff --git a/Ragon/Sources/Entity/EntityState.cs b/Ragon/Sources/Entity/EntityState.cs new file mode 100644 index 0000000..7dad8cb --- /dev/null +++ b/Ragon/Sources/Entity/EntityState.cs @@ -0,0 +1,32 @@ +using System; +using System.Runtime.Serialization; +using Ragon.Common; + +namespace Ragon.Core; + +public class EntityState +{ + public bool isDirty { get; private set; } + public RagonAuthority Authority { get; private set; } + + public byte[] Data + { + get => Data; + set + { + Data = value; + isDirty = true; + } + } + + public EntityState(RagonAuthority ragonAuthority) + { + Authority = ragonAuthority; + isDirty = true; + } + + public void Clear() + { + isDirty = true; + } +} \ No newline at end of file diff --git a/Ragon/Sources/Plugin/PluginBase.cs b/Ragon/Sources/Plugin/PluginBase.cs index 36246cc..7dfe213 100755 --- a/Ragon/Sources/Plugin/PluginBase.cs +++ b/Ragon/Sources/Plugin/PluginBase.cs @@ -280,7 +280,7 @@ namespace Ragon.Core { } - public virtual void OnTick(float deltaTime) + public virtual void OnTick(ulong ticks, float deltaTime) { } diff --git a/Ragon/Sources/Rooms/Room.cs b/Ragon/Sources/Rooms/Room.cs index 39d8b72..25e9cf3 100755 --- a/Ragon/Sources/Rooms/Room.cs +++ b/Ragon/Sources/Rooms/Room.cs @@ -22,6 +22,7 @@ namespace Ragon.Core private Dictionary _players = new(); private Dictionary _entities = new(); private uint _owner; + private uint _ticks; private readonly PluginBase _plugin; private readonly RoomThread _roomThread; @@ -69,7 +70,7 @@ namespace Ragon.Core var sendData = new byte[idRaw.Length + 18]; var data = sendData.AsSpan(); - + Span operationData = data.Slice(0, 2); Span peerData = data.Slice(2, 4); Span ownerData = data.Slice(4, 4); @@ -84,7 +85,7 @@ namespace Ragon.Core RagonHeader.WriteInt(PlayersMax, ref maxData); idRaw.CopyTo(idData); - + Send(peerId, sendData); } @@ -92,7 +93,7 @@ namespace Ragon.Core var sceneRawData = Encoding.UTF8.GetBytes(Map).AsSpan(); var sendData = new byte[sceneRawData.Length + 2]; var data = sendData.AsSpan(); - + Span operationData = data.Slice(0, 2); Span sceneData = data.Slice(2, sceneRawData.Length); @@ -135,56 +136,33 @@ namespace Ragon.Core { var entityData = rawData.Slice(2, 4); var entityId = RagonHeader.ReadInt(ref entityData); - if (_entities.TryGetValue(entityId, out var ent) && ent.OwnerId == peerId) + if (_entities.TryGetValue(entityId, out var ent)) { - ent.State = rawData.Slice(6, rawData.Length - 6).ToArray(); + if (ent.State.Authority == RagonAuthority.OWNER_ONLY && ent.OwnerId != peerId) + return; + + ent.State.Data = rawData.Slice(6, rawData.Length - 6).ToArray(); var data = new byte[rawData.Length]; - + rawData.CopyTo(data); - + Broadcast(_readyPlayers, data); } break; } - case RagonOperation.REPLICATE_ENTITY_PROPERTY: - { - var entityData = rawData.Slice(2, 4); - var entityId = RagonHeader.ReadInt(ref entityData); - if (_entities.TryGetValue(entityId, out var ent) && ent.OwnerId == peerId) - { - var propertyData = rawData.Slice(6, 4); - var propertyId = RagonHeader.ReadInt(ref propertyData); - var payload = rawData.Slice(10, rawData.Length - 10).ToArray(); - var props = _entities[entityId].Properties; - - if (props.ContainsKey(propertyId)) - props[propertyId] = payload; - else - props.Add(propertyId, payload); - - var sendData = new byte[rawData.Length]; - - rawData.CopyTo(sendData); - - Broadcast(_readyPlayers, sendData, DeliveryType.Reliable); - } - - break; - } case RagonOperation.REPLICATE_ENTITY_EVENT: { - var evntCodeData = rawData.Slice(2, 2); var entityIdData = rawData.Slice(4, 4); var evntId = RagonHeader.ReadUShort(ref evntCodeData); var entityId = RagonHeader.ReadInt(ref entityIdData); - - if (!_entities.ContainsKey(entityId)) + + if (!_entities.TryGetValue(entityId, out var ent)) return; - - if (_entities[entityId].OwnerId != peerId) + + if (ent.Authority == RagonAuthority.OWNER_ONLY && ent.OwnerId != peerId) return; var payload = rawData.Slice(8, rawData.Length - 8); @@ -208,21 +186,23 @@ namespace Ragon.Core return; var data = new byte[rawData.Length]; - + rawData.CopyTo(data); - + Broadcast(_readyPlayers, data, DeliveryType.Reliable); break; } case RagonOperation.CREATE_ENTITY: { var typeData = rawData.Slice(2, 2); + var authorityData = 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 stateAuthority = (RagonAuthority) authorityData[0]; + var eventAuthority = (RagonAuthority) authorityData[1]; + var entity = new Entity(peerId, entityType, stateAuthority, eventAuthority); + entity.State.Data = entityPayloadData.ToArray(); var player = _players[peerId]; player.Entities.Add(entity); @@ -233,16 +213,20 @@ namespace Ragon.Core _plugin.OnEntityCreated(player, entity); - var data = new byte[entityPayloadData.Length + 12]; + var data = new byte[entityPayloadData.Length + 14]; var sendData = data.AsSpan(); var operationData = sendData.Slice(0, 2); var entityTypeData = sendData.Slice(2, 2); - var entityIdData = sendData.Slice(4, 4); - var peerData = sendData.Slice(8, 4); - var payload = sendData.Slice(12, entityPayloadData.Length); + var authority = sendData.Slice(4, 2); + var entityIdData = sendData.Slice(6, 4); + var peerData = sendData.Slice(10, 4); + var payload = sendData.Slice(14, entityPayloadData.Length); entityPayloadData.CopyTo(payload); + authority[0] = authorityData[0]; + authority[1] = authorityData[1]; + RagonHeader.WriteUShort((ushort) RagonOperation.CREATE_ENTITY, ref operationData); RagonHeader.WriteUShort(entityType, ref entityTypeData); RagonHeader.WriteInt(entity.EntityId, ref entityIdData); @@ -257,24 +241,24 @@ namespace Ragon.Core var entityId = RagonHeader.ReadInt(ref entityData); if (_entities.TryGetValue(entityId, out var entity)) { - if (entity.OwnerId == peerId) - { - var player = _players[peerId]; + if (entity.Authority == RagonAuthority.OWNER_ONLY && entity.OwnerId != peerId) + return; + + var player = _players[peerId]; - player.Entities.Remove(entity); - player.EntitiesIds.Remove(entity.EntityId); + player.Entities.Remove(entity); + player.EntitiesIds.Remove(entity.EntityId); - _entities.Remove(entityId); - _entitiesAll = _entities.Values.ToArray(); + _entities.Remove(entityId); + _entitiesAll = _entities.Values.ToArray(); - _plugin.OnEntityDestroyed(player, entity); + _plugin.OnEntityDestroyed(player, entity); - var data = new byte[rawData.Length]; - Span sendData = data.AsSpan(); - rawData.CopyTo(sendData); - - Broadcast(_readyPlayers, data, DeliveryType.Reliable); - } + var data = new byte[rawData.Length]; + Span sendData = data.AsSpan(); + rawData.CopyTo(sendData); + + Broadcast(_readyPlayers, data, DeliveryType.Reliable); } break; @@ -284,23 +268,27 @@ namespace Ragon.Core Send(peerId, RagonOperation.RESTORE_BEGIN, DeliveryType.Reliable); foreach (var entity in _entities.Values) { - var entityState = entity.State.AsSpan(); - var data = new byte[entity.State.Length + 12]; - + var entityState = entity.State.Data.AsSpan(); + var data = new byte[entity.State.Data.Length + 12]; + Span sendData = data.AsSpan(); Span operationData = sendData.Slice(0, 2); Span entityTypeData = sendData.Slice(2, 2); - Span entityData = sendData.Slice(4, 4); - Span ownerData = sendData.Slice(8, 4); - Span entityStateData = sendData.Slice(12, entity.State.Length); + Span authorityData = sendData.Slice(4, 2); + Span entityData = sendData.Slice(6, 4); + Span ownerData = sendData.Slice(10, 4); + Span entityStateData = sendData.Slice(14, entity.State.Data.Length); RagonHeader.WriteUShort((ushort) RagonOperation.CREATE_ENTITY, ref operationData); RagonHeader.WriteUShort(entity.EntityType, ref entityTypeData); RagonHeader.WriteInt(entity.EntityId, ref entityData); RagonHeader.WriteInt((int) entity.OwnerId, ref ownerData); + + authorityData[0] = (byte) entity.State.Authority; + authorityData[1] = (byte) entity.Authority; entityState.CopyTo(entityStateData); - + Send(peerId, data, DeliveryType.Reliable); } @@ -320,7 +308,8 @@ namespace Ragon.Core public void Tick(float deltaTime) { - _plugin.OnTick(deltaTime); + _ticks++; + _plugin.OnTick(_ticks, deltaTime); } public void Start() @@ -350,7 +339,7 @@ namespace Ragon.Core { var rawData = new byte[2]; var rawDataSpan = new Span(rawData); - + RagonHeader.WriteUShort((ushort) operation, ref rawDataSpan); _roomThread.WriteOutEvent(new Event() diff --git a/Ragon/Sources/Rooms/RoomThread.cs b/Ragon/Sources/Rooms/RoomThread.cs index 8063f98..a326c4d 100755 --- a/Ragon/Sources/Rooms/RoomThread.cs +++ b/Ragon/Sources/Rooms/RoomThread.cs @@ -15,26 +15,27 @@ namespace Ragon.Core private readonly Thread _thread; private readonly Stopwatch _timer; private readonly ILogger _logger = LogManager.GetCurrentClassLogger(); - + private readonly float _deltaTime = 0.0f; private RingBuffer _receiveBuffer = new RingBuffer(8192 + 8192); private RingBuffer _sendBuffer = new RingBuffer(8192 + 8192); - + public Configuration Configuration { get; private set; } public bool ReadOutEvent(out Event evnt) => _sendBuffer.TryDequeue(out evnt); public void WriteOutEvent(Event evnt) => _sendBuffer.Enqueue(evnt); public bool ReadIntEvent(out Event evnt) => _receiveBuffer.TryDequeue(out evnt); public void WriteInEvent(Event evnt) => _receiveBuffer.Enqueue(evnt); - + public RoomThread(PluginFactory factory, Configuration configuration) { _thread = new Thread(Execute); _thread.IsBackground = true; _timer = new Stopwatch(); _socketByRooms = new Dictionary(); - + Configuration = configuration; - + _deltaTime = 1000.0f / Configuration.Server.TickRate; + _roomManager = new RoomManager(this, factory); _roomManager.OnJoined += (tuple) => _socketByRooms.Add(tuple.Item1, tuple.Item2); _roomManager.OnLeaved += (tuple) => _socketByRooms.Remove(tuple.Item1); @@ -55,52 +56,50 @@ namespace Ragon.Core { while (true) { - var deltaTime = _timer.ElapsedMilliseconds; - if (deltaTime > 1000 / 60) + while (_receiveBuffer.TryDequeue(out var evnt)) { - while (_receiveBuffer.TryDequeue(out var evnt)) + if (evnt.Type == EventType.DISCONNECTED || evnt.Type == EventType.TIMEOUT) { - if (evnt.Type == EventType.DISCONNECTED || evnt.Type == EventType.TIMEOUT) + if (_socketByRooms.ContainsKey(evnt.PeerId)) { - if (_socketByRooms.ContainsKey(evnt.PeerId)) - { - _roomManager.Disconnected(evnt.PeerId); - _socketByRooms.Remove(evnt.PeerId); - } - } - - if (evnt.Type == EventType.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)) - { - try - { - room.ProcessEvent(operation, evnt.PeerId, data); - } - catch (Exception exception) - { - _logger.Error(exception); - } - } - else - { - var payload = data.Slice(2, data.Length - 2); - _roomManager.ProcessEvent(operation, evnt.PeerId, payload); - } + _roomManager.Disconnected(evnt.PeerId); + _socketByRooms.Remove(evnt.PeerId); } } - _roomManager.Tick(deltaTime / 1000.0f); + if (evnt.Type == EventType.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)) + { + try + { + room.ProcessEvent(operation, evnt.PeerId, data); + } + catch (Exception exception) + { + _logger.Error(exception); + } + } + else + { + var payload = data.Slice(2, data.Length - 2); + _roomManager.ProcessEvent(operation, evnt.PeerId, payload); + } + } + } - _timer.Restart(); - } - else + var elapsedMilliseconds = _timer.ElapsedMilliseconds; + if (elapsedMilliseconds > _deltaTime) { - Thread.Sleep(1); + _roomManager.Tick(elapsedMilliseconds / 1000.0f); + _timer.Restart(); + continue; } + + Thread.Sleep(15); } } diff --git a/SimpleServer/config.json b/SimpleServer/config.json new file mode 100755 index 0000000..65d58c6 --- /dev/null +++ b/SimpleServer/config.json @@ -0,0 +1,8 @@ +{ + "key": "defaultkey", + "server": { + "port": 4444, + "skipTimeout": 60, + "tickRate": 30 + } +} \ No newline at end of file diff --git a/readme.md b/readme.md index 517587e..0b08648 100755 --- a/readme.md +++ b/readme.md @@ -34,6 +34,9 @@ Ragon is fully free high perfomance room based game server with plugin based arc - Docker support - Add additional API to plugin system +### Requirements +- OSX, Windows, Linux(Ubuntu, Debian) +- .NET 6.0 ### Dependencies * ENet-Sharp * NetStack