mirror of
https://github.com/BigJk/crt.git
synced 2026-02-06 10:47:25 +00:00
Performance optimization.
This commit is contained in:
parent
c18e3badb7
commit
032bbda893
54
crt.go
54
crt.go
@ -14,6 +14,7 @@ import (
|
||||
"image/color"
|
||||
"io"
|
||||
"sync"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// colorCache is the ansi color cache.
|
||||
@ -53,6 +54,7 @@ type Window struct {
|
||||
onPostDraw func(screen *ebiten.Image)
|
||||
|
||||
// Other
|
||||
seqBuffer []byte
|
||||
showTps bool
|
||||
fonts Fonts
|
||||
bgColors *image.RGBA
|
||||
@ -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{
|
||||
@ -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
|
||||
@ -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:
|
||||
@ -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 {
|
||||
@ -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 {
|
||||
@ -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++ {
|
||||
@ -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()
|
||||
}
|
||||
@ -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
|
||||
|
||||
|
||||
23
csi.go
23
csi.go
@ -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
|
||||
}
|
||||
@ -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
|
||||
|
||||
@ -11,6 +11,8 @@ import (
|
||||
"github.com/hajimehoshi/ebiten/v2"
|
||||
"image/color"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
_ "net/http/pprof"
|
||||
"time"
|
||||
)
|
||||
|
||||
@ -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)
|
||||
}
|
||||
@ -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))
|
||||
})
|
||||
|
||||
@ -79,6 +89,8 @@ func main() {
|
||||
win.SetShader(lotte)
|
||||
}
|
||||
|
||||
win.ShowTPS(true)
|
||||
|
||||
if err := win.Run("Simple"); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
11
go.mod
11
go.mod
@ -3,24 +3,26 @@ 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
|
||||
@ -28,11 +30,10 @@ require (
|
||||
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
|
||||
|
||||
6
go.sum
6
go.sum
@ -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=
|
||||
@ -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=
|
||||
@ -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=
|
||||
@ -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=
|
||||
|
||||
17
sgr.go
17
sgr.go
@ -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 {
|
||||
@ -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
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user