Skip to content

Refactored :meth:~.Scene.play #1019

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

Conversation

huguesdevimeux
Copy link
Member

@huguesdevimeux huguesdevimeux commented Feb 10, 2021

Motivation

Main motivation was to fix #874, and I ended but by refactoring everything.
The main goal was to refactor scene.compile_animation_data. This method did way too much, including starting the animations, determining the run time and the time progression, saving static frame, etc.

Overview / Explanation for Changes

  • Removed the three decorators of :meth:~.Scene.play, in particular: caching logic and file writer logic are now included within :meth:~.Scene.play (it wasn't possible before, because scene.wait and scene.play were two different things).
  • Added is_static_wait attributes to Wait. (<=> if wait is a frozen frame).
  • Renamed and moved scene.add_static_frame to renderer.freeze_current_frame.
  • Now when calling play without animation, it raises ValueError instead of just a warning.
  • Fixed :pr:874 by modfying renderer.update_skipping_status
  • renderer starts the animation with scene.begin_animations (scene.compile_animation_data used to do this)
  • The run time and the time progression generation is now done in scene.play_internal although it'd make more sense that renderer processes it later.
  • Added a bunch of cool tests thanks to mocks, and thanks to the new syntax scene.render

Oneline Summary of Changes

- Refactored play logic (:pr:`1019`)

Testing Status

Tests run. Added new ones.

Further Comments

This has probably broken a little of the webgl renderer.
I let scene.compile_animation_data returns None or self in function of the play has some informations (if it's a static wait, it returns non, else it returns .. self). I would like to remove this logic, as it makes not really a sense that a such method returns any information.
cc @eulertour

Acknowledgements

Reviewer Checklist

  • Newly added functions/classes are either private or have a docstring
  • Newly added functions/classes have tests added and (optional) examples in the docs
  • Newly added documentation builds, looks correctly formatted, and adds no additional build warnings
  • The oneline summary has been included in the wiki

@behackl
Copy link
Member

behackl commented Feb 14, 2021

Tests do not pass for Python 3.7.

@huguesdevimeux
Copy link
Member Author

Tests do not pass for Python 3.7.

Fixed now. Thanks!

Copy link
Member

@eulertour eulertour left a comment

Choose a reason for hiding this comment

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

Only small nitpicks, this mostly looks good.

@huguesdevimeux
Copy link
Member Author

@eulertour So, I was worried that this could break the WebGL renderer. Is this ok?

@eulertour
Copy link
Member

I'd greatly prefer if it didn't since I'll probably stop working on it for a while after this release. Can you just ensure it doesn't break anything obvious?

@huguesdevimeux
Copy link
Member Author

I'd greatly prefer if it didn't since I'll probably stop working on it for a while after this release. Can you just ensure it doesn't break anything obvious?

So, I did that, the only thing I see that could break it is that now the animations are started in begin_animations, not in compile_animations_data anymore.

I didn't test it though because I'm completly unfamiliar with it, but it should be good.

@eulertour
Copy link
Member

It throws this error in case you can tell why that would happen:

  File "/home/devneal/github/manim-3/manim/grpc/impl/frame_server_impl.py", line 285, in load_scene_module
    self.generate_keyframe_data()

  File "/home/devneal/github/manim-3/manim/grpc/impl/frame_server_impl.py", line 295, in generate_keyframe_data
    self.scene.render()

  File "/home/devneal/github/manim-3/manim/scene/scene.py", line 169, in render
    self.construct()

  File "/home/devneal/github/manim-3/example_scenes/basic.py", line 80, in construct
    self.play(ShowCreation(square))

  File "/home/devneal/github/manim-3/manim/scene/scene.py", line 763, in play
    self.renderer.play(self, *args, **kwargs)

  File "/home/devneal/github/manim-3/manim/renderer/webgl_renderer.py", line 37, in play
    scene.play_internal(skip_rendering=True)

  File "/home/devneal/github/manim-3/manim/scene/scene.py", line 867, in play_internal
    self.update_to_time(t)

  File "/home/devneal/github/manim-3/manim/scene/scene.py", line 885, in update_to_time
    animation.update_mobjects(dt)

  File "/home/devneal/github/manim-3/manim/animation/animation.py", line 124, in update_mobjects
    mob.update(dt)

AttributeError: 'NoneType' object has no attribute 'update'

@huguesdevimeux
Copy link
Member Author

huguesdevimeux commented Mar 7, 2021

@eulertour At first sight I've no idea. This didn't in theory touche the mobject processing.
Maybe it's because of this ?

What was the scene ?

EDIT : after testing locally, it seems that the mobject logic is broken.
image
the mobjects are set to None for some reason.

@jsonvillanueva
Copy link
Member

jsonvillanueva commented Mar 9, 2021

And on Windows there's a DirDeletedEvent saying that basic.py is a directory which seems strange...

image

Edit: It also reports None issues, and I've tried reverting the raised exception from here to no avail.

@jsonvillanueva
Copy link
Member

jsonvillanueva commented Mar 9, 2021

After further debugging, it seems the issue is with the attribute .duration. If you change the scene to have Wait() animations (since Wait() sets this attribute, it works as intended). For other types of Animations inheriting from Animation, duration isn't ever set as an attribute -- hence the TypeError.

Copy link
Member

@jsonvillanueva jsonvillanueva left a comment

Choose a reason for hiding this comment

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

Some short feedback. The refactor seems fine otherwise.

Comment on lines +19 to +22
@pytest.mark.skipif(
sys.version_info < (3, 8),
reason="Mock object has a different implementation in python 3.7, which makes it broken with this logic.",
)
Copy link
Member

Choose a reason for hiding this comment

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

I'm unfamiliar with Mock, but if it's possible to configure this so that it runs on 3.7 (maybe using sys.version?) then I would prefer to not skip tests

Copy link
Member Author

Choose a reason for hiding this comment

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

I will see what I can do.

To be honest, I didn't want to spend too much on investigating on this uncompataibilty, givent that this test is logically independent from the python version.

@@ -248,6 +248,7 @@ def __init__(
self.duration = duration
self.mobject = None
self.stop_condition = stop_condition
self.is_static_wait = False
Copy link
Member

Choose a reason for hiding this comment

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

This is fine, but I think the name/phrase "static_wait" is misleading since a static variable in programming (and Python more specifically) refers to a variable shared between instances of the class.

Here the name is based on whether the Animation has updaters, so a better name to use here/elsewhere in Scene.py may be updaters, has_updaters, ...

Copy link
Member Author

Choose a reason for hiding this comment

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

Though I agree with you, I chose this name because it is how this type of wait is called within the codenbase.

I don't really like has_udapters, because it's not really what this variable is supposed to express here.
I thinl is_frozen_wait is fine, what do you think ?

Comment on lines +75 to +76
# Save a static image, to avoid rendering non moving objects.
self.static_image = self.save_static_frame_data(scene, scene.static_mobjects)
Copy link
Member

Choose a reason for hiding this comment

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

Altogether, the adjective "static" isn't helpful here since an image is already understood to be non-moving media. A better adjective might be "stationary", or "frozen" so it's not confused with static (as in @staticmethod) but it's not the image that is stationary/frozen, it's the Animation/the Mobjects in the Animation.

Suggested change
# Save a static image, to avoid rendering non moving objects.
self.static_image = self.save_static_frame_data(scene, scene.static_mobjects)
# Save a image, to avoid rendering non moving objects.
self.image = self.save_frame_data(scene, scene.static_mobjects)

Copy link
Member Author

Choose a reason for hiding this comment

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

Just for the record, this code is not mine (I moved this piece from elsewhere to here), the term static_image is braodly used to say that an image is static.

I believe this is not the right PR toc hange this, because it was not the main goal, but it's defintely an issue.

Copy link
Member

Choose a reason for hiding this comment

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

It's just an issue of better variable names, but if it helps move things along quicker then I'm fine with doing this in a future PR.

Copy link
Member Author

@huguesdevimeux huguesdevimeux left a comment

Choose a reason for hiding this comment

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

@jsonvillanueva Thanks for the review, I answered to your suggestions

@@ -248,6 +248,7 @@ def __init__(
self.duration = duration
self.mobject = None
self.stop_condition = stop_condition
self.is_static_wait = False
Copy link
Member Author

Choose a reason for hiding this comment

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

Though I agree with you, I chose this name because it is how this type of wait is called within the codenbase.

I don't really like has_udapters, because it's not really what this variable is supposed to express here.
I thinl is_frozen_wait is fine, what do you think ?

Comment on lines +75 to +76
# Save a static image, to avoid rendering non moving objects.
self.static_image = self.save_static_frame_data(scene, scene.static_mobjects)
Copy link
Member Author

Choose a reason for hiding this comment

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

Just for the record, this code is not mine (I moved this piece from elsewhere to here), the term static_image is braodly used to say that an image is static.

I believe this is not the right PR toc hange this, because it was not the main goal, but it's defintely an issue.

Comment on lines +19 to +22
@pytest.mark.skipif(
sys.version_info < (3, 8),
reason="Mock object has a different implementation in python 3.7, which makes it broken with this logic.",
)
Copy link
Member Author

Choose a reason for hiding this comment

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

I will see what I can do.

To be honest, I didn't want to spend too much on investigating on this uncompataibilty, givent that this test is logically independent from the python version.

@huguesdevimeux
Copy link
Member Author

I fear that this PR is made irrelevant with the new opengl_renderer, since the play logic is the same in open_gl renderer. and should be refactroed as well.

As long as open_gl renderer does not have proper tests, I won't work on it.

The tests are nevertheless addable, so I will make a separate PR.

@leotrs
Copy link
Contributor

leotrs commented Mar 22, 2021

Not irrelevant at all, still very desirable to refactor the play logic in the cairo renderer I think. So, unless we are planning to stop all development on the cairo renderer, this should be merged.

@huguesdevimeux
Copy link
Member Author

Not irrelevant at all, still very desirable to refactor the play logic in the cairo renderer I think. So, unless we are planning to stop all development on the cairo renderer, this should be merged.

Thanks for the reply. I changed my mind, so I tried to put again the old stuff this refactor has deleted (the cairo renderer doesn't use it anymore), to keep the opengl renderer working.

I tried a scene and it was working. Can someone confirm that it works ?

PS : Why on earth the OpenGL renderer does not have tests ??

PPS: Happy to see that you're back, @leotrs

@leotrs
Copy link
Contributor

leotrs commented Mar 22, 2021

PS : Why on earth the OpenGL renderer does not have tests ??

We need ASAP before continuing development, I think.

PPS: Happy to see that you're back, @leotrs

StoeNoDkYuum8cj8MV

@@ -60,7 +63,7 @@ def __init__(self, camera_class=None, skip_animations=False, **kwargs):
self.file_writer = None
camera_cls = camera_class if camera_class is not None else Camera
self.camera = camera_cls()
self.original_skipping_status = skip_animations
self._original_skipping_status = skip_animations
Copy link
Member

Choose a reason for hiding this comment

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

Either this needs to change or opengl_renderer.py needs to change

Copy link
Member Author

Choose a reason for hiding this comment

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

Good catch. I'll modify it

@huguesdevimeux
Copy link
Member Author

Thanks to @jsonvillanueva it is now seemingly working for the openGL renderer!

Copy link
Member

@eulertour eulertour left a comment

Choose a reason for hiding this comment

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

Trying an OpenGL scene crashes for me:

 manim-2 git:(refactored-play-logic)  poetry run manim example_scenes/opengl.py SurfaceExample --use_opengl_renderer -p -ql
Manim Community v0.4.0
Traceback (most recent call last):
╭──────────────────────────────────────────────────────────────────────────────────────╮
│ File "/home/devneal/github/manim-2/manim/__main__.py", line 84, in main              │
│     81                 try:                                                          │
│     82                     renderer = OpenGLRenderer()                               │
│     83                     scene = SceneClass(renderer)                              │
│  ❱  84                     scene.render()                                            │
│     85                 except Exception:                                             │
│     86                     console.print_exception()                                 │
│     87         elif config["use_webgl_renderer"]:                                    │
│ File "/home/devneal/github/manim-2/manim/scene/scene.py", line 177, in render        │
│     174         """                                                                  │
│     175         self.setup()                                                         │
│     176         try:                                                                 │
│  ❱  177             self.construct()                                                 │
│     178         except EndSceneEarlyException:                                       │
│     179             pass                                                             │
│     180         self.tear_down()                                                     │
│ File "/home/devneal/github/manim-2/example_scenes/opengl.py", line 99, in construct  │
│     96                                                                               │
│     97         surface = surfaces[0]                                                 │
│     98                                                                               │
│  ❱  99         self.play(                                                            │
│    100             FadeIn(surface),                                                  │
│    101             ShowCreation(surface.mesh, lag_ratio=0.01, run_time=3),           │
│    102         )                                                                     │
│ File "/home/devneal/github/manim-2/manim/scene/scene.py", line 789, in play          │
│     786             return np.max([animation.run_time for animation in animations])  │
│     787                                                                              │
│     788     def play(self, *args, **kwargs):                                         │
│  ❱  789         self.renderer.play(self, *args, **kwargs)                            │
│     790                                                                              │
│     791     def wait(self, duration=DEFAULT_WAIT_TIME, stop_condition=None):         │
│     792         self.play(Wait(duration=duration, stop_condition=stop_condition))    │
│ File "/home/devneal/github/manim-2/manim/utils/caching.py", line 27, in wrapper      │
│    24     # method has to be deleted.                                                │
│    25                                                                                │
│    26     def wrapper(self, scene, *args, **kwargs):                                 │
│  ❱ 27         self.skip_animations = self.original_skipping_status                   │
│    28         self.update_skipping_status()                                          │
│    29         animations = scene.compile_animations(*args, **kwargs)                 │
│    30         scene.add_mobjects_from_animations(animations)                         │
╰──────────────────────────────────────────────────────────────────────────────────────╯
AttributeError: 'OpenGLRenderer' object has no attribute 'original_skipping_status'
 manim-2 git:(refactored-play-logic) 

Co-authored-by: Devin Neal <devin@eulertour.com>
@huguesdevimeux
Copy link
Member Author

@eulertour the last commit I made was seemingly wrong.
I commîtes your suggestion

@huguesdevimeux huguesdevimeux merged commit fc6159b into ManimCommunity:master Mar 23, 2021
@huguesdevimeux huguesdevimeux deleted the refactored-play-logic branch March 23, 2021 08:58
@jsonvillanueva jsonvillanueva added refactor Refactor or redesign of existing code maintenance refactoring, typos, removing clutter/dead code, and other code quality improvements and removed refactor Refactor or redesign of existing code labels Mar 23, 2021
@jsonvillanueva jsonvillanueva changed the title Refactored play logic Refactored :meth:~.Scene.play Mar 24, 2021
@jsonvillanueva jsonvillanueva added the testing Anything related to testing the library label Mar 24, 2021
@behackl behackl added this to the Release v0.5.0 milestone Mar 24, 2021
@huguesdevimeux huguesdevimeux removed the testing Anything related to testing the library label Mar 27, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
maintenance refactoring, typos, removing clutter/dead code, and other code quality improvements
Projects
None yet
Development

Successfully merging this pull request may close these issues.

save_last_frame not working properly
5 participants