class_docstr: A utility for storing messages generated by `st` commands called inside
  a cached function.
  
  Data is stored in a thread-local object, so it's safe to use an instance
  of this class across multiple threads.
class_docstr_tok: ["A","utility","for","storing","messages","generated","by","`","st","`","commands","called","inside","a","cached","function",".","Data","is","stored","in","a","thread-local","object",",","so","it","'s","safe","to","use","an","instance","of","this","class","across","multiple","threads","."]
docstr: Record the element protobuf as having been produced during any currently
  executing cached functions, so they can be replayed any time the function's
  execution is skipped because they're in the cache.
code: def save_element_message(
      self,
      delta_type: str,
      element_proto: Message,
      invoked_dg_id: str,
      used_dg_id: str,
      returned_dg_id: str,
  ) -> None:
      """Record the element protobuf as having been produced during any currently
      executing cached functions, so they can be replayed any time the function's
      execution is skipped because they're in the cache.
      """
      if not runtime.exists():
          return
      if len(self._cached_message_stack) >= 1:
          id_to_save = self.select_dg_to_save(invoked_dg_id, used_dg_id)
          # Arrow dataframes have an ID but only set it when used as data editor
          # widgets, so we have to check that the ID has been actually set to
          # know if an element is a widget.
          if isinstance(element_proto, Widget) and element_proto.id:
              wid = element_proto.id
              # TODO replace `Message` with a more precise type
              if not self._registered_metadata:
                  _LOGGER.error(
                      "Trying to save widget message that wasn't registered. This should not be possible."
                  )
                  raise AttributeError
              widget_meta = WidgetMsgMetadata(
                  wid, None, metadata=self._registered_metadata
              )
          else:
              widget_meta = None
  
          media_data = self._media_data
  
          element_msg_data = ElementMsgData(
              delta_type,
              element_proto,
              id_to_save,
              returned_dg_id,
              widget_meta,
              media_data,
          )
          for msgs in self._cached_message_stack:
              if self._allow_widgets or widget_meta is None:
                  msgs.append(element_msg_data)
  
      # Reset instance state, now that it has been used for the
      # associated element.
      self._media_data = []
      self._registered_metadata = None
  
      for s in self._seen_dg_stack:
          s.add(returned_dg_id)
  
imports: from __future__ import annotations
  import contextlib
  import hashlib
  import threading
  import types
  from dataclasses import dataclass
  from typing import TYPE_CHECKING, Any, Iterator, Union
  from google.protobuf.message import Message
  from typing_extensions import Protocol, runtime_checkable
  import streamlit
  from streamlit import runtime, util
  from streamlit.elements import NONWIDGET_ELEMENTS, WIDGETS
  from streamlit.logger import get_logger
  from streamlit.proto.Block_pb2 import Block
  from streamlit.runtime.caching.cache_errors import CachedStFunctionWarning, CacheReplayClosureError
  from streamlit.runtime.caching.cache_type import CacheType
  from streamlit.runtime.caching.hashing import update_hash
  from streamlit.runtime.scriptrunner.script_run_context import ScriptRunContext, get_script_run_ctx
  from streamlit.runtime.state.common import WidgetMetadata
  from streamlit.util import HASHLIB_KWARGS
label_desc: Use image node_id 4 for calling the CachedMessageReplayContext obj's underlying member method code with example usage: obj.save_element_message(delta_type, element_proto, invoked_dg_id, used_dg_id, returned_dg_id) without return types