Fix level ordering in busiest slots

This commit is contained in:
Slendy 2023-06-14 11:16:16 -05:00
parent 15a3cbea42
commit 91e792aea0
No known key found for this signature in database
GPG key ID: 7288D68361B91428
3 changed files with 163 additions and 22 deletions

View file

@ -351,31 +351,30 @@ public class SlotsController : ControllerBase
PaginationData pageData = this.Request.GetPaginationData(); PaginationData pageData = this.Request.GetPaginationData();
Dictionary<int, int> playersBySlotId = new(); List<int> busiestSlots = RoomHelper.Rooms.Where(r => r.Slot.SlotType == SlotType.User)
.GroupBy(r => r.Slot.SlotId)
.OrderByDescending(kvp => kvp.Count())
.Select(kvp => kvp.Key)
.AsQueryable()
.ApplyPagination(pageData)
.ToList();
foreach (Room room in RoomHelper.Rooms) pageData.TotalElements = busiestSlots.Count;
List<SlotBase> slots = new();
Expression<Func<SlotEntity, bool>> filterQuery = this.FilterFromRequest(token).Build();
foreach (int slotId in busiestSlots)
{ {
// TODO: support developer slotTypes? SlotBase? slot = await this.database.Slots.Where(s => s.SlotId == slotId)
if (room.Slot.SlotType != SlotType.User) continue; .Where(filterQuery)
.Select(s => SlotBase.CreateFromEntity(s, token))
if (!playersBySlotId.TryGetValue(room.Slot.SlotId, out int playerCount)) .FirstOrDefaultAsync();
playersBySlotId.Add(room.Slot.SlotId, 0); if (slot == null) continue;
slots.Add(slot);
playerCount += room.PlayerIds.Count;
playersBySlotId.Remove(room.Slot.SlotId);
playersBySlotId.Add(room.Slot.SlotId, playerCount);
} }
pageData.TotalElements = playersBySlotId.Count;
List<int> orderedPlayersBySlotId = playersBySlotId.OrderByDescending(kvp => kvp.Value).Select(kvp => kvp.Key).ToList();
SlotQueryBuilder queryBuilder = this.FilterFromRequest(token);
queryBuilder.AddFilter(0, new SlotIdFilter(orderedPlayersBySlotId));
List<SlotBase> slots = await this.database.GetSlots(token, queryBuilder, pageData, new SlotSortBuilder<SlotEntity>());
return this.Ok(new GenericSlotResponse(slots, pageData)); return this.Ok(new GenericSlotResponse(slots, pageData));
} }
} }

View file

