feat: multi project support, maintaince

This commit is contained in:
2025-10-04 15:08:53 +03:00
parent 5136f08dab
commit e78e8048ff
18 changed files with 152 additions and 66 deletions
+13 -3
View File
@@ -12,13 +12,25 @@ public class RagonData
{
}
public void Read(RagonBuffer buffer)
public void Read(RagonBuffer buffer, int maxSize = 0)
{
var len = buffer.ReadUShort();
var totalSize = 0;
for (int i = 0; i < len; i++)
{
var key = buffer.ReadString();
var valueSize = buffer.ReadUShort();
if (maxSize > 0)
{
totalSize += valueSize;
if (totalSize > maxSize)
{
throw new InvalidOperationException($"User data exceeds limit: {totalSize} > {maxSize}");
}
}
if (valueSize > 0)
{
var value = buffer.ReadBytes(valueSize);
@@ -59,8 +71,6 @@ public class RagonData
buffer.WriteString(prop.Key);
buffer.WriteUShort((ushort)prop.Value.Length);
buffer.WriteBytes(prop.Value);
Console.WriteLine($"Key: {prop.Key} Value: {prop.Value.Length}");
}
}
}
@@ -19,6 +19,7 @@ using Ragon.Server.IO;
using Ragon.Server.Lobby;
using Ragon.Server.Logging;
using Ragon.Server.Plugin;
using Ragon.Server.Project;
namespace Ragon.Server.Handler
{
@@ -30,19 +31,22 @@ namespace Ragon.Server.Handler
private readonly RagonContextObserver _observer;
private readonly RagonServerConfiguration _configuration;
private readonly RagonBuffer _writer;
private readonly ProjectRegistry _projectRegistry;
public AuthorizationOperation(RagonBuffer reader,
RagonBuffer writer,
IRagonServer server,
IServerPlugin serverPlugin,
RagonContextObserver observer,
RagonServerConfiguration configuration) : base(reader, writer)
RagonServerConfiguration configuration,
ProjectRegistry projectRegistry) : base(reader, writer)
{
_serverPlugin = serverPlugin;
_configuration = configuration;
_observer = observer;
_writer = writer;
_server = server;
_projectRegistry = projectRegistry;
}
public override void Handle(RagonContext context, NetworkChannel channel)
@@ -59,31 +63,45 @@ namespace Ragon.Server.Handler
return;
}
var configuration = _configuration;
var key = Reader.ReadString();
var projectKey = Reader.ReadString();
var name = Reader.ReadString();
var payload = Reader.ReadString();
if (key == configuration.ServerKey)
if (!_projectRegistry.ValidateKey(projectKey))
{
var authorizeViaPlugin = _serverPlugin.OnAuthorize(new ConnectionRequest(_server, context.Connection.Id, payload));
if (authorizeViaPlugin)
return;
var id = Guid.NewGuid().ToString();
Approve(context, new ConnectionResponse(id, name, payload));
}
else
{
_logger.Warning($"Invalid key for connection {context.Connection.Id}");
_logger.Warning($"Invalid project key from connection {context.Connection.Id}");
Reject(context);
return;
}
if (!_projectRegistry.CanConnect(projectKey))
{
_logger.Warning($"Connection limit reached for project key: {projectKey}");
Reject(context);
return;
}
var authorizeViaPlugin = _serverPlugin.OnAuthorize(new ConnectionRequest(_server, context.Connection.Id, payload));
if (authorizeViaPlugin)
return;
var project = _projectRegistry.GetOrCreateProject(projectKey);
if (project == null)
{
_logger.Warning($"Failed to create project for key: {projectKey}");
Reject(context);
return;
}
var id = Guid.NewGuid().ToString();
Approve(context, new ConnectionResponse(id, name, payload), project.Id);
_projectRegistry.RegisterConnection(project.Id);
}
public void Approve(RagonContext context, ConnectionResponse result)
public void Approve(RagonContext context, ConnectionResponse result, int projectId)
{
var lobbyPlayer = new RagonLobbyPlayer(context.Connection, result.Id, result.Name, result.Payload);
var lobbyPlayer = new RagonLobbyPlayer(context.Connection, result.Id, result.Name, result.Payload, projectId);
context.SetPlayer(lobbyPlayer);
context.ConnectionStatus = ConnectionStatus.Authorized;
@@ -102,7 +120,7 @@ namespace Ragon.Server.Handler
var sendData = _writer.ToArray();
context.Connection.Reliable.Send(sendData);
_logger.Trace($"Approved {context.Connection.Id} as {playerId}|{context.LobbyPlayer.Name}");
_logger.Trace($"Approved {context.Connection.Id} as {playerId}|{context.LobbyPlayer.Name} for project {projectId}");
}
public void Reject(RagonContext context)
@@ -27,7 +27,7 @@ namespace Ragon.Server.Handler
return;
}
context.UserData.Read(Reader);
context.UserData.Read(Reader, _userDataLimit);
}
}
}
@@ -88,7 +88,7 @@ namespace Ragon.Server.Handler
var roomPlayer = new RagonRoomPlayer(context, lobbyPlayer.Id, lobbyPlayer.Name);
var roomPlugin = _serverPlugin.CreateRoomPlugin(information);
var room = new RagonRoom(roomId, information, roomPlugin);
var room = new RagonRoom(roomId, information, roomPlugin, lobbyPlayer.ProjectId);
room.Plugin.OnAttached(room);
roomPlayer.OnAttached(room);
@@ -43,6 +43,13 @@ public sealed class RoomJoinOperation : BaseOperation
return;
}
if (existsRoom.ProjectId != lobbyPlayer.ProjectId)
{
JoinFailed(context, Writer);
_logger.Warning($"Player {context.Connection.Id}|{lobbyPlayer.Name} tried to join room from different project");
return;
}
var player = new RagonRoomPlayer(context, lobbyPlayer.Id, lobbyPlayer.Name);
context.SetRoom(existsRoom, player);
@@ -57,6 +57,12 @@ public sealed class RoomJoinOrCreateOperation : BaseOperation
if (context.Lobby.FindRoomByScene(_roomParameters.Scene, out var existsRoom))
{
if (existsRoom.ProjectId != lobbyPlayer.ProjectId)
{
_logger.Warning($"Player {context.Connection.Id}|{lobbyPlayer.Name} tried to join room from different project");
return;
}
var player = new RagonRoomPlayer(context, lobbyPlayer.Id, lobbyPlayer.Name);
context.SetRoom(existsRoom, player);
@@ -81,7 +87,7 @@ public sealed class RoomJoinOrCreateOperation : BaseOperation
var roomPlayer = new RagonRoomPlayer(context, lobbyPlayer.Id, lobbyPlayer.Name);
var roomPlugin = _serverPlugin.CreateRoomPlugin(information);
var room = new RagonRoom(roomId, information, roomPlugin);
var room = new RagonRoom(roomId, information, roomPlugin, lobbyPlayer.ProjectId);
_serverPlugin.OnRoomCreate(lobbyPlayer, room);
@@ -45,6 +45,6 @@ public sealed class RoomUserDataOperation : BaseOperation
var room = context.Room;
if (room != null)
room.UserData.Read(Reader);
room.UserData.Read(Reader, _userDataLimit);
}
}
@@ -12,12 +12,17 @@ public class RagonLobbyDispatcher
_lobby = lobby;
}
public void Write(RagonBuffer writer)
public void Write(RagonBuffer writer, int projectId = 0)
{
writer.Clear();
writer.WriteOperation(RagonOperation.ROOM_LIST_UPDATED);
var rooms = _lobby.Rooms;
if (projectId > 0)
{
rooms = rooms.Where(r => r.ProjectId == projectId).ToList();
}
writer.WriteUShort((ushort)rooms.Count);
for (int i = 0; i < rooms.Count; i++)
{
@@ -32,12 +32,14 @@ public class RagonLobbyPlayer
public string Id { get; private set; }
public string Name { get; private set; }
public string Payload { get; private set; }
public RagonLobbyPlayer(INetworkConnection connection, string id, string name, string payload)
public int ProjectId { get; private set; }
public RagonLobbyPlayer(INetworkConnection connection, string id, string name, string payload, int projectId)
{
Id = id;
Name = name;
Connection = connection;
Payload = payload;
ProjectId = projectId;
}
}
@@ -47,7 +47,7 @@ namespace Ragon.Server.Plugin
return;
var operation = (AuthorizationOperation)_server.ResolveHandler(RagonOperation.AUTHORIZE);
operation.Approve(ctx, new ConnectionResponse(id, name, payload));
operation.Approve(ctx, new ConnectionResponse(id, name, payload), 0);
}
public void Reject()
+33 -15
View File
@@ -21,6 +21,7 @@ using Ragon.Server.IO;
using Ragon.Server.Lobby;
using Ragon.Server.Logging;
using Ragon.Server.Plugin;
using Ragon.Server.Project;
using Ragon.Server.Time;
namespace Ragon.Server;
@@ -28,7 +29,7 @@ namespace Ragon.Server;
public class RagonServer : IRagonServer, INetworkListener
{
private const string ServerVersion = "1.4.1";
private readonly IRagonLogger _logger = LoggerManager.GetLogger(nameof(RagonServer));
private readonly INetworkServer _server;
private readonly BaseOperation[] _handlers;
@@ -40,12 +41,14 @@ public class RagonServer : IRagonServer, INetworkListener
private readonly RagonScheduler _scheduler;
private readonly Dictionary<ushort, RagonContext> _contextsByConnection;
private readonly Dictionary<string, RagonContext> _contextsByPlayerId;
private readonly ProjectRegistry _projectRegistry;
private readonly Stopwatch _timer;
private readonly RagonLobbyDispatcher _lobbySerializer;
private readonly long _tickRate = 0;
private bool _isRunning = false;
public bool IsRunning => _isRunning;
public ProjectRegistry ProjectRegistry => _projectRegistry;
public RagonServer(
INetworkServer server,
@@ -57,6 +60,7 @@ public class RagonServer : IRagonServer, INetworkListener
_serverPlugin = plugin;
_contextsByConnection = new Dictionary<ushort, RagonContext>();
_contextsByPlayerId = new Dictionary<string, RagonContext>();
_projectRegistry = new ProjectRegistry(configuration.LimitConnectionsPerProject);
_lobby = new LobbyInMemory();
_lobbySerializer = new RagonLobbyDispatcher(_lobby);
_scheduler = new RagonScheduler();
@@ -73,7 +77,7 @@ public class RagonServer : IRagonServer, INetworkListener
_serverPlugin.OnAttached(this);
_handlers = new BaseOperation[byte.MaxValue];
_handlers[(byte)RagonOperation.AUTHORIZE] = new AuthorizationOperation(_reader, _writer, this, _serverPlugin, contextObserver, configuration);
_handlers[(byte)RagonOperation.AUTHORIZE] = new AuthorizationOperation(_reader, _writer, this, _serverPlugin, contextObserver, configuration, _projectRegistry);
_handlers[(byte)RagonOperation.JOIN_OR_CREATE_ROOM] = new RoomJoinOrCreateOperation(_reader, _writer, plugin, _configuration);
_handlers[(byte)RagonOperation.CREATE_ROOM] = new RoomCreateOperation(_reader, _writer, plugin, _configuration);
_handlers[(byte)RagonOperation.JOIN_ROOM] = new RoomJoinOperation(_reader, _writer);
@@ -152,12 +156,15 @@ public class RagonServer : IRagonServer, INetworkListener
if (room != null)
{
room.DetachPlayer(context.RoomPlayer);
_lobby.RemoveIfEmpty(room);
}
if (context.ConnectionStatus == ConnectionStatus.Authorized)
{
_contextsByPlayerId.Remove(context.LobbyPlayer.Id);
_projectRegistry.UnregisterConnection(context.LobbyPlayer.ProjectId);
}
_logger.Trace($"Disconnected: {connection.Id}");
}
@@ -177,10 +184,13 @@ public class RagonServer : IRagonServer, INetworkListener
room.DetachPlayer(context.RoomPlayer);
_lobby.RemoveIfEmpty(room);
}
if (context.ConnectionStatus == ConnectionStatus.Authorized)
{
_contextsByPlayerId.Remove(context.LobbyPlayer.Id);
_projectRegistry.UnregisterConnection(context.LobbyPlayer.ProjectId);
}
_logger.Trace($"Timeout: {connection.Id}|{context.LobbyPlayer.Name}|{context.LobbyPlayer.Id}");
}
else
@@ -198,9 +208,16 @@ public class RagonServer : IRagonServer, INetworkListener
_writer.Clear();
_reader.Clear();
_reader.FromArray(data);
var operation = _reader.ReadByte();
_handlers[operation]?.Handle(context, channel);
if (operation >= _handlers.Length || _handlers[operation] == null)
{
_logger.Warning($"Invalid operation code: {operation} from connection {connection.Id}");
return;
}
_handlers[operation].Handle(context, channel);
}
}
catch (Exception ex)
@@ -228,13 +245,14 @@ public class RagonServer : IRagonServer, INetworkListener
public void SendRoomList()
{
_lobbySerializer.Write(_writer);
var sendData = _writer.ToArray();
foreach (var (_, value) in _contextsByPlayerId)
foreach (var (_, context) in _contextsByPlayerId)
{
if (value.Room == null) // If only in lobby, then send room list data
value.Connection.Reliable.Send(sendData);
if (context.Room == null) // If only in lobby, then send room list data
{
_lobbySerializer.Write(_writer, context.LobbyPlayer.ProjectId);
var sendData = _writer.ToArray();
context.Connection.Reliable.Send(sendData);
}
}
}
@@ -287,7 +305,7 @@ public class RagonServer : IRagonServer, INetworkListener
{
return _contextsByPlayerId.TryGetValue(playerId, out var context) ? context : null;
}
private void CopyrightInfo()
{
_logger.Info($"Server Version: {ServerVersion}");
@@ -36,6 +36,7 @@ public struct RagonServerConfiguration
public int LimitBufferedEvents;
public int LimitUserDataSize;
public int LimitPropertySize;
public int LimitConnectionsPerProject;
private static Dictionary<string, ServerType> _serverTypes = new Dictionary<string, ServerType>()
{
+1
View File
@@ -24,6 +24,7 @@ public interface IRagonRoom
{
public string Id { get; }
public string Scene { get; }
public int ProjectId { get; }
public int PlayerMin { get; }
public int PlayerMax { get; }
public int PlayerCount { get; }
+4 -2
View File
@@ -31,7 +31,8 @@ public class RagonRoom : IRagonRoom, IRagonAction
public int PlayerMax { get; private set; }
public int PlayerMin { get; private set; }
public int PlayerCount => WaitPlayersList.Count;
public int ProjectId { get; private set; }
public bool IsDone { get; private set; }
public RagonData UserData { get; set; }
@@ -53,13 +54,14 @@ public class RagonRoom : IRagonRoom, IRagonAction
private readonly List<RagonEvent> _bufferedEvents;
private readonly int _limitBufferedEvents;
public RagonRoom(string roomId, RoomInformation info, IRoomPlugin roomPlugin)
public RagonRoom(string roomId, RoomInformation info, IRoomPlugin roomPlugin, int projectId)
{
Id = roomId;
Scene = info.Scene;
PlayerMax = info.Max;
PlayerMin = info.Min;
Plugin = roomPlugin;
ProjectId = projectId;
Players = new Dictionary<ushort, RagonRoomPlayer>(info.Max);
WaitPlayersList = new List<RagonRoomPlayer>(info.Max);