Chat Paint Performance Report #
Date: 2026-03-07
File: system/public/aesthetic.computer/disks/chat.mjs
Overview #
The paint function (lines 588-1500+) runs every frame. For ~20 visible
messages averaging 2 lines each, costs per frame are:
| Operation | Count/Frame | Severity |
|---|---|---|
write() calls |
120 | Medium |
text.width() calls |
~62 | HIGH |
parseMessageElements() |
20 | HIGH |
Shadow regex .replace() |
40 | Medium |
substring() + concat (color coding) |
400+ | HIGH |
stripInlineColorCodes() |
20 | HIGH (O(n^2)) |
timeAgo() Date creations |
20 | Low |
Critical Bottlenecks #
1. parseMessageElements() called fresh every frame (line 803) #
Runs 10+ regex patterns per message through chat-highlighting.mjs. For
KidLisp messages, also instantiates new KidLisp() + tokenize(). Not
cached between frames even though message content is static.
2. Color code string building (lines 851-942) #
Per colored element per line: 2x substring() + concatenation. With ~5
elements/line: 5 x 2 x 2 lines x 20 msgs = 400 substring() calls.
3. text.width() O(n) per call (disk.mjs lines 3983-4010) #
For proportional fonts, iterates char-by-char. Called for: last-line width, timestamp width, heart width, kidlisp background positioning.
4. stripInlineColorCodes() O(n^2) (lines 136-153) #
For each \, calls indexOf("\\", i+1) scanning forward. Called once per
message last line.
5. Shadow pass doubles write() calls (lines 944-959) #
Every line gets shadow write() + main write() + a regex .replace() to
darken embedded RGB codes.
Optimizations (Priority Order) #
P0: Cache parseMessageElements() on message object #
// Layout phase:
message._parsedElements = parseMessageElements(message.fullMessage);
// Paint (line 803):
const parsedElements = message._parsedElements;
Eliminates 20 multi-regex parse calls/frame.
P1: Cache color-coded + shadow lines #
After building colorCodedLine and shadowLine, cache on message. Invalidate
only on edit or hover state change.
Eliminates ~400 substrings, ~40 regex replacements, ~200 concatenations/frame when nothing is hovered.
P2: Cache text.width() results on layout #
Store lastLineStrippedWidth, timestampWidth, heartWidth on layout object.
~37 fewer width calls/frame.
P3: Fix stripInlineColorCodes() to O(n) #
function stripInlineColorCodes(s) {
if (!s || typeof s !== "string") return "";
let out = "", i = 0;
while (i < s.length) {
if (s[i] === "\\") {
i++;
while (i < s.length && s[i] !== "\\") i++;
i++;
} else {
out += s[i++];
}
}
return out;
}
P4: Throttle timeAgo() to 1s intervals #
Cache result per message, recompute only when >1s elapsed.
P5 (Architectural): Dirty-rect message rendering #
Render messages to offscreen buffer, only re-render changed/hovered messages. Brings steady-state cost to near-zero when chat is idle. Larger refactor.
Key Locations #
- Paint function:
disks/chat.mjs:588-1500 - Message parsing:
lib/chat-highlighting.mjs:21-262 - Text measurement:
lib/disk.mjs:3983-4233 stripInlineColorCodes:disks/chat.mjs:136-153timeAgo:disks/chat.mjs:4170-4190- Shadow pass:
disks/chat.mjs:944-959 - Color coding loop:
disks/chat.mjs:851-942