summary refs log tree commit diff
path: root/ModAS.Server/Services/RoomStateCacheService.cs
diff options
context:
space:
mode:
Diffstat (limited to 'ModAS.Server/Services/RoomStateCacheService.cs')
-rw-r--r--ModAS.Server/Services/RoomStateCacheService.cs66
1 files changed, 66 insertions, 0 deletions
diff --git a/ModAS.Server/Services/RoomStateCacheService.cs b/ModAS.Server/Services/RoomStateCacheService.cs
new file mode 100644
index 0000000..3fdf29a
--- /dev/null
+++ b/ModAS.Server/Services/RoomStateCacheService.cs
@@ -0,0 +1,66 @@
+using System.Collections.Frozen;
+using ArcaneLibs.Extensions;
+using Elastic.Apm;
+using Elastic.Apm.Api;
+using LibMatrix;
+using LibMatrix.RoomTypes;
+
+namespace ModAS.Server.Services;
+
+public class RoomStateCacheService(RoomContextService roomContextService) {
+    public FrozenDictionary<string, FrozenSet<StateEventResponse>> RoomStateCache { get; private set; } = FrozenDictionary<string, FrozenSet<StateEventResponse>>.Empty;
+    private SemaphoreSlim updateLock = new(1, 1);
+    public async Task<FrozenSet<StateEventResponse>> GetRoomState(string roomId, GenericRoom? roomReference = null) {
+        if (RoomStateCache.TryGetValue(roomId, out var roomState)) return roomState;
+        return await InvalidateRoomState(roomId, roomReference);
+    }
+
+    public async Task<FrozenSet<StateEventResponse>> InvalidateRoomState(string roomId, GenericRoom? roomReference = null) {
+        var invalidateSpan = currentTransaction.StartSpan($"invalidateRoomState - {roomId}", ApiConstants.TypeApp);
+        var getRoomReferenceSpan = currentTransaction.StartSpan($"getRoomReference - {roomId}", ApiConstants.TypeApp);
+        if (roomReference is null) {
+            var rc = await roomContextService.GetRoomContext(roomId);
+            if (rc is null) return FrozenSet<StateEventResponse>.Empty;
+            roomReference = await roomContextService.GetRoomReferenceById(roomId);
+        }
+
+        if (roomReference is null) {
+            currentTransaction.CaptureException(new Exception("Could not get room reference for room state invalidation"), roomId, true);
+            return FrozenSet<StateEventResponse>.Empty;
+        }
+
+        getRoomReferenceSpan.End();
+
+        var updateSpan = currentTransaction.StartSpan($"updateRoomState - {roomId}", ApiConstants.TypeApp);
+        await updateLock.WaitAsync();
+        var unfrozen = RoomStateCache.ToDictionary();
+        unfrozen[roomId] = (await roomReference.GetFullStateAsListAsync()).ToFrozenSet();
+        RoomStateCache = unfrozen.ToFrozenDictionary();
+        updateSpan.End();
+        updateLock.Release();
+
+        invalidateSpan.End();
+        if (!RoomStateCache.ContainsKey(roomId)) {
+            currentTransaction.CaptureException(new Exception("Room state cache does not contain room after invalidation"), roomId, false);
+            if (!unfrozen.ContainsKey(roomId))
+                currentTransaction.CaptureException(new Exception("Unfrozen room state cache does not contain room after invalidation either..."), roomId, false);
+        }
+
+        return RoomStateCache[roomId];
+    }
+
+    public async Task EnsureCachedFromRoomList(IEnumerable<GenericRoom> rooms) {
+        await updateLock.WaitAsync();
+        var unfrozen = RoomStateCache.ToDictionary();
+        
+        var tasks = rooms.Select(async room => {
+            if (RoomStateCache.ContainsKey(room.RoomId)) return;
+            unfrozen[room.RoomId] = (await room.GetFullStateAsListAsync()).ToFrozenSet();
+        }).ToList();
+        await Task.WhenAll(tasks);
+        RoomStateCache = unfrozen.ToFrozenDictionary();
+        updateLock.Release();
+    }
+
+    private static ITransaction currentTransaction => Agent.Tracer.CurrentTransaction;
+}
\ No newline at end of file