Skip to content

Commit

Permalink
Performance optimization.
Browse files Browse the repository at this point in the history
  • Loading branch information
BigJk committed May 23, 2023
1 parent c18e3ba commit 032bbda
Show file tree
Hide file tree
Showing 6 changed files with 104 additions and 25 deletions.
54 changes: 38 additions & 16 deletions crt.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"image/color"
"io"
"sync"
"unicode/utf8"
)

// colorCache is the ansi color cache.
Expand Down Expand Up @@ -53,6 +54,7 @@ type Window struct {
onPostDraw func(screen *ebiten.Image)

// Other
seqBuffer []byte
showTps bool
fonts Fonts
bgColors *image.RGBA
Expand Down Expand Up @@ -109,6 +111,7 @@ func NewGame(width int, height int, fonts Fonts, tty io.Reader, adapter InputAda
onPreDraw: func(screen *ebiten.Image) {},
onPostDraw: func(screen *ebiten.Image) {},
invalidateBuffer: true,
seqBuffer: make([]byte, 0, 2^12),
}

game.inputAdapter.HandleWindowSize(WindowSize{
Expand Down Expand Up @@ -187,6 +190,18 @@ func (g *Window) SetBgPixels(x, y int, c color.Color) {
g.InvalidateBuffer()
}

// SetBg sets the background color of a cell and checks if it needs to be redrawn.
func (g *Window) SetBg(x, y int, c color.Color) {
ra, rg, rb, _ := g.grid[y][x].Bg.RGBA()
ca, cg, cb, _ := c.RGBA()
if ra == ca && rg == cg && rb == cb {
return
}

g.SetBgPixels(x, y, c)
g.grid[y][x].Bg = c
}

// GetCellsWidth returns the number of cells in the x direction.
func (g *Window) GetCellsWidth() int {
return g.cellsWidth
Expand Down Expand Up @@ -266,22 +281,19 @@ func (g *Window) handleCSI(csi any) {
for i := g.cursorX; i < g.cellsWidth-g.cursorX; i++ {
g.grid[g.cursorY][g.cursorX+i].Char = ' '
g.grid[g.cursorY][g.cursorX+i].Fg = color.White
g.grid[g.cursorY][g.cursorX+i].Bg = g.defaultBg
g.SetBgPixels(g.cursorX+i, g.cursorY, g.defaultBg)
g.SetBg(g.cursorX+i, g.cursorY, g.defaultBg)
}
case 1: // erase from start of line to cursor
for i := 0; i < g.cursorX; i++ {
g.grid[g.cursorY][i].Char = ' '
g.grid[g.cursorY][i].Fg = color.White
g.grid[g.cursorY][i].Bg = g.defaultBg
g.SetBgPixels(i, g.cursorY, g.defaultBg)
g.SetBg(i, g.cursorY, g.defaultBg)
}
case 2: // erase entire line
for i := 0; i < g.cellsWidth; i++ {
g.grid[g.cursorY][i].Char = ' '
g.grid[g.cursorY][i].Fg = color.White
g.grid[g.cursorY][i].Bg = g.defaultBg
g.SetBgPixels(i, g.cursorY, g.defaultBg)
g.SetBg(i, g.cursorY, g.defaultBg)
}
}
case CursorShowSeq:
Expand Down Expand Up @@ -334,11 +346,9 @@ func (g *Window) handleSGR(sgr any) {
}

func (g *Window) parseSequences(str string, printExtra bool) int {
runes := []rune(str)

lastFound := 0
for i := 0; i < len(runes); i++ {
if sgr, ok := extractSGR(string(runes[i:])); ok {
for i := 0; i < len(str); i++ {
if sgr, ok := extractSGR(str[i:]); ok {
i += len(sgr) - 1

if sgr, ok := parseSGR(sgr); ok {
Expand All @@ -348,7 +358,7 @@ func (g *Window) parseSequences(str string, printExtra bool) int {
g.InvalidateBuffer()
}
}
} else if csi, ok := extractCSI(string(runes[i:])); ok {
} else if csi, ok := extractCSI(str[i:]); ok {
i += len(csi) - 1

if csi, ok := parseCSI(csi); ok {
Expand All @@ -357,13 +367,23 @@ func (g *Window) parseSequences(str string, printExtra bool) int {
g.InvalidateBuffer()
}
} else if printExtra {
g.PrintChar(runes[i], g.curFg, g.curBg, g.curWeight)
if r, size := utf8.DecodeRuneInString(str[i:]); r != utf8.RuneError {
g.PrintChar(r, g.curFg, g.curBg, g.curWeight)
i += size - 1
}
}
}

return lastFound
}

func (g *Window) drainSequence() {
if len(g.seqBuffer) > 0 {
g.parseSequences(string(g.seqBuffer), true)
g.seqBuffer = g.seqBuffer[:0]
}
}

// RecalculateBackgrounds syncs the background colors to the background pixels.
func (g *Window) RecalculateBackgrounds() {
for i := 0; i < g.cellsWidth; i++ {
Expand Down Expand Up @@ -439,8 +459,7 @@ func (g *Window) Update() error {

g.Lock()
{
line := string(buf[:n])
g.parseSequences(line, true)
g.seqBuffer = append(g.seqBuffer, buf[:n]...)
}
g.Unlock()
}
Expand Down Expand Up @@ -511,10 +530,13 @@ func (g *Window) Draw(screen *ebiten.Image) {
g.Lock()
defer g.Unlock()

screen.Fill(g.defaultBg)

g.onPreDraw(screen)

// We process the sequence buffer here so that we don't get flickering
g.drainSequence()

screen.Fill(g.defaultBg)

// Get current buffer
bufferImage := g.lastBuffer

Expand Down
23 changes: 23 additions & 0 deletions csi.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,12 @@ import (
"github.com/muesli/termenv"
"strconv"
"strings"
"sync"
)

var csiMtx = &sync.Mutex{}
var csiCache = map[string]any{}

type CursorUpSeq struct {
Count int
}
Expand Down Expand Up @@ -109,6 +113,25 @@ func parseCSI(s string) (any, bool) {
return nil, false
}

csiMtx.Lock()
if cached, ok := csiCache[s]; ok {
csiMtx.Unlock()
return cached, true
}
csiMtx.Unlock()

if val, ok := parseCSIStruct(s); ok {
csiMtx.Lock()
csiCache[s] = val
csiMtx.Unlock()

return val, true
}

return nil, false
}

func parseCSIStruct(s string) (any, bool) {
switch s {
case termenv.ShowCursorSeq:
return CursorShowSeq{}, true
Expand Down
18 changes: 15 additions & 3 deletions examples/benchmark/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import (
"github.com/hajimehoshi/ebiten/v2"
"image/color"
"math/rand"
"net/http"
_ "net/http/pprof"
"time"
)

Expand All @@ -36,10 +38,16 @@ func (m *model) View() string {
}

func main() {
go func() {
fmt.Println(http.ListenAndServe("localhost:6060", nil))
}()

rand.Seed(0)

enableShader := flag.Bool("shader", false, "Enable shader")
flag.Parse()

fonts, err := crt.LoadFaces("./fonts/IosevkaTermNerdFontMono-Regular.ttf", "./fonts/IosevkaTermNerdFontMono-Bold.ttf", "./fonts/IosevkaTermNerdFontMono-Italic.ttf", 72.0, 8.0)
fonts, err := crt.LoadFaces("./fonts/IosevkaTermNerdFontMono-Regular.ttf", "./fonts/IosevkaTermNerdFontMono-Bold.ttf", "./fonts/IosevkaTermNerdFontMono-Italic.ttf", 72.0, 9.0)
if err != nil {
panic(err)
}
Expand All @@ -60,13 +68,15 @@ func main() {
}()

var lastStart int64

win.SetOnPreDraw(func(screen *ebiten.Image) {
lastStart = time.Now().UnixMicro()
})

win.SetOnPostDraw(func(screen *ebiten.Image) {
elapsed := time.Now().UnixMicro() - lastStart
if (1000 / (float64(elapsed) * 0.001)) > 500 {
return
}

fmt.Printf("Frame took %d micro seconds FPS=%.2f\n", elapsed, 1000/(float64(elapsed)*0.001))
})

Expand All @@ -79,6 +89,8 @@ func main() {
win.SetShader(lotte)
}

win.ShowTPS(true)

if err := win.Run("Simple"); err != nil {
panic(err)
}
Expand Down
11 changes: 6 additions & 5 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,36 +3,37 @@ module github.com/BigJk/crt
go 1.20

require (
github.com/charmbracelet/bubbles v0.15.0
github.com/charmbracelet/bubbletea v0.24.0
github.com/charmbracelet/lipgloss v0.7.1
github.com/hajimehoshi/ebiten/v2 v2.5.4
github.com/lucasb-eyer/go-colorful v1.2.0
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6
github.com/muesli/termenv v0.15.1
github.com/stretchr/testify v1.8.2
golang.org/x/image v0.7.0
)

require (
github.com/atotto/clipboard v0.1.4 // indirect
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/charmbracelet/bubbles v0.15.0 // indirect
github.com/charmbracelet/harmonica v0.2.0 // indirect
github.com/charmbracelet/lipgloss v0.7.1 // indirect
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/ebitengine/purego v0.3.0 // indirect
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20221017161538-93cebf72946b // indirect
github.com/jezek/xgb v1.1.0 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/mattn/go-isatty v0.0.18 // indirect
github.com/mattn/go-localereader v0.0.1 // indirect
github.com/mattn/go-runewidth v0.0.14 // indirect
github.com/muesli/cancelreader v0.2.2 // indirect
github.com/muesli/reflow v0.3.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
github.com/stretchr/testify v1.8.2 // indirect
golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56 // indirect
golang.org/x/mobile v0.0.0-20230301163155-e0f57694e12c // indirect
golang.org/x/sync v0.1.0 // indirect
golang.org/x/sys v0.6.0 // indirect
golang.org/x/sys v0.8.0 // indirect
golang.org/x/term v0.6.0 // indirect
golang.org/x/text v0.9.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
Expand Down
6 changes: 5 additions & 1 deletion go.sum
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
github.com/aymanbagabas/go-osc52 v1.0.3/go.mod h1:zT8H+Rk4VSabYN90pWyugflM3ZhpTZNC7cASDfUCdT4=
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
Expand All @@ -23,6 +24,7 @@ github.com/ebitengine/purego v0.3.0 h1:BDv9pD98k6AuGNQf3IF41dDppGBOe0F4AofvhFtBX
github.com/ebitengine/purego v0.3.0/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20221017161538-93cebf72946b h1:GgabKamyOYguHqHjSkDACcgoPIz3w0Dis/zJ1wyHHHU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20221017161538-93cebf72946b/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/hajimehoshi/bitmapfont/v2 v2.2.3 h1:jmq/TMNj352V062Tr5e3hAoipkoxCbY1JWTzor0zNps=
github.com/hajimehoshi/ebiten/v2 v2.5.4 h1:NvUU6LvVc6oc+u+rD9KfHMjruRdpNwbpalVUINNXufU=
github.com/hajimehoshi/ebiten/v2 v2.5.4/go.mod h1:mnHSOVysTr/nUZrN1lBTRqhK4NG+T9NR3JsJP2rCppk=
github.com/jezek/xgb v1.1.0 h1:wnpxJzP1+rkbGclEkmwpVFQWpuE2PUGNUzP8SbfFobk=
Expand Down Expand Up @@ -103,8 +105,9 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
Expand All @@ -122,6 +125,7 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
Expand Down
17 changes: 17 additions & 0 deletions sgr.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,12 @@ import (
"fmt"
"github.com/muesli/termenv"
"strings"
"sync"
)

var sgrMtx = &sync.Mutex{}
var sgrCache = map[string][]any{}

// extractSGR extracts an SGR ansi sequence from the beginning of the string.
func extractSGR(s string) (string, bool) {
if len(s) < 2 {
Expand Down Expand Up @@ -66,6 +70,15 @@ func parseSGR(s string) ([]any, bool) {
return nil, false
}

sgrMtx.Lock()
if cached, ok := sgrCache[s]; ok {
sgrMtx.Unlock()
return cached, true
}
sgrMtx.Unlock()

full := s

if !strings.HasSuffix(s, "m") {
return nil, false
}
Expand Down Expand Up @@ -138,5 +151,9 @@ func parseSGR(s string) ([]any, bool) {
s = s[len(code)+1:]
}

sgrMtx.Lock()
sgrCache[full] = res
sgrMtx.Unlock()

return res, len(res) > 0
}

0 comments on commit 032bbda

Please sign in to comment.