Skip to content

Commit

Permalink
Server stats (#133)
Browse files Browse the repository at this point in the history
* Add stats endpoint with player, server, ticket and party count

* Update playlists

* Remove unnecessary .ToList

* Use REPR for auth and stats endpoints
Clean up 'Program.cs' a bit
Fix matchmaking namespace
  • Loading branch information
tobibodamer authored Oct 3, 2024
1 parent 2c6d9c6 commit 6d764a0
Show file tree
Hide file tree
Showing 29 changed files with 1,112 additions and 921 deletions.
3 changes: 3 additions & 0 deletions MatchmakingServer.Tests/MatchmakerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

using H2MLauncher.Core.Matchmaking.Models;

using MatchmakingServer.Matchmaking;
using MatchmakingServer.Matchmaking.Models;

namespace MatchmakingServer.Tests
{
public class MatchmakerTests
Expand Down
20 changes: 20 additions & 0 deletions MatchmakingServer/Api/EndpointRouteBuilderExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using MatchmakingServer.Authentication;
using MatchmakingServer.Stats;

namespace MatchmakingServer.Api
{
public static class EndpointRouteBuilderExtensions
{
public static void MapEndpoints(this WebApplication app)
{
app.MapEndpoint<AuthenticationEndpoint>();
app.MapEndpoint<StatsEndpoint>();
}

private static IEndpointRouteBuilder MapEndpoint<TEndpoint>(this IEndpointRouteBuilder app) where TEndpoint : IEndpoint
{
TEndpoint.Map(app);
return app;
}
}
}
7 changes: 7 additions & 0 deletions MatchmakingServer/Api/IEndpoint.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace MatchmakingServer.Api
{
public interface IEndpoint
{
static abstract void Map(IEndpointRouteBuilder app);
}
}
12 changes: 12 additions & 0 deletions MatchmakingServer/Api/RouteHandlerBuilderExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
namespace MatchmakingServer.Api
{
public static class RouteHandlerBuilderExtensions
{
public static RouteHandlerBuilder WithValidation<TRequest>(this RouteHandlerBuilder builder)
{
return builder
.AddEndpointFilter<ValidationFilter<TRequest>>()
.ProducesValidationProblem();
}
}
}
24 changes: 24 additions & 0 deletions MatchmakingServer/Api/ValidationFilter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using FluentValidation;

namespace MatchmakingServer.Api;

public class ValidationFilter<T>(IValidator<T> validator) : IEndpointFilter
{
public async ValueTask<object?> InvokeAsync(EndpointFilterInvocationContext context, EndpointFilterDelegate next)
{
T? request = context.Arguments.OfType<T>().First();

if (request is null)
{
return Results.BadRequest();
}

var validationResult = await validator.ValidateAsync(request);
if (!validationResult.IsValid)
{
return Results.ValidationProblem(validationResult.ToDictionary());
}

return await next(context);
}
}
42 changes: 42 additions & 0 deletions MatchmakingServer/ApplicationSetup.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
using MatchmakingServer.SignalR;

using Serilog;

namespace MatchmakingServer;

public static class ApplicationSetup
{
public static void UseRequestLogging(this IApplicationBuilder app)
{
app.UseSerilogRequestLogging(options =>
{
options.MessageTemplate = "HTTP {RequestMethod} {RequestPath} responded {StatusCode} in {Elapsed:0.0000} ms ({ClientAppName}/{ClientAppVersion})";
options.EnrichDiagnosticContext = (diagnosticContext, httpContext) =>
{
if (httpContext.Request.Headers.TryGetValue("X-App-Name", out var appNameValues) && appNameValues.Count != 0)
{
diagnosticContext.Set("ClientAppName", appNameValues.FirstOrDefault());
}
else
{
diagnosticContext.Set("ClientAppName", "Unknown");
}

if (httpContext.Request.Headers.TryGetValue("X-App-Version", out var appVersionValues) && appVersionValues.Count != 0)
{
diagnosticContext.Set("ClientAppVersion", appVersionValues.FirstOrDefault());
}
else
{
diagnosticContext.Set("ClientAppVersion", "?");
}
};
});
}

public static void MapHubs(this IEndpointRouteBuilder app)
{
app.MapHub<QueueingHub>("/Queue");
app.MapHub<PartyHub>("/Party");
}
}
41 changes: 41 additions & 0 deletions MatchmakingServer/Authentication/AuthenticationEndpoint.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
using System.Security.Claims;

using FluentValidation;

using MatchmakingServer.Api;

using Microsoft.AspNetCore.Authentication.BearerToken;
using Microsoft.AspNetCore.Http.HttpResults;

namespace MatchmakingServer.Authentication
{
public class AuthenticationEndpoint : IEndpoint
{
public static void Map(IEndpointRouteBuilder app) => app
.MapGet("/login", Handle)
.WithSummary("Logs the user in with the UID and player name and returns a bearer token.")
.WithValidation<Request>();

public record Request(string Uid, string PlayerName);
public class RequestValidator : AbstractValidator<Request>
{
public RequestValidator()
{
RuleFor(x => x.Uid).NotEmpty().MaximumLength(36);
RuleFor(x => x.PlayerName).NotEmpty().MaximumLength(69);
}
}

private static SignInHttpResult Handle([AsParameters] Request request)
{
ClaimsPrincipal claimsPrincipal = new(
new ClaimsIdentity(
[new Claim(ClaimTypes.Name, request.PlayerName), new Claim(ClaimTypes.NameIdentifier, request.Uid)],
BearerTokenDefaults.AuthenticationScheme
)
);

return TypedResults.SignIn(claimsPrincipal);
}
}
}
1 change: 1 addition & 0 deletions MatchmakingServer/Controllers/PlaylistsController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using H2MLauncher.Core.Networking.GameServer.HMW;
using H2MLauncher.Core.Services;

using MatchmakingServer.Matchmaking;
using MatchmakingServer.SignalR;

using Microsoft.AspNetCore.Mvc;
Expand Down
57 changes: 28 additions & 29 deletions MatchmakingServer/Matchmaking/MMServerPriorityComparer.cs
Original file line number Diff line number Diff line change
@@ -1,38 +1,37 @@
namespace MatchmakingServer
namespace MatchmakingServer.Matchmaking;

class MMServerPriorityComparer : IComparer<GameServer>
{
class MMServerPriorityComparer : IComparer<GameServer>
public int Compare(GameServer? x, GameServer? y)
{
public int Compare(GameServer? x, GameServer? y)
if (x?.LastServerInfo is null || y?.LastServerInfo is null)
{
if (x?.LastServerInfo is null || y?.LastServerInfo is null)
{
return 0;
}

// Check if we should prioritize based on TotalScore < 1000
bool xIsHalfFull = x.LastStatusResponse?.TotalScore < 1000 && x.LastServerInfo.RealPlayerCount < 6;
bool yIsHalfFull = y.LastStatusResponse?.TotalScore < 1000 && x.LastServerInfo.RealPlayerCount < 6;

// Case 1: If both servers are half empty and under the score limit,
// prioritize by player count (servers with fewer players should be prioritized)
if (xIsHalfFull && yIsHalfFull)
{
return x.LastServerInfo.RealPlayerCount.CompareTo(y.LastServerInfo.RealPlayerCount);
}
return 0;
}

// Case 2: If one server is half full and under the score limit and the other one is not,
// prioritize the half full server
if (xIsHalfFull && !yIsHalfFull)
{
return -1;
}
if (!xIsHalfFull && yIsHalfFull)
{
return 1;
}
// Check if we should prioritize based on TotalScore < 1000
bool xIsHalfFull = x.LastStatusResponse?.TotalScore < 1000 && x.LastServerInfo.RealPlayerCount < 6;
bool yIsHalfFull = y.LastStatusResponse?.TotalScore < 1000 && x.LastServerInfo.RealPlayerCount < 6;

// Case 3: If both servers are over the score limit, prioritize by fewer players
// Case 1: If both servers are half empty and under the score limit,
// prioritize by player count (servers with fewer players should be prioritized)
if (xIsHalfFull && yIsHalfFull)
{
return x.LastServerInfo.RealPlayerCount.CompareTo(y.LastServerInfo.RealPlayerCount);
}

// Case 2: If one server is half full and under the score limit and the other one is not,
// prioritize the half full server
if (xIsHalfFull && !yIsHalfFull)
{
return -1;
}
if (!xIsHalfFull && yIsHalfFull)
{
return 1;
}

// Case 3: If both servers are over the score limit, prioritize by fewer players
return x.LastServerInfo.RealPlayerCount.CompareTo(y.LastServerInfo.RealPlayerCount);
}
}
Loading

0 comments on commit 6d764a0

Please sign in to comment.