Repository: ManimCommunity/manim
File: manim/utils/hashing.py (Python)

Code before:
        _Memoizer.mark_as_processed(obj)
        if isinstance(obj, (dict, list, tuple)):
            return super().encode(self._cleaned_iterable(obj))
        return super().encode(obj)


def get_json(obj: Any) -> str:
    """Recursively serialize `object` to JSON using the :class:`CustomEncoder` class.

    Parameters
    ----------
    obj
        The dict to flatten

    Returns
    -------
    :class:`str`
        The flattened object
    """
    return json.dumps(obj, cls=_CustomEncoder)


def get_hash_from_play_call(
    scene_object: Scene,
    camera_object: Camera | OpenGLCamera,
    animations_list: Iterable[Animation] | None,
    current_mobjects_list: Iterable[Mobject],
) -> str:
    """Take the list of animations and a list of mobjects and output their hashes. This is meant to be used for `scene.play` function.

    Parameters
    -----------
    scene_object
        The scene object.

    camera_object
        The camera object used in the scene.

    animations_list
        The list of animations.

    current_mobjects_list
        The list of mobjects.

    Returns
    -------
    :class:`str`
        A string concatenation of the respective hashes of `camera_object`, `animations_list` and `current_mobjects_list`, separated by `_`.
    """
    logger.debug("Hashing ...")
    t_start = perf_counter()

Review comment (bug):
An issue with this is that `animations_list` is expected to be an iterable inside the function. If it's `None`, it will crash.

I noticed that `CairoRenderer.play(self, scene, *args, **kwargs)` passes `scene.animations` which is typed as `list[Animation] | None`. However, since `scene.compile_animation_data(*args, **kwargs)` is called before that, `scene.animations` will always be a `list[Animation]` at that point.

Therefore, instead of typing `animations_list` as `Iterable[Animation] | None`, you have to make an assertion inside `CairoRenderer.play()` that `scene.animations is not None` before the call to `get_hash_from_play_call()`.

Code after:
        Returns
        -------
        :class:`str`
           The object encoder with the standard json process.
        """
        _Memoizer.mark_as_processed(obj)
        if isinstance(obj, (dict, list, tuple)):
            return super().encode(self._cleaned_iterable(obj))
        return super().encode(obj)


def get_json(obj: Any) -> str:
    """Recursively serialize `object` to JSON using the :class:`CustomEncoder` class.

    Parameters
    ----------
    obj
        The dict to flatten

    Returns
    -------
    :class:`str`
        The flattened object
    """
    return json.dumps(obj, cls=_CustomEncoder)


def get_hash_from_play_call(
    scene_object: Scene,
    camera_object: Camera | OpenGLCamera,
    animations_list: Iterable[Animation],
    current_mobjects_list: Iterable[Mobject],
) -> str:
    """Take the list of animations and a list of mobjects and output their hashes. This is meant to be used for `scene.play` function.

    Parameters
    -----------
    scene_object
        The scene object.

    camera_object
        The camera object used in the scene.

    animations_list
        The list of animations.

    current_mobjects_list
        The list of mobjects.

    Returns
    -------