From 641e3eda9818cd931a74bc459698aec557eddfcd Mon Sep 17 00:00:00 2001 From: bloeys Date: Sat, 23 Jul 2022 03:08:44 +0400 Subject: [PATCH] Correct and simplify textBuf drawing using the new ViewsFromTo+ new ring funcs --- .gitignore | 9 ++++- ansi/ansi.go | 2 +- main.go | 57 +++++++++++++++------------ res/shaders/glyph.glsl | 2 +- ring/ring.go | 77 +++++++++++++++++++++++++++++++----- ring/ring_test.go | 89 ++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 199 insertions(+), 37 deletions(-) diff --git a/.gitignore b/.gitignore index ae2df1f..0c0f96a 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,13 @@ # Dependency directories (remove the comment below to include it) # vendor/ + +# Custom *.png *.cpu -1gb.txt \ No newline at end of file + +1gb.txt +1gb.bin + +test.txt +long-line.txt diff --git a/ansi/ansi.go b/ansi/ansi.go index 4fc878e..46ed47c 100755 --- a/ansi/ansi.go +++ b/ansi/ansi.go @@ -295,7 +295,7 @@ func ParseSGRArgs(info *AnsiCodeInfo, args []byte) { // @TODO Support bold/underline etc // @TODO Support 256 and RGB colors - panic("Code not supported yet: " + fmt.Sprint(intCode)) + println("Code not supported yet: " + fmt.Sprint(intCode)) } } diff --git a/main.go b/main.go index fd7287c..34b4bfa 100755 --- a/main.go +++ b/main.go @@ -73,8 +73,10 @@ type program struct { lastCmdCharPos *gglm.Vec3 scrollPos int64 scrollSpd int64 - maxCharsToShow int64 - maxLinesToShow int64 + + CellCountX int64 + CellCountY int64 + CellCount int64 activeCmd *Cmd Settings *Settings @@ -90,13 +92,12 @@ const ( defaultCmdBufSize = 4 * 1024 defaultTextBufSize = 4 * 1024 * 1024 - defaultScrollSpd = 5 + defaultScrollSpd = 1 ) var ( - // isDrawingBounds = false - drawManyLines = false drawGrid bool + drawManyLines = false textToShow = "" @@ -249,7 +250,7 @@ func (p *program) WriteToTextBuf(text []byte) { // This is locked because running cmds are potentially writing to it same time we are p.textBufMutex.Lock() p.textBuf.Write(text...) - p.scrollPos = clamp(p.textBuf.Len-p.maxCharsToShow, 0, p.textBuf.Len-1) + p.scrollPos = clamp(p.textBuf.Len-p.CellCount, 0, p.textBuf.Len-1) p.textBufMutex.Unlock() } @@ -296,13 +297,16 @@ func (p *program) MainUpdate() { if mouseWheelYNorm := -int64(input.GetMouseWheelYNorm()); mouseWheelYNorm != 0 { var newPosNewLines int64 - w, _ := p.GridSize() if mouseWheelYNorm < 0 { - newPosNewLines, _ = find_n_lines_index_iterator(p.textBuf.Iterator(), p.scrollPos, p.scrollSpd*mouseWheelYNorm-1, int64(w)) + newPosNewLines, _ = find_n_lines_index_iterator(p.textBuf.Iterator(), p.scrollPos, p.scrollSpd*mouseWheelYNorm-1, p.CellCountX) } else { - newPosNewLines, _ = find_n_lines_index_iterator(p.textBuf.Iterator(), p.scrollPos, p.scrollSpd*mouseWheelYNorm, int64(w)) + newPosNewLines, _ = find_n_lines_index_iterator(p.textBuf.Iterator(), p.scrollPos, p.scrollSpd*mouseWheelYNorm, p.CellCountX) } + a := p.textBuf.AbsIndex(uint64(p.scrollPos)) + b := p.textBuf.AbsIndex(uint64(newPosNewLines)) + println("was at:", a, "; Now at:", b) + // assert.T(p.textBuf.Get(uint64(newPosNewLines)) != '\n', fmt.Sprintf("Original AbsIndex %d; New line at AbsIndex %d\n", a, b)) p.scrollPos = clamp(newPosNewLines, 0, p.textBuf.Len) } @@ -317,18 +321,19 @@ func (p *program) MainUpdate() { } // Draw textBuf - v1, v2 := p.textBuf.Views() + from := p.scrollPos + to, _ := find_n_lines_index_iterator(p.textBuf.Iterator(), p.scrollPos, p.CellCountY-2, p.CellCountX) - from := clamp(p.scrollPos, 0, int64(len(v1)-1)) - to := clamp(p.scrollPos+p.maxCharsToShow, 0, int64(len(v1)-1)) - p.lastCmdCharPos.Data = p.DrawTextAnsiCodes(v1[from:to], *gglm.NewVec3(0, float32(p.GlyphRend.ScreenHeight)-p.GlyphRend.Atlas.LineHeight, 0)).Data - - if p.scrollPos >= int64(len(v1)) { - - from := clamp(p.scrollPos-int64(len(v1)), 0, int64(len(v2)-1)) - to := clamp(p.scrollPos+p.maxCharsToShow, 0, int64(len(v2)-1)) - p.lastCmdCharPos.Data = p.DrawTextAnsiCodes(v2[from:to], *p.lastCmdCharPos).Data + // to is the first character after the nth line. Passing this index to ViewsFromTo will show 1 more char than we want, + // so we decrement if needed + if to > 0 { + to-- } + assert.T(to >= 0, "'to' was less than zero") + v1, v2 := p.textBuf.ViewsFromTo(uint64(from), uint64(to)) + + p.lastCmdCharPos.Data = p.DrawTextAnsiCodes(v1, *gglm.NewVec3(0, float32(p.GlyphRend.ScreenHeight)-p.GlyphRend.Atlas.LineHeight, 0)).Data + p.lastCmdCharPos.Data = p.DrawTextAnsiCodes(v2, *p.lastCmdCharPos).Data sepLinePos.Data = p.lastCmdCharPos.Data @@ -673,8 +678,9 @@ func (p *program) DrawCursor() { p.rend.Draw(p.gridMesh, gglm.NewTrMatId().Translate(pos).Scale(gglm.NewVec3(0.1*p.GlyphRend.Atlas.SpaceAdvance, p.GlyphRend.Atlas.LineHeight, 1)), p.gridMat) } -func (p *program) GridSize() (w, h int32) { - return p.GlyphRend.ScreenWidth / int32(p.GlyphRend.Atlas.SpaceAdvance), p.GlyphRend.ScreenHeight / int32(p.GlyphRend.Atlas.LineHeight) +// GridSize returns how many cells horizontally (aka chars per line) and how many cells vertically (aka lines) +func (p *program) GridSize() (w, h int64) { + return int64(p.GlyphRend.ScreenWidth) / int64(p.GlyphRend.Atlas.SpaceAdvance), int64(p.GlyphRend.ScreenHeight) / int64(p.GlyphRend.Atlas.LineHeight) } func (p *program) ScreenPosToGridPos(screenPos *gglm.Vec3) { @@ -790,9 +796,8 @@ func (p *program) HandleWindowResize() { viewMtx := gglm.LookAt(gglm.NewVec3(0, 0, -10), gglm.NewVec3(0, 0, 0), gglm.NewVec3(0, 1, 0)) p.gridMat.SetUnifMat4("projViewMat", &projMtx.Mul(viewMtx).Mat4) - // We show a bit more than calculated to be safe against showing empty space - p.maxLinesToShow = int64(CeilF32(float32(h)/float32(p.GlyphRend.Atlas.LineHeight)) * 1.25) - p.maxCharsToShow = int64(CeilF32(float32(w)/float32(p.GlyphRend.Atlas.SpaceAdvance))*1.25) * p.maxLinesToShow + p.CellCountX, p.CellCountY = p.GridSize() + p.CellCount = p.CellCountX * p.CellCountY } func FloorF32(x float32) float32 { @@ -874,7 +879,7 @@ func find_n_lines_index_iterator(it ring.Iterator[byte], startIndex, n, charsPer buf := make([]byte, 4) it.GotoIndex(startIndex) - // @Note we should ignore zero width glyphs + // @Todo we should ignore zero width glyphs // @Note is this better in glyphs package? bytesSeen := int64(0) charsSeenThisLine := int64(0) @@ -914,6 +919,8 @@ func find_n_lines_index_iterator(it ring.Iterator[byte], startIndex, n, charsPer } else { + // @Todo this has wrong behavior when dealing with wrapped lines because we don't know what X position to be in + // after going up a line. Are we in the middle of the line? for !done || bytesToKeep > 0 { read, done = it.PrevN(buf[bytesToKeep:], 4) diff --git a/res/shaders/glyph.glsl b/res/shaders/glyph.glsl index cb050c0..f8d242d 100755 --- a/res/shaders/glyph.glsl +++ b/res/shaders/glyph.glsl @@ -47,7 +47,7 @@ uniform int drawBounds; void main() { vec4 texColor = texelFetch(diffTex, ivec2(v2fUV0), 0); - // This commented out part highlights the full region of the char + // This part highlights the full region of the char if (texColor.r == 0 && drawBounds != 0) { fragColor = vec4(0,1,0,0.25); diff --git a/ring/ring.go b/ring/ring.go index 8b052d7..503b8da 100755 --- a/ring/ring.go +++ b/ring/ring.go @@ -91,9 +91,24 @@ func clamp[T constraints.Ordered](x, min, max T) T { return x } +// Get returns the element at the index relative from Buffer.Start +// If there are no elements then the default value of T is returned +func (b *Buffer[T]) Get(index uint64) (val T) { + + if index >= uint64(b.Len) { + return val + } + + return b.Data[(b.Start+int64(index))%b.Cap] +} + +func (b *Buffer[T]) AbsIndex(relIndex uint64) uint64 { + return uint64((b.Start + int64(relIndex)) % b.Cap) +} + // Views returns two slices that have 'Len' elements in total between them. // The first slice is from Start till min(Start+Len, Cap). If Start+Len<=Cap then the first slice contains all the data and the second is empty. -// If Start+Len>Cap then the first slice contains the data from Start till Cap, and the second slice contains data from Zero till Start+Len-Cap (basically the remaining elements to reach Len in total) +// If Start+Len>Cap then the first slice contains the data from Start till Cap, and the second slice contains data from 0 till Start+Len-Cap (basically the remaining elements to reach Len in total) // // This function does NOT copy. Any changes on the returned slices will reflect on the buffer Data // @@ -109,15 +124,49 @@ func (b *Buffer[T]) Views() (v1, v2 []T) { return } -func (b *Buffer[T]) Iterator() Iterator[T] { +func (b *Buffer[T]) ViewsFromTo(fromIndex, toIndex uint64) (v1, v2 []T) { - v1, v2 := b.Views() - return Iterator[T]{ - V1: v1, - V2: v2, - Curr: 0, - InV1: true, + toIndex++ // We convert the index into a length (e.g. from=0, to=0 is from=0, len=1) + if toIndex <= fromIndex || fromIndex >= uint64(b.Len) { + return []T{}, []T{} } + + v1, v2 = b.Views() + v1Len := uint64(len(v1)) + v2Len := uint64(len(v2)) + startInV1 := fromIndex < v1Len + + if startInV1 { + + if toIndex <= v1Len { + v1 = v1[fromIndex:toIndex] + v2 = v2[:0] + return + } + + toIndex -= v1Len + if toIndex > v2Len { + toIndex = v2Len + } + + v1 = v1[fromIndex:v1Len] + v2 = v2[:toIndex] + return + } + + fromIndex -= v1Len - 1 + toIndex -= v1Len + if toIndex >= v2Len { + toIndex = v2Len + } + + v1 = v1[:0] + v2 = v2[fromIndex:toIndex] + return +} + +func (b *Buffer[T]) Iterator() Iterator[T] { + return NewIterator(b) } func NewBuffer[T any](capacity uint64) *Buffer[T] { @@ -263,7 +312,7 @@ func (it *Iterator[T]) PrevN(buf []T, n int) (read int, done bool) { // and the next Prev() call returns done=true func (it *Iterator[T]) GotoStart() { it.Curr = 0 - it.InV1 = true + it.InV1 = len(it.V1) > 0 } // GotoIndex goes to the index n relative to Buffer.Start @@ -297,3 +346,13 @@ func (it *Iterator[T]) GotoEnd() { it.Curr = int64(len(it.V2)) it.InV1 = false } + +func NewIterator[T any](b *Buffer[T]) Iterator[T] { + v1, v2 := b.Views() + return Iterator[T]{ + V1: v1, + V2: v2, + Curr: 0, + InV1: len(v1) > 0, // If buffer is empty we shouldn't be in V1 + } +} diff --git a/ring/ring_test.go b/ring/ring_test.go index 5307e19..1dce1d7 100755 --- a/ring/ring_test.go +++ b/ring/ring_test.go @@ -87,6 +87,78 @@ func TestRing(t *testing.T) { b2.DeleteN(2, 1) Check(t, 3, b2.Len) CheckArr(t, []int{5, 6, 8, 8}, b2.Data) + + // ViewsFromTo + b2 = ring.NewBuffer[int](4) + + v11, v22 := b2.ViewsFromTo(0, 0) + Check(t, 0, len(v11)) + Check(t, 0, len(v22)) + + b2.Write(1, 2, 3, 4) + + v11, v22 = b2.ViewsFromTo(5, 0) + Check(t, 0, len(v11)) + Check(t, 0, len(v22)) + + v11, v22 = b2.ViewsFromTo(0, 0) + Check(t, 1, len(v11)) + Check(t, 0, len(v22)) + CheckArr(t, []int{1}, v11) + + v11, v22 = b2.ViewsFromTo(0, 1) + Check(t, 2, len(v11)) + Check(t, 0, len(v22)) + CheckArr(t, []int{1, 2}, v11) + + v11, v22 = b2.ViewsFromTo(0, 3) + Check(t, 4, len(v11)) + Check(t, 0, len(v22)) + CheckArr(t, []int{1, 2, 3, 4}, v11) + + v11, v22 = b2.ViewsFromTo(0, 4) + Check(t, 4, len(v11)) + Check(t, 0, len(v22)) + CheckArr(t, []int{1, 2, 3, 4}, v11) + + v11, v22 = b2.ViewsFromTo(0, 40) + Check(t, 4, len(v11)) + Check(t, 0, len(v22)) + CheckArr(t, []int{1, 2, 3, 4}, v11) + + v11, v22 = b2.ViewsFromTo(3, 40) + Check(t, 1, len(v11)) + Check(t, 0, len(v22)) + CheckArr(t, []int{4}, v11) + + b2.Write(5, 6) + + v11, v22 = b2.ViewsFromTo(3, 40) + Check(t, 0, len(v11)) + Check(t, 0, len(v22)) + + v11, v22 = b2.ViewsFromTo(1, 2) + Check(t, 1, len(v11)) + Check(t, 1, len(v22)) + CheckArr(t, []int{4}, v11) + CheckArr(t, []int{5}, v22) + + v11, v22 = b2.ViewsFromTo(0, 1) + Check(t, 2, len(v11)) + Check(t, 0, len(v22)) + CheckArr(t, []int{3, 4}, v11) + + v11, v22 = b2.ViewsFromTo(0, 2) + Check(t, 2, len(v11)) + Check(t, 1, len(v22)) + CheckArr(t, []int{3, 4}, v11) + CheckArr(t, []int{5}, v22) + + v11, v22 = b2.ViewsFromTo(0, 3) + Check(t, 2, len(v11)) + Check(t, 2, len(v22)) + CheckArr(t, []int{3, 4}, v11) + CheckArr(t, []int{5, 6}, v22) } func TestIterator(t *testing.T) { @@ -198,6 +270,23 @@ func TestIterator(t *testing.T) { ans = []int{4, 3} it.PrevN(got, 2) CheckArr(t, ans, got) + + // Empty buffer + b = ring.NewBuffer[int](4) + + it = b.Iterator() + Check(t, 0, len(it.V1)) + Check(t, 0, len(it.V2)) + Check(t, false, it.InV1) + + it.GotoStart() + Check(t, false, it.InV1) + + it.GotoIndex(1) + Check(t, false, it.InV1) + + _, done := it.Next() + Check(t, true, done) } func Check[T comparable](t *testing.T, expected, got T) {