Skip to content

Commit 38202b2

Browse files
committed
fix: replace queueMicrotask array debounce with per-eventId Map for race-free stream patch
1 parent 0bb94ad commit 38202b2

1 file changed

Lines changed: 33 additions & 72 deletions

File tree

  • src/public/parts/shells/chat/public/src

src/public/parts/shells/chat/public/src/group.mjs

Lines changed: 33 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -140,8 +140,8 @@ async function applyGroupHash() {
140140
let displayMessages = []
141141
/** @type {object[]} 当前频道原始消息行(与 API messages 一致),供增量 merge */
142142
let rawMessages = []
143-
/** @type {object[]} 待合并的 WS 消息行(microtask 防抖批处理) */
144-
const pendingNewEvents = []
143+
/** @type {Map<string, object>} eventId → 最新待合并消息行(后写覆盖前写) */
144+
const pendingEventMap = new Map()
145145
/** @type {boolean} 是否已排队一次 microtask 批处理 */
146146
let patchScheduled = false
147147

@@ -173,85 +173,46 @@ async function applyGroupHash() {
173173
}
174174

175175
/**
176-
* 将新到的 DAG 事件增量合并进 displayMessages 并追加到虚拟列表,
177-
* 避免全量重绘导致的闪烁与滚动跳动。
178-
* @param {object[]} newEvents 尚未合并进 rawMessages 的新事件行
179-
* @returns {Promise<void>}
180-
*/
181-
async function patchNewMessages(newEvents) {
182-
if (!newEvents.length) return
183-
if (lastChannelMeta?.type === 'list' || !msgVirtualList) {
184-
await loadMessages()
185-
return
186-
}
187-
if (newEvents.some(e => e?.type === 'pin_message' || e?.type === 'unpin_message')) {
188-
await loadMessages()
189-
return
190-
}
191-
const known = new Set(
192-
rawMessages.map(m => m?.eventId || m?.id).filter(Boolean),
193-
)
194-
const toAdd = []
195-
for (const e of newEvents) {
196-
const id = e?.eventId || e?.id
197-
if (id && known.has(id)) continue
198-
if (id) known.add(id)
199-
toAdd.push(e)
200-
}
201-
if (!toAdd.length) return
202-
203-
const prevLen = displayMessages.length
204-
const merged = mergeChannelMessagesForDisplay([...rawMessages, ...toAdd])
205-
for (let i = 0; i < prevLen; i++) {
206-
const a = displayMessages[i]?.eventId ?? displayMessages[i]?.id
207-
const b = merged[i]?.eventId ?? merged[i]?.id
208-
if (a !== b) {
209-
await loadMessages()
210-
return
211-
}
212-
}
213-
const appended = merged.slice(prevLen)
214-
if (!appended.length) {
215-
await loadMessages()
216-
return
217-
}
218-
displayMessages.push(...appended)
219-
rawMessages.push(...toAdd)
220-
if (typeof msgVirtualList.appendItem === 'function') {
221-
for (const item of appended)
222-
await msgVirtualList.appendItem(item, false)
223-
if (msgScrollContainer)
224-
msgScrollContainer.scrollTop = msgScrollContainer.scrollHeight
225-
}
226-
else await loadMessages()
227-
228-
void attachLastMessageTimeline(displayMessages).catch(e => {
229-
Sentry.captureException(e)
230-
console.error('attachLastMessageTimeline failed:', e)
231-
})
232-
}
233-
234-
/**
235-
* 调度一次增量消息更新(防抖:同一 microtask 前多次入队合并为一批)。
236-
* @param {object} row 新到的频道消息行(与 API message 行形状一致)
176+
* 将 DAG 消息行调度进增量 patch 队列(同一 eventId 后写覆盖)。
177+
* @param {object} line 消息行(含 eventId 字段)
237178
* @returns {void}
238179
*/
239-
function scheduleMessagePatch(row) {
240-
if (!row || typeof row !== 'object') return
241-
pendingNewEvents.push(row)
180+
function scheduleMessagePatch(line) {
181+
if (!line || typeof line !== 'object') return
182+
const key = line.eventId || line.content?.chatLogEntryId || JSON.stringify(line)
183+
pendingEventMap.set(key, line)
242184
if (patchScheduled) return
243185
patchScheduled = true
244-
queueMicrotask(async () => {
245-
const eventsToProcess = pendingNewEvents.splice(0)
186+
queueMicrotask(() => {
246187
patchScheduled = false
247-
if (signal.aborted) return
188+
if (signal.aborted) {
189+
pendingEventMap.clear()
190+
return
191+
}
248192
try {
249-
await patchNewMessages(eventsToProcess)
193+
const lines = [...pendingEventMap.values()]
194+
pendingEventMap.clear()
195+
if (!lines.length) return
196+
let changed = false
197+
for (const l of lines) {
198+
const idx = rawMessages.findIndex(r => r.eventId === l.eventId)
199+
if (idx >= 0) {
200+
rawMessages[idx] = l
201+
changed = true
202+
}
203+
else {
204+
rawMessages.push(l)
205+
changed = true
206+
}
207+
}
208+
if (!changed) return
209+
displayMessages = mergeChannelMessagesForDisplay(rawMessages)
210+
msgVirtualList?.refresh?.()
250211
}
251212
catch (e) {
252213
Sentry.captureException(e)
253-
console.error('patchNewMessages failed, falling back to loadMessages:', e)
254-
await loadMessages()
214+
console.error('scheduleMessagePatch flush failed, falling back to loadMessages:', e)
215+
void loadMessages()
255216
}
256217
})
257218
}

0 commit comments

Comments
 (0)