mirror of
https://github.com/AndrioCelos/TableturfBattleApp.git
synced 2026-07-04 17:50:56 -05:00
Track inactive games and add a command to lock the server for updates
A game that has had no activity for 5 minutes is considered inactive. There can be a limited number of inactive games before they are deleted. A console command is added that will wait for all games to become inactive before stopping the server, and prevent starting new games.
This commit is contained in:
parent
7a7d03d165
commit
673eb18aa9
|
|
@ -47,7 +47,9 @@ preGameForm.addEventListener('submit', e => {
|
|||
setGameUrl(response.gameID);
|
||||
|
||||
getGameInfo(response.gameID, 0);
|
||||
} else
|
||||
} else if (request.status == 503)
|
||||
communicationError('The server is temporarily locked for an update. Please try again soon.', true, () => setLoadingMessage(null));
|
||||
else
|
||||
communicationError('Unable to create the room.', true, () => setLoadingMessage(null));
|
||||
});
|
||||
request.addEventListener('error', () => {
|
||||
|
|
|
|||
|
|
@ -24,6 +24,9 @@ public class Game {
|
|||
[JsonProperty("startSpaces")]
|
||||
public Point[]? StartSpaces;
|
||||
|
||||
[JsonIgnore]
|
||||
internal DateTime AbandonedSince = DateTime.UtcNow;
|
||||
|
||||
public Game(int maxPlayers) => this.MaxPlayers = maxPlayers;
|
||||
|
||||
public bool TryAddPlayer(Player player, out int playerIndex, out Error error) {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using System.Globalization;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
using System.Net;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
|
|
@ -22,7 +23,12 @@ internal class Program {
|
|||
internal static HttpServer? httpServer;
|
||||
|
||||
internal static Dictionary<Guid, Game> games = new();
|
||||
internal static Dictionary<Guid, Game> inactiveGames = new();
|
||||
internal static readonly Timer timer = new(500);
|
||||
private static bool lockdown;
|
||||
|
||||
private const int InactiveGameLimit = 1000;
|
||||
private static readonly TimeSpan InactiveGameTimeout = TimeSpan.FromMinutes(5);
|
||||
|
||||
private static string? GetClientRootPath() {
|
||||
var directory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
|
||||
|
|
@ -49,7 +55,21 @@ internal class Program {
|
|||
else
|
||||
Console.WriteLine($"Client files were not found.");
|
||||
|
||||
Thread.Sleep(Timeout.Infinite);
|
||||
while (true) {
|
||||
var s = Console.ReadLine();
|
||||
if (s == null)
|
||||
Thread.Sleep(Timeout.Infinite);
|
||||
else {
|
||||
s = s.Trim().ToLower();
|
||||
if (s == "update") {
|
||||
if (games.Count == 0)
|
||||
Environment.Exit(2);
|
||||
lockdown = true;
|
||||
Console.WriteLine("Locking server for update.");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private static void Timer_Elapsed(object? sender, ElapsedEventArgs e) {
|
||||
|
|
@ -57,8 +77,20 @@ internal class Program {
|
|||
foreach (var (id, game) in games) {
|
||||
lock (game.Players) {
|
||||
game.Tick();
|
||||
if (DateTime.UtcNow - game.AbandonedSince >= InactiveGameTimeout) {
|
||||
games.Remove(id);
|
||||
inactiveGames.Add(id, game);
|
||||
Console.WriteLine($"{games.Count} games active; {inactiveGames.Count} inactive");
|
||||
if (lockdown && games.Count == 0)
|
||||
Environment.Exit(2);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (inactiveGames.Count >= InactiveGameLimit) {
|
||||
foreach (var (k, _) in inactiveGames.Select(e => (e.Key, e.Value.AbandonedSince)).OrderBy(e => e.AbandonedSince).Take(InactiveGameLimit / 2))
|
||||
inactiveGames.Remove(k);
|
||||
Console.WriteLine($"{games.Count} games active; {inactiveGames.Count} inactive");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -85,6 +117,8 @@ internal class Program {
|
|||
if (e.Request.HttpMethod != "POST") {
|
||||
e.Response.AddHeader("Allow", "POST");
|
||||
SetErrorResponse(e.Response, new(HttpStatusCode.MethodNotAllowed, "MethodNotAllowed", "Invalid request method for this endpoint."));
|
||||
} else if (lockdown) {
|
||||
SetErrorResponse(e.Response, new(HttpStatusCode.ServiceUnavailable, "ServerLocked", "The server is temporarily locked for an update. Please try again soon."));
|
||||
} else if (e.Request.ContentLength64 >= 65536) {
|
||||
e.Response.StatusCode = (int) HttpStatusCode.RequestEntityTooLarge;
|
||||
} else {
|
||||
|
|
@ -112,8 +146,10 @@ internal class Program {
|
|||
var game = new Game(maxPlayers);
|
||||
game.Players.Add(new(game, name, clientToken));
|
||||
games.Add(game.ID, game);
|
||||
timer.Start();
|
||||
|
||||
SetResponse(e.Response, HttpStatusCode.OK, "application/json", JsonConvert.SerializeObject(new { gameID = game.ID, clientToken, maxPlayers }));
|
||||
Console.WriteLine($"New game started: {game.ID}; {games.Count} games active; {inactiveGames.Count} inactive");
|
||||
} catch (ArgumentException) {
|
||||
SetErrorResponse(e.Response, new(HttpStatusCode.BadRequest, "InvalidRequestData", "Invalid form data"));
|
||||
}
|
||||
|
|
@ -130,7 +166,7 @@ internal class Program {
|
|||
return;
|
||||
}
|
||||
lock (games) {
|
||||
if (!games.TryGetValue(gameID, out var game)) {
|
||||
if (!games.TryGetValue(gameID, out var game) && !inactiveGames.TryGetValue(gameID, out game)) {
|
||||
SetErrorResponse(e.Response, new(HttpStatusCode.NotFound, "GameNotFound", "Game not found."));
|
||||
return;
|
||||
}
|
||||
|
|
@ -502,4 +538,17 @@ internal class Program {
|
|||
SetResponse(response, HttpStatusCode.OK, "application/json", jsonContent);
|
||||
}
|
||||
}
|
||||
|
||||
internal static bool TryGetGame(Guid gameID, [MaybeNullWhen(false)] out Game game) {
|
||||
if (games.TryGetValue(gameID, out game)) {
|
||||
game.AbandonedSince = DateTime.UtcNow;
|
||||
return true;
|
||||
} else if (inactiveGames.TryGetValue(gameID, out game)) {
|
||||
inactiveGames.Remove(gameID);
|
||||
games[gameID] = game;
|
||||
game.AbandonedSince = DateTime.UtcNow;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,8 +5,10 @@ using WebSocketSharp.Server;
|
|||
namespace TableturfBattleServer;
|
||||
|
||||
internal class TableturfWebSocketBehaviour : WebSocketBehavior {
|
||||
public Guid GameID { get; set; }
|
||||
public Guid ClientToken { get; set; }
|
||||
public Guid GameID { get; private set; }
|
||||
public Guid ClientToken { get; private set; }
|
||||
public Game? Game { get; private set; }
|
||||
public Player? Player { get; private set; }
|
||||
|
||||
protected override void OnOpen() {
|
||||
var args = this.Context.RequestUri.Query[1..].Split('&').Select(s => s.Split('=', 2)).Where(a => a.Length == 2)
|
||||
|
|
@ -17,11 +19,13 @@ internal class TableturfWebSocketBehaviour : WebSocketBehavior {
|
|||
this.ClientToken = clientToken;
|
||||
|
||||
// Send an initial state payload.
|
||||
if (Program.games.TryGetValue(this.GameID, out var game)) {
|
||||
if (Program.TryGetGame(this.GameID, out var game)) {
|
||||
this.Game = game;
|
||||
DTO.PlayerData? playerData = null;
|
||||
for (int i = 0; i < game.Players.Count; i++) {
|
||||
var player = game.Players[i];
|
||||
if (player.Token == this.ClientToken) {
|
||||
this.Player = player;
|
||||
playerData = new(i, player);
|
||||
break;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user