openvibers/openvibe.live Commits
Channel chat history: the newest 50 rows first, then 10-row pieces per idle moment (A/B against production on /@goosely, phone CPU: TBT 2.3 s → 1.3 s, longest task 751 → 383 ms) (#210)
Changes
@@@ -2408,7 +2408,7 @@ async function hydrateActiveChatHistory(streamId, { clear = false } = {}) {const cached = _chatCacheRead(room);if (cached) {_chatPinned = true;-- _swapChatHistory(messages, _renderHistoryOffscreen(chatEl, () => _renderHistoryPayload(_historyPieces(cached, 120)[0])));++ _swapChatHistory(messages, _renderHistoryOffscreen(chatEl, () => _renderHistoryPayload(_historyPieces(cached, 50)[0])));messages.dataset.room = room;} else if (clear || roomChanged) {messages.innerHTML = '<div class="chat-loading-history"><i class="fa-solid fa-spinner fa-spin"></i> Loading history…</div>';@@@ -2435,9 +2435,10 @@ async function hydrateActiveChatHistory(streamId, { clear = false } = {}) {if (now.messages !== messages) return; // the page changed under usif (payload) {-- // The newest 120 rows first; older scrollback goes above them 25 rows at a time, as many as fit in each-- // idle moment (all 1000 rows at once was a 2.7 s task on a phone), keeping the reader where they are.-- const pieces = _historyPieces(payload, 25, 120);++ // The newest 50 rows first (more than a chat pane shows); older scrollback goes above them 10 rows at a++ // time, as many as fit in each idle moment, so no task blocks a phone (all 1000 rows at once took 2.7 s;++ // 25 rows were still ~75 ms), keeping the reader where they are.++ const pieces = _historyPieces(payload, 10, 50);_swapChatHistory(messages, _renderHistoryOffscreen(now, () => _renderHistoryPayload(pieces[0])));messages.dataset.room = room;_chatCacheWrite(room, payload);@@@ -2449,7 +2450,7 @@ async function hydrateActiveChatHistory(streamId, { clear = false } = {}) {if (gen !== _chatHydrateGen || getChatEl().messages !== messages) return;const top = messages.scrollTop, h = messages.scrollHeight;do messages.prepend(..._renderHistoryOffscreen(getChatEl(), () => _renderHistoryPayload(pieces[i++]), false).childNodes);-- while (i < pieces.length && dl && dl.timeRemaining() > 15);++ while (i < pieces.length && dl && dl.timeRemaining() > 20);messages.scrollTop = _chatPinned ? messages.scrollHeight : top + messages.scrollHeight - h;older(i);});@@@ -47,6 +47,7 @@ assert.ok(pieces[0].data.messages.some((m) => m.id === 'c299') && pieces[0].globassert.deepStrictEqual([pieces[0].kind, pieces[0].data.channel, pieces[0].data.liveSlots], ['channel', 'goosely', [1]]);// A bigger first piece (what fits on screen), then small ones.++assert.deepStrictEqual(_historyPieces(payload, 10, 50).map(rows), [50, ...Array(50).fill(10)], '550 rows: 50, then 50 × 10');assert.deepStrictEqual(_historyPieces(payload, 25, 120).map(rows), [120, ...Array(17).fill(25), 5], '550 rows: 120, then 17 × 25, then 5');// The global room has no second feed; a small payload is one piece; an empty one is itself.
@@ -2408,7 +2408,7 @@ async function hydrateActiveChatHistory(streamId, { clear = false } = {}) {
@@ -2408,7 +2408,7 @@ async function hydrateActiveChatHistory(streamId, { clear = false } = {}) {
const cached = _chatCacheRead(room);
const cached = _chatCacheRead(room);
if (cached) {
if (cached) {
_chatPinned = true;
_chatPinned = true;
- _swapChatHistory(messages, _renderHistoryOffscreen(chatEl, () => _renderHistoryPayload(_historyPieces(cached, 120)[0])));
+ _swapChatHistory(messages, _renderHistoryOffscreen(chatEl, () => _renderHistoryPayload(_historyPieces(cached, 50)[0])));
messages.dataset.room = room;
messages.dataset.room = room;
} else if (clear || roomChanged) {
} else if (clear || roomChanged) {
messages.innerHTML = '<div class="chat-loading-history"><i class="fa-solid fa-spinner fa-spin"></i> Loading history…</div>';
messages.innerHTML = '<div class="chat-loading-history"><i class="fa-solid fa-spinner fa-spin"></i> Loading history…</div>';
@@ -2435,9 +2435,10 @@ async function hydrateActiveChatHistory(streamId, { clear = false } = {}) {
@@ -2435,9 +2435,10 @@ async function hydrateActiveChatHistory(streamId, { clear = false } = {}) {
if (now.messages !== messages) return; // the page changed under us
if (now.messages !== messages) return; // the page changed under us
if (payload) {
if (payload) {
- // The newest 120 rows first; older scrollback goes above them 25 rows at a time, as many as fit in each
- // idle moment (all 1000 rows at once was a 2.7 s task on a phone), keeping the reader where they are.
- const pieces = _historyPieces(payload, 25, 120);
+ // The newest 50 rows first (more than a chat pane shows); older scrollback goes above them 10 rows at a
+ // time, as many as fit in each idle moment, so no task blocks a phone (all 1000 rows at once took 2.7 s;
+ // 25 rows were still ~75 ms), keeping the reader where they are.
+ const pieces = _historyPieces(payload, 10, 50);
_swapChatHistory(messages, _renderHistoryOffscreen(now, () => _renderHistoryPayload(pieces[0])));
_swapChatHistory(messages, _renderHistoryOffscreen(now, () => _renderHistoryPayload(pieces[0])));
messages.dataset.room = room;
messages.dataset.room = room;
_chatCacheWrite(room, payload);
_chatCacheWrite(room, payload);
@@ -2449,7 +2450,7 @@ async function hydrateActiveChatHistory(streamId, { clear = false } = {}) {
@@ -2449,7 +2450,7 @@ async function hydrateActiveChatHistory(streamId, { clear = false } = {}) {
if (gen !== _chatHydrateGen || getChatEl().messages !== messages) return;
if (gen !== _chatHydrateGen || getChatEl().messages !== messages) return;
const top = messages.scrollTop, h = messages.scrollHeight;
const top = messages.scrollTop, h = messages.scrollHeight;
do messages.prepend(..._renderHistoryOffscreen(getChatEl(), () => _renderHistoryPayload(pieces[i++]), false).childNodes);
do messages.prepend(..._renderHistoryOffscreen(getChatEl(), () => _renderHistoryPayload(pieces[i++]), false).childNodes);
- while (i < pieces.length && dl && dl.timeRemaining() > 15);
+ while (i < pieces.length && dl && dl.timeRemaining() > 20);
messages.scrollTop = _chatPinned ? messages.scrollHeight : top + messages.scrollHeight - h;
messages.scrollTop = _chatPinned ? messages.scrollHeight : top + messages.scrollHeight - h;
older(i);
older(i);
});
});
@@ -47,6 +47,7 @@ assert.ok(pieces[0].data.messages.some((m) => m.id === 'c299') && pieces[0].glob
@@ -47,6 +47,7 @@ assert.ok(pieces[0].data.messages.some((m) => m.id === 'c299') && pieces[0].glob
assert.deepStrictEqual([pieces[0].kind, pieces[0].data.channel, pieces[0].data.liveSlots], ['channel', 'goosely', [1]]);
assert.deepStrictEqual([pieces[0].kind, pieces[0].data.channel, pieces[0].data.liveSlots], ['channel', 'goosely', [1]]);
// A bigger first piece (what fits on screen), then small ones.
// A bigger first piece (what fits on screen), then small ones.
+assert.deepStrictEqual(_historyPieces(payload, 10, 50).map(rows), [50, ...Array(50).fill(10)], '550 rows: 50, then 50 × 10');
assert.deepStrictEqual(_historyPieces(payload, 25, 120).map(rows), [120, ...Array(17).fill(25), 5], '550 rows: 120, then 17 × 25, then 5');
assert.deepStrictEqual(_historyPieces(payload, 25, 120).map(rows), [120, ...Array(17).fill(25), 5], '550 rows: 120, then 17 × 25, then 5');
// The global room has no second feed; a small payload is one piece; an empty one is itself.
// The global room has no second feed; a small payload is one piece; an empty one is itself.