Skip to content

Commit d15116b

Browse files
committed
correct speed realization
1 parent ae83553 commit d15116b

File tree

4 files changed

+172
-90
lines changed

4 files changed

+172
-90
lines changed

element.go

+15-61
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@ import (
66
"math"
77
"sync"
88
"time"
9-
10-
"gopkg.in/VividCortex/ewma.v1"
119
)
1210

1311
const (
@@ -230,70 +228,26 @@ var ElementBar ElementFunc = func(state *State, args ...string) string {
230228
return p.buf.String()
231229
}
232230

233-
type speed struct {
234-
ewma ewma.MovingAverage
235-
prevValue int64
236-
prevTime time.Time
237-
}
238-
239-
func (s *speed) currentSpeed(value int64) float64 {
240-
var speed float64
241-
if s.prevTime.IsZero() {
242-
s.prevTime = time.Now()
243-
return 0
244-
}
245-
dur := time.Since(s.prevTime)
246-
if dur < time.Second && s.ewma != nil {
247-
return s.ewma.Value()
248-
}
249-
diff := float64(value - s.prevValue)
250-
speed = diff / dur.Seconds()
251-
if s.ewma == nil {
252-
s.ewma = ewma.NewMovingAverage()
253-
} else {
254-
s.ewma.Add(speed)
255-
}
256-
s.prevValue = value
257-
s.prevTime = time.Now()
258-
return s.ewma.Value()
259-
}
260-
261-
func getSpeedObj(state *State) (s *speed) {
262-
if sObj, ok := state.Get(speedObj).(*speed); ok {
263-
return sObj
264-
}
265-
s = new(speed)
266-
state.Set(speedObj, s)
267-
return
268-
}
269-
270-
// ElementSpeed calculates current speed by EWMA
271-
// Optionally can take one or two string arguments.
272-
// First string will be used as value for format speed, default is "%s p/s".
273-
// Second string will be used when speed not available, default is "? p/s"
274-
// In template use as follows: {{speed .}} or {{speed . "%s per second"}} or {{speed . "%s ps" "..."}
275-
var ElementSpeed ElementFunc = func(state *State, args ...string) string {
276-
sp := getSpeedObj(state).currentSpeed(state.Value())
277-
if sp == 0 {
278-
return argsHelper(args).getNotEmptyOr(1, "? p/s")
279-
}
280-
return fmt.Sprintf(argsHelper(args).getNotEmptyOr(0, "%s p/s"), state.Format(int64(round(sp))))
281-
}
282-
283231
// ElementRemainingTime calculates remaining time based on speed (EWMA)
284232
// Optionally can take one or two string arguments.
285233
// First string will be used as value for format time duration string, default is "%s".
286-
// Second string will be used when value not available, default is "?"
287-
// In template use as follows: {{rtime .}} or {{rtime . "%s remain"}} or {{rtime . "%s remain" ""}}
234+
// Second string will be used when bar finished and value indicates elapsed time, default is "%s"
235+
// Third string will be used when value not available, default is "?"
236+
// In template use as follows: {{rtime .}} or {{rtime . "%s remain"}} or {{rtime . "%s remain" "%s total" "???"}}
288237
var ElementRemainingTime ElementFunc = func(state *State, args ...string) string {
289238
var rts string
290-
sp := getSpeedObj(state).currentSpeed(state.Value())
291-
if sp > 0 {
292-
remain := float64(state.Total() - state.Value())
293-
remainDur := time.Duration(remain/sp) * time.Second
294-
rts = remainDur.String()
239+
sp := getSpeedObj(state).value(state)
240+
if !state.IsFinished() {
241+
if sp > 0 {
242+
remain := float64(state.Total() - state.Value())
243+
remainDur := time.Duration(remain/sp) * time.Second
244+
rts = remainDur.String()
245+
} else {
246+
return argsHelper(args).getOr(2, "?")
247+
}
295248
} else {
296-
return argsHelper(args).getOr(1, "?")
249+
rts = state.Time().Truncate(time.Second).Sub(state.StartTime().Truncate(time.Second)).String()
250+
return fmt.Sprintf(argsHelper(args).getOr(1, "%s"), rts)
297251
}
298252
return fmt.Sprintf(argsHelper(args).getOr(0, "%s"), rts)
299253
}
@@ -302,7 +256,7 @@ var ElementRemainingTime ElementFunc = func(state *State, args ...string) string
302256
// Optionally cat take one argument - it's format for time string.
303257
// In template use as follows: {{etime .}} or {{etime . "%s elapsed"}}
304258
var ElementElapsedTime ElementFunc = func(state *State, args ...string) string {
305-
etm := time.Now().Truncate(time.Second).Sub(state.StartTime().Truncate(time.Second))
259+
etm := state.Time().Truncate(time.Second).Sub(state.StartTime().Truncate(time.Second))
306260
return fmt.Sprintf(argsHelper(args).getOr(0, "%s"), etm.String())
307261
}
308262

element_test.go

+41-18
Original file line numberDiff line numberDiff line change
@@ -131,12 +131,31 @@ func TestElementBar(t *testing.T) {
131131

132132
func TestElementSpeed(t *testing.T) {
133133
var state = testState(1000, 0, 0, false)
134-
state.ProgressBar.startTime = time.Now()
134+
state.time = time.Now()
135135
for i := int64(0); i < 10; i++ {
136+
state.id = uint64(i) + 1
136137
state.current += 42
138+
state.time = state.time.Add(time.Second)
139+
state.finished = i == 9
140+
if state.finished {
141+
state.current += 100
142+
}
137143
r := ElementSpeed(state)
138-
if i <= 1 {
139-
// do not calc first two results
144+
r2 := ElementSpeed(state)
145+
if r != r2 {
146+
t.Errorf("Must be the same: '%s' vs '%s'", r, r2)
147+
}
148+
if i < 1 {
149+
// do not calc first result
150+
if w := "? p/s"; r != w {
151+
t.Errorf("Unexpected result[%d]: '%s' vs '%s'", i, r, w)
152+
}
153+
} else if state.finished {
154+
if w := "58 p/s"; r != w {
155+
t.Errorf("Unexpected result[%d]: '%s' vs '%s'", i, r, w)
156+
}
157+
state.time = state.time.Add(-time.Hour)
158+
r = ElementSpeed(state)
140159
if w := "? p/s"; r != w {
141160
t.Errorf("Unexpected result[%d]: '%s' vs '%s'", i, r, w)
142161
}
@@ -145,44 +164,48 @@ func TestElementSpeed(t *testing.T) {
145164
t.Errorf("Unexpected result[%d]: '%s' vs '%s'", i, r, w)
146165
}
147166
}
148-
var add = -time.Second
149-
if i > 7 {
150-
add = add / 2
151-
}
152-
getSpeedObj(state).prevTime = time.Now().Add(add)
153167
}
154168
}
155169

156170
func TestElementRemainingTime(t *testing.T) {
157-
var state = testState(1000, 0, 0, false)
158-
state.ProgressBar.startTime = time.Now()
171+
var state = testState(100, 0, 0, false)
172+
state.time = time.Now()
173+
state.startTime = state.time
159174
for i := int64(0); i < 10; i++ {
160-
state.current += 42
175+
state.id = uint64(i) + 1
176+
state.time = state.time.Add(time.Second)
177+
state.finished = i == 9
161178
r := ElementRemainingTime(state)
162-
if i <= 1 {
179+
if i < 1 {
163180
// do not calc first two results
164181
if w := "?"; r != w {
165182
t.Errorf("Unexpected result[%d]: '%s' vs '%s'", i, r, w)
166183
}
184+
} else if state.finished {
185+
// final elapsed time
186+
if w := "10s"; r != w {
187+
t.Errorf("Unexpected result[%d]: '%s' vs '%s'", i, r, w)
188+
}
167189
} else {
168-
w := fmt.Sprintf("%ds", 22-i)
190+
w := fmt.Sprintf("%ds", 10-i)
169191
if r != w {
170192
t.Errorf("Unexpected result[%d]: '%s' vs '%s'", i, r, w)
171193
}
172194
}
173-
getSpeedObj(state).prevTime = time.Now().Add(-time.Second)
195+
state.current += 10
174196
}
175197
}
176198

177199
func TestElementElapsedTime(t *testing.T) {
178200
var state = testState(1000, 0, 0, false)
179-
state.ProgressBar.startTime = time.Now().Truncate(time.Second)
201+
state.startTime = time.Now()
202+
state.time = state.startTime
180203
for i := int64(0); i < 10; i++ {
181204
r := ElementElapsedTime(state)
182205
if w := fmt.Sprintf("%ds", i); r != w {
183206
t.Errorf("Unexpected result[%d]: '%s' vs '%s'", i, r, w)
184207
}
185-
state.ProgressBar.startTime = state.ProgressBar.startTime.Add(-time.Second)
208+
state.time = state.time.Add(time.Second)
186209
}
187210
}
188211

@@ -209,14 +232,14 @@ func TestElementCycle(t *testing.T) {
209232

210233
func TestAdaptiveWrap(t *testing.T) {
211234
var state = testState(0, 0, 0, false)
212-
state.first = true
235+
state.id = 1
213236
state.Set("myKey", "my value")
214237
el := adaptiveWrap(ElementString)
215238
testElementBarString(t, state, el, adElPlaceholder, "myKey")
216239
if v := state.recalc[0].ProgressElement(state); v != "my value" {
217240
t.Errorf("Unexpected result: %s", v)
218241
}
219-
state.first = false
242+
state.id = 2
220243
testElementBarString(t, state, el, adElPlaceholder, "myKey1")
221244
state.Set("myKey", "my value1")
222245
if v := state.recalc[0].ProgressElement(state); v != "my value1" {

pb.go

+33-11
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import (
1818
)
1919

2020
// Version of ProgressBar library
21-
const Version = "2.0.3"
21+
const Version = "2.0.4"
2222

2323
type key int
2424

@@ -57,8 +57,8 @@ func New64(total int64) *ProgressBar {
5757
return pb.SetTotal(total)
5858
}
5959

60-
// Start starts new ProgressBar with Default template
61-
func Start(total int) *ProgressBar {
60+
// StartNew starts new ProgressBar with Default template
61+
func StartNew(total int) *ProgressBar {
6262
return New(total).Start()
6363
}
6464

@@ -147,9 +147,13 @@ func (pb *ProgressBar) configure() {
147147
func (pb *ProgressBar) Start() *ProgressBar {
148148
pb.mu.Lock()
149149
defer pb.mu.Unlock()
150+
if pb.finish != nil {
151+
return pb
152+
}
150153
pb.configure()
151154
pb.finished = false
152155
pb.state = nil
156+
pb.startTime = time.Now()
153157
if st, ok := pb.vars[Static].(bool); ok && st {
154158
return pb
155159
}
@@ -386,12 +390,15 @@ func (pb *ProgressBar) render() (result string, width int) {
386390
pb.mu.Lock()
387391
pb.configure()
388392
if pb.state == nil {
389-
pb.state = &State{first: true, ProgressBar: pb}
393+
pb.state = &State{ProgressBar: pb}
390394
pb.buf = bytes.NewBuffer(nil)
391-
} else {
392-
pb.state.first = false
393395
}
396+
if pb.startTime.IsZero() {
397+
pb.startTime = time.Now()
398+
}
399+
pb.state.id++
394400
pb.state.finished = pb.finished
401+
pb.state.time = time.Now()
395402
pb.mu.Unlock()
396403

397404
pb.state.width = pb.Width()
@@ -428,6 +435,8 @@ func (pb *ProgressBar) render() (result string, width int) {
428435
return
429436
}
430437

438+
// SetErr sets error to the ProgressBar
439+
// Error will be available over Err()
431440
func (pb *ProgressBar) SetErr(err error) *ProgressBar {
432441
pb.mu.Lock()
433442
pb.err = err
@@ -463,15 +472,23 @@ func (pb *ProgressBar) ProgressElement(s *State, args ...string) string {
463472
type State struct {
464473
*ProgressBar
465474

466-
total, current int64
467-
475+
id uint64
476+
total, current int64
468477
width, adaptiveElWidth int
469-
470-
first, finished, adaptive bool
478+
finished, adaptive bool
479+
time time.Time
471480

472481
recalc []Element
473482
}
474483

484+
// Id it's the current state identifier
485+
// - incremental
486+
// - starts with 1
487+
// - resets after finish/start
488+
func (s *State) Id() uint64 {
489+
return s.id
490+
}
491+
475492
// Total it's bar int64 total
476493
func (s *State) Total() int64 {
477494
return s.total
@@ -504,5 +521,10 @@ func (s *State) IsFinished() bool {
504521

505522
// IsFirst return true only in first render
506523
func (s *State) IsFirst() bool {
507-
return s.first
524+
return s.id == 1
525+
}
526+
527+
// Time when state was created
528+
func (s *State) Time() time.Time {
529+
return s.time
508530
}

0 commit comments

Comments
 (0)