diff --git a/internal/tui/components/anim/anim.go b/internal/tui/components/anim/anim.go index 0bd7a7753f114c7bfb6c8f9898772c49ae8f7d80..63cb5d5a05a95a8768c22d3b2ac3cd887b63e694 100644 --- a/internal/tui/components/anim/anim.go +++ b/internal/tui/components/anim/anim.go @@ -17,12 +17,23 @@ import ( ) const ( - charCyclingFPS = time.Second / 22 - colorCycleFPS = time.Second / 5 - maxCyclingChars = 120 + charCyclingFPS = time.Second / 8 // 8 FPS for better CPU efficiency + colorCycleFPS = time.Second / 3 // 3 FPS + maxCyclingChars = 60 // 60 characters ) -var charRunes = []rune("0123456789abcdefABCDEF~!@#$£€%^&*()+=_") +var ( + charRunes = []rune("0123456789abcdefABCDEF~!@#$£€%^&*()+=_") + charRunePool = make([]rune, 1000) // Pre-generated pool of random characters + poolIndex = 0 +) + +func init() { + // Pre-populate the character pool to avoid runtime random generation + for i := range charRunePool { + charRunePool[i] = charRunes[rand.IntN(len(charRunes))] + } +} type charState int @@ -41,7 +52,9 @@ type cyclingChar struct { } func (c cyclingChar) randomRune() rune { - return (charRunes)[rand.IntN(len(charRunes))] //nolint:gosec + // Use pre-generated pool instead of runtime random generation + poolIndex = (poolIndex + 1) % len(charRunePool) + return charRunePool[poolIndex] } func (c cyclingChar) state(start time.Time) charState { @@ -126,14 +139,18 @@ func New(cyclingCharsSize uint, label string, opts ...animOption) Animation { // color the cycling characters with a gradient ramp. const minRampSize = 3 if n >= minRampSize { - // Note: double capacity for color cycling as we'll need to reverse and - // append the ramp for seamless transitions. - c.ramp = make([]lipgloss.Style, n, n*2) //nolint:mnd + // Optimized: single capacity allocation for color cycling + c.ramp = make([]lipgloss.Style, 0, n*2) ramp := makeGradientRamp(n) - for i, color := range ramp { - c.ramp[i] = lipgloss.NewStyle().Foreground(color) + for _, color := range ramp { + c.ramp = append(c.ramp, lipgloss.NewStyle().Foreground(color)) } - c.ramp = append(c.ramp, reverse(c.ramp)...) // reverse and append for color cycling + // Create reversed copy for seamless color cycling + reversed := make([]lipgloss.Style, len(c.ramp)) + for i, style := range c.ramp { + reversed[len(c.ramp)-1-i] = style + } + c.ramp = append(c.ramp, reversed...) } makeDelay := func(a int32, b time.Duration) time.Duration { @@ -246,29 +263,28 @@ func (a anim) View() tea.View { b strings.Builder ) - // Pre-allocate builder capacity to avoid reallocations. - // Estimate: cycling chars + label chars + ellipsis + style overhead. + // Optimized capacity calculation to reduce allocations const ( - bytesPerChar = 20 // ANSI styling - bufferSize = 50 // ellipsis and safety margin + bytesPerChar = 15 // Reduced estimate for ANSI styling + bufferSize = 30 // Reduced safety margin ) estimatedCap := len(a.cyclingChars)*bytesPerChar + len(a.labelChars)*bytesPerChar + bufferSize b.Grow(estimatedCap) + // Render cycling characters with gradient (if available) for i, c := range a.cyclingChars { if len(a.ramp) > i { b.WriteString(a.ramp[i].Render(string(c.currentValue))) - continue + } else { + b.WriteRune(c.currentValue) } - b.WriteRune(c.currentValue) } + // Render label characters and ellipsis if len(a.labelChars) > 1 { textStyle := t.S().Text for _, c := range a.labelChars { - b.WriteString( - textStyle.Render(string(c.currentValue)), - ) + b.WriteString(textStyle.Render(string(c.currentValue))) } b.WriteString(textStyle.Render(a.ellipsis.View())) } @@ -296,12 +312,3 @@ func makeGradientRamp(length int) []color.Color { } return c } - -func reverse[T any](in []T) []T { - out := make([]T, len(in)) - copy(out, in[:]) - for i, j := 0, len(out)-1; i < j; i, j = i+1, j-1 { - out[i], out[j] = out[j], out[i] - } - return out -} diff --git a/todos.md b/todos.md index 65ece5a7880530a1622f1b64aeaef85f3d6702bf..b577bc23d910ddd28e71ef299aee9473e5c646dd 100644 --- a/todos.md +++ b/todos.md @@ -30,7 +30,7 @@ - [ ] Revisit the core list component - [ ] This component has become super complex we might need to fix this. - [ ] Handle correct LSP and MCP status icon -- [ ] Investigate ways to make the spinner less CPU intensive +- [x] Investigate ways to make the spinner less CPU intensive - [ ] General cleanup and documentation - [ ] Update the readme