Compare commits

..

28 Commits

Author SHA1 Message Date
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
27 changed files with 700 additions and 445 deletions
+1
View File
@@ -2,6 +2,7 @@ namespace Ragon.Common
{ {
public enum RagonAuthority: byte public enum RagonAuthority: byte
{ {
NONE,
OWNER_ONLY, OWNER_ONLY,
ALL, ALL,
} }
+1 -3
View File
@@ -15,15 +15,13 @@ namespace Ragon.Common
JOIN_FAILED, JOIN_FAILED,
LOAD_SCENE, LOAD_SCENE,
SCENE_IS_LOADED, SCENE_LOADED,
PLAYER_JOINED, PLAYER_JOINED,
PLAYER_LEAVED, PLAYER_LEAVED,
CREATE_ENTITY, CREATE_ENTITY,
CREATE_STATIC_ENTITY,
DESTROY_ENTITY, DESTROY_ENTITY,
SNAPSHOT, SNAPSHOT,
REPLICATE_ENTITY_STATE, REPLICATE_ENTITY_STATE,
@@ -0,0 +1,10 @@
namespace Ragon.Common
{
public enum RagonReplicationMode: byte
{
LOCAL,
SERVER,
LOCAL_AND_SERVER,
BUFFERED,
}
}
+100 -18
View File
@@ -11,10 +11,15 @@ namespace Ragon.Common
{ {
[FieldOffset(0)] public int Int; [FieldOffset(0)] public int Int;
[FieldOffset(0)] public float Float; [FieldOffset(0)] public float Float;
[FieldOffset(0)] public long Long;
[FieldOffset(0)] public byte Byte0; [FieldOffset(0)] public byte Byte0;
[FieldOffset(1)] public byte Byte1; [FieldOffset(1)] public byte Byte1;
[FieldOffset(2)] public byte Byte2; [FieldOffset(2)] public byte Byte2;
[FieldOffset(3)] public byte Byte3; [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 public class RagonSerializer
@@ -22,6 +27,7 @@ namespace Ragon.Common
private byte[] _data; private byte[] _data;
private int _offset; private int _offset;
private int _size; private int _size;
public int Lenght => _offset; public int Lenght => _offset;
public int Size => _size - _offset; public int Size => _size - _offset;
@@ -40,12 +46,18 @@ namespace Ragon.Common
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [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); ResizeIfNeed(1);
_data[_offset] = value; _data[_offset] = value;
_offset += 1; _offset += 1;
return 1;
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -57,12 +69,12 @@ namespace Ragon.Common
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteBool(bool value) public int WriteBool(bool value)
{ {
ResizeIfNeed(1); ResizeIfNeed(1);
_data[_offset] = value ? (byte) 1 : (byte) 0; _data[_offset] = value ? (byte) 1 : (byte) 0;
_offset += 1; _offset += 1;
return 1;
} }
@@ -76,7 +88,7 @@ namespace Ragon.Common
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteInt(int value) public int WriteInt(int value)
{ {
ResizeIfNeed(4); ResizeIfNeed(4);
var converter = new ValueConverter() {Int = value}; var converter = new ValueConverter() {Int = value};
@@ -85,21 +97,76 @@ namespace Ragon.Common
_data[_offset + 2] = converter.Byte2; _data[_offset + 2] = converter.Byte2;
_data[_offset + 3] = converter.Byte3; _data[_offset + 3] = converter.Byte3;
_offset += 4; _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)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public int ReadInt() 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; _offset += 4;
return converter.Int; return converter.Int;
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [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}; var converter = new ValueConverter() {Float = value};
WriteInt(converter.Int); WriteInt(converter.Int);
return 4;
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -112,15 +179,16 @@ namespace Ragon.Common
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteString(string value) public int WriteString(string value)
{ {
var stringRaw = Encoding.UTF8.GetBytes(value).AsSpan(); var rawData = Encoding.UTF8.GetBytes(value).AsSpan();
ResizeIfNeed(2 + stringRaw.Length); 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); return rawData.Length + 2;
var data = _data.AsSpan().Slice(_offset, stringRaw.Length);
stringRaw.CopyTo(data);
_offset += stringRaw.Length;
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -144,7 +212,7 @@ namespace Ragon.Common
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteData(ref ReadOnlySpan<byte> payload) public int WriteData(ref ReadOnlySpan<byte> payload)
{ {
ResizeIfNeed(payload.Length); ResizeIfNeed(payload.Length);
@@ -153,6 +221,7 @@ namespace Ragon.Common
payload.CopyTo(payloadData); payload.CopyTo(payloadData);
_offset += payload.Length; _offset += payload.Length;
return payload.Length;
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -168,12 +237,14 @@ namespace Ragon.Common
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteOperation(RagonOperation ragonOperation) public int WriteOperation(RagonOperation ragonOperation)
{ {
ResizeIfNeed(1); ResizeIfNeed(1);
_data[_offset] = (byte) ragonOperation; _data[_offset] = (byte) ragonOperation;
_offset += 1; _offset += 1;
return 1;
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -185,13 +256,24 @@ namespace Ragon.Common
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteUShort(ushort value) public int WriteUShort(ushort value)
{ {
ResizeIfNeed(2); ResizeIfNeed(2);
_data[_offset] = (byte) (value & 0x00FF); _data[_offset] = (byte) (value & 0x00FF);
_data[_offset + 1] = (byte) ((value & 0xFF00) >> 8); _data[_offset + 1] = (byte) ((value & 0xFF00) >> 8);
_offset += 2; _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)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -228,7 +310,7 @@ namespace Ragon.Common
{ {
Clear(); Clear();
ResizeIfNeed(data.Length); ResizeIfNeed(data.Length);
Buffer.BlockCopy(data, 0, _data, 0, _offset); Buffer.BlockCopy(data, 0, _data, 0, data.Length);
_size = data.Length; _size = data.Length;
} }
@@ -12,7 +12,7 @@ public class AuthorizationProviderByKey: IAuthorizationProvider
_configuration = configuration; _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) if (key == _configuration.Key)
{ {
@@ -1,4 +1,5 @@
using Ragon.Core; using NLog.Fluent;
using Ragon.Core;
namespace Game.Source namespace Game.Source
{ {
@@ -17,12 +18,22 @@ namespace Game.Source
public override void OnPlayerJoined(Player player) 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) 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", "key": "defaultkey",
"protocol": "1.0.0",
"statisticsInterval": 5, "statisticsInterval": 5,
"sendRate": 30, "sendRate": 30,
"port": 4444, "port": 4444,
+1 -1
View File
@@ -101,7 +101,7 @@ namespace Stress
simulationClient.InRoom = true; simulationClient.InRoom = true;
ragonSerializer.Clear(); ragonSerializer.Clear();
ragonSerializer.WriteOperation(RagonOperation.SCENE_IS_LOADED); ragonSerializer.WriteOperation(RagonOperation.SCENE_LOADED);
var sendData = ragonSerializer.ToArray(); var sendData = ragonSerializer.ToArray();
var packet = new Packet(); var packet = new Packet();
@@ -1,13 +1,13 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using NLog;
using NLog.Targets;
using Ragon.Common; using Ragon.Common;
namespace Ragon.Core; namespace Ragon.Core;
public class AuthorizationManager : IAuthorizationManager public class AuthorizationManager : IAuthorizationManager
{ {
private Logger _logger = LogManager.GetCurrentClassLogger();
private IAuthorizationProvider _provider; private IAuthorizationProvider _provider;
private IGameThread _gameThread; private IGameThread _gameThread;
private Lobby _lobby; private Lobby _lobby;
@@ -25,11 +25,17 @@ public class AuthorizationManager : IAuthorizationManager
_playersByPeers = new Dictionary<uint, Player>(); _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)
{ {
if (_playersByPeers.ContainsKey(peerId))
{
_logger.Warn($"Connection already authorized {peerId}");
return;
}
var dispatcher = _gameThread.ThreadDispatcher; var dispatcher = _gameThread.ThreadDispatcher;
_provider.OnAuthorizationRequest(key, name, protocol, Array.Empty<byte>(), _provider.OnAuthorizationRequest(key, name, Array.Empty<byte>(),
(playerId, playerName) => { dispatcher.Dispatch(() => Accepted(peerId, playerId, playerName)); }, (playerId, playerName) => { dispatcher.Dispatch(() => Accepted(peerId, playerId, playerName)); },
(errorCode) => { dispatcher.Dispatch(() => Rejected(peerId, errorCode)); }); (errorCode) => { dispatcher.Dispatch(() => Rejected(peerId, errorCode)); });
} }
@@ -48,7 +54,7 @@ public class AuthorizationManager : IAuthorizationManager
PeerId = peerId, PeerId = peerId,
IsLoaded = false, IsLoaded = false,
Entities = new List<Entity>(), Entities = new List<Entity>(),
EntitiesIds = new List<int>(), EntitiesIds = new List<ushort>(),
}; };
_playersByIds.Add(playerId, player); _playersByIds.Add(playerId, player);
@@ -5,5 +5,5 @@ namespace Ragon.Core;
public interface IAuthorizationProvider 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;
using System.Diagnostics;
using System.IO; using System.IO;
using NLog; using NLog;
@@ -16,5 +17,6 @@ namespace Ragon.Core
var app = new Application(factory, configuration); var app = new Application(factory, configuration);
return app; return app;
} }
} }
} }
@@ -6,6 +6,7 @@ namespace Ragon.Core
public struct Configuration public struct Configuration
{ {
public string Key; public string Key;
public string Protocol;
public int StatisticsInterval; public int StatisticsInterval;
public ushort SendRate; public ushort SendRate;
public ushort Port; public ushort Port;
@@ -9,7 +9,7 @@ namespace Ragon.Core
public static class ConfigurationLoader public static class ConfigurationLoader
{ {
private static readonly Logger _logger = LogManager.GetCurrentClassLogger(); private static readonly Logger _logger = LogManager.GetCurrentClassLogger();
private static readonly string _serverVersion = "1.0.9-rc"; private static readonly string _serverVersion = "1.0.18-rc";
private static void CopyrightInfo() private static void CopyrightInfo()
{ {
+56 -9
View File
@@ -1,26 +1,73 @@
using System;
using System.Collections.Generic;
using Ragon.Common; using Ragon.Common;
namespace Ragon.Core; namespace Ragon.Core;
public class Entity public class Entity
{ {
private static int _idGenerator = 0; private static ushort _idGenerator = 0;
public int EntityId { get; private set; } public ushort EntityId { get; private set; }
public int StaticId { get; private set; } public ushort StaticId { get; private set; }
public uint OwnerId { get; private set; }
public ushort EntityType { get; private set; } public ushort EntityType { get; private set; }
public ushort OwnerId { get; private set; }
public RagonAuthority Authority { get; private set; } public RagonAuthority Authority { get; private set; }
public EntityState State { get; private set; } public EntityProperty[] Properties { get; private set; }
public EntityState Payload { 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; OwnerId = ownerId;
StaticId = staticId; StaticId = staticId;
EntityType = entityType; EntityType = entityType;
EntityId = _idGenerator++; EntityId = _idGenerator++;
State = new EntityState(stateAuthority); Properties = new EntityProperty[props];
Payload = new EntityState(stateAuthority); Payload = Array.Empty<byte>();
Authority = eventAuthority; Authority = eventAuthority;
} }
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);
}
}
}
} }
+10
View File
@@ -0,0 +1,10 @@
using Ragon.Common;
namespace Ragon.Core;
public class EntityEvent
{
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;
}
}
+331 -246
View File
@@ -1,16 +1,20 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.Linq; using System.Linq;
using NLog; using NLog;
using Ragon.Common; using Ragon.Common;
namespace Ragon.Core namespace Ragon.Core
{ {
// TODO: Replace all serialization and packing into dedicated structures
// TODO: Split class on different managers
public class GameRoom : IGameRoom public class GameRoom : IGameRoom
{ {
public int PlayersMin { get; private set; } public int PlayersMin { get; private set; }
public int PlayersMax { get; private set; } public int PlayersMax { get; private set; }
public int PlayersCount => _players.Count; public int PlayersCount => _players.Count;
public int EntitiesCount => _entities.Count;
public string Id { get; private set; } public string Id { get; private set; }
public string Map { get; private set; } public string Map { get; private set; }
@@ -28,7 +32,10 @@ namespace Ragon.Core
private uint[] _readyPlayers = Array.Empty<uint>(); private uint[] _readyPlayers = Array.Empty<uint>();
private uint[] _allPlayers = Array.Empty<uint>(); private uint[] _allPlayers = Array.Empty<uint>();
private Entity[] _entitiesAll = Array.Empty<Entity>(); 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> _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) public GameRoom(IGameThread gameThread, PluginBase pluginBase, string roomId, string map, int min, int max)
{ {
@@ -44,24 +51,13 @@ namespace Ragon.Core
_plugin.Attach(this); _plugin.Attach(this);
} }
public void Joined(Player player, ReadOnlySpan<byte> payload) public void AddPlayer(Player player, ReadOnlySpan<byte> payload)
{ {
if (_players.Count == 0) if (_players.Count == 0)
{ {
_owner = player.PeerId; _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); _players.Add(player.PeerId, player);
_allPlayers = _players.Select(p => p.Key).ToArray(); _allPlayers = _players.Select(p => p.Key).ToArray();
@@ -88,7 +84,7 @@ namespace Ragon.Core
} }
} }
public void Leave(uint peerId) public void RemovePlayer(uint peerId)
{ {
if (_players.Remove(peerId, out var player)) if (_players.Remove(peerId, out var player))
{ {
@@ -102,97 +98,43 @@ namespace Ragon.Core
_serializer.WriteOperation(RagonOperation.PLAYER_LEAVED); _serializer.WriteOperation(RagonOperation.PLAYER_LEAVED);
_serializer.WriteString(player.Id); _serializer.WriteString(player.Id);
_serializer.WriteUShort((ushort) player.EntitiesIds.Count); var entitiesToDelete = player.Entities.Where(e => e.StaticId == 0).ToArray();
foreach (var entityId in player.EntitiesIds) _serializer.WriteUShort((ushort) entitiesToDelete.Length);
foreach (var entity in entitiesToDelete)
{ {
_serializer.WriteInt(entityId); _serializer.WriteUShort(entity.EntityId);
_entities.Remove(entityId); _entities.Remove(entity.EntityId);
} }
var sendData = _serializer.ToArray(); var sendData = _serializer.ToArray();
Broadcast(_readyPlayers, sendData); Broadcast(_readyPlayers, sendData);
} }
if (_allPlayers.Length > 0 && player.PeerId == _owner)
{
var nextOwnerId = _allPlayers[0];
_owner = nextOwnerId;
var nextOwner = _players[nextOwnerId];
_serializer.Clear();
_serializer.WriteOperation(RagonOperation.OWNERSHIP_CHANGED);
_serializer.WriteString(nextOwner.Id);
var sendData = _serializer.ToArray();
Broadcast(_readyPlayers, sendData);
}
_entitiesAll = _entities.Values.ToArray(); _entitiesAll = _entities.Values.ToArray();
} }
} }
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.Clear();
_serializer.FromSpan(ref payloadRawData); _serializer.FromSpan(ref payloadRawData);
switch (operation) 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: case RagonOperation.LOAD_SCENE:
{ {
var sceneName = _serializer.ReadString(); var sceneName = _serializer.ReadString();
@@ -208,91 +150,207 @@ namespace Ragon.Core
Broadcast(_allPlayers, sendData, DeliveryType.Reliable); Broadcast(_allPlayers, sendData, DeliveryType.Reliable);
break; break;
} }
case RagonOperation.REPLICATE_EVENT: case RagonOperation.SCENE_LOADED:
{ {
var evntId = _serializer.ReadUShort(); var player = _players[peerId];
var evntMode = _serializer.ReadByte(); 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(peerId);
_serializer.WriteByte((byte) RagonReplicationMode.SERVER);
_serializer.WriteUShort(value.EntityId);
ReadOnlySpan<byte> data = bufferedEvent.EventData.AsSpan();
_serializer.WriteData(ref data);
var sendData = _serializer.ToArray();
SendEvent(value, bufferedEvent.Target, sendData);
}
}
}
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 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.OWNER_ONLY && ent.OwnerId != peerId)
{
_logger.Warn($"Player have not enought authority for event with Id {eventId}");
return;
}
Span<byte> payloadRaw = stackalloc byte[_serializer.Size]; Span<byte> payloadRaw = stackalloc byte[_serializer.Size];
var payloadData = _serializer.ReadData(_serializer.Size); var payloadData = _serializer.ReadData(_serializer.Size);
payloadData.CopyTo(payloadRaw); payloadData.CopyTo(payloadRaw);
ReadOnlySpan<byte> payload = payloadRaw; ReadOnlySpan<byte> payload = payloadRaw;
if (_plugin.InternalHandle(peerId, evntId, ref payload)) if (_plugin.InternalHandle(peerId, entityId, eventId, ref payload))
return; return;
if (eventMode == RagonReplicationMode.BUFFERED && targetMode != RagonTarget.OWNER)
{
var bufferedEvent = new EntityEvent()
{
EventData = payload.ToArray(),
Target = targetMode,
EventId = eventId,
};
ent.BufferedEvents.Add(bufferedEvent);
}
_serializer.Clear(); _serializer.Clear();
_serializer.WriteOperation(RagonOperation.REPLICATE_EVENT); _serializer.WriteOperation(RagonOperation.REPLICATE_ENTITY_EVENT);
_serializer.WriteUShort((ushort) peerId); _serializer.WriteUShort(eventId);
_serializer.WriteByte(evntMode); _serializer.WriteUShort(peerId);
_serializer.WriteUShort(evntId); _serializer.WriteByte((byte) eventMode);
_serializer.WriteUShort(entityId);
_serializer.WriteData(ref payload); _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(); var sendData = _serializer.ToArray();
Broadcast(_readyPlayers, sendData, DeliveryType.Reliable); Broadcast(_readyPlayers, sendData, DeliveryType.Reliable);
break; break;
@@ -300,20 +358,29 @@ namespace Ragon.Core
case RagonOperation.CREATE_ENTITY: case RagonOperation.CREATE_ENTITY:
{ {
var entityType = _serializer.ReadUShort(); var entityType = _serializer.ReadUShort();
var stateAuthority = (RagonAuthority) _serializer.ReadByte();
var eventAuthority = (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); var entityPayload = _serializer.ReadData(_serializer.Size);
entity.Payload.Write(ref entityPayload); entity.Payload = entityPayload.ToArray();
} }
var player = _players[peerId]; var player = _players[peerId];
player.Entities.Add(entity); player.Entities.Add(entity);
player.EntitiesIds.Add(entity.EntityId); player.EntitiesIds.Add(entity.EntityId);
var ownerId = (ushort) peerId; var ownerId = peerId;
_entities.Add(entity.EntityId, entity); _entities.Add(entity.EntityId, entity);
_entitiesAll = _entities.Values.ToArray(); _entitiesAll = _entities.Values.ToArray();
@@ -323,13 +390,12 @@ namespace Ragon.Core
_serializer.Clear(); _serializer.Clear();
_serializer.WriteOperation(RagonOperation.CREATE_ENTITY); _serializer.WriteOperation(RagonOperation.CREATE_ENTITY);
_serializer.WriteUShort(entityType); _serializer.WriteUShort(entityType);
_serializer.WriteByte((byte) stateAuthority); _serializer.WriteUShort(entity.EntityId);
_serializer.WriteByte((byte) eventAuthority);
_serializer.WriteInt(entity.EntityId);
_serializer.WriteUShort(ownerId); _serializer.WriteUShort(ownerId);
{ {
var entityPayload = entity.Payload.Read(); ReadOnlySpan<byte> entityPayload = entity.Payload.AsSpan();
_serializer.WriteUShort((ushort) entityPayload.Length);
_serializer.WriteData(ref entityPayload); _serializer.WriteData(ref entityPayload);
} }
@@ -359,70 +425,12 @@ namespace Ragon.Core
_serializer.Clear(); _serializer.Clear();
_serializer.WriteOperation(RagonOperation.DESTROY_ENTITY); _serializer.WriteOperation(RagonOperation.DESTROY_ENTITY);
_serializer.WriteInt(entityId); _serializer.WriteInt(entityId);
_serializer.WriteUShort((ushort) destroyPayload.Length);
_serializer.WriteData(ref destroyPayload); _serializer.WriteData(ref destroyPayload);
var sendData = _serializer.ToArray(); var sendData = _serializer.ToArray();
Broadcast(_readyPlayers, sendData, DeliveryType.Reliable); Broadcast(_readyPlayers, sendData, DeliveryType.Reliable);
} }
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; break;
} }
} }
@@ -432,22 +440,76 @@ namespace Ragon.Core
{ {
_scheduler.Tick(deltaTime); _scheduler.Tick(deltaTime);
foreach (var entity in _entitiesAll) ReplicateProperties();
{ }
if (entity.State.isDirty)
{
var state = entity.State.Read();
// TODO: Move this to specialized class
void ReplicateSnapshot(uint peerId)
{
_serializer.Clear(); _serializer.Clear();
_serializer.WriteOperation(RagonOperation.REPLICATE_ENTITY_STATE); _serializer.WriteOperation(RagonOperation.SNAPSHOT);
_serializer.WriteInt(entity.EntityId); _serializer.WriteUShort((ushort) _readyPlayers.Length);
_serializer.WriteData(ref state); foreach (var playerPeerId in _readyPlayers)
{
_serializer.WriteUShort((ushort) playerPeerId);
_serializer.WriteString(_players[playerPeerId].Id);
_serializer.WriteString(_players[playerPeerId].PlayerName);
}
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();
_serializer.WriteUShort(entity.EntityType);
_serializer.WriteUShort(entity.EntityId);
_serializer.WriteUShort((ushort) entity.OwnerId);
_serializer.WriteUShort((ushort) payload.Length);
_serializer.WriteData(ref payload);
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(); var sendData = _serializer.ToArray();
Broadcast(_readyPlayers, sendData, DeliveryType.Unreliable); Send(peerId, sendData, DeliveryType.Reliable);
entity.State.Clear();
} }
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 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.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;
}
}
}
public void Send(uint peerId, byte[] rawData, DeliveryType deliveryType = DeliveryType.Unreliable) =>
_gameThread.Server.Send(peerId, rawData, deliveryType); _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); _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); _gameThread.Server.Broadcast(_allPlayers, rawData, deliveryType);
} }
} }
}
+34 -36
View File
@@ -1,5 +1,4 @@
using System; using System;
using System.Diagnostics;
using System.Threading; using System.Threading;
using Ragon.Common; using Ragon.Common;
using ENet; using ENet;
@@ -11,36 +10,33 @@ namespace Ragon.Core
{ {
private readonly RoomManager _roomManager; private readonly RoomManager _roomManager;
private readonly Thread _thread; private readonly Thread _thread;
private readonly Stopwatch _gameLoopTimer;
private readonly Lobby _lobby; private readonly Lobby _lobby;
private readonly ISocketServer _server;
private readonly IDispatcherInternal _dispatcherInternal;
private readonly IDispatcher _dispatcher;
private readonly ILogger _logger = LogManager.GetCurrentClassLogger(); private readonly ILogger _logger = LogManager.GetCurrentClassLogger();
private readonly float _deltaTime = 0.0f; private readonly float _deltaTime = 0.0f;
private readonly Stopwatch _statisticsTimer;
private readonly Configuration _configuration; private readonly Configuration _configuration;
private readonly IDispatcherInternal _dispatcherInternal;
public IDispatcher ThreadDispatcher { get; private set; } public ISocketServer Server => _server;
public ISocketServer Server { get; private set; } public IDispatcher ThreadDispatcher => _dispatcher;
public GameThread(PluginFactory factory, Configuration configuration) public GameThread(PluginFactory factory, Configuration configuration)
{ {
var authorizationProvider = factory.CreateAuthorizationProvider(configuration);
_configuration = configuration; _configuration = configuration;
var authorizationProvider = factory.CreateAuthorizationProvider(configuration);
var dispatcher = new Dispatcher(); var dispatcher = new Dispatcher();
_dispatcherInternal = dispatcher; _dispatcherInternal = dispatcher;
_dispatcher = dispatcher;
ThreadDispatcher = dispatcher; _server = new ENetServer(this);
Server = new ENetServer(this);
_deltaTime = 1000.0f / configuration.SendRate; _deltaTime = 1000.0f / configuration.SendRate;
_roomManager = new RoomManager(factory, this); _roomManager = new RoomManager(factory, this);
_lobby = new Lobby(authorizationProvider, _roomManager, this); _lobby = new Lobby(authorizationProvider, _roomManager, this);
_gameLoopTimer = new Stopwatch();
_statisticsTimer = new Stopwatch();
_thread = new Thread(Execute); _thread = new Thread(Execute);
_thread.Name = "Game Thread"; _thread.Name = "Game Thread";
_thread.IsBackground = true; _thread.IsBackground = true;
@@ -48,19 +44,33 @@ namespace Ragon.Core
public void Start() 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(); var parts = new uint[] {0, 0, 0};
_statisticsTimer.Start(); 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(); _thread.Start();
} }
public void Stop() public void Stop()
{ {
Server.Stop(); _server.Stop();
_gameLoopTimer.Stop();
_statisticsTimer.Stop();
_thread.Interrupt(); _thread.Interrupt();
} }
@@ -68,23 +78,11 @@ namespace Ragon.Core
{ {
while (true) while (true)
{ {
Server.Process(); _server.Process();
_dispatcherInternal.Process(); _dispatcherInternal.Process();
_roomManager.Tick(_deltaTime);
var elapsedMilliseconds = _gameLoopTimer.ElapsedMilliseconds; Thread.Sleep((int) _deltaTime);
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();
}
} }
} }
@@ -104,7 +102,7 @@ namespace Ragon.Core
{ {
try try
{ {
var peerId = evnt.Peer.ID; var peerId = (ushort) evnt.Peer.ID;
var dataRaw = new byte[evnt.Packet.Length]; var dataRaw = new byte[evnt.Packet.Length];
evnt.Packet.CopyTo(dataRaw); evnt.Packet.CopyTo(dataRaw);
-6
View File
@@ -1,6 +0,0 @@
namespace Ragon.Core;
public interface ILobby
{
}
+3 -4
View File
@@ -5,7 +5,7 @@ using Ragon.Common;
namespace Ragon.Core; namespace Ragon.Core;
public class Lobby : ILobby public class Lobby
{ {
private readonly ILogger _logger = LogManager.GetCurrentClassLogger(); private readonly ILogger _logger = LogManager.GetCurrentClassLogger();
private readonly RagonSerializer _serializer; private readonly RagonSerializer _serializer;
@@ -23,7 +23,7 @@ public class Lobby : ILobby
_authorizationManager = new AuthorizationManager(provider, gameThread, this, _serializer); _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.Clear();
_serializer.FromSpan(ref payload); _serializer.FromSpan(ref payload);
@@ -32,8 +32,7 @@ public class Lobby : ILobby
{ {
var key = _serializer.ReadString(); var key = _serializer.ReadString();
var playerName = _serializer.ReadString(); var playerName = _serializer.ReadString();
var protocol = _serializer.ReadByte(); _authorizationManager.OnAuthorization(peerId, key, playerName);
_authorizationManager.OnAuthorization(peerId, key, playerName, protocol);
return; return;
} }
+1 -1
View File
@@ -11,6 +11,6 @@ namespace Ragon.Core
public bool IsLoaded { get; set; } public bool IsLoaded { get; set; }
public List<Entity> Entities; 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 public class PluginBase
{ {
private delegate void SubscribeDelegate(Player player, ref ReadOnlySpan<byte> data); private delegate void SubscribeDelegate(Player player, ref ReadOnlySpan<byte> data);
private delegate void SubscribeEntityDelegate(Player player, Entity entity, ref ReadOnlySpan<byte> data); private delegate void SubscribeEntityDelegate(Player player, Entity entity, ref ReadOnlySpan<byte> data);
private Dictionary<ushort, SubscribeDelegate> _globalEvents = new(); private Dictionary<ushort, SubscribeDelegate> _globalEvents = new();
private Dictionary<int, Dictionary<ushort, SubscribeEntityDelegate>> _entityEvents = new(); private Dictionary<int, Dictionary<ushort, SubscribeEntityDelegate>> _entityEvents = new();
private readonly RagonSerializer _serializer = new(); private readonly RagonSerializer _serializer = new();
protected IGameRoom GameRoom { get; private set; } = null!; protected IGameRoom Room { get; private set; } = null!;
protected ILogger Logger = null!; protected ILogger Logger = null!;
public void Attach(GameRoom gameRoom) public void Attach(GameRoom gameRoom)
{ {
Logger = LogManager.GetLogger($"Plugin<{GetType().Name}>"); Logger = LogManager.GetLogger($"Plugin<{GetType().Name}>");
GameRoom = gameRoom; Room = gameRoom;
_globalEvents.Clear(); _globalEvents.Clear();
_entityEvents.Clear(); _entityEvents.Clear();
@@ -34,7 +33,7 @@ namespace Ragon.Core
_entityEvents.Clear(); _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)) 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)) if (_globalEvents.ContainsKey(evntCode))
{ {
@@ -69,7 +68,7 @@ namespace Ragon.Core
_globalEvents.Add(evntCode, (Player player, ref ReadOnlySpan<byte> raw) => { action.Invoke(player); }); _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)) 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)) if (_entityEvents.ContainsKey(entity.EntityId))
{ {
@@ -150,8 +149,9 @@ namespace Ragon.Core
if (!_entityEvents[entityId].ContainsKey(evntCode)) if (!_entityEvents[entityId].ContainsKey(evntCode))
return false; return false;
var player = GameRoom.GetPlayerById(peerId); var player = Room.GetPlayerById(peerId);
var entity = GameRoom.GetEntityById(entityId); var entity = Room.GetEntityById(entityId);
_entityEvents[entityId][evntCode].Invoke(player, entity, ref payload); _entityEvents[entityId][evntCode].Invoke(player, entity, ref payload);
return true; return true;
@@ -161,7 +161,7 @@ namespace Ragon.Core
{ {
if (_globalEvents.ContainsKey(evntCode)) if (_globalEvents.ContainsKey(evntCode))
{ {
var player = GameRoom.GetPlayerById(peerId); var player = Room.GetPlayerById(peerId);
_globalEvents[evntCode].Invoke(player, ref payload); _globalEvents[evntCode].Invoke(player, ref payload);
return true; return true;
} }
@@ -169,7 +169,7 @@ namespace Ragon.Core
return false; return false;
} }
public void SendEvent(Player player, uint eventCode, IRagonSerializable payload) public void ReplicateEvent(Player player, uint eventCode, IRagonSerializable payload)
{ {
_serializer.Clear(); _serializer.Clear();
_serializer.WriteOperation(RagonOperation.REPLICATE_EVENT); _serializer.WriteOperation(RagonOperation.REPLICATE_EVENT);
@@ -177,10 +177,10 @@ namespace Ragon.Core
payload.Serialize(_serializer); payload.Serialize(_serializer);
var sendData = _serializer.ToArray(); 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.Clear();
_serializer.WriteOperation(RagonOperation.REPLICATE_EVENT); _serializer.WriteOperation(RagonOperation.REPLICATE_EVENT);
@@ -188,10 +188,10 @@ namespace Ragon.Core
payload.Serialize(_serializer); payload.Serialize(_serializer);
var sendData = _serializer.ToArray(); 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.Clear();
_serializer.WriteOperation(RagonOperation.REPLICATE_ENTITY_EVENT); _serializer.WriteOperation(RagonOperation.REPLICATE_ENTITY_EVENT);
@@ -200,10 +200,10 @@ namespace Ragon.Core
payload.Serialize(_serializer); payload.Serialize(_serializer);
var sendData = _serializer.ToArray(); 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.Clear();
_serializer.WriteOperation(RagonOperation.REPLICATE_ENTITY_EVENT); _serializer.WriteOperation(RagonOperation.REPLICATE_ENTITY_EVENT);
@@ -212,7 +212,7 @@ namespace Ragon.Core
payload.Serialize(_serializer); payload.Serialize(_serializer);
var sendData = _serializer.ToArray(); 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) if (existRoom.Id == roomId && existRoom.PlayersCount < existRoom.PlayersMax)
{ {
existRoom.Joined(player, payload); existRoom.AddPlayer(player, payload);
_roomsBySocket.Add(player.PeerId, existRoom); _roomsBySocket.Add(player.PeerId, existRoom);
return; return;
} }
@@ -55,7 +55,7 @@ public class RoomManager
throw new NullReferenceException($"Plugin for map {map} is null"); throw new NullReferenceException($"Plugin for map {map} is null");
var room = new GameRoom(_gameThread, plugin, roomId, map, min, max); var room = new GameRoom(_gameThread, plugin, roomId, map, min, max);
room.Joined(creator, payload); room.AddPlayer(creator, payload);
room.Start(); room.Start();
_roomsBySocket.Add(creator.PeerId, room); _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}"); _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); _roomsBySocket.Add(player.PeerId, existRoom);
return; return;
} }
@@ -90,7 +90,7 @@ public class RoomManager
throw new NullReferenceException($"Plugin for map {map} is null"); throw new NullReferenceException($"Plugin for map {map} is null");
var room = new GameRoom(_gameThread, plugin, roomId, map, min, max); var room = new GameRoom(_gameThread, plugin, roomId, map, min, max);
room.Joined(player, payload); room.AddPlayer(player, payload);
room.Start(); room.Start();
_roomsBySocket.Add(player.PeerId, room); _roomsBySocket.Add(player.PeerId, room);
@@ -102,7 +102,7 @@ public class RoomManager
if (_roomsBySocket.Remove(player.PeerId, out var room)) if (_roomsBySocket.Remove(player.PeerId, out var room))
{ {
_logger.Trace($"Player ({player.PlayerName}|{player.Id}) left room with Id {room.Id}"); _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) if (room.PlayersCount < room.PlayersMin)
{ {
_logger.Trace($"Room with Id {room.Id} destroyed"); _logger.Trace($"Room with Id {room.Id} destroyed");
+27 -20
View File
@@ -1,46 +1,42 @@
using System; using System;
using System.Diagnostics;
using System.Timers;
using ENet; using ENet;
using NLog; using NLog;
namespace Ragon.Core namespace Ragon.Core
{ {
public enum Status
{
Stopped,
Listening,
Disconnecting,
Connecting,
Assigning,
Connected
}
public class ENetServer : ISocketServer public class ENetServer : ISocketServer
{ {
public Status Status { get; private set; }
private ILogger _logger = LogManager.GetCurrentClassLogger(); private ILogger _logger = LogManager.GetCurrentClassLogger();
private Host _host; private Host _host;
private uint _protocol;
private Address _address; private Address _address;
private Event _netEvent; private Event _netEvent;
private Peer[] _peers; private Peer[] _peers;
private IHandler _handler; private IHandler _handler;
private Stopwatch _timer;
public ENetServer(IHandler handler) public ENetServer(IHandler handler)
{ {
_handler = 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 = default;
_address.Port = port; _address.Port = port;
_peers = new Peer[connections]; _peers = new Peer[connections];
_protocol = protocol;
_host = new Host();
_host.Create(_address, connections, 2, 0, 0, 1024 * 1024); _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($"Network listening on {port}");
_logger.Info($"Protocol: {protocolDecoded}");
} }
public void Broadcast(uint[] peersIds, byte[] data, DeliveryType type) public void Broadcast(uint[] peersIds, byte[] data, DeliveryType type)
@@ -57,7 +53,7 @@ namespace Ragon.Core
else if (type == DeliveryType.Unreliable) else if (type == DeliveryType.Unreliable)
{ {
channel = 1; channel = 1;
packetFlags = PacketFlags.None; packetFlags = PacketFlags.UnreliableFragmented;
} }
newPacket.Create(data, data.Length, packetFlags); newPacket.Create(data, data.Length, packetFlags);
@@ -98,7 +94,7 @@ namespace Ragon.Core
{ {
if (_host.CheckEvents(out _netEvent) <= 0) if (_host.CheckEvents(out _netEvent) <= 0)
{ {
if (_host.Service(15, out _netEvent) <= 0) if (_host.Service(0, out _netEvent) <= 0)
break; break;
polled = true; polled = true;
@@ -107,11 +103,17 @@ namespace Ragon.Core
switch (_netEvent.Type) switch (_netEvent.Type)
{ {
case EventType.None: case EventType.None:
Console.WriteLine("None event"); {
_logger.Trace("None event");
break; break;
}
case EventType.Connect: case EventType.Connect:
{ {
// if (IsValidProtocol(_netEvent.Data))
// {
// _logger.Warn("Mismatched protocol, close connection");
// break;
// }
_peers[_netEvent.Peer.ID] = _netEvent.Peer; _peers[_netEvent.Peer.ID] = _netEvent.Peer;
_handler.OnEvent(_netEvent); _handler.OnEvent(_netEvent);
break; break;
@@ -140,5 +142,10 @@ namespace Ragon.Core
{ {
_host?.Dispose(); _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 interface ISocketServer
{ {
public void Start(ushort port, int connections); public void Start(ushort port, int connections, uint protocol);
public void Process(); public void Process();
public void Stop(); public void Stop();
public void Send(uint peerId, byte[] data, DeliveryType type); 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) Copyright (c) 2022 Eduard Kargin (theedison4@gmail.com)
MIT License:
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights in the Software without restriction, including without limitation the rights