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 }