feat: ticks, state authority, event authority

This commit is contained in:
2022-05-14 10:36:21 +04:00
parent 053d5c9383
commit f88aa728c3
16 changed files with 163 additions and 200 deletions
+1
View File
@@ -3,3 +3,4 @@
.vs
obj
bin
*.user
-70
View File
@@ -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<TestEvent>(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");
}
}
}
-7
View File
@@ -1,7 +0,0 @@
{
"apiKey": "123",
"server": {
"port": 5000,
"skipTimeout": 60
}
}
+8
View File
@@ -0,0 +1,8 @@
namespace Ragon.Common
{
public enum RagonAuthority: byte
{
OWNER_ONLY,
ALL,
}
}
-1
View File
@@ -23,7 +23,6 @@ namespace Ragon.Common
RESTORED,
REPLICATE_ENTITY_STATE,
REPLICATE_ENTITY_PROPERTY,
REPLICATE_ENTITY_EVENT,
REPLICATE_EVENT,
}
+2 -2
View File
@@ -8,14 +8,14 @@
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<OutputPath>/Users/edmand46/UnityProjects/Ragon-Unity-SDK/Assets/RagonSDK/Plugins/</OutputPath>
<OutputPath></OutputPath>
<DebugSymbols>false</DebugSymbols>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<DefineConstants>TRACE;NETSTACK_SPAN</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<OutputPath>/Users/edmand46/UnityProjects/Ragon-Unity-SDK/Assets/RagonSDK/Plugins/</OutputPath>
<OutputPath></OutputPath>
<DefineConstants>TRACE;NETSTACK_SPAN</DefineConstants>
</PropertyGroup>
+1 -1
View File
@@ -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
-2
View File
@@ -20,8 +20,6 @@
<PackageReference Include="ENet-CSharp" Version="2.4.8" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="NLog" Version="5.0.0-rc2" />
<PackageReference Include="StackExchange.Redis" Version="2.5.61" />
<PackageReference Include="StackExchange.Redis.Extensions.Core" Version="8.0.4" />
</ItemGroup>
<ItemGroup>
+2 -1
View File
@@ -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;
}
}
+6 -4
View File
@@ -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<int, byte[]> 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;
}
}
+32
View File
@@ -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;
}
}
+1 -1
View File
@@ -280,7 +280,7 @@ namespace Ragon.Core
{
}
public virtual void OnTick(float deltaTime)
public virtual void OnTick(ulong ticks, float deltaTime)
{
}
+58 -69
View File
@@ -22,6 +22,7 @@ namespace Ragon.Core
private Dictionary<uint, Player> _players = new();
private Dictionary<int, Entity> _entities = new();
private uint _owner;
private uint _ticks;
private readonly PluginBase _plugin;
private readonly RoomThread _roomThread;
@@ -69,7 +70,7 @@ namespace Ragon.Core
var sendData = new byte[idRaw.Length + 18];
var data = sendData.AsSpan();
Span<byte> operationData = data.Slice(0, 2);
Span<byte> peerData = data.Slice(2, 4);
Span<byte> 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<byte> operationData = data.Slice(0, 2);
Span<byte> 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<int, byte[]>();
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<byte> sendData = data.AsSpan();
rawData.CopyTo(sendData);
Broadcast(_readyPlayers, data, DeliveryType.Reliable);
}
var data = new byte[rawData.Length];
Span<byte> 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<byte> sendData = data.AsSpan();
Span<byte> operationData = sendData.Slice(0, 2);
Span<byte> entityTypeData = sendData.Slice(2, 2);
Span<byte> entityData = sendData.Slice(4, 4);
Span<byte> ownerData = sendData.Slice(8, 4);
Span<byte> entityStateData = sendData.Slice(12, entity.State.Length);
Span<byte> authorityData = sendData.Slice(4, 2);
Span<byte> entityData = sendData.Slice(6, 4);
Span<byte> ownerData = sendData.Slice(10, 4);
Span<byte> 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<byte>(rawData);
RagonHeader.WriteUShort((ushort) operation, ref rawDataSpan);
_roomThread.WriteOutEvent(new Event()
+41 -42
View File
@@ -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<Event> _receiveBuffer = new RingBuffer<Event>(8192 + 8192);
private RingBuffer<Event> _sendBuffer = new RingBuffer<Event>(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<uint, Room>();
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<byte>(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<byte>(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);
}
}
+8
View File
@@ -0,0 +1,8 @@
{
"key": "defaultkey",
"server": {
"port": 4444,
"skipTimeout": 60,
"tickRate": 30
}
}
+3
View File
@@ -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