Compare commits

...

17 Commits

Author SHA1 Message Date
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
19 changed files with 447 additions and 332 deletions
+3 -2
View File
@@ -2,7 +2,8 @@ namespace Ragon.Common
{ {
public enum RagonAuthority: byte public enum RagonAuthority: byte
{ {
OWNER_ONLY, None,
ALL, OwnerOnly,
All,
} }
} }
+1 -2
View File
@@ -15,13 +15,12 @@ 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_SCENE_ENTITY,
DESTROY_ENTITY, DESTROY_ENTITY,
SNAPSHOT, SNAPSHOT,
@@ -0,0 +1,10 @@
namespace Ragon.Common
{
public enum RagonReplicationMode: byte
{
Local,
Server,
LocalAndServer,
Buffered,
}
}
+1
View File
@@ -27,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;
+3 -3
View File
@@ -2,8 +2,8 @@ namespace Ragon.Common
{ {
public enum RagonTarget: byte public enum RagonTarget: byte
{ {
OWNER, Owner,
EXCEPT_OWNER, ExceptOwner,
ALL, All,
} }
} }
@@ -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 -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;
@@ -27,6 +27,12 @@ public class AuthorizationManager : IAuthorizationManager
public void OnAuthorization(uint peerId, string key, string name) 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, Array.Empty<byte>(), _provider.OnAuthorizationRequest(key, name, Array.Empty<byte>(),
@@ -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);
@@ -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.13-rc"; private static readonly string _serverVersion = "1.0.20-rc";
private static void CopyrightInfo() private static void CopyrightInfo()
{ {
+51 -2
View File
@@ -1,3 +1,5 @@
using System;
using System.Collections.Generic;
using Ragon.Common; using Ragon.Common;
namespace Ragon.Core; namespace Ragon.Core;
@@ -11,16 +13,63 @@ public class Entity
public ushort OwnerId { get; private set; } public ushort OwnerId { get; private set; }
public RagonAuthority Authority { get; private set; } public RagonAuthority Authority { get; private set; }
public EntityProperty[] Properties { get; private set; } public EntityProperty[] Properties { get; private set; }
public List<EntityEvent> BufferedEvents = new List<EntityEvent>();
public byte[] Payload { get; set; } public byte[] Payload { get; set; }
public Entity(ushort ownerId, ushort entityType, ushort staticId, RagonAuthority stateAuthority, RagonAuthority eventAuthority, int props) 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++;
Properties = new EntityProperty[props]; Properties = new EntityProperty[props];
Payload = new byte[1024]; Payload = Array.Empty<byte>();
Authority = eventAuthority; 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);
}
}
}
} }
+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; }
}
+4 -2
View File
@@ -6,17 +6,19 @@ namespace Ragon.Core;
public class EntityProperty public class EntityProperty
{ {
public int Size { get; set; } public int Size { get; set; }
public int Capacity { get; set; }
public bool IsDirty { get; private set; } public bool IsDirty { get; private set; }
public bool IsFixed { get; private set; } public bool IsFixed { get; private set; }
private byte[] _data; private byte[] _data;
public EntityProperty(int size, bool isFixed) public EntityProperty(int size, bool isFixed)
{ {
_data = new byte[512]; Capacity = 512;
Size = size; Size = size;
IsFixed = isFixed; IsFixed = isFixed;
IsDirty = true; IsDirty = true;
_data = new byte[Capacity];
} }
public ReadOnlySpan<byte> Read() public ReadOnlySpan<byte> Read()
+278 -246
View File
@@ -7,6 +7,8 @@ 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; }
@@ -33,6 +35,7 @@ namespace Ragon.Core
private HashSet<Entity> _entitiesDirtySet = new HashSet<Entity>(); private HashSet<Entity> _entitiesDirtySet = new HashSet<Entity>();
private List<Entity> _entitiesDirty = new List<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)
{ {
@@ -48,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();
@@ -92,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))
{ {
@@ -106,11 +98,12 @@ 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();
@@ -123,9 +116,17 @@ namespace Ragon.Core
_owner = nextOwnerId; _owner = nextOwnerId;
var nextOwner = _players[nextOwnerId]; var nextOwner = _players[nextOwnerId];
var entitiesToUpdate = player.Entities.Where(e => e.StaticId > 0).ToArray();
_serializer.Clear(); _serializer.Clear();
_serializer.WriteOperation(RagonOperation.OWNERSHIP_CHANGED); _serializer.WriteOperation(RagonOperation.OWNERSHIP_CHANGED);
_serializer.WriteString(nextOwner.Id); _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(); var sendData = _serializer.ToArray();
Broadcast(_readyPlayers, sendData); Broadcast(_readyPlayers, sendData);
@@ -135,13 +136,136 @@ namespace Ragon.Core
} }
} }
// TODO: Move this processing to specialized classes with structures
public void ProcessEvent(ushort peerId, RagonOperation operation, ReadOnlySpan<byte> payloadRawData) 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.LOAD_SCENE:
{
var sceneName = _serializer.ReadString();
_readyPlayers = Array.Empty<uint>();
_entitiesAll = Array.Empty<Entity>();
_entities.Clear();
_serializer.Clear();
_serializer.WriteOperation(RagonOperation.LOAD_SCENE);
_serializer.WriteString(sceneName);
var sendData = _serializer.ToArray();
Broadcast(_allPlayers, sendData, DeliveryType.Reliable);
break;
}
case RagonOperation.SCENE_LOADED:
{
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(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: case RagonOperation.REPLICATE_ENTITY_STATE:
{ {
var entitiesCount = _serializer.ReadUShort(); var entitiesCount = _serializer.ReadUShort();
@@ -161,11 +285,19 @@ namespace Ragon.Core
if (_serializer.ReadBool()) if (_serializer.ReadBool())
{ {
var property = ent.Properties[i]; var property = ent.Properties[i];
var size = property.Size;
if (!property.IsFixed) if (!property.IsFixed)
property.Size = _serializer.ReadUShort(); size = _serializer.ReadUShort();
var propertyPayload = _serializer.ReadData(property.Size); if (size > property.Capacity)
{
_logger.Warn($"Property {i} payload too large, size: {size}");
continue;
}
var propertyPayload = _serializer.ReadData(size);
property.Write(ref propertyPayload); property.Write(ref propertyPayload);
property.Size = size;
} }
} }
@@ -178,186 +310,73 @@ namespace Ragon.Core
break; break;
} }
} }
break; break;
} }
case RagonOperation.REPLICATE_ENTITY_EVENT: case RagonOperation.REPLICATE_ENTITY_EVENT:
{ {
var evntId = _serializer.ReadUShort(); var eventId = _serializer.ReadUShort();
var evntMode = _serializer.ReadByte(); var eventMode = (RagonReplicationMode) _serializer.ReadByte();
var targetMode = (RagonTarget) _serializer.ReadByte(); var targetMode = (RagonTarget) _serializer.ReadByte();
var entityId = _serializer.ReadUShort(); var entityId = _serializer.ReadUShort();
if (!_entities.TryGetValue(entityId, out var ent)) if (!_entities.TryGetValue(entityId, out var ent))
{
_logger.Warn($"Entity not found for event with Id {eventId}");
return; return;
}
if (ent.Authority == RagonAuthority.OWNER_ONLY && ent.OwnerId != peerId) if (ent.Authority == RagonAuthority.OwnerOnly && ent.OwnerId != peerId)
{
_logger.Warn($"Player have not enought authority for event with Id {eventId}");
return; 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, entityId, 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_ENTITY_EVENT); _serializer.WriteOperation(RagonOperation.REPLICATE_ENTITY_EVENT);
_serializer.WriteUShort(evntId); _serializer.WriteUShort(eventId);
_serializer.WriteUShort((ushort) peerId); _serializer.WriteUShort(peerId);
_serializer.WriteByte(evntMode); _serializer.WriteByte((byte) eventMode);
_serializer.WriteUShort(entityId); _serializer.WriteUShort(entityId);
_serializer.WriteData(ref payload); _serializer.WriteData(ref payload);
var sendData = _serializer.ToArray(); var sendData = _serializer.ToArray();
SendEvent(ent, targetMode, 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;
}
}
break;
}
case RagonOperation.LOAD_SCENE:
{
var sceneName = _serializer.ReadString();
_readyPlayers = Array.Empty<uint>();
_entitiesAll = Array.Empty<Entity>();
_entities.Clear();
_serializer.Clear();
_serializer.WriteOperation(RagonOperation.LOAD_SCENE);
_serializer.WriteString(sceneName);
var sendData = _serializer.ToArray();
Broadcast(_allPlayers, sendData, DeliveryType.Reliable);
break;
}
case RagonOperation.REPLICATE_EVENT:
{
var evntId = _serializer.ReadUShort();
var evntMode = _serializer.ReadByte();
var targetMode = (RagonTarget) _serializer.ReadByte();
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))
return;
_serializer.Clear();
_serializer.WriteOperation(RagonOperation.REPLICATE_EVENT);
_serializer.WriteUShort((ushort) peerId);
_serializer.WriteByte(evntMode);
_serializer.WriteUShort(evntId);
_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_SCENE_ENTITY:
{
var entityType = _serializer.ReadUShort();
var staticId = _serializer.ReadUShort();
var propertiesCount = _serializer.ReadUShort();
var entity = new Entity(peerId, entityType, staticId, RagonAuthority.ALL, RagonAuthority.OWNER_ONLY, 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 = entityPayload.ToArray();
}
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_SCENE_ENTITY);
_serializer.WriteUShort(entityType);
_serializer.WriteUShort(entity.EntityId);
_serializer.WriteUShort(staticId);
_serializer.WriteUShort(ownerId);
{
ReadOnlySpan<byte> entityPayload = entity.Payload.AsSpan();
_serializer.WriteData(ref entityPayload);
}
var sendData = _serializer.ToArray();
Broadcast(_readyPlayers, sendData, DeliveryType.Reliable);
break; break;
} }
case RagonOperation.CREATE_ENTITY: case RagonOperation.CREATE_ENTITY:
{ {
var entityType = _serializer.ReadUShort(); var entityType = _serializer.ReadUShort();
var eventAuthority = (RagonAuthority) _serializer.ReadByte();
var propertiesCount = _serializer.ReadUShort(); var propertiesCount = _serializer.ReadUShort();
var entity = new Entity(peerId, entityType, 0, RagonAuthority.ALL, RagonAuthority.ALL, propertiesCount);
// _logger.Trace("Created entity with properties: " + propertiesCount); _logger.Trace($"[{peerId}] Create Entity {entityType}");
var entity = new Entity(peerId, entityType, 0, eventAuthority, propertiesCount);
for (var i = 0; i < propertiesCount; i++) for (var i = 0; i < propertiesCount; i++)
{ {
var propertyType = _serializer.ReadBool(); var propertyType = _serializer.ReadBool();
var propertySize = _serializer.ReadUShort(); var propertySize = _serializer.ReadUShort();
entity.Properties[i] = new EntityProperty(propertySize, propertyType); entity.Properties[i] = new EntityProperty(propertySize, propertyType);
// _logger.Trace($"Property: {i} Size: {propertySize} IsFixed: {propertyType}");
} }
{ {
@@ -369,7 +388,7 @@ namespace Ragon.Core
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();
@@ -384,6 +403,7 @@ namespace Ragon.Core
{ {
ReadOnlySpan<byte> entityPayload = entity.Payload.AsSpan(); ReadOnlySpan<byte> entityPayload = entity.Payload.AsSpan();
_serializer.WriteUShort((ushort) entityPayload.Length);
_serializer.WriteData(ref entityPayload); _serializer.WriteData(ref entityPayload);
} }
@@ -396,7 +416,7 @@ namespace Ragon.Core
var entityId = _serializer.ReadInt(); var entityId = _serializer.ReadInt();
if (_entities.TryGetValue(entityId, out var entity)) 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; return;
var player = _players[peerId]; var player = _players[peerId];
@@ -413,6 +433,7 @@ 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();
@@ -421,54 +442,6 @@ namespace Ragon.Core
break; break;
} }
case RagonOperation.SCENE_IS_LOADED:
{
_serializer.Clear();
_serializer.WriteOperation(RagonOperation.SNAPSHOT);
_serializer.WriteUShort((ushort) _allPlayers.Length);
foreach (var playerPeerId in _allPlayers)
{
_serializer.WriteString(_players[playerPeerId].Id);
_serializer.WriteUShort((ushort) playerPeerId);
_serializer.WriteString(_players[playerPeerId].PlayerName);
}
var dynamicEntities = _entitiesAll.Where(e => e.StaticId == 0).ToArray();
_serializer.WriteUShort((ushort) dynamicEntities.Length);
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);
}
var staticCount = _entitiesAll.Where(e => e.StaticId != 0).ToArray();
_serializer.WriteUShort((ushort) staticCount.Length);
foreach (var entity in staticCount)
{
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);
}
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;
}
} }
} }
@@ -476,34 +449,70 @@ namespace Ragon.Core
{ {
_scheduler.Tick(deltaTime); _scheduler.Tick(deltaTime);
if (_entitiesDirty.Count > 0) 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)
{
_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();
Send(peerId, sendData, DeliveryType.Reliable);
}
private void ReplicateProperties()
{
var entities = (ushort) _entitiesDirty.Count;
if (entities > 0)
{ {
_serializer.Clear(); _serializer.Clear();
_serializer.WriteOperation(RagonOperation.REPLICATE_ENTITY_STATE); _serializer.WriteOperation(RagonOperation.REPLICATE_ENTITY_STATE);
_serializer.WriteUShort((ushort) _entitiesDirty.Count); _serializer.WriteUShort(entities);
for (var entityIndex = 0; entityIndex < _entitiesDirty.Count; entityIndex++) foreach (var entity in _entitiesDirty)
{ entity.ReplicateProperties(_serializer);
var entity = _entitiesDirty[entityIndex];
_serializer.WriteUShort(entity.EntityId);
for (int propertyIndex = 0; propertyIndex < entity.Properties.Length; propertyIndex++)
{
var property = entity.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);
}
}
}
_entitiesDirty.Clear(); _entitiesDirty.Clear();
_entitiesDirtySet.Clear(); _entitiesDirtySet.Clear();
@@ -537,19 +546,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.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); _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);
} }
} }
}
-6
View File
@@ -1,6 +0,0 @@
namespace Ragon.Core;
public interface ILobby
{
}
+1 -1
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;
+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");
+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