@@ -45,14 +45,22 @@ func (p *Progress) beginRender() bool {
4545}
4646
4747func (p * Progress ) consumeQueuedTrackers () {
48- if p .LengthInQueue () > 0 {
49- p .trackersActiveMutex .Lock ()
50- p .trackersInQueueMutex .Lock ()
51- p .trackersActive = append (p .trackersActive , p .trackersInQueue ... )
52- p .trackersInQueue = make ([]* Tracker , 0 )
48+ p .trackersInQueueMutex .Lock ()
49+ queueLen := len (p .trackersInQueue )
50+ if queueLen == 0 {
5351 p .trackersInQueueMutex .Unlock ()
54- p . trackersActiveMutex . Unlock ()
52+ return
5553 }
54+ // copy the slice to avoid race condition - another goroutine may append
55+ // to p.trackersInQueue while we're appending to p.trackersActive
56+ queued := make ([]* Tracker , len (p .trackersInQueue ))
57+ copy (queued , p .trackersInQueue )
58+ p .trackersInQueue = p .trackersInQueue [:0 ] // reuse slice capacity
59+ p .trackersInQueueMutex .Unlock ()
60+
61+ p .trackersActiveMutex .Lock ()
62+ p .trackersActive = append (p .trackersActive , queued ... )
63+ p .trackersActiveMutex .Unlock ()
5664}
5765
5866func (p * Progress ) endRender () {
@@ -69,8 +77,18 @@ func (p *Progress) extractDoneAndActiveTrackers() ([]*Tracker, []*Tracker) {
6977 // separate the active and done trackers
7078 var trackersActive , trackersDone []* Tracker
7179 var activeTrackersProgress int64
72- p .trackersActiveMutex .RLock ()
7380 var maxETA time.Duration
81+ var lengthDone int
82+
83+ // Get lengthDone while we have access to trackersDone
84+ p .trackersDoneMutex .RLock ()
85+ lengthDone = len (p .trackersDone )
86+ p .trackersDoneMutex .RUnlock ()
87+
88+ p .trackersActiveMutex .RLock ()
89+ // Pre-allocate slices with estimated capacity to reduce allocations
90+ trackersActive = make ([]* Tracker , 0 , len (p .trackersActive ))
91+ trackersDone = make ([]* Tracker , 0 , len (p .trackersActive )/ 4 ) // estimate ~25% done
7492 for _ , tracker := range p .trackersActive {
7593 if ! tracker .IsDone () {
7694 trackersActive = append (trackersActive , tracker )
@@ -87,7 +105,7 @@ func (p *Progress) extractDoneAndActiveTrackers() ([]*Tracker, []*Tracker) {
87105 p .sortBy .Sort (trackersActive )
88106
89107 // calculate the overall tracker's progress value
90- p .overallTracker .value = int64 (p . LengthDone () + len (trackersDone )) * 100
108+ p .overallTracker .value = int64 (lengthDone + len (trackersDone )) * 100
91109 p .overallTracker .value += activeTrackersProgress
92110 p .overallTracker .minETA = maxETA
93111 if len (trackersActive ) == 0 {
@@ -133,7 +151,13 @@ func (p *Progress) generateTrackerStrDeterminate(value int64, total int64, maxLe
133151 } else if pFinishedDotsFraction == 0 {
134152 pInProgress = ""
135153 }
136- pFinishedStrLen := text .StringWidthWithoutEscSequences (pFinished + pInProgress )
154+
155+ // Use strings.Builder to avoid temporary string allocation
156+ var combined strings.Builder
157+ combined .Grow (len (pFinished ) + len (pInProgress ))
158+ combined .WriteString (pFinished )
159+ combined .WriteString (pInProgress )
160+ pFinishedStrLen := text .StringWidthWithoutEscSequences (combined .String ())
137161 if pFinishedStrLen < maxLen {
138162 pUnfinished = strings .Repeat (p .style .Chars .Unfinished , maxLen - pFinishedStrLen )
139163 }
@@ -181,16 +205,16 @@ func (p *Progress) moveCursorToTheTop(out *strings.Builder) {
181205 }
182206}
183207
184- func (p * Progress ) renderPinnedMessages (out * strings.Builder ) {
208+ func (p * Progress ) renderPinnedMessages (out * strings.Builder , hint renderHint ) {
185209 p .pinnedMessageMutex .RLock ()
186210 defer p .pinnedMessageMutex .RUnlock ()
187211
188212 numLines := len (p .pinnedMessages )
189213 for _ , msg := range p .pinnedMessages {
190214 msg = strings .TrimSpace (msg )
191215 msg = p .style .Colors .Pinned .Sprint (msg )
192- if width := p . getTerminalWidth (); width > 0 {
193- msg = text .Trim (msg , width )
216+ if hint . terminalWidth > 0 {
217+ msg = text .Trim (msg , hint . terminalWidth )
194218 }
195219 out .WriteString (msg )
196220 out .WriteRune ('\n' )
@@ -202,8 +226,11 @@ func (p *Progress) renderPinnedMessages(out *strings.Builder) {
202226
203227func (p * Progress ) renderTracker (out * strings.Builder , t * Tracker , hint renderHint ) {
204228 message := t .message ()
205- message = strings .ReplaceAll (message , "\t " , " " )
206- message = strings .ReplaceAll (message , "\r " , "" ) // replace with text.ProcessCRLF?
229+ // Optimize: only process if message contains tabs or carriage returns
230+ if strings .ContainsAny (message , "\t \r " ) {
231+ message = strings .ReplaceAll (message , "\t " , " " )
232+ message = strings .ReplaceAll (message , "\r " , "" )
233+ }
207234 if p .lengthMessage > 0 {
208235 messageLen := text .StringWidthWithoutEscSequences (message )
209236 if messageLen < p .lengthMessage {
@@ -217,21 +244,21 @@ func (p *Progress) renderTracker(out *strings.Builder, t *Tracker, hint renderHi
217244 tOut .Grow (p .lengthProgressOverall )
218245 if hint .isOverallTracker {
219246 if ! t .IsDone () {
220- hint := renderHint {hideValue : true , isOverallTracker : true }
247+ hint := renderHint {hideValue : true , isOverallTracker : true , terminalWidth : hint . terminalWidth }
221248 p .renderTrackerProgress (tOut , t , message , p .generateTrackerStr (t , p .lengthProgressOverall , hint ), hint )
222249 }
223250 } else {
224251 if t .IsDone () {
225252 p .renderTrackerDone (tOut , t , message )
226253 } else {
227- hint := renderHint {hideTime : ! p .style .Visibility .Time , hideValue : ! p .style .Visibility .Value }
254+ hint := renderHint {hideTime : ! p .style .Visibility .Time , hideValue : ! p .style .Visibility .Value , terminalWidth : hint . terminalWidth }
228255 p .renderTrackerProgress (tOut , t , message , p .generateTrackerStr (t , p .lengthProgress , hint ), hint )
229256 }
230257 }
231258
232259 outStr := tOut .String ()
233- if width := p . getTerminalWidth (); width > 0 {
234- outStr = text .Trim (outStr , width )
260+ if hint . terminalWidth > 0 {
261+ outStr = text .Trim (outStr , hint . terminalWidth )
235262 }
236263 out .WriteString (outStr )
237264 out .WriteRune ('\n' )
@@ -298,6 +325,10 @@ func (p *Progress) renderTrackers(lastRenderLength int) int {
298325 return 0
299326 }
300327
328+ // Cache terminal width once per render cycle to avoid repeated mutex locks
329+ terminalWidth := p .getTerminalWidth ()
330+ hint := renderHint {terminalWidth : terminalWidth }
331+
301332 // buffer all output into a strings.Builder object
302333 var out strings.Builder
303334 out .Grow (lastRenderLength )
@@ -308,11 +339,12 @@ func (p *Progress) renderTrackers(lastRenderLength int) int {
308339 }
309340
310341 // render the trackers that are done, and then the ones that are active
311- p .renderTrackersDoneAndActive (& out )
342+ p .renderTrackersDoneAndActive (& out , hint )
312343
313344 // render the overall tracker
314345 if p .style .Visibility .TrackerOverall {
315- p .renderTracker (& out , p .overallTracker , renderHint {isOverallTracker : true })
346+ overallHint := renderHint {isOverallTracker : true , terminalWidth : terminalWidth }
347+ p .renderTracker (& out , p .overallTracker , overallHint )
316348 }
317349
318350 // write the text to the output writer
@@ -326,13 +358,13 @@ func (p *Progress) renderTrackers(lastRenderLength int) int {
326358 return out .Len ()
327359}
328360
329- func (p * Progress ) renderTrackersDoneAndActive (out * strings.Builder ) {
361+ func (p * Progress ) renderTrackersDoneAndActive (out * strings.Builder , hint renderHint ) {
330362 // find the currently "active" and "done" trackers
331363 trackersActive , trackersDone := p .extractDoneAndActiveTrackers ()
332364
333365 // sort and render the done trackers
334366 for _ , tracker := range trackersDone {
335- p .renderTracker (out , tracker , renderHint {} )
367+ p .renderTracker (out , tracker , hint )
336368 }
337369 p .trackersDoneMutex .Lock ()
338370 p .trackersDone = append (p .trackersDone , trackersDone ... )
@@ -350,12 +382,12 @@ func (p *Progress) renderTrackersDoneAndActive(out *strings.Builder) {
350382
351383 // render pinned messages
352384 if len (trackersActive ) > 0 && p .style .Visibility .Pinned {
353- p .renderPinnedMessages (out )
385+ p .renderPinnedMessages (out , hint )
354386 }
355387
356388 // sort and render the active trackers
357389 for _ , tracker := range trackersActive {
358- p .renderTracker (out , tracker , renderHint {} )
390+ p .renderTracker (out , tracker , hint )
359391 }
360392 p .trackersActiveMutex .Lock ()
361393 p .trackersActive = trackersActive
0 commit comments