-
Notifications
You must be signed in to change notification settings - Fork 0
/
coordinate_supplier_atomic.go
62 lines (52 loc) · 1.75 KB
/
coordinate_supplier_atomic.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
package coordinate_supplier
import (
"fmt"
"sync/atomic"
)
type coordinateSupplierAtomic struct {
coordinates []Coordinate
at uint64
done uint64
repeat bool
order Order
}
// NewCoordinateSupplierAtomic returns a CoordinateSupplier synchronized with atomic.AddUint64.
// It is the fastest implementation but some coordinates could be received slightly out-of-order when called concurrently.
func NewCoordinateSupplierAtomic(opts CoordinateSupplierOptions) (CoordinateSupplier, error) {
if opts.Width < 1 {
return nil, fmt.Errorf("minimum width is 1")
}
if opts.Height < 1 {
return nil, fmt.Errorf("minimum height is 1")
}
coords, err := MakeCoordinateList(opts.Width, opts.Height, opts.Order)
if err != nil {
return nil, fmt.Errorf("failed make coordinate list: %w", err)
}
cs := &coordinateSupplierAtomic{
repeat: opts.Repeat,
coordinates: coords,
}
return cs, nil
}
// Next returns the next coordinate to be supplied.
// It may be possible to receive some coordinates slightly out of order when called concurrently.
func (c *coordinateSupplierAtomic) Next() (x, y int, done bool) {
// check if already done
if atomic.LoadUint64(&c.done) > 0 {
// already done
return 0, 0, true
}
// concurrent-safe and in-order get the next element index
atNow := atomic.AddUint64(&c.at, 1) - 1
// check if now done
if !c.repeat && atNow >= uint64(len(c.coordinates)) {
// mark as done
atomic.AddUint64(&c.done, 1)
return 0, 0, true
}
// if repeating past the end, clamp to the current remainder position
atNowClamped := atNow % uint64(len(c.coordinates))
// return matching coordinate (by now the timing may be slightly out-of-order)
return c.coordinates[atNowClamped].X, c.coordinates[atNowClamped].Y, false
}