Repository: ManimCommunity/manim

Issue:
Manim scene caching does not play well with the -n flag
Here's a scene

```python
from manim import Scene, Integer, ShowCreation, Transform

class TestScene(Scene):
    def construct(self):
        number = Integer(0)
        self.add(number)
        for i in range(10):
            self.play(Transform(number, Integer(i)))
```

### Problem 1

This scene cannot be rendered with `manim -pl test.py -n5,10`. That is, if **the first time you render it**, you try to render only a specific number of animations, it cannot be done. It can be rendered normally without specifying the -n flag: `manim -pl test.py`.

### Problem 2
After successfully rendering with `manim -pl test.py`, I tried again `manim -pl test.py -n5,10`, but the whole scene is being rendered. The -n flag is ignored.

### Comments

Judging by the output, I think this has to do with scene caching (cc @huguesdevimeux). I recommend that while we are working out the kinks of the scene cachinc system, we add a flag `--disable_caching`, so that it can still be used even while the caching system is not fully debugged.

UPDATE: even when using `--disable_caching`, you get the same behavior.


Discussion:
Thank you for the report. I will take a look at this ASAP.

There is already a such flag implemented with the same name ;) 
> There is already a such flag implemented with the same name ;)

Oh shit didn't realize that!
> UPDATE: even when using --disable_caching, you get the same behavior.

Then I don't think this is related to scene caching directly. My idea is that this is related to `skip_animation`, as its functioning had been slightly modified in PR #166 .

I was using the -n flag correctly about a week ago, and I'm pretty sure that it changed at the time that #166 was merged. In any case, I agree with you I think it has to do with `skip_animation`.
I won't be able to work on it until a couple of days. If you discover a fix, don't hesitate to PR it !
I believe the issue may be related to the following code

https://github.com/ManimCommunity/manim/blob/704b63e61435ccf9be325935acac532a62870d69/manim/scene/scene.py#L66-L79

On L66, `self.original_skipping_status` is set to whatever it was in the config. On L79, it is again set to the config.... but the config was set to `False` in the previous line. So the end result is that both the config and `self.original_skipping_status` end up being False, instead of actually restoring whatever it used to be. Is this intended? Is this a typo? Does this sound like it might be related to the current issue?

@huguesdevimeux I know you're busy so please feel free to ignore me until you can take a look!
As the comment says, these lines are when you run multiple scenes in a row. if you don't reset`skip_animation`, it will be set to `True `for the next iteration (i.e, the next scene to render, the next time Scene is instanced) `file_writer["skip_animation"]` would have been set to true as hashed data would have been used. (Note that this is an issue, file_writer_config, as well as all the config stuff, should be constant and never modified by anything within manim. I would like to hear your opinion on that, but here, for example, we could replace that by adding file_writer_config  attribute to Scene, so it will be reset everything we instance Scene).

I _think_ that the line 66 is useless, as this is as the end of a scene generation so it won't affect anything, and as `original_skipping_status` is set again every time by line 66.

By the way, it looks like it's not related to this issue as these lines don't concern `self.construct()` (not sure though)


The real issue is, I think, `up_to_animation` that used to be/is in `file_writer_config`. #166 modified some stuff here (see https://github.com/ManimCommunity/manim/pull/166/files#r467937389). I will explain why I think is the source of the issue : 

Before Scene Caching, manim used index names for partial movie files, Basically, first scene got for name 0001.mp4, second 0002.mp4, you get it. Now that manim has scene-caching, in addition to being cooler, manim names partial-movie-files with their respective hash, as you may have noticed. 
**THE PROBLEM IS :**  To handle `-n i,j` flag, in the old world, manim used to take all the partial movies whose name was between i and j. (so, if i  = 1 et j = 3, then manim would take 0001.mp4, 0002.mp4, etc). But, Hugues, you might say, what did you do to handle the `-n` flag now that partial movies files names completely changed ? 

**_The answer is simple :_** 

I completely forget to implement that. lol. Here is the bug ! 100% my fault. 

