screen_damage.go RawHistoryFolder
1package vt
2
3import renderer "github.com/bnema/vev-vt/core"
4
5// maxPendingDamage bounds metadata retained while no render transaction can
6// acknowledge a screen. Saturation falls back to one exact full redraw.
7const maxPendingDamage = 1024
8
9// DamageCapture is an immutable copy of pending damage at one screen generation.
10type DamageCapture struct {
11 Damage []renderer.Damage
12 Generation uint64
13}
14
15// Damage returns the current damage list. The caller must not modify the
16// returned slice; ClearDamage must be called after the damage is consumed.
17func (s *Screen) Damage() []renderer.Damage { return s.damage }
18func (s *Screen) ClearDamage() {
19 s.damage = s.damage[:0]
20 s.damageSaturated = false
21 s.damageFullRedrawSticky = false
22}
23
24// CaptureDamage snapshots pending damage without consuming it.
25func (s *Screen) CaptureDamage() DamageCapture {
26 return DamageCapture{Damage: append([]renderer.Damage(nil), s.damage...), Generation: s.damageGeneration}
27}
28
29// AcknowledgeDamage consumes a capture only if no screen mutation occurred
30// since it was taken. A stale acknowledgement conservatively requests a full
31// redraw, ensuring intervening writes remain visible to the next capture.
32func (s *Screen) AcknowledgeDamage(generation uint64) bool {
33 if generation != s.damageGeneration {
34 s.fullRedraw()
35 return false
36 }
37 s.ClearDamage()
38 return true
39}
40
41func (s *Screen) record(d renderer.Damage) {
42 s.damageGeneration++
43 if s.damageSaturated || s.damageFullRedrawSticky {
44 return
45 }
46 if len(s.damage) >= maxPendingDamage {
47 s.damage = []renderer.Damage{renderer.FullRedraw()}
48 s.damageSaturated = true
49 return
50 }
51 // Replace FullRedraw with the first concrete damage item.
52 if len(s.damage) == 1 && s.damage[0].Kind == renderer.DamageFullRedraw {
53 s.damage[0] = d
54 return
55 }
56 // Coalesce adjacent single-cell text damage on the same line.
57 if d.Kind == renderer.DamageText && d.Width == 1 && d.Height == 1 && len(s.damage) > 0 {
58 last := &s.damage[len(s.damage)-1]
59 if last.Kind == renderer.DamageText && last.Y == d.Y && last.X+last.Width == d.X && last.Height == 1 {
60 last.Width++
61 return
62 }
63 }
64 s.damage = append(s.damage, d)
65}
66
67func (s *Screen) fullRedraw() {
68 s.damageGeneration++
69 s.damage = []renderer.Damage{renderer.FullRedraw()}
70 // A structural frame replacement (such as DEC 1049 exit) cannot be
71 // represented by the following text damage alone. Retain the redraw until
72 // the render owner acknowledges this screen generation.
73 s.damageFullRedrawSticky = true
74}