chore(game-server): merge main (emulator fixes: contracts, shop, fortuna, friends, admin, presence) #16
@@ -1,12 +1,31 @@
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
|
||||
namespace Prospect.Server.Api.Hubs;
|
||||
|
||||
public class CycleHub : Hub
|
||||
{
|
||||
private readonly SignalRConnectionRegistry _registry;
|
||||
|
||||
public CycleHub(SignalRConnectionRegistry registry)
|
||||
{
|
||||
_registry = registry;
|
||||
}
|
||||
|
||||
public override async Task OnConnectedAsync()
|
||||
{
|
||||
Console.WriteLine("Connected {0}", Context.ConnectionId);
|
||||
// The client connects with the SignalR URL returned by GetSignalRConnection, which
|
||||
// carries a `uid` query param — use it to map this connection to the player so
|
||||
// server-initiated messages can target a single player instead of everyone.
|
||||
var uid = Context.GetHttpContext()?.Request.Query["uid"].ToString();
|
||||
if (!string.IsNullOrEmpty(uid))
|
||||
_registry.Add(uid, Context.ConnectionId);
|
||||
Console.WriteLine("Connected {0} (uid={1})", Context.ConnectionId, uid);
|
||||
await base.OnConnectedAsync();
|
||||
}
|
||||
|
||||
public override async Task OnDisconnectedAsync(Exception? exception)
|
||||
{
|
||||
_registry.Remove(Context.ConnectionId);
|
||||
await base.OnDisconnectedAsync(exception);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
using System.Collections.Concurrent;
|
||||
|
||||
namespace Prospect.Server.Api.Hubs;
|
||||
|
||||
// Maps players to their live SignalR connection so server-initiated messages (e.g. the
|
||||
// matchmaking-success "travel to match" signal) can target a single player instead of being
|
||||
// broadcast to everyone. The client's SignalR URL carries a `uid` query param (added by
|
||||
// GetSignalRConnection); CycleHub records it on connect.
|
||||
public class SignalRConnectionRegistry
|
||||
{
|
||||
private readonly ConcurrentDictionary<string, string> _userToConn = new();
|
||||
private readonly ConcurrentDictionary<string, string> _connToUser = new();
|
||||
|
||||
public void Add(string userId, string connectionId)
|
||||
{
|
||||
if (string.IsNullOrEmpty(userId)) return;
|
||||
_userToConn[userId] = connectionId;
|
||||
_connToUser[connectionId] = userId;
|
||||
}
|
||||
|
||||
public void Remove(string connectionId)
|
||||
{
|
||||
if (_connToUser.TryRemove(connectionId, out var userId))
|
||||
_userToConn.TryRemove(userId, out _);
|
||||
}
|
||||
|
||||
public string? GetConnection(string userId)
|
||||
=> _userToConn.TryGetValue(userId, out var c) ? c : null;
|
||||
}
|
||||
@@ -24,13 +24,15 @@ public class EnterMatchmakingMatchFunction : ICloudScriptFunction<FYEnterMatchAz
|
||||
private readonly IHubContext<CycleHub> _hubContext;
|
||||
private readonly UserDataService _userDataService;
|
||||
private readonly TitleDataService _titleDataService;
|
||||
private readonly SignalRConnectionRegistry _registry;
|
||||
|
||||
public EnterMatchmakingMatchFunction(IHubContext<CycleHub> hubContext, IHttpContextAccessor httpContextAccessor, UserDataService userDataService, TitleDataService titleDataService)
|
||||
public EnterMatchmakingMatchFunction(IHubContext<CycleHub> hubContext, IHttpContextAccessor httpContextAccessor, UserDataService userDataService, TitleDataService titleDataService, SignalRConnectionRegistry registry)
|
||||
{
|
||||
_httpContextAccessor = httpContextAccessor;
|
||||
_hubContext = hubContext;
|
||||
_userDataService = userDataService;
|
||||
_titleDataService = titleDataService;
|
||||
_registry = registry;
|
||||
}
|
||||
|
||||
public async Task<FYEnterMatchmakingResult> ExecuteAsync(FYEnterMatchAzureFunction request)
|
||||
@@ -94,11 +96,20 @@ public class EnterMatchmakingMatchFunction : ICloudScriptFunction<FYEnterMatchAz
|
||||
}
|
||||
);
|
||||
|
||||
await _hubContext.Clients.All.SendAsync("OnSquadMatchmakingSuccess", new OnSquadMatchmakingSuccessMessage {
|
||||
// Send the "matchmaking succeeded / travel to match" signal only to the deploying
|
||||
// player (via their mapped SignalR connection) — broadcasting to Clients.All made
|
||||
// every connected player travel too. Fall back to broadcast only if the connection
|
||||
// isn't mapped yet.
|
||||
var deployMessage = new OnSquadMatchmakingSuccessMessage {
|
||||
Success = true,
|
||||
SessionID = request.MapName, // TODO: Need to implement TryGetCompleteSquadInfo and pass squad info
|
||||
SessionID = request.MapName, // TODO: pass real squad session info
|
||||
SquadID = request.SquadId,
|
||||
});
|
||||
};
|
||||
var connectionId = _registry.GetConnection(userId);
|
||||
if (connectionId != null)
|
||||
await _hubContext.Clients.Client(connectionId).SendAsync("OnSquadMatchmakingSuccess", deployMessage);
|
||||
else
|
||||
await _hubContext.Clients.All.SendAsync("OnSquadMatchmakingSuccess", deployMessage);
|
||||
|
||||
return new FYEnterMatchmakingResult
|
||||
{
|
||||
|
||||
@@ -2,6 +2,7 @@ using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Prospect.Server.Api.Config;
|
||||
using Prospect.Server.Api.Services.Auth.Extensions;
|
||||
using Prospect.Server.Api.Services.CloudScript.Models;
|
||||
|
||||
namespace Prospect.Server.Api.Services.CloudScript.Functions;
|
||||
@@ -43,7 +44,13 @@ public class GetSignalRConnection : ICloudScriptFunction<FYGetSignalRConnection,
|
||||
catch { /* keep the configured URL on any parsing issue */ }
|
||||
}
|
||||
|
||||
_logger.LogInformation("GetSignalRConnection: remoteIp={Remote} lan={Lan} -> {Url}", remote, lan, url);
|
||||
// Tag the connection with the player id so the hub can map connection -> user and
|
||||
// target server messages (deploy signal) instead of broadcasting to everyone.
|
||||
var userId = _httpContextAccessor.HttpContext?.User.FindAuthUserId();
|
||||
if (!string.IsNullOrEmpty(userId))
|
||||
url += (url.Contains('?') ? "&" : "?") + "uid=" + Uri.EscapeDataString(userId);
|
||||
|
||||
_logger.LogInformation("GetSignalRConnection: remoteIp={Remote} lan={Lan} uid={Uid} -> {Url}", remote, lan, userId, url);
|
||||
|
||||
return Task.FromResult(new FYGetSignalRConnectionResult
|
||||
{
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -36,6 +36,7 @@ public class Startup
|
||||
services.AddSingleton<DbUserDataService>();
|
||||
|
||||
services.AddSingleton<Prospect.Server.Api.Services.Squad.SquadService>();
|
||||
services.AddSingleton<Prospect.Server.Api.Hubs.SignalRConnectionRegistry>();
|
||||
|
||||
services.AddHostedService<QosService>();
|
||||
services.AddSingleton<QosServer>();
|
||||
|
||||
Reference in New Issue
Block a user