perf(chat): optimize O(N^2) sync merge loop with O(1) hash map lookups (#798)

Replaces the `indexOfFirst` linear scan over the unmerged incoming messages with a `HashMap` mapping message IDs to their index. This reduces the time complexity of the server message reconciliation loop from O(N^2) to O(N). Additionally, `removeAt()` calls on the unmerged list (which carry an O(N) penalty due to array shifting) were replaced by setting matched indices in a `MutableList<ChatMessage?>` to `null`.
This commit is contained in:
M57 authored and GitHub committed 2026-08-04 18:13:38 +03:00
1 parent 6d8cf09d9e
commit 0724d0104d
2 files changed
+22 -10

No files matched your search

@@ -2039,15 +2039,24 @@ class ChatViewModel(
// → dropped. Use sameLogicalMessage (canonical
// result-key match) and always preserve any WS
// message that has no REST counterpart.
val unmatchedIncoming = incoming.toMutableList()
val unmatchedIncoming: MutableList<ChatMessage?> = incoming.toMutableList()
val incomingById = HashMap<String, Int>(unmatchedIncoming.size)
for (i in unmatchedIncoming.indices) {
val id = unmatchedIncoming[i]?.id
if (id != null && !incomingById.containsKey(id)) {
incomingById[id] = i
}
}
val mergedList = mutableListOf<ChatMessage>()
for (existing in current.messages) {
val existingServerIndex = serverMessageIndex(existing.id, sessionId)
if (existingServerIndex != null) {
val matchIdx = unmatchedIncoming.indexOfFirst { it.id == existing.id }
if (matchIdx >= 0) {
mergedList.add(unmatchedIncoming.removeAt(matchIdx))
val matchIdx = incomingById[existing.id]
if (matchIdx != null && unmatchedIncoming[matchIdx] != null) {
mergedList.add(unmatchedIncoming[matchIdx]!!)
unmatchedIncoming[matchIdx] = null
} else {
mergedList.add(existing)
}
@@ -2057,14 +2066,13 @@ class ChatViewModel(
// toolName/content equality.
val matchIdx =
unmatchedIncoming.indexOfFirst { inc ->
sameLogicalMessage(inc, existing)
inc != null && sameLogicalMessage(inc, existing)
}
if (matchIdx >= 0) {
// Prefer the WS copy (richer payload,
// real tool name) when available.
val inc = unmatchedIncoming[matchIdx]
mergedList.add(existing)
unmatchedIncoming.removeAt(matchIdx)
unmatchedIncoming[matchIdx] = null
} else {
// No REST counterpart (server hasn't
// persisted yet) — KEEP the WS message.
@@ -2072,7 +2080,12 @@ class ChatViewModel(
}
}
}
mergedList.addAll(unmatchedIncoming)
for (inc in unmatchedIncoming) {
if (inc != null) {
mergedList.add(inc)
}
}
val merged = mergedList.distinctBy { it.id }
if (sameMessages(current.messages, merged)) current else current.copy(messages = merged)
}