From 2ea4e6528219e117cb7d811ea0cd4f57c9deece9 Mon Sep 17 00:00:00 2001 From: Ben Stein Date: Tue, 15 Nov 2022 13:24:15 -0500 Subject: [PATCH 1/2] Stability changes to the Unity 'add challenge data' endpoint - semaphore locked, and will no longer attempt to update gamebrain console urls if the data is already present. --- .../Features/UnityGames/IUnityGameService.cs | 1 + .../UnityGames/UnityGameController.cs | 40 +++++++++++++++++-- .../Features/UnityGames/UnityGameService.cs | 17 +++++--- 3 files changed, 49 insertions(+), 9 deletions(-) diff --git a/src/Gameboard.Api/Features/UnityGames/IUnityGameService.cs b/src/Gameboard.Api/Features/UnityGames/IUnityGameService.cs index c3187cef..a3a0c282 100644 --- a/src/Gameboard.Api/Features/UnityGames/IUnityGameService.cs +++ b/src/Gameboard.Api/Features/UnityGames/IUnityGameService.cs @@ -6,6 +6,7 @@ public interface IUnityGameService { Task AddChallenge(NewUnityChallenge newChallenge, User actor); Task CreateMissionEvent(UnityMissionUpdate model, Api.User actor); + Task HasChallengeData(NewUnityChallenge newUnityChallenge); Task DeleteChallengeData(string gameId); bool IsUnityGame(Game game); bool IsUnityGame(Data.Game game); diff --git a/src/Gameboard.Api/Features/UnityGames/UnityGameController.cs b/src/Gameboard.Api/Features/UnityGames/UnityGameController.cs index 41c02c15..324259ce 100644 --- a/src/Gameboard.Api/Features/UnityGames/UnityGameController.cs +++ b/src/Gameboard.Api/Features/UnityGames/UnityGameController.cs @@ -6,6 +6,7 @@ using System.Net.Http; using System.Net.Http.Headers; using System.Net.Http.Json; +using System.Threading; using System.Threading.Tasks; using AutoMapper; using Gameboard.Api.Features.UnityGames; @@ -25,6 +26,8 @@ namespace Gameboard.Api.Controllers; [Authorize] public class UnityGameController : _Controller { + private static SemaphoreSlim SP_CHALLENGE_DATA = new SemaphoreSlim(1, 1); + private readonly ConsoleActorMap _actorMap; private readonly GameService _gameService; private readonly IHttpClientFactory _httpClientFactory; @@ -127,7 +130,35 @@ public async Task UndeployUnitySpace([FromQuery] string gid, [FromRoute] ); await Validate(model); - var result = await _unityGameService.AddChallenge(model, Actor); + + // each _team_ will only get one copy of the challenge, and by rule, that challenge must have the id + // of the topo gamespace ID. If it's already in the DB, send them on their way with the challenge we've already got + // + // semaphore locking because, if i don't, may not sleep during the competition + Data.Challenge challengeData = null; + try + { + Console.Write("Entering the Unity challenge data semaphore"); + await SP_CHALLENGE_DATA.WaitAsync(); + + challengeData = await _unityGameService.HasChallengeData(model); + if (challengeData != null) + { + return challengeData; + } + + // otherwise, add new challenge data and send gamebrain the ids of the consoles (which are based on the challenge id) + challengeData = await _unityGameService.AddChallenge(model, Actor); + } + catch (Exception ex) + { + Console.WriteLine("Error inside the Unity challenge data semaphore:", ex); + throw; + } + finally + { + SP_CHALLENGE_DATA.Release(); + } // now that we have challenge IDs, we can update gamebrain's console urls var gamebrainClient = await CreateGamebrain(); @@ -135,7 +166,7 @@ public async Task UndeployUnitySpace([FromQuery] string gid, [FromRoute] var vmData = model.Vms.Select(vm => { var consoleHost = new UriBuilder(Request.Scheme, Request.Host.Host, Request.Host.Port ?? -1, $"{Request.PathBase}/mks"); - consoleHost.Query = $"f=1&s={result.Id}&v={vm.Name}"; + consoleHost.Query = $"f=1&s={challengeData.Id}&v={vm.Name}"; return new UnityGameVm { @@ -152,14 +183,15 @@ public async Task UndeployUnitySpace([FromQuery] string gid, [FromRoute] catch (Exception ex) { Console.Write("Calling gamebrain failed with", ex); + throw; } // notify the hub (if there is one) await _hub.Clients .Group(model.TeamId) - .ChallengeEvent(new HubEvent(_mapper.Map(result), EventAction.Updated)); + .ChallengeEvent(new HubEvent(_mapper.Map(challengeData), EventAction.Updated)); - return result; + return challengeData; } [HttpPost("api/unity/mission-update")] diff --git a/src/Gameboard.Api/Features/UnityGames/UnityGameService.cs b/src/Gameboard.Api/Features/UnityGames/UnityGameService.cs index 1d2ded74..f6493868 100644 --- a/src/Gameboard.Api/Features/UnityGames/UnityGameService.cs +++ b/src/Gameboard.Api/Features/UnityGames/UnityGameService.cs @@ -39,15 +39,13 @@ ConsoleActorMap actorMap public async Task AddChallenge(NewUnityChallenge newChallenge, User actor) { - // each _team_ should only get one copy of the challenge. If anyone else tries, send them on their way - // each player should only create their challenge data once, so if they call again, just return what they've already - // got + // each _team_ should only get one copy of the challenge, and by rule, that challenge must have the id + // of the topo gamespace ID. If it's already in the DB, send them on their way with the challenge we've already got var existingChallenge = await Store.DbContext .Challenges .AsNoTracking() .Include(c => c.Events) - .Where(c => c.GameId == newChallenge.GameId && c.TeamId == newChallenge.TeamId) - .FirstOrDefaultAsync(); + .FirstOrDefaultAsync(c => c.Id == newChallenge.GamespaceId); if (existingChallenge != null) { @@ -157,6 +155,15 @@ ConsoleActorMap actorMap return newChallengeEntity; } + public async Task HasChallengeData(NewUnityChallenge model) + { + return await Store.DbContext + .Challenges + .AsNoTracking() + .Include(c => c.Events) + .FirstOrDefaultAsync(c => c.Id == model.GamespaceId); + } + public async Task DeleteChallengeData(string gameId) { var challenges = await Store From bf8e78bc94e48675e33de0085a76efa6dc323f78 Mon Sep 17 00:00:00 2001 From: Ben Stein Date: Tue, 15 Nov 2022 17:04:23 -0500 Subject: [PATCH 2/2] - Resolved an issue that prevented scores from being displayed for players on the game scoreboard and player list. - Cubespace challenges now correctly update their "last scored at" field. --- .../Features/UnityGames/UnityGameController.cs | 14 +++++++++----- .../Features/UnityGames/UnityGameService.cs | 1 + 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/Gameboard.Api/Features/UnityGames/UnityGameController.cs b/src/Gameboard.Api/Features/UnityGames/UnityGameController.cs index 41c02c15..aade1088 100644 --- a/src/Gameboard.Api/Features/UnityGames/UnityGameController.cs +++ b/src/Gameboard.Api/Features/UnityGames/UnityGameController.cs @@ -8,6 +8,7 @@ using System.Net.Http.Json; using System.Threading.Tasks; using AutoMapper; +using Gameboard.Api.Data.Abstractions; using Gameboard.Api.Features.UnityGames; using Gameboard.Api.Hubs; using Gameboard.Api.Services; @@ -15,7 +16,6 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.SignalR; using Microsoft.Extensions.Caching.Distributed; using Microsoft.Extensions.Logging; @@ -25,11 +25,11 @@ namespace Gameboard.Api.Controllers; [Authorize] public class UnityGameController : _Controller { + private readonly IChallengeStore _challengeStore; private readonly ConsoleActorMap _actorMap; private readonly GameService _gameService; private readonly IHttpClientFactory _httpClientFactory; private readonly IHubContext _hub; - private readonly LinkGenerator _linkGenerator; private readonly IMapper _mapper; private readonly IUnityGameService _unityGameService; @@ -41,18 +41,19 @@ public UnityGameController( // other stuff ConsoleActorMap actorMap, GameService gameService, + PlayerService playerService, + IChallengeStore challengeStore, IHttpClientFactory httpClientFactory, IUnityGameService unityGameService, IHubContext hub, - LinkGenerator link, IMapper mapper ) : base(logger, cache, validator) { _actorMap = actorMap; + _challengeStore = challengeStore; _gameService = gameService; _httpClientFactory = httpClientFactory; _hub = hub; - _linkGenerator = link; _mapper = mapper; _unityGameService = unityGameService; } @@ -179,7 +180,10 @@ public async Task CreateMissionEvent([FromBody] UnityMissionUpdat return Accepted(); } - // this means we actually created an event + // this means we actually created an event, so also update player scores + await _challengeStore.UpdateTeam(model.TeamId); + + // call back with the event return Ok(challengeEvent); } diff --git a/src/Gameboard.Api/Features/UnityGames/UnityGameService.cs b/src/Gameboard.Api/Features/UnityGames/UnityGameService.cs index 1d2ded74..03aab839 100644 --- a/src/Gameboard.Api/Features/UnityGames/UnityGameService.cs +++ b/src/Gameboard.Api/Features/UnityGames/UnityGameService.cs @@ -215,6 +215,7 @@ public async Task DeleteChallengeData(string gameId) challenge.Events.Add(challengeEvent); // also update the score of the challenge + challenge.LastScoreTime = DateTimeOffset.UtcNow; challenge.Score += model.PointsScored; // save it up