Skip to content

Refactor: move more render lifecycle responsibilities to Manager - #5002

Open
behackl wants to merge 24 commits into
refactor/camera-renderer-ownershipfrom
refactor/manager-lifecycle
Open

behackl wants to merge 24 commits into
refactor/camera-renderer-ownershipfrom
refactor/manager-lifecycle

Conversation

@behackl

@behackl behackl commented Sep 8, 2026

Copy link
Copy Markdown
Member

Overview: What does this pull request change?

Stacked on #4989.

This PR makes Manager responsible for creating a scene's file writer and closing its rendering resources. Cairo and OpenGL follow the same cleanup rules for successful renders, failures, and CLI reruns.

  • Create the file writer on first use, making it available before setup() during rendering. Image inspection does not create a writer.
  • Allocate Cairo drawing buffers on demand. Defer OpenGL context, framebuffer, and preview-window creation until rendering or explicit GPU access; release partially created resources if initialization fails.
  • Stop unfinished encoding work and close rendering resources when a render fails, preserving the original exception, including interrupts. Failed direct play() calls also clean up, even outside render().
  • Open file logging when execution starts and remove only the log handler created for that scene.
  • Use a fresh scene and renderer for every CLI scene and rerun on both backends, while reusing a manager already attached by that scene's constructor.

Motivation and Explanation: Why and how do your changes improve the library?

An exception after encoding started could leave a worker waiting for more frames and prevent Python from exiting. Resource creation and cleanup were also spread across scene construction, renderers, and the CLI. Bringing cleanup together makes failures safer and allows scene construction and image inspection without opening unnecessary output resources.

Changes for callers

A renderer is used for one scene. Scene.renderer and renderer.file_writer are read-only; create a new renderer for another scene, and choose a custom writer through the renderer's file_writer_class constructor argument. Successive plays within a scene reuse the same renderer and writer.

Successful rendering normally closes the renderer before returning. To inspect its last-rendered frame, keep it open with an explicit manager context:

scene = MyScene()
manager = scene.manager or Manager(scene)
with manager:
    manager.render()
    frame = manager.renderer.get_frame()

For a fresh image of the current mobjects, scene.get_image() remains available after rendering. Raw OpenGL meshes require their original live context and must be inspected on its rendering thread.

Output and window settings are still saved during scene construction. Moving animation scheduling and the clock into Manager is left for the next PR; this branch does not add no-raster evaluation or timeline export.

Links to added or changed documentation pages

  • Updated the initialization, rendering, and cleanup discussion in docs/source/guides/deep_dive.rst.
  • Updated Manager, renderer, and Scene.renderer API docstrings.

Comment thread manim/manager.py
def cleanup(callback: Callable[[], Any]) -> None:
try:
callback()
except BaseException as error:
Comment thread manim/manager.py
def _cleanup_after_failure(self) -> None:
try:
self.close()
except BaseException:
def release(callback: Callable[[], Any]) -> None:
try:
callback()
except BaseException as error:
for packet in self._stream.encode():
self._container.mux(packet)
except Exception as error:
except BaseException as error:
try:
self._container.close()
except Exception as error:
except BaseException as error:
try:
self._container.close()
except Exception as error:
except BaseException as error:
try:
self.target.unlink(missing_ok=True)
except Exception as error:
except BaseException as error:

@nikolajmunk nikolajmunk left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Looks pretty good! I've added a bunch of suggestions for increased clarity in the docs. I suspect that once all these PRs are merged it would be a good idea to rewrite the entire deep dive section, but hopefully this helps for now.

Comment thread docs/source/guides/deep_dive.rst Outdated
Comment thread docs/source/guides/deep_dive.rst Outdated
Comment thread docs/source/guides/deep_dive.rst Outdated
Comment thread docs/source/guides/deep_dive.rst Outdated
Comment thread docs/source/guides/deep_dive.rst Outdated
Comment thread docs/source/guides/deep_dive.rst Outdated
@nikolajmunk

