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.utils.bezier import *
from manimlib.utils.cache import *
from manimlib.utils.color import *
from manimlib.utils.dict_ops import *
from manimlib.utils.customization import *

View file

@ -7,7 +7,7 @@ import re
from manimlib.constants import BLACK, WHITE
from manimlib.mobject.svg.svg_mobject import SVGMobject
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
@ -76,12 +76,12 @@ class SingleStringTex(SVGMobject):
self.additional_preamble
)
def get_file_path(self) -> str:
content = self.get_tex_file_body(self.tex_string)
file_path = tex_content_to_svg_file(
content, self.template, self.additional_preamble, self.tex_string
def get_svg_string_by_content(self, content: str) -> str:
return get_cached_value(
key=hash_string(str((content, self.template, self.additional_preamble))),
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:
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.images import get_full_vector_image_path
from manimlib.utils.iterables import hash_obj
from manimlib.utils.simple_functions import hash_string
from typing import 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.types.vectorized_mobject import VGroup
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 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.simple_functions import hash_string
from manimlib.logger import log
from typing import TYPE_CHECKING
@ -84,11 +86,11 @@ class Tex(StringMobject):
)
def get_svg_string_by_content(self, content: str) -> str:
# TODO, implement this without writing to a file
file_path = tex_content_to_svg_file(
content, self.template, self.additional_preamble, self.tex_string
return get_cached_value(
key=hash_string(str((content, self.template, self.additional_preamble))),
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:
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.logger import log
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 int_to_hex
from manimlib.utils.customization import get_customization
from manimlib.utils.directories import get_downloads_dir
from manimlib.utils.directories import get_text_dir
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:
# TODO, check the cache
hash_content = str((
key = hash_string(str((
content,
self.justify,
self.indent,
self.alignment,
self.line_width
))
# hash_string(hash_content)
key = hashlib.sha256(hash_content.encode()).hexdigest()
return self.markup_to_svg_string(content)
)))
return get_cached_value(key, lambda: self.markup_to_svg_string(content))
def markup_to_svg_string(self, markup_str: str) -> 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 contextlib import contextmanager
import os
import re
import yaml
from pathlib import Path
import tempfile
from manimlib.config import get_custom_config
from manimlib.config import get_manim_dir
from manimlib.logger import log
@ -51,9 +53,10 @@ def get_tex_config() -> dict[str, str]:
return SAVED_TEX_CONFIG
def tex_content_to_svg_file(
content: str, template: str, additional_preamble: str,
short_tex: str
def tex_to_svg(
content: str,
template: str,
additional_preamble: str,
) -> str:
tex_config = get_tex_config()
if not template or template == tex_config["template"]:
@ -74,14 +77,11 @@ def tex_content_to_svg_file(
"\\end{document}"
)) + "\n"
svg_file = os.path.join(
get_tex_dir(), hash_string(full_tex) + ".svg"
)
if not os.path.exists(svg_file):
# If svg doesn't exist, create it
with display_during_execution("Writing " + short_tex):
create_tex_svg(full_tex, svg_file, compiler)
return svg_file
with tempfile.NamedTemporaryFile(suffix='.svg', mode='r+') as tmp:
create_tex_svg(full_tex, tmp.name, compiler)
# Read the contents
tmp.seek(0)
return tmp.read()
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
# 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):
pass