(Once again although I'm responsible for this and therefore should be the one in charge of fixing it, feel free to do it if you can) 
Thanks for the explanation.

> Note that this is an issue, file_writer_config, as well as all the config stuff, should be constant and never modified by anything within manim. I would like to hear your opinion on that, but here, for example, we could replace that by adding file_writer_config attribute to Scene, so it will be reset everything we instance Scene.

I would like to have the global config dict be immutable, and perhaps add `manim.config.modify_config(config_options)` and `manim.config.restore_config`, or something like this, in order to manage modification of the global config from a single point. But we can leave this for the future.
 
> I _think_ that the line 66 is useless, as this is as the end of a scene generation so it won't affect anything, and as `original_skipping_status` is set again every time by line 66.

I see what you mean. But my point is that `file_writer_config["skip_animations"]` is never being restored back to its original value. It's always set to False. Am I missing anything here?
 
> By the way, it looks like it's not related to this issue as these lines don't concern `self.construct()` (not sure though) 

I think you're right, I was just confused by L66 and L78, and I thought this was the source of the bug.

> I completely forget to implement that. lol. Here is the bug ! 100% my fault.

Lol. I'm glad we found the issue. I know you're busy, but I don't know how to proceed here. What would be the fix?
> But my point is that file_writer_config["skip_animations"] is never being restored back to its original value.

You're right, and I don't think it is an issue as it is set to false by default at each scene rendering (this flag is not meant to be used by the user in any way). 

> Lol. I'm glad we found the issue. I know you're busy, but I don't know how to proceed here. What would be the fix?

I'm not that _busy_, I'm just working on other things (**_tests_**) and this is my priority right now. But I will be able to take care of this in a few days, no doubt. 

I think we should be smarter than what was implemented before. My idea would be to do some tricks with `scene.num_plays` in @handle_caching play and @handle_caching_wait: if `scene.num_plays` is between i and j, then we skip everything to the next iteration, if not, we can continue normally and it should work fine. This is just a draft if you don't see really how to fix I will be able to work on it in a few days. 

> You're right, and I don't think it is an issue as it is set to false by default at each scene rendering (this flag is not meant to be used by the user in any way).

If it's never touched by the user, and it's only touched by each Scene instance, then it should be a member of Scene, not in the config. But we can leave that for another issue.

Resolving patch:
diff --git a/manim/config/config_utils.py b/manim/config/config_utils.py
--- a/manim/config/config_utils.py
+++ b/manim/config/config_utils.py
@@ -147,9 +147,7 @@ def _parse_file_writer_config(config_parser, args):
     }
 
     # For internal use (no CLI flag)
-    fw_config["skip_animations"] = any(
-        [fw_config["save_last_frame"], fw_config["from_animation_number"]]
-    )
+    fw_config["skip_animations"] = fw_config["save_last_frame"]
     fw_config["max_files_cached"] = default.getint("max_files_cached")
     if fw_config["max_files_cached"] == -1:
         fw_config["max_files_cached"] = float("inf")
diff --git a/manim/scene/scene.py b/manim/scene/scene.py
--- a/manim/scene/scene.py
+++ b/manim/scene/scene.py
@@ -68,6 +68,7 @@ def __init__(self, **kwargs):
         self.file_writer = SceneFileWriter(self, **file_writer_config,)
         self.play_hashes_list = []
         self.mobjects = []
+        self.original_skipping_status = file_writer_config["skip_animations"]
         # TODO, remove need for foreground mobjects
         self.foreground_mobjects = []
         self.num_plays = 0
@@ -85,7 +86,7 @@ def __init__(self, **kwargs):
         self.tear_down()
         # We have to reset these settings in case of multiple renders.
         file_writer_config["skip_animations"] = False
-        self.original_skipping_status = file_writer_config["skip_animations"]
+
         self.file_writer.finish()
         self.print_end_message()
 
@@ -779,10 +780,10 @@ def update_skipping_status(self):
         """
 
         if file_writer_config["from_animation_number"]:
-            if self.num_plays == file_writer_config["from_animation_number"]:
-                file_writer_config["skip_animations"] = False
+            if self.num_plays < file_writer_config["from_animation_number"]:
+                file_writer_config["skip_animations"] = True
         if file_writer_config["upto_animation_number"]:
-            if self.num_plays >= file_writer_config["upto_animation_number"]:
+            if self.num_plays > file_writer_config["upto_animation_number"]:
                 file_writer_config["skip_animations"] = True
                 raise EndSceneEarlyException()
 
@@ -800,8 +801,13 @@ def handle_caching_play(func):
 
         def wrapper(self, *args, **kwargs):
             self.revert_to_original_skipping_status()
+            self.update_skipping_status()
             animations = self.compile_play_args_to_animation_list(*args, **kwargs)
             self.add_mobjects_from_animations(animations)
+            if file_writer_config["skip_animations"]:
+                logger.debug(f"Skipping animation {self.num_plays}")
+                func(self, *args, **kwargs)
+                return
             if not file_writer_config["disable_caching"]:
                 mobjects_on_scene = self.get_mobjects()
                 hash_play = get_hash_from_play_call(
@@ -834,6 +840,7 @@ def handle_caching_wait(func):
 
         def wrapper(self, duration=DEFAULT_WAIT_TIME, stop_condition=None):
             self.revert_to_original_skipping_status()
+            self.update_skipping_status()
             if not file_writer_config["disable_caching"]:
                 hash_wait = get_hash_from_wait_call(
                     self.camera, duration, stop_condition, self.get_mobjects()
@@ -873,7 +880,6 @@ def handle_play_like_call(func):
         """
 
         def wrapper(self, *args, **kwargs):
