26ed99fda6
Build and deploy / Build (push) Has been cancelled
Build and deploy / Update Contributors (push) Has been cancelled
Build and deploy / Deploy (push) Has been cancelled
137 lines
4.0 KiB
Go
137 lines
4.0 KiB
Go
package mcdb
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"fmt"
|
|
|
|
"github.com/df-mc/dragonfly/server/world"
|
|
"github.com/df-mc/dragonfly/server/world/chunk"
|
|
"github.com/df-mc/goleveldb/leveldb/iterator"
|
|
)
|
|
|
|
// 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 iterator.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 {
|
|
return &ColumnIterator{
|
|
db: db,
|
|
dbIter: db.ldb.NewIterator(nil, nil),
|
|
seen: make(map[dbKey]struct{}),
|
|
r: r,
|
|
}
|
|
}
|
|
|
|
// 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()
|
|
kLen := len(k)
|
|
if (kLen != 9 && kLen != 13) || (k[kLen-1] != keyVersion && k[kLen-1] != keyVersionOld) {
|
|
return iter.Next()
|
|
}
|
|
iter.dim = world.Dimension(world.Overworld)
|
|
if kLen > 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
|
|
}
|
|
|
|
// Release releases associated resources. Release should always success
|
|
// and can be called multiple times without causing error.
|
|
func (iter *ColumnIterator) Release() {
|
|
iter.dbIter.Release()
|
|
}
|
|
|
|
// 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]
|
|
}
|