Compare commits

...

36 Commits

Author SHA1 Message Date
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 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
edmand46 6ec29e5dcd 1.0.11-rc 2022-08-20 12:27:04 +04:00
edmand46 65c1d9c6d4 feat: added checks and new ragon serializer methods 2022-08-14 15:39:22 +04:00
edmand46 f2934bc8ee fix: static in snapshot 2022-08-14 12:36:12 +04:00
edmand46 81879d4fe2 fix: event replication 2022-08-14 11:31:56 +04:00
edmand46 2d89d267f5 Merge remote-tracking branch 'origin/main' 2022-08-14 11:17:59 +04:00
edmand46 34107cbd5f feat: added checking of owner 2022-08-14 11:17:22 +04:00
edmand46 ba2ce25aec chore: removed some logs 2022-08-14 11:14:11 +04:00
edmand46 ca17e7de83 Merge pull request #6 from edmand46/dev
feature: State -> Properties
2022-08-13 23:28:55 +04:00
edmand46 dff6dbcf97 feat: support static entity 2022-08-13 23:22:46 +04:00
edmand46 ae485f96d4 wip 2022-08-13 18:54:02 +04:00
edmand46 74904de16b Merge pull request #5 from edmand46/dev
Target of replication event
2022-07-31 19:13:36 +04:00
edmand46 8fd8558323 Merge pull request #4 from edmand46/dev
Remove NetStack dependency
2022-07-31 18:20:43 +04:00
edmand46 877ebdcde2 Merge pull request #3 from edmand46/dev
Replication level
2022-07-31 09:37:35 +04:00
28 changed files with 739 additions and 474 deletions
+3 -2
View File
@@ -2,7 +2,8 @@ namespace Ragon.Common
{
public enum RagonAuthority: byte
{
OWNER_ONLY,
ALL,
None,
OwnerOnly,
All,
}
}
+1 -3
View File
@@ -15,15 +15,13 @@ namespace Ragon.Common
JOIN_FAILED,
LOAD_SCENE,
SCENE_IS_LOADED,
SCENE_LOADED,
PLAYER_JOINED,
PLAYER_LEAVED,
CREATE_ENTITY,
CREATE_STATIC_ENTITY,
DESTROY_ENTITY,
SNAPSHOT,
REPLICATE_ENTITY_STATE,
@@ -0,0 +1,10 @@
namespace Ragon.Common
{
public enum RagonReplicationMode: byte
{
Local,
Server,
LocalAndServer,
Buffered,
}
}
+101 -19
View File
@@ -11,10 +11,15 @@ namespace Ragon.Common
{
[FieldOffset(0)] public int Int;
[FieldOffset(0)] public float Float;
[FieldOffset(0)] public long Long;
[FieldOffset(0)] public byte Byte0;
[FieldOffset(1)] public byte Byte1;
[FieldOffset(2)] public byte Byte2;
[FieldOffset(3)] public byte Byte3;
[FieldOffset(4)] public byte Byte4;
[FieldOffset(5)] public byte Byte5;
[FieldOffset(6)] public byte Byte6;
[FieldOffset(7)] public byte Byte7;
}
public class RagonSerializer
@@ -22,6 +27,7 @@ namespace Ragon.Common
private byte[] _data;
private int _offset;
private int _size;
public int Lenght => _offset;
public int Size => _size - _offset;
@@ -40,12 +46,18 @@ namespace Ragon.Common
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteByte(byte value)
public void AddOffset(int offset)
{
_offset += offset;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public int WriteByte(byte value)
{
ResizeIfNeed(1);
_data[_offset] = value;
_offset += 1;
return 1;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -57,12 +69,12 @@ namespace Ragon.Common
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteBool(bool value)
public int WriteBool(bool value)
{
ResizeIfNeed(1);
_data[_offset] = value ? (byte) 1 : (byte) 0;
_offset += 1;
return 1;
}
@@ -76,30 +88,85 @@ namespace Ragon.Common
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteInt(int value)
public int WriteInt(int value)
{
ResizeIfNeed(4);
var converter = new ValueConverter() { Int = value };
var converter = new ValueConverter() {Int = value};
_data[_offset] = converter.Byte0;
_data[_offset + 1] = converter.Byte1;
_data[_offset + 2] = converter.Byte2;
_data[_offset + 3] = converter.Byte3;
_offset += 4;
return 4;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public int WriteInt(int value, int offset)
{
ResizeIfNeed(4);
var converter = new ValueConverter() {Int = value};
_data[offset] = converter.Byte0;
_data[offset + 1] = converter.Byte1;
_data[offset + 2] = converter.Byte2;
_data[offset + 3] = converter.Byte3;
return 4;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public int ReadInt()
{
var converter = new ValueConverter() { Byte0 = _data[_offset], Byte1 = _data[_offset + 1], Byte2 = _data[_offset + 2], Byte3 = _data[_offset + 3] };
var converter = new ValueConverter {Byte0 = _data[_offset], Byte1 = _data[_offset + 1], Byte2 = _data[_offset + 2], Byte3 = _data[_offset + 3]};
_offset += 4;
return converter.Int;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteFloat(float value)
public void WriteLong(long value)
{
ResizeIfNeed(8);
WriteLong(value, _offset);
_offset += 8;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public int WriteLong(long value, int offset)
{
var converter = new ValueConverter() {Long = value};
_data[offset] = converter.Byte0;
_data[offset + 1] = converter.Byte1;
_data[offset + 2] = converter.Byte2;
_data[offset + 3] = converter.Byte3;
_data[offset + 4] = converter.Byte4;
_data[offset + 5] = converter.Byte5;
_data[offset + 6] = converter.Byte6;
_data[offset + 7] = converter.Byte7;
return 8;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public long ReadLong()
{
var converter = new ValueConverter
{
Byte0 = _data[_offset],
Byte1 = _data[_offset + 1],
Byte2 = _data[_offset + 2],
Byte3 = _data[_offset + 3],
Byte4 = _data[_offset + 4],
Byte5 = _data[_offset + 5],
Byte6 = _data[_offset + 6],
Byte7 = _data[_offset + 7],
};
_offset += 8;
return converter.Long;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public int WriteFloat(float value)
{
var converter = new ValueConverter() {Float = value};
WriteInt(converter.Int);
return 4;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -112,15 +179,16 @@ namespace Ragon.Common
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteString(string value)
public int WriteString(string value)
{
var stringRaw = Encoding.UTF8.GetBytes(value).AsSpan();
ResizeIfNeed(2 + stringRaw.Length);
var rawData = Encoding.UTF8.GetBytes(value).AsSpan();
ResizeIfNeed(2 + rawData.Length);
WriteUShort((ushort) rawData.Length);
var data = _data.AsSpan().Slice(_offset, rawData.Length);
rawData.CopyTo(data);
_offset += rawData.Length;
WriteUShort((ushort) stringRaw.Length);
var data = _data.AsSpan().Slice(_offset, stringRaw.Length);
stringRaw.CopyTo(data);
_offset += stringRaw.Length;
return rawData.Length + 2;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -144,7 +212,7 @@ namespace Ragon.Common
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteData(ref ReadOnlySpan<byte> payload)
public int WriteData(ref ReadOnlySpan<byte> payload)
{
ResizeIfNeed(payload.Length);
@@ -153,6 +221,7 @@ namespace Ragon.Common
payload.CopyTo(payloadData);
_offset += payload.Length;
return payload.Length;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -168,12 +237,14 @@ namespace Ragon.Common
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteOperation(RagonOperation ragonOperation)
public int WriteOperation(RagonOperation ragonOperation)
{
ResizeIfNeed(1);
_data[_offset] = (byte) ragonOperation;
_offset += 1;
return 1;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -185,13 +256,24 @@ namespace Ragon.Common
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteUShort(ushort value)
public int WriteUShort(ushort value)
{
ResizeIfNeed(2);
_data[_offset] = (byte) (value & 0x00FF);
_data[_offset + 1] = (byte) ((value & 0xFF00) >> 8);
_offset += 2;
return 2;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public int WriteUShort(ushort value, int offset)
{
ResizeIfNeed(2);
_data[offset] = (byte) (value & 0x00FF);
_data[offset + 1] = (byte) ((value & 0xFF00) >> 8);
return 2;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -228,7 +310,7 @@ namespace Ragon.Common
{
Clear();
ResizeIfNeed(data.Length);
Buffer.BlockCopy(data, 0, _data, 0, _offset);
Buffer.BlockCopy(data, 0, _data, 0, data.Length);
_size = data.Length;
}
+3 -3
View File
@@ -2,8 +2,8 @@ namespace Ragon.Common
{
public enum RagonTarget: byte
{
OWNER,
EXCEPT_OWNER,
ALL,
Owner,
ExceptOwner,
All,
}
}
@@ -12,7 +12,7 @@ public class AuthorizationProviderByKey: IAuthorizationProvider
_configuration = configuration;
}
public async Task OnAuthorizationRequest(string key, string name, byte protocol, byte[] additionalData, Action<string, string> accept, Action<uint> reject)
public async Task OnAuthorizationRequest(string key, string name, byte[] additionalData, Action<string, string> accept, Action<uint> reject)
{
if (key == _configuration.Key)
{
@@ -1,4 +1,5 @@
using Ragon.Core;
using NLog.Fluent;
using Ragon.Core;
namespace Game.Source
{
@@ -17,12 +18,22 @@ namespace Game.Source
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)
{
// _logger.Info($"Player({player.PlayerName}) left from Room({GameRoom.Id})");
// Logger.Info($"Player({player.PlayerName}) left from Room({Room.Id})");
}
public override void OnEntityCreated(Player player, Entity entity)
{
// Logger.Info($"Player({player.PlayerName}) create entity {entity.EntityId}:{entity.EntityType}");
}
public override void OnEntityDestroyed(Player player, Entity entity)
{
// Logger.Info($"Player({player.PlayerName}) destroy entity {entity.EntityId}:{entity.EntityType}");
}
}
}
+1
View File
@@ -1,5 +1,6 @@
{
"key": "defaultkey",
"protocol": "1.0.0",
"statisticsInterval": 5,
"sendRate": 30,
"port": 4444,
+1 -1
View File
@@ -101,7 +101,7 @@ namespace Stress
simulationClient.InRoom = true;
ragonSerializer.Clear();
ragonSerializer.WriteOperation(RagonOperation.SCENE_IS_LOADED);
ragonSerializer.WriteOperation(RagonOperation.SCENE_LOADED);
var sendData = ragonSerializer.ToArray();
var packet = new Packet();
@@ -1,13 +1,13 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using NLog.Targets;
using NLog;
using Ragon.Common;
namespace Ragon.Core;
public class AuthorizationManager : IAuthorizationManager
{
private Logger _logger = LogManager.GetCurrentClassLogger();
private IAuthorizationProvider _provider;
private IGameThread _gameThread;
private Lobby _lobby;
@@ -25,11 +25,17 @@ public class AuthorizationManager : IAuthorizationManager
_playersByPeers = new Dictionary<uint, Player>();
}
public void OnAuthorization(uint peerId, string key, string name, byte protocol)
public void OnAuthorization(uint peerId, string key, string name, ReadOnlySpan<byte> additionalData)
{
if (_playersByPeers.ContainsKey(peerId))
{
_logger.Warn($"Connection already authorized {peerId}");
return;
}
var dispatcher = _gameThread.ThreadDispatcher;
_provider.OnAuthorizationRequest(key, name, protocol, Array.Empty<byte>(),
_provider.OnAuthorizationRequest(key, name, additionalData.ToArray(),
(playerId, playerName) => { dispatcher.Dispatch(() => Accepted(peerId, playerId, playerName)); },
(errorCode) => { dispatcher.Dispatch(() => Rejected(peerId, errorCode)); });
}
@@ -48,7 +54,7 @@ public class AuthorizationManager : IAuthorizationManager
PeerId = peerId,
IsLoaded = false,
Entities = new List<Entity>(),
EntitiesIds = new List<int>(),
EntitiesIds = new List<ushort>(),
};
_playersByIds.Add(playerId, player);
@@ -5,5 +5,5 @@ namespace Ragon.Core;
public interface IAuthorizationProvider
{
Task OnAuthorizationRequest(string key, string playerName, byte protocol, byte[] additionalData, Action<string, string> Accept, Action<uint> Reject);
Task OnAuthorizationRequest(string key, string playerName, byte[] additionalData, Action<string, string> Accept, Action<uint> Reject);
}
+2
View File
@@ -1,4 +1,5 @@
using System;
using System.Diagnostics;
using System.IO;
using NLog;
@@ -16,5 +17,6 @@ namespace Ragon.Core
var app = new Application(factory, configuration);
return app;
}
}
}
@@ -6,6 +6,7 @@ namespace Ragon.Core
public struct Configuration
{
public string Key;
public string Protocol;
public int StatisticsInterval;
public ushort SendRate;
public ushort Port;
@@ -9,7 +9,7 @@ namespace Ragon.Core
public static class ConfigurationLoader
{
private static readonly Logger _logger = LogManager.GetCurrentClassLogger();
private static readonly string _serverVersion = "1.0.9-rc";
private static readonly string _serverVersion = "1.0.21-rc";
private static void CopyrightInfo()
{
+58 -9
View File
@@ -1,26 +1,75 @@
using System;
using System.Collections.Generic;
using Ragon.Common;
namespace Ragon.Core;
public class Entity
{
private static int _idGenerator = 0;
public int EntityId { get; private set; }
public int StaticId { get; private set; }
public uint OwnerId { get; private set; }
private static ushort _idGenerator = 0;
public ushort EntityId { get; private set; }
public ushort StaticId { get; private set; }
public ushort EntityType { get; private set; }
public ushort OwnerId { get; private set; }
public RagonAuthority Authority { get; private set; }
public EntityState State { get; private set; }
public EntityState Payload { get; private set; }
public EntityProperty[] Properties { get; private set; }
public List<EntityEvent> BufferedEvents = new List<EntityEvent>();
public Entity(uint ownerId, ushort entityType, int staticId, RagonAuthority stateAuthority, RagonAuthority eventAuthority)
public byte[] Payload { get; set; }
public Entity(ushort ownerId, ushort entityType, ushort staticId, RagonAuthority eventAuthority, int props)
{
OwnerId = ownerId;
StaticId = staticId;
EntityType = entityType;
EntityId = _idGenerator++;
State = new EntityState(stateAuthority);
Payload = new EntityState(stateAuthority);
Properties = new EntityProperty[props];
Payload = Array.Empty<byte>();
Authority = eventAuthority;
}
public void UpdateOwner(ushort ownerId) => OwnerId = ownerId;
public void ReplicateProperties(RagonSerializer serializer)
{
serializer.WriteUShort(EntityId);
for (int propertyIndex = 0; propertyIndex < Properties.Length; 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 Snapshot(RagonSerializer serializer)
{
for (int propertyIndex = 0; propertyIndex < Properties.Length; 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);
}
}
}
}
+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; }
}
+41
View File
@@ -0,0 +1,41 @@
using System;
using Ragon.Common;
namespace Ragon.Core;
public class EntityProperty
{
public int Size { get; set; }
public int Capacity { get; set; }
public bool IsDirty { get; private set; }
public bool IsFixed { get; private set; }
private byte[] _data;
public EntityProperty(int size, bool isFixed)
{
Capacity = 512;
Size = size;
IsFixed = isFixed;
IsDirty = true;
_data = new byte[Capacity];
}
public ReadOnlySpan<byte> Read()
{
var dataSpan = _data.AsSpan();
return dataSpan.Slice(0, Size);
}
public void Write(ref ReadOnlySpan<byte> src)
{
src.CopyTo(_data);
IsDirty = true;
}
public void Clear()
{
IsDirty = false;
}
}
-38
View File
@@ -1,38 +0,0 @@
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 int Size => _size;
private int _size = 0;
private byte[] _data = new byte[2048];
public EntityState(RagonAuthority ragonAuthority)
{
Authority = ragonAuthority;
isDirty = false;
}
public ReadOnlySpan<byte> Read()
{
return _data.AsSpan().Slice(0, _size);
}
public void Write(ref ReadOnlySpan<byte> src)
{
src.CopyTo(_data);
_size = src.Length;
isDirty = true;
}
public void Clear()
{
isDirty = false;
}
}
+354 -269
View File
@@ -1,16 +1,20 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using NLog;
using Ragon.Common;
namespace Ragon.Core
{
// TODO: Replace all serialization and packing into dedicated structures
// TODO: Split class on different managers
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; }
@@ -28,7 +32,10 @@ namespace Ragon.Core
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>();
private List<uint> _awaitingPeers = new List<uint>();
public GameRoom(IGameThread gameThread, PluginBase pluginBase, string roomId, string map, int min, int max)
{
@@ -44,51 +51,29 @@ namespace Ragon.Core
_plugin.Attach(this);
}
public void Joined(Player player, ReadOnlySpan<byte> payload)
public void AddPlayer(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);
}
_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);
_serializer.WriteString(Map);
var sendData = _serializer.ToArray();
Send(player.PeerId, sendData, DeliveryType.Reliable);
}
public void Leave(uint peerId)
public void RemovePlayer(uint peerId)
{
if (_players.Remove(peerId, out var player))
{
@@ -102,11 +87,34 @@ namespace Ragon.Core
_serializer.WriteOperation(RagonOperation.PLAYER_LEAVED);
_serializer.WriteString(player.Id);
_serializer.WriteUShort((ushort) player.EntitiesIds.Count);
foreach (var entityId in player.EntitiesIds)
var entitiesToDelete = player.Entities.Where(e => e.StaticId == 0).ToArray();
_serializer.WriteUShort((ushort) entitiesToDelete.Length);
foreach (var entity in entitiesToDelete)
{
_serializer.WriteInt(entityId);
_entities.Remove(entityId);
_serializer.WriteUShort(entity.EntityId);
_entities.Remove(entity.EntityId);
}
var sendData = _serializer.ToArray();
Broadcast(_readyPlayers, sendData);
}
if (_allPlayers.Length > 0 && player.PeerId == _owner)
{
var nextOwnerId = _allPlayers[0];
_owner = nextOwnerId;
var nextOwner = _players[nextOwnerId];
var entitiesToUpdate = player.Entities.Where(e => e.StaticId > 0).ToArray();
_serializer.Clear();
_serializer.WriteOperation(RagonOperation.OWNERSHIP_CHANGED);
_serializer.WriteString(nextOwner.Id);
_serializer.WriteUShort((ushort) entitiesToUpdate.Length);
foreach (var entity in entitiesToUpdate)
{
_serializer.WriteUShort(entity.EntityId);
entity.UpdateOwner((ushort) nextOwnerId);
}
var sendData = _serializer.ToArray();
@@ -117,82 +125,13 @@ namespace Ragon.Core
}
}
public void ProcessEvent(uint peerId, RagonOperation operation, ReadOnlySpan<byte> payloadRawData)
// TODO: Move this processing to specialized classes with structures
public void ProcessEvent(ushort peerId, RagonOperation operation, ReadOnlySpan<byte> payloadRawData)
{
_serializer.Clear();
_serializer.FromSpan(ref payloadRawData);
switch (operation)
{
case RagonOperation.REPLICATE_ENTITY_STATE:
{
var entityId = _serializer.ReadInt();
if (_entities.TryGetValue(entityId, out var ent))
{
if (ent.State.Authority == RagonAuthority.OWNER_ONLY && ent.OwnerId != peerId)
return;
var entityStateData = _serializer.ReadData(_serializer.Size);
ent.State.Write(ref entityStateData);
}
break;
}
case RagonOperation.REPLICATE_ENTITY_EVENT:
{
var evntId = _serializer.ReadUShort();
var evntMode = _serializer.ReadByte();
var targetMode = (RagonTarget) _serializer.ReadByte();
var entityId = _serializer.ReadInt();
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.WriteInt(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();
@@ -208,112 +147,239 @@ namespace Ragon.Core
Broadcast(_allPlayers, sendData, DeliveryType.Reliable);
break;
}
case RagonOperation.REPLICATE_EVENT:
case RagonOperation.SCENE_LOADED:
{
var evntId = _serializer.ReadUShort();
var evntMode = _serializer.ReadByte();
var player = _players[peerId];
if (peerId == _owner)
{
var statics = _serializer.ReadUShort();
for (var staticIndex = 0; staticIndex < statics; staticIndex++)
{
var entityType = _serializer.ReadUShort();
var entityAuthority = (RagonAuthority) _serializer.ReadByte();
var staticId = _serializer.ReadUShort();
var propertiesCount = _serializer.ReadUShort();
var entity = new Entity(peerId, entityType, staticId, entityAuthority, propertiesCount);
for (var propertyIndex = 0; propertyIndex < propertiesCount; propertyIndex++)
{
var propertyType = _serializer.ReadBool();
var propertySize = _serializer.ReadUShort();
entity.Properties[propertyIndex] = new EntityProperty(propertySize, propertyType);
}
player.Entities.Add(entity);
player.EntitiesIds.Add(entity.EntityId);
_entities.Add(entity.EntityId, 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");
{
_serializer.Clear();
_serializer.WriteOperation(RagonOperation.PLAYER_JOINED);
_serializer.WriteUShort((ushort) player.PeerId);
_serializer.WriteString(player.Id);
_serializer.WriteString(player.PlayerName);
var sendData = _serializer.ToArray();
var readyPlayersWithExcludedPeer = _readyPlayers.Where(p => p != peerId).ToArray();
Broadcast(readyPlayersWithExcludedPeer, sendData, DeliveryType.Reliable);
}
}
_readyPlayers = _players.Where(p => p.Value.IsLoaded).Select(p => p.Key).ToArray();
foreach (var peer in _awaitingPeers)
{
ReplicateSnapshot(peer);
}
_awaitingPeers.Clear();
}
else if (GetOwner().IsLoaded)
{
_logger.Trace($"[{_owner}][{peerId}] Player {player.Id} restored instantly");
player.IsLoaded = true;
{
_serializer.Clear();
_serializer.WriteOperation(RagonOperation.PLAYER_JOINED);
_serializer.WriteUShort((ushort) player.PeerId);
_serializer.WriteString(player.Id);
_serializer.WriteString(player.PlayerName);
var sendData = _serializer.ToArray();
var readyPlayersWithExcludedPeer = _readyPlayers.Where(p => p != peerId).ToArray();
Broadcast(readyPlayersWithExcludedPeer, sendData, DeliveryType.Reliable);
}
_readyPlayers = _players.Where(p => p.Value.IsLoaded).Select(p => p.Key).ToArray();
_plugin.OnPlayerJoined(player);
ReplicateSnapshot(peerId);
foreach (var (key, value) in _entities)
{
foreach (var bufferedEvent in value.BufferedEvents)
{
_serializer.Clear();
_serializer.WriteOperation(RagonOperation.REPLICATE_ENTITY_EVENT);
_serializer.WriteUShort(bufferedEvent.EventId);
_serializer.WriteUShort(bufferedEvent.PeerId);
_serializer.WriteByte((byte) RagonReplicationMode.Server);
_serializer.WriteUShort(value.EntityId);
ReadOnlySpan<byte> data = bufferedEvent.EventData.AsSpan();
_serializer.WriteData(ref data);
_logger.Trace($"[{peerId}] Restored buffered event {bufferedEvent.EventId}");
var sendData = _serializer.ToArray();
Send(peerId, sendData, DeliveryType.Reliable);
}
}
}
else
{
_logger.Trace($"[{_owner}][{peerId}] Player {player.Id} waiting");
_awaitingPeers.Add(peerId);
}
break;
}
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];
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;
}
}
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 eventId = _serializer.ReadUShort();
var eventMode = (RagonReplicationMode) _serializer.ReadByte();
var targetMode = (RagonTarget) _serializer.ReadByte();
var entityId = _serializer.ReadUShort();
if (!_entities.TryGetValue(entityId, out var ent))
{
_logger.Warn($"Entity not found for event with Id {eventId}");
return;
}
if (ent.Authority == RagonAuthority.OwnerOnly && ent.OwnerId != peerId)
{
_logger.Warn($"Player have not enought authority for event with Id {eventId}");
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, evntId, ref payload))
if (_plugin.InternalHandle(peerId, entityId, eventId, ref payload))
return;
if (eventMode == RagonReplicationMode.Buffered && targetMode != RagonTarget.Owner)
{
var bufferedEvent = new EntityEvent()
{
EventData = payload.ToArray(),
Target = targetMode,
EventId = eventId,
PeerId = peerId,
};
ent.BufferedEvents.Add(bufferedEvent);
}
_serializer.Clear();
_serializer.WriteOperation(RagonOperation.REPLICATE_EVENT);
_serializer.WriteUShort((ushort) peerId);
_serializer.WriteByte(evntMode);
_serializer.WriteUShort(evntId);
_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();
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_STATIC_ENTITY:
{
var entityType = _serializer.ReadUShort();
var staticId = _serializer.ReadUShort();
var stateAuthority = (RagonAuthority) _serializer.ReadByte();
var eventAuthority = (RagonAuthority) _serializer.ReadByte();
var entity = new Entity(peerId, entityType, staticId, stateAuthority, eventAuthority);
{
var entityPayload = _serializer.ReadData(_serializer.Size);
entity.Payload.Write(ref entityPayload);
}
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_STATIC_ENTITY);
_serializer.WriteUShort(entityType);
_serializer.WriteUShort(staticId);
_serializer.WriteByte((byte) stateAuthority);
_serializer.WriteByte((byte) eventAuthority);
_serializer.WriteInt(entity.EntityId);
_serializer.WriteUShort(ownerId);
{
var entityPayload = entity.Payload.Read();
_serializer.WriteData(ref entityPayload);
}
var sendData = _serializer.ToArray();
Broadcast(_readyPlayers, sendData, DeliveryType.Reliable);
SendEvent(ent, targetMode, sendData);
break;
}
case RagonOperation.CREATE_ENTITY:
{
var entityType = _serializer.ReadUShort();
var stateAuthority = (RagonAuthority) _serializer.ReadByte();
var eventAuthority = (RagonAuthority) _serializer.ReadByte();
var entity = new Entity(peerId, entityType, -1, stateAuthority, eventAuthority);
var propertiesCount = _serializer.ReadUShort();
_logger.Trace($"[{peerId}] Create Entity {entityType}");
var entity = new Entity(peerId, entityType, 0, eventAuthority, 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.Write(ref entityPayload);
entity.Payload = entityPayload.ToArray();
}
var player = _players[peerId];
player.Entities.Add(entity);
player.EntitiesIds.Add(entity.EntityId);
var ownerId = (ushort) peerId;
var ownerId = peerId;
_entities.Add(entity.EntityId, entity);
_entitiesAll = _entities.Values.ToArray();
@@ -323,13 +389,12 @@ namespace Ragon.Core
_serializer.Clear();
_serializer.WriteOperation(RagonOperation.CREATE_ENTITY);
_serializer.WriteUShort(entityType);
_serializer.WriteByte((byte) stateAuthority);
_serializer.WriteByte((byte) eventAuthority);
_serializer.WriteInt(entity.EntityId);
_serializer.WriteUShort(entity.EntityId);
_serializer.WriteUShort(ownerId);
{
var entityPayload = entity.Payload.Read();
ReadOnlySpan<byte> entityPayload = entity.Payload.AsSpan();
_serializer.WriteUShort((ushort) entityPayload.Length);
_serializer.WriteData(ref entityPayload);
}
@@ -342,7 +407,7 @@ namespace Ragon.Core
var entityId = _serializer.ReadInt();
if (_entities.TryGetValue(entityId, out var entity))
{
if (entity.Authority == RagonAuthority.OWNER_ONLY && entity.OwnerId != peerId)
if (entity.Authority == RagonAuthority.OwnerOnly && entity.OwnerId != peerId)
return;
var player = _players[peerId];
@@ -359,6 +424,7 @@ namespace Ragon.Core
_serializer.Clear();
_serializer.WriteOperation(RagonOperation.DESTROY_ENTITY);
_serializer.WriteInt(entityId);
_serializer.WriteUShort((ushort) destroyPayload.Length);
_serializer.WriteData(ref destroyPayload);
var sendData = _serializer.ToArray();
@@ -367,64 +433,6 @@ namespace Ragon.Core
break;
}
case RagonOperation.SCENE_IS_LOADED:
{
_serializer.Clear();
_serializer.WriteOperation(RagonOperation.SNAPSHOT);
_serializer.WriteInt(_allPlayers.Length);
foreach (var playerPeerId in _allPlayers)
{
_serializer.WriteString(_players[playerPeerId].Id);
_serializer.WriteUShort((ushort) playerPeerId);
_serializer.WriteString(_players[playerPeerId].PlayerName);
}
var dynamicCount = _entitiesAll.Where(e => e.StaticId == -1).ToArray();
_serializer.WriteInt(dynamicCount.Length);
foreach (var entity in dynamicCount)
{
if (entity.StaticId != -1) continue;
var payload = entity.Payload.Read();
var state = entity.State.Read();
_serializer.WriteInt(entity.EntityId);
_serializer.WriteByte((byte) entity.State.Authority);
_serializer.WriteByte((byte) entity.Authority);
_serializer.WriteUShort(entity.EntityType);
_serializer.WriteUShort((ushort) entity.OwnerId);
_serializer.WriteUShort((ushort) payload.Length);
_serializer.WriteData(ref payload);
_serializer.WriteData(ref state);
}
var staticCount = _entitiesAll.Where(e => e.StaticId != -1).ToArray();
_serializer.WriteInt(staticCount.Length);
foreach (var entity in staticCount)
{
var payload = entity.Payload.Read();
var state = entity.State.Read();
_serializer.WriteInt(entity.EntityId);
_serializer.WriteUShort((ushort) entity.StaticId);
_serializer.WriteByte((byte) entity.State.Authority);
_serializer.WriteByte((byte) entity.Authority);
_serializer.WriteUShort(entity.EntityType);
_serializer.WriteUShort((ushort) entity.OwnerId);
_serializer.WriteUShort((ushort) payload.Length);
_serializer.WriteData(ref payload);
_serializer.WriteData(ref state);
}
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;
}
}
}
@@ -432,22 +440,76 @@ namespace Ragon.Core
{
_scheduler.Tick(deltaTime);
foreach (var entity in _entitiesAll)
ReplicateProperties();
}
// TODO: Move this to specialized class
void ReplicateSnapshot(uint peerId)
{
_serializer.Clear();
_serializer.WriteOperation(RagonOperation.SNAPSHOT);
_serializer.WriteUShort((ushort) _readyPlayers.Length);
foreach (var playerPeerId in _readyPlayers)
{
if (entity.State.isDirty)
{
var state = entity.State.Read();
_serializer.WriteUShort((ushort) playerPeerId);
_serializer.WriteString(_players[playerPeerId].Id);
_serializer.WriteString(_players[playerPeerId].PlayerName);
}
_serializer.Clear();
_serializer.WriteOperation(RagonOperation.REPLICATE_ENTITY_STATE);
_serializer.WriteInt(entity.EntityId);
_serializer.WriteData(ref state);
var dynamicEntities = _entitiesAll.Where(e => e.StaticId == 0).ToArray();
var dynamicEntitiesCount = (ushort) dynamicEntities.Length;
_serializer.WriteUShort(dynamicEntitiesCount);
foreach (var entity in dynamicEntities)
{
ReadOnlySpan<byte> payload = entity.Payload.AsSpan();
var sendData = _serializer.ToArray();
Broadcast(_readyPlayers, sendData, DeliveryType.Unreliable);
_serializer.WriteUShort(entity.EntityType);
_serializer.WriteUShort(entity.EntityId);
_serializer.WriteUShort((ushort) entity.OwnerId);
_serializer.WriteUShort((ushort) payload.Length);
_serializer.WriteData(ref payload);
entity.State.Clear();
}
entity.Snapshot(_serializer);
}
var staticEntities = _entitiesAll.Where(e => e.StaticId != 0).ToArray();
var staticEntitiesCount = (ushort) staticEntities.Length;
_serializer.WriteUShort(staticEntitiesCount);
foreach (var entity in staticEntities)
{
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);
entity.Snapshot(_serializer);
}
var sendData = _serializer.ToArray();
Send(peerId, sendData, DeliveryType.Reliable);
}
private void ReplicateProperties()
{
var entities = (ushort) _entitiesDirty.Count;
if (entities > 0)
{
_serializer.Clear();
_serializer.WriteOperation(RagonOperation.REPLICATE_ENTITY_STATE);
_serializer.WriteUShort(entities);
foreach (var entity in _entitiesDirty)
entity.ReplicateProperties(_serializer);
_entitiesDirty.Clear();
_entitiesDirtySet.Clear();
var sendData = _serializer.ToArray();
Broadcast(_readyPlayers, sendData);
}
}
@@ -475,19 +537,42 @@ namespace Ragon.Core
public IScheduler GetScheduler() => _scheduler;
public void Send(uint peerId, byte[] rawData, DeliveryType deliveryType = DeliveryType.Unreliable)
// TODO: Move this to Entity Event Manager
public void SendEvent(Entity ent, RagonTarget targetMode, byte[] sendData)
{
switch (targetMode)
{
case RagonTarget.Owner:
{
Send(ent.OwnerId, sendData, DeliveryType.Reliable);
break;
}
case RagonTarget.ExceptOwner:
{
_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;
}
}
}
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)
{
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)
{
public void Broadcast(byte[] rawData, DeliveryType deliveryType = DeliveryType.Unreliable) =>
_gameThread.Server.Broadcast(_allPlayers, rawData, deliveryType);
}
}
}
+34 -36
View File
@@ -1,5 +1,4 @@
using System;
using System.Diagnostics;
using System.Threading;
using Ragon.Common;
using ENet;
@@ -11,36 +10,33 @@ namespace Ragon.Core
{
private readonly RoomManager _roomManager;
private readonly Thread _thread;
private readonly Stopwatch _gameLoopTimer;
private readonly Lobby _lobby;
private readonly ISocketServer _server;
private readonly IDispatcherInternal _dispatcherInternal;
private readonly IDispatcher _dispatcher;
private readonly ILogger _logger = LogManager.GetCurrentClassLogger();
private readonly float _deltaTime = 0.0f;
private readonly Stopwatch _statisticsTimer;
private readonly Configuration _configuration;
private readonly IDispatcherInternal _dispatcherInternal;
public IDispatcher ThreadDispatcher { get; private set; }
public ISocketServer Server { get; private set; }
public ISocketServer Server => _server;
public IDispatcher ThreadDispatcher => _dispatcher;
public GameThread(PluginFactory factory, Configuration configuration)
{
var authorizationProvider = factory.CreateAuthorizationProvider(configuration);
_configuration = configuration;
var authorizationProvider = factory.CreateAuthorizationProvider(configuration);
var dispatcher = new Dispatcher();
_dispatcherInternal = dispatcher;
_dispatcher = dispatcher;
ThreadDispatcher = dispatcher;
Server = new ENetServer(this);
_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();
_thread = new Thread(Execute);
_thread.Name = "Game Thread";
_thread.IsBackground = true;
@@ -48,19 +44,33 @@ namespace Ragon.Core
public void Start()
{
Server.Start(_configuration.Port, _configuration.MaxConnections);
var strings = _configuration.Protocol.Split(".");
if (strings.Length < 3)
{
_logger.Error("Wrong protocol passed to connect method");
return;
}
_gameLoopTimer.Start();
_statisticsTimer.Start();
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);
_thread.Start();
}
public void Stop()
{
Server.Stop();
_gameLoopTimer.Stop();
_statisticsTimer.Stop();
_server.Stop();
_thread.Interrupt();
}
@@ -68,23 +78,11 @@ namespace Ragon.Core
{
while (true)
{
Server.Process();
_server.Process();
_dispatcherInternal.Process();
_roomManager.Tick(_deltaTime);
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)
{
_logger.Trace($"Rooms: {_roomManager.Rooms.Count} Clients: {_roomManager.RoomsBySocket.Count}");
_statisticsTimer.Restart();
}
Thread.Sleep((int) _deltaTime);
}
}
@@ -104,7 +102,7 @@ namespace Ragon.Core
{
try
{
var peerId = evnt.Peer.ID;
var peerId = (ushort) evnt.Peer.ID;
var dataRaw = new byte[evnt.Packet.Length];
evnt.Packet.CopyTo(dataRaw);
-6
View File
@@ -1,6 +0,0 @@
namespace Ragon.Core;
public interface ILobby
{
}
+11 -5
View File
@@ -5,7 +5,7 @@ using Ragon.Common;
namespace Ragon.Core;
public class Lobby : ILobby
public class Lobby
{
private readonly ILogger _logger = LogManager.GetCurrentClassLogger();
private readonly RagonSerializer _serializer;
@@ -23,21 +23,27 @@ public class Lobby : ILobby
_authorizationManager = new AuthorizationManager(provider, gameThread, this, _serializer);
}
public void ProcessEvent(uint peerId, RagonOperation op, ReadOnlySpan<byte> payload)
public void ProcessEvent(ushort peerId, RagonOperation op, ReadOnlySpan<byte> payload)
{
_serializer.Clear();
_serializer.FromSpan(ref payload);
var player = _authorizationManager.GetPlayer(peerId);
if (op == RagonOperation.AUTHORIZE)
{
if (player != null)
{
_logger.Warn("Player already authorized");
return;
}
var key = _serializer.ReadString();
var playerName = _serializer.ReadString();
var protocol = _serializer.ReadByte();
_authorizationManager.OnAuthorization(peerId, key, playerName, protocol);
var additionalData = _serializer.ReadData(_serializer.Size);
_authorizationManager.OnAuthorization(peerId, key, playerName, additionalData);
return;
}
var player = _authorizationManager.GetPlayer(peerId);
if (player == null)
{
_logger.Warn($"Peer not authorized {peerId} trying to {op}");
+2 -2
View File
@@ -6,11 +6,11 @@ namespace Ragon.Core
public class Player
{
public string Id { get; set; }
public uint PeerId { get; set; }
public string PlayerName { get; set; }
public uint PeerId { get; set; }
public bool IsLoaded { get; set; }
public List<Entity> Entities;
public List<int> EntitiesIds;
public List<ushort> EntitiesIds;
}
}
+18 -18
View File
@@ -8,21 +8,20 @@ namespace Ragon.Core
public class PluginBase
{
private delegate void SubscribeDelegate(Player player, ref ReadOnlySpan<byte> data);
private delegate void SubscribeEntityDelegate(Player player, Entity entity, ref ReadOnlySpan<byte> data);
private Dictionary<ushort, SubscribeDelegate> _globalEvents = new();
private Dictionary<int, Dictionary<ushort, SubscribeEntityDelegate>> _entityEvents = new();
private readonly RagonSerializer _serializer = new();
protected IGameRoom GameRoom { get; private set; } = null!;
protected IGameRoom Room { get; private set; } = null!;
protected ILogger Logger = null!;
public void Attach(GameRoom gameRoom)
{
Logger = LogManager.GetLogger($"Plugin<{GetType().Name}>");
GameRoom = gameRoom;
Room = gameRoom;
_globalEvents.Clear();
_entityEvents.Clear();
@@ -34,7 +33,7 @@ namespace Ragon.Core
_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))
{
@@ -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))
{
@@ -69,7 +68,7 @@ namespace Ragon.Core
_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))
{
@@ -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))
{
@@ -150,8 +149,9 @@ namespace Ragon.Core
if (!_entityEvents[entityId].ContainsKey(evntCode))
return false;
var player = GameRoom.GetPlayerById(peerId);
var entity = GameRoom.GetEntityById(entityId);
var player = Room.GetPlayerById(peerId);
var entity = Room.GetEntityById(entityId);
_entityEvents[entityId][evntCode].Invoke(player, entity, ref payload);
return true;
@@ -161,7 +161,7 @@ namespace Ragon.Core
{
if (_globalEvents.ContainsKey(evntCode))
{
var player = GameRoom.GetPlayerById(peerId);
var player = Room.GetPlayerById(peerId);
_globalEvents[evntCode].Invoke(player, ref payload);
return true;
}
@@ -169,7 +169,7 @@ namespace Ragon.Core
return false;
}
public void SendEvent(Player player, uint eventCode, IRagonSerializable payload)
public void ReplicateEvent(Player player, uint eventCode, IRagonSerializable payload)
{
_serializer.Clear();
_serializer.WriteOperation(RagonOperation.REPLICATE_EVENT);
@@ -177,10 +177,10 @@ namespace Ragon.Core
payload.Serialize(_serializer);
var sendData = _serializer.ToArray();
GameRoom.Send(player.PeerId, sendData);
Room.Send(player.PeerId, sendData);
}
public void BroadcastEvent(ushort eventCode, IRagonSerializable payload)
public void ReplicateEvent(ushort eventCode, IRagonSerializable payload)
{
_serializer.Clear();
_serializer.WriteOperation(RagonOperation.REPLICATE_EVENT);
@@ -188,10 +188,10 @@ namespace Ragon.Core
payload.Serialize(_serializer);
var sendData = _serializer.ToArray();
GameRoom.Broadcast(sendData, DeliveryType.Reliable);
Room.Broadcast(sendData, DeliveryType.Reliable);
}
public void SendEntityEvent(Player player, Entity entity, IRagonSerializable payload)
public void ReplicateEntityEvent(Player player, Entity entity, IRagonSerializable payload)
{
_serializer.Clear();
_serializer.WriteOperation(RagonOperation.REPLICATE_ENTITY_EVENT);
@@ -200,10 +200,10 @@ namespace Ragon.Core
payload.Serialize(_serializer);
var sendData = _serializer.ToArray();
GameRoom.Send(player.PeerId, sendData, DeliveryType.Reliable);
Room.Send(player.PeerId, sendData, DeliveryType.Reliable);
}
public void BroadcastEntityEvent(Entity entity, IRagonSerializable payload)
public void ReplicateEntityEvent(Entity entity, IRagonSerializable payload)
{
_serializer.Clear();
_serializer.WriteOperation(RagonOperation.REPLICATE_ENTITY_EVENT);
@@ -212,7 +212,7 @@ namespace Ragon.Core
payload.Serialize(_serializer);
var sendData = _serializer.ToArray();
GameRoom.Broadcast(sendData);
Room.Broadcast(sendData);
}
+5 -5
View File
@@ -34,7 +34,7 @@ public class RoomManager
{
if (existRoom.Id == roomId && existRoom.PlayersCount < existRoom.PlayersMax)
{
existRoom.Joined(player, payload);
existRoom.AddPlayer(player, payload);
_roomsBySocket.Add(player.PeerId, existRoom);
return;
}
@@ -55,7 +55,7 @@ public class RoomManager
throw new NullReferenceException($"Plugin for map {map} is null");
var room = new GameRoom(_gameThread, plugin, roomId, map, min, max);
room.Joined(creator, payload);
room.AddPlayer(creator, payload);
room.Start();
_roomsBySocket.Add(creator.PeerId, room);
@@ -76,7 +76,7 @@ public class RoomManager
{
_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);
return;
}
@@ -90,7 +90,7 @@ public class RoomManager
throw new NullReferenceException($"Plugin for map {map} is null");
var room = new GameRoom(_gameThread, plugin, roomId, map, min, max);
room.Joined(player, payload);
room.AddPlayer(player, payload);
room.Start();
_roomsBySocket.Add(player.PeerId, room);
@@ -102,7 +102,7 @@ public class RoomManager
if (_roomsBySocket.Remove(player.PeerId, out var room))
{
_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)
{
_logger.Trace($"Room with Id {room.Id} destroyed");
+27 -20
View File
@@ -1,46 +1,42 @@
using System;
using System.Diagnostics;
using System.Timers;
using ENet;
using NLog;
namespace Ragon.Core
{
public enum Status
{
Stopped,
Listening,
Disconnecting,
Connecting,
Assigning,
Connected
}
public class ENetServer : ISocketServer
{
public Status Status { get; private set; }
private ILogger _logger = LogManager.GetCurrentClassLogger();
private Host _host;
private uint _protocol;
private Address _address;
private Event _netEvent;
private Peer[] _peers;
private IHandler _handler;
private Stopwatch _timer;
public ENetServer(IHandler handler)
{
_handler = handler;
_timer = Stopwatch.StartNew();
_peers = Array.Empty<Peer>();
_host = new Host();
}
public void Start(ushort port, int connections)
public void Start(ushort port, int connections, uint protocol)
{
_address = default;
_address.Port = port;
_peers = new Peer[connections];
_host = new Host();
_protocol = protocol;
_host.Create(_address, connections, 2, 0, 0, 1024 * 1024);
Status = Status.Listening;
var protocolDecoded = (protocol >> 16 & 0xFF) + "." + (protocol >> 8 & 0xFF) + "." + (protocol & 0xFF);
_logger.Info($"Network listening on {port}");
_logger.Info($"Protocol: {protocolDecoded}");
}
public void Broadcast(uint[] peersIds, byte[] data, DeliveryType type)
@@ -57,7 +53,7 @@ namespace Ragon.Core
else if (type == DeliveryType.Unreliable)
{
channel = 1;
packetFlags = PacketFlags.None;
packetFlags = PacketFlags.UnreliableFragmented;
}
newPacket.Create(data, data.Length, packetFlags);
@@ -98,7 +94,7 @@ namespace Ragon.Core
{
if (_host.CheckEvents(out _netEvent) <= 0)
{
if (_host.Service(15, out _netEvent) <= 0)
if (_host.Service(0, out _netEvent) <= 0)
break;
polled = true;
@@ -107,11 +103,17 @@ namespace Ragon.Core
switch (_netEvent.Type)
{
case EventType.None:
Console.WriteLine("None event");
{
_logger.Trace("None event");
break;
}
case EventType.Connect:
{
// if (IsValidProtocol(_netEvent.Data))
// {
// _logger.Warn("Mismatched protocol, close connection");
// break;
// }
_peers[_netEvent.Peer.ID] = _netEvent.Peer;
_handler.OnEvent(_netEvent);
break;
@@ -140,5 +142,10 @@ namespace Ragon.Core
{
_host?.Dispose();
}
private bool IsValidProtocol(uint protocol)
{
return protocol == _protocol;
}
}
}
+1 -1
View File
@@ -2,7 +2,7 @@ namespace Ragon.Core;
public interface ISocketServer
{
public void Start(ushort port, int connections);
public void Start(ushort port, int connections, uint protocol);
public void Process();
public void Stop();
public void Send(uint peerId, byte[] data, DeliveryType type);
+2 -2
View File
@@ -1,7 +1,7 @@
MIT License
Copyright (c) 2022 Eduard Kargin (theedison4@gmail.com)
MIT License:
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights