Abstract Redis away from room & friends

This commit is contained in:
jvyden 2022-05-15 15:51:23 -04:00
parent fb1a2e564d
commit 9ee2778595
No known key found for this signature in database
GPG key ID: 18BCF2BE0262B278
13 changed files with 297 additions and 110 deletions

View file

@ -2,6 +2,8 @@
using System.Xml.Serialization; using System.Xml.Serialization;
using LBPUnion.ProjectLighthouse.Helpers; using LBPUnion.ProjectLighthouse.Helpers;
using LBPUnion.ProjectLighthouse.Serialization; using LBPUnion.ProjectLighthouse.Serialization;
using LBPUnion.ProjectLighthouse.StorableLists;
using LBPUnion.ProjectLighthouse.StorableLists.Stores;
using LBPUnion.ProjectLighthouse.Types; using LBPUnion.ProjectLighthouse.Types;
using LBPUnion.ProjectLighthouse.Types.Profiles; using LBPUnion.ProjectLighthouse.Types.Profiles;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
@ -53,13 +55,13 @@ public class FriendsController : ControllerBase
blockedUsers.Add(blockedUser.UserId); blockedUsers.Add(blockedUser.UserId);
} }
UserFriendStore? friendStore = Redis.GetUserFriendStore(user.UserId); UserFriendData? friendStore = UserFriendStore.GetUserFriendData(user.UserId);
if (friendStore == null) friendStore = Redis.CreateUserFriendStore(user.UserId); if (friendStore == null) friendStore = UserFriendStore.CreateUserFriendData(user.UserId);
friendStore.FriendIds = friends.Select(u => u.UserId).ToList(); friendStore.FriendIds = friends.Select(u => u.UserId).ToList();
friendStore.BlockedIds = blockedUsers; friendStore.BlockedIds = blockedUsers;
Redis.UpdateFriendStore(friendStore); UserFriendStore.UpdateFriendData(friendStore);
string friendsSerialized = friends.Aggregate(string.Empty, (current, user1) => current + LbpSerializer.StringElement("npHandle", user1.Username)); string friendsSerialized = friends.Aggregate(string.Empty, (current, user1) => current + LbpSerializer.StringElement("npHandle", user1.Username));
@ -77,7 +79,7 @@ public class FriendsController : ControllerBase
User user = userAndToken.Value.Item1; User user = userAndToken.Value.Item1;
GameToken gameToken = userAndToken.Value.Item2; GameToken gameToken = userAndToken.Value.Item2;
UserFriendStore? friendStore = Redis.GetUserFriendStore(user.UserId); UserFriendData? friendStore = UserFriendStore.GetUserFriendData(user.UserId);
if (friendStore == null) if (friendStore == null)
return this.Ok(LbpSerializer.BlankElement("myFriends")); return this.Ok(LbpSerializer.BlankElement("myFriends"));

View file

@ -40,7 +40,7 @@ public class RoomVisualizerController : ControllerBase
#if !DEBUG #if !DEBUG
return this.NotFound(); return this.NotFound();
#else #else
RoomHelper.Rooms.DeleteAll(); RoomHelper.Rooms.RemoveAll();
return this.Redirect("/debug/roomVisualizer"); return this.Redirect("/debug/roomVisualizer");
#endif #endif
} }

View file

@ -7,21 +7,5 @@ namespace LBPUnion.ProjectLighthouse.Extensions;
[SuppressMessage("ReSharper", "LoopCanBePartlyConvertedToQuery")] [SuppressMessage("ReSharper", "LoopCanBePartlyConvertedToQuery")]
public static class RedisCollectionExtensions public static class RedisCollectionExtensions
{ {
public static void DeleteAll<T>(this IRedisCollection<T> collection, Func<T, bool> predicate)
{
foreach (T item in collection)
{
if (!predicate.Invoke(item)) continue;
collection.DeleteSync(item);
}
}
public static void DeleteAll<T>(this IRedisCollection<T> collection)
{
foreach (T item in collection)
{
collection.DeleteSync(item);
}
}
} }

View file

