1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
//! Cold undo-tree node jump cost vs history depth — the regression guard for
//! the "keyframe materialization" item of issue #302.
//!
//! Storage (`src/undo.rs`, Phase 3a): the root holds a full base rope, every
//! other edge holds a reversible `Delta`, and a node's content is reconstructed
//! on demand behind a bounded warm LRU. `u` / `<C-r>` have an adjacent-node fast
//! path (one inverse delta apply), but `g-` / `g+` (`seq_earlier_step` /
//! `seq_later_step`) go through `entry_of` → `materialize`, which walks UP to
//! the nearest ancestor holding content and replays forward deltas. Before
//! keyframes that ancestor could be the root, making one jump O(depth) and a
//! full walk O(depth²); pinning a materialized rope every `KEYFRAME_INTERVAL`
//! nodes bounds a jump at O(K) and — since `materialize` also caches the
//! intermediates it replays — a full walk at O(N).
//!
//! The two benches below measure the two shapes that matter:
//!
//! - `cold_jump_back` — hold `g-` / `:earlier 9999`: walk tip → root.
//! - `single_deep_jump` — ONE `g-` deep inside the history (see [`WALKBACK`]
//! for what that isolates before and after keyframes).
//!
//! Both are parameterized over history depth N so the scaling curve is visible.
//! History construction always happens in `iter_batched`'s SETUP closure, so it
//! is never inside the timed region; the timed closure only makes jump calls.
use ;
use ;
use black_box;
use ;
/// Base document size: ~2000 lines of realistic prose-ish width.
const BASE_LINES: usize = 2_000;
const BASE_WIDTH: usize = 60;
/// History depths to chart. N = 16 is the shallow control point; the rest
/// progressively exceed the warm window (`WARM_CAP`).
const DEPTHS: = ;
/// How far back `single_deep_jump`'s setup walks before the timed jump.
///
/// Held at 20 so the number stays comparable across the keyframe change, but
/// note what it measures on each side. Before keyframes (`WARM_CAP = 16`, only
/// the jump TARGET cached) 20 steps left the warm LRU holding descendants only,
/// so the timed jump replayed the whole ancestor chain from the root base — the
/// cold-jump cost. Now `materialize` caches every intermediate it replays, so
/// walking back pre-warms the nodes ahead of the walk and no sequential `g-`
/// lands cold: the timed jump measures the steady-state per-step cost of holding
/// `g-` instead. The worst-case cold bound (`< KEYFRAME_INTERVAL` delta applies
/// from any node) is asserted directly in
/// `undo.rs::keyframes_bound_the_cold_replay_distance`, which is a sharper tool
/// for it than a wall-clock bench.
const WALKBACK: usize = 20;
/// Build a `View` with a LINEAR undo history of `n` committed nodes.
///
/// One node per edit, using the only public commit path there is — the same
/// pre-edit-snapshot dance `Editor::push_undo_at` + `mutate_edit` performs in
/// `hjkl-engine`, and the same one the in-crate tests use
/// (`buffer.rs::undo_stack_shared_across_views`, `undo.rs::Driver::edit`):
/// push an `UndoEntry` holding the PRE-edit rope/cursor/marks, then mutate the
/// buffer. Timestamps are deterministic (`UNIX_EPOCH + i s`) like the tests'
/// fixtures, so no wall-clock jitter leaks into the history shape.
///
/// Each edit is small and realistic (a short marker inserted at the start of a
/// line), so each edge `Delta` is small — exactly the case keyframes would
/// target, since small deltas are what makes a long replay chain plausible.
/// One `g-` step against `view`, mirroring `Editor::seq_earlier_core`: hand the
/// tree the LIVE rope (stashed into the node being left) and take the restored
/// snapshot back. Returns the restored rope, which becomes the next call's live
/// rope — i.e. what `restore_rope` would have put in the buffer.
/// Hold `g-` from the tip all the way to the root.
///
/// Fresh deep-history `View` per iteration via `iter_batched` setup: walking
/// back mutates `current` and warms the LRU, so a reused tree would measure a
/// warm walk from iteration 2 onward.
/// ONE `g-` deep inside a long history, after an untimed `WALKBACK`-step walk.
///
/// See [`WALKBACK`] for exactly what this measures before and after keyframes.
///
/// N = 16 is excluded: that whole history fits in the warm window, so there is
/// no deep jump to measure there — reporting that point would be a warm number
/// wearing a cold label.
criterion_group!;
criterion_main!;