up3
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
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
This commit is contained in:
@@ -0,0 +1,199 @@
|
||||
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]...)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user