-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathiterator.go
More file actions
140 lines (125 loc) · 4.02 KB
/
iterator.go
File metadata and controls
140 lines (125 loc) · 4.02 KB
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
package mcpb
import (
"encoding/binary"
"fmt"
"github.com/cockroachdb/pebble"
"github.com/df-mc/dragonfly/server/world"
"github.com/df-mc/dragonfly/server/world/chunk"
)
// ColumnIterator iterates over a DB's position/column pairs in key order.
//
// When an error is encountered, any call to Next will return false and will
// yield no position/chunk pairs. The error can be queried by calling the Error
// method. Calling Release is still necessary.
//
// An iterator must be released after use, but it is not necessary to read
// an iterator until exhaustion.
// Also, an iterator is not necessarily safe for concurrent use, but it is
// safe to use multiple iterators concurrently, with each in a dedicated
// goroutine.
type ColumnIterator struct {
dbIter *pebble.Iterator
db *DB
r *IteratorRange
err error
current *chunk.Column
pos world.ChunkPos
dim world.Dimension
seen map[dbKey]struct{}
}
func newColumnIterator(db *DB, r *IteratorRange) (*ColumnIterator, error) {
iter, err := db.pdb.NewIter(nil)
if err != nil {
return nil, fmt.Errorf("column iterator: %w", err)
}
return &ColumnIterator{
db: db,
dbIter: iter,
seen: make(map[dbKey]struct{}),
r: r,
}, nil
}
// Next moves the iterator to the next key/value pair.
// It returns false if the iterator is exhausted.
func (iter *ColumnIterator) Next() bool {
if iter.err != nil || !iter.dbIter.Next() {
iter.current = nil
iter.dim = nil
return false
}
k := iter.dbIter.Key()
if (len(k) != 9 && len(k) != 13) || (k[8] != keyVersion) {
return iter.Next()
}
iter.dim = world.Dimension(world.Overworld)
if len(k) > 9 {
var ok bool
id := int(binary.LittleEndian.Uint32(k[8:12]))
if iter.dim, ok = world.DimensionByID(id); !ok {
iter.err = fmt.Errorf("unknown dimension id %v", id)
return false
}
}
iter.pos = world.ChunkPos{
int32(binary.LittleEndian.Uint32(k[:4])),
int32(binary.LittleEndian.Uint32(k[4:8])),
}
if !iter.r.within(iter.pos, iter.dim) {
return iter.Next()
}
key := dbKey{dim: iter.dim, pos: iter.pos}
if _, ok := iter.seen[key]; ok {
// Already encountered this chunk. This might happen if there are
// multiple version keys.
return iter.Next()
}
iter.current, iter.err = iter.db.LoadColumn(iter.pos, iter.dim)
if iter.err != nil {
iter.err = fmt.Errorf("load chunk %v: %w", iter.pos, iter.err)
return false
}
iter.seen[key] = struct{}{}
return true
}
// Column returns the value of the current position/column pair, or nil if none.
func (iter *ColumnIterator) Column() *chunk.Column {
return iter.current
}
// Position returns the position of the current position/column pair.
func (iter *ColumnIterator) Position() world.ChunkPos {
return iter.pos
}
// Dimension returns the dimension of the current position/column pair, or nil
// if none.
func (iter *ColumnIterator) Dimension() world.Dimension {
return iter.dim
}
// Close releases associated resources and can be called multiple
// times without causing error.
func (iter *ColumnIterator) Close() error {
return iter.dbIter.Close()
}
// Error returns any accumulated error. Exhausting all the key/value pairs
// is not considered to be an error.
func (iter *ColumnIterator) Error() error {
return iter.err
}
// IteratorRange is a range used to limit what columns are accumulated by a
// ColumnIterator.
type IteratorRange struct {
// Min and Max limit what chunk positions are returned by a ColumnIterator.
// A zero value for both Min and Max causes all positions to be within the
// range.
Min, Max world.ChunkPos
// Dimension specifies what world.Dimension chunks should be accumulated
// from. If nil, all dimensions will be read from.
Dimension world.Dimension
}
// within checks if a position and dimension is within the IteratorRange.
func (r *IteratorRange) within(pos world.ChunkPos, dim world.Dimension) bool {
if dim != r.Dimension && r.Dimension != nil {
return false
}
return ((r.Min == world.ChunkPos{}) && (r.Max == world.ChunkPos{})) ||
pos[0] >= r.Min[0] && pos[0] < r.Max[0] && pos[1] >= r.Min[1] && pos[1] < r.Max[1]
}