on main help sign in
Merge pull request #21 from charmbracelet/anim-perf
57f3c42Kujtim Hoxha
internal/tui/components/anim/anim.go+22−10
@@ -3,7 +3,7 @@ package anim
3import (
3import (
4 "fmt"
4 "fmt"
5 "image/color"
5 "image/color"
6− "math/rand"
6+ "math/rand/v2"
7 "strings"
7 "strings"
8 "time"
8 "time"
9
9
@@ -41,7 +41,7 @@ type cyclingChar struct {
41}
41}
42
42
43func (c cyclingChar) randomRune() rune {
43func (c cyclingChar) randomRune() rune {
44− return (charRunes)[rand.Intn(len(charRunes))] //nolint:gosec
44+ return (charRunes)[rand.IntN(len(charRunes))] //nolint:gosec
45}
45}
46
46
47func (c cyclingChar) state(start time.Time) charState {
47func (c cyclingChar) state(start time.Time) charState {
@@ -137,7 +137,7 @@ func New(cyclingCharsSize uint, label string, opts ...animOption) Animation {
137 }
137 }
138
138
139 makeDelay := func(a int32, b time.Duration) time.Duration {
139 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
141 }
141 }
142
142
143 makeInitialDelay := func() time.Duration {
143 makeInitialDelay := func() time.Duration {
@@ -226,22 +226,34 @@ func (a anim) ID() string {
226}
226}
227
227
228func (a *anim) updateChars(chars *[]cyclingChar) {
228func (a *anim) updateChars(chars *[]cyclingChar) {
229+ charSlice := *chars // dereference to avoid repeated pointer access
229− for i, c := range *chars {
230+ for i, c := range charSlice {
230 switch c.state(a.start) {
231 switch c.state(a.start) {
231 case charInitialState:
232 case charInitialState:
232− (*chars)[i].currentValue = '.'
233+ charSlice[i].currentValue = '.'
233 case charCyclingState:
234 case charCyclingState:
234− (*chars)[i].currentValue = c.randomRune()
235+ charSlice[i].currentValue = c.randomRune()
235 case charEndOfLifeState:
236 case charEndOfLifeState:
236− (*chars)[i].currentValue = c.finalValue
237+ charSlice[i].currentValue = c.finalValue
237 }
238 }
238 }
239 }
239}
240}
240
241
241// View renders the animation.
242// View renders the animation.
242func (a anim) View() tea.View {
243func (a anim) View() tea.View {
244+ var (
243− t := styles.CurrentTheme()
245+ t = styles.CurrentTheme()
244− var b strings.Builder
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)
245
257
246 for i, c := range a.cyclingChars {
258 for i, c := range a.cyclingChars {
247 if len(a.ramp) > i {
259 if len(a.ramp) > i {
@@ -258,7 +270,7 @@ func (a anim) View() tea.View {
258 textStyle.Render(string(c.currentValue)),
270 textStyle.Render(string(c.currentValue)),
259 )
271 )
260 }
272 }
261− return tea.NewView(b.String() + textStyle.Render(a.ellipsis.View()))
273+ b.WriteString(textStyle.Render(a.ellipsis.View()))
262 }
274 }
263
275
264 return tea.NewView(b.String())
276 return tea.NewView(b.String())