Add caching functionality, and have Tex and Text both use it for saved svg strings

This commit is contained in:
Grant Sanderson 2024-12-04 19:51:01 -06:00
parent 88370d4d5d
commit 129e512b0c
7 changed files with 64 additions and 46 deletions

View file

@ -61,6 +61,7 @@ from manimlib.scene.interactive_scene import *
from manimlib.scene.scene import * from manimlib.scene.scene import *
from manimlib.utils.bezier import * from manimlib.utils.bezier import *
from manimlib.utils.cache import *
from manimlib.utils.color import * from manimlib.utils.color import *
from manimlib.utils.dict_ops import * from manimlib.utils.dict_ops import *
from manimlib.utils.customization import * from manimlib.utils.customization import *

View file

@ -7,7 +7,7 @@ import re
from manimlib.constants import BLACK, WHITE from manimlib.constants import BLACK, WHITE
from manimlib.mobject.svg.svg_mobject import SVGMobject from manimlib.mobject.svg.svg_mobject import SVGMobject
from manimlib.mobject.types.vectorized_mobject import VGroup from manimlib.mobject.types.vectorized_mobject import VGroup
from manimlib.utils.tex_file_writing import tex_content_to_svg_file from manimlib.utils.tex_file_writing import tex_to_svg
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
@ -76,12 +76,12 @@ class SingleStringTex(SVGMobject):
self.additional_preamble self.additional_preamble
) )
def get_file_path(self) -> str: def get_svg_string_by_content(self, content: str) -> str:
content = self.get_tex_file_body(self.tex_string) return get_cached_value(
file_path = tex_content_to_svg_file( key=hash_string(str((content, self.template, self.additional_preamble))),
content, self.template, self.additional_preamble, self.tex_string value_func=lambda: tex_to_svg(content, self.template, self.additional_preamble),
message=f"Writing {self.tex_string}..."
) )
return file_path
def get_tex_file_body(self, tex_string: str) -> str: def get_tex_file_body(self, tex_string: str) -> str:
new_tex = self.get_modified_expression(tex_string) new_tex = self.get_modified_expression(tex_string)

View file

@ -20,7 +20,6 @@ from manimlib.mobject.types.vectorized_mobject import VMobject
from manimlib.utils.directories import get_mobject_data_dir from manimlib.utils.directories import get_mobject_data_dir
from manimlib.utils.images import get_full_vector_image_path from manimlib.utils.images import get_full_vector_image_path
from manimlib.utils.iterables import hash_obj from manimlib.utils.iterables import hash_obj
from manimlib.utils.simple_functions import hash_string
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
if TYPE_CHECKING: if TYPE_CHECKING:

View file

@ -6,10 +6,12 @@ from pathlib import Path
from manimlib.mobject.svg.string_mobject import StringMobject from manimlib.mobject.svg.string_mobject import StringMobject
from manimlib.mobject.types.vectorized_mobject import VGroup from manimlib.mobject.types.vectorized_mobject import VGroup
from manimlib.mobject.types.vectorized_mobject import VMobject from manimlib.mobject.types.vectorized_mobject import VMobject
from manimlib.utils.cache import get_cached_value
from manimlib.utils.color import color_to_hex from manimlib.utils.color import color_to_hex
from manimlib.utils.color import hex_to_int from manimlib.utils.color import hex_to_int
from manimlib.utils.tex_file_writing import tex_content_to_svg_file from manimlib.utils.tex_file_writing import tex_to_svg
from manimlib.utils.tex import num_tex_symbols from manimlib.utils.tex import num_tex_symbols
from manimlib.utils.simple_functions import hash_string
from manimlib.logger import log from manimlib.logger import log
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
@ -84,11 +86,11 @@ class Tex(StringMobject):
) )
def get_svg_string_by_content(self, content: str) -> str: def get_svg_string_by_content(self, content: str) -> str:
# TODO, implement this without writing to a file return get_cached_value(
file_path = tex_content_to_svg_file( key=hash_string(str((content, self.template, self.additional_preamble))),
content, self.template, self.additional_preamble, self.tex_string value_func=lambda: tex_to_svg(content, self.template, self.additional_preamble),
message=f"Writing {self.tex_string}..."
) )
return Path(file_path).read_text()
def _handle_scale_side_effects(self, scale_factor: float) -> Self: def _handle_scale_side_effects(self, scale_factor: float) -> Self:
self.font_size *= scale_factor self.font_size *= scale_factor

View file

