diff --git a/manimlib/animation/animation.py b/manimlib/animation/animation.py index 8ec26de2..e587f813 100644 --- a/manimlib/animation/animation.py +++ b/manimlib/animation/animation.py @@ -52,6 +52,7 @@ class Animation(object): # played. As much initialization as possible, # especially any mobject copying, should live in # this method + self.mobject.set_animating_status(True) self.starting_mobject = self.create_starting_mobject() if self.suspend_mobject_updating: # All calls to self.mobject's internal updaters @@ -66,6 +67,7 @@ class Animation(object): def finish(self) -> None: self.interpolate(self.final_alpha_value) + self.mobject.set_animating_status(False) if self.suspend_mobject_updating: self.mobject.resume_updating() diff --git a/manimlib/camera/camera.py b/manimlib/camera/camera.py index 6466ffc9..2884216c 100644 --- a/manimlib/camera/camera.py +++ b/manimlib/camera/camera.py @@ -199,7 +199,10 @@ class Camera(object): self.init_textures() self.init_light_source() self.refresh_perspective_uniforms() - self.static_mobject_to_render_group_list = {} + # A cached map from mobjects to their associated list of render groups + # so that these render groups are not regenerated unnecessarily for static + # mobjects + self.mob_to_render_groups = {} def init_frame(self) -> None: self.frame = CameraFrame(**self.frame_config) @@ -365,11 +368,21 @@ class Camera(object): if render_group["single_use"]: self.release_render_group(render_group) - def get_render_group_list(self, mobject: Mobject) -> list[dict[str]] | map[dict[str]]: - try: - return self.static_mobject_to_render_group_list[id(mobject)] - except KeyError: - return map(self.get_render_group, mobject.get_shader_wrapper_list()) + def get_render_group_list(self, mobject: Mobject) -> Iterable[dict[str]]: + if mobject.is_changing(): + return self.generate_render_group_list(mobject) + + # Otherwise, cache result for later use + key = id(mobject) + if key not in self.mob_to_render_groups: + self.mob_to_render_groups[key] = list(self.generate_render_group_list(mobject)) + return self.mob_to_render_groups[key] + + def generate_render_group_list(self, mobject: Mobject) -> Iterable[dict[str]]: + return ( + self.get_render_group(sw, single_use=mobject.is_changing()) + for sw in mobject.get_shader_wrapper_list() + ) def get_render_group( self, @@ -408,19 +421,10 @@ class Camera(object): if render_group[key] is not None: render_group[key].release() - def set_mobjects_as_static(self, *mobjects: Mobject) -> None: - # Creates buffer and array objects holding each mobjects shader data - for mob in mobjects: - self.static_mobject_to_render_group_list[id(mob)] = [ - self.get_render_group(sw, single_use=False) - for sw in mob.get_shader_wrapper_list() - ] - - def release_static_mobjects(self) -> None: - for rg_list in self.static_mobject_to_render_group_list.values(): - for render_group in rg_list: - self.release_render_group(render_group) - self.static_mobject_to_render_group_list = {} + def refresh_static_mobjects(self) -> None: + for render_group in it.chain(*self.mob_to_render_groups.values()): + self.release_render_group(render_group) + self.mob_to_render_groups = {} # Shaders def init_shaders(self) -> None: diff --git a/manimlib/mobject/mobject.py b/manimlib/mobject/mobject.py index 4d95b170..39454f6d 100644 --- a/manimlib/mobject/mobject.py +++ b/manimlib/mobject/mobject.py @@ -71,7 +71,7 @@ class Mobject(object): # Must match in attributes of vert shader "shader_dtype": [ ('point', np.float32, (3,)), - ] + ], } def __init__(self, **kwargs): @@ -81,6 +81,8 @@ class Mobject(object): self.family: list[Mobject] = [self] self.locked_data_keys: set[str] = set() self.needs_new_bounding_box: bool = True + self._is_animating: bool = False + self._is_movable: bool = False self.init_data() self.init_uniforms() @@ -421,7 +423,6 @@ class Mobject(object): self.center() return self - def sort( self, point_to_num_func: Callable[[np.ndarray], float] = lambda p: p[0], @@ -637,6 +638,23 @@ class Mobject(object): self.has_updaters = any(mob.get_updaters() for mob in self.get_family()) return self + # Check if mark as static or not for camera + + def is_changing(self) -> bool: + return self._is_animating or self.has_updaters or self._is_movable + + def set_animating_status(self, is_animating: bool) -> None: + self._is_animating = is_animating + + def set_movable_status(self, is_movable: bool) -> None: + self._is_movable = is_movable + + def is_movable(self) -> bool: + return self._is_movable + + def make_movable(self) -> None: + self._is_movable = True + # Transforming operations def shift(self, vector: np.ndarray): diff --git a/manimlib/scene/scene.py b/manimlib/scene/scene.py index 69d79db5..bb40ebc1 100644 --- a/manimlib/scene/scene.py +++ b/manimlib/scene/scene.py @@ -115,16 +115,16 @@ class Scene(object): # If there is a window, enter a loop # which updates the frame while under # the hood calling the pyglet event loop - log.info("Tips: You are now in the interactive mode. Now you can use the keyboard" - " and the mouse to interact with the scene. Just press `q` if you want to quit.") + log.info( + "Tips: You are now in the interactive mode. Now you can use the keyboard" + " and the mouse to interact with the scene. Just press `q` if you want to quit." + ) self.quit_interaction = False - self.lock_static_mobject_data() + self.refresh_static_mobjects() while not (self.window.is_closing or self.quit_interaction): self.update_frame(1 / self.camera.frame_rate) if self.window.is_closing: self.window.destroy() - if self.quit_interaction: - self.unlock_mobject_data() def embed(self, close_scene_on_exit: bool = True) -> None: if not self.preview: @@ -141,6 +141,7 @@ class Scene(object): from IPython.terminal.embed import InteractiveShellEmbed shell = InteractiveShellEmbed() # Have the frame update after each command + shell.events.register('post_run_cell', lambda *a, **kw: self.refresh_static_mobjects()) shell.events.register('post_run_cell', lambda *a, **kw: self.update_frame()) # Use the locals of the caller as the local namespace # once embedded, and add a few custom shortcuts @@ -442,6 +443,7 @@ class Scene(object): self.real_animation_start_time = time.time() self.virtual_animation_start_time = self.time + self.refresh_static_mobjects() func(self, *args, **kwargs) if should_write: @@ -450,23 +452,8 @@ class Scene(object): self.num_plays += 1 return wrapper - def lock_static_mobject_data(self, *animations: Animation) -> None: - movers = list(it.chain(*[ - anim.mobject.get_family() - for anim in animations - ])) - for mobject in self.mobjects: - if mobject in movers or mobject.get_family_updaters(): - continue - self.camera.set_mobjects_as_static(mobject) - - def unlock_mobject_data(self) -> None: - self.camera.release_static_mobjects() - - def refresh_locked_data(self): - self.unlock_mobject_data() - self.lock_static_mobject_data() - return self + def refresh_static_mobjects(self) -> None: + self.camera.refresh_static_mobjects() def begin_animations(self, animations: Iterable[Animation]) -> None: for animation in animations: @@ -506,11 +493,9 @@ class Scene(object): log.warning("Called Scene.play with no animations") return animations = self.anims_from_play_args(*args, **kwargs) - self.lock_static_mobject_data(*animations) self.begin_animations(animations) self.progress_through_animations(animations) self.finish_animations(animations) - self.unlock_mobject_data() @handle_play_like_call def wait( @@ -523,7 +508,6 @@ class Scene(object): if note: log.info(note) self.update_mobjects(dt=0) # Any problems with this? - self.lock_static_mobject_data() if self.presenter_mode and not self.skip_animations and not ignore_presenter_mode: while self.hold_on_wait: self.update_frame(dt=1 / self.camera.frame_rate) @@ -538,7 +522,7 @@ class Scene(object): self.emit_frame() if stop_condition is not None and stop_condition(): break - self.unlock_mobject_data() + self.refresh_static_mobjects() return self def wait_until(