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
200 lines
5.3 KiB
Go
200 lines
5.3 KiB
Go
package world
|
|
|
|
import (
|
|
"github.com/go-gl/mathgl/mgl64"
|
|
"maps"
|
|
"math"
|
|
"sync"
|
|
)
|
|
|
|
// Loader implements the loading of the world. A loader can typically be moved around the world to load
|
|
// different parts of the world. An example usage is the player, which uses a loader to load chunks around it
|
|
// so that it can view them.
|
|
type Loader struct {
|
|
r int
|
|
w *World
|
|
viewer Viewer
|
|
|
|
mu sync.RWMutex
|
|
pos ChunkPos
|
|
loadQueue []ChunkPos
|
|
loaded map[ChunkPos]*Column
|
|
|
|
closed bool
|
|
}
|
|
|
|
// NewLoader creates a new loader using the chunk radius passed. Chunks beyond this radius from the position
|
|
// of the loader will never be loaded.
|
|
// The Viewer passed will handle the loading of chunks, including the viewing of entities that were loaded in
|
|
// those chunks.
|
|
func NewLoader(chunkRadius int, world *World, v Viewer) *Loader {
|
|
l := &Loader{r: chunkRadius, loaded: make(map[ChunkPos]*Column), viewer: v}
|
|
l.world(world)
|
|
return l
|
|
}
|
|
|
|
// World returns the World that the Loader is in.
|
|
func (l *Loader) World() *World {
|
|
l.mu.RLock()
|
|
defer l.mu.RUnlock()
|
|
return l.w
|
|
}
|
|
|
|
// ChangeWorld changes the World of the Loader. The currently loaded chunks are reset and any future loading
|
|
// is done from the new World.
|
|
func (l *Loader) ChangeWorld(tx *Tx, new *World) {
|
|
l.mu.Lock()
|
|
defer l.mu.Unlock()
|
|
|
|
loaded := maps.Clone(l.loaded)
|
|
l.w.Exec(func(tx *Tx) {
|
|
for pos := range loaded {
|
|
tx.World().removeViewer(tx, pos, l)
|
|
}
|
|
})
|
|
clear(l.loaded)
|
|
l.w.viewerMu.Lock()
|
|
delete(l.w.viewers, l)
|
|
l.w.viewerMu.Unlock()
|
|
|
|
l.world(new)
|
|
}
|
|
|
|
// ChangeRadius changes the maximum chunk radius of the Loader.
|
|
func (l *Loader) ChangeRadius(tx *Tx, new int) {
|
|
l.mu.Lock()
|
|
defer l.mu.Unlock()
|
|
|
|
l.r = new
|
|
l.evictUnused(tx)
|
|
l.populateLoadQueue()
|
|
}
|
|
|
|
// Move moves the loader to the position passed. The position is translated to a chunk position to load
|
|
func (l *Loader) Move(tx *Tx, pos mgl64.Vec3) {
|
|
l.mu.Lock()
|
|
defer l.mu.Unlock()
|
|
|
|
chunkPos := chunkPosFromVec3(pos)
|
|
if chunkPos == l.pos {
|
|
return
|
|
}
|
|
l.pos = chunkPos
|
|
l.evictUnused(tx)
|
|
l.populateLoadQueue()
|
|
}
|
|
|
|
// Load loads n chunks around the centre of the chunk, starting with the middle and working outwards. For
|
|
// every chunk loaded, the Viewer passed through construction in New has its ViewChunk method called.
|
|
// Load does nothing for n <= 0.
|
|
func (l *Loader) Load(tx *Tx, n int) {
|
|
l.mu.Lock()
|
|
defer l.mu.Unlock()
|
|
|
|
if l.closed || l.w == nil {
|
|
return
|
|
}
|
|
for i := 0; i < n; i++ {
|
|
if len(l.loadQueue) == 0 {
|
|
break
|
|
}
|
|
|
|
pos := l.loadQueue[0]
|
|
c := tx.w.chunk(pos)
|
|
|
|
l.viewer.ViewChunk(pos, l.w.Dimension(), c.BlockEntities, c.Chunk)
|
|
l.w.addViewer(tx, c, l)
|
|
|
|
l.loaded[pos] = c
|
|
|
|
// Shift the first element from the load queue off so that we can take a new one during the next
|
|
// iteration.
|
|
l.loadQueue = l.loadQueue[1:]
|
|
}
|
|
}
|
|
|
|
// Chunk attempts to return a chunk at the given ChunkPos. If the chunk is not loaded, the second return value will
|
|
// be false.
|
|
func (l *Loader) Chunk(pos ChunkPos) (*Column, bool) {
|
|
l.mu.RLock()
|
|
c, ok := l.loaded[pos]
|
|
l.mu.RUnlock()
|
|
return c, ok
|
|
}
|
|
|
|
// Close closes the loader. It unloads all chunks currently loaded for the viewer, and hides all entities that
|
|
// are currently shown to it.
|
|
func (l *Loader) Close(tx *Tx) {
|
|
l.mu.Lock()
|
|
defer l.mu.Unlock()
|
|
|
|
for pos := range l.loaded {
|
|
tx.World().removeViewer(tx, pos, l)
|
|
}
|
|
l.loaded = map[ChunkPos]*Column{}
|
|
|
|
l.w.viewerMu.Lock()
|
|
delete(l.w.viewers, l)
|
|
l.w.viewerMu.Unlock()
|
|
|
|
l.closed = true
|
|
l.viewer = nil
|
|
}
|
|
|
|
// world sets the loader's world, adds them to the world's viewer list, then starts populating the load queue.
|
|
// This is only here to get rid of duplicated code, ChangeWorld should be used instead of this.
|
|
func (l *Loader) world(new *World) {
|
|
l.w = new
|
|
l.w.addWorldViewer(l)
|
|
l.populateLoadQueue()
|
|
}
|
|
|
|
// evictUnused gets rid of chunks in the loaded map which are no longer within the chunk radius of the loader,
|
|
// and should therefore be removed.
|
|
func (l *Loader) evictUnused(tx *Tx) {
|
|
for pos := range l.loaded {
|
|
diffX, diffZ := pos[0]-l.pos[0], pos[1]-l.pos[1]
|
|
dist := math.Sqrt(float64(diffX*diffX) + float64(diffZ*diffZ))
|
|
if int(dist) > l.r {
|
|
delete(l.loaded, pos)
|
|
l.w.removeViewer(tx, pos, l)
|
|
}
|
|
}
|
|
}
|
|
|
|
// populateLoadQueue populates the load queue of the loader. This method is called once to create the order in
|
|
// which chunks around the position the loader is now in should be loaded. Chunks are ordered to be loaded
|
|
// from the middle outwards.
|
|
func (l *Loader) populateLoadQueue() {
|
|
// We'll first load the chunk positions to load in a map indexed by the distance to the centre (basically,
|
|
// what precedence it should have), and put them in the loadQueue in that order.
|
|
queue := map[int32][]ChunkPos{}
|
|
|
|
r := int32(l.r)
|
|
for x := -r; x <= r; x++ {
|
|
for z := -r; z <= r; z++ {
|
|
distance := math.Sqrt(float64(x*x) + float64(z*z))
|
|
chunkDistance := int32(math.Round(distance))
|
|
if chunkDistance > r {
|
|
// The chunk was outside the chunk radius.
|
|
continue
|
|
}
|
|
pos := ChunkPos{x + l.pos[0], z + l.pos[1]}
|
|
if _, ok := l.loaded[pos]; ok {
|
|
// The chunk was already loaded, so we don't need to do anything.
|
|
continue
|
|
}
|
|
if m, ok := queue[chunkDistance]; ok {
|
|
queue[chunkDistance] = append(m, pos)
|
|
continue
|
|
}
|
|
queue[chunkDistance] = []ChunkPos{pos}
|
|
}
|
|
}
|
|
|
|
l.loadQueue = l.loadQueue[:0]
|
|
for i := int32(0); i < r; i++ {
|
|
l.loadQueue = append(l.loadQueue, queue[i]...)
|
|
}
|
|
}
|