diff --git a/Assets/Plugins/StreamChat/Core/LowLevelClient/EventDrainStopwatch.cs b/Assets/Plugins/StreamChat/Core/LowLevelClient/EventDrainStopwatch.cs
new file mode 100644
index 00000000..062cf971
--- /dev/null
+++ b/Assets/Plugins/StreamChat/Core/LowLevelClient/EventDrainStopwatch.cs
@@ -0,0 +1,24 @@
+using System.Diagnostics;
+
+namespace StreamChat.Core.LowLevelClient
+{
+ ///
+ /// Elapsed-time source for the per-frame event drain budget. Production uses
+ /// ; tests inject a fake so pacing is deterministic.
+ ///
+ internal interface IElapsedStopwatch
+ {
+ void Restart();
+
+ double ElapsedMilliseconds { get; }
+ }
+
+ internal sealed class DiagnosticsElapsedStopwatch : IElapsedStopwatch
+ {
+ public void Restart() => _stopwatch.Restart();
+
+ public double ElapsedMilliseconds => _stopwatch.Elapsed.TotalMilliseconds;
+
+ private readonly Stopwatch _stopwatch = new Stopwatch();
+ }
+}
diff --git a/Assets/Plugins/StreamChat/Core/LowLevelClient/EventDrainStopwatch.cs.meta b/Assets/Plugins/StreamChat/Core/LowLevelClient/EventDrainStopwatch.cs.meta
new file mode 100644
index 00000000..8b7e1da9
--- /dev/null
+++ b/Assets/Plugins/StreamChat/Core/LowLevelClient/EventDrainStopwatch.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: e4d8a91c7b2f4e6a9d1c3b5f708294a6
+timeCreated: 1756152000
diff --git a/Assets/Plugins/StreamChat/Core/LowLevelClient/IStreamChatLowLevelClient.cs b/Assets/Plugins/StreamChat/Core/LowLevelClient/IStreamChatLowLevelClient.cs
index a9709bd4..9d1c035e 100644
--- a/Assets/Plugins/StreamChat/Core/LowLevelClient/IStreamChatLowLevelClient.cs
+++ b/Assets/Plugins/StreamChat/Core/LowLevelClient/IStreamChatLowLevelClient.cs
@@ -103,6 +103,11 @@ void SetReconnectStrategySettings(ReconnectStrategy reconnectStrategy, float? ex
[Obsolete("Use DisconnectAsync(DisconnectCause). true maps to UserLogout, false to ConnectionReleased.")]
Task DisconnectAsync(bool permanent);
+ ///
+ /// Fetch missed events via /sync and apply them. The returned task completes when
+ /// those events have been processed, which may span several calls
+ /// after a large catch-up. Keep calling while awaiting.
+ ///
Task FetchAndProcessEventsSinceLastReceivedEvent(IEnumerable channelCids);
///
diff --git a/Assets/Plugins/StreamChat/Core/LowLevelClient/StreamChatLowLevelClient.cs b/Assets/Plugins/StreamChat/Core/LowLevelClient/StreamChatLowLevelClient.cs
index d9d0d609..d03883fd 100644
--- a/Assets/Plugins/StreamChat/Core/LowLevelClient/StreamChatLowLevelClient.cs
+++ b/Assets/Plugins/StreamChat/Core/LowLevelClient/StreamChatLowLevelClient.cs
@@ -26,7 +26,6 @@
using StreamChat.Libs.Utils;
using StreamChat.Libs.Websockets;
using StreamChat.Core.LowLevelClient.Requests;
-using System.Linq;
using StreamChat.Core.Helpers;
using Thread = System.Threading.Thread;
@@ -218,6 +217,11 @@ private set
if (value == ConnectionState.Disconnected)
{
_disconnectionLastEventReceivedAt = _lastEventReceivedAt;
+ ClearPendingHistoryEvents();
+
+ // _heldLiveMessage is deliberately NOT cleared. IWebsocketClient's receive queue
+ // survives the disconnect, so dropping just the one message we happened to hold
+ // would punch a hole in an otherwise intact sequence.
RaiseDisconnected();
}
}
@@ -430,13 +434,7 @@ public void Update(float deltaTime)
_websocketClient.Update();
- while (_websocketClient.TryDequeueMessage(out var msg))
- {
-#if STREAM_DEBUG_ENABLED
- _logs.Info(_authCredentials.UserId + " WS message: " + msg);
-#endif
- HandleNewWebsocketMessage(msg, isLiveEvent: true);
- }
+ DrainPendingEvents();
}
public bool IsLocalUser(User user) => user.Id == _authCredentials.UserId;
@@ -453,8 +451,20 @@ public void SetReconnectStrategySettings(ReconnectStrategy reconnectStrategy, fl
public async Task FetchAndProcessEventsSinceLastReceivedEvent(IEnumerable channelCids)
{
+ var generation = _historyDrainGeneration;
var response = await TrySyncHistoryAsync(channelCids);
- ReplayHistoryEvents(response?.Events);
+
+ if (generation != _historyDrainGeneration)
+ {
+ return;
+ }
+
+ if (response?.Events == null || response.Events.Count == 0)
+ {
+ return;
+ }
+
+ await EnqueueHistoryEventsForReplay(response.Events);
}
///
@@ -542,6 +552,8 @@ internal HistorySyncApplyResult ApplyHistoryEvents(IEnumerable