Repository: ManimCommunity/manim
Pull request: Fixed OpenGL segfaults when running :meth:`.Scene.play` or :meth:`.Scene.wait` in interactive mode
## Overview: What does this pull request change?
self.play checks if it's running on the main thread; If it isn't, it schedules the play method to run using the queue.
<!-- If there is more information than the PR title that should be added to our release changelog, add it in the following changelog section. This is optional, but recommended for larger pull requests. -->
<!--changelog-start-->

<!--changelog-end-->

## Motivation and Explanation: Why and how do your changes improve the library?
`self.wait` and `self.play` no longer segfault when using the OpenGL renderer.


## Further Information and Comments
<!-- If applicable, put further comments for the reviewers here. -->
Closes #2669.

The cause (as much as I understand) has to do with calling OpenGL rendering from the subthread which segfaults. Therefore, attempting to run the `self.play` method from the subthread rather than through the (execution?) queue segfaults.
While it could have been nice to do something like
```python
local_namespace["self"].play = local_namespace["play"]
```
so that it just calls the "good" play, I think it's using the same object as the main thread(?) so changing the subthread's self.play changes the main's one which then probably leads to infinite recursion :(


<!-- Thank you again for contributing! Do not modify the lines below, they are for reviewers. -->
## Reviewer Checklist
- [x] The PR title is descriptive enough for the changelog, and the PR is labeled correctly
- [ ] If applicable: newly added non-private functions and classes have a docstring including a short summary and a PARAMETERS section
- [ ] If applicable: newly added functions and classes are tested


Problem statement:
Running `self.play` etc. in the IPython terminal spawned by `interactive_embed` does not work
## Description of bug / unexpected behavior
<!-- Add a clear and concise description of the problem you encountered. -->

While the scene is available as `self` in the spawned IPython terminal, running rendering methods like `self.wait` or `self.play` makes Manim crash (hard):
```
In [1]: self.wait()
zsh: segmentation fault
```


## Expected behavior
<!-- Add a clear and concise description of what you expected to happen. -->

The commands should run as expected, no segfault.

## How to reproduce the issue
<!-- Provide a piece of code illustrating the undesired behavior. -->


```py
class EmbedCrash(Scene):
    def construct(self):
        self.wait()
        self.interactive_embed()
```

Take this code, render with `--renderer=opengl`, then run `self.wait()` in the IPython shell.

## Additional comments
<!-- Add further context that you think might be relevant for this issue here. -->

There is some relevant code already which provides the functions `wait`, `play`, etc (without `self.`) in the IPython shell; either `interactive_embed` should monkey-patch `play`, `wait` to do the same -- or `self` should be turned into a special scene wrapper `InteractiveScene` or so which provides the appropriate methods directly.

Hints from issue discussion:

Commit message: make play run only on main thread

Patch to review:
diff --git a/manim/scene/scene.py b/manim/scene/scene.py
index 397fab6a8a..ee04413a39 100644
--- a/manim/scene/scene.py
+++ b/manim/scene/scene.py
@@ -1008,6 +1008,22 @@ def play(
             All other keywords are passed to the renderer.
 
         """
+        # Make sure this is running on the main thread
+        if threading.current_thread().name != "MainThread":
+            self.queue.put(
+                (
+                    "play",
+                    args,
+                    kwargs
+                    | {
+                        "subcaption": subcaption,
+                        "subcaption_duration": subcaption_duration,
+                        "subcaption_offset": subcaption_offset,
+                    },
+                )
+            )
+            return
+
         start_time = self.renderer.time
         self.renderer.play(self, *args, **kwargs)
         run_time = self.renderer.time - start_time


Code review comments:
diff_hunk: @@ -1008,6 +1008,22 @@ def play(
             All other keywords are passed to the renderer.
 
         """
+        # Make sure this is running on the main thread
+        if threading.current_thread().name != "MainThread":
+            self.queue.put(
+                (
+                    "play",
+                    args,
+                    kwargs
+                    | {
path: manim/scene/scene.py
text: @user1:
I think it would be better to use the conventional update method for dicts, the `|` syntax was only introduced with like Python 3.9 and up, no? Without a corresponding `__future__` import, this will break Manim on Python 3.7 + 3.8.