Take every whole-message parse off the composing thread, and count the rest
Bryan's report after the last change read no worse in the numbers -- medians unchanged, worst stall halved -- but scrolling felt bumpier while messages loaded, and instrumenting the split paths found the feeling's likely source: work the counters never saw. The live reply's block split (BlockedMarkdown) recomputed on the composing thread at every delta -- a whole-message parse of the growing text, 815 of them and 2.9 seconds inside one streamed reply, a few milliseconds per delta on the thread that draws, plus a cache entry per partial text that nothing reads again. It now works the way parsedMarkdown already did one file over: first split inline so the row has its height, every later one off-thread, drawing one split behind, cached nowhere. Emulator, same streamed fixture: anim p90 7.3 -> 3.4ms, p99 9.3 -> 4.7ms. The settle moment had the same shape: nothing warms live deltas, so the just-finished reply's split parse ran inside the flatten, uncounted, in a frame. The flatten now splits a reply only when ParsedReplies.splitReady says warm() has made its parses; the session screen warms the one cold row off-thread and re-flattens (warmedTick), so the whole-to-blocks swap always composes against ready parses. Readiness is an explicit mark set by warm() rather than a peek into the blocks cache, because a message with memory notes is warmed as its parts -- inferred readiness left it unsplittable forever and re-warmed on every fold. Also: user slices shrink to ~1000 chars (about one viewport, so a slice composing mid-fling costs a few milliseconds, not sixty), and the split and flatten paths are all timed -- "units flattened", "markdown split into blocks", "message cut into parts", "user message cut into slices", "blocks split while streaming" -- so the next "it feels bumpier" report names its cause instead of hiding it in anim. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
333d2de92b
commit
356dee65f1
5 files changed
+113
-8
No files matched your search
@@ -4,6 +4,8 @@ import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.drawWithContent
|
||||
@@ -11,6 +13,8 @@ import androidx.compose.ui.graphics.graphicsLayer
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.mikepenz.markdown.model.State
|
||||
import com.mikepenz.markdown.model.parseMarkdown
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
/**
|
||||
* A message's top-level markdown blocks, cut where the parser says the blocks are.
|
||||
@@ -70,7 +74,23 @@ fun BlockedMarkdown(
|
||||
modifier: Modifier = Modifier,
|
||||
live: Boolean = false,
|
||||
) {
|
||||
val blocks = remember(text) { replies.blocksOf(text) }
|
||||
// The first split is inline for the reason [parsedMarkdown]'s first parse is: the row must
|
||||
// have its real height in its first frame. Every split after that is a delta landing, and it
|
||||
// runs off the composing thread with the message drawing the split it already has until the
|
||||
// new one arrives -- when this recomputed wherever composition ran, one streamed reply cost
|
||||
// 815 whole-message parses and 2.9 seconds of them, a few milliseconds per delta, on the
|
||||
// thread that draws. Not through [replies]: a reply mid-stream is a different text per
|
||||
// delta, and each would leave a cache entry nothing reads again.
|
||||
val split = remember { mutableStateOf(text to markdownBlocks(text)) }
|
||||
LaunchedEffect(text) {
|
||||
if (split.value.first == text) return@LaunchedEffect
|
||||
split.value =
|
||||
text to
|
||||
withContext(Dispatchers.Default) {
|
||||
DebugStats.timed("blocks split while streaming") { markdownBlocks(text) }
|
||||
}
|
||||
}
|
||||
val blocks = split.value.second
|
||||
if (blocks.size == 1) {
|
||||
MarkdownText(blocks.first(), replies, modifier)
|
||||
return
|
||||
|
||||
Reference in new issue
Block a user