on main help sign in
Merge pull request #21 from charmbracelet/anim-perf

Anim perf

commit57f3c420577d2a15cb8111263f35fe2e4b038e55 authorKujtim Hoxha <kujtimii.h@gmail.com> dateSat 7 Jun 2025, 15:39 (1 year ago) parent6188effcec2792
1 file changed+22−10
internal/tui/components/anim/anim.go+22−10
internal/tui/components/anim/anim.go+22−10
@@ -3,7 +3,7 @@ package anim
33import (
44 "fmt"
55 "image/color"
6− "math/rand"
6+ "math/rand/v2"
77 "strings"
88 "time"
99
@@ -41,7 +41,7 @@ type cyclingChar struct {
4141}
4242
4343func (c cyclingChar) randomRune() rune {
44− return (charRunes)[rand.Intn(len(charRunes))] //nolint:gosec
44+ return (charRunes)[rand.IntN(len(charRunes))] //nolint:gosec
4545}
4646
4747func (c cyclingChar) state(start time.Time) charState {
@@ -137,7 +137,7 @@ func New(cyclingCharsSize uint, label string, opts ...animOption) Animation {
137137 }
138138
139139 makeDelay := func(a int32, b time.Duration) time.Duration {
140− return time.Duration(rand.Int31n(a)) * (time.Millisecond * b) //nolint:gosec
140+ return time.Duration(rand.Int32N(a)) * (time.Millisecond * b) //nolint:gosec
141141 }
142142
143143 makeInitialDelay := func() time.Duration {
@@ -226,22 +226,34 @@ func (a anim) ID() string {
226226}
227227
228228func (a *anim) updateChars(chars *[]cyclingChar) {
229− for i, c := range *chars {
229+ charSlice := *chars // dereference to avoid repeated pointer access
230+ for i, c := range charSlice {
230231 switch c.state(a.start) {
231232 case charInitialState:
232− (*chars)[i].currentValue = '.'
233+ charSlice[i].currentValue = '.'
233234 case charCyclingState:
234− (*chars)[i].currentValue = c.randomRune()
235+ charSlice[i].currentValue = c.randomRune()
235236 case charEndOfLifeState:
236− (*chars)[i].currentValue = c.finalValue
237+ charSlice[i].currentValue = c.finalValue
237238 }
238239 }
239240}
240241
241242// View renders the animation.
242243func (a anim) View() tea.View {
243− t := styles.CurrentTheme()
244− var b strings.Builder
244+ var (
245+ t = styles.CurrentTheme()
246+ b strings.Builder
247+ )
248+
249+ // Pre-allocate builder capacity to avoid reallocations.
250+ // Estimate: cycling chars + label chars + ellipsis + style overhead.
251+ const (
252+ bytesPerChar = 20 // ANSI styling
253+ bufferSize = 50 // ellipsis and safety margin
254+ )
255+ estimatedCap := len(a.cyclingChars)*bytesPerChar + len(a.labelChars)*bytesPerChar + bufferSize
256+ b.Grow(estimatedCap)
245257
246258 for i, c := range a.cyclingChars {
247259 if len(a.ramp) > i {
@@ -258,7 +270,7 @@ func (a anim) View() tea.View {
258270 textStyle.Render(string(c.currentValue)),
259271 )
260272 }
261− return tea.NewView(b.String() + textStyle.Render(a.ellipsis.View()))
273+ b.WriteString(textStyle.Render(a.ellipsis.View()))
262274 }
263275
264276 return tea.NewView(b.String())