| 21 | 21 |
|
| 22 | 22 |
// Progress represents the container that renders Progress bars
|
| 23 | 23 |
type Progress struct {
|
| 24 | |
wg *sync.WaitGroup
|
| 25 | 24 |
uwg *sync.WaitGroup
|
|
25 |
cwg *sync.WaitGroup
|
|
26 |
bwg *sync.WaitGroup
|
| 26 | 27 |
operateState chan func(*pState)
|
| 27 | 28 |
done chan struct{}
|
| 28 | 29 |
}
|
|
| 31 | 32 |
bHeap *priorityQueue
|
| 32 | 33 |
shutdownPending []*Bar
|
| 33 | 34 |
heapUpdated bool
|
| 34 | |
zeroWait bool
|
| 35 | 35 |
idCounter int
|
| 36 | 36 |
width int
|
| 37 | 37 |
format string
|
|
| 39 | 39 |
cw *cwriter.Writer
|
| 40 | 40 |
pMatrix map[int][]chan int
|
| 41 | 41 |
aMatrix map[int][]chan int
|
|
42 |
forceRefreshCh chan time.Time
|
| 42 | 43 |
|
| 43 | 44 |
// following are provided/overrided by user
|
| 44 | 45 |
ctx context.Context
|
|
| 55 | 56 |
pq := make(priorityQueue, 0)
|
| 56 | 57 |
heap.Init(&pq)
|
| 57 | 58 |
s := &pState{
|
| 58 | |
ctx: context.Background(),
|
| 59 | |
bHeap: &pq,
|
| 60 | |
width: pwidth,
|
| 61 | |
cw: cwriter.New(os.Stdout),
|
| 62 | |
rr: prr,
|
| 63 | |
waitBars: make(map[*Bar]*Bar),
|
| 64 | |
debugOut: ioutil.Discard,
|
|
59 |
ctx: context.Background(),
|
|
60 |
bHeap: &pq,
|
|
61 |
width: pwidth,
|
|
62 |
cw: cwriter.New(os.Stdout),
|
|
63 |
rr: prr,
|
|
64 |
waitBars: make(map[*Bar]*Bar),
|
|
65 |
debugOut: ioutil.Discard,
|
|
66 |
forceRefreshCh: make(chan time.Time),
|
| 65 | 67 |
}
|
| 66 | 68 |
|
| 67 | 69 |
for _, opt := range options {
|
|
| 72 | 74 |
|
| 73 | 75 |
p := &Progress{
|
| 74 | 76 |
uwg: s.uwg,
|
| 75 | |
wg: new(sync.WaitGroup),
|
|
77 |
cwg: new(sync.WaitGroup),
|
|
78 |
bwg: new(sync.WaitGroup),
|
| 76 | 79 |
operateState: make(chan func(*pState)),
|
| 77 | 80 |
done: make(chan struct{}),
|
| 78 | 81 |
}
|
|
82 |
p.cwg.Add(1)
|
| 79 | 83 |
go p.serve(s)
|
| 80 | 84 |
return p
|
| 81 | 85 |
}
|
|
| 96 | 100 |
|
| 97 | 101 |
// Add creates a bar which renders itself by provided filler.
|
| 98 | 102 |
func (p *Progress) Add(total int64, filler Filler, options ...BarOption) *Bar {
|
| 99 | |
p.wg.Add(1)
|
|
103 |
p.bwg.Add(1)
|
| 100 | 104 |
result := make(chan *Bar)
|
| 101 | 105 |
select {
|
| 102 | 106 |
case p.operateState <- func(s *pState) {
|
| 103 | |
b := newBar(s.ctx, p.wg, filler, s.idCounter, s.width, total, options...)
|
|
107 |
b := newBar(s.ctx, p.bwg, filler, s.idCounter, s.width, total, options...)
|
| 104 | 108 |
if b.runningBar != nil {
|
| 105 | 109 |
s.waitBars[b.runningBar] = b
|
| 106 | 110 |
} else {
|
|
| 112 | 116 |
}:
|
| 113 | 117 |
return <-result
|
| 114 | 118 |
case <-p.done:
|
| 115 | |
p.wg.Done()
|
|
119 |
p.bwg.Done()
|
| 116 | 120 |
return nil
|
| 117 | 121 |
}
|
| 118 | 122 |
}
|
|
| 156 | 160 |
}
|
| 157 | 161 |
}
|
| 158 | 162 |
|
| 159 | |
// Wait first waits for user provided *sync.WaitGroup, if any, then
|
| 160 | |
// waits far all bars to complete and finally shutdowns master goroutine.
|
|
163 |
// Wait waits far all bars to complete and finally shutdowns container.
|
| 161 | 164 |
// After this method has been called, there is no way to reuse *Progress
|
| 162 | 165 |
// instance.
|
| 163 | 166 |
func (p *Progress) Wait() {
|
| 164 | 167 |
if p.uwg != nil {
|
|
168 |
// wait for user wg
|
| 165 | 169 |
p.uwg.Wait()
|
| 166 | 170 |
}
|
| 167 | 171 |
|
| 168 | |
p.wg.Wait()
|
| 169 | |
|
| 170 | |
select {
|
| 171 | |
case p.operateState <- func(s *pState) { s.zeroWait = true }:
|
| 172 | |
<-p.done
|
| 173 | |
case <-p.done:
|
| 174 | |
}
|
| 175 | |
}
|
| 176 | |
|
| 177 | |
func (s *pState) updateSyncMatrix() {
|
| 178 | |
s.pMatrix = make(map[int][]chan int)
|
| 179 | |
s.aMatrix = make(map[int][]chan int)
|
| 180 | |
for i := 0; i < s.bHeap.Len(); i++ {
|
| 181 | |
bar := (*s.bHeap)[i]
|
| 182 | |
table := bar.wSyncTable()
|
| 183 | |
pRow, aRow := table[0], table[1]
|
| 184 | |
|
| 185 | |
for i, ch := range pRow {
|
| 186 | |
s.pMatrix[i] = append(s.pMatrix[i], ch)
|
| 187 | |
}
|
| 188 | |
|
| 189 | |
for i, ch := range aRow {
|
| 190 | |
s.aMatrix[i] = append(s.aMatrix[i], ch)
|
| 191 | |
}
|
| 192 | |
}
|
| 193 | |
}
|
| 194 | |
|
| 195 | |
func (s *pState) render(tw int) {
|
|
172 |
// wait for bars to quit, if any
|
|
173 |
p.bwg.Wait()
|
|
174 |
|
|
175 |
close(p.done)
|
|
176 |
|
|
177 |
// wait for container to quit
|
|
178 |
p.cwg.Wait()
|
|
179 |
}
|
|
180 |
|
|
181 |
func (p *Progress) serve(s *pState) {
|
|
182 |
defer p.cwg.Done()
|
|
183 |
|
|
184 |
manualOrTickCh, cleanUp := s.manualOrTick()
|
|
185 |
defer cleanUp()
|
|
186 |
|
|
187 |
refreshCh := fanInRefreshSrc(p.done, s.forceRefreshCh, manualOrTickCh)
|
|
188 |
|
|
189 |
for {
|
|
190 |
select {
|
|
191 |
case op := <-p.operateState:
|
|
192 |
op(s)
|
|
193 |
case _, ok := <-refreshCh:
|
|
194 |
if !ok {
|
|
195 |
if s.shutdownNotifier != nil {
|
|
196 |
close(s.shutdownNotifier)
|
|
197 |
}
|
|
198 |
return
|
|
199 |
}
|
|
200 |
tw, err := s.cw.GetWidth()
|
|
201 |
if err != nil {
|
|
202 |
tw = s.width
|
|
203 |
}
|
|
204 |
s.render(p.done, tw)
|
|
205 |
}
|
|
206 |
}
|
|
207 |
}
|
|
208 |
|
|
209 |
func (s *pState) render(done <-chan struct{}, tw int) {
|
| 196 | 210 |
if s.heapUpdated {
|
| 197 | 211 |
s.updateSyncMatrix()
|
| 198 | 212 |
s.heapUpdated = false
|
|
| 205 | 219 |
go bar.render(s.debugOut, tw)
|
| 206 | 220 |
}
|
| 207 | 221 |
|
| 208 | |
if err := s.flush(s.bHeap.Len()); err != nil {
|
|
222 |
if err := s.flush(done, s.bHeap.Len()); err != nil {
|
| 209 | 223 |
fmt.Fprintf(s.debugOut, "%s %s %v\n", "[mpb]", time.Now(), err)
|
| 210 | 224 |
}
|
| 211 | 225 |
}
|
| 212 | 226 |
|
| 213 | |
func (s *pState) flush(lineCount int) error {
|
|
227 |
func (s *pState) flush(done <-chan struct{}, lineCount int) error {
|
| 214 | 228 |
for s.bHeap.Len() > 0 {
|
| 215 | 229 |
bar := heap.Pop(s.bHeap).(*Bar)
|
| 216 | 230 |
frameReader := <-bar.frameReaderCh
|
| 217 | 231 |
defer func() {
|
| 218 | 232 |
if frameReader.toShutdown {
|
|
233 |
// force next refresh asap, without waiting for ticker
|
|
234 |
go func() {
|
|
235 |
select {
|
|
236 |
case s.forceRefreshCh <- time.Now():
|
|
237 |
case <-done:
|
|
238 |
return
|
|
239 |
}
|
|
240 |
}()
|
| 219 | 241 |
// shutdown at next flush, in other words decrement underlying WaitGroup
|
| 220 | 242 |
// only after the bar with completed state has been flushed. this
|
| 221 | 243 |
// ensures no bar ends up with less than 100% rendered.
|
|
| 244 | 266 |
return s.cw.Flush(lineCount)
|
| 245 | 267 |
}
|
| 246 | 268 |
|
|
269 |
func (s *pState) manualOrTick() (<-chan time.Time, func()) {
|
|
270 |
if s.manualRefreshCh != nil {
|
|
271 |
return s.manualRefreshCh, func() {}
|
|
272 |
}
|
|
273 |
ticker := time.NewTicker(s.rr)
|
|
274 |
return ticker.C, ticker.Stop
|
|
275 |
}
|
|
276 |
|
|
277 |
func (s *pState) updateSyncMatrix() {
|
|
278 |
s.pMatrix = make(map[int][]chan int)
|
|
279 |
s.aMatrix = make(map[int][]chan int)
|
|
280 |
for i := 0; i < s.bHeap.Len(); i++ {
|
|
281 |
bar := (*s.bHeap)[i]
|
|
282 |
table := bar.wSyncTable()
|
|
283 |
pRow, aRow := table[0], table[1]
|
|
284 |
|
|
285 |
for i, ch := range pRow {
|
|
286 |
s.pMatrix[i] = append(s.pMatrix[i], ch)
|
|
287 |
}
|
|
288 |
|
|
289 |
for i, ch := range aRow {
|
|
290 |
s.aMatrix[i] = append(s.aMatrix[i], ch)
|
|
291 |
}
|
|
292 |
}
|
|
293 |
}
|
|
294 |
|
| 247 | 295 |
func syncWidth(matrix map[int][]chan int) {
|
| 248 | 296 |
for _, column := range matrix {
|
| 249 | 297 |
column := column
|
|
| 261 | 309 |
}()
|
| 262 | 310 |
}
|
| 263 | 311 |
}
|
|
312 |
|
|
313 |
func fanInRefreshSrc(done <-chan struct{}, channels ...<-chan time.Time) <-chan time.Time {
|
|
314 |
var wg sync.WaitGroup
|
|
315 |
multiplexedStream := make(chan time.Time)
|
|
316 |
|
|
317 |
multiplex := func(c <-chan time.Time) {
|
|
318 |
defer wg.Done()
|
|
319 |
for {
|
|
320 |
select {
|
|
321 |
case v := <-c:
|
|
322 |
multiplexedStream <- v
|
|
323 |
case <-done:
|
|
324 |
return
|
|
325 |
}
|
|
326 |
}
|
|
327 |
}
|
|
328 |
|
|
329 |
wg.Add(len(channels))
|
|
330 |
for _, c := range channels {
|
|
331 |
go multiplex(c)
|
|
332 |
}
|
|
333 |
|
|
334 |
go func() {
|
|
335 |
wg.Wait()
|
|
336 |
close(multiplexedStream)
|
|
337 |
}()
|
|
338 |
|
|
339 |
return multiplexedStream
|
|
340 |
}
|