-
Notifications
You must be signed in to change notification settings - Fork 48
/
order.go
167 lines (139 loc) · 3.63 KB
/
order.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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
package annotate
import (
"context"
"sync"
"github.com/paulmach/osm"
)
// RelationHistoryDatasourcer is an more strict interface for when we only need the relation history.
type RelationHistoryDatasourcer interface {
RelationHistory(context.Context, osm.RelationID) (osm.Relations, error)
NotFound(error) bool
}
var _ RelationHistoryDatasourcer = &osm.HistoryDatasource{}
// A ChildFirstOrdering is a struct that allows for a set of relations to be
// processed in a dept first order. Since relations can reference other
// relations we need to make sure children are added before parents.
type ChildFirstOrdering struct {
// CompletedIndex is the number of relation ids in the provided
// array that have been finished. This can be used as a good restart position.
CompletedIndex int
ctx context.Context
done context.CancelFunc
ds RelationHistoryDatasourcer
visited map[osm.RelationID]struct{}
out chan osm.RelationID
wg sync.WaitGroup
id osm.RelationID
err error
}
// NewChildFirstOrdering creates a new ordering object. It is used to provided
// a child before parent ordering for relations. This order must be used when
// inserting+annotating relations into the datastore.
func NewChildFirstOrdering(
ctx context.Context,
ids []osm.RelationID,
ds RelationHistoryDatasourcer,
) *ChildFirstOrdering {
ctx, done := context.WithCancel(ctx)
o := &ChildFirstOrdering{
ctx: ctx,
done: done,
ds: ds,
visited: make(map[osm.RelationID]struct{}, len(ids)),
out: make(chan osm.RelationID),
}
o.wg.Add(1)
go func() {
defer o.wg.Done()
defer close(o.out)
path := make([]osm.RelationID, 0, 100)
for i, id := range ids {
err := o.walk(id, path)
if err != nil {
o.err = err
return
}
o.CompletedIndex = i
}
}()
return o
}
// Err returns a non-nil error if something went wrong with search,
// like a cycle, or a datasource error.
func (o *ChildFirstOrdering) Err() error {
if o.err != nil {
return o.err
}
return o.ctx.Err()
}
// Next locates the next relation id that can be used.
// Returns false if the context is closed, something went wrong
// or the full tree has been walked.
func (o *ChildFirstOrdering) Next() bool {
if o.err != nil || o.ctx.Err() != nil {
return false
}
select {
case id := <-o.out:
if id == 0 {
return false
}
o.id = id
return true
case <-o.ctx.Done():
return false
}
}
// RelationID is the id found by the previous scan.
func (o *ChildFirstOrdering) RelationID() osm.RelationID {
return o.id
}
// Close can be used to terminate the scanning process before
// all ids have been walked.
func (o *ChildFirstOrdering) Close() {
o.done()
o.wg.Wait()
}
func (o *ChildFirstOrdering) walk(id osm.RelationID, path []osm.RelationID) error {
if _, ok := o.visited[id]; ok {
return nil
}
relations, err := o.ds.RelationHistory(o.ctx, id)
if o.ds.NotFound(err) {
return nil
}
if err != nil {
return err
}
for _, r := range relations {
for _, m := range r.Members {
if m.Type != osm.TypeRelation {
continue
}
mid := osm.RelationID(m.Ref)
for _, pid := range path {
if pid == mid {
// circular relations are allowed,
// source: https://github.com/openstreetmap/openstreetmap-website/issues/1465#issuecomment-282323187
// since this relation is already being worked through higher
// up the stack, we can just return here.
return nil
}
}
err := o.walk(mid, append(path, mid))
if err != nil {
return err
}
}
}
if o.ctx.Err() != nil {
return o.ctx.Err()
}
o.visited[id] = struct{}{}
select {
case o.out <- id:
case <-o.ctx.Done():
return o.ctx.Err()
}
return nil
}