Files
mc/server/world/mcdb/iterator.go
T
TarnaWijaya 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
up3
2026-07-09 08:33:57 +08:00

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]
}