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
-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);
}
}