Split the verified renderer, layout engine, Grid support, native boundary, tests, examples, and paired documentation into the independent Ebox repository. Keep ETAF and application concerns outside this package.
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
|