-            self.update_skipping_status()
             allow_write = not file_writer_config["skip_animations"]
             self.file_writer.begin_animation(allow_write)
             func(self, *args, **kwargs)
diff --git a/manim/scene/scene_file_writer.py b/manim/scene/scene_file_writer.py
--- a/manim/scene/scene_file_writer.py
+++ b/manim/scene/scene_file_writer.py
@@ -44,6 +44,7 @@ def __init__(self, scene, **kwargs):
         self.init_output_directories()
         self.init_audio()
         self.frame_count = 0
+        self.index_partial_movie_file = 0
 
     # Output directories and files
     def init_output_directories(self):
@@ -191,10 +192,11 @@ def get_next_partial_movie_path(self):
         result = os.path.join(
             self.partial_movie_directory,
             "{}{}".format(
-                self.scene.play_hashes_list[self.scene.num_plays],
+                self.scene.play_hashes_list[self.index_partial_movie_file],
                 file_writer_config["movie_file_extension"],
             ),
         )
+        self.index_partial_movie_file += 1
         return result
 
     def get_movie_file_path(self):


Test patch:
diff --git a/tests/control_data/videos_data/SceneWithMultipleCallsWithNFlag.json b/tests/control_data/videos_data/SceneWithMultipleCallsWithNFlag.json
new file mode 100644
--- /dev/null
+++ b/tests/control_data/videos_data/SceneWithMultipleCallsWithNFlag.json
@@ -0,0 +1,11 @@
+{
+    "name": "SceneWithMultipleCallsWithNFlag",
+    "config": {
+        "codec_name": "h264",
+        "width": 1920,
+        "height": 1080,
+        "avg_frame_rate": "60/1",
+        "duration": "4.000000",
+        "nb_frames": "240"
+    }
+}
\ No newline at end of file
diff --git a/tests/helpers/video_utils.py b/tests/helpers/video_utils.py
--- a/tests/helpers/video_utils.py
+++ b/tests/helpers/video_utils.py
@@ -4,7 +4,7 @@
 import subprocess
 import json
 
-from manim.logger import logger
+from manim.config.logger import logger
 
 
 def capture(command):
diff --git a/tests/test_scene_rendering/simple_scenes.py b/tests/test_scene_rendering/simple_scenes.py
--- a/tests/test_scene_rendering/simple_scenes.py
+++ b/tests/test_scene_rendering/simple_scenes.py
@@ -6,3 +6,12 @@ def construct(self):
         square = Square()
         circle = Circle()
         self.play(Transform(square, circle))
+
+
+class SceneWithMultipleCalls(Scene):
+    def construct(self):
+        number = Integer(0)
+        self.add(number)
+        for i in range(10):
+            number.become(Integer(i))
+            self.play(Animation(number))
diff --git a/tests/test_scene_rendering/test_cli_flags.py b/tests/test_scene_rendering/test_cli_flags.py
--- a/tests/test_scene_rendering/test_cli_flags.py
+++ b/tests/test_scene_rendering/test_cli_flags.py
@@ -44,3 +44,24 @@ def test_basic_scene_l_flag(tmp_path, manim_cfg_file, simple_scenes_path):
     ]
     out, err, exit_code = capture(command)
     assert exit_code == 0, err
+
+
+@pytest.mark.slow
+@video_comparison(
+    "SceneWithMultipleCallsWithNFlag.json",
+    "videos/simple_scenes/1080p60/SceneWithMultipleCalls.mp4",
+)
+def test_n_flag(tmp_path, simple_scenes_path):
+    scene_name = "SceneWithMultipleCalls"
+    command = [
+        "python",
+        "-m",
+        "manim",
+        simple_scenes_path,
+        scene_name,
+        "-n 3,6",
+        "--media_dir",
+        str(tmp_path),
+    ]
+    _, err, exit_code = capture(command)
+    assert exit_code == 0, err