MLP216: always_redraw rebuilds identical curated topology every frame¶
qual reports MLP216 when an always_redraw factory provably
reconstructs the same topology every frame: the factory is a lambda that
returns a single curated leaf constructor (Square, Circle, Dot,
Arc, Line, Rectangle) whose guards hold, so only placement / style
parameters can differ between reconstructions. Every frame then pays
construction plus the factory's implicit become (a full point / style /
submobject rewrite) for geometry that never changes shape.
- Default severity:
info - Minimum confidence:
medium - Implementation phase:
3 - Fix: none
The severity stays info/medium deliberately (DESIGN §7.3): replacing the factory with relative affine updates on a persistent object can accumulate drift over many frames, so the rewrite is not equivalence-preserving in general — prefer updaters that recompute the absolute placement. The rule would only strengthen if topology and absolute-transform equivalence were proven.
Scope (silence beyond it): lambda factories only — the returned-mobject
proof comes from the lambda's return facts, and named-function factories
cannot pin the returned constructor without interprocedural dataflow.
Argument sub-expressions inside the constructor
(Line(dot.get_center(), RIGHT)) are fine — they parameterize placement,
not topology. A banned kwarg (Circle(num_components=...)), a container
factory (VGroup(...)), or a chained method call on the result voids
the proof and stays silent.
Proven execution (liveness)¶
A hot context only proves the call is reachable from a per-frame entry point. The diagnostic additionally requires a play where the factory's updater provably (or at least possibly) executes per frame (DESIGN 3.2/3.3):
- the registration is active at the play (registered on every path and not removed before it),
- the host is in the scene family there,
- the host is not suspended:
Animation.begin()suspends the animated mobject's updaters — recursively over its family — for the whole play (animation.py), unless the animation was constructed withsuspend_mobject_updating=False, - the play actually runs callbacks: a
play(...), or a wait that is provably dynamic (scene.pyshould_update_mobjects). A one-argument updater leaves the defaultwait()a static frame.
self.add(label); self.play(FadeIn(label), run_time=2); self.wait(3)
therefore stays silent: the only play suspends the updater and the wait
is static, so the callback runs about once — never once per frame.
Evidence lists the counted plays (execution) so the claim is
auditable. A factory whose updater provably never runs per frame rebuilds
nothing and is silent; proven or possible execution keeps this advisory
info/medium diagnostic.
Wrong¶
class Demo(Scene):
def construct(self):
dot = Dot()
bar = always_redraw(lambda: Line(dot.get_center(), RIGHT * 2))
self.add(dot, bar)
self.play(dot.animate.shift(UP), run_time=4)
Right¶
class Demo(Scene):
def construct(self):
dot = Dot()
bar = Line(dot.get_center(), RIGHT * 2)
# Recompute the absolute placement of the persistent Line instead
# of reconstructing it every frame.
bar.add_updater(lambda m: m.put_start_and_end_on(dot.get_center(), RIGHT * 2))
self.add(dot, bar)
self.play(dot.animate.shift(UP), run_time=4)