Retain native node deltas across confirmed sessions
Some checks are pending
CI / test (push) Waiting to run
CI / native-build (macos-latest) (push) Waiting to run
CI / native-build (ubuntu-latest) (push) Waiting to run
CI / native-build (windows-latest) (push) Waiting to run
CI / native-msrv (macos-latest) (push) Waiting to run
CI / native-msrv (ubuntu-latest) (push) Waiting to run
CI / native-msrv (windows-latest) (push) Waiting to run
Some checks are pending
CI / test (push) Waiting to run
CI / native-build (macos-latest) (push) Waiting to run
CI / native-build (ubuntu-latest) (push) Waiting to run
CI / native-build (windows-latest) (push) Waiting to run
CI / native-msrv (macos-latest) (push) Waiting to run
CI / native-msrv (ubuntu-latest) (push) Waiting to run
CI / native-msrv (windows-latest) (push) Waiting to run
This commit is contained in:
parent
bc4516e171
commit
b824467791
@ -9974,6 +9974,10 @@ role, and outside-complement compatibility before publication."
|
|||||||
(plist-put overrides :native-inherited-dirty-node-ids
|
(plist-put overrides :native-inherited-dirty-node-ids
|
||||||
(plist-get candidate-state
|
(plist-get candidate-state
|
||||||
:native-inherited-dirty-node-ids)))
|
:native-inherited-dirty-node-ids)))
|
||||||
|
(setq overrides
|
||||||
|
(plist-put overrides :native-local-dirty-entries
|
||||||
|
(plist-get candidate-state
|
||||||
|
:native-local-dirty-entries)))
|
||||||
(setq overrides
|
(setq overrides
|
||||||
(plist-put
|
(plist-put
|
||||||
overrides :native-topology-stable-p
|
overrides :native-topology-stable-p
|
||||||
@ -10963,9 +10967,10 @@ fresh by the mixed projection."
|
|||||||
((eq projection-kind 'native-frame)
|
((eq projection-kind 'native-frame)
|
||||||
(append
|
(append
|
||||||
(ebox-incremental--viewport-state-overrides
|
(ebox-incremental--viewport-state-overrides
|
||||||
candidate-state t)
|
candidate-state t)
|
||||||
(list :native-topology-stable-p t
|
(list :native-topology-stable-p t
|
||||||
:native-touched-node-ids nil
|
:native-touched-node-ids nil
|
||||||
|
:native-local-dirty-entries nil
|
||||||
:native-removed-node-ids nil)))
|
:native-removed-node-ids nil)))
|
||||||
((memq projection-kind
|
((memq projection-kind
|
||||||
'(viewport-reflow viewport-reflow-mixed-scroll))
|
'(viewport-reflow viewport-reflow-mixed-scroll))
|
||||||
|
|||||||
@ -339,6 +339,7 @@ new session with the same bounded configuration used by retained frames."
|
|||||||
(progn
|
(progn
|
||||||
(plist-put candidate-state :native-topology-stable-p t)
|
(plist-put candidate-state :native-topology-stable-p t)
|
||||||
(plist-put candidate-state :native-touched-node-ids nil)
|
(plist-put candidate-state :native-touched-node-ids nil)
|
||||||
|
(plist-put candidate-state :native-local-dirty-entries nil)
|
||||||
(plist-put candidate-state :native-removed-node-ids nil)
|
(plist-put candidate-state :native-removed-node-ids nil)
|
||||||
t)))
|
t)))
|
||||||
|
|
||||||
@ -375,6 +376,8 @@ new session with the same bounded configuration used by retained frames."
|
|||||||
old-state candidate-state))
|
old-state candidate-state))
|
||||||
(plist-put candidate-state :native-topology-stable-p stable)
|
(plist-put candidate-state :native-topology-stable-p stable)
|
||||||
(plist-put candidate-state :native-touched-node-ids layout-touched)
|
(plist-put candidate-state :native-touched-node-ids layout-touched)
|
||||||
|
(plist-put candidate-state :native-local-dirty-entries
|
||||||
|
(copy-tree (plist-get prepared :dirty-set)))
|
||||||
(plist-put candidate-state :native-inherited-dirty-node-ids
|
(plist-put candidate-state :native-inherited-dirty-node-ids
|
||||||
(ebox-native-commit-inherited-dirty-node-ids prepared))
|
(ebox-native-commit-inherited-dirty-node-ids prepared))
|
||||||
(when context-axes-stable
|
(when context-axes-stable
|
||||||
@ -612,6 +615,7 @@ publication transaction can still roll back."
|
|||||||
(1+ (or (plist-get state :runtime-revision) 0))))
|
(1+ (or (plist-get state :runtime-revision) 0))))
|
||||||
(plist-put state :native-frame-kind
|
(plist-put state :native-frame-kind
|
||||||
(if (plist-get frame :native-patch) 'patch 'full))
|
(if (plist-get frame :native-patch) 'patch 'full))
|
||||||
|
(cl-remf state :native-local-dirty-entries)
|
||||||
frame)))
|
frame)))
|
||||||
|
|
||||||
(defconst ebox-native-commit--failed-render-state-keys
|
(defconst ebox-native-commit--failed-render-state-keys
|
||||||
@ -632,6 +636,7 @@ publication transaction can still roll back."
|
|||||||
:native-reuse-mount-projection-p
|
:native-reuse-mount-projection-p
|
||||||
:native-reuse-ownership-p
|
:native-reuse-ownership-p
|
||||||
:native-inherited-dirty-node-ids
|
:native-inherited-dirty-node-ids
|
||||||
|
:native-local-dirty-entries
|
||||||
:native-topology-stable-p
|
:native-topology-stable-p
|
||||||
:native-touched-node-ids
|
:native-touched-node-ids
|
||||||
:native-removed-node-ids)
|
:native-removed-node-ids)
|
||||||
|
|||||||
@ -95,7 +95,7 @@
|
|||||||
|
|
||||||
(defvar ebox--flex-content-min-width-table)
|
(defvar ebox--flex-content-min-width-table)
|
||||||
|
|
||||||
(defconst ebox-native-reflow-abi-version "11:7:12"
|
(defconst ebox-native-reflow-abi-version "12:7:12"
|
||||||
"Version tuple shared by the native module, layout IR, and render tape.")
|
"Version tuple shared by the native module, layout IR, and render tape.")
|
||||||
|
|
||||||
(defconst ebox-native-reflow--minimum-rust-version "1.82.0"
|
(defconst ebox-native-reflow--minimum-rust-version "1.82.0"
|
||||||
@ -222,7 +222,10 @@ module while loading the package."
|
|||||||
styles
|
styles
|
||||||
layout-package
|
layout-package
|
||||||
layout-fragment-cache
|
layout-fragment-cache
|
||||||
|
layout-fragment-index
|
||||||
layout-fragment-revision
|
layout-fragment-revision
|
||||||
|
layout-style-index
|
||||||
|
layout-property-template-index
|
||||||
readiness-process
|
readiness-process
|
||||||
readiness-preparation
|
readiness-preparation
|
||||||
released-p)
|
released-p)
|
||||||
@ -286,9 +289,74 @@ module while loading the package."
|
|||||||
(defvar ebox-native-reflow--compile-styles nil
|
(defvar ebox-native-reflow--compile-styles nil
|
||||||
"Dynamically bound ordered style registry for one Layout IR compile.")
|
"Dynamically bound ordered style registry for one Layout IR compile.")
|
||||||
|
|
||||||
|
(defvar ebox-native-reflow--compile-style-base-count 0
|
||||||
|
"Number of retained styles preceding the current append batch.")
|
||||||
|
|
||||||
|
(defvar ebox-native-reflow--compile-style-index nil
|
||||||
|
"Persistent retained style-to-id index for incremental compilation.")
|
||||||
|
|
||||||
(defvar ebox-native-reflow--compile-property-templates nil
|
(defvar ebox-native-reflow--compile-property-templates nil
|
||||||
"Dynamically bound ordered opaque text-property registry.")
|
"Dynamically bound ordered opaque text-property registry.")
|
||||||
|
|
||||||
|
(defvar ebox-native-reflow--compile-property-template-base-count 0
|
||||||
|
"Number of retained property templates preceding current additions.")
|
||||||
|
|
||||||
|
(defvar ebox-native-reflow--compile-property-template-index nil
|
||||||
|
"Persistent retained property-template-to-id index.")
|
||||||
|
|
||||||
|
(defvar ebox-native-reflow--compile-retained-registry-p nil
|
||||||
|
"Non-nil while style and property registries append incrementally.")
|
||||||
|
|
||||||
|
(defconst ebox-native-reflow--persistent-index-depth 16
|
||||||
|
"Fixed number of four-bit branches in a retained native index path.")
|
||||||
|
|
||||||
|
(defun ebox-native-reflow--persistent-index-code (key)
|
||||||
|
"Return a stable nonnegative retained-index code for KEY."
|
||||||
|
(if (and (integerp key) (>= key 0))
|
||||||
|
key
|
||||||
|
(logand (sxhash-equal key) #xffffffffffffffff)))
|
||||||
|
|
||||||
|
(defun ebox-native-reflow--persistent-index-get (root key &optional missing)
|
||||||
|
"Return KEY's value from persistent ROOT, or MISSING."
|
||||||
|
(let ((node root)
|
||||||
|
(code (ebox-native-reflow--persistent-index-code key))
|
||||||
|
(depth 0))
|
||||||
|
(while (and node (< depth ebox-native-reflow--persistent-index-depth))
|
||||||
|
(setq node (and (vectorp node)
|
||||||
|
(aref node (logand (ash code (* -4 depth)) 15)))
|
||||||
|
depth (1+ depth)))
|
||||||
|
(if (= depth ebox-native-reflow--persistent-index-depth)
|
||||||
|
(let ((pair (cl-assoc key node :test #'equal)))
|
||||||
|
(if pair (cdr pair) missing))
|
||||||
|
missing)))
|
||||||
|
|
||||||
|
(defun ebox-native-reflow--persistent-index-put (root key value &optional depth)
|
||||||
|
"Return persistent ROOT updated to map KEY to VALUE.
|
||||||
|
Only the fixed-depth path for KEY is copied. DEPTH is for recursion."
|
||||||
|
(let ((depth (or depth 0)))
|
||||||
|
(if (= depth ebox-native-reflow--persistent-index-depth)
|
||||||
|
(cons (cons key value)
|
||||||
|
(cl-remove key root :key #'car :test #'equal))
|
||||||
|
(let* ((code (ebox-native-reflow--persistent-index-code key))
|
||||||
|
(branch (logand (ash code (* -4 depth)) 15))
|
||||||
|
(copy (if (vectorp root)
|
||||||
|
(copy-sequence root)
|
||||||
|
(make-vector 16 nil))))
|
||||||
|
(aset copy branch
|
||||||
|
(ebox-native-reflow--persistent-index-put
|
||||||
|
(aref copy branch) key value (1+ depth)))
|
||||||
|
copy))))
|
||||||
|
|
||||||
|
(defun ebox-native-reflow--persistent-index-from-sequence (sequence)
|
||||||
|
"Return an immutable persistent index for ordered SEQUENCE values."
|
||||||
|
(let (root)
|
||||||
|
(cl-loop for value across (vconcat sequence)
|
||||||
|
for id from 0
|
||||||
|
do (setq root
|
||||||
|
(ebox-native-reflow--persistent-index-put
|
||||||
|
root value id)))
|
||||||
|
root))
|
||||||
|
|
||||||
(defvar ebox-native-reflow--compile-property-template-ids nil
|
(defvar ebox-native-reflow--compile-property-template-ids nil
|
||||||
"Dynamically bound opaque property-template lookup table.")
|
"Dynamically bound opaque property-template lookup table.")
|
||||||
|
|
||||||
@ -1338,14 +1406,20 @@ copied separately; no pending frame or registered Rust document is inherited."
|
|||||||
(condition-case err
|
(condition-case err
|
||||||
(progn
|
(progn
|
||||||
(setf (ebox-native-reflow-session-styles fork)
|
(setf (ebox-native-reflow-session-styles fork)
|
||||||
(copy-sequence (ebox-native-reflow-session-styles session))
|
(ebox-native-reflow-session-styles session)
|
||||||
(ebox-native-reflow-session-layout-package fork)
|
(ebox-native-reflow-session-layout-package fork)
|
||||||
(ebox-native-reflow-session-layout-package session)
|
(ebox-native-reflow-session-layout-package session)
|
||||||
(ebox-native-reflow-session-layout-fragment-cache fork)
|
(ebox-native-reflow-session-layout-fragment-cache fork)
|
||||||
(copy-hash-table
|
(ebox-native-reflow-session-layout-fragment-cache session)
|
||||||
(ebox-native-reflow-session-layout-fragment-cache session))
|
(ebox-native-reflow-session-layout-fragment-index fork)
|
||||||
|
(ebox-native-reflow-session-layout-fragment-index session)
|
||||||
(ebox-native-reflow-session-layout-fragment-revision fork)
|
(ebox-native-reflow-session-layout-fragment-revision fork)
|
||||||
(ebox-native-reflow-session-layout-fragment-revision session))
|
(ebox-native-reflow-session-layout-fragment-revision session)
|
||||||
|
(ebox-native-reflow-session-layout-style-index fork)
|
||||||
|
(ebox-native-reflow-session-layout-style-index session)
|
||||||
|
(ebox-native-reflow-session-layout-property-template-index fork)
|
||||||
|
(ebox-native-reflow-session-layout-property-template-index
|
||||||
|
session))
|
||||||
fork)
|
fork)
|
||||||
(error
|
(error
|
||||||
(ebox-native-reflow-release-session fork)
|
(ebox-native-reflow-release-session fork)
|
||||||
@ -1409,7 +1483,11 @@ copied separately; no pending frame or registered Rust document is inherited."
|
|||||||
:properties owned-properties))
|
:properties owned-properties))
|
||||||
(missing (make-symbol "missing-property-template"))
|
(missing (make-symbol "missing-property-template"))
|
||||||
(existing
|
(existing
|
||||||
(if (hash-table-p
|
(if ebox-native-reflow--compile-retained-registry-p
|
||||||
|
(ebox-native-reflow--persistent-index-get
|
||||||
|
ebox-native-reflow--compile-property-template-index
|
||||||
|
entry missing)
|
||||||
|
(if (hash-table-p
|
||||||
ebox-native-reflow--compile-property-template-ids)
|
ebox-native-reflow--compile-property-template-ids)
|
||||||
(gethash
|
(gethash
|
||||||
entry ebox-native-reflow--compile-property-template-ids
|
entry ebox-native-reflow--compile-property-template-ids
|
||||||
@ -1417,13 +1495,19 @@ copied separately; no pending frame or registered Rust document is inherited."
|
|||||||
(or (cl-position
|
(or (cl-position
|
||||||
entry ebox-native-reflow--compile-property-templates
|
entry ebox-native-reflow--compile-property-templates
|
||||||
:test #'equal)
|
:test #'equal)
|
||||||
missing))))
|
missing)))))
|
||||||
(if (not (eq existing missing))
|
(if (not (eq existing missing))
|
||||||
existing
|
existing
|
||||||
(let ((id (length ebox-native-reflow--compile-property-templates)))
|
(let ((id (+ ebox-native-reflow--compile-property-template-base-count
|
||||||
|
(length ebox-native-reflow--compile-property-templates))))
|
||||||
(setq ebox-native-reflow--compile-property-templates
|
(setq ebox-native-reflow--compile-property-templates
|
||||||
(append ebox-native-reflow--compile-property-templates
|
(append ebox-native-reflow--compile-property-templates
|
||||||
(list entry)))
|
(list entry)))
|
||||||
|
(when ebox-native-reflow--compile-retained-registry-p
|
||||||
|
(setq ebox-native-reflow--compile-property-template-index
|
||||||
|
(ebox-native-reflow--persistent-index-put
|
||||||
|
ebox-native-reflow--compile-property-template-index
|
||||||
|
entry id)))
|
||||||
(when (hash-table-p
|
(when (hash-table-p
|
||||||
ebox-native-reflow--compile-property-template-ids)
|
ebox-native-reflow--compile-property-template-ids)
|
||||||
(puthash
|
(puthash
|
||||||
@ -1592,13 +1676,27 @@ When FORMATTED-P is non-nil, preserve exact display spaces produced by KP."
|
|||||||
(defun ebox-native-reflow--register-style (mode face)
|
(defun ebox-native-reflow--register-style (mode face)
|
||||||
"Return the stable numeric id for a MODE and FACE operation."
|
"Return the stable numeric id for a MODE and FACE operation."
|
||||||
(let* ((entry (list :mode mode :face face))
|
(let* ((entry (list :mode mode :face face))
|
||||||
(existing (cl-position entry ebox-native-reflow--compile-styles
|
(missing (make-symbol "missing-native-style"))
|
||||||
:test #'equal)))
|
(existing
|
||||||
(or existing
|
(if ebox-native-reflow--compile-retained-registry-p
|
||||||
(prog1 (length ebox-native-reflow--compile-styles)
|
(ebox-native-reflow--persistent-index-get
|
||||||
|
ebox-native-reflow--compile-style-index entry missing)
|
||||||
|
(or (cl-position entry ebox-native-reflow--compile-styles
|
||||||
|
:test #'equal)
|
||||||
|
missing))))
|
||||||
|
(if (not (eq existing missing))
|
||||||
|
existing
|
||||||
|
(prog1 (+ ebox-native-reflow--compile-style-base-count
|
||||||
|
(length ebox-native-reflow--compile-styles))
|
||||||
(setq ebox-native-reflow--compile-styles
|
(setq ebox-native-reflow--compile-styles
|
||||||
(append ebox-native-reflow--compile-styles
|
(append ebox-native-reflow--compile-styles
|
||||||
(list entry)))))))
|
(list entry)))
|
||||||
|
(when ebox-native-reflow--compile-retained-registry-p
|
||||||
|
(setq ebox-native-reflow--compile-style-index
|
||||||
|
(ebox-native-reflow--persistent-index-put
|
||||||
|
ebox-native-reflow--compile-style-index entry
|
||||||
|
(+ ebox-native-reflow--compile-style-base-count
|
||||||
|
(1- (length ebox-native-reflow--compile-styles))))))))))
|
||||||
|
|
||||||
(defun ebox-native-reflow--compile-add-face-style (face)
|
(defun ebox-native-reflow--compile-add-face-style (face)
|
||||||
"Register additive FACE and return its id, or nil."
|
"Register additive FACE and return its id, or nil."
|
||||||
@ -2286,6 +2384,16 @@ Otherwise return CANDIDATE with the next document revision."
|
|||||||
(plist-put replacement :document-revision
|
(plist-put replacement :document-revision
|
||||||
(1+ (or (plist-get old :document-revision) 0)))))))
|
(1+ (or (plist-get old :document-revision) 0)))))))
|
||||||
|
|
||||||
|
(defun ebox-native-reflow--confirmed-layout-package (package)
|
||||||
|
"Return PACKAGE without one-shot retained input transport fields."
|
||||||
|
(if (plist-get package :document-delta)
|
||||||
|
(let ((confirmed (copy-sequence package)))
|
||||||
|
(dolist (key '(:document-delta :native-fragment-index
|
||||||
|
:native-fragment-revision :native-style-index))
|
||||||
|
(cl-remf confirmed key))
|
||||||
|
confirmed)
|
||||||
|
package))
|
||||||
|
|
||||||
(defun ebox-native-reflow--buffer-display-signature (buffer)
|
(defun ebox-native-reflow--buffer-display-signature (buffer)
|
||||||
"Return BUFFER's live canonical Surface display capability."
|
"Return BUFFER's live canonical Surface display capability."
|
||||||
(with-current-buffer buffer
|
(with-current-buffer buffer
|
||||||
@ -2491,9 +2599,9 @@ per call, and no call recursively visits the captured Ebox tree."
|
|||||||
(gethash child flex-content-min-widths)))
|
(gethash child flex-content-min-widths)))
|
||||||
(ebox-native-reflow--retained-layout-children node))))))
|
(ebox-native-reflow--retained-layout-children node))))))
|
||||||
|
|
||||||
(defun ebox-native-reflow--compile-retained-layout-package
|
(defun ebox-native-reflow--compile-retained-layout-package-full
|
||||||
(session state node)
|
(session state node)
|
||||||
"Compile NODE incrementally into retained native SESSION from STATE."
|
"Compile a complete retained package for NODE in SESSION from STATE."
|
||||||
(let ((postorder (plist-get state :native-node-postorder)))
|
(let ((postorder (plist-get state :native-node-postorder)))
|
||||||
(unless (and (vectorp postorder) (> (length postorder) 0))
|
(unless (and (vectorp postorder) (> (length postorder) 0))
|
||||||
(setq postorder
|
(setq postorder
|
||||||
@ -2632,7 +2740,9 @@ per call, and no call recursively visits the captured Ebox tree."
|
|||||||
(setq fragment
|
(setq fragment
|
||||||
(ebox-native-reflow--compile-scene-node
|
(ebox-native-reflow--compile-scene-node
|
||||||
current fragments flex-content-min-widths)
|
current fragments flex-content-min-widths)
|
||||||
|
fragment (plist-put fragment :node-id node-id)
|
||||||
revision (1+ revision)
|
revision (1+ revision)
|
||||||
|
fragment (plist-put fragment :node-revision revision)
|
||||||
entry (list :signature (copy-tree signature)
|
entry (list :signature (copy-tree signature)
|
||||||
:fragment fragment
|
:fragment fragment
|
||||||
:revision revision)))
|
:revision revision)))
|
||||||
@ -2658,10 +2768,354 @@ per call, and no call recursively visits the captured Ebox tree."
|
|||||||
candidate)))
|
candidate)))
|
||||||
(setf (ebox-native-reflow-session-layout-fragment-cache session)
|
(setf (ebox-native-reflow-session-layout-fragment-cache session)
|
||||||
new-cache
|
new-cache
|
||||||
|
(ebox-native-reflow-session-layout-fragment-index session)
|
||||||
|
(let (index)
|
||||||
|
(maphash
|
||||||
|
(lambda (node-id entry)
|
||||||
|
(setq index
|
||||||
|
(ebox-native-reflow--persistent-index-put
|
||||||
|
index node-id entry)))
|
||||||
|
new-cache)
|
||||||
|
index)
|
||||||
(ebox-native-reflow-session-layout-fragment-revision session)
|
(ebox-native-reflow-session-layout-fragment-revision session)
|
||||||
revision)
|
revision
|
||||||
|
(ebox-native-reflow-session-layout-style-index session)
|
||||||
|
(ebox-native-reflow--persistent-index-from-sequence
|
||||||
|
(plist-get package :styles))
|
||||||
|
(ebox-native-reflow-session-layout-property-template-index
|
||||||
|
session)
|
||||||
|
(ebox-native-reflow--persistent-index-from-sequence
|
||||||
|
(plist-get package :property-templates)))
|
||||||
package)))))
|
package)))))
|
||||||
|
|
||||||
|
(defconst ebox-native-reflow--delta-edge-fields
|
||||||
|
'(:type :child :children :items :node-id :node-revision)
|
||||||
|
"Fragment fields that a stable local node delta cannot replace.")
|
||||||
|
|
||||||
|
(defconst ebox-native-reflow--delta-flex-edge-properties
|
||||||
|
'(:order :flex-grow :flex-shrink :flex-basis :align-self
|
||||||
|
:flex-direction :cross-align)
|
||||||
|
"Changed properties that alter retained Flex item metadata in N1.")
|
||||||
|
|
||||||
|
(defun ebox-native-reflow--delta-flex-edge-change-p (state)
|
||||||
|
"Return non-nil when STATE changes unsupported Flex edge metadata."
|
||||||
|
(cl-some
|
||||||
|
(lambda (dirty)
|
||||||
|
(let* ((keys (plist-get dirty :changed-keys))
|
||||||
|
(parents (plist-get state :parent-table))
|
||||||
|
(nodes (plist-get state :node-table))
|
||||||
|
(dirty-id (plist-get dirty :node-id))
|
||||||
|
(dirty-node (and nodes (gethash dirty-id nodes)))
|
||||||
|
(raw-parent-id (and parents (gethash dirty-id parents)))
|
||||||
|
(raw-parent (and nodes (gethash raw-parent-id nodes)))
|
||||||
|
(owner-id
|
||||||
|
(if (and (eq (plist-get dirty-node :ebox-kind) 'text)
|
||||||
|
(eq (plist-get raw-parent :ebox-kind) 'box)
|
||||||
|
(when-let* ((layout
|
||||||
|
(plist-get raw-parent
|
||||||
|
:ebox-layout-config)))
|
||||||
|
(eq (ebox-layout-config-kind layout) 'normal)))
|
||||||
|
raw-parent-id
|
||||||
|
dirty-id))
|
||||||
|
(edge-parent-id (and parents (gethash owner-id parents)))
|
||||||
|
(edge-parent (and nodes (gethash edge-parent-id nodes)))
|
||||||
|
(config
|
||||||
|
(and edge-parent
|
||||||
|
(plist-get edge-parent :ebox-layout-config))))
|
||||||
|
(or
|
||||||
|
(cl-some (lambda (key)
|
||||||
|
(memq key ebox-native-reflow--delta-flex-edge-properties))
|
||||||
|
keys)
|
||||||
|
;; A child-local edit can change content-min-width in a Flex item or
|
||||||
|
;; derived align-self in typed axis lowering. N1 has no edge patch,
|
||||||
|
;; so retain correctness through full input until exact edge facts are
|
||||||
|
;; independently addressable.
|
||||||
|
(or (eq (plist-get edge-parent :ebox-type) 'flex)
|
||||||
|
(and config
|
||||||
|
(let ((kind (ebox-layout-config-kind config))
|
||||||
|
(props (ebox-layout-config-props config)))
|
||||||
|
(or (eq kind 'flex)
|
||||||
|
(and (memq kind '(row column))
|
||||||
|
(not (and (equal (plist-get props :item-gap) 0)
|
||||||
|
(eq (plist-get props :cross-align)
|
||||||
|
'stretch)))))))))))
|
||||||
|
(plist-get state :native-local-dirty-entries)))
|
||||||
|
|
||||||
|
(defun ebox-native-reflow--fragment-local (fragment)
|
||||||
|
"Return FRAGMENT's scalar and local-content fields."
|
||||||
|
(let ((tail fragment) local)
|
||||||
|
(while tail
|
||||||
|
(let ((key (pop tail))
|
||||||
|
(value (pop tail)))
|
||||||
|
(unless (memq key ebox-native-reflow--delta-edge-fields)
|
||||||
|
(setq local (nconc local (list key value))))))
|
||||||
|
local))
|
||||||
|
|
||||||
|
(defun ebox-native-reflow--local-difference (old new)
|
||||||
|
"Return NEW local fields whose values differ from OLD."
|
||||||
|
(let ((tail new) difference)
|
||||||
|
(while tail
|
||||||
|
(let ((key (pop tail))
|
||||||
|
(value (pop tail)))
|
||||||
|
(unless (equal value (plist-get old key))
|
||||||
|
(setq difference (nconc difference (list key value))))))
|
||||||
|
difference))
|
||||||
|
|
||||||
|
(defun ebox-native-reflow--apply-local-difference (fragment difference)
|
||||||
|
"Return FRAGMENT with scalar fields from DIFFERENCE replaced."
|
||||||
|
(let ((copy (copy-sequence fragment))
|
||||||
|
(tail difference))
|
||||||
|
(while tail
|
||||||
|
(setq copy (plist-put copy (pop tail) (pop tail))))
|
||||||
|
copy))
|
||||||
|
|
||||||
|
(defun ebox-native-reflow--delta-owner-id (state node-id index)
|
||||||
|
"Return addressable owner for NODE-ID in STATE and retained INDEX."
|
||||||
|
(let ((parents (plist-get state :parent-table))
|
||||||
|
(nodes (plist-get state :node-table))
|
||||||
|
(current node-id))
|
||||||
|
(when-let* ((source (and nodes (gethash current nodes)))
|
||||||
|
(parent-id (and parents (gethash current parents)))
|
||||||
|
(parent (and nodes (gethash parent-id nodes))))
|
||||||
|
(when (and (eq (plist-get source :ebox-kind) 'text)
|
||||||
|
(eq (plist-get parent :ebox-kind) 'box)
|
||||||
|
(eq (ebox-layout-config-kind
|
||||||
|
(plist-get parent :ebox-layout-config))
|
||||||
|
'normal))
|
||||||
|
(setq current parent-id)))
|
||||||
|
(while (and current
|
||||||
|
(null (ebox-native-reflow--persistent-index-get
|
||||||
|
index current)))
|
||||||
|
(setq current (and parents (gethash current parents))))
|
||||||
|
current))
|
||||||
|
|
||||||
|
(defun ebox-native-reflow--compile-delta-slots (node old-fragment)
|
||||||
|
"Compile NODE local slots while retaining OLD-FRAGMENT edges."
|
||||||
|
(pcase (plist-get node :ebox-type)
|
||||||
|
('box
|
||||||
|
(if (eq (plist-get node :ebox-kind) 'text)
|
||||||
|
(vector (ebox-native-reflow--compile-text-node node))
|
||||||
|
(let* ((config (and (eq (plist-get node :ebox-kind) 'box)
|
||||||
|
(plist-get node :ebox-layout-config)))
|
||||||
|
(kind (and config (ebox-layout-config-kind config)))
|
||||||
|
(old-child (plist-get old-fragment :child))
|
||||||
|
(fused-p (and (eq kind 'normal)
|
||||||
|
(let ((child (car (ebox-tree-node-children node))))
|
||||||
|
(and child
|
||||||
|
(eq (plist-get child :ebox-kind) 'text)))))
|
||||||
|
(inner
|
||||||
|
(pcase kind
|
||||||
|
((or 'row 'column)
|
||||||
|
(ebox-native-reflow--compile-typed-axis-children
|
||||||
|
kind config nil))
|
||||||
|
('flex
|
||||||
|
(ebox-native-reflow--compile-flex-inner
|
||||||
|
(copy-sequence (ebox-layout-config-props config)) nil))
|
||||||
|
(_ nil)))
|
||||||
|
(child (cond (fused-p
|
||||||
|
(ebox-native-reflow--compile-text-node
|
||||||
|
(car (ebox-tree-node-children node))))
|
||||||
|
(inner inner)
|
||||||
|
(t old-child)))
|
||||||
|
(outer (ebox-native-reflow--compile-box node child nil t)))
|
||||||
|
(if inner (vector outer inner) (vector outer)))))
|
||||||
|
('flex
|
||||||
|
(let* ((wrapper (plist-get node :box))
|
||||||
|
(props (ebox--flex-container-content-props
|
||||||
|
(plist-get node :props) (plist-get node :raw-props)
|
||||||
|
wrapper)))
|
||||||
|
(when wrapper
|
||||||
|
(plist-put props :width nil)
|
||||||
|
(when (plist-member (plist-get node :raw-props) :height)
|
||||||
|
(plist-put props :height 'viewport-height)))
|
||||||
|
(let ((inner (ebox-native-reflow--compile-flex-inner props nil)))
|
||||||
|
(if wrapper
|
||||||
|
(vector (ebox-native-reflow--compile-box wrapper inner t) inner)
|
||||||
|
(vector inner)))))
|
||||||
|
(_ (vector old-fragment))))
|
||||||
|
|
||||||
|
(defun ebox-native-reflow--delta-direct-owner-ids (state index)
|
||||||
|
"Return owners for STATE's direct dirty entries found in INDEX."
|
||||||
|
(let ((seen (make-hash-table :test 'equal)) result)
|
||||||
|
(dolist (dirty (plist-get state :native-local-dirty-entries))
|
||||||
|
(when-let* ((owner
|
||||||
|
(ebox-native-reflow--delta-owner-id
|
||||||
|
state (plist-get dirty :node-id) index)))
|
||||||
|
(unless (gethash owner seen)
|
||||||
|
(puthash owner t seen)
|
||||||
|
(push owner result))))
|
||||||
|
(nreverse result)))
|
||||||
|
|
||||||
|
(defun ebox-native-reflow--compile-retained-layout-delta
|
||||||
|
(session state node)
|
||||||
|
"Return SESSION's local delta for NODE under STATE, or nil for fallback."
|
||||||
|
(let* ((old-package (ebox-native-reflow-session-layout-package session))
|
||||||
|
(old-index (ebox-native-reflow-session-layout-fragment-index session))
|
||||||
|
(node-table (plist-get state :node-table))
|
||||||
|
(touched (plist-get state :native-touched-node-ids)))
|
||||||
|
(when (and old-package old-index (hash-table-p node-table)
|
||||||
|
(plist-get state :native-topology-stable-p)
|
||||||
|
(plist-member state :native-local-dirty-entries)
|
||||||
|
(not (ebox-native-reflow--delta-flex-edge-change-p state))
|
||||||
|
;; Descendant inherited-style expansion needs its own bounded
|
||||||
|
;; affected-node index. Until then it is an explicit full
|
||||||
|
;; input fallback rather than a partial semantic update.
|
||||||
|
(null (plist-get state :native-inherited-dirty-node-ids)))
|
||||||
|
(if (null touched)
|
||||||
|
old-package
|
||||||
|
(let* ((style-base (length (plist-get old-package :styles)))
|
||||||
|
(template-base
|
||||||
|
(length (plist-get old-package :property-templates)))
|
||||||
|
(ebox-native-reflow--compile-styles nil)
|
||||||
|
(ebox-native-reflow--compile-style-base-count style-base)
|
||||||
|
(ebox-native-reflow--compile-style-index
|
||||||
|
(ebox-native-reflow-session-layout-style-index session))
|
||||||
|
(ebox-native-reflow--compile-property-templates nil)
|
||||||
|
(ebox-native-reflow--compile-property-template-base-count
|
||||||
|
template-base)
|
||||||
|
(ebox-native-reflow--compile-property-template-index
|
||||||
|
(ebox-native-reflow-session-layout-property-template-index
|
||||||
|
session))
|
||||||
|
(ebox-native-reflow--compile-retained-registry-p t)
|
||||||
|
(ebox-native-reflow--compile-display-signature
|
||||||
|
(ebox--current-display-signature))
|
||||||
|
(ebox-native-reflow--compile-root-node node)
|
||||||
|
(ebox--render-string-pixel-width-cache
|
||||||
|
(make-hash-table :test 'equal))
|
||||||
|
(ebox--render-string-max-pixel-width-cache
|
||||||
|
(make-hash-table :test 'eq))
|
||||||
|
(direct (ebox-native-reflow--delta-direct-owner-ids
|
||||||
|
state old-index))
|
||||||
|
(direct-set (make-hash-table :test 'equal))
|
||||||
|
(entry-ids nil)
|
||||||
|
(seen (make-hash-table :test 'equal))
|
||||||
|
(new-index old-index)
|
||||||
|
(revision
|
||||||
|
(or (ebox-native-reflow-session-layout-fragment-revision
|
||||||
|
session) 0))
|
||||||
|
entries unsupported)
|
||||||
|
(dolist (id direct) (puthash id t direct-set))
|
||||||
|
(dolist (id touched)
|
||||||
|
(when-let* ((owner
|
||||||
|
(ebox-native-reflow--delta-owner-id state id old-index)))
|
||||||
|
(unless (gethash owner seen)
|
||||||
|
(puthash owner t seen)
|
||||||
|
(push owner entry-ids))))
|
||||||
|
(dolist (owner-id (nreverse entry-ids))
|
||||||
|
(let* ((cached (ebox-native-reflow--persistent-index-get
|
||||||
|
old-index owner-id))
|
||||||
|
(old-fragment (plist-get cached :fragment))
|
||||||
|
(source (gethash owner-id node-table))
|
||||||
|
(expected (plist-get cached :revision))
|
||||||
|
patches new-fragment)
|
||||||
|
(unless (and cached source (integerp expected))
|
||||||
|
(setq unsupported t))
|
||||||
|
(when (and (not unsupported) (gethash owner-id direct-set))
|
||||||
|
(let ((slots
|
||||||
|
(ebox-native-reflow--compile-delta-slots
|
||||||
|
source old-fragment)))
|
||||||
|
(unless (equal (plist-get (aref slots 0) :type)
|
||||||
|
(plist-get old-fragment :type))
|
||||||
|
(setq unsupported t))
|
||||||
|
(unless unsupported
|
||||||
|
(let ((local
|
||||||
|
(ebox-native-reflow--local-difference
|
||||||
|
(ebox-native-reflow--fragment-local old-fragment)
|
||||||
|
(ebox-native-reflow--fragment-local
|
||||||
|
(aref slots 0)))))
|
||||||
|
(when local
|
||||||
|
(push (list :slot 0 :local local) patches)
|
||||||
|
(setq new-fragment
|
||||||
|
(ebox-native-reflow--apply-local-difference
|
||||||
|
old-fragment local))))
|
||||||
|
(when (> (length slots) 1)
|
||||||
|
(let ((old-inner (plist-get old-fragment :child))
|
||||||
|
(new-inner (aref slots 1)))
|
||||||
|
(unless (equal (plist-get old-inner :type)
|
||||||
|
(plist-get new-inner :type))
|
||||||
|
(setq unsupported t))
|
||||||
|
(unless unsupported
|
||||||
|
(let ((local
|
||||||
|
(ebox-native-reflow--local-difference
|
||||||
|
(ebox-native-reflow--fragment-local old-inner)
|
||||||
|
(ebox-native-reflow--fragment-local new-inner))))
|
||||||
|
(when local
|
||||||
|
(push (list :slot 1 :local local) patches)
|
||||||
|
(let ((outer (or new-fragment
|
||||||
|
(copy-sequence old-fragment))))
|
||||||
|
(plist-put
|
||||||
|
outer :child
|
||||||
|
(ebox-native-reflow--apply-local-difference
|
||||||
|
old-inner local))
|
||||||
|
(setq new-fragment outer))))))))))
|
||||||
|
(unless unsupported
|
||||||
|
(setq revision (1+ revision))
|
||||||
|
(unless new-fragment
|
||||||
|
(setq new-fragment (copy-sequence old-fragment)))
|
||||||
|
(plist-put new-fragment :node-id owner-id)
|
||||||
|
(plist-put new-fragment :node-revision revision)
|
||||||
|
(let* ((entry
|
||||||
|
(append
|
||||||
|
(list :node-id owner-id
|
||||||
|
:expected-revision expected
|
||||||
|
:target-revision revision)
|
||||||
|
(when patches
|
||||||
|
(list :slot-patches (vconcat (nreverse patches))))))
|
||||||
|
(new-cache-entry
|
||||||
|
(list :signature nil
|
||||||
|
:fragment (or new-fragment old-fragment)
|
||||||
|
:revision revision)))
|
||||||
|
(push entry entries)
|
||||||
|
(setq new-index
|
||||||
|
(ebox-native-reflow--persistent-index-put
|
||||||
|
new-index owner-id new-cache-entry))))))
|
||||||
|
(when (and (not unsupported)
|
||||||
|
;; N1 can authenticate retained templates, but cannot
|
||||||
|
;; append their Emacs-owned values to Rust atomically.
|
||||||
|
(= template-base
|
||||||
|
(+ template-base
|
||||||
|
(length ebox-native-reflow--compile-property-templates))))
|
||||||
|
(let* ((styles-append ebox-native-reflow--compile-styles)
|
||||||
|
(styles
|
||||||
|
(if styles-append
|
||||||
|
;; Materializing the global style output is explicitly
|
||||||
|
;; outside retained input work; the common no-append
|
||||||
|
;; path preserves the old vector by identity.
|
||||||
|
(vconcat (plist-get old-package :styles) styles-append)
|
||||||
|
(plist-get old-package :styles)))
|
||||||
|
(base (or (plist-get old-package :document-revision) 0))
|
||||||
|
(package (copy-sequence old-package)))
|
||||||
|
(plist-put package :document-revision (1+ base))
|
||||||
|
(plist-put package :styles styles)
|
||||||
|
(plist-put
|
||||||
|
package :document-delta
|
||||||
|
(list :style-base-count style-base
|
||||||
|
:styles-append
|
||||||
|
(vconcat
|
||||||
|
(mapcar
|
||||||
|
(lambda (descriptor)
|
||||||
|
(list :mode (symbol-name (plist-get descriptor :mode))
|
||||||
|
:face
|
||||||
|
(list :lisp
|
||||||
|
(prin1-to-string
|
||||||
|
(plist-get descriptor :face)))))
|
||||||
|
styles-append))
|
||||||
|
:property-template-base-count template-base
|
||||||
|
:property-template-target-count template-base
|
||||||
|
:entries (vconcat (nreverse entries))))
|
||||||
|
(plist-put package :native-fragment-index new-index)
|
||||||
|
(plist-put package :native-fragment-revision revision)
|
||||||
|
(plist-put package :native-style-index
|
||||||
|
ebox-native-reflow--compile-style-index)
|
||||||
|
package)))))))
|
||||||
|
|
||||||
|
(defun ebox-native-reflow--compile-retained-layout-package
|
||||||
|
(session state node)
|
||||||
|
"Compile NODE for SESSION using a delta when STATE proves it supported."
|
||||||
|
(or (ebox-native-reflow--compile-retained-layout-delta session state node)
|
||||||
|
(ebox-native-reflow--compile-retained-layout-package-full
|
||||||
|
session state node)))
|
||||||
|
|
||||||
(defun ebox-native-reflow-compile-layout-ir (node)
|
(defun ebox-native-reflow-compile-layout-ir (node)
|
||||||
"Compile NODE to one complete versioned native layout document."
|
"Compile NODE to one complete versioned native layout document."
|
||||||
(plist-get (ebox-native-reflow--compile-layout-package node) :document))
|
(plist-get (ebox-native-reflow--compile-layout-package node) :document))
|
||||||
@ -2682,9 +3136,10 @@ per call, and no call recursively visits the captured Ebox tree."
|
|||||||
|
|
||||||
(defun ebox-native-reflow--layout-control-json
|
(defun ebox-native-reflow--layout-control-json
|
||||||
(document frames &optional document-base-revision
|
(document frames &optional document-base-revision
|
||||||
document-target-revision)
|
document-target-revision document-delta)
|
||||||
"Return strict control JSON for optional layout DOCUMENT and FRAMES.
|
"Return strict control JSON for optional layout DOCUMENT and FRAMES.
|
||||||
DOCUMENT-BASE-REVISION and DOCUMENT-TARGET-REVISION bind retained input."
|
DOCUMENT-BASE-REVISION and DOCUMENT-TARGET-REVISION bind retained input.
|
||||||
|
DOCUMENT-DELTA is the stable-topology local replacement batch."
|
||||||
(unless (and (listp frames) frames)
|
(unless (and (listp frames) frames)
|
||||||
(error "Native reflow requires at least one layout frame"))
|
(error "Native reflow requires at least one layout frame"))
|
||||||
(let ((control
|
(let ((control
|
||||||
@ -2792,6 +3247,8 @@ DOCUMENT-BASE-REVISION and DOCUMENT-TARGET-REVISION bind retained input."
|
|||||||
document-target-revision)))
|
document-target-revision)))
|
||||||
(when document
|
(when document
|
||||||
(setq control (plist-put control :document document)))
|
(setq control (plist-put control :document document)))
|
||||||
|
(when document-delta
|
||||||
|
(setq control (plist-put control :document-delta document-delta)))
|
||||||
(ebox-native-reflow--serialize-layout-control control)))
|
(ebox-native-reflow--serialize-layout-control control)))
|
||||||
|
|
||||||
(defun ebox-native-reflow-submit-layout
|
(defun ebox-native-reflow-submit-layout
|
||||||
@ -2807,9 +3264,12 @@ LAYOUT-PACKAGE may reuse a caller-validated document for the same NODE."
|
|||||||
node
|
node
|
||||||
(ebox-native-reflow-session-styles session)
|
(ebox-native-reflow-session-styles session)
|
||||||
(plist-get old-package :property-templates))))
|
(plist-get old-package :property-templates))))
|
||||||
|
(document-delta (plist-get candidate-package :document-delta))
|
||||||
(package
|
(package
|
||||||
(ebox-native-reflow--reuse-exact-layout-package
|
(if document-delta
|
||||||
old-package candidate-package))
|
candidate-package
|
||||||
|
(ebox-native-reflow--reuse-exact-layout-package
|
||||||
|
old-package candidate-package)))
|
||||||
(register-layout-p
|
(register-layout-p
|
||||||
(not (eq package
|
(not (eq package
|
||||||
old-package)))
|
old-package)))
|
||||||
@ -2821,12 +3281,14 @@ LAYOUT-PACKAGE may reuse a caller-validated document for the same NODE."
|
|||||||
(ebox-native-reflow--live-handle session)
|
(ebox-native-reflow--live-handle session)
|
||||||
generation
|
generation
|
||||||
(ebox-native-reflow--layout-control-json
|
(ebox-native-reflow--layout-control-json
|
||||||
(and register-layout-p (plist-get package :document)) frames
|
(and register-layout-p (not document-delta)
|
||||||
base-revision target-revision))))
|
(plist-get package :document))
|
||||||
|
frames base-revision target-revision document-delta))))
|
||||||
(setf (ebox-native-reflow-session-generation session) generation)
|
(setf (ebox-native-reflow-session-generation session) generation)
|
||||||
(setf (ebox-native-reflow-session-styles session)
|
(setf (ebox-native-reflow-session-styles session)
|
||||||
(plist-get package :styles))
|
(plist-get package :styles))
|
||||||
(setf (ebox-native-reflow-session-layout-package session) package)
|
(setf (ebox-native-reflow-session-layout-package session)
|
||||||
|
(ebox-native-reflow--confirmed-layout-package package))
|
||||||
accepted))
|
accepted))
|
||||||
|
|
||||||
(defun ebox-native-reflow--tape-cursor (payload &optional position)
|
(defun ebox-native-reflow--tape-cursor (payload &optional position)
|
||||||
@ -3968,13 +4430,16 @@ root effect metadata required by a thin host commit."
|
|||||||
(plist-get
|
(plist-get
|
||||||
(ebox-native-reflow-session-layout-package session)
|
(ebox-native-reflow-session-layout-package session)
|
||||||
:property-templates)))))
|
:property-templates)))))
|
||||||
|
(document-delta (plist-get candidate-package :document-delta))
|
||||||
(package
|
(package
|
||||||
(ebox-native-reflow--reuse-exact-layout-package
|
(if document-delta
|
||||||
old-package candidate-package))
|
candidate-package
|
||||||
|
(ebox-native-reflow--reuse-exact-layout-package
|
||||||
|
old-package candidate-package)))
|
||||||
(generation (1+ (ebox-native-reflow-session-generation session)))
|
(generation (1+ (ebox-native-reflow-session-generation session)))
|
||||||
(control-frame (copy-sequence frame))
|
(control-frame (copy-sequence frame))
|
||||||
(native-key (or (plist-get control-frame :key) 1))
|
(native-key (or (plist-get control-frame :key) 1))
|
||||||
(reuse-document-p (eq package old-package))
|
(reuse-document-p (or document-delta (eq package old-package)))
|
||||||
(base-revision (or (plist-get old-package :document-revision) 0))
|
(base-revision (or (plist-get old-package :document-revision) 0))
|
||||||
(target-revision (or (plist-get package :document-revision)
|
(target-revision (or (plist-get package :document-revision)
|
||||||
(1+ base-revision))))
|
(1+ base-revision))))
|
||||||
@ -3986,12 +4451,22 @@ root effect metadata required by a thin host commit."
|
|||||||
generation
|
generation
|
||||||
(ebox-native-reflow--layout-control-json
|
(ebox-native-reflow--layout-control-json
|
||||||
(unless reuse-document-p (plist-get package :document))
|
(unless reuse-document-p (plist-get package :document))
|
||||||
(list control-frame) base-revision target-revision))
|
(list control-frame) base-revision target-revision
|
||||||
|
document-delta))
|
||||||
package control-frame generation)))
|
package control-frame generation)))
|
||||||
(setf (ebox-native-reflow-session-generation session) generation
|
(setf (ebox-native-reflow-session-generation session) generation
|
||||||
(ebox-native-reflow-session-layout-package session) package
|
(ebox-native-reflow-session-layout-package session)
|
||||||
|
(ebox-native-reflow--confirmed-layout-package package)
|
||||||
(ebox-native-reflow-session-styles session)
|
(ebox-native-reflow-session-styles session)
|
||||||
(plist-get package :styles))
|
(plist-get package :styles))
|
||||||
|
(when document-delta
|
||||||
|
(setf (ebox-native-reflow-session-layout-fragment-cache session) nil
|
||||||
|
(ebox-native-reflow-session-layout-fragment-index session)
|
||||||
|
(plist-get package :native-fragment-index)
|
||||||
|
(ebox-native-reflow-session-layout-fragment-revision session)
|
||||||
|
(plist-get package :native-fragment-revision)
|
||||||
|
(ebox-native-reflow-session-layout-style-index session)
|
||||||
|
(plist-get package :native-style-index)))
|
||||||
result)))
|
result)))
|
||||||
|
|
||||||
(defun ebox-native-reflow-render-proof-sync
|
(defun ebox-native-reflow-render-proof-sync
|
||||||
|
|||||||
@ -278,6 +278,12 @@ rather than relying on an implicit default.")
|
|||||||
(:symbol ebox-native-reflow--compile-property-template-ids
|
(:symbol ebox-native-reflow--compile-property-template-ids
|
||||||
:reason dynamically-bound-compile-local-scratch
|
:reason dynamically-bound-compile-local-scratch
|
||||||
:evidence let-bound-per-compile-and-never-committed)
|
:evidence let-bound-per-compile-and-never-committed)
|
||||||
|
(:symbol ebox-native-reflow--compile-property-template-index
|
||||||
|
:reason dynamically-bound-compile-local-scratch
|
||||||
|
:evidence let-bound-per-compile-and-published-only-through-session-root)
|
||||||
|
(:symbol ebox-native-reflow--compile-style-index
|
||||||
|
:reason dynamically-bound-compile-local-scratch
|
||||||
|
:evidence let-bound-per-compile-and-published-only-through-session-root)
|
||||||
(:symbol ebox--render-source-generations
|
(:symbol ebox--render-source-generations
|
||||||
:reason dynamically-bound-render-proof-input
|
:reason dynamically-bound-render-proof-input
|
||||||
:evidence let-bound-to-candidate-and-prior-generations-for-one-plan)
|
:evidence let-bound-to-candidate-and-prior-generations-for-one-plan)
|
||||||
|
|||||||
@ -164,7 +164,7 @@ ebox_module_version(emacs_env *env, ptrdiff_t nargs, emacs_value *args,
|
|||||||
(void) nargs;
|
(void) nargs;
|
||||||
(void) args;
|
(void) args;
|
||||||
(void) data;
|
(void) data;
|
||||||
static const char version[] = "11:7:12";
|
static const char version[] = "12:7:12";
|
||||||
return env->make_string(env, version, (ptrdiff_t) (sizeof version - 1));
|
return env->make_string(env, version, (ptrdiff_t) (sizeof version - 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
1717
native/src/layout.rs
1717
native/src/layout.rs
File diff suppressed because it is too large
Load Diff
@ -1,8 +1,8 @@
|
|||||||
mod layout;
|
mod layout;
|
||||||
|
|
||||||
use layout::{
|
use layout::{
|
||||||
encode_error_tape, LayoutContext, LayoutDocument, LayoutTape, TapeIdentity, TapeOutputOptions,
|
encode_error_tape, DocumentDelta, LayoutContext, LayoutDocument, LayoutTape, RetainedDocument,
|
||||||
MAX_LAYOUT_DIMENSION, MIN_TAPE_BYTES,
|
TapeIdentity, TapeOutputOptions, MAX_LAYOUT_DIMENSION, MIN_TAPE_BYTES,
|
||||||
};
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::collections::{HashMap, HashSet, VecDeque};
|
use std::collections::{HashMap, HashSet, VecDeque};
|
||||||
@ -324,6 +324,8 @@ struct ControlBatch {
|
|||||||
version: u32,
|
version: u32,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
document: Option<LayoutDocument>,
|
document: Option<LayoutDocument>,
|
||||||
|
#[serde(default, rename = "document-delta")]
|
||||||
|
document_delta: Option<DocumentDelta>,
|
||||||
#[serde(default, rename = "document-base-revision")]
|
#[serde(default, rename = "document-base-revision")]
|
||||||
document_base_revision: Option<u64>,
|
document_base_revision: Option<u64>,
|
||||||
#[serde(default, rename = "document-target-revision")]
|
#[serde(default, rename = "document-target-revision")]
|
||||||
@ -375,7 +377,7 @@ struct ControlFrame {
|
|||||||
enum JobPayload {
|
enum JobPayload {
|
||||||
Echo(Vec<u8>),
|
Echo(Vec<u8>),
|
||||||
Layout {
|
Layout {
|
||||||
document: Arc<LayoutDocument>,
|
document: LayoutSource,
|
||||||
context: LayoutContext,
|
context: LayoutContext,
|
||||||
root_width: i64,
|
root_width: i64,
|
||||||
root_width_override: bool,
|
root_width_override: bool,
|
||||||
@ -388,9 +390,43 @@ enum JobPayload {
|
|||||||
root_metadata: bool,
|
root_metadata: bool,
|
||||||
document_base_revision: u64,
|
document_base_revision: u64,
|
||||||
document_target_revision: u64,
|
document_target_revision: u64,
|
||||||
|
validation_resolver_lookups: u64,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
enum LayoutSource {
|
||||||
|
Full(Arc<LayoutDocument>),
|
||||||
|
Retained(Arc<RetainedDocument>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LayoutSource {
|
||||||
|
fn validate_context(&self, context: LayoutContext) -> Result<(), String> {
|
||||||
|
match self {
|
||||||
|
Self::Full(document) => document.validate_context(context),
|
||||||
|
Self::Retained(document) => document.validate_context(context),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn layout_tape(
|
||||||
|
&self,
|
||||||
|
context: LayoutContext,
|
||||||
|
root_width: Option<i64>,
|
||||||
|
) -> Result<LayoutTape, String> {
|
||||||
|
match self {
|
||||||
|
Self::Full(document) => document.layout_tape(context, root_width),
|
||||||
|
Self::Retained(document) => document.layout_tape(context, root_width),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn styles(&self) -> Result<Vec<layout::StyleTemplate>, String> {
|
||||||
|
match self {
|
||||||
|
Self::Full(document) => Ok(document.styles.clone()),
|
||||||
|
Self::Retained(document) => document.styles(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
struct BaselineIdentity {
|
struct BaselineIdentity {
|
||||||
context: LayoutContext,
|
context: LayoutContext,
|
||||||
@ -404,7 +440,7 @@ struct BaselineIdentity {
|
|||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
struct ConfirmedBaseline {
|
struct ConfirmedBaseline {
|
||||||
identity: BaselineIdentity,
|
identity: BaselineIdentity,
|
||||||
document: Arc<LayoutDocument>,
|
document: LayoutSource,
|
||||||
document_revision: u64,
|
document_revision: u64,
|
||||||
tape: LayoutTape,
|
tape: LayoutTape,
|
||||||
styles: Vec<layout::StyleTemplate>,
|
styles: Vec<layout::StyleTemplate>,
|
||||||
@ -413,7 +449,7 @@ struct ConfirmedBaseline {
|
|||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct PendingBaseline {
|
struct PendingBaseline {
|
||||||
confirmed_identity: BaselineIdentity,
|
confirmed_identity: BaselineIdentity,
|
||||||
document: Arc<LayoutDocument>,
|
document: LayoutSource,
|
||||||
document_revision: u64,
|
document_revision: u64,
|
||||||
tape: LayoutTape,
|
tape: LayoutTape,
|
||||||
styles: Vec<layout::StyleTemplate>,
|
styles: Vec<layout::StyleTemplate>,
|
||||||
@ -431,6 +467,20 @@ struct RenderedJob {
|
|||||||
baseline_hit: bool,
|
baseline_hit: bool,
|
||||||
base_renders: u64,
|
base_renders: u64,
|
||||||
target_renders: u64,
|
target_renders: u64,
|
||||||
|
resolver_lookups: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct DocumentInputStats {
|
||||||
|
parses: u64,
|
||||||
|
validations: u64,
|
||||||
|
reuses: u64,
|
||||||
|
full_input_bytes: u64,
|
||||||
|
delta_input_bytes: u64,
|
||||||
|
full_nodes_parsed: u64,
|
||||||
|
delta_entries_parsed: u64,
|
||||||
|
delta_entries_validated: u64,
|
||||||
|
trie_path_nodes_copied: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@ -463,6 +513,13 @@ struct RuntimeState {
|
|||||||
document_parses: u64,
|
document_parses: u64,
|
||||||
document_validations: u64,
|
document_validations: u64,
|
||||||
document_reuses: u64,
|
document_reuses: u64,
|
||||||
|
document_full_input_bytes: u64,
|
||||||
|
document_delta_input_bytes: u64,
|
||||||
|
document_full_nodes_parsed: u64,
|
||||||
|
document_delta_entries_parsed: u64,
|
||||||
|
document_delta_entries_validated: u64,
|
||||||
|
document_trie_path_nodes_copied: u64,
|
||||||
|
document_resolver_lookups: u64,
|
||||||
confirmed_baseline: Option<Arc<ConfirmedBaseline>>,
|
confirmed_baseline: Option<Arc<ConfirmedBaseline>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -491,7 +548,7 @@ struct Shared {
|
|||||||
struct Session {
|
struct Session {
|
||||||
shared: Arc<Shared>,
|
shared: Arc<Shared>,
|
||||||
workers: Mutex<Option<Vec<JoinHandle<()>>>>,
|
workers: Mutex<Option<Vec<JoinHandle<()>>>>,
|
||||||
layout_document: Mutex<Option<Arc<LayoutDocument>>>,
|
layout_document: Mutex<Option<LayoutSource>>,
|
||||||
worker_count: usize,
|
worker_count: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -516,6 +573,13 @@ struct SessionStats {
|
|||||||
document_parses: u64,
|
document_parses: u64,
|
||||||
document_validations: u64,
|
document_validations: u64,
|
||||||
document_reuses: u64,
|
document_reuses: u64,
|
||||||
|
document_full_input_bytes: u64,
|
||||||
|
document_delta_input_bytes: u64,
|
||||||
|
document_full_nodes_parsed: u64,
|
||||||
|
document_delta_entries_parsed: u64,
|
||||||
|
document_delta_entries_validated: u64,
|
||||||
|
document_trie_path_nodes_copied: u64,
|
||||||
|
document_resolver_lookups: u64,
|
||||||
pending_baselines: usize,
|
pending_baselines: usize,
|
||||||
confirmed_baseline: bool,
|
confirmed_baseline: bool,
|
||||||
confirmed_baseline_bytes: usize,
|
confirmed_baseline_bytes: usize,
|
||||||
@ -581,6 +645,13 @@ impl Session {
|
|||||||
document_parses: 0,
|
document_parses: 0,
|
||||||
document_validations: 0,
|
document_validations: 0,
|
||||||
document_reuses: 0,
|
document_reuses: 0,
|
||||||
|
document_full_input_bytes: 0,
|
||||||
|
document_delta_input_bytes: 0,
|
||||||
|
document_full_nodes_parsed: 0,
|
||||||
|
document_delta_entries_parsed: 0,
|
||||||
|
document_delta_entries_validated: 0,
|
||||||
|
document_trie_path_nodes_copied: 0,
|
||||||
|
document_resolver_lookups: 0,
|
||||||
confirmed_baseline,
|
confirmed_baseline,
|
||||||
}),
|
}),
|
||||||
readiness_channel: Mutex::new(None),
|
readiness_channel: Mutex::new(None),
|
||||||
@ -611,7 +682,7 @@ impl Session {
|
|||||||
.unwrap_or_else(|poison| poison.into_inner())
|
.unwrap_or_else(|poison| poison.into_inner())
|
||||||
.confirmed_baseline
|
.confirmed_baseline
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|baseline| Arc::clone(&baseline.document));
|
.map(|baseline| baseline.document.clone());
|
||||||
Ok(Box::new(Self {
|
Ok(Box::new(Self {
|
||||||
shared,
|
shared,
|
||||||
workers: Mutex::new(Some(handles)),
|
workers: Mutex::new(Some(handles)),
|
||||||
@ -643,6 +714,9 @@ impl Session {
|
|||||||
|
|
||||||
fn submit(&self, generation: u64, payload: &[u8]) -> Result<usize, String> {
|
fn submit(&self, generation: u64, payload: &[u8]) -> Result<usize, String> {
|
||||||
let batch = parse_control_batch(payload)?;
|
let batch = parse_control_batch(payload)?;
|
||||||
|
if batch.document_delta.is_some() {
|
||||||
|
return Err("Native async reflow does not accept retained document deltas".to_owned());
|
||||||
|
}
|
||||||
if !self.shared.alive.load(Ordering::Acquire) {
|
if !self.shared.alive.load(Ordering::Acquire) {
|
||||||
return Err("Native reflow session is closed".to_owned());
|
return Err("Native reflow session is closed".to_owned());
|
||||||
}
|
}
|
||||||
@ -672,12 +746,12 @@ impl Session {
|
|||||||
let document = match batch.document {
|
let document = match batch.document {
|
||||||
Some(document) => {
|
Some(document) => {
|
||||||
document.validate()?;
|
document.validate()?;
|
||||||
Some(Arc::new(document))
|
Some(LayoutSource::Full(Arc::new(document)))
|
||||||
}
|
}
|
||||||
None if layout_requested => Some(
|
None if layout_requested => Some(
|
||||||
confirmed_baseline
|
confirmed_baseline
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|baseline| Arc::clone(&baseline.document))
|
.map(|baseline| baseline.document.clone())
|
||||||
.or_else(|| {
|
.or_else(|| {
|
||||||
self.layout_document
|
self.layout_document
|
||||||
.lock()
|
.lock()
|
||||||
@ -852,10 +926,13 @@ impl Session {
|
|||||||
.unwrap_or_else(|poison| poison.into_inner())
|
.unwrap_or_else(|poison| poison.into_inner())
|
||||||
.confirmed_baseline
|
.confirmed_baseline
|
||||||
.clone();
|
.clone();
|
||||||
let (document, document_parses, document_validations, document_reuses) = match batch
|
let (document, input_stats) = match (batch.document, batch.document_delta) {
|
||||||
.document
|
(Some(_), Some(_)) => {
|
||||||
{
|
return Err(
|
||||||
Some(document) => {
|
"Native retained render cannot combine document and document-delta".to_owned(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
(Some(document), None) => {
|
||||||
if document_base_revision.checked_add(1) != Some(document_target_revision) {
|
if document_base_revision.checked_add(1) != Some(document_target_revision) {
|
||||||
return Err(
|
return Err(
|
||||||
"Native retained document replacement must advance one revision".to_owned(),
|
"Native retained document replacement must advance one revision".to_owned(),
|
||||||
@ -876,10 +953,60 @@ impl Session {
|
|||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
document.validate()?;
|
let retained = document.retained_root_p();
|
||||||
(Arc::new(document), 1, 1, 0)
|
let (source, node_count) = if retained {
|
||||||
|
let (document, node_count) = RetainedDocument::bootstrap(document)?;
|
||||||
|
(LayoutSource::Retained(document), node_count)
|
||||||
|
} else {
|
||||||
|
document.validate()?;
|
||||||
|
let node_count = document.input_node_count();
|
||||||
|
(LayoutSource::Full(Arc::new(document)), node_count)
|
||||||
|
};
|
||||||
|
(
|
||||||
|
source,
|
||||||
|
DocumentInputStats {
|
||||||
|
parses: 1,
|
||||||
|
validations: 1,
|
||||||
|
full_input_bytes: payload.len() as u64,
|
||||||
|
full_nodes_parsed: node_count,
|
||||||
|
..DocumentInputStats::default()
|
||||||
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
None => {
|
(None, Some(delta)) => {
|
||||||
|
if document_base_revision.checked_add(1) != Some(document_target_revision) {
|
||||||
|
return Err(
|
||||||
|
"Native retained document delta must advance one revision".to_owned()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
let baseline = confirmed.as_ref().ok_or_else(|| {
|
||||||
|
"Native retained document delta requires a confirmed baseline".to_owned()
|
||||||
|
})?;
|
||||||
|
if baseline.document_revision != document_base_revision {
|
||||||
|
return Err(
|
||||||
|
"Native retained document delta base revision does not match confirmed state"
|
||||||
|
.to_owned(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
let LayoutSource::Retained(document) = &baseline.document else {
|
||||||
|
return Err(
|
||||||
|
"Native retained document delta requires an identified bootstrap"
|
||||||
|
.to_owned(),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
let (document, parsed, copied) = document.apply_delta(delta)?;
|
||||||
|
(
|
||||||
|
LayoutSource::Retained(document),
|
||||||
|
DocumentInputStats {
|
||||||
|
delta_input_bytes: payload.len() as u64,
|
||||||
|
delta_entries_parsed: parsed,
|
||||||
|
delta_entries_validated: parsed,
|
||||||
|
trie_path_nodes_copied: copied,
|
||||||
|
..DocumentInputStats::default()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
(None, None) => {
|
||||||
if document_base_revision != document_target_revision {
|
if document_base_revision != document_target_revision {
|
||||||
return Err(
|
return Err(
|
||||||
"Native retained document reuse requires equal base and target revisions"
|
"Native retained document reuse requires equal base and target revisions"
|
||||||
@ -902,7 +1029,13 @@ impl Session {
|
|||||||
.to_owned(),
|
.to_owned(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
(Arc::clone(&baseline.document), 0, 0, 1)
|
(
|
||||||
|
baseline.document.clone(),
|
||||||
|
DocumentInputStats {
|
||||||
|
reuses: 1,
|
||||||
|
..DocumentInputStats::default()
|
||||||
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let frame = batch
|
let frame = batch
|
||||||
@ -943,9 +1076,16 @@ impl Session {
|
|||||||
}
|
}
|
||||||
state.base_renders += output.base_renders;
|
state.base_renders += output.base_renders;
|
||||||
state.target_renders += output.target_renders;
|
state.target_renders += output.target_renders;
|
||||||
state.document_parses += document_parses;
|
state.document_resolver_lookups += output.resolver_lookups;
|
||||||
state.document_validations += document_validations;
|
state.document_parses += input_stats.parses;
|
||||||
state.document_reuses += document_reuses;
|
state.document_validations += input_stats.validations;
|
||||||
|
state.document_reuses += input_stats.reuses;
|
||||||
|
state.document_full_input_bytes += input_stats.full_input_bytes;
|
||||||
|
state.document_delta_input_bytes += input_stats.delta_input_bytes;
|
||||||
|
state.document_full_nodes_parsed += input_stats.full_nodes_parsed;
|
||||||
|
state.document_delta_entries_parsed += input_stats.delta_entries_parsed;
|
||||||
|
state.document_delta_entries_validated += input_stats.delta_entries_validated;
|
||||||
|
state.document_trie_path_nodes_copied += input_stats.trie_path_nodes_copied;
|
||||||
state.pending_baselines.clear();
|
state.pending_baselines.clear();
|
||||||
if let Some(pending) = output.pending {
|
if let Some(pending) = output.pending {
|
||||||
state
|
state
|
||||||
@ -1078,6 +1218,13 @@ impl Session {
|
|||||||
document_parses: state.document_parses,
|
document_parses: state.document_parses,
|
||||||
document_validations: state.document_validations,
|
document_validations: state.document_validations,
|
||||||
document_reuses: state.document_reuses,
|
document_reuses: state.document_reuses,
|
||||||
|
document_full_input_bytes: state.document_full_input_bytes,
|
||||||
|
document_delta_input_bytes: state.document_delta_input_bytes,
|
||||||
|
document_full_nodes_parsed: state.document_full_nodes_parsed,
|
||||||
|
document_delta_entries_parsed: state.document_delta_entries_parsed,
|
||||||
|
document_delta_entries_validated: state.document_delta_entries_validated,
|
||||||
|
document_trie_path_nodes_copied: state.document_trie_path_nodes_copied,
|
||||||
|
document_resolver_lookups: state.document_resolver_lookups,
|
||||||
pending_baselines,
|
pending_baselines,
|
||||||
confirmed_baseline: state.confirmed_baseline.is_some(),
|
confirmed_baseline: state.confirmed_baseline.is_some(),
|
||||||
confirmed_baseline_bytes,
|
confirmed_baseline_bytes,
|
||||||
@ -1129,7 +1276,7 @@ fn parse_control_batch(payload: &[u8]) -> Result<ControlBatch, String> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn checked_layout_context(
|
fn checked_layout_context(
|
||||||
document: &LayoutDocument,
|
document: &LayoutSource,
|
||||||
frame_key: i64,
|
frame_key: i64,
|
||||||
viewport_width: i64,
|
viewport_width: i64,
|
||||||
viewport_width_known: bool,
|
viewport_width_known: bool,
|
||||||
@ -1179,11 +1326,12 @@ fn confirmed_baseline_matches(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn prepare_layout_job(
|
fn prepare_layout_job(
|
||||||
document: &Arc<LayoutDocument>,
|
document: &LayoutSource,
|
||||||
frame: ControlFrame,
|
frame: ControlFrame,
|
||||||
document_base_revision: u64,
|
document_base_revision: u64,
|
||||||
document_target_revision: u64,
|
document_target_revision: u64,
|
||||||
) -> Result<PreparedJob, String> {
|
) -> Result<PreparedJob, String> {
|
||||||
|
layout::reset_resolver_lookups();
|
||||||
if frame.payload.is_some() {
|
if frame.payload.is_some() {
|
||||||
return Err(format!(
|
return Err(format!(
|
||||||
"Native layout frame {} cannot contain an echo payload",
|
"Native layout frame {} cannot contain an echo payload",
|
||||||
@ -1197,7 +1345,7 @@ fn prepare_layout_job(
|
|||||||
.viewport_height
|
.viewport_height
|
||||||
.ok_or_else(|| format!("Native layout frame {} requires viewport-height", frame.key))?;
|
.ok_or_else(|| format!("Native layout frame {} requires viewport-height", frame.key))?;
|
||||||
let context = checked_layout_context(
|
let context = checked_layout_context(
|
||||||
document.as_ref(),
|
document,
|
||||||
frame.key,
|
frame.key,
|
||||||
viewport_width,
|
viewport_width,
|
||||||
frame.viewport_width_known,
|
frame.viewport_width_known,
|
||||||
@ -1223,7 +1371,7 @@ fn prepare_layout_job(
|
|||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
let base_context = checked_layout_context(
|
let base_context = checked_layout_context(
|
||||||
document.as_ref(),
|
document,
|
||||||
frame.key,
|
frame.key,
|
||||||
base_viewport_width,
|
base_viewport_width,
|
||||||
frame.base_viewport_width_known,
|
frame.base_viewport_width_known,
|
||||||
@ -1254,7 +1402,7 @@ fn prepare_layout_job(
|
|||||||
key: frame.key,
|
key: frame.key,
|
||||||
delay_ms: frame.delay_ms,
|
delay_ms: frame.delay_ms,
|
||||||
payload: JobPayload::Layout {
|
payload: JobPayload::Layout {
|
||||||
document: Arc::clone(document),
|
document: document.clone(),
|
||||||
context,
|
context,
|
||||||
root_width,
|
root_width,
|
||||||
root_width_override: frame.root_width_override,
|
root_width_override: frame.root_width_override,
|
||||||
@ -1267,6 +1415,7 @@ fn prepare_layout_job(
|
|||||||
root_metadata: frame.root_metadata,
|
root_metadata: frame.root_metadata,
|
||||||
document_base_revision,
|
document_base_revision,
|
||||||
document_target_revision,
|
document_target_revision,
|
||||||
|
validation_resolver_lookups: layout::resolver_lookups(),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -1287,6 +1436,7 @@ fn render_layout_payload(
|
|||||||
baseline_hit: false,
|
baseline_hit: false,
|
||||||
base_renders: 0,
|
base_renders: 0,
|
||||||
target_renders: 0,
|
target_renders: 0,
|
||||||
|
resolver_lookups: 0,
|
||||||
},
|
},
|
||||||
JobPayload::Layout {
|
JobPayload::Layout {
|
||||||
document,
|
document,
|
||||||
@ -1302,6 +1452,7 @@ fn render_layout_payload(
|
|||||||
root_metadata,
|
root_metadata,
|
||||||
document_base_revision,
|
document_base_revision,
|
||||||
document_target_revision,
|
document_target_revision,
|
||||||
|
validation_resolver_lookups,
|
||||||
} => {
|
} => {
|
||||||
let identity = TapeIdentity {
|
let identity = TapeIdentity {
|
||||||
session_id,
|
session_id,
|
||||||
@ -1331,8 +1482,20 @@ fn render_layout_payload(
|
|||||||
// job with neither result nor error tape, so the Elisp ready
|
// job with neither result nor error tape, so the Elisp ready
|
||||||
// watcher would poll forever. Convert panics into the same
|
// watcher would poll forever. Convert panics into the same
|
||||||
// error-tape channel ordinary layout failures use.
|
// error-tape channel ordinary layout failures use.
|
||||||
type LayoutRenderOutcome = Result<(Vec<u8>, LayoutTape, bool, u64, u64), String>;
|
type LayoutRenderOutcome = Result<
|
||||||
|
(
|
||||||
|
Vec<u8>,
|
||||||
|
LayoutTape,
|
||||||
|
Vec<layout::StyleTemplate>,
|
||||||
|
bool,
|
||||||
|
u64,
|
||||||
|
u64,
|
||||||
|
),
|
||||||
|
String,
|
||||||
|
>;
|
||||||
|
layout::reset_resolver_lookups();
|
||||||
let result = catch_unwind(AssertUnwindSafe(|| -> LayoutRenderOutcome {
|
let result = catch_unwind(AssertUnwindSafe(|| -> LayoutRenderOutcome {
|
||||||
|
let target_styles = document.styles()?;
|
||||||
if let Some(base_context) = base_context {
|
if let Some(base_context) = base_context {
|
||||||
let base_identity = BaselineIdentity {
|
let base_identity = BaselineIdentity {
|
||||||
context: base_context,
|
context: base_context,
|
||||||
@ -1351,7 +1514,7 @@ fn render_layout_payload(
|
|||||||
&base_identity,
|
&base_identity,
|
||||||
) && layout::style_registry_extends_exact_prefix(
|
) && layout::style_registry_extends_exact_prefix(
|
||||||
&baseline.styles,
|
&baseline.styles,
|
||||||
&document.styles,
|
&target_styles,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.cloned();
|
.cloned();
|
||||||
@ -1360,12 +1523,12 @@ fn render_layout_payload(
|
|||||||
.layout_tape(context, root_width_override.then_some(root_width))?;
|
.layout_tape(context, root_width_override.then_some(root_width))?;
|
||||||
let bytes = layout::encode_layout_tape(
|
let bytes = layout::encode_layout_tape(
|
||||||
target.clone(),
|
target.clone(),
|
||||||
&document.styles,
|
&target_styles,
|
||||||
identity,
|
identity,
|
||||||
output.root_metadata,
|
output.root_metadata,
|
||||||
output.max_bytes,
|
output.max_bytes,
|
||||||
)?;
|
)?;
|
||||||
return Ok((bytes, target, false, 0, 1));
|
return Ok((bytes, target, target_styles, false, 0, 1));
|
||||||
}
|
}
|
||||||
let (old, baseline_hit, base_renders) = if let Some(baseline) = base_hit {
|
let (old, baseline_hit, base_renders) = if let Some(baseline) = base_hit {
|
||||||
(baseline.tape.clone(), true, 0)
|
(baseline.tape.clone(), true, 0)
|
||||||
@ -1384,45 +1547,51 @@ fn render_layout_payload(
|
|||||||
let bytes = layout::encode_layout_patch_tape(
|
let bytes = layout::encode_layout_patch_tape(
|
||||||
old,
|
old,
|
||||||
target.clone(),
|
target.clone(),
|
||||||
&document.styles,
|
&target_styles,
|
||||||
identity,
|
identity,
|
||||||
output.root_metadata,
|
output.root_metadata,
|
||||||
output.max_bytes,
|
output.max_bytes,
|
||||||
)?;
|
)?;
|
||||||
Ok((bytes, target, baseline_hit, base_renders, 1))
|
Ok((bytes, target, target_styles, baseline_hit, base_renders, 1))
|
||||||
} else {
|
} else {
|
||||||
let target =
|
let target =
|
||||||
document.layout_tape(context, root_width_override.then_some(root_width))?;
|
document.layout_tape(context, root_width_override.then_some(root_width))?;
|
||||||
let bytes = layout::encode_layout_tape(
|
let bytes = layout::encode_layout_tape(
|
||||||
target.clone(),
|
target.clone(),
|
||||||
&document.styles,
|
&target_styles,
|
||||||
identity,
|
identity,
|
||||||
output.root_metadata,
|
output.root_metadata,
|
||||||
output.max_bytes,
|
output.max_bytes,
|
||||||
)?;
|
)?;
|
||||||
Ok((bytes, target, false, 0, 1))
|
Ok((bytes, target, target_styles, false, 0, 1))
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
let resolver_lookups =
|
||||||
|
validation_resolver_lookups.saturating_add(layout::resolver_lookups());
|
||||||
match result {
|
match result {
|
||||||
Ok(Ok((bytes, tape, baseline_hit, base_renders, target_renders))) => RenderedJob {
|
Ok(Ok((bytes, tape, styles, baseline_hit, base_renders, target_renders))) => {
|
||||||
bytes,
|
RenderedJob {
|
||||||
pending: Some(PendingBaseline {
|
bytes,
|
||||||
confirmed_identity: pending_identity,
|
pending: Some(PendingBaseline {
|
||||||
document: Arc::clone(&document),
|
confirmed_identity: pending_identity,
|
||||||
document_revision: document_target_revision,
|
document: document.clone(),
|
||||||
tape,
|
document_revision: document_target_revision,
|
||||||
styles: document.styles.clone(),
|
tape,
|
||||||
}),
|
styles,
|
||||||
baseline_hit,
|
}),
|
||||||
base_renders,
|
baseline_hit,
|
||||||
target_renders,
|
base_renders,
|
||||||
},
|
target_renders,
|
||||||
|
resolver_lookups,
|
||||||
|
}
|
||||||
|
}
|
||||||
Ok(Err(error)) => RenderedJob {
|
Ok(Err(error)) => RenderedJob {
|
||||||
bytes: encode_error_tape(identity, &error, max_result_bytes),
|
bytes: encode_error_tape(identity, &error, max_result_bytes),
|
||||||
pending: None,
|
pending: None,
|
||||||
baseline_hit: false,
|
baseline_hit: false,
|
||||||
base_renders: 0,
|
base_renders: 0,
|
||||||
target_renders: 0,
|
target_renders: 0,
|
||||||
|
resolver_lookups,
|
||||||
},
|
},
|
||||||
Err(_) => RenderedJob {
|
Err(_) => RenderedJob {
|
||||||
bytes: encode_error_tape(identity, "native layout panicked", max_result_bytes),
|
bytes: encode_error_tape(identity, "native layout panicked", max_result_bytes),
|
||||||
@ -1430,6 +1599,7 @@ fn render_layout_payload(
|
|||||||
baseline_hit: false,
|
baseline_hit: false,
|
||||||
base_renders: 0,
|
base_renders: 0,
|
||||||
target_renders: 0,
|
target_renders: 0,
|
||||||
|
resolver_lookups,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1464,7 +1634,8 @@ fn render_proof(payload: &[u8]) -> Result<Vec<u8>, String> {
|
|||||||
if frame.delay_ms != 0 {
|
if frame.delay_ms != 0 {
|
||||||
return Err("Native proof render cannot contain delay-ms".to_owned());
|
return Err("Native proof render cannot contain delay-ms".to_owned());
|
||||||
}
|
}
|
||||||
let prepared = prepare_layout_job(&Arc::new(document), frame, 0, 1)?;
|
let document = LayoutSource::Full(Arc::new(document));
|
||||||
|
let prepared = prepare_layout_job(&document, frame, 0, 1)?;
|
||||||
let output = render_layout_payload(
|
let output = render_layout_payload(
|
||||||
prepared.payload,
|
prepared.payload,
|
||||||
SYNC_RENDER_SESSION_ID,
|
SYNC_RENDER_SESSION_ID,
|
||||||
@ -1577,6 +1748,7 @@ fn worker_loop(shared: Arc<Shared>) {
|
|||||||
}
|
}
|
||||||
state.base_renders += output.base_renders;
|
state.base_renders += output.base_renders;
|
||||||
state.target_renders += output.target_renders;
|
state.target_renders += output.target_renders;
|
||||||
|
state.document_resolver_lookups += output.resolver_lookups;
|
||||||
state.results.insert(
|
state.results.insert(
|
||||||
(job.generation, job.key),
|
(job.generation, job.key),
|
||||||
ResultEntry {
|
ResultEntry {
|
||||||
@ -1994,6 +2166,14 @@ mod tests {
|
|||||||
.into_bytes()
|
.into_bytes()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn identified_proof_layout_payload(frames: &str) -> Vec<u8> {
|
||||||
|
let mut payload: serde_json::Value =
|
||||||
|
serde_json::from_slice(&proof_layout_payload(frames)).unwrap();
|
||||||
|
payload["document"]["root"]["node-id"] = serde_json::json!(1);
|
||||||
|
payload["document"]["root"]["node-revision"] = serde_json::json!(7);
|
||||||
|
serde_json::to_vec(&payload).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
fn replacement_layout_payload(
|
fn replacement_layout_payload(
|
||||||
document_base_revision: u64,
|
document_base_revision: u64,
|
||||||
document_target_revision: u64,
|
document_target_revision: u64,
|
||||||
@ -2336,6 +2516,79 @@ mod tests {
|
|||||||
grandchild.stop(true);
|
grandchild.stop(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn nonzero_document_delta_renders_and_promotes_without_full_reparse() {
|
||||||
|
let session = Session::new(1, 4, 4, 64 * 1024).unwrap();
|
||||||
|
let first = identified_proof_layout_payload(
|
||||||
|
r#"[{"key":1,"viewport-width":80,"viewport-height":10,"root-width":80,"runtime-revision":0,"context-hash":77}]"#,
|
||||||
|
);
|
||||||
|
let first_tape = session.render_sync(1, &first).unwrap();
|
||||||
|
assert!(!tape_patch_p(&first_tape));
|
||||||
|
assert!(session.confirm(1, 1, 1).unwrap());
|
||||||
|
|
||||||
|
let delta = serde_json::to_vec(&serde_json::json!({
|
||||||
|
"version": 1,
|
||||||
|
"document-base-revision": 1,
|
||||||
|
"document-target-revision": 2,
|
||||||
|
"document-delta": {
|
||||||
|
"style-base-count": 0,
|
||||||
|
"styles-append": [],
|
||||||
|
"property-template-base-count": 0,
|
||||||
|
"property-template-target-count": 0,
|
||||||
|
"entries": [{
|
||||||
|
"node-id": 1,
|
||||||
|
"expected-revision": 7,
|
||||||
|
"target-revision": 21,
|
||||||
|
"slot-patches": [{
|
||||||
|
"slot": 0,
|
||||||
|
"local": {
|
||||||
|
"content": {"lines": [{"clusters": [{
|
||||||
|
"text": "y", "width": 8, "cjk": false, "space": false
|
||||||
|
}]}]}
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
"frames": [{
|
||||||
|
"key": 2,
|
||||||
|
"viewport-width": 80,
|
||||||
|
"viewport-height": 10,
|
||||||
|
"root-width": 80,
|
||||||
|
"patch": true,
|
||||||
|
"base-viewport-width": 80,
|
||||||
|
"base-viewport-height": 10,
|
||||||
|
"base-root-width": 80,
|
||||||
|
"runtime-revision": 1,
|
||||||
|
"context-hash": 77
|
||||||
|
}]
|
||||||
|
}))
|
||||||
|
.unwrap();
|
||||||
|
let delta_tape = session.render_sync(2, &delta).unwrap();
|
||||||
|
assert!(tape_patch_p(&delta_tape));
|
||||||
|
let stats = session.stats();
|
||||||
|
assert_eq!(stats.document_parses, 1);
|
||||||
|
assert_eq!(stats.document_validations, 1);
|
||||||
|
assert_eq!(stats.document_full_nodes_parsed, 1);
|
||||||
|
assert_eq!(stats.document_delta_entries_parsed, 1);
|
||||||
|
assert_eq!(stats.document_delta_entries_validated, 1);
|
||||||
|
assert_eq!(stats.document_trie_path_nodes_copied, 17);
|
||||||
|
assert_eq!(stats.document_delta_input_bytes, delta.len() as u64);
|
||||||
|
assert!(
|
||||||
|
stats.document_resolver_lookups >= 3,
|
||||||
|
"target/base validation and target render lookups must all remain visible"
|
||||||
|
);
|
||||||
|
assert!(session.confirm(2, 2, 2).unwrap());
|
||||||
|
|
||||||
|
let child = session.fork_confirmed().unwrap();
|
||||||
|
let retained = retained_layout_payload(
|
||||||
|
2,
|
||||||
|
r#"[{"key":3,"viewport-width":80,"viewport-height":10,"root-width":80,"runtime-revision":2,"context-hash":77}]"#,
|
||||||
|
);
|
||||||
|
assert!(!child.render_sync(1, &retained).unwrap().is_empty());
|
||||||
|
session.stop(true);
|
||||||
|
child.stop(true);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn retained_document_reuse_rejects_wrong_or_ambiguous_revision() {
|
fn retained_document_reuse_rejects_wrong_or_ambiguous_revision() {
|
||||||
let parent = Session::new(1, 4, 4, 64 * 1024).unwrap();
|
let parent = Session::new(1, 4, 4, 64 * 1024).unwrap();
|
||||||
|
|||||||
@ -2372,6 +2372,14 @@ remain retained identities."
|
|||||||
session initial-state root))
|
session initial-state root))
|
||||||
(next (copy-tree root))
|
(next (copy-tree root))
|
||||||
(next-child (car (ebox-tree-node-children next))))
|
(next-child (car (ebox-tree-node-children next))))
|
||||||
|
(let ((bootstrap-root
|
||||||
|
(plist-get (plist-get initial-package :document) :root)))
|
||||||
|
(should (= (plist-get root :node-id)
|
||||||
|
(plist-get bootstrap-root :node-id)))
|
||||||
|
(should (integerp (plist-get bootstrap-root :node-revision)))
|
||||||
|
;; The Text is fused into its owner and therefore has no addressable
|
||||||
|
;; child entry in the full wire document.
|
||||||
|
(should (eq :null (plist-get bootstrap-root :child))))
|
||||||
(setf (ebox-native-reflow-session-styles session)
|
(setf (ebox-native-reflow-session-styles session)
|
||||||
(plist-get initial-package :styles)
|
(plist-get initial-package :styles)
|
||||||
(ebox-native-reflow-session-layout-package session)
|
(ebox-native-reflow-session-layout-package session)
|
||||||
@ -2567,6 +2575,434 @@ remain retained identities."
|
|||||||
(should (= (plist-get (car controls) :document-base-revision) 10))
|
(should (= (plist-get (car controls) :document-base-revision) 10))
|
||||||
(should (= (plist-get (car controls) :document-target-revision) 11)))))
|
(should (= (plist-get (car controls) :document-target-revision) 11)))))
|
||||||
|
|
||||||
|
(ert-deftest ebox-native-persistent-index-path-copies-without-changing-base ()
|
||||||
|
"A retained index update shares the base and leaves its values immutable."
|
||||||
|
(require 'ebox-native-reflow)
|
||||||
|
(let* ((base (ebox-native-reflow--persistent-index-put nil 1 'one))
|
||||||
|
(next (ebox-native-reflow--persistent-index-put base 17 'seventeen)))
|
||||||
|
(should (eq 'one (ebox-native-reflow--persistent-index-get base 1)))
|
||||||
|
(should-not (ebox-native-reflow--persistent-index-get base 17))
|
||||||
|
(should (eq 'one (ebox-native-reflow--persistent-index-get next 1)))
|
||||||
|
(should (eq 'seventeen
|
||||||
|
(ebox-native-reflow--persistent-index-get next 17)))
|
||||||
|
(should-not (eq base next))))
|
||||||
|
|
||||||
|
(ert-deftest ebox-native-session-fork-shares-immutable-compiler-roots ()
|
||||||
|
"Forking does not clone the retained fragment map or persistent indexes."
|
||||||
|
(require 'ebox-native-reflow)
|
||||||
|
(let* ((cache (make-hash-table :test 'equal))
|
||||||
|
(index (ebox-native-reflow--persistent-index-put nil 1 'entry))
|
||||||
|
(styles (vector 'style))
|
||||||
|
(session
|
||||||
|
(ebox-native-reflow--make-session
|
||||||
|
:handle 'parent :generation 4 :styles styles
|
||||||
|
:layout-package 'package :layout-fragment-cache cache
|
||||||
|
:layout-fragment-index index :layout-fragment-revision 8
|
||||||
|
:layout-style-index index :layout-property-template-index index)))
|
||||||
|
(cl-letf (((symbol-function 'ebox-native--module-fork-confirmed)
|
||||||
|
(lambda (_handle) 'child)))
|
||||||
|
(let ((fork (ebox-native-reflow-fork-session session)))
|
||||||
|
(should (eq cache
|
||||||
|
(ebox-native-reflow-session-layout-fragment-cache fork)))
|
||||||
|
(should (eq index
|
||||||
|
(ebox-native-reflow-session-layout-fragment-index fork)))
|
||||||
|
(should (eq styles (ebox-native-reflow-session-styles fork)))
|
||||||
|
(should (= 8
|
||||||
|
(ebox-native-reflow-session-layout-fragment-revision
|
||||||
|
fork)))))))
|
||||||
|
|
||||||
|
(ert-deftest ebox-native-failed-delta-keeps-fork-and-parent-indexes-unchanged ()
|
||||||
|
"A rejected candidate cannot publish its path-copied compiler index."
|
||||||
|
(require 'ebox-native-reflow)
|
||||||
|
(let* ((cache (make-hash-table :test 'equal))
|
||||||
|
(base-index (ebox-native-reflow--persistent-index-put nil 1 'old))
|
||||||
|
(next-index
|
||||||
|
(ebox-native-reflow--persistent-index-put base-index 1 'new))
|
||||||
|
(old (list :document '(:version 2) :document-revision 4
|
||||||
|
:styles [] :property-templates []))
|
||||||
|
(next (copy-sequence old))
|
||||||
|
(parent
|
||||||
|
(ebox-native-reflow--make-session
|
||||||
|
:handle 'parent :layout-package old :styles []
|
||||||
|
:layout-fragment-cache cache :layout-fragment-index base-index)))
|
||||||
|
(plist-put next :document-revision 5)
|
||||||
|
(plist-put next :document-delta
|
||||||
|
'(:style-base-count 0 :styles-append []
|
||||||
|
:property-template-base-count 0
|
||||||
|
:property-template-target-count 0 :entries []))
|
||||||
|
(plist-put next :native-fragment-index next-index)
|
||||||
|
(cl-letf (((symbol-function 'ebox-native--module-fork-confirmed)
|
||||||
|
(lambda (_handle) 'child)))
|
||||||
|
(let ((fork (ebox-native-reflow-fork-session parent)))
|
||||||
|
(cl-letf (((symbol-function
|
||||||
|
'ebox-native-reflow--compile-retained-layout-package)
|
||||||
|
(lambda (&rest _) next))
|
||||||
|
((symbol-function 'ebox-native--module-render-session-frame)
|
||||||
|
(lambda (&rest _) (error "reject delta"))))
|
||||||
|
(should-error
|
||||||
|
(ebox-native-reflow-execute-session-sync
|
||||||
|
fork 'node '(:key 1 :viewport-width 80 :viewport-height 10)
|
||||||
|
nil 'state))
|
||||||
|
(should (eq base-index
|
||||||
|
(ebox-native-reflow-session-layout-fragment-index fork)))
|
||||||
|
(should (eq base-index
|
||||||
|
(ebox-native-reflow-session-layout-fragment-index parent)))
|
||||||
|
(should (eq cache
|
||||||
|
(ebox-native-reflow-session-layout-fragment-cache fork))))))))
|
||||||
|
|
||||||
|
(ert-deftest ebox-native-accepted-delta-invalidates-stale-full-cache ()
|
||||||
|
"A later full fallback cannot resurrect pre-delta legacy fragments."
|
||||||
|
(require 'ebox-native-reflow)
|
||||||
|
(let* ((cache (make-hash-table :test 'equal))
|
||||||
|
(base-index (ebox-native-reflow--persistent-index-put nil 1 'old))
|
||||||
|
(next-index
|
||||||
|
(ebox-native-reflow--persistent-index-put base-index 1 'new))
|
||||||
|
(old (list :document '(:version 2) :document-revision 4
|
||||||
|
:styles [] :property-templates []))
|
||||||
|
(next (copy-sequence old))
|
||||||
|
(session
|
||||||
|
(ebox-native-reflow--make-session
|
||||||
|
:handle 'test :generation 0 :layout-package old :styles []
|
||||||
|
:layout-fragment-cache cache :layout-fragment-index base-index)))
|
||||||
|
(puthash 1 'stale-fragment cache)
|
||||||
|
(plist-put next :document-revision 5)
|
||||||
|
(plist-put next :document-delta
|
||||||
|
'(:style-base-count 0 :styles-append []
|
||||||
|
:property-template-base-count 0
|
||||||
|
:property-template-target-count 0 :entries []))
|
||||||
|
(plist-put next :native-fragment-index next-index)
|
||||||
|
(plist-put next :native-fragment-revision 9)
|
||||||
|
(cl-letf (((symbol-function
|
||||||
|
'ebox-native-reflow--compile-retained-layout-package)
|
||||||
|
(lambda (&rest _) next))
|
||||||
|
((symbol-function 'ebox-native--module-render-session-frame)
|
||||||
|
(lambda (&rest _) 'native-frame))
|
||||||
|
((symbol-function 'ebox-native-reflow--materialize-module-frame)
|
||||||
|
(lambda (&rest _) '(:rendered "ok"))))
|
||||||
|
(ebox-native-reflow-execute-session-sync
|
||||||
|
session 'node '(:key 1 :viewport-width 80 :viewport-height 10)
|
||||||
|
nil 'state)
|
||||||
|
(should-not
|
||||||
|
(ebox-native-reflow-session-layout-fragment-cache session))
|
||||||
|
(should (eq next-index
|
||||||
|
(ebox-native-reflow-session-layout-fragment-index session)))
|
||||||
|
(should (= 9
|
||||||
|
(ebox-native-reflow-session-layout-fragment-revision
|
||||||
|
session)))
|
||||||
|
;; Model the next unsupported B update. The current source contains A's
|
||||||
|
;; accepted value while the persistent index is deliberately stale; the
|
||||||
|
;; invalidated legacy cache forces a complete compile from current state.
|
||||||
|
(let* ((input
|
||||||
|
(ebox-test-box (ebox-test-text "A") :bgcolor "#00ff00"))
|
||||||
|
(root (ebox-test-root input))
|
||||||
|
(_ids (ebox--runtime-node-ids root))
|
||||||
|
(full
|
||||||
|
(ebox-native-reflow--compile-retained-layout-package-full
|
||||||
|
session
|
||||||
|
(list :native-node-postorder
|
||||||
|
(ebox-native-reflow--retained-layout-postorder root)
|
||||||
|
:native-topology-stable-p nil
|
||||||
|
:source-index (ebox-test-source-index input))
|
||||||
|
root))
|
||||||
|
(document-root (plist-get (plist-get full :document) :root))
|
||||||
|
(style-id (plist-get document-root :background-style)))
|
||||||
|
(should
|
||||||
|
(equal '(:background "#00ff00")
|
||||||
|
(plist-get (aref (plist-get full :styles) style-id)
|
||||||
|
:face)))))))
|
||||||
|
|
||||||
|
(ert-deftest ebox-native-node-delta-is-local-and-bumps-ancestors ()
|
||||||
|
"A local change patches one owner and only revises its retained ancestor."
|
||||||
|
(require 'ebox-native-reflow)
|
||||||
|
(let* ((leaf-fragment
|
||||||
|
'(:type "box" :background-style :null :child :null
|
||||||
|
:node-id 2 :node-revision 3))
|
||||||
|
(root-fragment
|
||||||
|
(list :type "box" :background-style :null :child leaf-fragment
|
||||||
|
:node-id 1 :node-revision 4))
|
||||||
|
(leaf-entry (list :fragment leaf-fragment :revision 3))
|
||||||
|
(root-entry (list :fragment root-fragment :revision 4))
|
||||||
|
(index (ebox-native-reflow--persistent-index-put nil 1 root-entry))
|
||||||
|
(_index (setq index
|
||||||
|
(ebox-native-reflow--persistent-index-put
|
||||||
|
index 2 leaf-entry)))
|
||||||
|
(style-index
|
||||||
|
(ebox-native-reflow--persistent-index-put
|
||||||
|
nil '(:mode add :face (:background "red")) 0))
|
||||||
|
(package
|
||||||
|
(list :document '(:version 2) :document-revision 7
|
||||||
|
:styles [] :property-templates []))
|
||||||
|
(session
|
||||||
|
(ebox-native-reflow--make-session
|
||||||
|
:handle 'test :generation 0 :styles [] :layout-package package
|
||||||
|
:layout-fragment-index index :layout-fragment-revision 4
|
||||||
|
:layout-style-index style-index))
|
||||||
|
(nodes (make-hash-table :test 'equal))
|
||||||
|
(parents (make-hash-table :test 'equal))
|
||||||
|
(state
|
||||||
|
(list :node-table nodes :parent-table parents
|
||||||
|
:native-topology-stable-p t
|
||||||
|
:native-touched-node-ids '(2 1)
|
||||||
|
:native-local-dirty-entries
|
||||||
|
'((:node-id 2 :dirty-kind paint
|
||||||
|
:changed-keys (:background-color))))))
|
||||||
|
(puthash 1 '(:node-id 1) nodes)
|
||||||
|
(puthash 2 '(:node-id 2) nodes)
|
||||||
|
(puthash 2 1 parents)
|
||||||
|
(cl-letf (((symbol-function 'ebox--current-display-signature)
|
||||||
|
(lambda () 'display))
|
||||||
|
((symbol-function 'ebox-native-reflow--compile-delta-slots)
|
||||||
|
(lambda (_node _old)
|
||||||
|
(vector
|
||||||
|
'(:type "box" :background-style 0 :child :null)))))
|
||||||
|
(let* ((next
|
||||||
|
(ebox-native-reflow--compile-retained-layout-delta
|
||||||
|
session state 'root))
|
||||||
|
(delta (plist-get next :document-delta))
|
||||||
|
(entries (plist-get delta :entries))
|
||||||
|
(leaf (aref entries 0))
|
||||||
|
(root (aref entries 1)))
|
||||||
|
(should (= 8 (plist-get next :document-revision)))
|
||||||
|
(should (= 0 (plist-get delta :style-base-count)))
|
||||||
|
(should (= 2 (length entries)))
|
||||||
|
(should (equal [(:slot 0 :local (:background-style 0))]
|
||||||
|
(plist-get leaf :slot-patches)))
|
||||||
|
(should-not (plist-member root :slot-patches))
|
||||||
|
(should (= 5 (plist-get leaf :target-revision)))
|
||||||
|
(should (= 6 (plist-get root :target-revision)))))))
|
||||||
|
|
||||||
|
(ert-deftest ebox-native-node-delta-deduplicates-multiple-leaf-closures ()
|
||||||
|
"Multiple changed leaves produce one entry each and one shared ancestor."
|
||||||
|
(require 'ebox-native-reflow)
|
||||||
|
(let ((index nil)
|
||||||
|
(nodes (make-hash-table :test 'equal))
|
||||||
|
(parents (make-hash-table :test 'equal)))
|
||||||
|
(dolist (pair '((1 . 3) (2 . 1) (3 . 2)))
|
||||||
|
(setq index
|
||||||
|
(ebox-native-reflow--persistent-index-put
|
||||||
|
index (car pair)
|
||||||
|
(list :fragment
|
||||||
|
'(:type "box" :background-style :null :child :null)
|
||||||
|
:revision (cdr pair))))
|
||||||
|
(puthash (car pair) (list :node-id (car pair)) nodes))
|
||||||
|
(puthash 2 1 parents)
|
||||||
|
(puthash 3 1 parents)
|
||||||
|
(let* ((package (list :document '(:version 2) :document-revision 2
|
||||||
|
:styles [] :property-templates []))
|
||||||
|
(session
|
||||||
|
(ebox-native-reflow--make-session
|
||||||
|
:handle 'test :layout-package package
|
||||||
|
:layout-fragment-index index :layout-fragment-revision 3))
|
||||||
|
(state
|
||||||
|
(list :node-table nodes :parent-table parents
|
||||||
|
:native-topology-stable-p t
|
||||||
|
:native-touched-node-ids '(2 1 3 1)
|
||||||
|
:native-local-dirty-entries
|
||||||
|
'((:node-id 2 :changed-keys (:background-color))
|
||||||
|
(:node-id 3 :changed-keys (:background-color))))))
|
||||||
|
(cl-letf (((symbol-function 'ebox--current-display-signature)
|
||||||
|
(lambda () 'display))
|
||||||
|
((symbol-function 'ebox-native-reflow--compile-delta-slots)
|
||||||
|
(lambda (node _old)
|
||||||
|
(vector
|
||||||
|
(list :type "box" :background-style
|
||||||
|
(plist-get node :node-id) :child :null)))))
|
||||||
|
(let* ((next
|
||||||
|
(ebox-native-reflow--compile-retained-layout-delta
|
||||||
|
session state 'root))
|
||||||
|
(entries
|
||||||
|
(plist-get (plist-get next :document-delta) :entries)))
|
||||||
|
(should (= 3 (length entries)))
|
||||||
|
(should (equal '(2 1 3)
|
||||||
|
(mapcar (lambda (entry)
|
||||||
|
(plist-get entry :node-id))
|
||||||
|
(append entries nil))))
|
||||||
|
(should (plist-member (aref entries 0) :slot-patches))
|
||||||
|
(should-not (plist-member (aref entries 1) :slot-patches))
|
||||||
|
(should (plist-member (aref entries 2) :slot-patches)))))))
|
||||||
|
|
||||||
|
(ert-deftest ebox-native-fused-text-delta-resolves-to-box-owner ()
|
||||||
|
"A fused text id addresses its containing Box rather than a hidden node."
|
||||||
|
(require 'ebox-native-reflow)
|
||||||
|
(let ((nodes (make-hash-table :test 'equal))
|
||||||
|
(parents (make-hash-table :test 'equal))
|
||||||
|
(index (ebox-native-reflow--persistent-index-put nil 10 'owner)))
|
||||||
|
(puthash 10
|
||||||
|
(list :node-id 10 :ebox-kind 'box
|
||||||
|
:ebox-layout-config
|
||||||
|
(ebox-normal-layout-create))
|
||||||
|
nodes)
|
||||||
|
(puthash 11 '(:node-id 11 :ebox-kind text) nodes)
|
||||||
|
(puthash 11 10 parents)
|
||||||
|
(should (= 10
|
||||||
|
(ebox-native-reflow--delta-owner-id
|
||||||
|
(list :node-table nodes :parent-table parents) 11 index)))))
|
||||||
|
|
||||||
|
(ert-deftest ebox-native-flex-item-metadata-change-uses-full-input ()
|
||||||
|
"N1 does not misrepresent Flex item metadata as a local scalar patch."
|
||||||
|
(require 'ebox-native-reflow)
|
||||||
|
(let ((session
|
||||||
|
(ebox-native-reflow--make-session
|
||||||
|
:handle 'test :layout-package 'old
|
||||||
|
:layout-fragment-index
|
||||||
|
(ebox-native-reflow--persistent-index-put
|
||||||
|
nil 1 '(:fragment (:type "box" :child :null) :revision 1))))
|
||||||
|
(state
|
||||||
|
'(:native-topology-stable-p t :native-touched-node-ids (1)
|
||||||
|
:native-local-dirty-entries
|
||||||
|
((:node-id 1 :dirty-kind geometry
|
||||||
|
:changed-keys (:flex-grow)))))
|
||||||
|
full-called)
|
||||||
|
(cl-letf (((symbol-function
|
||||||
|
'ebox-native-reflow--compile-retained-layout-package-full)
|
||||||
|
(lambda (&rest _)
|
||||||
|
(setq full-called t)
|
||||||
|
'full))
|
||||||
|
((symbol-function 'ebox-native-reflow--compile-delta-slots)
|
||||||
|
(lambda (&rest _)
|
||||||
|
(ert-fail "Flex item metadata reached local delta"))))
|
||||||
|
(should (eq 'full
|
||||||
|
(ebox-native-reflow--compile-retained-layout-package
|
||||||
|
session state 'node)))
|
||||||
|
(should full-called))))
|
||||||
|
|
||||||
|
(ert-deftest ebox-native-flex-child-content-change-uses-full-input ()
|
||||||
|
"Child content may alter retained Flex edge measurement in N1."
|
||||||
|
(require 'ebox-native-reflow)
|
||||||
|
(let ((nodes (make-hash-table :test 'equal))
|
||||||
|
(parents (make-hash-table :test 'equal))
|
||||||
|
(index
|
||||||
|
(ebox-native-reflow--persistent-index-put
|
||||||
|
nil 2 '(:fragment (:type "box" :content [] :child :null)
|
||||||
|
:revision 3)))
|
||||||
|
full-called)
|
||||||
|
(puthash 1 '(:node-id 1 :ebox-type flex) nodes)
|
||||||
|
(puthash 2
|
||||||
|
(list :node-id 2 :ebox-type 'box :ebox-kind 'box
|
||||||
|
:ebox-layout-config (ebox-normal-layout-create))
|
||||||
|
nodes)
|
||||||
|
(puthash 3 '(:node-id 3 :ebox-type box :ebox-kind text) nodes)
|
||||||
|
(puthash 2 1 parents)
|
||||||
|
(puthash 3 2 parents)
|
||||||
|
(let ((session
|
||||||
|
(ebox-native-reflow--make-session
|
||||||
|
:handle 'test :layout-package 'old
|
||||||
|
:layout-fragment-index index))
|
||||||
|
(state
|
||||||
|
(list :node-table nodes :parent-table parents
|
||||||
|
:native-topology-stable-p t
|
||||||
|
:native-touched-node-ids '(3 2 1)
|
||||||
|
:native-local-dirty-entries
|
||||||
|
'((:node-id 3 :dirty-kind content
|
||||||
|
:changed-keys (:content))))))
|
||||||
|
(cl-letf (((symbol-function
|
||||||
|
'ebox-native-reflow--compile-retained-layout-package-full)
|
||||||
|
(lambda (&rest _)
|
||||||
|
(setq full-called t)
|
||||||
|
'full))
|
||||||
|
((symbol-function 'ebox-native-reflow--compile-delta-slots)
|
||||||
|
(lambda (&rest _)
|
||||||
|
(ert-fail "Flex child content reached local delta"))))
|
||||||
|
(should (eq 'full
|
||||||
|
(ebox-native-reflow--compile-retained-layout-package
|
||||||
|
session state 'node)))
|
||||||
|
(should full-called)))))
|
||||||
|
|
||||||
|
(ert-deftest ebox-native-default-axis-child-change-keeps-local-input ()
|
||||||
|
"Default Row/Column edges carry no derived Flex item metadata."
|
||||||
|
(require 'ebox-native-reflow)
|
||||||
|
(let ((nodes (make-hash-table :test 'equal))
|
||||||
|
(parents (make-hash-table :test 'equal)))
|
||||||
|
(puthash 1
|
||||||
|
(list :node-id 1 :ebox-type 'box :ebox-kind 'box
|
||||||
|
:ebox-layout-config (ebox-column-layout-create))
|
||||||
|
nodes)
|
||||||
|
(puthash 2
|
||||||
|
(list :node-id 2 :ebox-type 'box :ebox-kind 'box
|
||||||
|
:ebox-layout-config (ebox-normal-layout-create))
|
||||||
|
nodes)
|
||||||
|
(puthash 3 '(:node-id 3 :ebox-type box :ebox-kind text) nodes)
|
||||||
|
(puthash 2 1 parents)
|
||||||
|
(puthash 3 2 parents)
|
||||||
|
(should-not
|
||||||
|
(ebox-native-reflow--delta-flex-edge-change-p
|
||||||
|
(list :node-table nodes :parent-table parents
|
||||||
|
:native-local-dirty-entries
|
||||||
|
'((:node-id 3 :dirty-kind geometry
|
||||||
|
:changed-keys (:content))))))))
|
||||||
|
|
||||||
|
(ert-deftest ebox-native-surface-overrides-carry-local-dirty-entries ()
|
||||||
|
"The incremental producer preserves exact local dirtiness to native input."
|
||||||
|
(let* ((dirty '((:node-id 7 :dirty-kind paint
|
||||||
|
:changed-keys (:background-color))))
|
||||||
|
(candidate (list :runtime-revision 3
|
||||||
|
:native-topology-stable-p t
|
||||||
|
:native-touched-node-ids '(7 1)
|
||||||
|
:native-local-dirty-entries dirty))
|
||||||
|
(overrides
|
||||||
|
(ebox-incremental--surface-state-overrides
|
||||||
|
nil '(:display-signature display) candidate 'native-frame)))
|
||||||
|
(should (equal dirty
|
||||||
|
(plist-get overrides :native-local-dirty-entries)))))
|
||||||
|
|
||||||
|
(ert-deftest ebox-native-wide-owner-local-compile-does-not-enumerate-children ()
|
||||||
|
"A scalar slot update never walks a stable owner's unchanged children."
|
||||||
|
(require 'ebox-native-reflow)
|
||||||
|
(let ((node (list :ebox-type 'box :ebox-kind 'box
|
||||||
|
:ebox-layout-config (ebox-flex-layout-create)))
|
||||||
|
(old '(:type "box" :child (:type "flex" :items [a b c]))))
|
||||||
|
(cl-letf (((symbol-function 'ebox-tree-node-children)
|
||||||
|
(lambda (&rest _)
|
||||||
|
(ert-fail "stable delta enumerated unchanged children")))
|
||||||
|
((symbol-function 'ebox-native-reflow--compile-flex-inner)
|
||||||
|
(lambda (_props items &rest _)
|
||||||
|
(should-not items)
|
||||||
|
'(:type "flex" :items [])))
|
||||||
|
((symbol-function 'ebox-native-reflow--compile-box)
|
||||||
|
(lambda (_box child &rest _)
|
||||||
|
(list :type "box" :child child))))
|
||||||
|
(let ((slots (ebox-native-reflow--compile-delta-slots node old)))
|
||||||
|
(should (= 2 (length slots)))
|
||||||
|
(should (equal [] (plist-get (aref slots 1) :items)))))))
|
||||||
|
|
||||||
|
(ert-deftest ebox-native-retained-sync-sends-document-delta-without-document ()
|
||||||
|
"A supported retained compile sends only its node delta and revisions."
|
||||||
|
(require 'ebox-native-reflow)
|
||||||
|
(let* ((old (list :document '(:version 2) :document-revision 4
|
||||||
|
:styles [] :property-templates []))
|
||||||
|
(delta '(:style-base-count 0 :styles-append []
|
||||||
|
:property-template-base-count 0
|
||||||
|
:property-template-target-count 0 :entries []))
|
||||||
|
(next (copy-sequence old))
|
||||||
|
(session
|
||||||
|
(ebox-native-reflow--make-session
|
||||||
|
:handle 'test :generation 0 :styles [] :layout-package old))
|
||||||
|
control)
|
||||||
|
(plist-put next :document-revision 5)
|
||||||
|
(plist-put next :document-delta delta)
|
||||||
|
(cl-letf (((symbol-function
|
||||||
|
'ebox-native-reflow--compile-retained-layout-package)
|
||||||
|
(lambda (&rest _) next))
|
||||||
|
((symbol-function 'ebox-native--module-render-session-frame)
|
||||||
|
(lambda (_handle _generation payload)
|
||||||
|
(setq control
|
||||||
|
(json-parse-string payload :object-type 'plist
|
||||||
|
:array-type 'array))
|
||||||
|
'native-frame))
|
||||||
|
((symbol-function 'ebox-native-reflow--materialize-module-frame)
|
||||||
|
(lambda (&rest _) '(:rendered "ok"))))
|
||||||
|
(ebox-native-reflow-execute-session-sync
|
||||||
|
session 'node
|
||||||
|
'(:key 1 :viewport-width 80 :viewport-height 10) nil 'state)
|
||||||
|
(should-not (plist-member control :document))
|
||||||
|
(should (equal delta (plist-get control :document-delta)))
|
||||||
|
(should (= 4 (plist-get control :document-base-revision)))
|
||||||
|
(should (= 5 (plist-get control :document-target-revision)))
|
||||||
|
(should-not
|
||||||
|
(plist-member
|
||||||
|
(ebox-native-reflow-session-layout-package session)
|
||||||
|
:document-delta)))))
|
||||||
|
|
||||||
(provide 'ebox-commit-tests)
|
(provide 'ebox-commit-tests)
|
||||||
|
|
||||||
;;; ebox-commit-tests.el ends here
|
;;; ebox-commit-tests.el ends here
|
||||||
|
|||||||
@ -411,6 +411,55 @@
|
|||||||
:authority ebox :lifetime candidate-until-final-accept
|
:authority ebox :lifetime candidate-until-final-accept
|
||||||
:rollback private-session-discard :rebuild native-frame-stage
|
:rollback private-session-discard :rebuild native-frame-stage
|
||||||
:cleanup confirm-or-rollback)
|
:cleanup confirm-or-rollback)
|
||||||
|
(:id candidate/native-local-dirty-entries
|
||||||
|
:storage (:state-key :native-local-dirty-entries)
|
||||||
|
:proposed-category generation-fact :owner ebox-native-commit
|
||||||
|
:authority ebox :lifetime candidate-until-native-input
|
||||||
|
:rollback candidate-discard :rebuild prepared-dirty-set
|
||||||
|
:cleanup native-frame-consume-or-downgrade)
|
||||||
|
(:id native-session/layout-fragment-index
|
||||||
|
:storage (:struct-slot ebox-native-reflow-session layout-fragment-index)
|
||||||
|
:proposed-category generation-bound-mutable :owner ebox-native-reflow
|
||||||
|
:authority ebox :lifetime private-native-session
|
||||||
|
:rollback private-session-discard :rebuild full-bootstrap-or-path-copy
|
||||||
|
:cleanup native-session-release)
|
||||||
|
(:id native-session/layout-style-index
|
||||||
|
:storage (:struct-slot ebox-native-reflow-session layout-style-index)
|
||||||
|
:proposed-category generation-bound-mutable :owner ebox-native-reflow
|
||||||
|
:authority ebox :lifetime private-native-session
|
||||||
|
:rollback private-session-discard :rebuild full-bootstrap-or-style-append
|
||||||
|
:cleanup native-session-release)
|
||||||
|
(:id native-session/layout-property-template-index
|
||||||
|
:storage
|
||||||
|
(:struct-slot ebox-native-reflow-session layout-property-template-index)
|
||||||
|
:proposed-category generation-bound-mutable :owner ebox-native-reflow
|
||||||
|
:authority ebox :lifetime private-native-session
|
||||||
|
:rollback private-session-discard :rebuild full-bootstrap
|
||||||
|
:cleanup native-session-release)
|
||||||
|
(:id native-compile/style-index
|
||||||
|
:storage (:global ebox-native-reflow--compile-style-index)
|
||||||
|
:proposed-category generation-fact :owner ebox-native-reflow
|
||||||
|
:authority ebox :lifetime dynamic-native-compile
|
||||||
|
:rollback dynamic-binding-unwind :rebuild retained-session-root
|
||||||
|
:cleanup dynamic-binding-unwind)
|
||||||
|
(:id native-compile/property-template-index
|
||||||
|
:storage (:global ebox-native-reflow--compile-property-template-index)
|
||||||
|
:proposed-category generation-fact :owner ebox-native-reflow
|
||||||
|
:authority ebox :lifetime dynamic-native-compile
|
||||||
|
:rollback dynamic-binding-unwind :rebuild retained-session-root
|
||||||
|
:cleanup dynamic-binding-unwind)
|
||||||
|
(:id native-package/fragment-index
|
||||||
|
:storage (:state-key :native-fragment-index)
|
||||||
|
:proposed-category generation-fact :owner ebox-native-reflow
|
||||||
|
:authority ebox :lifetime native-call-until-success
|
||||||
|
:rollback package-discard :rebuild path-copied-session-root
|
||||||
|
:cleanup strip-from-confirmed-package)
|
||||||
|
(:id native-package/style-index
|
||||||
|
:storage (:state-key :native-style-index)
|
||||||
|
:proposed-category generation-fact :owner ebox-native-reflow
|
||||||
|
:authority ebox :lifetime native-call-until-success
|
||||||
|
:rollback package-discard :rebuild style-append-path-copy
|
||||||
|
:cleanup strip-from-confirmed-package)
|
||||||
(:id process/scroll-global-state
|
(:id process/scroll-global-state
|
||||||
:storage (:global ebox--scroll-global-state)
|
:storage (:global ebox--scroll-global-state)
|
||||||
:proposed-category generation-bound-mutable :owner ebox-incremental
|
:proposed-category generation-bound-mutable :owner ebox-incremental
|
||||||
|
|||||||
@ -466,18 +466,20 @@ exclusion freshness, but only retained/container evidence is coverage-gated."
|
|||||||
'generation-bound-mutable-authority))))
|
'generation-bound-mutable-authority))))
|
||||||
|
|
||||||
(ert-deftest ebox-state-contract-excludes-native-compile-scratch-from-authority ()
|
(ert-deftest ebox-state-contract-excludes-native-compile-scratch-from-authority ()
|
||||||
"Compile-local template ids are not mislabeled as native session authority."
|
"Compile-local indexes are not mislabeled as native session authority."
|
||||||
(should-not
|
(let ((authority-storage
|
||||||
(memq 'ebox-native-reflow--compile-property-template-ids
|
|
||||||
(plist-get (ebox-state-contract-record 'native-runtime-authority)
|
(plist-get (ebox-state-contract-record 'native-runtime-authority)
|
||||||
:storage)))
|
:storage)))
|
||||||
(let ((exclusion
|
(dolist (symbol '(ebox-native-reflow--compile-property-template-ids
|
||||||
(cl-find 'ebox-native-reflow--compile-property-template-ids
|
ebox-native-reflow--compile-property-template-index
|
||||||
ebox-state-contract-source-scan-exclusions
|
ebox-native-reflow--compile-style-index))
|
||||||
:key (lambda (entry) (plist-get entry :symbol)))))
|
(should-not (memq symbol authority-storage))
|
||||||
(should exclusion)
|
(let ((exclusion
|
||||||
(should (eq (plist-get exclusion :reason)
|
(cl-find symbol ebox-state-contract-source-scan-exclusions
|
||||||
'dynamically-bound-compile-local-scratch))))
|
:key (lambda (entry) (plist-get entry :symbol)))))
|
||||||
|
(should exclusion)
|
||||||
|
(should (eq (plist-get exclusion :reason)
|
||||||
|
'dynamically-bound-compile-local-scratch))))))
|
||||||
|
|
||||||
(ert-deftest ebox-state-contract-tp-custody-is-opaque ()
|
(ert-deftest ebox-state-contract-tp-custody-is-opaque ()
|
||||||
"The inventory separates current whole-state custody from its M2a target."
|
"The inventory separates current whole-state custody from its M2a target."
|
||||||
|
|||||||
@ -1656,17 +1656,25 @@ candidate cannot hide mutations by restoring the old hash-table pointer."
|
|||||||
(plist-get (tp-surface-client-state surface)
|
(plist-get (tp-surface-client-state surface)
|
||||||
:native-sync-session))
|
:native-sync-session))
|
||||||
(should (eq (plist-get report :projection-kind) 'native-frame))
|
(should (eq (plist-get report :projection-kind) 'native-frame))
|
||||||
;; A style-table change cannot consume the prior confirmed
|
;; A root paint delta and appended styles preserve the old
|
||||||
;; document and therefore remains a truthful full frame.
|
;; registry prefix and publish against the confirmed tape.
|
||||||
(should (eq (plist-get report :native-frame-kind) 'full))
|
(should (eq (plist-get report :native-frame-kind) 'patch))
|
||||||
(should next-session)
|
(should next-session)
|
||||||
(should-not (eq next-session winning-session))
|
(should-not (eq next-session winning-session))
|
||||||
(should (ebox-native-reflow-session-released-p winning-session))
|
(should (ebox-native-reflow-session-released-p winning-session))
|
||||||
(should-not (ebox-native-reflow-session-released-p
|
(should-not (ebox-native-reflow-session-released-p
|
||||||
next-session))
|
next-session))
|
||||||
|
(should
|
||||||
|
(equal-including-properties
|
||||||
|
(with-current-buffer buffer
|
||||||
|
(buffer-substring (point-min) (point-max)))
|
||||||
|
(ebox-surface-test--render-runtime
|
||||||
|
(tp-surface-client-state surface))))
|
||||||
(let ((stats (ebox-native-reflow-stats next-session)))
|
(let ((stats (ebox-native-reflow-stats next-session)))
|
||||||
(should (= (plist-get stats :document-parses) 1))
|
(should (= (plist-get stats :document-parses) 0))
|
||||||
(should (= (plist-get stats :document-validations) 1))
|
(should (= (plist-get stats :document-validations) 0))
|
||||||
|
(should (= (plist-get stats :document-delta-entries-parsed) 1))
|
||||||
|
(should (= (plist-get stats :document-delta-entries-validated) 1))
|
||||||
(should (= (plist-get stats :document-reuses) 0))))))
|
(should (= (plist-get stats :document-reuses) 0))))))
|
||||||
(when (buffer-live-p buffer)
|
(when (buffer-live-p buffer)
|
||||||
(kill-buffer buffer))
|
(kill-buffer buffer))
|
||||||
@ -1725,6 +1733,108 @@ candidate cannot hide mutations by restoring the old hash-table pointer."
|
|||||||
(when (buffer-live-p buffer)
|
(when (buffer-live-p buffer)
|
||||||
(kill-buffer buffer)))))
|
(kill-buffer buffer)))))
|
||||||
|
|
||||||
|
(ert-deftest ebox-native-node-delta-survives-viewport-and-repeated-commits ()
|
||||||
|
"Real local commits preserve earlier edits across viewport document reuse."
|
||||||
|
(skip-unless (ebox-native-reflow-layout-ready-p))
|
||||||
|
(ebox-surface-test--reset-render-state)
|
||||||
|
(let ((buffer (generate-new-buffer " *ebox-native-delta-history*"))
|
||||||
|
(ebox-viewport-width 320)
|
||||||
|
(ebox-viewport-height 40)
|
||||||
|
(ebox-runtime-idle-prewarm nil)
|
||||||
|
(ebox-runtime-idle-reflow-cache-prewarm nil))
|
||||||
|
(unwind-protect
|
||||||
|
(cl-labels
|
||||||
|
((item
|
||||||
|
(index text)
|
||||||
|
(ebox-test-box
|
||||||
|
:key index :source-identity index :width '(8) :height 1
|
||||||
|
(ebox-test-text text :key (+ 100 index)
|
||||||
|
:source-identity (+ 100 index))))
|
||||||
|
(source
|
||||||
|
(count)
|
||||||
|
(apply #'ebox-test-column
|
||||||
|
(append (list :key 'delta-history
|
||||||
|
:source-identity 'delta-history
|
||||||
|
:width '(viewport))
|
||||||
|
(cl-loop for index below count
|
||||||
|
collect (item index "A")))))
|
||||||
|
(ids
|
||||||
|
(state)
|
||||||
|
(let (result)
|
||||||
|
(ebox-surface-test--walk-runtime
|
||||||
|
(plist-get state :root-node)
|
||||||
|
(lambda (node) (push (plist-get node :node-id) result)))
|
||||||
|
(nreverse result))))
|
||||||
|
(cl-letf (((symbol-function 'ebox-native-reflow-layout-ready-p)
|
||||||
|
(lambda () nil)))
|
||||||
|
(ebox-render-to-buffer buffer (source 15)))
|
||||||
|
(ebox-commit buffer (source 16))
|
||||||
|
(let* ((surface
|
||||||
|
(with-current-buffer buffer ebox-surface--buffer-surface))
|
||||||
|
(baseline-ids (ids (tp-surface-client-state surface)))
|
||||||
|
(original-send
|
||||||
|
(symbol-function 'ebox-native--module-render-session-frame))
|
||||||
|
(expected-texts (make-vector 16 "A"))
|
||||||
|
previous-document-revision events)
|
||||||
|
(should (plist-get (tp-surface-client-state surface)
|
||||||
|
:native-sync-confirmed-p))
|
||||||
|
(cl-letf
|
||||||
|
(((symbol-function 'ebox-native--module-render-session-frame)
|
||||||
|
(lambda (handle generation control)
|
||||||
|
(push (json-parse-string control :object-type 'plist
|
||||||
|
:array-type 'array)
|
||||||
|
events)
|
||||||
|
(funcall original-send handle generation control))))
|
||||||
|
(dolist (step '((4 "B") viewport (12 "C") (4 "D")))
|
||||||
|
(setq events nil)
|
||||||
|
(if (eq step 'viewport)
|
||||||
|
(progn
|
||||||
|
(setq ebox-viewport-width 360)
|
||||||
|
(ebox-rerender-buffer-with-context buffer 360 40))
|
||||||
|
(let ((candidate (ebox-candidate-begin buffer)))
|
||||||
|
(ebox-candidate-replace-host-ref
|
||||||
|
candidate (car step) (item (car step) (cadr step)))
|
||||||
|
(ebox-commit buffer candidate)
|
||||||
|
(aset expected-texts (car step) (cadr step))))
|
||||||
|
(ert-info ((format "Native delta history step %S" step))
|
||||||
|
(should (= 1 (length events))))
|
||||||
|
(let* ((control (car events))
|
||||||
|
(base (plist-get control :document-base-revision))
|
||||||
|
(target (plist-get control :document-target-revision))
|
||||||
|
(state (tp-surface-client-state surface))
|
||||||
|
(session (plist-get state :native-sync-session)))
|
||||||
|
(should-not (plist-get control :document))
|
||||||
|
(when previous-document-revision
|
||||||
|
(should (= base previous-document-revision)))
|
||||||
|
(if (eq step 'viewport)
|
||||||
|
(progn
|
||||||
|
(should-not (plist-get control :document-delta))
|
||||||
|
(should (= base target)))
|
||||||
|
(should (plist-get control :document-delta))
|
||||||
|
(should (= (1+ base) target)))
|
||||||
|
(setq previous-document-revision target)
|
||||||
|
(should (equal baseline-ids (ids state)))
|
||||||
|
(should
|
||||||
|
(equal (mapconcat #'identity expected-texts "")
|
||||||
|
(with-current-buffer buffer
|
||||||
|
(replace-regexp-in-string
|
||||||
|
"[[:space:]]" ""
|
||||||
|
(buffer-substring-no-properties
|
||||||
|
(point-min) (point-max))))))
|
||||||
|
(should (plist-get state :native-sync-confirmed-p))
|
||||||
|
(should-not (plist-get state :native-sync-pending))
|
||||||
|
(should-not
|
||||||
|
(plist-get
|
||||||
|
(ebox-native-reflow-session-layout-package session)
|
||||||
|
:document-delta))
|
||||||
|
(should
|
||||||
|
(equal-including-properties
|
||||||
|
(with-current-buffer buffer
|
||||||
|
(buffer-substring (point-min) (point-max)))
|
||||||
|
(ebox-surface-test--render-runtime state))))))))
|
||||||
|
(when (buffer-live-p buffer)
|
||||||
|
(kill-buffer buffer)))))
|
||||||
|
|
||||||
(ert-deftest ebox-viewport-reflow-retains-final-sized-flex-child-fragments ()
|
(ert-deftest ebox-viewport-reflow-retains-final-sized-flex-child-fragments ()
|
||||||
"Viewport reflow should reuse final-sized Flex child fragments exactly."
|
"Viewport reflow should reuse final-sized Flex child fragments exactly."
|
||||||
(ebox-surface-test--reset-render-state)
|
(ebox-surface-test--reset-render-state)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user