-
Notifications
You must be signed in to change notification settings - Fork 2.1k
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
Allow playing Animations asynchronously using OpenGLRenderer #1528
Changes from all commits
514a3a3
928abf1
e38b1d7
03271d5
682a100
e3c0647
d7766b4
236ea3e
0a7b668
8e1da5f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,6 +12,7 @@ | |
import types | ||
import warnings | ||
from queue import Queue | ||
from typing import Any, Optional | ||
|
||
import numpy as np | ||
from tqdm import tqdm | ||
|
@@ -91,6 +92,7 @@ def __init__( | |
self.random_seed = random_seed | ||
|
||
self.animations = None | ||
self.sync_play_running = False | ||
self.stop_condition = None | ||
self.moving_mobjects = None | ||
self.static_mobjects = None | ||
|
@@ -849,8 +851,92 @@ def get_run_time(self, animations): | |
else: | ||
return np.max([animation.run_time for animation in animations]) | ||
|
||
def play(self, *args, **kwargs): | ||
self.renderer.play(self, *args, **kwargs) | ||
def play( | ||
self, *animations: Animation, run_async: Optional[bool] = None, **kwargs: Any | ||
): | ||
"""Play :class:`Animations<.Animation>` in the scene. | ||
|
||
Parameters | ||
---------- | ||
animations | ||
The animations to be played. They are started at the same time and played | ||
in parallel. | ||
run_async | ||
Whether to play the animations asynchronously. Asynchronous animations don't | ||
interrupt the execution of :meth:`construct` and can be started while | ||
synchronous animations are running. If ``run_async`` is not defined, it | ||
defaults to ``True`` if called while a synchronous animation is running, | ||
to ``False`` otherwise. | ||
|
||
.. note:: | ||
|
||
This feature is only available when using the OpenGL-Renderer. The | ||
parameter will be ignored otherwise. | ||
kwargs | ||
Attributes of all passed animations to be set. Typical usecases are setting | ||
`run_time` or `rate_func`. | ||
|
||
Raises | ||
------ | ||
ValueError | ||
If an animation is played synchronously while another synchronous animation | ||
is running. | ||
""" | ||
|
||
if config["renderer"] != "opengl": | ||
run_async = False | ||
|
||
if self.sync_play_running: | ||
if run_async is None: | ||
run_async = True | ||
if not run_async: | ||
raise ValueError( | ||
"Cannot start a synchronous animation while another is running." | ||
) | ||
elif run_async is None: | ||
run_async = False | ||
|
||
if run_async: | ||
animations = self.compile_animations(*animations, **kwargs) | ||
|
||
self.add_mobjects_from_animations(animations) | ||
for animation in animations: | ||
self._turn_animation_to_updater(animation) | ||
return | ||
else: | ||
self.renderer.play(self, *animations, **kwargs) | ||
|
||
def _turn_animation_to_updater(self, animation: Animation): | ||
"""Turn an :class:`~.Animation` into an updater of it's :class:`~.Mobject`. | ||
|
||
The updater will remove itself when the animation is over. | ||
|
||
Parameters | ||
---------- | ||
animation | ||
The animation to be added | ||
|
||
""" | ||
mobject = animation.mobject | ||
|
||
animation.suspend_mobject_updating = False | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Question : why ? |
||
animation.begin() | ||
|
||
def update(mob, dt): | ||
run_time = animation.get_run_time() | ||
alpha = update.total_time / run_time | ||
if alpha >= 1: | ||
animation.finish() | ||
animation.clean_up_from_scene(self) | ||
mob.remove_updater(update) | ||
return | ||
animation.interpolate(alpha) | ||
animation.update_mobjects(dt) | ||
update.total_time += dt | ||
|
||
update.total_time = 0 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Coment : Although I undestand what you're doing here, I'm not really a big fan of adding attributes to functions. |
||
mobject.add_updater(update) | ||
return self | ||
|
||
def wait(self, duration=DEFAULT_WAIT_TIME, stop_condition=None): | ||
self.play(Wait(run_time=duration, stop_condition=stop_condition)) | ||
|
@@ -946,6 +1032,7 @@ def play_internal(self, skip_rendering=False): | |
named parameters affecting what was passed in ``args``, | ||
e.g. ``run_time``, ``lag_ratio`` and so on. | ||
""" | ||
self.sync_play_running = True | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think using a decorator here would make sense and would improve readability imo |
||
self.duration = self.get_run_time(self.animations) | ||
self.time_progression = self._get_animation_time_progression( | ||
self.animations, self.duration | ||
|
@@ -966,6 +1053,7 @@ def play_internal(self, skip_rendering=False): | |
self.renderer.static_image = None | ||
# Closing the progress bar at the end of the play. | ||
self.time_progression.close() | ||
self.sync_play_running = False | ||
|
||
def interactive_embed(self): | ||
""" | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe the whole logic can be simplified.
You could do instead
What do you think ?