Skip to content

Commit af1ca48

Browse files
committed
fixup! fixup! drop snapshots
1 parent eabb3b1 commit af1ca48

File tree

4 files changed

+8
-291
lines changed

4 files changed

+8
-291
lines changed

frontends/rioterm/src/application.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -261,7 +261,7 @@ impl ApplicationHandler<EventPayload> for Application<'_> {
261261
.val
262262
.renderable_content
263263
.pending_update
264-
.mark_for_damage_check();
264+
.set_dirty();
265265
route.schedule_redraw(&mut self.scheduler, route_id);
266266
}
267267
}

frontends/rioterm/src/context/mod.rs

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -75,9 +75,7 @@ impl<T: EventListener> Context<T> {
7575
drop(terminal);
7676

7777
// Mark pending update as dirty so the selection change is rendered
78-
self.renderable_content
79-
.pending_update
80-
.invalidate_full(&self.terminal);
78+
self.renderable_content.pending_update.set_dirty();
8179
}
8280

8381
self.renderable_content.selection_range = selection_range;
@@ -86,9 +84,7 @@ impl<T: EventListener> Context<T> {
8684
#[inline]
8785
pub fn set_hyperlink_range(&mut self, hyperlink_range: Option<SelectionRange>) {
8886
self.renderable_content.hyperlink_range = hyperlink_range;
89-
self.renderable_content
90-
.pending_update
91-
.invalidate_full(&self.terminal);
87+
self.renderable_content.pending_update.set_dirty();
9288
}
9389

9490
#[inline]

frontends/rioterm/src/context/renderable.rs

Lines changed: 1 addition & 276 deletions
Original file line numberDiff line numberDiff line change
@@ -83,18 +83,6 @@ pub struct TerminalSnapshot {
8383
pub screen_lines: usize,
8484
}
8585

86-
/// Manages pending terminal updates and their rendering state.
87-
///
88-
/// This struct handles two types of updates:
89-
/// 1. **Synchronized updates** - These come with damage events and create snapshots immediately.
90-
/// Snapshots capture the terminal state at the time of the damage event, avoiding the need
91-
/// to lock the terminal during rendering. This is ideal for synchronized terminal sequences
92-
/// where we know exactly what changed.
93-
///
94-
/// 2. **Non-synchronized updates** - These come via Wakeup events and defer damage checking.
95-
/// Instead of creating snapshots immediately (which would require locking the terminal for
96-
/// each update), we just mark the update as pending and check for damage at render time.
97-
/// This reduces lock contention for rapid, non-synchronized terminal output like in notcurses-demo.
9886
#[derive(Debug, Default)]
9987
pub struct PendingUpdate {
10088
/// Whether there's any pending update that needs rendering
@@ -110,25 +98,7 @@ impl PendingUpdate {
11098

11199
/// Mark as needing to check for damage on next render
112100
/// This is used by Wakeup events to defer damage calculation
113-
pub fn mark_for_damage_check(&mut self) {
114-
self.dirty = true;
115-
}
116-
117-
/// Mark as needing update.
118-
/// The actual snapshot will be computed at render time.
119-
pub fn invalidate<U: rio_backend::event::EventListener>(
120-
&mut self,
121-
_damage: TerminalDamage,
122-
_terminal: &FairMutex<Crosswords<U>>,
123-
) {
124-
self.dirty = true;
125-
}
126-
127-
/// Mark as needing full update
128-
pub fn invalidate_full<U: rio_backend::event::EventListener>(
129-
&mut self,
130-
_terminal: &FairMutex<Crosswords<U>>,
131-
) {
101+
pub fn set_dirty(&mut self) {
132102
self.dirty = true;
133103
}
134104

@@ -137,248 +107,3 @@ impl PendingUpdate {
137107
self.dirty = false;
138108
}
139109
}
140-
141-
#[cfg(test)]
142-
mod tests {
143-
use super::*;
144-
use rio_backend::crosswords::pos::{Column, Line, Pos};
145-
use rio_backend::crosswords::{Crosswords, LineDamage};
146-
use rio_backend::event::VoidListener;
147-
use std::collections::BTreeSet;
148-
149-
// Helper to create a test terminal
150-
fn create_test_terminal() -> FairMutex<Crosswords<VoidListener>> {
151-
use rio_backend::ansi::CursorShape;
152-
use rio_backend::crosswords::CrosswordsSize;
153-
use rio_window::window::WindowId;
154-
155-
let dimensions = CrosswordsSize::new(80, 24);
156-
let terminal = Crosswords::new(
157-
dimensions,
158-
CursorShape::Block,
159-
VoidListener,
160-
WindowId::from(0),
161-
0,
162-
);
163-
FairMutex::new(terminal)
164-
}
165-
166-
#[test]
167-
fn test_hint_matches_persistence() {
168-
let mut content = RenderableContent::from_cursor_config(&CursorConfig::default());
169-
170-
// Set hint matches
171-
let matches = vec![
172-
Pos::new(Line(0), Column(0))..=Pos::new(Line(0), Column(4)),
173-
Pos::new(Line(1), Column(5))..=Pos::new(Line(1), Column(9)),
174-
];
175-
content.hint_matches = Some(matches.clone());
176-
177-
// Verify matches persist
178-
assert_eq!(content.hint_matches, Some(matches));
179-
}
180-
181-
#[test]
182-
fn test_hint_labels_with_damage() {
183-
let mut content = RenderableContent::from_cursor_config(&CursorConfig::default());
184-
let terminal = create_test_terminal();
185-
186-
// Reset terminal damage to start fresh
187-
terminal.lock().reset_damage();
188-
189-
// Add hint labels
190-
content.hint_labels.push(HintLabel {
191-
position: Pos::new(Line(5), Column(10)),
192-
label: vec!['a', 'b'],
193-
is_first: true,
194-
});
195-
content.hint_labels.push(HintLabel {
196-
position: Pos::new(Line(10), Column(20)),
197-
label: vec!['c'],
198-
is_first: false,
199-
});
200-
201-
// Create damage for the hint label lines
202-
let mut damaged_lines = BTreeSet::new();
203-
damaged_lines.insert(LineDamage::new(5, true));
204-
damaged_lines.insert(LineDamage::new(10, true));
205-
let damage = TerminalDamage::Partial(damaged_lines);
206-
207-
// Invalidate with damage
208-
content.pending_update.invalidate(damage, &terminal);
209-
210-
// Verify update is marked as dirty
211-
assert!(content.pending_update.is_dirty());
212-
213-
// Take snapshot and verify damage
214-
let snapshot = content.pending_update.take_snapshot().unwrap();
215-
match snapshot.damage {
216-
TerminalDamage::Partial(lines) => {
217-
assert_eq!(lines.len(), 2);
218-
assert!(lines.iter().any(|l| l.line == 5));
219-
assert!(lines.iter().any(|l| l.line == 10));
220-
}
221-
_ => panic!("Expected partial damage"),
222-
}
223-
}
224-
225-
#[test]
226-
fn test_clear_hint_state_triggers_full_damage() {
227-
let mut content = RenderableContent::from_cursor_config(&CursorConfig::default());
228-
let terminal = create_test_terminal();
229-
230-
// Set hint state
231-
content.hint_matches = Some(vec![
232-
Pos::new(Line(0), Column(0))..=Pos::new(Line(0), Column(4)),
233-
]);
234-
content.hint_labels.push(HintLabel {
235-
position: Pos::new(Line(0), Column(0)),
236-
label: vec!['a'],
237-
is_first: true,
238-
});
239-
240-
// Clear hint state and trigger full damage
241-
content.hint_matches = None;
242-
content.hint_labels.clear();
243-
content
244-
.pending_update
245-
.invalidate(TerminalDamage::Full, &terminal);
246-
247-
// Verify update is marked as dirty
248-
assert!(content.pending_update.is_dirty());
249-
250-
// Take snapshot and verify full damage
251-
let snapshot = content.pending_update.take_snapshot().unwrap();
252-
assert_eq!(snapshot.damage, TerminalDamage::Full);
253-
}
254-
255-
#[test]
256-
fn test_damage_merging() {
257-
let mut content = RenderableContent::from_cursor_config(&CursorConfig::default());
258-
let terminal = create_test_terminal();
259-
260-
// Reset terminal damage to start fresh
261-
terminal.lock().reset_damage();
262-
263-
// First invalidation with partial damage
264-
let mut damaged_lines1 = BTreeSet::new();
265-
damaged_lines1.insert(LineDamage::new(5, true));
266-
content
267-
.pending_update
268-
.invalidate(TerminalDamage::Partial(damaged_lines1), &terminal);
269-
270-
// Second invalidation with different partial damage
271-
let mut damaged_lines2 = BTreeSet::new();
272-
damaged_lines2.insert(LineDamage::new(10, true));
273-
damaged_lines2.insert(LineDamage::new(15, true));
274-
content
275-
.pending_update
276-
.invalidate(TerminalDamage::Partial(damaged_lines2), &terminal);
277-
278-
// Take snapshot and verify merged damage
279-
let snapshot = content.pending_update.take_snapshot().unwrap();
280-
match snapshot.damage {
281-
TerminalDamage::Partial(lines) => {
282-
assert_eq!(lines.len(), 3);
283-
assert!(lines.iter().any(|l| l.line == 5));
284-
assert!(lines.iter().any(|l| l.line == 10));
285-
assert!(lines.iter().any(|l| l.line == 15));
286-
}
287-
_ => panic!("Expected partial damage"),
288-
}
289-
}
290-
291-
#[test]
292-
fn test_full_damage_overrides_partial() {
293-
let mut content = RenderableContent::from_cursor_config(&CursorConfig::default());
294-
let terminal = create_test_terminal();
295-
296-
// First invalidation with partial damage
297-
let mut damaged_lines = BTreeSet::new();
298-
damaged_lines.insert(LineDamage::new(5, true));
299-
content
300-
.pending_update
301-
.invalidate(TerminalDamage::Partial(damaged_lines), &terminal);
302-
303-
// Second invalidation with full damage
304-
content
305-
.pending_update
306-
.invalidate(TerminalDamage::Full, &terminal);
307-
308-
// Take snapshot and verify full damage
309-
let snapshot = content.pending_update.take_snapshot().unwrap();
310-
assert_eq!(snapshot.damage, TerminalDamage::Full);
311-
}
312-
313-
#[test]
314-
fn test_hint_state_update_flow() {
315-
let mut content = RenderableContent::from_cursor_config(&CursorConfig::default());
316-
let terminal = create_test_terminal();
317-
318-
// Simulate hint activation
319-
content.hint_matches = Some(vec![
320-
Pos::new(Line(2), Column(5))..=Pos::new(Line(2), Column(10)),
321-
Pos::new(Line(5), Column(0))..=Pos::new(Line(5), Column(7)),
322-
]);
323-
content.hint_labels.push(HintLabel {
324-
position: Pos::new(Line(2), Column(5)),
325-
label: vec!['a'],
326-
is_first: true,
327-
});
328-
content.hint_labels.push(HintLabel {
329-
position: Pos::new(Line(5), Column(0)),
330-
label: vec!['b'],
331-
is_first: true,
332-
});
333-
334-
// Trigger damage for hint lines
335-
let mut damaged_lines = BTreeSet::new();
336-
damaged_lines.insert(LineDamage::new(2, true));
337-
damaged_lines.insert(LineDamage::new(5, true));
338-
content
339-
.pending_update
340-
.invalidate(TerminalDamage::Partial(damaged_lines), &terminal);
341-
342-
assert!(content.pending_update.is_dirty());
343-
344-
// Simulate hint deactivation
345-
content.hint_matches = None;
346-
content.hint_labels.clear();
347-
content
348-
.pending_update
349-
.invalidate(TerminalDamage::Full, &terminal);
350-
351-
// Verify state after deactivation
352-
assert!(content.hint_matches.is_none());
353-
assert!(content.hint_labels.is_empty());
354-
assert!(content.pending_update.is_dirty());
355-
}
356-
357-
#[test]
358-
fn test_multiple_snapshots() {
359-
let mut content = RenderableContent::from_cursor_config(&CursorConfig::default());
360-
let terminal = create_test_terminal();
361-
362-
// First update
363-
content
364-
.pending_update
365-
.invalidate(TerminalDamage::Full, &terminal);
366-
assert!(content.pending_update.is_dirty());
367-
368-
// Reset dirty flag - simulating render
369-
content.pending_update.reset();
370-
assert!(!content.pending_update.is_dirty());
371-
372-
// Second update
373-
let mut damaged_lines = BTreeSet::new();
374-
damaged_lines.insert(LineDamage::new(3, true));
375-
content
376-
.pending_update
377-
.invalidate(TerminalDamage::Partial(damaged_lines), &terminal);
378-
assert!(content.pending_update.is_dirty());
379-
380-
// Reset dirty flag again
381-
content.pending_update.reset();
382-
assert!(!content.pending_update.is_dirty());
383-
}
384-
}

frontends/rioterm/src/screen/mod.rs

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2529,10 +2529,7 @@ impl Screen<'_> {
25292529
// Force invalidation for search
25302530
{
25312531
let current = self.context_manager.current_mut();
2532-
current.renderable_content.pending_update.invalidate(
2533-
rio_backend::event::TerminalDamage::Full,
2534-
&current.terminal,
2535-
);
2532+
current.renderable_content.pending_update.set_dirty();
25362533
}
25372534
}
25382535

@@ -2746,19 +2743,18 @@ impl Screen<'_> {
27462743
}
27472744

27482745
if !damaged_lines.is_empty() {
2749-
let damage = TerminalDamage::Partial(damaged_lines);
27502746
let current = self.context_manager.current_mut();
27512747
current
27522748
.renderable_content
27532749
.pending_update
2754-
.invalidate(damage, &current.terminal);
2750+
.set_dirty();
27552751
} else {
27562752
// Force full damage if no specific lines (for hint highlights)
27572753
let current = self.context_manager.current_mut();
27582754
current
27592755
.renderable_content
27602756
.pending_update
2761-
.invalidate(TerminalDamage::Full, &current.terminal);
2757+
.set_dirty();
27622758
}
27632759
} else {
27642760
// Clear hint state
@@ -2776,7 +2772,7 @@ impl Screen<'_> {
27762772
current
27772773
.renderable_content
27782774
.pending_update
2779-
.invalidate(TerminalDamage::Full, &current.terminal);
2775+
.set_dirty();
27802776
}
27812777
}
27822778

0 commit comments

Comments
 (0)