From f4f951469f2ee8f291109393f7936ab142eb4fec Mon Sep 17 00:00:00 2001 From: OFF0 Date: Sat, 21 Jan 2023 18:39:32 +0100 Subject: [PATCH] feed: less eager rendering before this change every new incoming text note called a render, that filters, sorts and iterates all known text notes and creates missing dom elements and appends into the right place. this change throttles and debounces (both!) the render function, that less checks have to be performed, especially on page load when potentially 100s of events arrive within a short time. it is important to throttle and debounce, else either the last call is missed or no render is called while events are being received. this change surfaced an error in recommend server that depended on all known text notes already being rendered and inside the dom. this function should probably be handled by render feed itself. --- src/main.js | 59 ++++++++++++++++++++++++++-------------------------- src/utils.js | 22 ++++++++++++++++++++ 2 files changed, 52 insertions(+), 29 deletions(-) create mode 100644 src/utils.js diff --git a/src/main.js b/src/main.js index 32c1a59..4aac0be 100644 --- a/src/main.js +++ b/src/main.js @@ -1,5 +1,6 @@ import {relayPool, generatePrivateKey, getPublicKey, signEvent} from 'nostr-tools'; -import {zeroLeadingBitsCount} from './cryptoutils'; +import {bounce} from './utils.js'; +import {zeroLeadingBitsCount} from './cryptoutils.js'; import {elem, parseTextContent} from './domutil.js'; import {dateTime, formatTime} from './timeutil.js'; // curl -H 'accept: application/nostr+json' https://relay.nostr.ch/ @@ -262,6 +263,33 @@ const hasEventTag = tag => tag[0] === 'e'; const isReply = ([tag, , , marker]) => tag === 'e' && marker !== 'mention'; const isMention = ([tag, , , marker]) => tag === 'e' && marker === 'mention'; +const renderFeed = bounce(() => { + const now = Math.floor(Date.now() * 0.001); + const sortedFeeds = textNoteList + // dont render notes from the future + .filter(note => note.created_at < now) + // if difficulty filter is configured dont render notes with too little pow + .filter(note => { + return !fitlerDifficulty || note.tags.some(([tag, , commitment]) => { + return tag === 'nonce' && commitment >= fitlerDifficulty && zeroLeadingBitsCount(note.id) >= fitlerDifficulty; + }); + }) + .sort(sortByCreatedAt).reverse(); + sortedFeeds.forEach((evt, i) => { + if (feedDomMap[evt.id]) { + // TODO check eventRelayMap if event was published to different relays + return; + } + const article = createTextNote(evt, eventRelayMap[evt.id]); + if (i === 0) { + feedContainer.append(article); + } else { + feedDomMap[sortedFeeds[i - 1].id].before(article); + } + feedDomMap[evt.id] = article; + }); +}, 17); // (16.666 rounded, a bit arbitrary but that it doesnt update more than 60x per s) + function handleTextNote(evt, relay) { if (eventRelayMap[evt.id]) { eventRelayMap[evt.id] = [relay, ...(eventRelayMap[evt.id])]; @@ -345,33 +373,6 @@ const sortByCreatedAt = (evt1, evt2) => { return evt1.created_at > evt2.created_at ? -1 : 1; }; -function renderFeed() { - const now = Math.floor(Date.now() * 0.001); - const sortedFeeds = textNoteList - // dont render notes from the future - .filter(note => note.created_at < now) - // if difficulty filter is configured dont render notes with too little pow - .filter(note => { - return !fitlerDifficulty || note.tags.some(([tag, , commitment]) => { - return tag === 'nonce' && commitment >= fitlerDifficulty && zeroLeadingBitsCount(note.id) >= fitlerDifficulty; - }); - }) - .sort(sortByCreatedAt).reverse(); - sortedFeeds.forEach((evt, i) => { - if (feedDomMap[evt.id]) { - // TODO check eventRelayMap if event was published to different relays - return; - } - const article = createTextNote(evt, eventRelayMap[evt.id]); - if (i === 0) { - feedContainer.append(article); - } else { - feedDomMap[sortedFeeds[i - 1].id].before(article); - } - feedDomMap[evt.id] = article; - }); -} - function rerenderFeed() { Object.keys(feedDomMap).forEach(key => delete feedDomMap[key]); Object.keys(replyDomMap).forEach(key => delete replyDomMap[key]); @@ -548,7 +549,7 @@ function handleRecommendServer(evt, relay) { const closestTextNotes = textNoteList .filter(note => !fitlerDifficulty || note.tags.some(([tag, , commitment]) => tag === 'nonce' && commitment >= fitlerDifficulty)) .sort(sortEventCreatedAt(evt.created_at)); - feedDomMap[closestTextNotes[0].id].after(art); + feedDomMap[closestTextNotes[0].id]?.after(art); // TODO: note might not be in the dom yet, recommendedServers could be controlled by renderFeed } feedDomMap[evt.id] = art; } diff --git a/src/utils.js b/src/utils.js new file mode 100644 index 0000000..5ba1f1a --- /dev/null +++ b/src/utils.js @@ -0,0 +1,22 @@ +/** + * throttle and debounce given function in regular time interval, + * but with the difference that the last call will be debounced and therefore never missed. + * @param {*} function to throttle and debounce + * @param {*} time desired interval to execute function + * @returns callback + */ +export const bounce = (fn, time) => { + let throttle; + let debounce; + return (/*...args*/) => { + if (throttle) { + clearTimeout(debounce); + debounce = setTimeout(() => fn(/*...args*/), time); + return; + } + fn(/*...args*/); + throttle = setTimeout(() => { + throttle = false; + }, time); + }; +};