Level: Manage block cache appropriately when loading/unloading/replacing chunks, close #1813 (#1823)

This changes the block cache to be a 2-dimensional hashmap of [chunkHash][blockHash]. This is needed to more effectively dispose of chunk-specific block caches when chunks are modified.

This now destroys the cache for specific chunk indexes in the following circumstances:
- When a chunk is unloaded (the cache isn't needed anymore)
- When a chunk is set into the world (for example, from the generator)
- When a chunk is loaded (probably unnecessary)

This resolves the ghost blocks bug many have been complaining about.
This commit is contained in:
Dylan K. Taylor 2017-12-16 09:49:48 +00:00 committed by GitHub
parent 4ec8416f9a
commit 3853938ef3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -127,7 +127,7 @@ class Level implements ChunkManager, Metadatable{
public $updateEntities = [];
/** @var Tile[] */
public $updateTiles = [];
/** @var Block[] */
/** @var Block[][] */
private $blockCache = [];
/** @var BatchPacket[] */
@ -1450,15 +1450,17 @@ class Level implements ChunkManager, Metadatable{
*/
public function getBlockAt(int $x, int $y, int $z, bool $cached = true, bool $addToCache = true) : Block{
$fullState = 0;
$index = null;
$blockHash = null;
$chunkHash = Level::chunkHash($x >> 4, $z >> 4);
if($this->isInWorld($x, $y, $z)){
$index = Level::blockHash($x, $y, $z);
if($cached and isset($this->blockCache[$index])){
return $this->blockCache[$index];
$blockHash = Level::blockHash($x, $y, $z);
if($cached and isset($this->blockCache[$chunkHash][$blockHash])){
return $this->blockCache[$chunkHash][$blockHash];
}
$chunk = $this->chunks[$chunkIndex = Level::chunkHash($x >> 4, $z >> 4)] ?? null;
$chunk = $this->chunks[$chunkHash] ?? null;
if($chunk !== null){
$fullState = $chunk->getFullBlock($x & 0x0f, $y, $z & 0x0f);
}else{
@ -1473,8 +1475,8 @@ class Level implements ChunkManager, Metadatable{
$block->z = $z;
$block->level = $this;
if($addToCache and $index !== null){
$this->blockCache[$index] = $block;
if($addToCache and $blockHash !== null){
$this->blockCache[$chunkHash][$blockHash] = $block;
}
return $block;
@ -1612,19 +1614,21 @@ class Level implements ChunkManager, Metadatable{
$block->position($pos);
$block->clearCaches();
unset($this->blockCache[$blockHash = Level::blockHash($pos->x, $pos->y, $pos->z)]);
$index = Level::chunkHash($pos->x >> 4, $pos->z >> 4);
$chunkHash = Level::chunkHash($pos->x >> 4, $pos->z >> 4);
$blockHash = Level::blockHash($pos->x, $pos->y, $pos->z);
unset($this->blockCache[$chunkHash][$blockHash]);
if($direct === true){
$this->sendBlocks($this->getChunkPlayers($pos->x >> 4, $pos->z >> 4), [$block], UpdateBlockPacket::FLAG_ALL_PRIORITY);
unset($this->chunkCache[$index]);
unset($this->chunkCache[$chunkHash]);
}else{
if(!isset($this->changedBlocks[$index])){
$this->changedBlocks[$index] = [];
if(!isset($this->changedBlocks[$chunkHash])){
$this->changedBlocks[$chunkHash] = [];
}
$this->changedBlocks[$index][$blockHash] = clone $block;
$this->changedBlocks[$chunkHash][$blockHash] = clone $block;
}
foreach($this->getChunkLoaders($pos->x >> 4, $pos->z >> 4) as $loader){
@ -2146,13 +2150,13 @@ class Level implements ChunkManager, Metadatable{
* @param int $id 0-255
*/
public function setBlockIdAt(int $x, int $y, int $z, int $id){
unset($this->blockCache[$blockHash = Level::blockHash($x, $y, $z)]);
unset($this->blockCache[$chunkHash = Level::chunkHash($x >> 4, $z >> 4)][$blockHash = Level::blockHash($x, $y, $z)]);
$this->getChunk($x >> 4, $z >> 4, true)->setBlockId($x & 0x0f, $y, $z & 0x0f, $id & 0xff);
if(!isset($this->changedBlocks[$index = Level::chunkHash($x >> 4, $z >> 4)])){
$this->changedBlocks[$index] = [];
if(!isset($this->changedBlocks[$chunkHash])){
$this->changedBlocks[$chunkHash] = [];
}
$this->changedBlocks[$index][$blockHash] = $v = new Vector3($x, $y, $z);
$this->changedBlocks[$chunkHash][$blockHash] = $v = new Vector3($x, $y, $z);
foreach($this->getChunkLoaders($x >> 4, $z >> 4) as $loader){
$loader->onBlockChanged($v);
}
@ -2208,13 +2212,14 @@ class Level implements ChunkManager, Metadatable{
* @param int $data 0-15
*/
public function setBlockDataAt(int $x, int $y, int $z, int $data){
unset($this->blockCache[$blockHash = Level::blockHash($x, $y, $z)]);
unset($this->blockCache[$chunkHash = Level::chunkHash($x >> 4, $z >> 4)][$blockHash = Level::blockHash($x, $y, $z)]);
$this->getChunk($x >> 4, $z >> 4, true)->setBlockData($x & 0x0f, $y, $z & 0x0f, $data & 0x0f);
if(!isset($this->changedBlocks[$index = Level::chunkHash($x >> 4, $z >> 4)])){
$this->changedBlocks[$index] = [];
if(!isset($this->changedBlocks[$chunkHash])){
$this->changedBlocks[$chunkHash] = [];
}
$this->changedBlocks[$index][$blockHash] = $v = new Vector3($x, $y, $z);
$this->changedBlocks[$chunkHash][$blockHash] = $v = new Vector3($x, $y, $z);
foreach($this->getChunkLoaders($x >> 4, $z >> 4) as $loader){
$loader->onBlockChanged($v);
}
@ -2396,7 +2401,7 @@ class Level implements ChunkManager, Metadatable{
if($chunk === null){
return;
}
$index = Level::chunkHash($chunkX, $chunkZ);
$chunkHash = Level::chunkHash($chunkX, $chunkZ);
$oldChunk = $this->getChunk($chunkX, $chunkZ, false);
if($unload and $oldChunk !== null){
$this->unloadChunk($chunkX, $chunkZ, false, false);
@ -2418,9 +2423,10 @@ class Level implements ChunkManager, Metadatable{
}
$this->provider->setChunk($chunkX, $chunkZ, $chunk);
$this->chunks[$index] = $chunk;
$this->chunks[$chunkHash] = $chunk;
unset($this->chunkCache[$index]);
unset($this->blockCache[$chunkHash]);
unset($this->chunkCache[$chunkHash]);
$chunk->setChanged();
if(!$this->isChunkInUse($chunkX, $chunkZ)){
@ -2662,7 +2668,7 @@ class Level implements ChunkManager, Metadatable{
* @throws \InvalidStateException
*/
public function loadChunk(int $x, int $z, bool $generate = true) : bool{
if(isset($this->chunks[$index = Level::chunkHash($x, $z)])){
if(isset($this->chunks[$chunkHash = Level::chunkHash($x, $z)])){
return true;
}
@ -2678,7 +2684,9 @@ class Level implements ChunkManager, Metadatable{
return false;
}
$this->chunks[$index] = $chunk;
$this->chunks[$chunkHash] = $chunk;
unset($this->blockCache[$chunkHash]);
$chunk->initChunk($this);
$this->server->getPluginManager()->callEvent(new ChunkLoadEvent($this, $chunk, !$chunk->isGenerated()));
@ -2730,9 +2738,9 @@ class Level implements ChunkManager, Metadatable{
$this->timings->doChunkUnload->startTiming();
$index = Level::chunkHash($x, $z);
$chunkHash = Level::chunkHash($x, $z);
$chunk = $this->chunks[$index] ?? null;
$chunk = $this->chunks[$chunkHash] ?? null;
if($chunk !== null){
$this->server->getPluginManager()->callEvent($ev = new ChunkUnloadEvent($this, $chunk));
@ -2762,9 +2770,10 @@ class Level implements ChunkManager, Metadatable{
$logger->logException($e);
}
unset($this->chunks[$index]);
unset($this->chunkTickList[$index]);
unset($this->chunkCache[$index]);
unset($this->chunks[$chunkHash]);
unset($this->chunkTickList[$chunkHash]);
unset($this->chunkCache[$chunkHash]);
unset($this->blockCache[$chunkHash]);
$this->timings->doChunkUnload->stopTiming();