perf: rebuild shadow patch output in one pass

This commit is contained in:
Kinneyzhang 2026-09-05 06:53:10 +08:00
parent c05174ff9d
commit b2b9462269
2 changed files with 212 additions and 7 deletions

View File

@ -39,6 +39,37 @@
(error condition)
(quit condition)))
(defun tp-surface-test--shadow-apply-commit-batch-oracle (string batch)
"Return the former exact replay result for STRING and BATCH."
(let ((result (copy-sequence string)))
(dolist (patch (reverse (tp-commit-batch-patches batch)))
(setq result
(concat (substring result 0 (plist-get patch :old-start))
(plist-get patch :replacement)
(substring result (plist-get patch :old-end)))))
result))
(defun tp-surface-test--source-function (name)
"Return interpreted NAME from the current tp-surface.el source."
(let ((source
(expand-file-name
"tp-surface.el"
(file-name-directory (or (locate-library "tp-surface")
(error "Cannot locate tp-surface")))))
definition)
(with-temp-buffer
(insert-file-contents source)
(condition-case nil
(while (not definition)
(let ((form (read (current-buffer))))
(when (and (eq (car-safe form) 'defun)
(eq (cadr form) name))
(setq definition form))))
(end-of-file nil)))
(unless definition
(error "Cannot find source definition for %S" name))
(eval `(lambda ,(nth 2 definition) ,@(nthcdr 3 definition)) t)))
(defun tp-surface-test--update-mode (mode surface object plan signal value)
"Update SURFACE in MODE with PLAN while touching SIGNAL to VALUE."
(tp-with-transaction
@ -107,6 +138,177 @@
(tp-surface-client-state surface))))
(should-error (tp-surface-commit-batch surface batch)))))
(ert-deftest tp-surface-test-shadow-batch-replay-preserves-exact-properties ()
"Shadow replay preserves Unicode, properties, order, and input ownership."
(let* ((opaque (make-symbol "owner"))
(base (propertize (concat "A" "乙丙丁戊")
'owner opaque 'face '(bold)))
(insertion (propertize (concat "a" "β")
'owner opaque 'face '(italic)))
(replacement
(propertize (copy-sequence "Q")
'owner opaque 'display '(space :width 2)))
(batch
(tp-commit-batch-create
:base-revision 0 :target-revision 1
:base-extent 5 :target-extent 5
:patches
(list
(list :old-start 0 :old-end 0 :new-start 0 :new-end 2
:replacement insertion)
(list :old-start 1 :old-end 3 :new-start 3 :new-end 3
:replacement "")
(list :old-start 4 :old-end 5 :new-start 4 :new-end 5
:replacement replacement))))
(expected
(concat insertion (substring base 0 1)
(substring base 3 4) replacement))
(result (tp--shadow-apply-commit-batch-to-string base batch)))
(should (equal-including-properties expected result))
(should (eq opaque (get-text-property 0 'owner result)))
(aset base 0 ?X)
(aset insertion 0 ?z)
(aset replacement 0 ?R)
(should (equal-including-properties expected result))))
(ert-deftest tp-surface-test-shadow-batch-replay-preserves-adjacent-inserts ()
"Zero-width patches at one old coordinate retain declared list order."
(let* ((first (propertize "α" 'face 'bold))
(second (propertize "" 'face 'italic))
(batch
(tp-commit-batch-create
:base-revision 0 :target-revision 1
:base-extent 0 :target-extent 2
:patches
(list
(list :old-start 0 :old-end 0 :new-start 0 :new-end 1
:replacement first)
(list :old-start 0 :old-end 0 :new-start 1 :new-end 2
:replacement second))
:coordinate-patches
'((:old-start 0 :old-end 0 :new-start 0 :new-end 1)
(:old-start 0 :old-end 0 :new-start 1 :new-end 2)))))
(let ((result (tp--shadow-apply-commit-batch-to-string "" batch)))
(should (equal-including-properties (concat first second) result))
(should (eq (get-text-property 0 'face result) 'bold))
(should (eq (get-text-property 1 'face result) 'italic)))))
(ert-deftest tp-surface-test-shadow-batch-replay-no-patches-copies-input ()
"No-patch shadow replay returns an independently mutable string."
(let* ((base (copy-sequence "value"))
(batch
(tp-commit-batch-create
:base-revision 0 :target-revision 1
:base-extent 5 :target-extent 5))
(result (tp--shadow-apply-commit-batch-to-string base batch)))
(should (equal base result))
(should-not (eq base result))
(aset result 0 ?X)
(should (equal base "value"))))
(ert-deftest tp-surface-test-shadow-batch-replay-matches-oracle ()
"Single-pass replay matches the former exact algorithm over varied patches."
(let ((cases
'(("abcdef" ((0 0 "α") (1 3 "XY") (6 6 "")))
("abcdef" ((0 6 "") (6 6 "新值")))
("abcdef" ((1 2 "") (2 2 "") (4 5 "四五六"))))))
(dolist (case cases)
(let ((old-cursor 0) (new-cursor 0) patches)
(dolist (spec (cadr case))
(pcase-let ((`(,old-start ,old-end ,replacement) spec))
(let ((new-start (+ new-cursor (- old-start old-cursor)))
(new-end
(+ new-cursor (- old-start old-cursor)
(length replacement))))
(push (list :old-start old-start :old-end old-end
:new-start new-start :new-end new-end
:replacement replacement)
patches)
(setq old-cursor old-end new-cursor new-end))))
(setq patches (nreverse patches))
(let* ((base (car case))
(target-extent (+ new-cursor (- (length base) old-cursor)))
(coordinates
(mapcar
(lambda (patch)
(list :old-start (plist-get patch :old-start)
:old-end (plist-get patch :old-end)
:new-start (plist-get patch :new-start)
:new-end (plist-get patch :new-end)))
patches))
(batch
(tp-commit-batch-create
:base-revision 0 :target-revision 1
:base-extent (length base) :target-extent target-extent
:patches patches :coordinate-patches coordinates)))
(should
(equal-including-properties
(tp-surface-test--shadow-apply-commit-batch-oracle base batch)
(tp--shadow-apply-commit-batch-to-string base batch))))))))
(ert-deftest tp-surface-test-shadow-batch-replay-work-is-linear ()
"Replay slices the base once and concatenates exactly the target extent."
(let* ((patch-count 512)
(base (make-string (* patch-count 2) ?a))
patches
(old 0)
(new 0))
(dotimes (index patch-count)
(push (list :old-start old :old-end (1+ old)
:new-start new :new-end (+ new 2)
:replacement (if (zerop (% index 2)) "甲乙" "αβ"))
patches)
(setq old (+ old 2) new (+ new 3)))
(let* ((patches (nreverse patches))
(target-extent (+ new (- (length base) old)))
(coordinates
(mapcar
(lambda (patch)
(list :old-start (plist-get patch :old-start)
:old-end (plist-get patch :old-end)
:new-start (plist-get patch :new-start)
:new-end (plist-get patch :new-end)))
patches))
(batch
(tp-commit-batch-create
:base-revision 0 :target-revision 1
:base-extent (length base) :target-extent target-extent
:patches patches :coordinate-patches coordinates))
(source-function
(tp-surface-test--source-function
'tp--shadow-apply-commit-batch-to-string))
(real-substring (symbol-function 'substring))
(real-concat (symbol-function 'concat))
(substring-count 0)
(substring-output 0)
(concat-count 0)
(concat-input 0)
recording)
(cl-letf (((symbol-function 'substring)
(lambda (string start &optional end)
(let ((result (funcall real-substring string start end)))
(when recording
(cl-incf substring-count)
(cl-incf substring-output (length result)))
result)))
((symbol-function 'concat)
(lambda (&rest sequences)
(when recording
(cl-incf concat-count)
(cl-incf concat-input
(apply #'+ (mapcar #'length sequences))))
(apply real-concat sequences))))
(let ((result
(progn
(setq recording t)
(prog1 (funcall source-function base batch)
(setq recording nil)))))
(should (= (length result) target-extent))))
(should (= substring-count (1+ patch-count)))
(should (<= substring-output (length base)))
(should (= concat-count 1))
(should (= concat-input target-extent)))))
(ert-deftest tp-surface-test-producer-can-return-equal-coordinate-batch ()
"A retained producer can publish a strict batch through normal TP phases."
(tp-surface-test--with-buffer

View File

@ -3406,13 +3406,16 @@ When RETAIN-MARKERS is non-nil, keep snapshot markers for rollback."
(defun tp--shadow-apply-commit-batch-to-string (string batch)
"Return STRING with BATCH patches applied without touching a buffer."
(let ((result (copy-sequence string)))
(dolist (patch (reverse (tp-commit-batch-patches batch)))
(setq result
(concat (substring result 0 (plist-get patch :old-start))
(plist-get patch :replacement)
(substring result (plist-get patch :old-end)))))
result))
(let ((patches (tp-commit-batch-patches batch)))
(if (null patches)
(copy-sequence string)
(let ((cursor 0) pieces)
(dolist (patch patches)
(push (substring string cursor (plist-get patch :old-start)) pieces)
(push (plist-get patch :replacement) pieces)
(setq cursor (plist-get patch :old-end)))
(push (substring string cursor) pieces)
(apply #'concat (nreverse pieces))))))
(defun tp--shadow-apply-property-operations-to-string
(string base operations)