github.com/df-mc/dragonfly@v0.9.13/server/world/loader.go (about)

     1  package world
     2  
     3  import (
     4  	"github.com/go-gl/mathgl/mgl64"
     5  	"math"
     6  	"sync"
     7  )
     8  
     9  // Loader implements the loading of the world. A loader can typically be moved around the world to load
    10  // different parts of the world. An example usage is the player, which uses a loader to load chunks around it
    11  // so that it can view them.
    12  type Loader struct {
    13  	r      int
    14  	w      *World
    15  	viewer Viewer
    16  
    17  	mu        sync.RWMutex
    18  	pos       ChunkPos
    19  	loadQueue []ChunkPos
    20  	loaded    map[ChunkPos]*Column
    21  
    22  	closed bool
    23  }
    24  
    25  // NewLoader creates a new loader using the chunk radius passed. Chunks beyond this radius from the position
    26  // of the loader will never be loaded.
    27  // The Viewer passed will handle the loading of chunks, including the viewing of entities that were loaded in
    28  // those chunks.
    29  func NewLoader(chunkRadius int, world *World, v Viewer) *Loader {
    30  	l := &Loader{r: chunkRadius, loaded: make(map[ChunkPos]*Column), viewer: v}
    31  	l.world(world)
    32  	return l
    33  }
    34  
    35  // World returns the World that the Loader is in.
    36  func (l *Loader) World() *World {
    37  	l.mu.RLock()
    38  	defer l.mu.RUnlock()
    39  	return l.w
    40  }
    41  
    42  // ChangeWorld changes the World of the Loader. The currently loaded chunks are reset and any future loading
    43  // is done from the new World.
    44  func (l *Loader) ChangeWorld(new *World) {
    45  	l.mu.Lock()
    46  	defer l.mu.Unlock()
    47  
    48  	l.reset()
    49  	l.world(new)
    50  }
    51  
    52  // ChangeRadius changes the maximum chunk radius of the Loader.
    53  func (l *Loader) ChangeRadius(new int) {
    54  	l.mu.Lock()
    55  	defer l.mu.Unlock()
    56  
    57  	l.r = new
    58  	l.evictUnused()
    59  	l.populateLoadQueue()
    60  }
    61  
    62  // Move moves the loader to the position passed. The position is translated to a chunk position to load
    63  func (l *Loader) Move(pos mgl64.Vec3) {
    64  	l.mu.Lock()
    65  	defer l.mu.Unlock()
    66  
    67  	chunkPos := chunkPosFromVec3(pos)
    68  	if chunkPos == l.pos {
    69  		return
    70  	}
    71  	l.pos = chunkPos
    72  	l.evictUnused()
    73  	l.populateLoadQueue()
    74  }
    75  
    76  // Load loads n chunks around the centre of the chunk, starting with the middle and working outwards. For
    77  // every chunk loaded, the Viewer passed through construction in New has its ViewChunk method called.
    78  // Load does nothing for n <= 0.
    79  func (l *Loader) Load(n int) {
    80  	l.mu.Lock()
    81  	defer l.mu.Unlock()
    82  
    83  	if l.closed || l.w == nil {
    84  		return
    85  	}
    86  	for i := 0; i < n; i++ {
    87  		if len(l.loadQueue) == 0 {
    88  			break
    89  		}
    90  
    91  		pos := l.loadQueue[0]
    92  		c := l.w.chunk(pos)
    93  
    94  		l.viewer.ViewChunk(pos, c.Chunk, c.BlockEntities)
    95  		l.w.addViewer(c, l)
    96  
    97  		l.loaded[pos] = c
    98  
    99  		// Shift the first element from the load queue off so that we can take a new one during the next
   100  		// iteration.
   101  		l.loadQueue = l.loadQueue[1:]
   102  	}
   103  }
   104  
   105  // Chunk attempts to return a chunk at the given ChunkPos. If the chunk is not loaded, the second return value will
   106  // be false.
   107  func (l *Loader) Chunk(pos ChunkPos) (*Column, bool) {
   108  	l.mu.RLock()
   109  	c, ok := l.loaded[pos]
   110  	l.mu.RUnlock()
   111  	return c, ok
   112  }
   113  
   114  // Close closes the loader. It unloads all chunks currently loaded for the viewer, and hides all entities that
   115  // are currently shown to it.
   116  func (l *Loader) Close() error {
   117  	l.mu.Lock()
   118  	defer l.mu.Unlock()
   119  
   120  	l.reset()
   121  	l.closed = true
   122  	l.viewer = nil
   123  	return nil
   124  }
   125  
   126  // Reset clears all chunks loaded by the Loader and repopulates the loading queue so that they can all be loaded again.
   127  func (l *Loader) Reset() {
   128  	l.mu.Lock()
   129  	defer l.mu.Unlock()
   130  
   131  	l.reset()
   132  	l.w.addWorldViewer(l)
   133  	l.populateLoadQueue()
   134  }
   135  
   136  // reset clears the Loader so that it may be used as if it was created again with NewLoader.
   137  func (l *Loader) reset() {
   138  	for pos := range l.loaded {
   139  		l.w.removeViewer(pos, l)
   140  	}
   141  	l.loaded = map[ChunkPos]*Column{}
   142  	l.w.removeWorldViewer(l)
   143  }
   144  
   145  // world sets the loader's world, adds them to the world's viewer list, then starts populating the load queue.
   146  // This is only here to get rid of duplicated code, ChangeWorld should be used instead of this.
   147  func (l *Loader) world(new *World) {
   148  	l.w = new
   149  	l.w.addWorldViewer(l)
   150  	l.populateLoadQueue()
   151  }
   152  
   153  // evictUnused gets rid of chunks in the loaded map which are no longer within the chunk radius of the loader,
   154  // and should therefore be removed.
   155  func (l *Loader) evictUnused() {
   156  	for pos := range l.loaded {
   157  		diffX, diffZ := pos[0]-l.pos[0], pos[1]-l.pos[1]
   158  		dist := math.Sqrt(float64(diffX*diffX) + float64(diffZ*diffZ))
   159  		if int(dist) > l.r {
   160  			delete(l.loaded, pos)
   161  			l.w.removeViewer(pos, l)
   162  		}
   163  	}
   164  }
   165  
   166  // populateLoadQueue populates the load queue of the loader. This method is called once to create the order in
   167  // which chunks around the position the loader is now in should be loaded. Chunks are ordered to be loaded
   168  // from the middle outwards.
   169  func (l *Loader) populateLoadQueue() {
   170  	// We'll first load the chunk positions to load in a map indexed by the distance to the center (basically,
   171  	// what precedence it should have), and put them in the loadQueue in that order.
   172  	queue := map[int32][]ChunkPos{}
   173  
   174  	r := int32(l.r)
   175  	for x := -r; x <= r; x++ {
   176  		for z := -r; z <= r; z++ {
   177  			distance := math.Sqrt(float64(x*x) + float64(z*z))
   178  			chunkDistance := int32(math.Round(distance))
   179  			if chunkDistance > r {
   180  				// The chunk was outside the chunk radius.
   181  				continue
   182  			}
   183  			pos := ChunkPos{x + l.pos[0], z + l.pos[1]}
   184  			if _, ok := l.loaded[pos]; ok {
   185  				// The chunk was already loaded, so we don't need to do anything.
   186  				continue
   187  			}
   188  			if m, ok := queue[chunkDistance]; ok {
   189  				queue[chunkDistance] = append(m, pos)
   190  				continue
   191  			}
   192  			queue[chunkDistance] = []ChunkPos{pos}
   193  		}
   194  	}
   195  
   196  	l.loadQueue = l.loadQueue[:0]
   197  	for i := int32(0); i < r; i++ {
   198  		l.loadQueue = append(l.loadQueue, queue[i]...)
   199  	}
   200  }