Copy link
Copy Markdown
Contributor

Question: I also note that there are quite a few catches of BaseException rather than Exception or some more precise exception type, which intuitively feels a bit weird to me. Am I assuming correctly that this is because you're trying to also catch things like KeyboardInterrupt?

For the record, I have no idea if there are better ways of going about it. It's entirely possible that except BaseException is simply the best thing to do!

@behackl behackl added breaking changes This PR introduces breaking changes refactor Refactor or redesign of existing code labels Sep 8, 2026
Comment thread docs/source/guides/deep_dive.rst Outdated
Comment on lines 296 to 305
@@ -304,6 +303,18 @@ frames or using media and cache resources. In contrast, ``format = none`` only
suppresses the primary artifact; an OpenGL live preview with automatic output
still rasterizes and displays frames without writing a file. Both requests have
an effective output format of ``none``, so the session's ``dry_run`` field

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change

Oops, I must have selected the wrong range of lines last time. Let's remove this part.

behackl and others added 19 commits September 9, 2026 20:02
Keep control-flow exceptions unwrapped during open and encode. Preserve the first failure when finish or abort cleanup also fails, retaining contextual wrapping for ordinary exceptions.
Add acceptance probes for allocation-free construction and lifecycle-wide cleanup. Nine strict xfails explicitly mark unfinished 3B1 boundaries; no Manager behavior changes in this commit.
Extend Manager's existing failure cleanup to setup, teardown, finalization, preview opening, and failed rerun handling. Preserve the primary exception if abort itself fails, without changing construction-control-flow handling or timed execution.
Keep resolved raster settings separate from optional target storage. Preserve initial readback, close guards, and snapshot isolation without changing settings resolution or animation scheduling.
Release acquired standalone attachments before their context, or close the owning window for a borrowed context. Preserve the primary exception while attempting every cleanup, and publish renderer state only after configuration succeeds.
Cover failures during parent context setup and later window configuration, preserving the constructor exception if close also fails. Exercise actual native windows with deterministic event delegates and cleanup even when a regression fails the test.
Explicit image or output requests may already attach a Manager before the CLI invokes a scene. Preserve that owner instead of trying to attach a second one, while retaining custom render overrides.
Construct the writer on explicit output demand or before setup, not during Scene construction. Keep renderer access as a single-owner compatibility view, preserve constructor-resolved settings and direct-play output, and never create a writer while cleaning up failed preflight or creation.
Capture concrete sizing and placement inputs once, and allow the opener to consume supplied immutable settings without rereading global configuration. Keep normal construction-time resolution and existing placement/HiDPI behavior; defer the later Manager opening cutover.
Keep Scene construction free of contexts/windows, open before setup or explicit GPU demand, and use a temporary headless host for cold snapshots. Retire owned targets, textures and cached programs on the owning thread, preserve legacy scene rebinding, and allow failed host cleanup to be retried.
Remove constructor-time log files, close the exact owned handler at render end, and retire backend resources on propagated failures or explicit Manager scope exit. Preserve primary failures, allow cleanup retries, protect newer backend bindings, and drain a replaced writer before transferring ownership.
Use fresh Scene-owned rendering state for each CLI scene or rerun. Remove backend rebinding and writer replacement, and abort failed direct plays without relying on an outer render scope. Consolidate lifecycle tests around fixed ownership and actual resource boundaries.
Co-authored-by: nikolajmunk <28557236+nikolajmunk@users.noreply.github.com>
@behackl
behackl force-pushed the refactor/manager-lifecycle branch from c3f055a to 7d4d2bb Compare September 9, 2026 20:02
Revert diagnostic-only commits 1e728de, 46fffe8, and 7d4d2bb. Preserve the WGL activation fix, regression coverage, and existing test deadlines.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

breaking changes This PR introduces breaking changes refactor Refactor or redesign of existing code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants