Optimize incremental mixed-owner rendering
This commit is contained in:
parent
b65b5937cc
commit
dd731cfbbf
@ -168,7 +168,7 @@ property;`ebox-region-update` 接受其目标支持的可变 content/style/scr
|
|||||||
| Padding | `:padding`、`:padding-inline`、`:padding-block`;longhand `:padding-block-start`、`:padding-inline-end`、`:padding-block-end`、`:padding-inline-start`;别名 `:padding-top`、`:padding-right`、`:padding-bottom`、`:padding-left`,以及 `:padding-right-pixel`、`:padding-left-pixel`、`:padding-top-height`、`:padding-bottom-height` |
|
| Padding | `:padding`、`:padding-inline`、`:padding-block`;longhand `:padding-block-start`、`:padding-inline-end`、`:padding-block-end`、`:padding-inline-start`;别名 `:padding-top`、`:padding-right`、`:padding-bottom`、`:padding-left`,以及 `:padding-right-pixel`、`:padding-left-pixel`、`:padding-top-height`、`:padding-bottom-height` |
|
||||||
| Margin | `:margin`、`:margin-inline`、`:margin-block`;longhand `:margin-block-start`、`:margin-inline-end`、`:margin-block-end`、`:margin-inline-start`;别名 `:margin-top`、`:margin-right`、`:margin-bottom`、`:margin-left`,以及 `:margin-right-pixel`、`:margin-left-pixel`、`:margin-top-height`、`:margin-bottom-height` |
|
| Margin | `:margin`、`:margin-inline`、`:margin-block`;longhand `:margin-block-start`、`:margin-inline-end`、`:margin-block-end`、`:margin-inline-start`;别名 `:margin-top`、`:margin-right`、`:margin-bottom`、`:margin-left`,以及 `:margin-right-pixel`、`:margin-left-pixel`、`:margin-top-height`、`:margin-bottom-height` |
|
||||||
| Border | `:border`、`:border-top`、`:border-right`、`:border-bottom`、`:border-left`、`:border-width`、`:border-style`、`:border-color`;side longhand `:border-*-width`、`:border-*-style`、`:border-*-color`;兼容别名 `:border-top-p`、`:border-bottom-p`、`:border-left-pixel`、`:border-right-pixel` |
|
| Border | `:border`、`:border-top`、`:border-right`、`:border-bottom`、`:border-left`、`:border-width`、`:border-style`、`:border-color`;side longhand `:border-*-width`、`:border-*-style`、`:border-*-color`;兼容别名 `:border-top-p`、`:border-bottom-p`、`:border-left-pixel`、`:border-right-pixel` |
|
||||||
| Paint | `:color`、`:background-color` 和别名 `:bgcolor` |
|
| Paint | `:color`、`:background-color`、别名 `:bgcolor` |
|
||||||
| Typography | `:font`、`:font-family`、`:font-height` 和别名 `:font-size`、`:font-weight`、`:font-slant` |
|
| Typography | `:font`、`:font-family`、`:font-height` 和别名 `:font-size`、`:font-weight`、`:font-slant` |
|
||||||
| Text/layout | `:text-align`(`left`、`center`、`right`)、`:vertical-align`(`top`、`center`/`middle`、`bottom`)、`:overflow`(`scroll`、`hidden`、`visible`)、`:wrap-mode`(`word`、`char`、`kp` 或 nil)、`:visibility`(`visible` 或 `hidden`) |
|
| Text/layout | `:text-align`(`left`、`center`、`right`)、`:vertical-align`(`top`、`center`/`middle`、`bottom`)、`:overflow`(`scroll`、`hidden`、`visible`)、`:wrap-mode`(`word`、`char`、`kp` 或 nil)、`:visibility`(`visible` 或 `hidden`) |
|
||||||
| Structural style | `:display` 会在 node/layout context 中计算;有效的 display tuple 通常由布局构造器选择。 |
|
| Structural style | `:display` 会在 node/layout context 中计算;有效的 display tuple 通常由布局构造器选择。 |
|
||||||
|
|||||||
@ -8229,6 +8229,14 @@ result in the same owner-proof shape consumed by span publication."
|
|||||||
(list :root (plist-get prepared :root)
|
(list :root (plist-get prepared :root)
|
||||||
:scope-node-ids scope-node-ids
|
:scope-node-ids scope-node-ids
|
||||||
:report-base report
|
:report-base report
|
||||||
|
;; A child Range replacement can legitimately change the separator
|
||||||
|
;; owned by an adjacent sibling when its item count changes. Keep
|
||||||
|
;; the fast scoped proof for stable topology, but let the surface
|
||||||
|
;; publisher use its exact root fallback when that proof cannot
|
||||||
|
;; establish the outside text invariant.
|
||||||
|
:on-mismatch
|
||||||
|
(and (> (or (plist-get prepared :range-replacement-count) 0) 0)
|
||||||
|
'root)
|
||||||
:projection-kind projection-kind
|
:projection-kind projection-kind
|
||||||
:preserve-identities-p t
|
:preserve-identities-p t
|
||||||
:state-overrides
|
:state-overrides
|
||||||
|
|||||||
@ -115,21 +115,30 @@ of pixel measurements it triggers do not each rebuild the display signature.")
|
|||||||
(fillarray ebox--string-pixel-width-cache-ring nil)
|
(fillarray ebox--string-pixel-width-cache-ring nil)
|
||||||
(setq ebox--string-pixel-width-cache-ring-index 0))
|
(setq ebox--string-pixel-width-cache-ring-index 0))
|
||||||
|
|
||||||
|
(defun ebox--string-pixel-width-visual-key (line factor)
|
||||||
|
"Return a width cache key for LINE at FACTOR when visual properties are constant."
|
||||||
|
(let ((end (length line)))
|
||||||
|
(list factor
|
||||||
|
(substring-no-properties line)
|
||||||
|
(cl-loop for property in '(face display)
|
||||||
|
for value = (and (> end 0)
|
||||||
|
(get-text-property 0 property line))
|
||||||
|
when value append (list property value)))))
|
||||||
|
|
||||||
(defun ebox--string-pixel-width-cache-key (line factor)
|
(defun ebox--string-pixel-width-cache-key (line factor)
|
||||||
"Return a safe display-scoped measurement key for LINE at FACTOR."
|
"Return a safe display-scoped measurement key for LINE at FACTOR."
|
||||||
(let* ((end (length line))
|
(let* ((end (length line))
|
||||||
(properties (and (> end 0) (text-properties-at 0 line)))
|
;; Ebox layout text uses `face' and `display' as its width-bearing
|
||||||
|
;; properties. Ownership/region metadata is deliberately ignored;
|
||||||
|
;; the other generic text properties are not emitted by Ebox's
|
||||||
|
;; layout pipeline and would make this hot key scan needlessly broad.
|
||||||
(constant-p
|
(constant-p
|
||||||
(= (or (next-property-change 0 line end) end) end)))
|
(and (= (or (next-single-property-change 0 'face line end) end)
|
||||||
(cond
|
end)
|
||||||
((and constant-p (null properties))
|
(= (or (next-single-property-change 0 'display line end) end)
|
||||||
(list factor line))
|
end))))
|
||||||
((and constant-p
|
(and constant-p
|
||||||
(= (length properties) 2)
|
(ebox--string-pixel-width-visual-key line factor))))
|
||||||
(eq (car properties) 'face))
|
|
||||||
(list factor
|
|
||||||
line
|
|
||||||
(cadr properties))))))
|
|
||||||
|
|
||||||
(defun ebox--cached-string-pixel-width (line factor key)
|
(defun ebox--cached-string-pixel-width (line factor key)
|
||||||
"Return LINE width at FACTOR through the render and display caches at KEY."
|
"Return LINE width at FACTOR through the render and display caches at KEY."
|
||||||
@ -161,7 +170,9 @@ of pixel measurements it triggers do not each rebuild the display signature.")
|
|||||||
Text characters follow the current buffer text scale. Fixed display spaces
|
Text characters follow the current buffer text scale. Fixed display spaces
|
||||||
keep their absolute pixel width."
|
keep their absolute pixel width."
|
||||||
(ebox--ensure-display-cache-current)
|
(ebox--ensure-display-cache-current)
|
||||||
(let* ((line (car (split-string (or string "") "\n")))
|
(let* ((source (or string ""))
|
||||||
|
(newline (and (stringp source) (string-match "\n" source)))
|
||||||
|
(line (if newline (substring source 0 newline) source))
|
||||||
(factor (ebox--text-scale-factor))
|
(factor (ebox--text-scale-factor))
|
||||||
(pos 0)
|
(pos 0)
|
||||||
(end (length line))
|
(end (length line))
|
||||||
@ -171,17 +182,27 @@ keep their absolute pixel width."
|
|||||||
(ebox--cached-string-pixel-width line factor cache-key)
|
(ebox--cached-string-pixel-width line factor cache-key)
|
||||||
(while (< pos end)
|
(while (< pos end)
|
||||||
(let* ((display (get-text-property pos 'display line))
|
(let* ((display (get-text-property pos 'display line))
|
||||||
(next (or (next-single-property-change
|
(next
|
||||||
pos 'display line end)
|
(cl-loop for property in
|
||||||
end))
|
'(face display)
|
||||||
|
minimize (or (next-single-property-change
|
||||||
|
pos property line end)
|
||||||
|
end)))
|
||||||
|
(segment (substring line pos next))
|
||||||
|
;; The segment ends at every visual property change, so its
|
||||||
|
;; width key can skip the second constant-property scan.
|
||||||
|
(segment-key
|
||||||
|
(ebox--string-pixel-width-visual-key segment factor))
|
||||||
(space-width (ebox--display-space-width display)))
|
(space-width (ebox--display-space-width display)))
|
||||||
(setq total
|
(setq total
|
||||||
(+ total
|
(+ total
|
||||||
(if space-width
|
(if space-width
|
||||||
(* space-width (- next pos))
|
(* space-width (- next pos))
|
||||||
(ebox--scaled-string-pixel-width
|
(if segment-key
|
||||||
(substring line pos next)
|
(ebox--cached-string-pixel-width
|
||||||
factor))))
|
segment factor segment-key)
|
||||||
|
(ebox--scaled-string-pixel-width
|
||||||
|
segment factor)))))
|
||||||
(setq pos next)))
|
(setq pos next)))
|
||||||
total)))
|
total)))
|
||||||
|
|
||||||
|
|||||||
@ -2077,6 +2077,12 @@ causes the caller to use the ordinary full projection."
|
|||||||
(ebox-surface--formatting-context-reflow-output
|
(ebox-surface--formatting-context-reflow-output
|
||||||
buffer state)))))
|
buffer state)))))
|
||||||
(old-fragments (plist-get previous-state :surface-fragments))
|
(old-fragments (plist-get previous-state :surface-fragments))
|
||||||
|
(old-text
|
||||||
|
(and (buffer-live-p buffer)
|
||||||
|
(with-current-buffer buffer
|
||||||
|
(save-restriction
|
||||||
|
(widen)
|
||||||
|
(buffer-substring (point-min) (point-max))))))
|
||||||
(old-by-address (make-hash-table :test #'equal))
|
(old-by-address (make-hash-table :test #'equal))
|
||||||
(old-by-local-address (make-hash-table :test #'equal)))
|
(old-by-local-address (make-hash-table :test #'equal)))
|
||||||
(when (and output old-fragments paint-owner-ids)
|
(when (and output old-fragments paint-owner-ids)
|
||||||
@ -2116,6 +2122,13 @@ causes the caller to use the ordinary full projection."
|
|||||||
(old-fragment
|
(old-fragment
|
||||||
(or (and address (gethash address old-by-address))
|
(or (and address (gethash address old-by-address))
|
||||||
(and (= (length matches) 1) (car matches))))
|
(and (= (length matches) 1) (car matches))))
|
||||||
|
(old-fragment-text
|
||||||
|
(and old-text old-fragment
|
||||||
|
(integerp (plist-get old-fragment :start))
|
||||||
|
(integerp (plist-get old-fragment :end))
|
||||||
|
(substring old-text
|
||||||
|
(plist-get old-fragment :start)
|
||||||
|
(plist-get old-fragment :end))))
|
||||||
(affected-p
|
(affected-p
|
||||||
(cl-some (lambda (entry)
|
(cl-some (lambda (entry)
|
||||||
(memq (cdr entry) paint-owner-ids))
|
(memq (cdr entry) paint-owner-ids))
|
||||||
@ -2141,10 +2154,45 @@ causes the caller to use the ordinary full projection."
|
|||||||
(plist-get old-fragment
|
(plist-get old-fragment
|
||||||
:paint-role-ids))
|
:paint-role-ids))
|
||||||
roles)))
|
roles)))
|
||||||
(push (ebox-surface--repaint-fragment
|
;; Geometry reflow still produces a complete candidate text,
|
||||||
copy previous-state state
|
;; but an unaffected fragment whose text/properties are
|
||||||
old-face-cache new-face-cache)
|
;; byte-for-byte identical and whose semantic face
|
||||||
pieces)))
|
;; contributions are unchanged already has the correct
|
||||||
|
;; composed face. Reuse its candidate metadata without
|
||||||
|
;; rebuilding it; changed/paint-owned fragments retain the
|
||||||
|
;; existing baseline-safe repaint path.
|
||||||
|
(let* ((old-roles (plist-get copy :old-paint-role-ids))
|
||||||
|
(new-roles (plist-get copy :paint-role-ids))
|
||||||
|
(reusable-p
|
||||||
|
(and old-fragment
|
||||||
|
(not affected-p)
|
||||||
|
old-fragment-text
|
||||||
|
(equal-including-properties
|
||||||
|
old-fragment-text text)
|
||||||
|
(equal old-roles new-roles)
|
||||||
|
(let ((old-faces
|
||||||
|
(or (gethash old-roles old-face-cache)
|
||||||
|
(let ((value
|
||||||
|
(ebox-surface--face-contributions
|
||||||
|
previous-state old-roles)))
|
||||||
|
(puthash old-roles value old-face-cache)
|
||||||
|
value)))
|
||||||
|
(new-faces
|
||||||
|
(or (gethash new-roles new-face-cache)
|
||||||
|
(let ((value
|
||||||
|
(ebox-surface--face-contributions
|
||||||
|
state new-roles)))
|
||||||
|
(puthash new-roles value new-face-cache)
|
||||||
|
value))))
|
||||||
|
(equal old-faces new-faces)))))
|
||||||
|
(push (if reusable-p
|
||||||
|
(progn
|
||||||
|
(cl-remf copy :old-paint-role-ids)
|
||||||
|
copy)
|
||||||
|
(ebox-surface--repaint-fragment
|
||||||
|
copy previous-state state
|
||||||
|
old-face-cache new-face-cache))
|
||||||
|
pieces))))
|
||||||
(let* ((fragments (nreverse pieces))
|
(let* ((fragments (nreverse pieces))
|
||||||
(rendered
|
(rendered
|
||||||
(apply #'concat
|
(apply #'concat
|
||||||
|
|||||||
5
ebox.el
5
ebox.el
@ -404,7 +404,6 @@ caller, such as the Playground preview, renders against a concrete window.")
|
|||||||
;; 上下边框由于 Emacs 限制,用 overline/underline 实现,不支持设置宽度
|
;; 上下边框由于 Emacs 限制,用 overline/underline 实现,不支持设置宽度
|
||||||
:border-top-p nil :border-top-color nil
|
:border-top-p nil :border-top-color nil
|
||||||
:border-bottom-p nil :border-bottom-color nil
|
:border-bottom-p nil :border-bottom-color nil
|
||||||
|
|
||||||
:color nil
|
:color nil
|
||||||
:bgcolor nil
|
:bgcolor nil
|
||||||
:font nil :font-family nil :font-height nil
|
:font nil :font-family nil :font-height nil
|
||||||
@ -4588,7 +4587,9 @@ Return the successful publication report stored by `ebox-buffer-update-report'."
|
|||||||
buffer source scope-node-ids
|
buffer source scope-node-ids
|
||||||
(plist-get commit-input :report-base)
|
(plist-get commit-input :report-base)
|
||||||
(plist-get commit-input :state-overrides)
|
(plist-get commit-input :state-overrides)
|
||||||
callback nil nil
|
callback
|
||||||
|
(plist-get commit-input :on-mismatch)
|
||||||
|
nil
|
||||||
(plist-get commit-input :projection-kind)
|
(plist-get commit-input :projection-kind)
|
||||||
t participant)
|
t participant)
|
||||||
(ebox-surface-mount-buffer
|
(ebox-surface-mount-buffer
|
||||||
|
|||||||
@ -1473,6 +1473,36 @@ remain retained identities."
|
|||||||
(ebox-render (ebox--buffer-root-node buffer))))))))
|
(ebox-render (ebox--buffer-root-node buffer))))))))
|
||||||
(when (buffer-live-p buffer) (kill-buffer buffer)))))
|
(when (buffer-live-p buffer) (kill-buffer buffer)))))
|
||||||
|
|
||||||
|
(ert-deftest ebox-commit-mixed-owner-reflow-reuses-unchanged-fragments ()
|
||||||
|
"Skip face recomposition for unchanged fragments in a mixed projection."
|
||||||
|
(let* ((buffer
|
||||||
|
(ebox-render-to-buffer
|
||||||
|
(generate-new-buffer-name " *ebox-mixed-owner-reuse* ")
|
||||||
|
(ebox-commit-test--mixed-owner-root
|
||||||
|
"left-old" "right-old" "#111111" "#222222" "#333333")))
|
||||||
|
(surface (with-current-buffer buffer ebox-surface--buffer-surface))
|
||||||
|
(old-state (tp-surface-client-state surface))
|
||||||
|
(fragment-count (length (plist-get old-state :surface-fragments)))
|
||||||
|
(repaints 0)
|
||||||
|
(original (symbol-function 'ebox-surface--repaint-fragment)))
|
||||||
|
(unwind-protect
|
||||||
|
(progn
|
||||||
|
(cl-letf (((symbol-function 'ebox-surface--repaint-fragment)
|
||||||
|
(lambda (&rest args)
|
||||||
|
(cl-incf repaints)
|
||||||
|
(apply original args))))
|
||||||
|
(should (eq (plist-get
|
||||||
|
(ebox-commit
|
||||||
|
buffer
|
||||||
|
(ebox-commit-test--mixed-owner-candidate
|
||||||
|
buffer "left-new" "right-new"
|
||||||
|
"#AAAAAA" "#BBBBBB" "#CCCCCC"))
|
||||||
|
:projection-kind)
|
||||||
|
'mixed-owner-reflow)))
|
||||||
|
(should (> fragment-count 0))
|
||||||
|
(should (< repaints fragment-count)))
|
||||||
|
(when (buffer-live-p buffer) (kill-buffer buffer)))))
|
||||||
|
|
||||||
(provide 'ebox-commit-tests)
|
(provide 'ebox-commit-tests)
|
||||||
|
|
||||||
;;; ebox-commit-tests.el ends here
|
;;; ebox-commit-tests.el ends here
|
||||||
|
|||||||
@ -4543,6 +4543,26 @@
|
|||||||
(should (= (ebox--string-max-pixel-width exact) 120))
|
(should (= (ebox--string-max-pixel-width exact) 120))
|
||||||
(should (plist-get exact-parent :ebox-content-width-exact-p))))
|
(should (plist-get exact-parent :ebox-content-width-exact-p))))
|
||||||
|
|
||||||
|
(ert-deftest ebox-string-pixel-width-cache-ignores-ebox-ownership-metadata ()
|
||||||
|
"Reuse visual width measurements across Ebox-owned text properties."
|
||||||
|
(let ((line (propertize "cached row"
|
||||||
|
'ebox-content 7
|
||||||
|
'ebox-content-idx 0
|
||||||
|
'ebox-content-owner 7
|
||||||
|
'ebox-content-owners '(7)))
|
||||||
|
(calls 0)
|
||||||
|
(original (symbol-function 'ebox--scaled-string-pixel-width)))
|
||||||
|
(ebox--clear-string-pixel-width-cache)
|
||||||
|
(cl-letf (((symbol-function 'ebox--scaled-string-pixel-width)
|
||||||
|
(lambda (&rest args)
|
||||||
|
(cl-incf calls)
|
||||||
|
(apply original args))))
|
||||||
|
(let ((first (ebox--string-pixel-width line))
|
||||||
|
(second (ebox--string-pixel-width (copy-sequence line))))
|
||||||
|
(should (> first 0))
|
||||||
|
(should (= first second))))
|
||||||
|
(should (= calls 1))))
|
||||||
|
|
||||||
(ert-deftest ebox-render-caches-lazy-box-content-within-render-pass ()
|
(ert-deftest ebox-render-caches-lazy-box-content-within-render-pass ()
|
||||||
"A lazy child layout should be rendered once per box/context in one pass."
|
"A lazy child layout should be rendered once per box/context in one pass."
|
||||||
(ebox-test--reset-runtime-state)
|
(ebox-test--reset-runtime-state)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user