github.com/df-mc/dragonfly@v0.9.13/server/world/mcdb/iterator.go (about) 1 package mcdb 2 3 import ( 4 "encoding/binary" 5 "fmt" 6 "github.com/df-mc/dragonfly/server/world" 7 "github.com/df-mc/goleveldb/leveldb/iterator" 8 ) 9 10 // ColumnIterator iterates over a DB's position/column pairs in key order. 11 // 12 // When an error is encountered, any call to Next will return false and will 13 // yield no position/chunk pairs. The error can be queried by calling the Error 14 // method. Calling Release is still necessary. 15 // 16 // An iterator must be released after use, but it is not necessary to read 17 // an iterator until exhaustion. 18 // Also, an iterator is not necessarily safe for concurrent use, but it is 19 // safe to use multiple iterators concurrently, with each in a dedicated 20 // goroutine. 21 type ColumnIterator struct { 22 dbIter iterator.Iterator 23 db *DB 24 r *IteratorRange 25 26 err error 27 28 current *world.Column 29 pos world.ChunkPos 30 dim world.Dimension 31 seen map[dbKey]struct{} 32 } 33 34 func newColumnIterator(db *DB, r *IteratorRange) *ColumnIterator { 35 return &ColumnIterator{ 36 db: db, 37 dbIter: db.ldb.NewIterator(nil, nil), 38 seen: make(map[dbKey]struct{}), 39 r: r, 40 } 41 } 42 43 // Next moves the iterator to the next key/value pair. 44 // It returns false if the iterator is exhausted. 45 func (iter *ColumnIterator) Next() bool { 46 if iter.err != nil || !iter.dbIter.Next() { 47 iter.current = nil 48 iter.dim = nil 49 return false 50 } 51 k := iter.dbIter.Key() 52 if (len(k) != 9 && len(k) != 13) || (k[8] != keyVersion && k[8] != keyVersionOld) { 53 return iter.Next() 54 } 55 iter.dim = world.Dimension(world.Overworld) 56 if len(k) > 9 { 57 var ok bool 58 id := int(binary.LittleEndian.Uint32(k[8:12])) 59 if iter.dim, ok = world.DimensionByID(id); !ok { 60 iter.err = fmt.Errorf("unknown dimension id %v", id) 61 return false 62 } 63 } 64 iter.pos = world.ChunkPos{ 65 int32(binary.LittleEndian.Uint32(k[:4])), 66 int32(binary.LittleEndian.Uint32(k[4:8])), 67 } 68 if !iter.r.within(iter.pos, iter.dim) { 69 return iter.Next() 70 } 71 key := dbKey{dim: iter.dim, pos: iter.pos} 72 if _, ok := iter.seen[key]; ok { 73 // Already encountered this chunk. This might happen if there are 74 // multiple version keys. 75 return iter.Next() 76 } 77 iter.current, iter.err = iter.db.LoadColumn(iter.pos, iter.dim) 78 if iter.err != nil { 79 iter.err = fmt.Errorf("load chunk %v: %w", iter.pos, iter.err) 80 return false 81 } 82 iter.seen[key] = struct{}{} 83 return true 84 } 85 86 // Column returns the value of the current position/column pair, or nil if none. 87 func (iter *ColumnIterator) Column() *world.Column { 88 return iter.current 89 } 90 91 // Position returns the position of the current position/column pair. 92 func (iter *ColumnIterator) Position() world.ChunkPos { 93 return iter.pos 94 } 95 96 // Dimension returns the dimension of the current position/column pair, or nil 97 // if none. 98 func (iter *ColumnIterator) Dimension() world.Dimension { 99 return iter.dim 100 } 101 102 // Release releases associated resources. Release should always success 103 // and can be called multiple times without causing error. 104 func (iter *ColumnIterator) Release() { 105 iter.dbIter.Release() 106 } 107 108 // Error returns any accumulated error. Exhausting all the key/value pairs 109 // is not considered to be an error. 110 func (iter *ColumnIterator) Error() error { 111 return iter.err 112 } 113 114 // IteratorRange is a range used to limit what columns are accumulated by a 115 // ColumnIterator. 116 type IteratorRange struct { 117 // Min and Max limit what chunk positions are returned by a ColumnIterator. 118 // A zero value for both Min and Max causes all positions to be within the 119 // range. 120 Min, Max world.ChunkPos 121 // Dimension specifies what world.Dimension chunks should be accumulated 122 // from. If nil, all dimensions will be read from. 123 Dimension world.Dimension 124 } 125 126 // within checks if a position and dimension is within the IteratorRange. 127 func (r *IteratorRange) within(pos world.ChunkPos, dim world.Dimension) bool { 128 if dim != r.Dimension && r.Dimension != nil { 129 return false 130 } 131 return ((r.Min == world.ChunkPos{}) && (r.Max == world.ChunkPos{})) || 132 pos[0] >= r.Min[0] && pos[0] < r.Max[0] && pos[1] >= r.Min[1] && pos[1] < r.Max[1] 133 }