@ -1,13 +1,16 @@
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using LBPUnion.ProjectLighthouse.Database; using LBPUnion.ProjectLighthouse.Database;
using LBPUnion.ProjectLighthouse.Helpers;
using LBPUnion.ProjectLighthouse.Servers.GameServer.Controllers.Slots; using LBPUnion.ProjectLighthouse.Servers.GameServer.Controllers.Slots;
using LBPUnion.ProjectLighthouse.Tests.Helpers; using LBPUnion.ProjectLighthouse.Tests.Helpers;
using LBPUnion.ProjectLighthouse.Types.Entities.Level; using LBPUnion.ProjectLighthouse.Types.Entities.Level;
using LBPUnion.ProjectLighthouse.Types.Entities.Profile; using LBPUnion.ProjectLighthouse.Types.Entities.Profile;
using LBPUnion.ProjectLighthouse.Types.Entities.Token; using LBPUnion.ProjectLighthouse.Types.Entities.Token;
using LBPUnion.ProjectLighthouse.Types.Levels; using LBPUnion.ProjectLighthouse.Types.Levels;
using LBPUnion.ProjectLighthouse.Types.Matchmaking.Rooms;
using LBPUnion.ProjectLighthouse.Types.Serialization; using LBPUnion.ProjectLighthouse.Types.Serialization;
using LBPUnion.ProjectLighthouse.Types.Users; using LBPUnion.ProjectLighthouse.Types.Users;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
@ -377,4 +380,139 @@ public class SlotControllerTests
} }
#endregion #endregion
#region BusiestLevels
// Rather than trying to mock a singleton
// we just make the unit tests take turns
private static readonly Mutex roomMutex = new(false);
private static async Task AddRoom(int slotId, SlotType type, params int[] playerIds)
{
await RoomHelper.Rooms.AddAsync(new Room
{
PlayerIds = new List<int>(playerIds),
Slot = new RoomSlot
{
SlotId = slotId,
SlotType = type,
},
});
}
[Fact]
public async Task BusiestLevels_ShouldReturnSlots_OrderedByRoomCount()
{
roomMutex.WaitOne();
try
{
DatabaseContext db = await MockHelper.GetTestDatabase(new[]
{
new List<SlotEntity>
{
new()
{
SlotId = 1,
Type = SlotType.User,
},
new()
{
SlotId = 2,
Type = SlotType.User,
},
new()
{
SlotId = 3,
Type = SlotType.User,
},
new()
{
SlotId = 4,
Type = SlotType.Developer,
InternalSlotId = 10,
},
},
});
SlotsController controller = new(db);
controller.SetupTestController();
await AddRoom(1, SlotType.User, 1);
await AddRoom(2, SlotType.User, 2);
await AddRoom(2, SlotType.User, 3);
await AddRoom(3, SlotType.User, 4);
await AddRoom(3, SlotType.User, 5);
await AddRoom(3, SlotType.User, 6);
await AddRoom(10, SlotType.Developer, 7);
IActionResult result = await controller.BusiestLevels();
GenericSlotResponse slotResponse = result.CastTo<OkObjectResult, GenericSlotResponse>();
Assert.Equal(3, slotResponse.Slots.Count);
Assert.IsType<GameUserSlot>(slotResponse.Slots[0]);
Assert.Equal(3, ((GameUserSlot)slotResponse.Slots[0]).SlotId);
Assert.IsType<GameUserSlot>(slotResponse.Slots[1]);
Assert.Equal(2, ((GameUserSlot)slotResponse.Slots[1]).SlotId);
Assert.IsType<GameUserSlot>(slotResponse.Slots[2]);
Assert.Equal(1, ((GameUserSlot)slotResponse.Slots[2]).SlotId);
}
finally
{
roomMutex.ReleaseMutex();
}
}
[Fact]
public async Task BusiestLevels_ShouldNotIncludeDeveloperSlots()
{
roomMutex.WaitOne();
try
{
DatabaseContext db = await MockHelper.GetTestDatabase(new[]
{
new List<SlotEntity>
{
new()
{
SlotId = 4,
Type = SlotType.Developer,
InternalSlotId = 10,
},
},
});
SlotsController controller = new(db);
controller.SetupTestController();
await AddRoom(10, SlotType.Developer, 1);
IActionResult result = await controller.BusiestLevels();
GenericSlotResponse slotResponse = result.CastTo<OkObjectResult, GenericSlotResponse>();
Assert.Empty(slotResponse.Slots);
}
finally
{
roomMutex.ReleaseMutex();
}
}
[Fact]
public async Task BusiestLevels_ShouldNotIncludeInvalidSlots()
{
roomMutex.WaitOne();
try
{
DatabaseContext db = await MockHelper.GetTestDatabase();
SlotsController controller = new(db);
controller.SetupTestController();
await AddRoom(1, SlotType.User, 1);
IActionResult result = await controller.BusiestLevels();
GenericSlotResponse slotResponse = result.CastTo<OkObjectResult, GenericSlotResponse>();
Assert.Empty(slotResponse.Slots);
}
finally
{
roomMutex.ReleaseMutex();
}
}
#endregion
} }

View file

@ -7,6 +7,7 @@ using System.Runtime.CompilerServices;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using LBPUnion.ProjectLighthouse.Database; using LBPUnion.ProjectLighthouse.Database;
using LBPUnion.ProjectLighthouse.Helpers;
using LBPUnion.ProjectLighthouse.Types.Entities.Profile; using LBPUnion.ProjectLighthouse.Types.Entities.Profile;
using LBPUnion.ProjectLighthouse.Types.Entities.Token; using LBPUnion.ProjectLighthouse.Types.Entities.Token;
using LBPUnion.ProjectLighthouse.Types.Users; using LBPUnion.ProjectLighthouse.Types.Users;
@ -53,6 +54,8 @@ public static class MockHelper
public static async Task<DatabaseContext> GetTestDatabase(IEnumerable<IList> sets, [CallerMemberName] string caller = "", [CallerLineNumber] int lineNum = 0) public static async Task<DatabaseContext> GetTestDatabase(IEnumerable<IList> sets, [CallerMemberName] string caller = "", [CallerLineNumber] int lineNum = 0)
{ {
await RoomHelper.Rooms.RemoveAllAsync();
Dictionary<Type, IList> setDict = new(); Dictionary<Type, IList> setDict = new();
foreach (IList list in sets) foreach (IList list in sets)
{ {
@ -77,7 +80,6 @@ public static class MockHelper
}; };
} }
DbContextOptions<DatabaseContext> options = new DbContextOptionsBuilder<DatabaseContext>() DbContextOptions<DatabaseContext> options = new DbContextOptionsBuilder<DatabaseContext>()
.UseInMemoryDatabase($"{caller}_{lineNum}") .UseInMemoryDatabase($"{caller}_{lineNum}")
.Options; .Options;
@ -101,6 +103,8 @@ public static class MockHelper
[CallerMemberName] string caller = "", [CallerLineNumber] int lineNum = 0 [CallerMemberName] string caller = "", [CallerLineNumber] int lineNum = 0
) )
{ {
await RoomHelper.Rooms.RemoveAllAsync();
users ??= new List<UserEntity> users ??= new List<UserEntity>
{ {
GetUnitTestUser(), GetUnitTestUser(),