Room @room.RoomId
@@ -73,9 +74,9 @@
You are currently in this room.
}
-
@room.Players.Count players, state is @room.State, version is @room.RoomVersion.ToPrettyString()on paltform @room.RoomPlatform
+
@room.PlayerIds.Count players, state is @room.State, version is @room.RoomVersion.ToPrettyString()on paltform @room.RoomPlatform
Slot type: @room.Slot.SlotType, slot id: @room.Slot.SlotId
- @foreach (User player in room.Players)
+ @foreach (User player in room.GetPlayers(Model.Database))
{
@player.Username
}
diff --git a/ProjectLighthouse/Extensions/IRedisCollectionExtensions.cs b/ProjectLighthouse/Extensions/IRedisCollectionExtensions.cs
new file mode 100644
index 00000000..b9384846
--- /dev/null
+++ b/ProjectLighthouse/Extensions/IRedisCollectionExtensions.cs
@@ -0,0 +1,27 @@
+using System;
+using System.Diagnostics.CodeAnalysis;
+using Redis.OM.Searching;
+
+namespace LBPUnion.ProjectLighthouse.Extensions;
+
+[SuppressMessage("ReSharper", "LoopCanBePartlyConvertedToQuery")]
+public static class IRedisCollectionExtensions
+{
+ public static void DeleteAll
(this IRedisCollection collection, Func predicate)
+ {
+ foreach (T item in collection)
+ {
+ if (!predicate.Invoke(item)) continue;
+
+ collection.DeleteSync(item);
+ }
+ }
+
+ public static void DeleteAll(this IRedisCollection collection)
+ {
+ foreach (T item in collection)
+ {
+ collection.DeleteSync(item);
+ }
+ }
+}
\ No newline at end of file
diff --git a/ProjectLighthouse/Extensions/RoomExtensions.cs b/ProjectLighthouse/Extensions/RoomExtensions.cs
new file mode 100644
index 00000000..32976beb
--- /dev/null
+++ b/ProjectLighthouse/Extensions/RoomExtensions.cs
@@ -0,0 +1,25 @@
+#nullable enable
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using LBPUnion.ProjectLighthouse.Types;
+using LBPUnion.ProjectLighthouse.Types.Match;
+
+namespace LBPUnion.ProjectLighthouse.Extensions;
+
+public static class RoomExtensions
+{
+ public static List GetPlayers(this Room room, Database database)
+ {
+ List players = new();
+ foreach (int playerId in room.PlayerIds)
+ {
+ User? player = database.Users.FirstOrDefault(p => p.UserId == playerId);
+ Debug.Assert(player != null);
+
+ players.Add(player);
+ }
+
+ return players;
+ }
+}
\ No newline at end of file
diff --git a/ProjectLighthouse/Helpers/RoomHelper.cs b/ProjectLighthouse/Helpers/RoomHelper.cs
index b925f55b..84e98c8f 100644
--- a/ProjectLighthouse/Helpers/RoomHelper.cs
+++ b/ProjectLighthouse/Helpers/RoomHelper.cs
@@ -3,17 +3,20 @@ using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Threading.Tasks;
+using LBPUnion.ProjectLighthouse.Extensions;
using LBPUnion.ProjectLighthouse.Logging;
using LBPUnion.ProjectLighthouse.Types;
using LBPUnion.ProjectLighthouse.Types.Levels;
using LBPUnion.ProjectLighthouse.Types.Match;
using LBPUnion.ProjectLighthouse.Types.Profiles;
+using Redis.OM;
+using Redis.OM.Searching;
namespace LBPUnion.ProjectLighthouse.Helpers;
public class RoomHelper
{
- public static readonly List Rooms = new();
+ public static readonly IRedisCollection Rooms = Redis.GetRooms();
public static readonly RoomSlot PodSlot = new()
{
@@ -49,14 +52,9 @@ public class RoomHelper
return null;
}
- bool anyRoomsLookingForPlayers;
- List rooms;
+ IEnumerable rooms = Rooms;
- lock(Rooms)
- {
- anyRoomsLookingForPlayers = Rooms.Any(r => r.IsLookingForPlayers);
- rooms = anyRoomsLookingForPlayers ? Rooms.Where(r => anyRoomsLookingForPlayers && r.IsLookingForPlayers).ToList() : Rooms;
- }
+ rooms = rooms.OrderBy(r => r.IsLookingForPlayers);
rooms = rooms.Where(r => r.RoomVersion == roomVersion).ToList();
if (platform != null) rooms = rooms.Where(r => r.RoomPlatform == platform).ToList();
@@ -72,24 +70,24 @@ public class RoomHelper
// Don't attempt to dive into the current room the player is in.
if (user != null)
{
- rooms = rooms.Where(r => !r.Players.Contains(user)).ToList();
+ rooms = rooms.Where(r => !r.PlayerIds.Contains(user.UserId)).ToList();
}
foreach (Room room in rooms)
// Look for rooms looking for players before moving on to rooms that are idle.
{
- if (user != null && MatchHelper.DidUserRecentlyDiveInWith(user.UserId, room.Host.UserId)) continue;
+ if (user != null && MatchHelper.DidUserRecentlyDiveInWith(user.UserId, room.HostId)) continue;
Dictionary relevantUserLocations = new();
// Determine if all players in a room have UserLocations stored, also store the relevant userlocations while we're at it
- bool allPlayersHaveLocations = room.Players.All
+ bool allPlayersHaveLocations = room.PlayerIds.All
(
p =>
{
- bool gotValue = MatchHelper.UserLocations.TryGetValue(p.UserId, out string? value);
+ bool gotValue = MatchHelper.UserLocations.TryGetValue(p, out string? value);
- if (gotValue && value != null) relevantUserLocations.Add(p.UserId, value);
+ if (gotValue && value != null) relevantUserLocations.Add(p, value);
return gotValue;
}
);
@@ -104,7 +102,7 @@ public class RoomHelper
response.Players = new List();
response.Locations = new List();
- foreach (User player in room.Players)
+ foreach (User player in room.GetPlayers(new Database()))
{
response.Players.Add
(
@@ -147,74 +145,82 @@ public class RoomHelper
return null;
}
- public static Room CreateRoom(User user, GameVersion roomVersion, Platform roomPlatform, RoomSlot? slot = null)
+ public static Room CreateRoom(int userId, GameVersion roomVersion, Platform roomPlatform, RoomSlot? slot = null)
=> CreateRoom
(
- new List
+ new List
{
- user,
+ userId,
},
roomVersion,
roomPlatform,
slot
);
- public static Room CreateRoom(List users, GameVersion roomVersion, Platform roomPlatform, RoomSlot? slot = null)
+ public static Room CreateRoom(List users, GameVersion roomVersion, Platform roomPlatform, RoomSlot? slot = null)
{
Room room = new()
{
RoomId = RoomIdIncrement,
- Players = users,
+ PlayerIds = users,
State = RoomState.Idle,
Slot = slot ?? PodSlot,
RoomVersion = roomVersion,
RoomPlatform = roomPlatform,
};
- CleanupRooms(room.Host, room);
- lock(Rooms) Rooms.Add(room);
- Logger.LogInfo($"Created room (id: {room.RoomId}) for host {room.Host.Username} (id: {room.Host.UserId})", LogArea.Match);
+ CleanupRooms(room.HostId, room);
+ lock(Rooms) Rooms.Insert(room);
+ Logger.LogInfo($"Created room (id: {room.RoomId}) for host {room.HostId}", LogArea.Match);
return room;
}
- public static Room? FindRoomByUser(User user, GameVersion roomVersion, Platform roomPlatform, bool createIfDoesNotExist = false)
+ public static Room? FindRoomByUser(int userId, GameVersion roomVersion, Platform roomPlatform, bool createIfDoesNotExist = false)
{
lock(Rooms)
- foreach (Room room in Rooms.Where(room => room.Players.Any(player => user == player)))
+ foreach (Room room in Rooms.Where(room => room.PlayerIds.Any(player => userId == player)))
return room;
- return createIfDoesNotExist ? CreateRoom(user, roomVersion, roomPlatform) : null;
+ return createIfDoesNotExist ? CreateRoom(userId, roomVersion, roomPlatform) : null;
}
public static Room? FindRoomByUserId(int userId)
{
lock(Rooms)
- foreach (Room room in Rooms.Where(room => room.Players.Any(player => player.UserId == userId)))
- return room;
+ foreach (Room room in Rooms)
+ {
+ if (room.PlayerIds.Any(p => p == userId))
+ {
+ return room;
+ }
+ }
return null;
}
[SuppressMessage("ReSharper", "InvertIf")]
- public static void CleanupRooms(User? host = null, Room? newRoom = null)
+ public static void CleanupRooms(int? hostId = null, Room? newRoom = null, Database? database = null)
{
+// return;
lock(Rooms)
{
- int roomCountBeforeCleanup = Rooms.Count;
+ int roomCountBeforeCleanup = Rooms.Count();
// Remove offline players from rooms
foreach (Room room in Rooms)
{
- // do not shorten, this prevents collection modified errors
- List playersToRemove = room.Players.Where(player => player.Status.StatusType == StatusType.Offline).ToList();
- foreach (User user in playersToRemove) room.Players.Remove(user);
+ List players = room.GetPlayers(database ?? new Database());
+
+ List playersToRemove = players.Where(player => player.Status.StatusType == StatusType.Offline).Select(player => player.UserId).ToList();
+
+ foreach (int player in playersToRemove) room.PlayerIds.Remove(player);
}
// Delete old rooms based on host
- if (host != null)
+ if (hostId != null)
try
{
- Rooms.RemoveAll(r => r.Host == host);
+ Rooms.DeleteAll(r => r.HostId == hostId);
}
catch
{
@@ -227,13 +233,13 @@ public class RoomHelper
{
if (room == newRoom) continue;
- foreach (User newRoomPlayer in newRoom.Players) room.Players.RemoveAll(p => p == newRoomPlayer);
+ foreach (int newRoomPlayer in newRoom.PlayerIds) room.PlayerIds.RemoveAll(p => p == newRoomPlayer);
}
- Rooms.RemoveAll(r => r.Players.Count == 0); // Remove empty rooms
- Rooms.RemoveAll(r => r.Players.Count > 4); // Remove obviously bogus rooms
+ Rooms.DeleteAll(r => r.PlayerIds.Count == 0); // Remove empty rooms
+ Rooms.DeleteAll(r => r.PlayerIds.Count > 4); // Remove obviously bogus rooms
- int roomCountAfterCleanup = Rooms.Count;
+ int roomCountAfterCleanup = Rooms.Count();
if (roomCountBeforeCleanup != roomCountAfterCleanup)
{
diff --git a/ProjectLighthouse/Logging/LogArea.cs b/ProjectLighthouse/Logging/LogArea.cs
index a43c59b8..ebb74275 100644
--- a/ProjectLighthouse/Logging/LogArea.cs
+++ b/ProjectLighthouse/Logging/LogArea.cs
@@ -18,4 +18,5 @@ public enum LogArea
Photos,
Resources,
Logger,
+ Redis,
}
\ No newline at end of file
diff --git a/ProjectLighthouse/Redis.cs b/ProjectLighthouse/Redis.cs
new file mode 100644
index 00000000..c2b26683
--- /dev/null
+++ b/ProjectLighthouse/Redis.cs
@@ -0,0 +1,41 @@
+using System;
+using System.Threading.Tasks;
+using LBPUnion.ProjectLighthouse.Logging;
+using LBPUnion.ProjectLighthouse.Types.Match;
+using LBPUnion.ProjectLighthouse.Types.Settings;
+using Redis.OM;
+using Redis.OM.Contracts;
+using Redis.OM.Searching;
+
+namespace LBPUnion.ProjectLighthouse;
+
+public static class Redis
+{
+ private static readonly RedisConnectionProvider provider;
+
+ static Redis()
+ {
+ provider = new RedisConnectionProvider(ServerConfiguration.Instance.RedisConnectionString);
+ }
+
+ private static bool initialized = false;
+ public static async Task Initialize()
+ {
+ if (initialized) throw new InvalidOperationException("Redis has already been initialized.");
+
+ IRedisConnection connection = getConnection();
+
+ await connection.CreateIndexAsync(typeof(Room));
+
+ initialized = true;
+ Logger.LogSuccess("Initialized Redis.", LogArea.Redis);
+ }
+
+ private static IRedisConnection getConnection()
+ {
+ Logger.LogDebug("Getting a redis connection", LogArea.Redis);
+ return provider.Connection;
+ }
+
+ public static IRedisCollection GetRooms() => provider.RedisCollection();
+}
\ No newline at end of file
diff --git a/ProjectLighthouse/StartupTasks.cs b/ProjectLighthouse/StartupTasks.cs
index 221a33f6..5ea135c8 100644
--- a/ProjectLighthouse/StartupTasks.cs
+++ b/ProjectLighthouse/StartupTasks.cs
@@ -76,6 +76,9 @@ public static class StartupTasks
Logger.LogInfo("Starting room cleanup thread...", LogArea.Startup);
RoomHelper.StartCleanupThread();
+ Logger.LogInfo("Initializing Redis...", LogArea.Startup);
+ Redis.Initialize().Wait();
+
stopwatch.Stop();
Logger.LogSuccess($"Ready! Startup took {stopwatch.ElapsedMilliseconds}ms. Passing off control to ASP.NET...", LogArea.Startup);
}
diff --git a/ProjectLighthouse/Types/Match/Room.cs b/ProjectLighthouse/Types/Match/Room.cs
index 63128150..6772cc04 100644
--- a/ProjectLighthouse/Types/Match/Room.cs
+++ b/ProjectLighthouse/Types/Match/Room.cs
@@ -1,36 +1,47 @@
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Text.Json.Serialization;
-using LBPUnion.ProjectLighthouse.Types.Levels;
+using Redis.OM.Modeling;
namespace LBPUnion.ProjectLighthouse.Types.Match;
+[Document(StorageType = StorageType.Json)]
public class Room
{
- [JsonIgnore]
- public List Players { get; set; }
+ private int roomId;
- public int RoomId { get; set; }
+ public int RoomId {
+ get => this.roomId;
+ set {
+ this.RedisId = value.ToString();
+ this.roomId = value;
+ }
+ }
- [JsonIgnore]
+ [RedisIdField]
+ public string RedisId { get; set; }
+
+ [Indexed]
+ public List PlayerIds { get; set; }
+
+ [Indexed]
public GameVersion RoomVersion { get; set; }
- [JsonIgnore]
+ [Indexed]
public Platform RoomPlatform { get; set; }
+ [Indexed]
public RoomSlot Slot { get; set; }
+
+ [Indexed]
public RoomState State { get; set; }
[JsonIgnore]
- public bool IsInPod => this.Slot.SlotType == SlotType.Pod;
-
- [JsonIgnore]
+ [Indexed]
public bool IsLookingForPlayers => this.State == RoomState.PlayingLevel || this.State == RoomState.DivingInWaiting;
[JsonIgnore]
- public User Host => this.Players[0];
-
- public int PlayerCount => this.Players.Count;
+ public int HostId => this.PlayerIds[0];
#nullable enable
public override bool Equals(object? obj)
diff --git a/ProjectLighthouse/Types/Settings/ServerConfiguration.cs b/ProjectLighthouse/Types/Settings/ServerConfiguration.cs
index a52031bc..31727ac4 100644
--- a/ProjectLighthouse/Types/Settings/ServerConfiguration.cs
+++ b/ProjectLighthouse/Types/Settings/ServerConfiguration.cs
@@ -22,7 +22,7 @@ public class ServerConfiguration
// You can use an ObsoleteAttribute instead. Make sure you set it to error, though.
//
// Thanks for listening~
- public const int CurrentConfigVersion = 3;
+ public const int CurrentConfigVersion = 4;
#region Meta
@@ -170,6 +170,7 @@ public class ServerConfiguration
public string ApiListenUrl { get; set; } = "http://localhost:10062";
public string DbConnectionString { get; set; } = "server=127.0.0.1;uid=root;pwd=lighthouse;database=lighthouse";
+ public string RedisConnectionString { get; set; } = "";
public string ExternalUrl { get; set; } = "http://localhost:10060";
public bool ConfigReloading { get; set; }
public string EulaText { get; set; } = "";
diff --git a/docker-compose.yml b/docker-compose.yml
index 0f507cfe..e35dd091 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -14,6 +14,15 @@ services:
- '3306' # Expose port to localhost:3306
volumes:
- lighthouse-db:/var/lib/mysql
+ lighthouse-redis:
+ image: redis/redis-stack
+ ports:
+ - '6379:6379'
+ - '8001:8001'
+ expose:
+ - '6379'
+ - '8001'
+
volumes:
lighthouse-db:
\ No newline at end of file