217 lines
7.7 KiB
EmacsLisp
217 lines
7.7 KiB
EmacsLisp
;;; ebox-cache.el --- Cache ownership metadata for Ebox -*- lexical-binding: t; -*-
|
|
|
|
;;; Commentary:
|
|
;; Owns shared cache contracts, cache invalidation metadata, and cache
|
|
;; reporting helpers. It does not own rendering, layout, or buffer mutation.
|
|
|
|
;;; Code:
|
|
|
|
(require 'cl-lib)
|
|
(require 'subr-x)
|
|
|
|
(cl-defstruct (ebox-cache-spec
|
|
(:constructor ebox-cache-spec-create))
|
|
name owner scope key-deps invalidates lifetime value-owner)
|
|
|
|
(defvar ebox-cache--registry (make-hash-table :test 'eq)
|
|
"Registry of cache specs keyed by cache name.")
|
|
|
|
(defvar ebox-cache--buffer-report-table (make-hash-table :test 'eq)
|
|
"Buffer-keyed cache report facts for the current incremental operation.")
|
|
|
|
(defvar ebox-cache-report-buffer nil
|
|
"Dynamically bound buffer whose cache report should be appended to reports.")
|
|
|
|
(defconst ebox-cache--default-specs
|
|
'((:name measurement
|
|
:owner ebox-measure
|
|
:scope display
|
|
:key-deps (string face text-scale display-signature)
|
|
:invalidates (font frame text-scale display-signature)
|
|
:lifetime global-or-display
|
|
:value-owner ebox-measure)
|
|
(:name computed-style
|
|
:owner ebox-style
|
|
:scope node
|
|
:key-deps (specified-style-signature class-version theme-version)
|
|
:invalidates (style class theme)
|
|
:lifetime buffer-runtime
|
|
:value-owner ebox-style)
|
|
(:name viewport-dependency
|
|
:owner ebox-incremental
|
|
:scope buffer-runtime
|
|
:key-deps (root-id computed-style-tree-version viewport-width)
|
|
:invalidates (width display children style viewport)
|
|
:lifetime buffer-runtime
|
|
:value-owner ebox-incremental)
|
|
(:name layout-fragment
|
|
:owner ebox-layout
|
|
:scope formatting-context
|
|
:key-deps (node-id computed-style-signature child-signatures available-size)
|
|
:invalidates (geometry structure viewport)
|
|
:lifetime buffer-runtime
|
|
:value-owner ebox-layout)
|
|
(:name render-body
|
|
:owner ebox-buffer-backend
|
|
:scope buffer-render
|
|
:key-deps (fragment-signature paint-signature backend-display-signature)
|
|
:invalidates (paint geometry structure backend-display)
|
|
:lifetime buffer-runtime
|
|
:value-owner ebox-buffer-backend)
|
|
(:name snapshot-signature
|
|
:owner ebox-fragment
|
|
:scope buffer-runtime
|
|
:key-deps (node-id fragment-version style-signature)
|
|
:invalidates (style geometry placement structure)
|
|
:lifetime buffer-runtime
|
|
:value-owner ebox-fragment))
|
|
"Default cache contracts for existing Ebox engine caches.")
|
|
|
|
(defun ebox-cache-clear-registry ()
|
|
"Clear the cache spec registry."
|
|
(clrhash ebox-cache--registry))
|
|
|
|
(cl-defun ebox-cache-register
|
|
(&key name owner scope key-deps invalidates lifetime value-owner)
|
|
"Register and return a cache spec."
|
|
(unless name
|
|
(error "ebox-cache: cache spec requires :name"))
|
|
(let ((spec (ebox-cache-spec-create
|
|
:name name
|
|
:owner owner
|
|
:scope scope
|
|
:key-deps key-deps
|
|
:invalidates invalidates
|
|
:lifetime lifetime
|
|
:value-owner value-owner)))
|
|
(puthash name spec ebox-cache--registry)
|
|
spec))
|
|
|
|
(defun ebox-cache-spec (name)
|
|
"Return registered cache spec NAME."
|
|
(gethash name ebox-cache--registry))
|
|
|
|
(defun ebox-cache-register-default-specs ()
|
|
"Register default cache specs and return the registry."
|
|
(dolist (spec ebox-cache--default-specs)
|
|
(apply #'ebox-cache-register spec))
|
|
ebox-cache--registry)
|
|
|
|
(defun ebox-cache-reset-default-registry ()
|
|
"Reset the registry to the default engine cache specs."
|
|
(ebox-cache-clear-registry)
|
|
(ebox-cache-register-default-specs))
|
|
|
|
(defun ebox-cache--intersects-p (left right)
|
|
"Return non-nil when LEFT and RIGHT share a symbol."
|
|
(cl-intersection left right :test #'eq))
|
|
|
|
(defun ebox-cache-invalidated-spec-names (dirty-kinds)
|
|
"Return cache spec names whose invalidation rules intersect DIRTY-KINDS."
|
|
(let (names)
|
|
(maphash
|
|
(lambda (name spec)
|
|
(when (ebox-cache--intersects-p
|
|
dirty-kinds (ebox-cache-spec-invalidates spec))
|
|
(push name names)))
|
|
ebox-cache--registry)
|
|
names))
|
|
|
|
(defun ebox-cache--empty-report ()
|
|
"Return a new empty cache report plist."
|
|
(list :cache-invalidated nil
|
|
:cache-hit-count 0
|
|
:cache-miss-count 0
|
|
:cache-scope nil
|
|
:cache-fallback-reason nil))
|
|
|
|
(defun ebox-cache-clear-report (buffer)
|
|
"Clear cache report facts for BUFFER."
|
|
(when buffer
|
|
(ebox-cache-publish-report buffer (ebox-cache--empty-report))))
|
|
|
|
(defun ebox-cache-publish-report (buffer report)
|
|
"Atomically replace BUFFER's cache REPORT facts."
|
|
(when buffer
|
|
(puthash buffer (copy-sequence report)
|
|
ebox-cache--buffer-report-table)))
|
|
|
|
(defun ebox-cache-snapshot-report (buffer)
|
|
"Return an exact restorable snapshot of BUFFER's cache report entry."
|
|
(let* ((missing (make-symbol "missing"))
|
|
(report (and buffer
|
|
(gethash buffer ebox-cache--buffer-report-table missing))))
|
|
(list :present-p (and buffer (not (eq report missing)))
|
|
:report (unless (eq report missing) (copy-sequence report)))))
|
|
|
|
(defun ebox-cache-restore-report (buffer snapshot)
|
|
"Restore BUFFER's cache report entry from SNAPSHOT."
|
|
(when buffer
|
|
(if (plist-get snapshot :present-p)
|
|
(puthash buffer (copy-sequence (plist-get snapshot :report))
|
|
ebox-cache--buffer-report-table)
|
|
(remhash buffer ebox-cache--buffer-report-table))))
|
|
|
|
(defun ebox-cache-discard-report (buffer)
|
|
"Discard cache report facts owned by BUFFER."
|
|
(when buffer
|
|
(remhash buffer ebox-cache--buffer-report-table)))
|
|
|
|
(defun ebox-cache--report-state (buffer)
|
|
"Return mutable cache report state for BUFFER."
|
|
(when buffer
|
|
(or (gethash buffer ebox-cache--buffer-report-table)
|
|
(let ((state (ebox-cache--empty-report)))
|
|
(puthash buffer state ebox-cache--buffer-report-table)
|
|
state))))
|
|
|
|
(defun ebox-cache--push-unique-values (state key values)
|
|
"Append VALUES uniquely to STATE at KEY."
|
|
(dolist (value values)
|
|
(when value
|
|
(cl-pushnew value (plist-get state key) :test #'equal))))
|
|
|
|
(defun ebox-cache--spec-scopes (cache-names)
|
|
"Return distinct scopes for CACHE-NAMES."
|
|
(let (scopes)
|
|
(dolist (name cache-names)
|
|
(when-let* ((spec (ebox-cache-spec name)))
|
|
(cl-pushnew (ebox-cache-spec-scope spec) scopes :test #'equal)))
|
|
scopes))
|
|
|
|
(defun ebox-cache-record-hit (buffer cache-name)
|
|
"Record a CACHE-NAME hit for BUFFER's current update report."
|
|
(when-let* ((state (ebox-cache--report-state buffer)))
|
|
(plist-put state :cache-hit-count
|
|
(1+ (or (plist-get state :cache-hit-count) 0)))
|
|
(ebox-cache--push-unique-values
|
|
state :cache-scope (ebox-cache--spec-scopes (list cache-name)))))
|
|
|
|
(defun ebox-cache-record-miss (buffer cache-name)
|
|
"Record a CACHE-NAME miss for BUFFER's current update report."
|
|
(when-let* ((state (ebox-cache--report-state buffer)))
|
|
(plist-put state :cache-miss-count
|
|
(1+ (or (plist-get state :cache-miss-count) 0)))
|
|
(ebox-cache--push-unique-values
|
|
state :cache-scope (ebox-cache--spec-scopes (list cache-name)))))
|
|
|
|
(defun ebox-cache-record-invalidation (buffer cache-names reason)
|
|
"Record invalidated CACHE-NAMES and REASON for BUFFER."
|
|
(when-let* ((state (ebox-cache--report-state buffer)))
|
|
(ebox-cache--push-unique-values state :cache-invalidated cache-names)
|
|
(ebox-cache--push-unique-values
|
|
state :cache-scope (ebox-cache--spec-scopes cache-names))
|
|
(plist-put state :cache-fallback-reason reason)))
|
|
|
|
(defun ebox-cache-report (buffer)
|
|
"Return cache report plist for BUFFER."
|
|
(copy-sequence
|
|
(or (gethash buffer ebox-cache--buffer-report-table)
|
|
(ebox-cache--empty-report))))
|
|
|
|
(ebox-cache-register-default-specs)
|
|
|
|
(provide 'ebox-cache)
|
|
|
|
;;; ebox-cache.el ends here
|