7 Commits
4 changed files with 32 additions and 35 deletions
+2 -1
View File
@@ -158,7 +158,8 @@ func (s *session) recvLoop() {
t := s.client.Transport() t := s.client.Transport()
oldNum := t.LastRecvOldNum() oldNum := t.LastRecvOldNum()
newNum := t.LastRecvNewNum() newNum := t.LastRecvNewNum()
s.tracker.applyDiff(diff, oldNum, newNum) throwawayNum := t.ThrowawayNum()
s.tracker.applyDiff(diff, oldNum, newNum, throwawayNum)
} }
} }
+16 -30
View File
@@ -41,7 +41,8 @@ func newStateTracker(cols, rows int) *stateTracker {
} }
// applyDiff processes an incoming server diff using state tracking. // applyDiff processes an incoming server diff using state tracking.
func (st *stateTracker) applyDiff(diff []byte, oldNum, newNum uint64) { // throwawayNum is the server's indication of which states it no longer references.
func (st *stateTracker) applyDiff(diff []byte, oldNum, newNum, throwawayNum uint64) {
st.mu.Lock() st.mu.Lock()
defer st.mu.Unlock() defer st.mu.Unlock()
@@ -60,11 +61,13 @@ func (st *stateTracker) applyDiff(diff []byte, oldNum, newNum uint64) {
// Need the base state to apply the diff. // Need the base state to apply the diff.
base, ok := st.states[oldNum] base, ok := st.states[oldNum]
if !ok { if !ok {
return // After a resize we have no states. Start fresh with a
} // clean emulator so the first post-resize diff works.
st.shadow = vt.NewEmulator(st.cols, st.rows)
// Restore shadow to base state if needed. st.shadowState = oldNum
if st.shadowState != oldNum { base = mosh.SnapshotEmulator(st.shadow, true)
st.states[oldNum] = base
} else if st.shadowState != oldNum {
st.shadow = vt.NewEmulator(st.cols, st.rows) st.shadow = vt.NewEmulator(st.cols, st.rows)
// Write the base framebuffer as ANSI to restore state. // Write the base framebuffer as ANSI to restore state.
if base != nil { if base != nil {
@@ -91,9 +94,13 @@ func (st *stateTracker) applyDiff(diff []byte, oldNum, newNum uint64) {
st.latestState = newNum st.latestState = newNum
} }
// Bound states list. // Prune states the server no longer references.
if len(st.states) > 128 { if throwawayNum > 0 {
st.pruneStates() for n := range st.states {
if n < throwawayNum {
delete(st.states, n)
}
}
} }
// Display diff: compute ANSI difference between displayed and latest. // Display diff: compute ANSI difference between displayed and latest.
@@ -131,24 +138,3 @@ func (st *stateTracker) resize(cols, rows int) {
st.displayedFB = nil st.displayedFB = nil
} }
func (st *stateTracker) pruneStates() {
if len(st.states) <= 64 {
return
}
// Keep the 64 most recent states.
var nums []uint64
for n := range st.states {
nums = append(nums, n)
}
// Simple sort for small lists.
for i := range nums {
for j := i + 1; j < len(nums); j++ {
if nums[j] < nums[i] {
nums[i], nums[j] = nums[j], nums[i]
}
}
}
for _, n := range nums[:len(nums)-64] {
delete(st.states, n)
}
}
+6 -4
View File
@@ -15,8 +15,10 @@ const (
attrFaint = 1 << 1 attrFaint = 1 << 1
attrItalic = 1 << 2 attrItalic = 1 << 2
attrBlink = 1 << 3 attrBlink = 1 << 3
attrReverse = 1 << 4 attrRapidBlink = 1 << 4
attrStrikethrough = 1 << 5 attrReverse = 1 << 5
attrConceal = 1 << 6
attrStrikethrough = 1 << 7
) )
// Framebuffer represents a terminal screen state for mosh SSP. // Framebuffer represents a terminal screen state for mosh SSP.
@@ -172,11 +174,11 @@ func (fb *Framebuffer) fullRedraw() []byte {
buf = append(buf, "\r\n"...) buf = append(buf, "\r\n"...)
} }
rowOff := y * fb.W rowOff := y * fb.W
// Track trailing spaces to avoid sending them. // Track trailing default-attr spaces to avoid sending them.
lastNonSpace := -1 lastNonSpace := -1
for x := fb.W - 1; x >= 0; x-- { for x := fb.W - 1; x >= 0; x-- {
c := &fb.Cells[rowOff+x] c := &fb.Cells[rowOff+x]
if c.Rune != ' ' || c.Rune == 0 || c.Attr != (Attr{}) { if (c.Rune != ' ' && c.Rune != 0) || c.Attr != (Attr{}) {
lastNonSpace = x lastNonSpace = x
break break
} }
+8
View File
@@ -379,6 +379,14 @@ func (t *Transport) LastRecvNewNum() uint64 {
return t.lastRecvNewNum return t.lastRecvNewNum
} }
// ThrowawayNum returns the server's throwaway number — states below this
// are no longer referenced by the server and can be safely pruned.
func (t *Transport) ThrowawayNum() uint64 {
t.mu.Lock()
defer t.mu.Unlock()
return t.throwawayNum
}
// LastRecv returns the time of the last received datagram. // LastRecv returns the time of the last received datagram.
func (t *Transport) LastRecv() time.Time { func (t *Transport) LastRecv() time.Time {
t.mu.Lock() t.mu.Lock()