@@ -17,12 +17,23 @@ import (
)
const (
- charCyclingFPS = time.Second / 22
- colorCycleFPS = time.Second / 5
- maxCyclingChars = 120
+ charCyclingFPS = time.Second / 8 // Reduced from 22 to 8 FPS for better CPU efficiency
+ colorCycleFPS = time.Second / 3 // Reduced from 5 to 3 FPS
+ maxCyclingChars = 60 // Reduced from 120 to 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
-}
@@ -0,0 +1,63 @@
+package anim
+
+import (
+ "testing"
+ "time"
+)
+
+func BenchmarkAnimationUpdate(b *testing.B) {
+ anim := New(30, "Loading test data")
+
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ // Simulate character cycling update
+ _, _ = anim.Update(StepCharsMsg{id: anim.ID()})
+ }
+}
+
+func BenchmarkAnimationView(b *testing.B) {
+ anim := New(30, "Loading test data")
+
+ // Initialize with some cycling
+ for i := 0; i < 10; i++ {
+ anim.Update(StepCharsMsg{id: anim.ID()})
+ }
+
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ _ = anim.View()
+ }
+}
+
+func BenchmarkRandomRune(b *testing.B) {
+ c := cyclingChar{}
+
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ _ = c.randomRune()
+ }
+}
+
+func TestAnimationPerformance(t *testing.T) {
+ anim := New(30, "Performance test")
+
+ start := time.Now()
+ iterations := 1000
+
+ // Simulate rapid updates
+ for i := 0; i < iterations; i++ {
+ anim.Update(StepCharsMsg{id: anim.ID()})
+ _ = anim.View()
+ }
+
+ duration := time.Since(start)
+ avgPerUpdate := duration / time.Duration(iterations)
+
+ // Should complete 1000 updates in reasonable time
+ if avgPerUpdate > time.Millisecond {
+ t.Errorf("Animation update too slow: %v per update (should be < 1ms)", avgPerUpdate)
+ }
+
+ t.Logf("Animation performance: %v per update (%d updates in %v)",
+ avgPerUpdate, iterations, duration)
+}
@@ -25,7 +25,7 @@
- [ ] Implement responsive mode
- [ ] Revisit the core list component
- [ ] This component has become super complex we might need to fix this.
-- [ ] 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