added transfer ownership, limit buffered events

This commit is contained in:
2023-07-01 07:47:57 +03:00
parent 20662ae24d
commit 105457ffa0
24 changed files with 134 additions and 108 deletions
+1 -1
View File
@@ -226,7 +226,7 @@ namespace Ragon.Client
var prevOwner = Owner; var prevOwner = Owner;
Owner = player; Owner = player;
HasAuthority = player.PeerId == _client.Room.Local.PeerId; HasAuthority = player.IsLocal;
OwnershipChanged?.Invoke(prevOwner, player); OwnershipChanged?.Invoke(prevOwner, player);
} }
@@ -19,13 +19,13 @@ using Ragon.Protocol;
namespace Ragon.Client; namespace Ragon.Client;
internal class OwnershipEntityHandler: Handler internal class EntityOwnershipHandler: Handler
{ {
private readonly RagonListenerList _listenerList; private readonly RagonListenerList _listenerList;
private readonly RagonPlayerCache _playerCache; private readonly RagonPlayerCache _playerCache;
private readonly RagonEntityCache _entityCache; private readonly RagonEntityCache _entityCache;
public OwnershipEntityHandler( public EntityOwnershipHandler(
RagonListenerList listenerList, RagonListenerList listenerList,
RagonPlayerCache playerCache, RagonPlayerCache playerCache,
RagonEntityCache entityCache) RagonEntityCache entityCache)
@@ -37,13 +37,16 @@ internal class OwnershipEntityHandler: Handler
public void Handle(RagonBuffer buffer) public void Handle(RagonBuffer buffer)
{ {
var newOwnerId = buffer.ReadString(); var newOwnerId = buffer.ReadUShort();
var player = _playerCache.GetPlayerById(newOwnerId);
var entities = buffer.ReadUShort(); var entities = buffer.ReadUShort();
var player = _playerCache.GetPlayerByPeer(newOwnerId);
for (var i = 0; i < entities; i++) for (var i = 0; i < entities; i++)
{ {
var entityId = buffer.ReadUShort(); var entityId = buffer.ReadUShort();
_entityCache.OnOwnershipChanged(player, entityId); _entityCache.OnOwnershipChanged(player, entityId);
RagonLog.Trace("Entity changed owner: " + entityId);
} }
} }
} }
@@ -37,8 +37,8 @@ internal class OwnershipRoomHandler: Handler
public void Handle(RagonBuffer buffer) public void Handle(RagonBuffer buffer)
{ {
var newOwnerId = buffer.ReadString(); var newOwnerId = buffer.ReadUShort();
var player = _playerCache.GetPlayerById(newOwnerId); var player = _playerCache.GetPlayerByPeer(newOwnerId);
_playerCache.OnOwnershipChanged(newOwnerId); _playerCache.OnOwnershipChanged(newOwnerId);
_listenerList.OnOwnershipChanged(player); _listenerList.OnOwnershipChanged(player);
+1 -1
View File
@@ -91,7 +91,7 @@ namespace Ragon.Client
_handlers[(byte)RagonOperation.JOIN_FAILED] = new JoinFailedHandler(_listenerList); _handlers[(byte)RagonOperation.JOIN_FAILED] = new JoinFailedHandler(_listenerList);
_handlers[(byte)RagonOperation.LEAVE_ROOM] = new LeaveRoomHandler(this, _listenerList, _entityCache); _handlers[(byte)RagonOperation.LEAVE_ROOM] = new LeaveRoomHandler(this, _listenerList, _entityCache);
_handlers[(byte)RagonOperation.OWNERSHIP_ROOM_CHANGED] = new OwnershipRoomHandler(_listenerList, _playerCache, _entityCache); _handlers[(byte)RagonOperation.OWNERSHIP_ROOM_CHANGED] = new OwnershipRoomHandler(_listenerList, _playerCache, _entityCache);
_handlers[(byte)RagonOperation.OWNERSHIP_ENTITY_CHANGED] = new OwnershipEntityHandler(_listenerList, _playerCache, _entityCache); _handlers[(byte)RagonOperation.OWNERSHIP_ENTITY_CHANGED] = new EntityOwnershipHandler(_listenerList, _playerCache, _entityCache);
_handlers[(byte)RagonOperation.PLAYER_JOINED] = new PlayerJoinHandler(_playerCache, _listenerList); _handlers[(byte)RagonOperation.PLAYER_JOINED] = new PlayerJoinHandler(_playerCache, _listenerList);
_handlers[(byte)RagonOperation.PLAYER_LEAVED] = new PlayerLeftHandler(_entityCache, _playerCache, _listenerList); _handlers[(byte)RagonOperation.PLAYER_LEAVED] = new PlayerLeftHandler(_entityCache, _playerCache, _listenerList);
_handlers[(byte)RagonOperation.LOAD_SCENE] = new SceneLoadHandler(this, _listenerList); _handlers[(byte)RagonOperation.LOAD_SCENE] = new SceneLoadHandler(this, _listenerList);
+10 -1
View File
@@ -239,8 +239,17 @@ public sealed class RagonEntityCache
internal void OnOwnershipChanged(RagonPlayer player, ushort entityId) internal void OnOwnershipChanged(RagonPlayer player, ushort entityId)
{ {
if (_entityMap.TryGetValue(entityId, out var entity)) if (_entityMap.TryGetValue(entityId, out var entity))
entity.OnOwnershipChanged(player); {
if (player.IsLocal)
_entityList.Add(entity);
else else
_entityList.Remove(entity);
entity.OnOwnershipChanged(player);
}
else
{
RagonLog.Warn($"Entity {entityId} not found!"); RagonLog.Warn($"Entity {entityId} not found!");
} }
} }
}
+5 -3
View File
@@ -71,13 +71,15 @@ public sealed class RagonPlayerCache
} }
} }
public void OnOwnershipChanged(string playerId) public void OnOwnershipChanged(ushort playerPeerId)
{ {
foreach (var player in _players) foreach (var player in _players)
{ {
if (player.Id == playerId) if (player.PeerId == playerPeerId)
{
Owner = player; Owner = player;
player.IsRoomOwner = player.Id == playerId; Owner.IsRoomOwner = true;
}
} }
} }
+1 -1
View File
@@ -24,7 +24,7 @@
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Ragon.Server.ENet\Ragon.Server.ENet.csproj" /> <ProjectReference Include="..\Ragon.Server.ENet\Ragon.Server.ENet.csproj" />
<ProjectReference Include="..\Ragon.Server.DotNetWebSockets\Ragon.Server.DotNetWebSockets.csproj" /> <ProjectReference Include="..\Ragon.Server.WebSocketServer\Ragon.Server.WebSocketServer.csproj" />
<ProjectReference Include="..\Ragon.Server\Ragon.Server.csproj" /> <ProjectReference Include="..\Ragon.Server\Ragon.Server.csproj" />
</ItemGroup> </ItemGroup>
+4 -4
View File
@@ -17,7 +17,7 @@
using NLog; using NLog;
using Ragon.Server; using Ragon.Server;
using Ragon.Server.ENet; using Ragon.Server.ENet;
using Ragon.Server.DotNetWebsockets; using Ragon.Server.WebSocketServer;
using Ragon.Server.IO; using Ragon.Server.IO;
using Ragon.Server.Plugin; using Ragon.Server.Plugin;
@@ -30,8 +30,8 @@ public class Relay
var logger = LogManager.GetLogger("Ragon.Relay"); var logger = LogManager.GetLogger("Ragon.Relay");
logger.Info("Relay Application"); logger.Info("Relay Application");
var configuration = Configuration.Load("relay.config.json"); var configuration = RagonServerConfiguration.Load("relay.config.json");
var serverType = Configuration.GetServerType(configuration.ServerType); var serverType = RagonServerConfiguration.GetServerType(configuration.ServerType);
INetworkServer networkServer = new ENetServer(); INetworkServer networkServer = new ENetServer();
IServerPlugin plugin = new RelayServerPlugin(); IServerPlugin plugin = new RelayServerPlugin();
@@ -41,7 +41,7 @@ public class Relay
networkServer = new ENetServer(); networkServer = new ENetServer();
break; break;
case ServerType.WEBSOCKET: case ServerType.WEBSOCKET:
networkServer = new DotNetWebSocketServer(); networkServer = new WebSocketServer();
break; break;
} }
+1
View File
@@ -9,6 +9,7 @@
"limitConnections": 4095, "limitConnections": 4095,
"limitPlayersPerRoom": 20, "limitPlayersPerRoom": 20,
"limitRooms": 200, "limitRooms": 200,
"limitBufferedEvents": 50,
"webHooks": "webHooks":
{ {
"room-created": "http://127.0.0.1:3000/service/create-room", "room-created": "http://127.0.0.1:3000/service/create-room",
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net6.0</TargetFramework> <TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<RootNamespace>Ragon.WebSockets</RootNamespace> <RootNamespace>Ragon.WebSockets</RootNamespace>
@@ -18,7 +18,7 @@ using NLog;
using System.Net.WebSockets; using System.Net.WebSockets;
using Ragon.Server.IO; using Ragon.Server.IO;
namespace Ragon.Server.DotNetWebsockets; namespace Ragon.Server.WebSocketServer;
public sealed class WebSocketConnection : INetworkConnection public sealed class WebSocketConnection : INetworkConnection
{ {
@@ -17,7 +17,7 @@
using System.Net.WebSockets; using System.Net.WebSockets;
using Ragon.Server.IO; using Ragon.Server.IO;
namespace Ragon.Server.DotNetWebsockets; namespace Ragon.Server.WebSocketServer;
public class WebSocketReliableChannel : INetworkChannel public class WebSocketReliableChannel : INetworkChannel
{ {
@@ -20,9 +20,9 @@ using NLog;
using Ragon.Protocol; using Ragon.Protocol;
using Ragon.Server.IO; using Ragon.Server.IO;
namespace Ragon.Server.DotNetWebsockets; namespace Ragon.Server.WebSocketServer;
public class DotNetWebSocketServer : INetworkServer public class WebSocketServer : INetworkServer
{ {
public Executor Executor => _executor; public Executor Executor => _executor;
@@ -35,7 +35,7 @@ public class DotNetWebSocketServer : INetworkServer
private List<WebSocketConnection> _activeConnections; private List<WebSocketConnection> _activeConnections;
private CancellationTokenSource _cancellationTokenSource; private CancellationTokenSource _cancellationTokenSource;
public DotNetWebSocketServer() public WebSocketServer()
{ {
_sequencer = new Stack<ushort>(); _sequencer = new Stack<ushort>();
_connections = Array.Empty<WebSocketConnection>(); _connections = Array.Empty<WebSocketConnection>();
+5 -2
View File
@@ -33,6 +33,7 @@ public class RagonEntity: IRagonEntity
public IRagonEntityState State => _state; public IRagonEntityState State => _state;
private readonly List<RagonEvent> _bufferedEvents; private readonly List<RagonEvent> _bufferedEvents;
private readonly int _limitBufferedEvents;
private readonly RagonEntityState _state; private readonly RagonEntityState _state;
public RagonEntity(RagonEntityParameters parameters) public RagonEntity(RagonEntityParameters parameters)
@@ -47,6 +48,7 @@ public class RagonEntity: IRagonEntity
_state = new RagonEntityState(this); _state = new RagonEntityState(this);
_bufferedEvents = new List<RagonEvent>(); _bufferedEvents = new List<RagonEvent>();
_limitBufferedEvents = parameters.BufferedEvents;
} }
public void Attach(RagonRoomPlayer owner) public void Attach(RagonRoomPlayer owner)
@@ -56,7 +58,6 @@ public class RagonEntity: IRagonEntity
public void Detach() public void Detach()
{ {
} }
public void RestoreBufferedEvents(RagonRoomPlayer roomPlayer, RagonBuffer writer) public void RestoreBufferedEvents(RagonRoomPlayer roomPlayer, RagonBuffer writer)
@@ -163,7 +164,9 @@ public class RagonEntity: IRagonEntity
return; return;
} }
if (eventMode == RagonReplicationMode.Buffered && targetMode != RagonTarget.Owner) if (eventMode == RagonReplicationMode.Buffered &&
targetMode != RagonTarget.Owner &&
_bufferedEvents.Count < _limitBufferedEvents)
{ {
_bufferedEvents.Add(evnt); _bufferedEvents.Add(evnt);
} }
@@ -24,4 +24,5 @@ public ref struct RagonEntityParameters
public ushort StaticId; public ushort StaticId;
public ushort AttachId; public ushort AttachId;
public RagonAuthority Authority; public RagonAuthority Authority;
public int BufferedEvents;
} }
@@ -28,16 +28,14 @@ public sealed class AuthorizationOperation: IRagonOperation
private Logger _logger = LogManager.GetCurrentClassLogger(); private Logger _logger = LogManager.GetCurrentClassLogger();
private readonly RagonWebHookPlugin _ragonWebHook; private readonly RagonWebHookPlugin _ragonWebHook;
private readonly RagonContextObserver _contextObserver; private readonly RagonContextObserver _contextObserver;
private readonly Configuration _configuration;
private readonly RagonBuffer _writer; private readonly RagonBuffer _writer;
public AuthorizationOperation(RagonWebHookPlugin ragonWebHook, public AuthorizationOperation(
RagonWebHookPlugin ragonWebHook,
RagonContextObserver contextObserver, RagonContextObserver contextObserver,
RagonBuffer writer, RagonBuffer writer)
Configuration configuration)
{ {
_ragonWebHook = ragonWebHook; _ragonWebHook = ragonWebHook;
_configuration = configuration;
_contextObserver = contextObserver; _contextObserver = contextObserver;
_writer = writer; _writer = writer;
} }
@@ -56,11 +54,12 @@ public sealed class AuthorizationOperation: IRagonOperation
return; return;
} }
var configuration = context.Configuration;
var key = reader.ReadString(); var key = reader.ReadString();
var name = reader.ReadString(); var name = reader.ReadString();
var payload = reader.ReadString(); var payload = reader.ReadString();
if (key == _configuration.ServerKey) if (key == configuration.ServerKey)
{ {
if (_ragonWebHook.RequestAuthorization(context, name, payload)) if (_ragonWebHook.RequestAuthorization(context, name, payload))
return; return;
@@ -38,7 +38,8 @@ public sealed class EntityCreateOperation : IRagonOperation
Type = entityType, Type = entityType,
Authority = eventAuthority, Authority = eventAuthority,
AttachId = attachId, AttachId = attachId,
StaticId = 0 StaticId = 0,
BufferedEvents = context.Configuration.LimitBufferedEvents,
}; };
var entity = new RagonEntity(entityParameters); var entity = new RagonEntity(entityParameters);
@@ -9,11 +9,11 @@ public sealed class EntityOwnershipOperation : IRagonOperation
public void Handle(RagonContext context, RagonBuffer reader, RagonBuffer writer) public void Handle(RagonContext context, RagonBuffer reader, RagonBuffer writer)
{ {
var player = context.RoomPlayer; var currentOwner = context.RoomPlayer;
var room = context.Room; var room = context.Room;
var entityId = reader.ReadUShort(); var entityId = reader.ReadUShort();
var playerId = reader.ReadUShort(); var playerPeerId = reader.ReadUShort();
if (!room.Entities.TryGetValue(entityId, out var entity)) if (!room.Entities.TryGetValue(entityId, out var entity))
{ {
@@ -21,29 +21,33 @@ public sealed class EntityOwnershipOperation : IRagonOperation
return; return;
} }
if (entity.Owner.Connection.Id != player.Connection.Id) if (entity.Owner.Connection.Id != currentOwner.Connection.Id)
{ {
_logger.Error($"Player not owner of entity with id {entityId}"); _logger.Error($"Player not owner of entity with id {entityId}");
return; return;
} }
if (!room.Players.TryGetValue(playerId, out var nextOwner)) if (!room.Players.TryGetValue(playerPeerId, out var nextOwner))
{ {
_logger.Error($"Player not found with id {entityId}"); _logger.Error($"Player not found with id {entityId}");
return; return;
} }
writer.Clear(); currentOwner.Entities.Remove(entity);
writer.WriteOperation(RagonOperation.OWNERSHIP_ENTITY_CHANGED);
writer.WriteUShort(nextOwner.Connection.Id);
writer.WriteUShort(1);
writer.WriteUShort(entity.Id);
player.Entities.Remove(entity);
nextOwner.Entities.Add(entity); nextOwner.Entities.Add(entity);
entity.Attach(nextOwner); entity.Attach(nextOwner);
_logger.Trace($"Entity {entity.Id} next owner {nextOwner.Connection.Id}"); _logger.Trace($"Entity {entity.Id} next owner {nextOwner.Connection.Id}");
writer.Clear();
writer.WriteOperation(RagonOperation.OWNERSHIP_ENTITY_CHANGED);
writer.WriteUShort(playerPeerId);
writer.WriteUShort(1);
writer.WriteUShort(entity.Id);
var sendData = writer.ToArray();
foreach (var player in room.PlayerList)
player.Connection.Reliable.Send(sendData);
} }
} }
+1 -1
View File
@@ -91,7 +91,7 @@ public class RagonHttpServer
} }
} }
public void Start(Configuration configuration) public void Start(RagonServerConfiguration configuration)
{ {
_cancellationTokenSource = new CancellationTokenSource(); _cancellationTokenSource = new CancellationTokenSource();
_logger.Info($"Listen at http://0.0.0.0:{configuration.HttpPort}/"); _logger.Info($"Listen at http://0.0.0.0:{configuration.HttpPort}/");
@@ -31,7 +31,7 @@ public class RagonWebHookPlugin
private RagonServer _server; private RagonServer _server;
private HttpClient _httpClient; private HttpClient _httpClient;
public RagonWebHookPlugin(RagonServer server, Configuration configuration) public RagonWebHookPlugin(RagonServer server, RagonServerConfiguration configuration)
{ {
_webHooks = new Dictionary<string, string>(configuration.WebHooks); _webHooks = new Dictionary<string, string>(configuration.WebHooks);
_httpClient = new HttpClient(); _httpClient = new HttpClient();
+3 -1
View File
@@ -26,7 +26,7 @@ public class RagonContext
public ConnectionStatus ConnectionStatus { get; set; } public ConnectionStatus ConnectionStatus { get; set; }
public INetworkConnection Connection { get; } public INetworkConnection Connection { get; }
public IExecutor Executor { get; private set; } public IExecutor Executor { get; private set; }
public RagonServerConfiguration Configuration { get; private set; }
public IRagonLobby Lobby { get; private set; } public IRagonLobby Lobby { get; private set; }
public RagonLobbyPlayer? LobbyPlayer { get; private set; } public RagonLobbyPlayer? LobbyPlayer { get; private set; }
@@ -37,11 +37,13 @@ public class RagonContext
public RagonContext( public RagonContext(
INetworkConnection connection, INetworkConnection connection,
RagonServerConfiguration configuration,
IExecutor executor, IExecutor executor,
IRagonLobby lobby, IRagonLobby lobby,
RagonScheduler scheduler) RagonScheduler scheduler)
{ {
ConnectionStatus = ConnectionStatus.Unauthorized; ConnectionStatus = ConnectionStatus.Unauthorized;
Configuration = configuration;
Connection = connection; Connection = connection;
Executor = executor; Executor = executor;
Lobby = lobby; Lobby = lobby;
+4 -4
View File
@@ -36,7 +36,7 @@ public class RagonServer : IRagonServer, INetworkListener
private readonly IServerPlugin _serverPlugin; private readonly IServerPlugin _serverPlugin;
private readonly Thread _dedicatedThread; private readonly Thread _dedicatedThread;
private readonly Executor _executor; private readonly Executor _executor;
private readonly Configuration _configuration; private readonly RagonServerConfiguration _configuration;
private readonly RagonWebHookPlugin _webhooks; private readonly RagonWebHookPlugin _webhooks;
private readonly RagonHttpServer _httpServer; private readonly RagonHttpServer _httpServer;
private readonly RagonBuffer _reader; private readonly RagonBuffer _reader;
@@ -50,7 +50,7 @@ public class RagonServer : IRagonServer, INetworkListener
public RagonServer( public RagonServer(
INetworkServer server, INetworkServer server,
IServerPlugin plugin, IServerPlugin plugin,
Configuration configuration) RagonServerConfiguration configuration)
{ {
_server = server; _server = server;
_executor = _server.Executor; _executor = _server.Executor;
@@ -74,7 +74,7 @@ public class RagonServer : IRagonServer, INetworkListener
_serverPlugin.OnAttached(this); _serverPlugin.OnAttached(this);
_handlers = new IRagonOperation[byte.MaxValue]; _handlers = new IRagonOperation[byte.MaxValue];
_handlers[(byte) RagonOperation.AUTHORIZE] = new AuthorizationOperation(_webhooks, contextObserver, _writer, configuration); _handlers[(byte) RagonOperation.AUTHORIZE] = new AuthorizationOperation(_webhooks, contextObserver, _writer);
_handlers[(byte) RagonOperation.JOIN_OR_CREATE_ROOM] = new RoomJoinOrCreateOperation(plugin, _webhooks); _handlers[(byte) RagonOperation.JOIN_OR_CREATE_ROOM] = new RoomJoinOrCreateOperation(plugin, _webhooks);
_handlers[(byte) RagonOperation.CREATE_ROOM] = new RoomCreateOperation(plugin, _webhooks); _handlers[(byte) RagonOperation.CREATE_ROOM] = new RoomCreateOperation(plugin, _webhooks);
_handlers[(byte) RagonOperation.JOIN_ROOM] = new RoomJoinOperation(_webhooks); _handlers[(byte) RagonOperation.JOIN_ROOM] = new RoomJoinOperation(_webhooks);
@@ -136,7 +136,7 @@ public class RagonServer : IRagonServer, INetworkListener
public void OnConnected(INetworkConnection connection) public void OnConnected(INetworkConnection connection)
{ {
var context = new RagonContext(connection, _executor, _lobby, _scheduler); var context = new RagonContext(connection, _configuration, _executor, _lobby, _scheduler);
_logger.Trace($"Connected: {connection.Id}"); _logger.Trace($"Connected: {connection.Id}");
_contextsByConnection.Add(connection.Id, context); _contextsByConnection.Add(connection.Id, context);
@@ -31,7 +31,7 @@ public class WebHook
} }
[Serializable] [Serializable]
public struct Configuration public struct RagonServerConfiguration
{ {
public string ServerKey; public string ServerKey;
public string ServerType; public string ServerType;
@@ -43,6 +43,7 @@ public struct Configuration
public int LimitConnections; public int LimitConnections;
public int LimitPlayersPerRoom; public int LimitPlayersPerRoom;
public int LimitRooms; public int LimitRooms;
public int LimitBufferedEvents;
public Dictionary<string, string> WebHooks; public Dictionary<string, string> WebHooks;
private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
@@ -53,12 +54,12 @@ public struct Configuration
{"websocket", Server.ServerType.WEBSOCKET} {"websocket", Server.ServerType.WEBSOCKET}
}; };
public static Configuration Load(string filePath) public static RagonServerConfiguration Load(string filePath)
{ {
CopyrightInfo(); CopyrightInfo();
var data = File.ReadAllText(filePath); var data = File.ReadAllText(filePath);
var configuration = JsonConvert.DeserializeObject<Configuration>(data); var configuration = JsonConvert.DeserializeObject<RagonServerConfiguration>(data);
return configuration; return configuration;
} }
+1 -1
View File
@@ -6,7 +6,7 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ragon.Protocol", "Ragon.Pro
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ragon.Server", "Ragon.Server\Ragon.Server.csproj", "{F4AA86B9-2486-4B53-BA77-43D958A2FDC3}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ragon.Server", "Ragon.Server\Ragon.Server.csproj", "{F4AA86B9-2486-4B53-BA77-43D958A2FDC3}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ragon.Server.DotNetWebSockets", "Ragon.Server.DotNetWebSockets\Ragon.Server.DotNetWebSockets.csproj", "{81050343-A9B8-487B-86C8-7A5B7DD9C39B}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ragon.Server.WebSocketServer", "Ragon.Server.WebSocketServer\Ragon.Server.WebSocketServer.csproj", "{81050343-A9B8-487B-86C8-7A5B7DD9C39B}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ragon.Server.ENet", "Ragon.Server.ENet\Ragon.Server.ENet.csproj", "{DD79AC4F-9E5C-4938-850E-805D537E68D0}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ragon.Server.ENet", "Ragon.Server.ENet\Ragon.Server.ENet.csproj", "{DD79AC4F-9E5C-4938-850E-805D537E68D0}"
EndProject EndProject