MLP226: Frame-varying Text/TeX cache key in a per-frame callback¶
qual reports MLP226 when a Text / MarkupText / MathTex /
Tex constructor inside a proven per-frame context takes an f-string key —
so the cache key provably varies per frame and K_resource ≈ F.
Each invocation performs object construction and a cache-key lookup; a
frame-varying key defeats the cache, so each frame pays a cache miss, and
for the TeX classes each distinct key also launches the external TeX
compiler and dvisvgm, leaving roughly one disk asset per rendered frame.
- Default severity:
warning - Minimum confidence:
high - Implementation phase:
3 - Fix: none —
DecimalNumber+set_valueis suggested in the explanation (it changes typography) - Supersedes
MLP201on the same span: the same construction carries only this more specific diagnostic
The distinct-key count is bound to real frame numbers summed over the
plays where the callback provably executes (e.g. "may create ~480
distinct keys" for one proven 8 s play at 60 fps); maybe plays widen only
the upper bound, and without literal durations the evidence stays
"per-frame" — never a fabricated number. A static key in a hot context is
MLP201 territory (the cache absorbs the compile, construction remains),
and a key whose variation cannot be proven (concatenation, .format)
falls back to MLP201 as well.
Proven execution (liveness)¶
A hot context only proves the construction is reachable from a
per-frame entry point. The diagnostic additionally requires at least one
play where the callback provably executes per frame (DESIGN 3.2/3.3):
the registration is active, the host is in the scene family, the host is
not suspended (Animation.begin() suspends the animated mobject's
updaters for the whole play unless suspend_mobject_updating=False was
passed), and the play actually runs callbacks (a play(...), or a
provably dynamic wait — a one-argument always_redraw updater leaves the
default wait() static).
The canonical near miss stays silent:
label = always_redraw(lambda: MathTex(f"x = {tracker.get_value():.2f}"))
self.add(label)
self.play(FadeIn(label), run_time=2) # begin() suspends label's updaters
self.wait(3) # static: no dt updater, no scene updater
The callback here runs about once, not ~120 times — no per-frame key
claim is made (the scene's real defect, a frozen tracker-driven updater,
is MLC112 territory). Evidence lists the counted plays (execution)
so the claim is auditable; only-maybe evidence never reaches this rule's
high confidence.
Wrong¶
class Demo(Scene):
def construct(self):
tracker = ValueTracker(0)
label = always_redraw(lambda: MathTex(f"x={tracker.get_value():.2f}"))
self.add(label)
self.play(tracker.animate.set_value(8), run_time=8)