Draw a peer message a block at a time, and parse it off the drawing thread
A message from another agent was the one markdown in the app still rendered whole: one parse and one display list for the entire thing. Every settled reply has been cut into blocks since the transcript was made lazy, and `warm` has been making those parses ahead on a background thread -- but it filtered for assistant replies alone, so the longest message a transcript holds was also the only one parsed on the thread that draws. Measured on the emulator against a 43KB peer message, opening it: 177ms in `markdown parsed while composing`, against none afterwards and 156 blocks already ready. What is left is the card being a single list item, so all 156 blocks are still measured, placed and recorded at once -- 118ms of placement in that same frame. The `when` in `warm` is now the rule rather than a filter: every row that draws markdown belongs in it. Blocks are spaced by the transcript's own BLOCK_SPACING rather than the renderer's internal padding, which moves a heading about 6px (2.3dp) closer to the paragraph above it. The message's total height is unchanged, and it now matches every reply in the transcript. While here: FrameStats was remembered per session screen and DebugStats is a global emptied only by the copy button, so the two halves of a render report covered different stretches of time -- and `drawAccounting` divides one by the other. A report copied after visiting two sessions claimed 36.8 seconds of placement inside a 13.5 second window, and clamped "everything else" to 0.00ms (0%), which reads as a screen whose entire cost is this app's code. One FrameStats for the app, so both halves mean "since this was last copied". Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
6d12388063
commit
80aaf286c2
4 files changed
+56
-33
No files matched your search
@@ -466,32 +466,46 @@ private fun updateTool(
|
||||
private val parsingThreads = Dispatchers.Default.limitedParallelism(2)
|
||||
|
||||
/**
|
||||
* Parses the replies among [rows], off whatever thread is drawing.
|
||||
* Parses the markdown among [rows], off whatever thread is drawing.
|
||||
*
|
||||
* Called where a page of transcript is folded rather than where a row is composed, which is the
|
||||
* whole point: the work happens seconds before the reader reaches the rows it was done for. See
|
||||
* [ParsedReplies].
|
||||
*
|
||||
* What is warmed mirrors what the rows draw, unit by unit -- prose split into its blocks, a memory
|
||||
* note whole -- because a string warmed under a key no row ever looks up is a miss that nothing
|
||||
* reports; see [transcriptUnits], which is the flatten this has to agree with. It reads the same
|
||||
* [ParsedReplies.partsOf] and [ParsedReplies.blocksOf] caches the flatten does, so a message is
|
||||
* scanned once however many pages hand it back through here, while the whole loaded transcript
|
||||
* crosses this on every page.
|
||||
* note whole, a peer message split the same way prose is -- because a string warmed under a key no
|
||||
* row ever looks up is a miss that nothing reports; see [transcriptUnits], which is the flatten
|
||||
* this has to agree with. It reads the same [ParsedReplies.partsOf] and [ParsedReplies.blocksOf]
|
||||
* caches the flatten does, so a message is scanned once however many pages hand it back through
|
||||
* here, while the whole loaded transcript crosses this on every page.
|
||||
*
|
||||
* Every kind of row that draws markdown belongs in the `when` below. That is the rule the peer
|
||||
* message was missing: this used to filter for assistant replies alone, so the one row type nobody
|
||||
* had thought about paid its whole parse in the frame it appeared in, with no counter saying which
|
||||
* row it was.
|
||||
*/
|
||||
suspend fun warm(replies: ParsedReplies, rows: List<TranscriptItem>) {
|
||||
withContext(parsingThreads) {
|
||||
val texts =
|
||||
rows
|
||||
.filterIsInstance<TranscriptItem.AssistantMsg>()
|
||||
.flatMap { replies.partsOf(it.text) }
|
||||
.flatMap { part ->
|
||||
when (part) {
|
||||
is MessagePart.Prose -> replies.blocksOf(part.text)
|
||||
// Drawn as one MarkdownText, so its whole text is the key looked up.
|
||||
is MessagePart.Remembered -> listOf(part.text)
|
||||
val texts = rows.flatMap { row ->
|
||||
when (row) {
|
||||
is TranscriptItem.AssistantMsg ->
|
||||
replies.partsOf(row.text).flatMap { part ->
|
||||
when (part) {
|
||||
is MessagePart.Prose -> replies.blocksOf(part.text)
|
||||
// Drawn as one MarkdownText, so its whole text is the key
|
||||
// looked up.
|
||||
is MessagePart.Remembered -> listOf(part.text)
|
||||
}
|
||||
}
|
||||
}
|
||||
// A message from another agent is markdown too, and it is the longest thing
|
||||
// in a transcript often enough that leaving it out was the whole of why one
|
||||
// cost a fifth of a second to open: it was the only markdown in the app
|
||||
// parsed on the thread that draws. Its blocks, not its text, because
|
||||
// [PeerMessageRow] draws it a block at a time.
|
||||
is TranscriptItem.PeerNote -> replies.blocksOf(row.text)
|
||||
else -> emptyList()
|
||||
}
|
||||
}
|
||||
if (texts.isNotEmpty()) replies.warm(texts)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user