-
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
Conversation
I've tried to play your example #1267, with preview enabled, after converting it to OpenGL, but it didn't work as it should. When I write to a file, I receive this error. ValueError: write to closed file Find the scene below. Note that you should temporarily change this line to class UsingUpdater(Scene):
def construct(self):
# Setup
dots = OpenGLVGroup(
*[
Dot(RIGHT * random.randrange(-6, 6) + UP * random.randrange(-2, 2))
for i in range(10)
]
)
line = Line(UP * 3, DOWN * 3).align_on_border(LEFT)
self.add(dots)
self.play(Create(line))
# Updater
def flash_hit_dots(mob):
for dot in dots:
if dot.get_center()[0] < mob.get_center()[0] and not hasattr(
dot, "marked"
):
self.play(Flash(dot, run_time=0.5))
dot.marked = True
line.add_updater(flash_hit_dots)
# Animate Line
self.play(line.animate.align_on_border(RIGHT), run_time=5)
self.wait() |
That is the error that one gets without the changes I made. Have you made sure that you are actually running the code on the branch of this PR? I just tested the same code and don't get that error.
I tested the code and had to make a tiny adjustment for it to work: class UsingUpdater(Scene):
def construct(self):
# Setup
dots = OpenGLVGroup(
*[
Dot(RIGHT * random.randrange(-6, 6) + UP * random.randrange(-2, 2))
for i in range(10)
]
)
line = Line(UP * 3, DOWN * 3).align_on_border(LEFT)
self.add(dots)
# Updater
def flash_hit_dots(mob):
for dot in dots:
if dot.get_center()[0] < line.get_center()[0] and not hasattr(
dot, "marked"
):
self.play(Flash(dot, run_time=0.5, rate_func=rush_from))
dot.marked = True
line.add_updater(flash_hit_dots)
# Animate Line
self.play(line.animate.align_on_border(RIGHT), run_time=5)
self.wait() Resulting in: UsingUpdater.mp4 |
The old scene still doesn't work but with your edits, it does. When the |
Definitely not, and I |
Thank you for the unstoppable contributions, your work is astonishing. This PR LGTM now. |
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.
So, that's a great addition. I left some comments.
This crucially needs two things to my eyes :
- Comments of the logic ; there are parts that are kind of obscure and need explanation.
- This needs test, I think both graphical and units. You could use mocks to test, I think.
Aside, that's (again!) a great contribution !
@@ -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 comment
The 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
""" | ||
mobject = animation.mobject | ||
|
||
animation.suspend_mobject_updating = False |
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.
Question : why ?
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 comment
The 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.
Why not turing update
into a proper class with a __call__
method ?
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 |
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
<run_sync is False by default)
if self.sync_play_running
run_sync = False if config[blabla] = blab else run_sync
if not run_sync:
# ValueError blabla
# and then the play logic (inside l 899).
# here (outside the if) the normal play logic.
What do you think ?
Hi, can I check on the status of this PR? |
Closing this for now because there is no activity. We can come back to this. The issue is on the project board and the PR is linked! |
Changelog / Overview
Scene.play
can take a new keyword argumentrun_async
. IfTrue
this will start the animation and return to executingScene.construct
immediately. This feature is only availably using theOpenGLRenderer
.Motivation
see #1488
closes #1267
Explanation for Changes
Documentation Reference
Testing Status
Further Comments
I would like to add an example to the docs, but as far as I know we cannot render OpenGl-videos to the docs, right?
Checklist
Reviewer Checklist