Rebuild invalidated retained component anchors

This commit is contained in:
Kinneyzhang 2026-08-22 08:21:14 +08:00
parent d636b63e04
commit 00918de9d4
2 changed files with 58 additions and 4 deletions

View File

@ -3460,10 +3460,20 @@ The candidate uses resolved VALUE, DEPS, and NODES."
(car output-range)))) (car output-range))))
(etaf-runtime-candidate-artifacts runtime)) (etaf-runtime-candidate-artifacts runtime))
(push identity (etaf-runtime-candidate-rendered-identities runtime)) (push identity (etaf-runtime-candidate-rendered-identities runtime))
(list candidate (let ((committed-artifact
(and (etaf--semantic-component-artifact-key committed)
(gethash (etaf--semantic-component-artifact-key committed) (gethash (etaf--semantic-component-artifact-key committed)
(etaf-runtime-artifact-registry runtime)) (etaf-runtime-artifact-registry runtime)))))
(gethash effect-id (etaf-runtime-candidate-artifacts runtime)))))) ;; Descendant-only publications invalidate cached ancestor artifacts
;; while retaining the ancestor semantic tree. When that ancestor
;; becomes dirty on a later turn, rebuild its committed backend anchor
;; from the immutable generation before diffing the new render.
(unless committed-artifact
(setq committed-artifact
(etaf--runtime-rebuild-component-artifact runtime committed)))
(list candidate committed-artifact
(gethash effect-id
(etaf-runtime-candidate-artifacts runtime)))))))
(defun etaf--runtime-render-dirty-range (runtime effect range) (defun etaf--runtime-render-dirty-range (runtime effect range)
"Evaluate RUNTIME dirty RANGE EFFECT without running its Component owner." "Evaluate RUNTIME dirty RANGE EFFECT without running its Component owner."

View File

@ -86,6 +86,8 @@
(defvar etaf-test-transparent-parent-renders 0) (defvar etaf-test-transparent-parent-renders 0)
(defvar etaf-test-transparent-inner-renders 0) (defvar etaf-test-transparent-inner-renders 0)
(defvar etaf-test-transparent-outer-renders 0) (defvar etaf-test-transparent-outer-renders 0)
(defvar etaf-test-ancestor-parent-source nil)
(defvar etaf-test-ancestor-child-source nil)
(defvar etaf-test-context-provider-source nil) (defvar etaf-test-context-provider-source nil)
(defvar etaf-test-context-provider-renders 0) (defvar etaf-test-context-provider-renders 0)
(defvar etaf-test-context-consumer-renders 0) (defvar etaf-test-context-consumer-renders 0)
@ -594,6 +596,19 @@
"Place a transparent Component chain in one material parent." "Place a transparent Component chain in one material parent."
:view (column (etaf-test-transparent-outer))) :view (column (etaf-test-transparent-outer)))
(etaf-define-component etaf-test-ancestor-artifact-child ()
"Render a child whose local update invalidates ancestor artifacts."
:view
(text (expr :value
(if (etaf-value etaf-test-ancestor-child-source)
"detail" "child"))))
(etaf-define-component etaf-test-ancestor-artifact-parent ()
"Render a parent that can update after one descendant-only publication."
:view
(column :bgcolor (etaf-value etaf-test-ancestor-parent-source)
(etaf-test-ancestor-artifact-child)))
(etaf-define-component etaf-test-generation-context-consumer () (etaf-define-component etaf-test-generation-context-consumer ()
"Render one generation-owned Context dependency." "Render one generation-owned Context dependency."
:setup :setup
@ -3813,6 +3828,35 @@ Event composition is a Runtime contract, not a UI-library helper contract."
(etaf-unmount runtime)) (etaf-unmount runtime))
(when-let ((buffer (get-buffer buffer-name))) (kill-buffer buffer))))) (when-let ((buffer (get-buffer buffer-name))) (kill-buffer buffer)))))
(ert-deftest etaf-runtime-parent-rerender-rebuilds-invalidated-artifact ()
"Rebuild a committed parent anchor after a descendant-only publication."
(let ((buffer-name " *etaf-ancestor-artifact-test*")
(etaf-test-ancestor-parent-source (etaf-ref "light"))
(etaf-test-ancestor-child-source (etaf-ref nil)))
(unwind-protect
(progn
(etaf-mount buffer-name
(etaf-view (etaf-test-ancestor-artifact-parent)))
(setf (etaf-value etaf-test-ancestor-child-source) t)
(let* ((runtime (etaf-runtime-for-buffer buffer-name))
(generation (etaf-runtime-current-generation runtime))
(parent
(etaf--generation-semantic
generation '(etaf-test-ancestor-artifact-parent (root)))))
(should (etaf--semantic-component-p parent))
(should-not (etaf--semantic-component-artifact-key parent))
(setf (etaf-value etaf-test-ancestor-parent-source) "dark")
(should (equal
(plist-get
(etaf-runtime-host-props-for
runtime '(etaf-host (root :view)))
:bgcolor)
"dark"))))
(when-let ((runtime (etaf-runtime-for-buffer buffer-name)))
(etaf-unmount runtime))
(when-let ((buffer (get-buffer buffer-name)))
(kill-buffer buffer)))))
(ert-deftest etaf-runtime-context-provider-promotes-consumer-atomically () (ert-deftest etaf-runtime-context-provider-promotes-consumer-atomically ()
"Promote Context provider/consumer frames only with their generation." "Promote Context provider/consumer frames only with their generation."
(let ((buffer-name " *etaf-generation-context-test*") (let ((buffer-name " *etaf-generation-context-test*")