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  }