Repository: ManimCommunity/manim
File: manim/scene/scene.py (Python)

Code before:

        If old_mobject is a submobject of some other Mobject (e.g. a Group),
        the new_mobject will replace it inside the group, without otherwise
        changing the parent mobject.

        Parameters
        ----------
        old_mobject - A Mobject which must be in the scene. This method asserts if
                      old_mobject is not in the scene
        new_mobject - A Mobject which must not already be in the scene.

        """
        assert old_mobject is not None
        assert new_mobject is not None

        def replace_in_list(
            mobj_list: list[Mobject], old_m: Mobject, new_m: Mobject
        ) -> bool:
            for i in range(0, len(mobj_list)):
                # Is this the old mobject?
                if mobj_list[i] == old_m:
                    # If so, write the new object to the same spot and stop looking.
                    mobj_list[i] = new_m
                    return True
                # It is not this object, so recursively look into submobjects
                if replace_in_list(mobj_list[i].submobjects, old_m, new_m):
                    # If we found it in a submobject, stop looking.
                    return True
            # If we did not find the mobject in the mobject list or any submobjects,
            # (or the list was empty), indicate we did not make the replacement.
            return False

        # Make use of short-circuiting conditionals to check mobjects and then
        # foreground_mobjects
        replaced = replace_in_list(
            self.mobjects, old_mobject, new_mobject
        ) or replace_in_list(self.foreground_mobjects, old_mobject, new_mobject)
        assert replaced, "Could not find old_mobject in Scene"

    def add_updater(self, func: Callable[[float], None]) -> None:
        """Add an update function to the scene.

        The scene updater functions are run every frame,
        and they are the last type of updaters to run.

        .. WARNING::

            When using the Cairo renderer, scene updaters that
            modify mobjects are not detected in the same way
            that mobject updaters are. To be more concrete,
            a mobject only modified via a scene updater will

Review comment (performance):
I'm a little concerned about performance with the depth first traversal approach you are doing here to go through submobjects. As `ReplacementTransform` is generally used for "first level" mobjects (i.e, at the first level of the scene graph), I think that a BFS approach is more suited for this problem. It would avoid unnecessarily going through numerous submobjects.
It can be a big game changer for scenes with heavy nested mobjects, such as `StreamLines`, for example.

Code after:

        If old_mobject is a submobject of some other Mobject (e.g. a Group),
        the new_mobject will replace it inside the group, without otherwise
        changing the parent mobject.

        Parameters
        ----------
        old_mobject - A Mobject which must be in the scene. This method asserts if
                      old_mobject is not in the scene
        new_mobject - A Mobject which must not already be in the scene.

        """
        assert old_mobject is not None
        assert new_mobject is not None

        def replace_in_list(
            mobj_list: list[Mobject], old_m: Mobject, new_m: Mobject
        ) -> bool:
            # We use breadth-first search because some Mobjects get very deep and
            # we expect top-level elements to be the most common targets for replace.
            for i in range(0, len(mobj_list)):
                # Is this the old mobject?
                if mobj_list[i] == old_m:
                    # If so, write the new object to the same spot and stop looking.
                    mobj_list[i] = new_m
                    return True
            # Now check all the children of all these mobs.
            for mob in mobj_list:  # noqa: SIM110
                if replace_in_list(mob.submobjects, old_m, new_m):
                    # If we found it in a submobject, stop looking.
                    return True
            # If we did not find the mobject in the mobject list or any submobjects,
            # (or the list was empty), indicate we did not make the replacement.
            return False

        # Make use of short-circuiting conditionals to check mobjects and then
        # foreground_mobjects
        replaced = replace_in_list(
            self.mobjects, old_mobject, new_mobject
        ) or replace_in_list(self.foreground_mobjects, old_mobject, new_mobject)
        assert replaced, "Could not find old_mobject in Scene"

    def add_updater(self, func: Callable[[float], None]) -> None:
        """Add an update function to the scene.

        The scene updater functions are run every frame,
        and they are the last type of updaters to run.

        .. WARNING::

            When using the Cairo renderer, scene updaters that