8000 Connect the Animation event source callback in the constructor. by anntzer · Pull Request #26774 · matplotlib/matplotlib · GitHub
[go: up one dir, main page]

Skip to content

Connect the Animation event source callback in the constructor. #26774

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 11, 2025

Conversation

anntzer
Copy link
Contributor
@anntzer anntzer commented Sep 14, 2023

Currently, there is no "official" way of knowing whether a Figure hosts an Animation. Yet, this information can be of interest e.g. for third-party backends. For example, a variant of the ipython inline backend could choose to actually animate its (inline) output in that case; indeed, this feature was already supported by the itermplot backend. itermplot is actually now outdated but the general idea can still be made to work -- I have a working patch for mplterm.

The idea is for the backend to override FigureCanvas.new_timer (on the backend-specific canvas subclass) to internally register any timer that gets created (e.g. by the animation module), then, when the canvas is show()n, to first trigger a fake draw_event so that animation callbacks (if any) get attached to the timer, and then to introspect what callbacks have been attached to the timer and retrieve any Animation instance from that.

While this all works, there is brittleness on a specific point, namely the need to trigger the fake draw_event (as the callback only gets attached in Animation._start) early in show(): at that point there may be no renderer available so one cannot construct a real DrawEvent (in fact, itermplot just sets renderer to None). If there are additional (end-user-provided) draw_event handlers connected, they may well error out on this fake draw_event.

Yet, it is easy to work around this problem, by connecting the animation stepping callback immediately to the timer when the Animation is constructed, rather than waiting for the first draw. The timer is still only started at the first draw, so nothing changes from the point of view of the end user.

Note that this does not create a strong reference holding the Animation in the "usual" case (of backends that don't keep a reference to the timers they create) -- it's just a reference loop of the Animation holding the Timer holding an Animation method as a callback. Furthermore, the callback could easily be replaced by a WeakMethod to completely get rid of the reference loop.

(If we agree on the change here I can also add a test that explicitly checks for this early-binding.)


Edit: Actually, a much simpler and more general issue with the "trigger draw to see what gets connected to the timer" is that that also starts the timer and therefore subsequent introspection becomes racy and may be too slow to catch the first frames.

PR summary

PR checklist

Copy link
Contributor
@greglucas greglucas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems reasonable to me.

@anntzer
Copy link
Contributor Author
anntzer commented Mar 8, 2025

@dopplershift Perhaps you can comment on this change as well?

Copy link
Contributor
@dopplershift dopplershift left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems good to me. I'm sure there were reasons for this structure given the original implementation of animations, but I don't see any limitations now.

@timhoffm
Copy link
Member

Sound also reasonable to me.

Why can't we merge? I looks like the "Prevent Merging" action got hung up. @anntzer please rebase.

Currently, there is no "official" way of knowing whether a Figure hosts
an Animation.  Yet, this information can be of interest e.g. for
third-party backends.  For example, a variant of the ipython inline
backend could choose to actually animate its (inline) output in that
case; indeed, this feature was already supported by the itermplot backend.
itermplot is actually now outdated but the general idea can still be
made to work -- I have a working patch for mplterm.

The idea is for the backend to override FigureCanvas.new_timer (on the
backend-specific canvas subclass) to internally register any timer
that gets created (e.g. by the animation module), then, when the canvas
is show()n, to first trigger a fake draw_event so that animation
callbacks (if any) get attached to the timer, and then to introspect
what callbacks have been attached to the timer and retrieve any
Animation instance from that.

While this all works, there is brittleness on a specific point, namely
the need to trigger the fake draw_event (as the callback only gets
attached in Animation._start) early in show(): at that point there may
be no renderer available so one cannot construct a real DrawEvent (in
fact, itermplot just sets renderer to None).  If there are additional
(end-user-provided) draw_event handlers connected, they may well error
out on this fake draw_event.

Yet, it is easy to work around this problem, by connecting the animation
stepping callback immediately to the timer when the Animation is
constructed, rather than waiting for the first draw.  The timer is still
only *started* at the first draw, so nothing changes from the point of
view of the end user.

Note that this does not create a strong reference holding the Animation
in the "usual" case (of backends that don't keep a reference to the
timers they create) -- it's just a reference loop of the Animation
holding the Timer holding an Animation method as a callback.
@anntzer
Copy link
Contributor Author
anntzer commented Mar 11, 2025

rebased

@timhoffm timhoffm merged commit 84ad4e5 into matplotlib:main Mar 11, 2025
38 of 41 checks passed
@anntzer anntzer deleted the eacc branch March 11, 2025 11:43
@QuLogic QuLogic added this to the v3.11.0 milestone Mar 18, 2025
@QuLogic
Copy link
Member
QuLogic commented Mar 18, 2025

Why can't we merge? I looks like the "Prevent Merging" action got hung up.

The PR was just older than the action, so it was never run for it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants
0