wip
This commit is contained in:
Executable
+407
@@ -0,0 +1,407 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using System.Linq;
|
||||
using NLog;
|
||||
using Ragon.Common;
|
||||
|
||||
namespace Ragon.Core
|
||||
{
|
||||
public class GameRoom : IGameRoom
|
||||
{
|
||||
public int PlayersMin { get; private set; }
|
||||
public int PlayersMax { get; private set; }
|
||||
public int PlayersCount => _players.Count;
|
||||
public string Id { get; private set; }
|
||||
public string Map { get; private set; }
|
||||
|
||||
private ILogger _logger = LogManager.GetCurrentClassLogger();
|
||||
private Dictionary<uint, Player> _players = new();
|
||||
private Dictionary<int, Entity> _entities = new();
|
||||
private uint _owner;
|
||||
private uint _ticks;
|
||||
|
||||
private readonly PluginBase _plugin;
|
||||
private readonly IGameThread _sender;
|
||||
private readonly RagonSerializer _serializer = new(512);
|
||||
|
||||
// Cache
|
||||
private uint[] _readyPlayers = Array.Empty<uint>();
|
||||
private uint[] _allPlayers = Array.Empty<uint>();
|
||||
private Entity[] _entitiesAll = Array.Empty<Entity>();
|
||||
|
||||
public GameRoom(IGameThread sender, PluginBase pluginBase, string map, int min, int max)
|
||||
{
|
||||
_sender = sender;
|
||||
_plugin = pluginBase;
|
||||
|
||||
Map = map;
|
||||
PlayersMin = min;
|
||||
PlayersMax = max;
|
||||
Id = Guid.NewGuid().ToString();
|
||||
|
||||
_logger.Info($"Room created with plugin: {_plugin.GetType().Name}");
|
||||
_plugin.Attach(this);
|
||||
}
|
||||
|
||||
public void Joined(Player player, ReadOnlySpan<byte> payload)
|
||||
{
|
||||
if (_players.Count == 0)
|
||||
{
|
||||
_owner = player.PeerId;
|
||||
}
|
||||
|
||||
{
|
||||
_serializer.Clear();
|
||||
_serializer.WriteOperation(RagonOperation.PLAYER_JOINED);
|
||||
_serializer.WriteUShort((ushort) player.PeerId);
|
||||
_serializer.WriteString(player.Id);
|
||||
_serializer.WriteString(player.PlayerName);
|
||||
|
||||
var sendData = _serializer.ToArray();
|
||||
Broadcast(_readyPlayers, sendData, DeliveryType.Reliable);
|
||||
}
|
||||
|
||||
_players.Add(player.PeerId, player);
|
||||
_allPlayers = _players.Select(p => p.Key).ToArray();
|
||||
|
||||
{
|
||||
_serializer.Clear();
|
||||
_serializer.WriteOperation(RagonOperation.JOIN_SUCCESS);
|
||||
_serializer.WriteString(Id);
|
||||
_serializer.WriteString(player.Id);
|
||||
_serializer.WriteString(GetOwner().Id);
|
||||
_serializer.WriteUShort((ushort) PlayersMin);
|
||||
_serializer.WriteUShort((ushort) PlayersMax);
|
||||
|
||||
var sendData = _serializer.ToArray();
|
||||
Send(player.PeerId, sendData, DeliveryType.Reliable);
|
||||
}
|
||||
|
||||
{
|
||||
_serializer.Clear();
|
||||
_serializer.WriteOperation(RagonOperation.LOAD_SCENE);
|
||||
_serializer.WriteString(Map);
|
||||
|
||||
var sendData = _serializer.ToArray();
|
||||
Send(player.PeerId, sendData, DeliveryType.Reliable);
|
||||
}
|
||||
}
|
||||
|
||||
public void Leave(uint peerId)
|
||||
{
|
||||
if (_players.Remove(peerId, out var player))
|
||||
{
|
||||
_allPlayers = _players.Select(p => p.Key).ToArray();
|
||||
_readyPlayers = _players.Where(p => p.Value.IsLoaded).Select(p => p.Key).ToArray();
|
||||
|
||||
var isOwnershipChange = player.PeerId == _owner;
|
||||
|
||||
{
|
||||
_plugin.OnPlayerLeaved(player);
|
||||
|
||||
_serializer.Clear();
|
||||
_serializer.WriteOperation(RagonOperation.PLAYER_LEAVED);
|
||||
_serializer.WriteString(player.Id);
|
||||
|
||||
_serializer.WriteUShort((ushort) player.EntitiesIds.Count);
|
||||
foreach (var entityId in player.EntitiesIds)
|
||||
{
|
||||
_serializer.WriteInt(entityId);
|
||||
_entities.Remove(entityId);
|
||||
}
|
||||
|
||||
var sendData = _serializer.ToArray();
|
||||
Broadcast(_readyPlayers, sendData);
|
||||
}
|
||||
|
||||
if (_allPlayers.Length > 0 && isOwnershipChange)
|
||||
{
|
||||
var newRoomOwnerId = _allPlayers[0];
|
||||
var newRoomOwner = _players[newRoomOwnerId];
|
||||
|
||||
{
|
||||
_plugin.OnOwnershipChanged(newRoomOwner);
|
||||
|
||||
_serializer.Clear();
|
||||
_serializer.WriteOperation(RagonOperation.OWNERSHIP_CHANGED);
|
||||
_serializer.WriteString(newRoomOwner.Id);
|
||||
|
||||
var sendData = _serializer.ToArray();
|
||||
Broadcast(_readyPlayers, sendData);
|
||||
}
|
||||
}
|
||||
|
||||
_entitiesAll = _entities.Values.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
public void ProcessEvent(uint peerId, ReadOnlySpan<byte> rawData)
|
||||
{
|
||||
var operation = (RagonOperation) rawData[0];
|
||||
var payloadRawData = rawData.Slice(1, rawData.Length - 1);
|
||||
|
||||
_serializer.Clear();
|
||||
_serializer.FromSpan(ref payloadRawData);
|
||||
|
||||
switch (operation)
|
||||
{
|
||||
case RagonOperation.REPLICATE_ENTITY_STATE:
|
||||
{
|
||||
var entityId = _serializer.ReadInt();
|
||||
if (_entities.TryGetValue(entityId, out var ent))
|
||||
{
|
||||
if (ent.State.Authority == RagonAuthority.OWNER_ONLY && ent.OwnerId != peerId)
|
||||
return;
|
||||
|
||||
var entityStateData = _serializer.ReadData(_serializer.Size);
|
||||
ent.State.Write(ref entityStateData);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case RagonOperation.REPLICATE_ENTITY_EVENT:
|
||||
{
|
||||
var evntId = _serializer.ReadUShort();
|
||||
var 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.WriteInt(entityId);
|
||||
_serializer.WriteData(ref payload);
|
||||
var sendData = _serializer.ToArray();
|
||||
|
||||
Broadcast(_readyPlayers, sendData, DeliveryType.Reliable);
|
||||
break;
|
||||
}
|
||||
case RagonOperation.REPLICATE_EVENT:
|
||||
{
|
||||
var evntId = _serializer.ReadUShort();
|
||||
|
||||
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(evntId);
|
||||
_serializer.WriteData(ref payload);
|
||||
|
||||
var sendData = _serializer.ToArray();
|
||||
Broadcast(_readyPlayers, sendData, DeliveryType.Reliable);
|
||||
break;
|
||||
}
|
||||
case RagonOperation.CREATE_ENTITY:
|
||||
{
|
||||
var entityType = _serializer.ReadUShort();
|
||||
var stateAuthority = (RagonAuthority) _serializer.ReadByte();
|
||||
var eventAuthority = (RagonAuthority) _serializer.ReadByte();
|
||||
var entity = new Entity(peerId, entityType, 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_ENTITY);
|
||||
_serializer.WriteUShort(entityType);
|
||||
_serializer.WriteByte((byte) stateAuthority);
|
||||
_serializer.WriteByte((byte) eventAuthority);
|
||||
_serializer.WriteInt(entity.EntityId);
|
||||
_serializer.WriteUShort(ownerId);
|
||||
|
||||
{
|
||||
var entityPayload = entity.Payload.Read();
|
||||
_serializer.WriteData(ref entityPayload);
|
||||
}
|
||||
|
||||
var sendData = _serializer.ToArray();
|
||||
Broadcast(_readyPlayers, sendData, DeliveryType.Reliable);
|
||||
break;
|
||||
}
|
||||
case RagonOperation.DESTROY_ENTITY:
|
||||
{
|
||||
var entityId = _serializer.ReadInt();
|
||||
if (_entities.TryGetValue(entityId, out var entity))
|
||||
{
|
||||
if (entity.Authority == RagonAuthority.OWNER_ONLY && entity.OwnerId != peerId)
|
||||
return;
|
||||
|
||||
var player = _players[peerId];
|
||||
var destroyPayload = _serializer.ReadData(_serializer.Size);
|
||||
|
||||
player.Entities.Remove(entity);
|
||||
player.EntitiesIds.Remove(entity.EntityId);
|
||||
|
||||
_entities.Remove(entityId);
|
||||
_entitiesAll = _entities.Values.ToArray();
|
||||
|
||||
_plugin.OnEntityDestroyed(player, entity);
|
||||
|
||||
_serializer.Clear();
|
||||
_serializer.WriteOperation(RagonOperation.DESTROY_ENTITY);
|
||||
_serializer.WriteInt(entityId);
|
||||
_serializer.WriteData(ref destroyPayload);
|
||||
|
||||
var sendData = _serializer.ToArray();
|
||||
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);
|
||||
}
|
||||
|
||||
_serializer.WriteInt(_entitiesAll.Length);
|
||||
foreach (var entity in _entitiesAll)
|
||||
{
|
||||
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.WriteUShort((ushort) state.Length);
|
||||
_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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Tick(float deltaTime)
|
||||
{
|
||||
_ticks++;
|
||||
_plugin.OnTick(_ticks, deltaTime);
|
||||
|
||||
foreach (var entity in _entitiesAll)
|
||||
{
|
||||
if (entity.State.isDirty)
|
||||
{
|
||||
var state = entity.State.Read();
|
||||
|
||||
_serializer.Clear();
|
||||
_serializer.WriteOperation(RagonOperation.REPLICATE_ENTITY_STATE);
|
||||
_serializer.WriteInt(entity.EntityId);
|
||||
_serializer.WriteData(ref state);
|
||||
|
||||
var sendData = _serializer.ToArray();
|
||||
Broadcast(_readyPlayers, sendData, DeliveryType.Unreliable);
|
||||
|
||||
entity.State.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
_logger.Info("Room started");
|
||||
_plugin.OnStart();
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
_logger.Info("Room stopped");
|
||||
_plugin.OnStop();
|
||||
|
||||
_plugin.Detach();
|
||||
}
|
||||
|
||||
public Player GetPlayerById(uint peerId) => _players[peerId];
|
||||
|
||||
public Entity GetEntityById(int entityId) => _entities[entityId];
|
||||
|
||||
public Player GetOwner() => _players[_owner];
|
||||
|
||||
public void Send(uint peerId, byte[] rawData, DeliveryType deliveryType = DeliveryType.Unreliable)
|
||||
{
|
||||
_sender.SendSocketEvent(new SocketEvent()
|
||||
{
|
||||
PeerId = peerId,
|
||||
Data = rawData,
|
||||
Type = EventType.DATA,
|
||||
Delivery = deliveryType,
|
||||
});
|
||||
}
|
||||
|
||||
public void Broadcast(uint[] peersIds, byte[] rawData, DeliveryType deliveryType = DeliveryType.Unreliable)
|
||||
{
|
||||
foreach (var peer in peersIds)
|
||||
{
|
||||
_sender.SendSocketEvent(new SocketEvent()
|
||||
{
|
||||
PeerId = peer,
|
||||
Data = rawData,
|
||||
Type = EventType.DATA,
|
||||
Delivery = deliveryType,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public void Broadcast(byte[] rawData, DeliveryType deliveryType = DeliveryType.Unreliable)
|
||||
{
|
||||
foreach (var player in _players.Values.ToArray())
|
||||
{
|
||||
_sender.SendSocketEvent(new SocketEvent()
|
||||
{
|
||||
PeerId = player.PeerId,
|
||||
Data = rawData,
|
||||
Type = EventType.DATA,
|
||||
Delivery = deliveryType,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Executable
+138
@@ -0,0 +1,138 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Threading;
|
||||
using NLog;
|
||||
|
||||
namespace Ragon.Core
|
||||
{
|
||||
public class GameThread : IGameThread
|
||||
{
|
||||
private readonly Dictionary<uint, GameRoom> _socketByRooms;
|
||||
private readonly RoomManager _roomManager;
|
||||
private readonly ENetServer _socketServer;
|
||||
private readonly Thread _thread;
|
||||
private readonly Server _serverConfiguration;
|
||||
private readonly Stopwatch _gameLoopTimer;
|
||||
private readonly Lobby _lobby;
|
||||
private readonly IDispatcher _dispatcher;
|
||||
private readonly ILogger _logger = LogManager.GetCurrentClassLogger();
|
||||
private readonly float _deltaTime = 0.0f;
|
||||
|
||||
private int _packets = 0;
|
||||
private readonly Stopwatch _packetsTimer;
|
||||
public GameThread(PluginFactory factory, Configuration configuration)
|
||||
{
|
||||
var authorizationProvider = factory.CreateAuthorizationProvider(configuration);
|
||||
|
||||
_serverConfiguration = configuration.Server;
|
||||
_deltaTime = 1000.0f / configuration.TickRate;
|
||||
|
||||
_dispatcher = new Dispatcher();
|
||||
_roomManager = new RoomManager(factory, this);
|
||||
_lobby = new Lobby(authorizationProvider, _roomManager, this);
|
||||
_socketServer = new ENetServer();
|
||||
_gameLoopTimer = new Stopwatch();
|
||||
_packetsTimer = new Stopwatch();
|
||||
_socketByRooms = new Dictionary<uint, GameRoom>();
|
||||
|
||||
_thread = new Thread(Execute);
|
||||
_thread.Name = "Game Thread";
|
||||
_thread.IsBackground = true;
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
_gameLoopTimer.Start();
|
||||
_packetsTimer.Start();
|
||||
|
||||
_socketServer.Start(_serverConfiguration.Port);
|
||||
_thread.Start();
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
_gameLoopTimer.Stop();
|
||||
_packetsTimer.Stop();
|
||||
_socketServer.Stop();
|
||||
_thread.Interrupt();
|
||||
}
|
||||
|
||||
private void Execute()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
_dispatcher.Process();
|
||||
|
||||
while (_socketServer.ReceiveBuffer.TryDequeue(out var evnt))
|
||||
{
|
||||
if (evnt.Type == EventType.DISCONNECTED || evnt.Type == EventType.TIMEOUT)
|
||||
{
|
||||
if (_socketByRooms.Remove(evnt.PeerId, out var room))
|
||||
room.Leave(evnt.PeerId);
|
||||
|
||||
_lobby.OnDisconnected(evnt.PeerId);
|
||||
}
|
||||
|
||||
if (evnt.Type == EventType.DATA)
|
||||
{
|
||||
_packets += 1;
|
||||
try
|
||||
{
|
||||
var peerId = evnt.PeerId;
|
||||
var data = new ReadOnlySpan<byte>(evnt.Data);
|
||||
if (_socketByRooms.TryGetValue(evnt.PeerId, out var room))
|
||||
{
|
||||
room.ProcessEvent(peerId, data);
|
||||
}
|
||||
else
|
||||
{
|
||||
_lobby.ProcessEvent(peerId, data);
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
_logger.Error(exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var elapsedMilliseconds = _gameLoopTimer.ElapsedMilliseconds;
|
||||
if (elapsedMilliseconds > _deltaTime)
|
||||
{
|
||||
_roomManager.Tick(elapsedMilliseconds / 1000.0f);
|
||||
_gameLoopTimer.Restart();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (_packetsTimer.Elapsed.Seconds > 1)
|
||||
{
|
||||
_logger.Trace($"Clients: {_socketByRooms.Keys.Count} Packets: {_packets} per sec");
|
||||
_packetsTimer.Restart();
|
||||
_packets = 0;
|
||||
}
|
||||
Thread.Sleep(15);
|
||||
}
|
||||
}
|
||||
|
||||
public void Attach(uint peerId, GameRoom room)
|
||||
{
|
||||
_socketByRooms.Add(peerId, room);
|
||||
}
|
||||
|
||||
public void Detach(uint peerId)
|
||||
{
|
||||
_socketByRooms.Remove(peerId);
|
||||
}
|
||||
|
||||
public void SendSocketEvent(SocketEvent socketEvent)
|
||||
{
|
||||
_socketServer.SendBuffer.Enqueue(socketEvent);
|
||||
}
|
||||
|
||||
public IDispatcher GetDispatcher()
|
||||
{
|
||||
return _dispatcher;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
namespace Ragon.Core;
|
||||
|
||||
public interface IGameRoom
|
||||
{
|
||||
public string Id { get; }
|
||||
public string Map { get; }
|
||||
public int PlayersMin { get; }
|
||||
public int PlayersMax { get; }
|
||||
public int PlayersCount { get; }
|
||||
|
||||
public Player GetPlayerById(uint peerId);
|
||||
public Entity GetEntityById(int entityId);
|
||||
public Player GetOwner();
|
||||
|
||||
public void Send(uint peerId, byte[] rawData, DeliveryType deliveryType = DeliveryType.Unreliable);
|
||||
public void Broadcast(uint[] peersIds, byte[] rawData, DeliveryType deliveryType = DeliveryType.Unreliable);
|
||||
public void Broadcast(byte[] rawData, DeliveryType deliveryType = DeliveryType.Unreliable);
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
using Ragon.Common;
|
||||
|
||||
namespace Ragon.Core;
|
||||
|
||||
public interface IGameThread
|
||||
{
|
||||
public void Attach(uint peerId, GameRoom room);
|
||||
public void Detach(uint peerId);
|
||||
public void SendSocketEvent(SocketEvent socketEvent);
|
||||
public IDispatcher GetDispatcher();
|
||||
}
|
||||
Reference in New Issue
Block a user