What is true today
Layout runs on the UI thread, and GrandStaffPainter runs it in its constructor — so building the widget lays out the whole group before the frame can continue.
Why the obvious fix does not work
Moving LayoutEngine into an isolate is the answer everyone reaches for, and it is currently impossible as written: LayoutEngine._measuredTextWidth uses TextPainter, which requires the Flutter engine and does not exist in a background isolate. Anything that measures a lyric, a tempo mark, a rehearsal letter or a dynamic goes through it.
So an isolate offload would first have to replace real text measurement with a pure-Dart metric approximation — which trades engraving fidelity for latency. That is a bad trade for a notation package and should not be made silently.
What is actually worth doing
- Get the layout out of the constructor.
GrandStaffPainter can lay out lazily or accept an already-laid-out result. This is independent of isolates and is the part with a real win.
- Measure before optimising. Current numbers, at
staffSpace = 12: a 400-measure single staff lays out in ~33.8 ms warm median (the frequently quoted 94.5 ms figure is the second cold call and is not representative). Cost is close to linear in measure count — 1600 → 3200 measures is well under the quadratic bound.
- Only then consider whether text measurement can be cached across layouts (the same syllables and marks recur), which would shrink the isolate blocker rather than route around it.
Acceptance criteria
- Constructing
GrandStaff does not lay out; layout happens where it can be scheduled.
- A documented benchmark covering 100 / 400 / 1600 / 3200 measures, checked in, so a regression is visible rather than felt.
- If an isolate path is ever attempted, the text-measurement substitution is measured against the goldens before it is accepted.
What is true today
Layout runs on the UI thread, and
GrandStaffPainterruns it in its constructor — so building the widget lays out the whole group before the frame can continue.Why the obvious fix does not work
Moving
LayoutEngineinto an isolate is the answer everyone reaches for, and it is currently impossible as written:LayoutEngine._measuredTextWidthusesTextPainter, which requires the Flutter engine and does not exist in a background isolate. Anything that measures a lyric, a tempo mark, a rehearsal letter or a dynamic goes through it.So an isolate offload would first have to replace real text measurement with a pure-Dart metric approximation — which trades engraving fidelity for latency. That is a bad trade for a notation package and should not be made silently.
What is actually worth doing
GrandStaffPaintercan lay out lazily or accept an already-laid-out result. This is independent of isolates and is the part with a real win.staffSpace = 12: a 400-measure single staff lays out in ~33.8 ms warm median (the frequently quoted 94.5 ms figure is the second cold call and is not representative). Cost is close to linear in measure count — 1600 → 3200 measures is well under the quadratic bound.Acceptance criteria
GrandStaffdoes not lay out; layout happens where it can be scheduled.