Skip to content

MLP204: Scene graph grows with a fresh mobject every frame

qual reports MLP204 when Scene.add (or a foreground / fixed-in-frame adder) runs inside a proven per-frame callback with an argument that is itself a fresh mobject construction.

Every frame then allocates a new object and inserts it into the persistent scene graph, so nothing is ever freed while the scene renders: the family grows O(F) and rasterizing the accumulated members across the play approaches O(F²).

  • Default severity: warning
  • Minimum confidence: high
  • Implementation phase: 3
  • Fix: none

Re-adding an existing object inside an updater is only a reorder / plan invalidation; per DESIGN §7.3 that branch is info for provably large scenes only, and scene size is not yet a cost fact — so it stays silent. An argument whose freshness cannot be proven (a variable, a helper call) also stays silent: adds_fresh_allocation = false means "not proven", never "proven existing".

Proven execution (liveness)

A hot context only proves the call 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 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 with suspend_mobject_updating=False,
  • the play actually runs callbacks: a play(...), or a wait that is provably dynamic (scene.py should_update_mobjects). A one-argument updater leaves the default wait() 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; quantities sum over the proven plays only, and only-maybe evidence (a branch-dependent registration, an unknown animation that may suspend the host) never reaches this rule's high confidence.

Wrong

class Demo(Scene):
    def construct(self):
        dot = Dot()
        self.add_updater(lambda dt: self.add(Dot()))
        self.play(dot.animate.shift(RIGHT), run_time=4)
class Demo(Scene):
    def construct(self):
        dot = Dot()
        trail = TracedPath(dot.get_center, dissipating_time=0.5)
        self.add(dot, trail)
        self.play(dot.animate.shift(RIGHT), run_time=4)