diff --git a/bar.go b/bar.go index 4991f4f..2d36361 100644 --- a/bar.go +++ b/bar.go @@ -29,7 +29,7 @@ recoveredPanic interface{} } -type extenderFunc func(in io.Reader, reqWidth int, st decor.Statistics) (out io.Reader, lines int) +type extenderFunc func(rows []io.Reader, width int, stat decor.Statistics) []io.Reader // bState is actual bar's state. type bState struct { @@ -62,9 +62,8 @@ } type renderFrame struct { - reader io.Reader - lines int - shutdown bool + rows []io.Reader + shutdown int } func newBar(container *Progress, bs *bState) *Bar { @@ -359,8 +358,7 @@ func (b *Bar) render(tw int) { select { case b.operateState <- func(s *bState) { - var reader io.Reader - var lines int + var rows []io.Reader stat := newStatistics(tw, s) defer func() { // recovering if user defined decorator panics for example @@ -371,36 +369,37 @@ } s.aborted = !s.completed s.extender = makePanicExtender(p) - reader, lines = s.extender(nil, s.reqWidth, stat) b.recoveredPanic = p } - frame := renderFrame{ - reader: reader, - lines: lines + 1, - shutdown: s.completed || s.aborted, - } - if frame.shutdown { + if s.extender != nil { + rows = s.extender(rows, s.reqWidth, stat) + } + frame := &renderFrame{ + rows: rows, + } + if s.completed || s.aborted { b.cancel() - } - b.frameCh <- &frame + frame.shutdown++ + } + b.frameCh <- frame }() if b.recoveredPanic == nil { - reader = s.draw(stat) - } - reader, lines = s.extender(reader, s.reqWidth, stat) - }: - case <-b.done: - var reader io.Reader - var lines int - stat, s := newStatistics(tw, b.bs), b.bs + rows = append(rows, s.draw(stat)) + } + }: + case <-b.done: + var rows []io.Reader + s, stat := b.bs, newStatistics(tw, b.bs) if b.recoveredPanic == nil { - reader = s.draw(stat) - } - reader, lines = s.extender(reader, s.reqWidth, stat) - b.frameCh <- &renderFrame{ - reader: reader, - lines: lines + 1, - } + rows = append(rows, s.draw(stat)) + } + if s.extender != nil { + rows = s.extender(rows, s.reqWidth, stat) + } + frame := &renderFrame{ + rows: rows, + } + b.frameCh <- frame } } @@ -596,11 +595,11 @@ func makePanicExtender(p interface{}) extenderFunc { pstr := fmt.Sprint(p) - return func(_ io.Reader, _ int, st decor.Statistics) (io.Reader, int) { - mr := io.MultiReader( - strings.NewReader(runewidth.Truncate(pstr, st.AvailableWidth, "…")), - strings.NewReader("\n"), + return func(rows []io.Reader, _ int, stat decor.Statistics) []io.Reader { + r := io.MultiReader( + strings.NewReader(runewidth.Truncate(pstr, stat.AvailableWidth, "…")), + bytes.NewReader([]byte("\n")), ) - return mr, 0 - } -} + return append(rows, r) + } +} diff --git a/bar_option.go b/bar_option.go index 8599f0a..50095ce 100644 --- a/bar_option.go +++ b/bar_option.go @@ -131,9 +131,17 @@ func makeExtenderFunc(filler BarFiller) extenderFunc { buf := new(bytes.Buffer) - return func(r io.Reader, reqWidth int, st decor.Statistics) (io.Reader, int) { - filler.Fill(buf, reqWidth, st) - return io.MultiReader(r, buf), bytes.Count(buf.Bytes(), []byte("\n")) + return func(rows []io.Reader, width int, stat decor.Statistics) []io.Reader { + buf.Reset() + filler.Fill(buf, width, stat) + for { + b, err := buf.ReadBytes('\n') + if err != nil { + break + } + rows = append(rows, bytes.NewReader(b)) + } + return rows } } diff --git a/priority_queue.go b/priority_queue.go index 29d9bd5..152482e 100644 --- a/priority_queue.go +++ b/priority_queue.go @@ -6,7 +6,8 @@ func (pq priorityQueue) Len() int { return len(pq) } func (pq priorityQueue) Less(i, j int) bool { - return pq[i].priority < pq[j].priority + // less priority pops first + return pq[i].priority > pq[j].priority } func (pq priorityQueue) Swap(i, j int) { diff --git a/progress.go b/progress.go index 56c4316..b0ad1bf 100644 --- a/progress.go +++ b/progress.go @@ -12,7 +12,6 @@ "time" "github.com/vbauerster/mpb/v7/cwriter" - "github.com/vbauerster/mpb/v7/decor" ) const ( @@ -41,6 +40,7 @@ // following are provided/overrided by user idCount int reqWidth int + popPriority int popCompleted bool outputDiscarded bool rr time.Duration @@ -64,10 +64,11 @@ // method has been called. func NewWithContext(ctx context.Context, options ...ContainerOption) *Progress { s := &pState{ - bHeap: priorityQueue{}, - rr: prr, - queueBars: make(map[*Bar]*Bar), - output: os.Stdout, + bHeap: priorityQueue{}, + rr: prr, + queueBars: make(map[*Bar]*Bar), + output: os.Stdout, + popPriority: math.MinInt32, } for _, opt := range options { @@ -239,42 +240,52 @@ syncWidth(s.pMatrix) syncWidth(s.aMatrix) - tw, err := cw.GetWidth() + width, height, err := cw.GetTermSize() if err != nil { - tw = s.reqWidth + width = s.reqWidth } for i := 0; i < s.bHeap.Len(); i++ { bar := s.bHeap[i] - go bar.render(tw) - } - - return s.flush(cw) -} - -func (s *pState) flush(cw *cwriter.Writer) error { - var lines int + go bar.render(width) + } + + return s.flush(cw, height) +} + +func (s *pState) flush(cw *cwriter.Writer, height int) error { + var popCount int + rows := make([]io.Reader, 0, s.bHeap.Len()) pool := make([]*Bar, 0, s.bHeap.Len()) for s.bHeap.Len() > 0 { + var frameRowsUsed int b := heap.Pop(&s.bHeap).(*Bar) frame := <-b.frameCh - lines += frame.lines - _, err := cw.ReadFrom(frame.reader) - if err != nil { - return err - } - if frame.shutdown { + for i := len(frame.rows) - 1; i >= 0; i-- { + if len(rows) == height { + break + } + rows = append(rows, frame.rows[i]) + frameRowsUsed++ + } + if frame.shutdown != 0 { b.Wait() // waiting for b.done, so it's safe to read b.bs - var toDrop bool + drop := b.bs.dropOnComplete if qb, ok := s.queueBars[b]; ok { delete(s.queueBars, b) qb.priority = b.priority + qb.bs.dropOnComplete = drop pool = append(pool, qb) - toDrop = true + drop = true } else if s.popCompleted && !b.bs.noPop { - lines -= frame.lines - toDrop = true - } - if toDrop || b.bs.dropOnComplete { + if frame.shutdown > 1 { + popCount += frameRowsUsed + drop = true + } else { + s.popPriority++ + b.priority = s.popPriority + } + } + if drop { s.heapUpdated = true continue } @@ -286,7 +297,14 @@ heap.Push(&s.bHeap, b) } - return cw.Flush(lines) + for i := len(rows) - 1; i >= 0; i-- { + _, err := cw.ReadFrom(rows[i]) + if err != nil { + return err + } + } + + return cw.Flush(len(rows) - popCount) } func (s *pState) newTicker(done <-chan struct{}) chan time.Time { @@ -352,7 +370,6 @@ reqWidth: s.reqWidth, total: total, filler: filler, - extender: func(r io.Reader, _ int, _ decor.Statistics) (io.Reader, int) { return r, 0 }, debugOut: s.debugOut, } @@ -369,10 +386,6 @@ if bs.middleware != nil { bs.filler = bs.middleware(filler) bs.middleware = nil - } - - if s.popCompleted && !bs.noPop { - bs.priority = -(math.MaxInt32 - s.idCount) } for i := 0; i < len(bs.buffers); i++ {