Skip to content

MLP219: updater lifetime spans many subsequent plays

qual reports MLP219 when a mobject updater registered on every path provably stays registered — no remove_updater / clear_updaters or unknown updater write intervenes — while its host lives in the scene family across many rendering plays. Every covered frame pays the updater invocation and keeps the host in Cairo's moving scope.

  • Default severity: info
  • Minimum confidence: medium (a lifetime estimate, not a defect proof)
  • Implementation phase: 3
  • Fix: none

Thresholds (documented constants in rules/performance/updater_waste.rs):

  • MLP219_MIN_LIVE_SECONDS = 5.0 — proven lower bound of the covered play durations;
  • MLP219_MIN_COVERED_PLAYS = 3 — "many subsequent plays".

Covered plays are those after the registration where the host's family membership is Present, the play renders frames (a play, or a provably dynamic wait), and the play does not animate the host itself (animations suspend their targets' updaters, DESIGN 3.2). Unknown durations open the upper bound instead of being fabricated.

Per the binding DESIGN 7.3 gate the rule never claims the updater is unused — static analysis cannot prove when the effect stops being needed. It reports the estimated live span only; if the whole span is intended, the cost is the intended cost.

Wrong

class Demo(Scene):
    def construct(self):
        square = Square()
        self.add(square)
        square.add_updater(spin)                 # needed for the first play only
        self.play(intro, run_time=2)
        self.play(middle, run_time=2)            # spin still runs every frame
        self.play(outro, run_time=2)
class Demo(Scene):
    def construct(self):
        square = Square()
        self.add(square)
        square.add_updater(spin)
        self.play(intro, run_time=2)
        square.clear_updaters()                  # effect provably over
        self.play(middle, run_time=2)
        self.play(outro, run_time=2)