@ -16,9 +16,10 @@ from manimlib.constants import DEFAULT_PIXEL_WIDTH, FRAME_WIDTH
from manimlib.constants import NORMAL from manimlib.constants import NORMAL
from manimlib.logger import log from manimlib.logger import log
from manimlib.mobject.svg.string_mobject import StringMobject from manimlib.mobject.svg.string_mobject import StringMobject
from manimlib.utils.customization import get_customization from manimlib.utils.cache import get_cached_value
from manimlib.utils.color import color_to_hex from manimlib.utils.color import color_to_hex
from manimlib.utils.color import int_to_hex from manimlib.utils.color import int_to_hex
from manimlib.utils.customization import get_customization
from manimlib.utils.directories import get_downloads_dir from manimlib.utils.directories import get_downloads_dir
from manimlib.utils.directories import get_text_dir from manimlib.utils.directories import get_text_dir
from manimlib.utils.simple_functions import hash_string from manimlib.utils.simple_functions import hash_string
@ -172,17 +173,14 @@ class MarkupText(StringMobject):
) )
def get_svg_string_by_content(self, content: str) -> str: def get_svg_string_by_content(self, content: str) -> str:
# TODO, check the cache key = hash_string(str((
hash_content = str((
content, content,
self.justify, self.justify,
self.indent, self.indent,
self.alignment, self.alignment,
self.line_width self.line_width
)) )))
# hash_string(hash_content) return get_cached_value(key, lambda: self.markup_to_svg_string(content))
key = hashlib.sha256(hash_content.encode()).hexdigest()
return self.markup_to_svg_string(content)
def markup_to_svg_string(self, markup_str: str) -> str: def markup_to_svg_string(self, markup_str: str) -> str:
self.validate_markup_string(markup_str) self.validate_markup_string(markup_str)

33
manimlib/utils/cache.py Normal file
View file

@ -0,0 +1,33 @@
import appdirs
import os
from diskcache import Cache
from contextlib import contextmanager
CACHE_SIZE = 1e9 # 1 Gig
def get_cached_value(key, value_func, message=""):
cache_dir = appdirs.user_cache_dir("manim")
cache = Cache(cache_dir, size_limit=CACHE_SIZE)
value = cache.get(key)
if value is None:
with display_during_execution(message):
value = value_func()
cache.set(key, value)
return value
@contextmanager
def display_during_execution(message: str):
# Merge into a single line
to_print = message.replace("\n", " ")
max_characters = os.get_terminal_size().columns - 1
if len(to_print) > max_characters:
to_print = to_print[:max_characters - 3] + "..."
try:
print(to_print, end="\r")
yield
finally:
print(" " * len(to_print), end="\r")

View file

@ -1,10 +1,12 @@
from __future__ import annotations from __future__ import annotations
from contextlib import contextmanager
import os import os
import re import re
import yaml import yaml
from pathlib import Path
import tempfile
from manimlib.config import get_custom_config from manimlib.config import get_custom_config
from manimlib.config import get_manim_dir from manimlib.config import get_manim_dir
from manimlib.logger import log from manimlib.logger import log
@ -51,9 +53,10 @@ def get_tex_config() -> dict[str, str]:
return SAVED_TEX_CONFIG return SAVED_TEX_CONFIG
def tex_content_to_svg_file( def tex_to_svg(
content: str, template: str, additional_preamble: str, content: str,
short_tex: str template: str,
additional_preamble: str,
) -> str: ) -> str:
tex_config = get_tex_config() tex_config = get_tex_config()
if not template or template == tex_config["template"]: if not template or template == tex_config["template"]:
@ -74,14 +77,11 @@ def tex_content_to_svg_file(
"\\end{document}" "\\end{document}"
)) + "\n" )) + "\n"
svg_file = os.path.join( with tempfile.NamedTemporaryFile(suffix='.svg', mode='r+') as tmp:
get_tex_dir(), hash_string(full_tex) + ".svg" create_tex_svg(full_tex, tmp.name, compiler)
) # Read the contents
if not os.path.exists(svg_file): tmp.seek(0)
# If svg doesn't exist, create it return tmp.read()
with display_during_execution("Writing " + short_tex):
create_tex_svg(full_tex, svg_file, compiler)
return svg_file
def create_tex_svg(full_tex: str, svg_file: str, compiler: str) -> None: def create_tex_svg(full_tex: str, svg_file: str, compiler: str) -> None:
@ -145,20 +145,5 @@ def create_tex_svg(full_tex: str, svg_file: str, compiler: str) -> None:
pass pass
# TODO, perhaps this should live elsewhere
@contextmanager
def display_during_execution(message: str):
# Merge into a single line
to_print = message.replace("\n", " ")
max_characters = os.get_terminal_size().columns - 1
if len(to_print) > max_characters:
to_print = to_print[:max_characters - 3] + "..."
try:
print(to_print, end="\r")
yield
finally:
print(" " * len(to_print), end="\r")
class LatexError(Exception): class LatexError(Exception):
pass pass