github.com/df-mc/dragonfly@v0.9.13/server/world/world.go (about) 1 package world 2 3 import ( 4 "errors" 5 "github.com/df-mc/goleveldb/leveldb" 6 "math/rand" 7 "sync" 8 "time" 9 10 "github.com/df-mc/atomic" 11 "github.com/df-mc/dragonfly/server/block/cube" 12 "github.com/df-mc/dragonfly/server/event" 13 "github.com/df-mc/dragonfly/server/internal/sliceutil" 14 "github.com/df-mc/dragonfly/server/world/chunk" 15 "github.com/go-gl/mathgl/mgl64" 16 "github.com/google/uuid" 17 "golang.org/x/exp/maps" 18 "slices" 19 ) 20 21 // World implements a Minecraft world. It manages all aspects of what players can see, such as blocks, 22 // entities and particles. 23 // World generally provides a synchronised state: All entities, blocks and players usually operate in this 24 // world, so World ensures that all its methods will always be safe for simultaneous calls. 25 // A nil *World is safe to use but not functional. 26 type World struct { 27 conf Config 28 ra cube.Range 29 // advance is a bool that specifies if this World should advance the current tick, time and weather saved in the 30 // Settings struct held by the World. 31 advance bool 32 33 o sync.Once 34 35 set *Settings 36 handler atomic.Value[Handler] 37 38 weather 39 ticker 40 41 lastPos ChunkPos 42 lastChunk *Column 43 44 closing chan struct{} 45 running sync.WaitGroup 46 47 chunkMu sync.Mutex 48 // chunks holds a cache of chunks currently loaded. These chunks are cleared from this map after some time 49 // of not being used. 50 chunks map[ChunkPos]*Column 51 52 entityMu sync.RWMutex 53 // entities holds a map of entities currently loaded and the last ChunkPos that the Entity was in. 54 // These are tracked so that a call to RemoveEntity can find the correct entity. 55 entities map[Entity]ChunkPos 56 57 r *rand.Rand 58 59 updateMu sync.Mutex 60 // scheduledUpdates is a map of tick time values indexed by the block position at which an update is 61 // scheduled. If the current tick exceeds the tick value passed, the block update will be performed 62 // and the entry will be removed from the map. 63 scheduledUpdates map[cube.Pos]int64 64 neighbourUpdates []neighbourUpdate 65 66 viewersMu sync.Mutex 67 viewers map[*Loader]Viewer 68 } 69 70 // New creates a new initialised world. The world may be used right away, but it will not be saved or loaded 71 // from files until it has been given a different provider than the default. (NopProvider) 72 // By default, the name of the world will be 'World'. 73 func New() *World { 74 var conf Config 75 return conf.New() 76 } 77 78 // Name returns the display name of the world. Generally, this name is displayed at the top of the player list 79 // in the pause screen in-game. 80 // If a provider is set, the name will be updated according to the name that it provides. 81 func (w *World) Name() string { 82 w.set.Lock() 83 defer w.set.Unlock() 84 return w.set.Name 85 } 86 87 // Dimension returns the Dimension assigned to the World in world.New. The sky colour and behaviour of a variety of 88 // world features differ based on the Dimension assigned to a World. 89 func (w *World) Dimension() Dimension { 90 if w == nil { 91 return nopDim{} 92 } 93 return w.conf.Dim 94 } 95 96 // Range returns the range in blocks of the World (min and max). It is equivalent to calling World.Dimension().Range(). 97 func (w *World) Range() cube.Range { 98 if w == nil { 99 return cube.Range{} 100 } 101 return w.ra 102 } 103 104 // EntityRegistry returns the EntityRegistry that was passed to the World's 105 // Config upon construction. 106 func (w *World) EntityRegistry() EntityRegistry { 107 return w.conf.Entities 108 } 109 110 // Block reads a block from the position passed. If a chunk is not yet loaded at that position, the chunk is 111 // loaded, or generated if it could not be found in the world save, and the block returned. Chunks will be 112 // loaded synchronously. 113 func (w *World) Block(pos cube.Pos) Block { 114 if w == nil || pos.OutOfBounds(w.Range()) { 115 // Fast way out. 116 return air() 117 } 118 c := w.chunk(chunkPosFromBlockPos(pos)) 119 defer c.Unlock() 120 121 rid := c.Block(uint8(pos[0]), int16(pos[1]), uint8(pos[2]), 0) 122 if nbtBlocks[rid] { 123 // The block was also a block entity, so we look it up in the block entity map. 124 if nbtB, ok := c.BlockEntities[pos]; ok { 125 return nbtB 126 } 127 } 128 b, _ := BlockByRuntimeID(rid) 129 return b 130 } 131 132 // Biome reads the biome at the position passed. If a chunk is not yet loaded at that position, the chunk is 133 // loaded, or generated if it could not be found in the world save, and the biome returned. Chunks will be 134 // loaded synchronously. 135 func (w *World) Biome(pos cube.Pos) Biome { 136 if w == nil || pos.OutOfBounds(w.Range()) { 137 // Fast way out. 138 return ocean() 139 } 140 c := w.chunk(chunkPosFromBlockPos(pos)) 141 defer c.Unlock() 142 143 id := int(c.Biome(uint8(pos[0]), int16(pos[1]), uint8(pos[2]))) 144 b, ok := BiomeByID(id) 145 if !ok { 146 w.conf.Log.Errorf("could not find biome by ID %v", id) 147 } 148 return b 149 } 150 151 // blockInChunk reads a block from the world at the position passed. The block is assumed to be in the chunk 152 // passed, which is also assumed to be locked already or otherwise not yet accessible. 153 func (w *World) blockInChunk(c *Column, pos cube.Pos) Block { 154 if pos.OutOfBounds(w.Range()) { 155 // Fast way out. 156 return air() 157 } 158 rid := c.Block(uint8(pos[0]), int16(pos[1]), uint8(pos[2]), 0) 159 if nbtBlocks[rid] { 160 // The block was also a block entity, so we look it up in the block entity map. 161 if b, ok := c.BlockEntities[pos]; ok { 162 return b 163 } 164 } 165 b, _ := BlockByRuntimeID(rid) 166 return b 167 } 168 169 // HighestLightBlocker gets the Y value of the highest fully light blocking block at the x and z values 170 // passed in the world. 171 func (w *World) HighestLightBlocker(x, z int) int { 172 if w == nil { 173 return w.Range()[0] 174 } 175 c := w.chunk(ChunkPos{int32(x >> 4), int32(z >> 4)}) 176 defer c.Unlock() 177 return int(c.HighestLightBlocker(uint8(x), uint8(z))) 178 } 179 180 // HighestBlock looks up the highest non-air block in the world at a specific x and z in the world. The y 181 // value of the highest block is returned, or 0 if no blocks were present in the column. 182 func (w *World) HighestBlock(x, z int) int { 183 if w == nil { 184 return w.Range()[0] 185 } 186 c := w.chunk(ChunkPos{int32(x >> 4), int32(z >> 4)}) 187 defer c.Unlock() 188 return int(c.HighestBlock(uint8(x), uint8(z))) 189 } 190 191 // highestObstructingBlock returns the highest block in the world at a given x and z that has at least a solid top or 192 // bottom face. 193 func (w *World) highestObstructingBlock(x, z int) int { 194 if w == nil { 195 return 0 196 } 197 yHigh := w.HighestBlock(x, z) 198 for y := yHigh; y >= w.Range()[0]; y-- { 199 pos := cube.Pos{x, y, z} 200 m := w.Block(pos).Model() 201 if m.FaceSolid(pos, cube.FaceUp, w) || m.FaceSolid(pos, cube.FaceDown, w) { 202 return y 203 } 204 } 205 return w.Range()[0] 206 } 207 208 // SetOpts holds several parameters that may be set to disable updates in the World of different kinds as a result of 209 // a call to SetBlock. 210 type SetOpts struct { 211 // DisableBlockUpdates makes SetBlock not update any neighbouring blocks as a result of the SetBlock call. 212 DisableBlockUpdates bool 213 // DisableLiquidDisplacement disables the displacement of liquid blocks to the second layer (or back to the first 214 // layer, if it already was on the second layer). Disabling this is not strongly recommended unless performance is 215 // very important or where it is known no liquid can be present anyway. 216 DisableLiquidDisplacement bool 217 } 218 219 // SetBlock writes a block to the position passed. If a chunk is not yet loaded at that position, the chunk is 220 // first loaded or generated if it could not be found in the world save. 221 // SetBlock panics if the block passed has not yet been registered using RegisterBlock(). 222 // Nil may be passed as the block to set the block to air. 223 // 224 // A SetOpts struct may be passed to additionally modify behaviour of SetBlock, specifically to improve performance 225 // under specific circumstances. Nil should be passed where performance is not essential, to make sure the world is 226 // updated adequately. 227 // 228 // SetBlock should be avoided in situations where performance is critical when needing to set a lot of blocks 229 // to the world. BuildStructure may be used instead. 230 func (w *World) SetBlock(pos cube.Pos, b Block, opts *SetOpts) { 231 if w == nil || pos.OutOfBounds(w.Range()) { 232 // Fast way out. 233 return 234 } 235 if opts == nil { 236 opts = &SetOpts{} 237 } 238 239 x, y, z := uint8(pos[0]), int16(pos[1]), uint8(pos[2]) 240 c := w.chunk(chunkPosFromBlockPos(pos)) 241 242 rid := BlockRuntimeID(b) 243 244 var before uint32 245 if rid != airRID && !opts.DisableLiquidDisplacement { 246 before = c.Block(x, y, z, 0) 247 } 248 249 c.modified = true 250 c.SetBlock(x, y, z, 0, rid) 251 if nbtBlocks[rid] { 252 c.BlockEntities[pos] = b 253 } else { 254 delete(c.BlockEntities, pos) 255 } 256 257 viewers := slices.Clone(c.viewers) 258 259 if !opts.DisableLiquidDisplacement { 260 var secondLayer Block 261 262 if rid == airRID { 263 if li := c.Block(x, y, z, 1); li != airRID { 264 c.SetBlock(x, y, z, 0, li) 265 c.SetBlock(x, y, z, 1, airRID) 266 secondLayer = air() 267 b, _ = BlockByRuntimeID(li) 268 } 269 } else if liquidDisplacingBlocks[rid] && liquidBlocks[before] { 270 l, _ := BlockByRuntimeID(before) 271 if b.(LiquidDisplacer).CanDisplace(l.(Liquid)) { 272 c.SetBlock(x, y, z, 1, before) 273 secondLayer = l 274 } 275 } 276 c.Unlock() 277 278 if secondLayer != nil { 279 for _, viewer := range viewers { 280 viewer.ViewBlockUpdate(pos, secondLayer, 1) 281 } 282 } 283 } else { 284 c.Unlock() 285 } 286 287 for _, viewer := range viewers { 288 viewer.ViewBlockUpdate(pos, b, 0) 289 } 290 291 if !opts.DisableBlockUpdates { 292 w.doBlockUpdatesAround(pos) 293 } 294 } 295 296 // SetBiome sets the biome at the position passed. If a chunk is not yet loaded at that position, the chunk is 297 // first loaded or generated if it could not be found in the world save. 298 func (w *World) SetBiome(pos cube.Pos, b Biome) { 299 if w == nil || pos.OutOfBounds(w.Range()) { 300 // Fast way out. 301 return 302 } 303 c := w.chunk(chunkPosFromBlockPos(pos)) 304 defer c.Unlock() 305 306 c.modified = true 307 c.SetBiome(uint8(pos[0]), int16(pos[1]), uint8(pos[2]), uint32(b.EncodeBiome())) 308 } 309 310 // BuildStructure builds a Structure passed at a specific position in the world. Unlike SetBlock, it takes a 311 // Structure implementation, which provides blocks to be placed at a specific location. 312 // BuildStructure is specifically tinkered to be able to process a large batch of chunks simultaneously and 313 // will do so within much less time than separate SetBlock calls would. 314 // The method operates on a per-chunk basis, setting all blocks within a single chunk part of the structure 315 // before moving on to the next chunk. 316 func (w *World) BuildStructure(pos cube.Pos, s Structure) { 317 if w == nil { 318 return 319 } 320 dim := s.Dimensions() 321 width, height, length := dim[0], dim[1], dim[2] 322 maxX, maxY, maxZ := pos[0]+width, pos[1]+height, pos[2]+length 323 324 for chunkX := pos[0] >> 4; chunkX <= maxX>>4; chunkX++ { 325 for chunkZ := pos[2] >> 4; chunkZ <= maxZ>>4; chunkZ++ { 326 // We approach this on a per-chunk basis, so that we can keep only one chunk in memory at a time 327 // while not needing to acquire a new chunk lock for every block. This also allows us not to send 328 // block updates, but instead send a single chunk update once. 329 chunkPos := ChunkPos{int32(chunkX), int32(chunkZ)} 330 c := w.chunk(chunkPos) 331 f := func(x, y, z int) Block { 332 actual := cube.Pos{pos[0] + x, pos[1] + y, pos[2] + z} 333 if actual[0]>>4 == chunkX && actual[2]>>4 == chunkZ { 334 return w.blockInChunk(c, actual) 335 } 336 return w.Block(actual) 337 } 338 baseX, baseZ := chunkX<<4, chunkZ<<4 339 subs := c.Sub() 340 for i, sub := range subs { 341 baseY := (i + (w.Range()[0] >> 4)) << 4 342 if baseY>>4 < pos[1]>>4 { 343 continue 344 } else if baseY >= maxY { 345 break 346 } 347 348 for localY := 0; localY < 16; localY++ { 349 yOffset := baseY + localY 350 if yOffset > w.Range()[1] || yOffset >= maxY { 351 // We've hit the height limit for blocks. 352 break 353 } else if yOffset < w.Range()[0] || yOffset < pos[1] { 354 // We've got a block below the minimum, but other blocks might still reach above 355 // it, so don't break but continue. 356 continue 357 } 358 for localX := 0; localX < 16; localX++ { 359 xOffset := baseX + localX 360 if xOffset < pos[0] || xOffset >= maxX { 361 continue 362 } 363 for localZ := 0; localZ < 16; localZ++ { 364 zOffset := baseZ + localZ 365 if zOffset < pos[2] || zOffset >= maxZ { 366 continue 367 } 368 b, liq := s.At(xOffset-pos[0], yOffset-pos[1], zOffset-pos[2], f) 369 if b != nil { 370 rid := BlockRuntimeID(b) 371 sub.SetBlock(uint8(xOffset), uint8(yOffset), uint8(zOffset), 0, rid) 372 373 nbtPos := cube.Pos{xOffset, yOffset, zOffset} 374 if nbtBlocks[rid] { 375 c.BlockEntities[nbtPos] = b 376 } else { 377 delete(c.BlockEntities, nbtPos) 378 } 379 } 380 if liq != nil { 381 sub.SetBlock(uint8(xOffset), uint8(yOffset), uint8(zOffset), 1, BlockRuntimeID(liq)) 382 } else if len(sub.Layers()) > 1 { 383 sub.SetBlock(uint8(xOffset), uint8(yOffset), uint8(zOffset), 1, airRID) 384 } 385 } 386 } 387 } 388 } 389 c.SetBlock(0, 0, 0, 0, c.Block(0, 0, 0, 0)) // Make sure the heightmap is recalculated. 390 c.modified = true 391 392 // After setting all blocks of the structure within a single chunk, we show the new chunk to all 393 // viewers once, and unlock it. 394 for _, viewer := range c.viewers { 395 viewer.ViewChunk(chunkPos, c.Chunk, c.BlockEntities) 396 } 397 c.Unlock() 398 } 399 } 400 } 401 402 // Liquid attempts to return any liquid block at the position passed. This liquid may be in the foreground or 403 // in any other layer. 404 // If found, the liquid is returned. If not, the bool returned is false and the liquid is nil. 405 func (w *World) Liquid(pos cube.Pos) (Liquid, bool) { 406 if w == nil || pos.OutOfBounds(w.Range()) { 407 // Fast way out. 408 return nil, false 409 } 410 c := w.chunk(chunkPosFromBlockPos(pos)) 411 defer c.Unlock() 412 x, y, z := uint8(pos[0]), int16(pos[1]), uint8(pos[2]) 413 414 id := c.Block(x, y, z, 0) 415 b, ok := BlockByRuntimeID(id) 416 if !ok { 417 w.conf.Log.Errorf("failed getting liquid: cannot get block by runtime ID %v", id) 418 return nil, false 419 } 420 if liq, ok := b.(Liquid); ok { 421 return liq, true 422 } 423 id = c.Block(x, y, z, 1) 424 425 b, ok = BlockByRuntimeID(id) 426 if !ok { 427 w.conf.Log.Errorf("failed getting liquid: cannot get block by runtime ID %v", id) 428 return nil, false 429 } 430 liq, ok := b.(Liquid) 431 return liq, ok 432 } 433 434 // SetLiquid sets the liquid at a specific position in the world. Unlike SetBlock, SetLiquid will not 435 // overwrite any existing blocks. It will instead be in the same position as a block currently there, unless 436 // there already is a liquid at that position, in which case it will be overwritten. 437 // If nil is passed for the liquid, any liquid currently present will be removed. 438 func (w *World) SetLiquid(pos cube.Pos, b Liquid) { 439 if w == nil || pos.OutOfBounds(w.Range()) { 440 // Fast way out. 441 return 442 } 443 chunkPos := chunkPosFromBlockPos(pos) 444 c := w.chunk(chunkPos) 445 if b == nil { 446 w.removeLiquids(c, pos) 447 c.Unlock() 448 w.doBlockUpdatesAround(pos) 449 return 450 } 451 x, y, z := uint8(pos[0]), int16(pos[1]), uint8(pos[2]) 452 if !replaceable(w, c, pos, b) { 453 if displacer, ok := w.blockInChunk(c, pos).(LiquidDisplacer); !ok || !displacer.CanDisplace(b) { 454 c.Unlock() 455 return 456 } 457 } 458 rid := BlockRuntimeID(b) 459 if w.removeLiquids(c, pos) { 460 c.SetBlock(x, y, z, 0, rid) 461 for _, v := range c.viewers { 462 v.ViewBlockUpdate(pos, b, 0) 463 } 464 } else { 465 c.SetBlock(x, y, z, 1, rid) 466 for _, v := range c.viewers { 467 v.ViewBlockUpdate(pos, b, 1) 468 } 469 } 470 c.modified = true 471 c.Unlock() 472 473 w.doBlockUpdatesAround(pos) 474 } 475 476 // removeLiquids removes any liquid blocks that may be present at a specific block position in the chunk 477 // passed. 478 // The bool returned specifies if no blocks were left on the foreground layer. 479 func (w *World) removeLiquids(c *Column, pos cube.Pos) bool { 480 x, y, z := uint8(pos[0]), int16(pos[1]), uint8(pos[2]) 481 482 noneLeft := false 483 if noLeft, changed := w.removeLiquidOnLayer(c.Chunk, x, y, z, 0); noLeft { 484 if changed { 485 for _, v := range c.viewers { 486 v.ViewBlockUpdate(pos, air(), 0) 487 } 488 } 489 noneLeft = true 490 } 491 if _, changed := w.removeLiquidOnLayer(c.Chunk, x, y, z, 1); changed { 492 for _, v := range c.viewers { 493 v.ViewBlockUpdate(pos, air(), 1) 494 } 495 } 496 return noneLeft 497 } 498 499 // removeLiquidOnLayer removes a liquid block from a specific layer in the chunk passed, returning true if 500 // successful. 501 func (w *World) removeLiquidOnLayer(c *chunk.Chunk, x uint8, y int16, z, layer uint8) (bool, bool) { 502 id := c.Block(x, y, z, layer) 503 504 b, ok := BlockByRuntimeID(id) 505 if !ok { 506 w.conf.Log.Errorf("failed removing liquids: cannot get block by runtime ID %v", id) 507 return false, false 508 } 509 if _, ok := b.(Liquid); ok { 510 c.SetBlock(x, y, z, layer, airRID) 511 return true, true 512 } 513 return id == airRID, false 514 } 515 516 // additionalLiquid checks if the block at a position has additional liquid on another layer and returns the 517 // liquid if so. 518 func (w *World) additionalLiquid(pos cube.Pos) (Liquid, bool) { 519 if pos.OutOfBounds(w.Range()) { 520 // Fast way out. 521 return nil, false 522 } 523 c := w.chunk(chunkPosFromBlockPos(pos)) 524 id := c.Block(uint8(pos[0]), int16(pos[1]), uint8(pos[2]), 1) 525 c.Unlock() 526 b, ok := BlockByRuntimeID(id) 527 if !ok { 528 w.conf.Log.Errorf("failed getting liquid: cannot get block by runtime ID %v", id) 529 return nil, false 530 } 531 liq, ok := b.(Liquid) 532 return liq, ok 533 } 534 535 // Light returns the light level at the position passed. This is the highest of the sky and block light. 536 // The light value returned is a value in the range 0-15, where 0 means there is no light present, whereas 537 // 15 means the block is fully lit. 538 func (w *World) Light(pos cube.Pos) uint8 { 539 if w == nil || pos[1] < w.Range()[0] { 540 // Fast way out. 541 return 0 542 } 543 if pos[1] > w.Range()[1] { 544 // Above the rest of the world, so full skylight. 545 return 15 546 } 547 c := w.chunk(chunkPosFromBlockPos(pos)) 548 defer c.Unlock() 549 return c.Light(uint8(pos[0]), int16(pos[1]), uint8(pos[2])) 550 } 551 552 // SkyLight returns the skylight level at the position passed. This light level is not influenced by blocks 553 // that emit light, such as torches or glowstone. The light value, similarly to Light, is a value in the 554 // range 0-15, where 0 means no light is present. 555 func (w *World) SkyLight(pos cube.Pos) uint8 { 556 if w == nil || pos[1] < w.Range()[0] { 557 // Fast way out. 558 return 0 559 } 560 if pos[1] > w.Range()[1] { 561 // Above the rest of the world, so full skylight. 562 return 15 563 } 564 c := w.chunk(chunkPosFromBlockPos(pos)) 565 defer c.Unlock() 566 return c.SkyLight(uint8(pos[0]), int16(pos[1]), uint8(pos[2])) 567 } 568 569 // Time returns the current time of the world. The time is incremented every 1/20th of a second, unless 570 // World.StopTime() is called. 571 func (w *World) Time() int { 572 if w == nil { 573 return 0 574 } 575 w.set.Lock() 576 defer w.set.Unlock() 577 return int(w.set.Time) 578 } 579 580 // SetTime sets the new time of the world. SetTime will always work, regardless of whether the time is stopped 581 // or not. 582 func (w *World) SetTime(new int) { 583 if w == nil { 584 return 585 } 586 w.set.Lock() 587 w.set.Time = int64(new) 588 w.set.Unlock() 589 590 viewers, _ := w.allViewers() 591 for _, viewer := range viewers { 592 viewer.ViewTime(new) 593 } 594 } 595 596 // StopTime stops the time in the world. When called, the time will no longer cycle and the world will remain 597 // at the time when StopTime is called. The time may be restarted by calling World.StartTime(). 598 // StopTime will not do anything if the time is already stopped. 599 func (w *World) StopTime() { 600 w.enableTimeCycle(false) 601 } 602 603 // StartTime restarts the time in the world. When called, the time will start cycling again and the day/night 604 // cycle will continue. The time may be stopped again by calling World.StopTime(). 605 // StartTime will not do anything if the time is already started. 606 func (w *World) StartTime() { 607 w.enableTimeCycle(true) 608 } 609 610 // enableTimeCycle enables or disables the time cycling of the World. 611 func (w *World) enableTimeCycle(v bool) { 612 if w == nil { 613 return 614 } 615 w.set.Lock() 616 defer w.set.Unlock() 617 w.set.TimeCycle = v 618 } 619 620 // Temperature returns the temperature in the World at a specific position. Higher altitudes and different biomes 621 // influence the temperature returned. 622 func (w *World) Temperature(pos cube.Pos) float64 { 623 const ( 624 tempDrop = 1.0 / 600 625 seaLevel = 64 626 ) 627 diff := pos[1] - seaLevel 628 if diff < 0 { 629 diff = 0 630 } 631 return w.Biome(pos).Temperature() - float64(diff)*tempDrop 632 } 633 634 // AddParticle spawns a particle at a given position in the world. Viewers that are viewing the chunk will be 635 // shown the particle. 636 func (w *World) AddParticle(pos mgl64.Vec3, p Particle) { 637 if w == nil { 638 return 639 } 640 p.Spawn(w, pos) 641 for _, viewer := range w.Viewers(pos) { 642 viewer.ViewParticle(pos, p) 643 } 644 } 645 646 // PlaySound plays a sound at a specific position in the world. Viewers of that position will be able to hear 647 // the sound if they're close enough. 648 func (w *World) PlaySound(pos mgl64.Vec3, s Sound) { 649 ctx := event.C() 650 if w.Handler().HandleSound(ctx, s, pos); ctx.Cancelled() { 651 return 652 } 653 for _, viewer := range w.Viewers(pos) { 654 viewer.ViewSound(pos, s) 655 } 656 } 657 658 var ( 659 worldsMu sync.RWMutex 660 // entityWorlds holds a list of all entities added to a world. It may be used to look up the world that an 661 // entity is currently in. 662 entityWorlds = map[Entity]*World{} 663 ) 664 665 // AddEntity adds an entity to the world at the position that the entity has. The entity will be visible to 666 // all viewers of the world that have the chunk of the entity loaded. 667 // If the chunk that the entity is in is not yet loaded, it will first be loaded. 668 // If the entity passed to AddEntity is currently in a world, it is first removed from that world. 669 func (w *World) AddEntity(e Entity) { 670 if w == nil { 671 return 672 } 673 674 // Remove the Entity from any previous World it might be in. 675 e.World().RemoveEntity(e) 676 677 add(e, w) 678 679 chunkPos := chunkPosFromVec3(e.Position()) 680 w.entityMu.Lock() 681 w.entities[e] = chunkPos 682 w.entityMu.Unlock() 683 684 c := w.chunk(chunkPos) 685 c.Entities = append(c.Entities, e) 686 viewers := slices.Clone(c.viewers) 687 c.Unlock() 688 689 for _, v := range viewers { 690 // We show the entity to all viewers currently in the chunk that the entity is spawned in. 691 showEntity(e, v) 692 } 693 694 w.Handler().HandleEntitySpawn(e) 695 } 696 697 // add maps an Entity to a World in the entityWorlds map. 698 func add(e Entity, w *World) { 699 worldsMu.Lock() 700 entityWorlds[e] = w 701 worldsMu.Unlock() 702 } 703 704 // RemoveEntity removes an entity from the world that is currently present in it. Any viewers of the entity 705 // will no longer be able to see it. 706 // RemoveEntity operates assuming the position of the entity is the same as where it is currently in the 707 // world. If it can not find it there, it will loop through all entities and try to find it. 708 // RemoveEntity assumes the entity is currently loaded and in a loaded chunk. If not, the function will not do 709 // anything. 710 func (w *World) RemoveEntity(e Entity) { 711 if w == nil { 712 return 713 } 714 w.entityMu.Lock() 715 chunkPos, found := w.entities[e] 716 w.entityMu.Unlock() 717 if !found { 718 // The entity currently isn't in this world. 719 return 720 } 721 722 w.Handler().HandleEntityDespawn(e) 723 724 worldsMu.Lock() 725 delete(entityWorlds, e) 726 worldsMu.Unlock() 727 728 c, ok := w.chunkFromCache(chunkPos) 729 if !ok { 730 // The chunk wasn't loaded, so we can't remove any entity from the chunk. 731 return 732 } 733 c.Entities = sliceutil.DeleteVal(c.Entities, e) 734 viewers := slices.Clone(c.viewers) 735 c.Unlock() 736 737 w.entityMu.Lock() 738 delete(w.entities, e) 739 w.entityMu.Unlock() 740 741 for _, v := range viewers { 742 v.HideEntity(e) 743 } 744 } 745 746 // EntitiesWithin does a lookup through the entities in the chunks touched by the BBox passed, returning all 747 // those which are contained within the BBox when it comes to their position. 748 func (w *World) EntitiesWithin(box cube.BBox, ignored func(Entity) bool) []Entity { 749 if w == nil { 750 return nil 751 } 752 // Make an estimate of 16 entities on average. 753 m := make([]Entity, 0, 16) 754 755 minPos, maxPos := chunkPosFromVec3(box.Min()), chunkPosFromVec3(box.Max()) 756 757 for x := minPos[0]; x <= maxPos[0]; x++ { 758 for z := minPos[1]; z <= maxPos[1]; z++ { 759 c, ok := w.chunkFromCache(ChunkPos{x, z}) 760 if !ok { 761 // The chunk wasn't loaded, so there are no entities here. 762 continue 763 } 764 entities := slices.Clone(c.Entities) 765 c.Unlock() 766 767 for _, entity := range entities { 768 if ignored != nil && ignored(entity) { 769 continue 770 } 771 if box.Vec3Within(entity.Position()) { 772 // The entity position was within the BBox, so we add it to the slice to return. 773 m = append(m, entity) 774 } 775 } 776 } 777 } 778 return m 779 } 780 781 // Entities returns a list of all entities currently added to the World. 782 func (w *World) Entities() []Entity { 783 if w == nil { 784 return nil 785 } 786 w.entityMu.RLock() 787 defer w.entityMu.RUnlock() 788 m := make([]Entity, 0, len(w.entities)) 789 for e := range w.entities { 790 m = append(m, e) 791 } 792 return m 793 } 794 795 // OfEntity attempts to return a world that an entity is currently in. If the entity was not currently added 796 // to a world, the world returned is nil and the bool returned is false. 797 func OfEntity(e Entity) (*World, bool) { 798 worldsMu.RLock() 799 w, ok := entityWorlds[e] 800 worldsMu.RUnlock() 801 return w, ok 802 } 803 804 // Spawn returns the spawn of the world. Every new player will by default spawn on this position in the world 805 // when joining. 806 func (w *World) Spawn() cube.Pos { 807 if w == nil { 808 return cube.Pos{} 809 } 810 w.set.Lock() 811 s := w.set.Spawn 812 w.set.Unlock() 813 if s[1] > w.Range()[1] { 814 s[1] = w.highestObstructingBlock(s[0], s[2]) + 1 815 } 816 return s 817 } 818 819 // SetSpawn sets the spawn of the world to a different position. The player will be spawned in the center of 820 // this position when newly joining. 821 func (w *World) SetSpawn(pos cube.Pos) { 822 if w == nil { 823 return 824 } 825 w.set.Lock() 826 w.set.Spawn = pos 827 w.set.Unlock() 828 829 viewers, _ := w.allViewers() 830 for _, viewer := range viewers { 831 viewer.ViewWorldSpawn(pos) 832 } 833 } 834 835 // PlayerSpawn returns the spawn position of a player with a UUID in this World. 836 func (w *World) PlayerSpawn(uuid uuid.UUID) cube.Pos { 837 if w == nil { 838 return cube.Pos{} 839 } 840 pos, exist, err := w.conf.Provider.LoadPlayerSpawnPosition(uuid) 841 if err != nil { 842 w.conf.Log.Errorf("failed to get player spawn: %v", err) 843 return w.Spawn() 844 } 845 if !exist { 846 return w.Spawn() 847 } 848 return pos 849 } 850 851 // SetPlayerSpawn sets the spawn position of a player with a UUID in this World. If the player has a spawn in the world, 852 // the player will be teleported to this location on respawn. 853 func (w *World) SetPlayerSpawn(uuid uuid.UUID, pos cube.Pos) { 854 if w == nil { 855 return 856 } 857 if err := w.conf.Provider.SavePlayerSpawnPosition(uuid, pos); err != nil { 858 w.conf.Log.Errorf("failed to set player spawn: %v", err) 859 } 860 } 861 862 // DefaultGameMode returns the default game mode of the world. When players join, they are given this game 863 // mode. 864 // The default game mode may be changed using SetDefaultGameMode(). 865 func (w *World) DefaultGameMode() GameMode { 866 if w == nil { 867 return GameModeSurvival 868 } 869 w.set.Lock() 870 defer w.set.Unlock() 871 return w.set.DefaultGameMode 872 } 873 874 // SetTickRange sets the range in chunks around each Viewer that will have the chunks (their blocks and entities) 875 // ticked when the World is ticked. 876 func (w *World) SetTickRange(v int) { 877 if w == nil { 878 return 879 } 880 w.set.Lock() 881 defer w.set.Unlock() 882 w.set.TickRange = int32(v) 883 } 884 885 // tickRange returns the tick range around each Viewer. 886 func (w *World) tickRange() int { 887 w.set.Lock() 888 defer w.set.Unlock() 889 return int(w.set.TickRange) 890 } 891 892 // SetDefaultGameMode changes the default game mode of the world. When players join, they are then given that 893 // game mode. 894 func (w *World) SetDefaultGameMode(mode GameMode) { 895 if w == nil { 896 return 897 } 898 w.set.Lock() 899 defer w.set.Unlock() 900 w.set.DefaultGameMode = mode 901 } 902 903 // Difficulty returns the difficulty of the world. Properties of mobs in the world and the player's hunger 904 // will depend on this difficulty. 905 func (w *World) Difficulty() Difficulty { 906 if w == nil { 907 return DifficultyNormal 908 } 909 w.set.Lock() 910 defer w.set.Unlock() 911 return w.set.Difficulty 912 } 913 914 // SetDifficulty changes the difficulty of a world. 915 func (w *World) SetDifficulty(d Difficulty) { 916 if w == nil { 917 return 918 } 919 w.set.Lock() 920 defer w.set.Unlock() 921 w.set.Difficulty = d 922 } 923 924 // ScheduleBlockUpdate schedules a block update at the position passed after a specific delay. If the block at 925 // that position does not handle block updates, nothing will happen. 926 func (w *World) ScheduleBlockUpdate(pos cube.Pos, delay time.Duration) { 927 if w == nil || pos.OutOfBounds(w.Range()) { 928 return 929 } 930 w.updateMu.Lock() 931 defer w.updateMu.Unlock() 932 if _, exists := w.scheduledUpdates[pos]; exists { 933 return 934 } 935 w.set.Lock() 936 t := w.set.CurrentTick 937 w.set.Unlock() 938 939 w.scheduledUpdates[pos] = t + delay.Nanoseconds()/int64(time.Second/20) 940 } 941 942 // doBlockUpdatesAround schedules block updates directly around and on the position passed. 943 func (w *World) doBlockUpdatesAround(pos cube.Pos) { 944 if w == nil || pos.OutOfBounds(w.Range()) { 945 return 946 } 947 948 changed := pos 949 950 w.updateMu.Lock() 951 w.updateNeighbour(pos, changed) 952 pos.Neighbours(func(pos cube.Pos) { 953 w.updateNeighbour(pos, changed) 954 }, w.Range()) 955 w.updateMu.Unlock() 956 } 957 958 // neighbourUpdate represents a position that needs to be updated because of a neighbour that changed. 959 type neighbourUpdate struct { 960 pos, neighbour cube.Pos 961 } 962 963 // updateNeighbour ticks the position passed as a result of the neighbour passed being updated. 964 func (w *World) updateNeighbour(pos, changedNeighbour cube.Pos) { 965 w.neighbourUpdates = append(w.neighbourUpdates, neighbourUpdate{pos: pos, neighbour: changedNeighbour}) 966 } 967 968 // Handle changes the current Handler of the world. As a result, events called by the world will call 969 // handlers of the Handler passed. 970 // Handle sets the world's Handler to NopHandler if nil is passed. 971 func (w *World) Handle(h Handler) { 972 if w == nil { 973 return 974 } 975 if h == nil { 976 h = NopHandler{} 977 } 978 w.handler.Store(h) 979 } 980 981 // Viewers returns a list of all viewers viewing the position passed. A viewer will be assumed to be watching 982 // if the position is within one of the chunks that the viewer is watching. 983 func (w *World) Viewers(pos mgl64.Vec3) (viewers []Viewer) { 984 if w == nil { 985 return nil 986 } 987 c, ok := w.chunkFromCache(chunkPosFromVec3(pos)) 988 if !ok { 989 return nil 990 } 991 defer c.Unlock() 992 return slices.Clone(c.viewers) 993 } 994 995 // PortalDestination returns the destination world for a portal of a specific Dimension. If no destination World could 996 // be found, the current World is returned. 997 func (w *World) PortalDestination(dim Dimension) *World { 998 if w.conf.PortalDestination == nil { 999 return w 1000 } 1001 if res := w.conf.PortalDestination(dim); res != nil { 1002 return res 1003 } 1004 return w 1005 } 1006 1007 // Close closes the world and saves all chunks currently loaded. 1008 func (w *World) Close() error { 1009 if w == nil { 1010 return nil 1011 } 1012 w.o.Do(w.close) 1013 return nil 1014 } 1015 1016 // close stops the World from ticking, saves all chunks to the Provider and updates the world's settings. 1017 func (w *World) close() { 1018 // Let user code run anything that needs to be finished before the World is closed. 1019 w.Handler().HandleClose() 1020 w.Handle(NopHandler{}) 1021 1022 close(w.closing) 1023 w.running.Wait() 1024 1025 w.conf.Log.Debugf("Saving chunks in memory to disk...") 1026 1027 w.chunkMu.Lock() 1028 w.lastChunk = nil 1029 toSave := maps.Clone(w.chunks) 1030 maps.Clear(w.chunks) 1031 w.chunkMu.Unlock() 1032 1033 for pos, c := range toSave { 1034 w.saveChunk(pos, c) 1035 } 1036 1037 w.set.ref.Dec() 1038 if !w.advance { 1039 return 1040 } 1041 1042 if !w.conf.ReadOnly { 1043 w.conf.Log.Debugf("Updating level.dat values...") 1044 1045 w.provider().SaveSettings(w.set) 1046 } 1047 1048 w.conf.Log.Debugf("Closing provider...") 1049 if err := w.provider().Close(); err != nil { 1050 w.conf.Log.Errorf("error closing world provider: %v", err) 1051 } 1052 } 1053 1054 // allViewers returns a list of all loaders of the world, regardless of where in the world they are viewing. 1055 func (w *World) allViewers() ([]Viewer, []*Loader) { 1056 w.viewersMu.Lock() 1057 defer w.viewersMu.Unlock() 1058 1059 viewers, loaders := make([]Viewer, 0, len(w.viewers)), make([]*Loader, 0, len(w.viewers)) 1060 for k, v := range w.viewers { 1061 viewers = append(viewers, v) 1062 loaders = append(loaders, k) 1063 } 1064 return viewers, loaders 1065 } 1066 1067 // addWorldViewer adds a viewer to the world. Should only be used while the viewer isn't viewing any chunks. 1068 func (w *World) addWorldViewer(l *Loader) { 1069 w.viewersMu.Lock() 1070 w.viewers[l] = l.viewer 1071 w.viewersMu.Unlock() 1072 l.viewer.ViewTime(w.Time()) 1073 w.set.Lock() 1074 raining, thundering := w.set.Raining, w.set.Raining && w.set.Thundering 1075 w.set.Unlock() 1076 l.viewer.ViewWeather(raining, thundering) 1077 l.viewer.ViewWorldSpawn(w.Spawn()) 1078 } 1079 1080 // removeWorldViewer removes a viewer from the world. Should only be used while the viewer isn't viewing any chunks. 1081 func (w *World) removeWorldViewer(l *Loader) { 1082 w.viewersMu.Lock() 1083 delete(w.viewers, l) 1084 w.viewersMu.Unlock() 1085 } 1086 1087 // addViewer adds a viewer to the world at a given position. Any events that happen in the chunk at that 1088 // position, such as block changes, entity changes etc., will be sent to the viewer. 1089 func (w *World) addViewer(c *Column, loader *Loader) { 1090 if w == nil { 1091 return 1092 } 1093 c.viewers = append(c.viewers, loader.viewer) 1094 c.loaders = append(c.loaders, loader) 1095 1096 entities := slices.Clone(c.Entities) 1097 c.Unlock() 1098 1099 for _, entity := range entities { 1100 showEntity(entity, loader.viewer) 1101 } 1102 } 1103 1104 // removeViewer removes a viewer from the world at a given position. All entities will be hidden from the 1105 // viewer and no more calls will be made when events in the chunk happen. 1106 func (w *World) removeViewer(pos ChunkPos, loader *Loader) { 1107 if w == nil { 1108 return 1109 } 1110 c, ok := w.chunkFromCache(pos) 1111 if !ok { 1112 return 1113 } 1114 if i := slices.Index(c.loaders, loader); i != -1 { 1115 c.viewers = slices.Delete(c.viewers, i, i+1) 1116 c.loaders = slices.Delete(c.loaders, i, i+1) 1117 } 1118 e := slices.Clone(c.Entities) 1119 c.Unlock() 1120 1121 // After removing the loader from the chunk, we also need to hide all entities from the viewer. 1122 for _, entity := range e { 1123 loader.viewer.HideEntity(entity) 1124 } 1125 } 1126 1127 // provider returns the provider of the world. It should always be used, rather than direct field access, in 1128 // order to provide synchronisation safety. 1129 func (w *World) provider() Provider { 1130 return w.conf.Provider 1131 } 1132 1133 // Handler returns the Handler of the world. It should always be used, rather than direct field access, in 1134 // order to provide synchronisation safety. 1135 func (w *World) Handler() Handler { 1136 if w == nil { 1137 return NopHandler{} 1138 } 1139 return w.handler.Load() 1140 } 1141 1142 // chunkFromCache attempts to fetch a chunk at the chunk position passed from the cache. If not found, the 1143 // chunk returned is nil and false is returned. 1144 func (w *World) chunkFromCache(pos ChunkPos) (*Column, bool) { 1145 w.chunkMu.Lock() 1146 c, ok := w.chunks[pos] 1147 w.chunkMu.Unlock() 1148 if ok { 1149 c.Lock() 1150 } 1151 return c, ok 1152 } 1153 1154 // showEntity shows an entity to a viewer of the world. It makes sure everything of the entity, including the 1155 // items held, is shown. 1156 func showEntity(e Entity, viewer Viewer) { 1157 viewer.ViewEntity(e) 1158 viewer.ViewEntityItems(e) 1159 viewer.ViewEntityArmour(e) 1160 } 1161 1162 // chunk reads a chunk from the position passed. If a chunk at that position is not yet loaded, the chunk is 1163 // loaded from the provider, or generated if it did not yet exist. Both of these actions are done 1164 // synchronously. 1165 // An error is returned if the chunk could not be loaded successfully. 1166 // chunk locks the chunk returned, meaning that any call to chunk made at the same time has to wait until the 1167 // user calls Chunk.Unlock() on the chunk returned. 1168 func (w *World) chunk(pos ChunkPos) *Column { 1169 w.chunkMu.Lock() 1170 if pos == w.lastPos && w.lastChunk != nil { 1171 c := w.lastChunk 1172 w.chunkMu.Unlock() 1173 c.Lock() 1174 return c 1175 } 1176 c, ok := w.chunks[pos] 1177 if !ok { 1178 var err error 1179 c, err = w.loadChunk(pos) 1180 chunk.LightArea([]*chunk.Chunk{c.Chunk}, int(pos[0]), int(pos[1])).Fill() 1181 if err != nil { 1182 w.chunkMu.Unlock() 1183 w.conf.Log.Errorf("load chunk: failed loading %v: %v\n", pos, err) 1184 return c 1185 } 1186 c.Unlock() 1187 w.chunkMu.Lock() 1188 1189 w.calculateLight(pos) 1190 } 1191 w.lastChunk, w.lastPos = c, pos 1192 w.chunkMu.Unlock() 1193 1194 c.Lock() 1195 return c 1196 } 1197 1198 // setChunk sets the chunk.Chunk passed at a specific ChunkPos without replacing any entities at that 1199 // position. 1200 // 1201 //lint:ignore U1000 This method is explicitly present to be used using compiler directives. 1202 func (w *World) setChunk(pos ChunkPos, c *chunk.Chunk, e map[cube.Pos]Block) { 1203 if w == nil { 1204 return 1205 } 1206 w.chunkMu.Lock() 1207 defer w.chunkMu.Unlock() 1208 1209 col := newColumn(c) 1210 maps.Copy(col.BlockEntities, e) 1211 if o, ok := w.chunks[pos]; ok { 1212 col.viewers = o.viewers 1213 } 1214 w.chunks[pos] = col 1215 } 1216 1217 // loadChunk attempts to load a chunk from the provider, or generates a chunk if one doesn't currently exist. 1218 func (w *World) loadChunk(pos ChunkPos) (*Column, error) { 1219 col, err := w.provider().LoadColumn(pos, w.conf.Dim) 1220 switch { 1221 case err == nil: 1222 w.chunks[pos] = col 1223 // Iterate through the entities twice and make sure they're added to all relevant maps. Note that this iteration 1224 // happens twice to avoid having to lock both worldsMu and entityMu. This is intentional, to avoid deadlocks. 1225 worldsMu.Lock() 1226 for _, e := range col.Entities { 1227 entityWorlds[e] = w 1228 } 1229 worldsMu.Unlock() 1230 1231 w.entityMu.Lock() 1232 for _, e := range col.Entities { 1233 w.entities[e] = pos 1234 } 1235 w.entityMu.Unlock() 1236 1237 col.Lock() 1238 w.chunkMu.Unlock() 1239 return col, nil 1240 case errors.Is(err, leveldb.ErrNotFound): 1241 // The provider doesn't have a chunk saved at this position, so we generate a new one. 1242 col = newColumn(chunk.New(airRID, w.Range())) 1243 w.chunks[pos] = col 1244 1245 col.Lock() 1246 w.chunkMu.Unlock() 1247 1248 w.conf.Generator.GenerateChunk(pos, col.Chunk) 1249 return col, nil 1250 default: 1251 col = newColumn(chunk.New(airRID, w.Range())) 1252 col.Lock() 1253 return col, err 1254 } 1255 } 1256 1257 // calculateLight calculates the light in the chunk passed and spreads the light of any of the surrounding 1258 // neighbours if they have all chunks loaded around it as a result of the one passed. 1259 func (w *World) calculateLight(centre ChunkPos) { 1260 for x := int32(-1); x <= 1; x++ { 1261 for z := int32(-1); z <= 1; z++ { 1262 // For all the neighbours of this chunk, if they exist, check if all neighbours of that chunk 1263 // now exist because of this one. 1264 pos := ChunkPos{centre[0] + x, centre[1] + z} 1265 if _, ok := w.chunks[pos]; ok { 1266 // Attempt to spread the light of all neighbours into the ones surrounding them. 1267 w.spreadLight(pos) 1268 } 1269 } 1270 } 1271 } 1272 1273 // spreadLight spreads the light from the chunk passed at the position passed to all neighbours if each of 1274 // them is loaded. 1275 func (w *World) spreadLight(pos ChunkPos) { 1276 chunks := make([]*Column, 0, 9) 1277 for z := int32(-1); z <= 1; z++ { 1278 for x := int32(-1); x <= 1; x++ { 1279 neighbour, ok := w.chunks[ChunkPos{pos[0] + x, pos[1] + z}] 1280 if !ok { 1281 // Not all surrounding chunks existed: Stop spreading light as we can't do it completely yet. 1282 return 1283 } 1284 chunks = append(chunks, neighbour) 1285 } 1286 } 1287 for _, neighbour := range chunks { 1288 neighbour.Lock() 1289 } 1290 // All chunks of the current one are present, so we can spread the light from this chunk 1291 // to all chunks. 1292 c := make([]*chunk.Chunk, 9) 1293 for i := range chunks { 1294 c[i] = chunks[i].Chunk 1295 } 1296 chunk.LightArea(c, int(pos[0])-1, int(pos[1])-1).Spread() 1297 for _, neighbour := range chunks { 1298 neighbour.Unlock() 1299 } 1300 } 1301 1302 // saveChunk is called when a chunk is removed from the cache. We first compact the chunk, then we write it to 1303 // the provider. 1304 func (w *World) saveChunk(pos ChunkPos, c *Column) { 1305 c.Lock() 1306 if !w.conf.ReadOnly && (len(c.BlockEntities) > 0 || len(c.Entities) > 0 || c.modified) { 1307 c.Compact() 1308 if err := w.provider().StoreColumn(pos, w.conf.Dim, c); err != nil { 1309 w.conf.Log.Errorf("save chunk: %v", err) 1310 } 1311 } 1312 ent := c.Entities 1313 c.Entities = nil 1314 c.Unlock() 1315 1316 for _, e := range ent { 1317 _ = e.Close() 1318 } 1319 } 1320 1321 // chunkCacheJanitor runs until the world is running, cleaning chunks that are no longer in use from the cache. 1322 func (w *World) chunkCacheJanitor() { 1323 t := time.NewTicker(time.Minute * 5) 1324 defer t.Stop() 1325 1326 w.running.Add(1) 1327 chunksToRemove := map[ChunkPos]*Column{} 1328 for { 1329 select { 1330 case <-t.C: 1331 w.chunkMu.Lock() 1332 for pos, c := range w.chunks { 1333 c.Lock() 1334 v := len(c.viewers) 1335 c.Unlock() 1336 if v == 0 { 1337 chunksToRemove[pos] = c 1338 delete(w.chunks, pos) 1339 if w.lastPos == pos { 1340 w.lastChunk = nil 1341 } 1342 } 1343 } 1344 w.chunkMu.Unlock() 1345 1346 for pos, c := range chunksToRemove { 1347 w.saveChunk(pos, c) 1348 delete(chunksToRemove, pos) 1349 } 1350 case <-w.closing: 1351 w.running.Done() 1352 return 1353 } 1354 } 1355 } 1356 1357 // Column represents the data of a chunk including the block entities and loaders. This data is protected 1358 // by the mutex present in the chunk.Chunk held. 1359 type Column struct { 1360 sync.Mutex 1361 modified bool 1362 1363 *chunk.Chunk 1364 Entities []Entity 1365 BlockEntities map[cube.Pos]Block 1366 1367 viewers []Viewer 1368 loaders []*Loader 1369 } 1370 1371 // newColumn returns a new Column wrapper around the chunk.Chunk passed. 1372 func newColumn(c *chunk.Chunk) *Column { 1373 return &Column{Chunk: c, BlockEntities: map[cube.Pos]Block{}} 1374 }