Skip to content

Commit

Permalink
Added a background service which caches/fetches game server details f…
Browse files Browse the repository at this point in the history
…or some specific games, to ensure that these games always have available servers for display
  • Loading branch information
Bernt Andreas Eide committed Mar 8, 2024
1 parent 15e7f71 commit dd496fb
Show file tree
Hide file tree
Showing 5 changed files with 91 additions and 43 deletions.
1 change: 0 additions & 1 deletion GameServerList.App/Helpers/GameList.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using GameServerList.Common.Model;
using GameServerList.Common.Utils;
using Newtonsoft.Json;

namespace GameServerList.Helpers;

Expand Down
83 changes: 83 additions & 0 deletions GameServerList.App/Helpers/GameServerWorker.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
using GameServerList.Common.External;
using GameServerList.Common.Model.A2S;
using GameServerList.Common.Model;
using System.Collections.Concurrent;

namespace GameServerList.Helpers;

public class GameServerWorker : BackgroundService
{
public GameServerWorker()
{
}

protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
var nextUpdateAt = DateTime.MinValue;
var games = GameList
.Games
.Where(g => (g.UseDefinedServerList.HasValue && g.UseDefinedServerList.Value) || g.MasterServer.HasValue)
.ToList();

while (!stoppingToken.IsCancellationRequested)
{
if (DateTime.UtcNow <= nextUpdateAt)
{
Thread.Sleep(100); // put the thread on hold while waiting
continue;
}

foreach (var game in games)
{
try
{
List<GameServerItem> servers = null;

if (game.UseDefinedServerList ?? false)
servers = await QueryServers(game, game.Servers);
else if (game.MasterServer.HasValue)
{
var legacyServers = await A2SQuery.QueryServerList(
A2SQuery.GetMasterServerAddress(game.MasterServer.Value), game
);
servers = await QueryServers(game, legacyServers.Select(s => s.Address).ToList());
}

game.GameServers = servers;
}
catch
{
// skip
}
}

nextUpdateAt = DateTime.UtcNow.AddMinutes(15);
}
}

private static bool IsServerValid(Game game, ServerInfo? server)
{
if (server is null || !server.HasValue)
return false;

if (server.Value.MaxPlayers > 128 || server.Value.MaxPlayers <= 1 || server.Value.Players > server.Value.MaxPlayers)
return false;

if (game.MasterServer.HasValue && game.MasterServer.Value == MasterServer.GoldSrc && !server.Value.Version.EndsWith("/Stdio"))
return false;

return true;
}

private static async Task<List<GameServerItem>> QueryServers(Game game, List<string> servers, int timeout = 1500)
{
var items = new ConcurrentBag<GameServerItem>();
await Parallel.ForEachAsync(servers, async (address, _) =>
{
var obj = await A2SQuery.QueryServerInfo(address, timeout);
if (IsServerValid(game, obj))
items.Add(obj.Value.MapToGameServerItem(game));
});
return [.. items];
}
}
2 changes: 2 additions & 0 deletions GameServerList.App/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
services.AddSingleton<SteamServerBrowserApiService>();
services.AddSingleton<SteamPlayerDetailApiService>();

services.AddHostedService<GameServerWorker>();

services.AddRazorComponents()
.AddInteractiveServerComponents();

Expand Down
3 changes: 3 additions & 0 deletions GameServerList.Common/Model/Game.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,7 @@ public class Game

[JsonIgnore]
public List<string>? Servers { get; set; }

[JsonIgnore]
public List<GameServerItem>? GameServers { get; set; }
}
45 changes: 3 additions & 42 deletions GameServerList.Common/Services/SteamServerBrowserApiService.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
using GameServerList.Common.External;
using GameServerList.Common.Model;
using GameServerList.Common.Model.A2S;
using GameServerList.Common.Model;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Configuration;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using System.Collections.Concurrent;
using System.Net;

namespace GameServerList.Common.Services;
Expand Down Expand Up @@ -48,18 +45,8 @@ public async Task<List<GameServerItem>> FetchServers(Game? game)
{
entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(1);
if (game.UseDefinedServerList ?? false)
{
entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(10);
return await QueryServers(game, game.Servers);
}
else if (game.MasterServer.HasValue)
{
var legacyServers = await A2SQuery.QueryServerList(
A2SQuery.GetMasterServerAddress(game.MasterServer.Value), game
);
return await QueryServers(game, legacyServers.Select(s => s.Address).ToList());
}
if ((game.UseDefinedServerList ?? false) || game.MasterServer.HasValue)
return (game.GameServers is null) ? [] : game.GameServers;
else
{
var gamedirFilter = string.IsNullOrEmpty(game.GameDir) ? string.Empty : $"\\gamedir\\{game.GameDir}";
Expand All @@ -72,20 +59,6 @@ public async Task<List<GameServerItem>> FetchServers(Game? game)
});
}

private static bool IsServerValid(Game game, ServerInfo? server)
{
if (server is null || !server.HasValue)
return false;

if (server.Value.MaxPlayers > 128 || server.Value.MaxPlayers <= 1)
return false;

if (game.MasterServer.HasValue && game.MasterServer.Value == MasterServer.GoldSrc && !server.Value.Version.EndsWith("/Stdio"))
return false;

return true;
}

private async Task<List<T>> Fetch<T>(string url)
{
try
Expand All @@ -105,16 +78,4 @@ private async Task<List<T>> Fetch<T>(string url)
return [];
}
}

private static async Task<List<GameServerItem>> QueryServers(Game game, List<string> servers, int timeout = 1500)
{
var items = new ConcurrentBag<GameServerItem>();
await Parallel.ForEachAsync(servers, async (address, _) =>
{
var obj = await A2SQuery.QueryServerInfo(address, timeout);
if (IsServerValid(game, obj))
items.Add(obj.Value.MapToGameServerItem(game));
});
return [.. items];
}
}

0 comments on commit dd496fb

Please sign in to comment.