@ -5,18 +5,18 @@ using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using LBPUnion.ProjectLighthouse.Extensions; using LBPUnion.ProjectLighthouse.Extensions;
using LBPUnion.ProjectLighthouse.Logging; using LBPUnion.ProjectLighthouse.Logging;
using LBPUnion.ProjectLighthouse.StorableLists;
using LBPUnion.ProjectLighthouse.StorableLists.Stores;
using LBPUnion.ProjectLighthouse.Types; using LBPUnion.ProjectLighthouse.Types;
using LBPUnion.ProjectLighthouse.Types.Levels; using LBPUnion.ProjectLighthouse.Types.Levels;
using LBPUnion.ProjectLighthouse.Types.Match; using LBPUnion.ProjectLighthouse.Types.Match;
using LBPUnion.ProjectLighthouse.Types.Profiles; using LBPUnion.ProjectLighthouse.Types.Profiles;
using Redis.OM;
using Redis.OM.Searching;
namespace LBPUnion.ProjectLighthouse.Helpers; namespace LBPUnion.ProjectLighthouse.Helpers;
public class RoomHelper public class RoomHelper
{ {
public static readonly IRedisCollection<Room> Rooms = Redis.GetRooms(); public static readonly StorableList<Room> Rooms = RoomStore.GetRooms();
public static readonly RoomSlot PodSlot = new() public static readonly RoomSlot PodSlot = new()
{ {
@ -169,7 +169,7 @@ public class RoomHelper
}; };
CleanupRooms(room.HostId, room); CleanupRooms(room.HostId, room);
lock(Rooms) Rooms.Insert(room); lock(Rooms) Rooms.Add(room);
Logger.LogInfo($"Created room (id: {room.RoomId}) for host {room.HostId}", LogArea.Match); Logger.LogInfo($"Created room (id: {room.RoomId}) for host {room.HostId}", LogArea.Match);
return room; return room;
@ -201,7 +201,6 @@ public class RoomHelper
[SuppressMessage("ReSharper", "InvertIf")] [SuppressMessage("ReSharper", "InvertIf")]
public static void CleanupRooms(int? hostId = null, Room? newRoom = null, Database? database = null) public static void CleanupRooms(int? hostId = null, Room? newRoom = null, Database? database = null)
{ {
// return;
lock(Rooms) lock(Rooms)
{ {
int roomCountBeforeCleanup = Rooms.Count(); int roomCountBeforeCleanup = Rooms.Count();
@ -220,7 +219,7 @@ public class RoomHelper
if (hostId != null) if (hostId != null)
try try
{ {
Rooms.DeleteAll(r => r.HostId == hostId); Rooms.RemoveAll(r => r.HostId == hostId);
} }
catch catch
{ {
@ -236,8 +235,8 @@ public class RoomHelper
foreach (int newRoomPlayer in newRoom.PlayerIds) room.PlayerIds.RemoveAll(p => p == newRoomPlayer); foreach (int newRoomPlayer in newRoom.PlayerIds) room.PlayerIds.RemoveAll(p => p == newRoomPlayer);
} }
Rooms.DeleteAll(r => r.PlayerIds.Count == 0); // Remove empty rooms Rooms.RemoveAll(r => r.PlayerIds.Count == 0); // Remove empty rooms
Rooms.DeleteAll(r => r.PlayerIds.Count > 4); // Remove obviously bogus rooms Rooms.RemoveAll(r => r.PlayerIds.Count > 4); // Remove obviously bogus rooms
int roomCountAfterCleanup = Rooms.Count(); int roomCountAfterCleanup = Rooms.Count();

View file

@ -1,77 +0,0 @@
#nullable enable
using System;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Threading.Tasks;
using LBPUnion.ProjectLighthouse.Extensions;
using LBPUnion.ProjectLighthouse.Helpers;
using LBPUnion.ProjectLighthouse.Logging;
using LBPUnion.ProjectLighthouse.Types;
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;
public static async Task Initialize()
{
if (initialized) throw new InvalidOperationException("Redis has already been initialized.");
IRedisConnection connection = getConnection();
string pong = (await connection.ExecuteAsync("PING")).ToString(CultureInfo.InvariantCulture);
if (pong != "PONG")
{
Logger.LogError("Could not ping, ping returned " + pong, LogArea.Redis);
return;
}
await connection.RecreateIndexAsync(typeof(Room));
await connection.RecreateIndexAsync(typeof(UserFriendStore));
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<Room> GetRooms() => provider.RedisCollection<Room>();
private static IRedisCollection<UserFriendStore> userFriendStoreCollection => provider.RedisCollection<UserFriendStore>();
public static UserFriendStore? GetUserFriendStore(int userId) =>
userFriendStoreCollection.FirstOrDefault(s => s.UserId == userId);
public static UserFriendStore CreateUserFriendStore(int userId)
{
UserFriendStore friendStore = new()
{
UserId = userId,
};
userFriendStoreCollection.Insert(friendStore);
return friendStore;
}
public static void UpdateFriendStore(UserFriendStore friendStore)
{
userFriendStoreCollection.UpdateSync(friendStore);
}
}

View file

@ -4,6 +4,7 @@ using LBPUnion.ProjectLighthouse.Helpers;
using LBPUnion.ProjectLighthouse.Logging; using LBPUnion.ProjectLighthouse.Logging;
using LBPUnion.ProjectLighthouse.Logging.Loggers; using LBPUnion.ProjectLighthouse.Logging.Loggers;
using LBPUnion.ProjectLighthouse.Startup; using LBPUnion.ProjectLighthouse.Startup;
using LBPUnion.ProjectLighthouse.StorableLists;
using LBPUnion.ProjectLighthouse.Types; using LBPUnion.ProjectLighthouse.Types;
using LBPUnion.ProjectLighthouse.Types.Settings; using LBPUnion.ProjectLighthouse.Types.Settings;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
@ -77,7 +78,7 @@ public static class StartupTasks
RoomHelper.StartCleanupThread(); RoomHelper.StartCleanupThread();
Logger.LogInfo("Initializing Redis...", LogArea.Startup); Logger.LogInfo("Initializing Redis...", LogArea.Startup);
Redis.Initialize().Wait(); RedisDatabase.Initialize().Wait();
stopwatch.Stop(); stopwatch.Stop();
Logger.LogSuccess($"Ready! Startup took {stopwatch.ElapsedMilliseconds}ms. Passing off control to ASP.NET...", LogArea.Startup); Logger.LogSuccess($"Ready! Startup took {stopwatch.ElapsedMilliseconds}ms. Passing off control to ASP.NET...", LogArea.Startup);

View file

@ -0,0 +1,60 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace LBPUnion.ProjectLighthouse.StorableLists;
public class NormalStorableList<T> : StorableList<T>
{
private readonly List<T> list;
public NormalStorableList(List<T> normalCollection) : base(normalCollection)
{
this.list = normalCollection;
}
public override void Add(T item)
{
this.list.Add(item);
}
public override Task AddAsync(T item)
{
this.list.Add(item);
return Task.CompletedTask;
}
public override void RemoveAll(Predicate<T> predicate)
{
this.list.RemoveAll(predicate);
}
public override Task RemoveAllAsync(Predicate<T> predicate)
{
this.list.RemoveAll(predicate);
return Task.CompletedTask;
}
public override Task RemoveAllAsync()
{
this.list.RemoveAll(_ => true);
return Task.CompletedTask;
}
public override void RemoveAll()
{
this.list.RemoveAll(_ => true);
}
public override void Remove(T item)
{
this.list.Remove(item);
}
public override Task RemoveAsync(T item)
{
this.list.Remove(item);
return Task.CompletedTask;
}
public override void Update(T item) {}
public override Task UpdateAsync(T item) => Task.CompletedTask;
}

View file

@ -0,0 +1,65 @@
#nullable enable
using System;
using System.Globalization;
using System.Linq;
using System.Threading.Tasks;
using LBPUnion.ProjectLighthouse.Extensions;
using LBPUnion.ProjectLighthouse.Logging;
using LBPUnion.ProjectLighthouse.Types;
using LBPUnion.ProjectLighthouse.Types.Match;
using LBPUnion.ProjectLighthouse.Types.Settings;
using Redis.OM;
using Redis.OM.Contracts;
using Redis.OM.Searching;
namespace LBPUnion.ProjectLighthouse.StorableLists;
public static class RedisDatabase
{
private static readonly RedisConnectionProvider provider;
static RedisDatabase()
{
provider = new RedisConnectionProvider(ServerConfiguration.Instance.RedisConnectionString);
}
public static bool Initialized { get; private set; }
public static async Task Initialize()
{
if (Initialized) throw new InvalidOperationException("Redis has already been initialized.");
try
{
IRedisConnection connection = getConnection();
string pong = (await connection.ExecuteAsync("PING")).ToString(CultureInfo.InvariantCulture);
if (pong != "PONG")
{
Logger.LogError("Could not ping, ping returned " + pong,
LogArea.Redis);
return;
}
await connection.RecreateIndexAsync(typeof(Room));
await connection.RecreateIndexAsync(typeof(UserFriendData));
}
catch(Exception e)
{
Logger.LogError("Could not initialize Redis:\n" + e, LogArea.Redis);
return;
}
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<UserFriendData> UserFriendStoreCollection => provider.RedisCollection<UserFriendData>();
internal static IRedisCollection<Room> GetRooms() => provider.RedisCollection<Room>();
}

View file

@ -0,0 +1,31 @@
using System.Threading.Tasks;
using Redis.OM.Searching;
namespace LBPUnion.ProjectLighthouse.StorableLists;
public class RedisStorableList<T> : StorableList<T>
{
private readonly IRedisCollection<T> redisNormalCollection;
public RedisStorableList(IRedisCollection<T> normalCollection) : base(normalCollection)
{
this.redisNormalCollection = normalCollection;
}
public override Task AddAsync(T item) => this.redisNormalCollection.InsertAsync(item);
public override void Add(T item)
{
this.redisNormalCollection.Insert(item);
}
public override Task RemoveAsync(T item) => this.redisNormalCollection.Delete(item);
public override void Remove(T item)
{
this.redisNormalCollection.DeleteSync(item);
}
public override Task UpdateAsync(T item) => this.redisNormalCollection.Update(item);
public override void Update(T item)
{
this.redisNormalCollection.UpdateSync(item);
}
}

View file

@ -0,0 +1,61 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace LBPUnion.ProjectLighthouse.StorableLists;
public abstract class StorableList<T> : IEnumerable<T>
{
private protected readonly IEnumerable<T> NormalCollection;
protected StorableList(IEnumerable<T> normalCollection)
{
this.NormalCollection = normalCollection;
}
public abstract void Add(T item);
public abstract Task AddAsync(T item);
public abstract void Remove(T item);
public abstract Task RemoveAsync(T item);
public abstract void Update(T item);
public abstract Task UpdateAsync(T item);
public virtual void RemoveAll(Predicate<T> predicate)
{
foreach (T item in this.NormalCollection)
{
if (!predicate.Invoke(item)) continue;
this.Remove(item);
}
}
public virtual async Task RemoveAllAsync(Predicate<T> predicate)
{
foreach (T item in this.NormalCollection)
{
if (!predicate.Invoke(item)) continue;
await this.RemoveAsync(item);
}
}
public virtual void RemoveAll()
{
foreach (T item in this.NormalCollection)
{
this.Remove(item);
}
}
public virtual async Task RemoveAllAsync()
{
foreach (T item in this.NormalCollection)
{
await this.RemoveAsync(item);
}
}
public IEnumerator<T> GetEnumerator() => this.NormalCollection.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
}

View file

@ -0,0 +1,21 @@
#nullable enable
using System.Collections.Generic;
using LBPUnion.ProjectLighthouse.Types.Match;
namespace LBPUnion.ProjectLighthouse.StorableLists.Stores;
public static class RoomStore
{
private static List<Room>? rooms;
public static StorableList<Room> GetRooms()
{
if (RedisDatabase.Initialized)
{
return new RedisStorableList<Room>(RedisDatabase.GetRooms());
}
rooms ??= new List<Room>();
return new NormalStorableList<Room>(rooms);
}
}

View file

@ -0,0 +1,40 @@
#nullable enable
using System.Collections.Generic;
using System.Linq;
using LBPUnion.ProjectLighthouse.Types;
namespace LBPUnion.ProjectLighthouse.StorableLists.Stores;
public static class UserFriendStore
{
private static List<UserFriendData>? friendDataStore;
private static StorableList<UserFriendData> getStorableFriendData()
{
if (RedisDatabase.Initialized)
{
return new RedisStorableList<UserFriendData>(RedisDatabase.UserFriendStoreCollection);
}
friendDataStore ??= new List<UserFriendData>();
return new NormalStorableList<UserFriendData>(friendDataStore);
}
public static UserFriendData? GetUserFriendData(int userId) => getStorableFriendData().FirstOrDefault(s => s.UserId == userId);
public static UserFriendData CreateUserFriendData(int userId)
{
UserFriendData friendData = new()
{
UserId = userId,
};
getStorableFriendData().Add(friendData);
return friendData;
}
public static void UpdateFriendData(UserFriendData friendData)
{
getStorableFriendData().Update(friendData);
}
}

View file

@ -6,7 +6,7 @@ namespace LBPUnion.ProjectLighthouse.Types;
[SuppressMessage("ReSharper", "CollectionNeverQueried.Global")] [SuppressMessage("ReSharper", "CollectionNeverQueried.Global")]
[Document(StorageType = StorageType.Json)] [Document(StorageType = StorageType.Json)]
public class UserFriendStore public class UserFriendData
{ {
private int userId; private int userId;
public int UserId { public int UserId {