Added ChunkLoader interface

This commit is contained in:
Shoghi Cervantes 2015-05-16 15:28:19 +02:00
parent 7a34417e67
commit d6ebff412c
No known key found for this signature in database
GPG Key ID: 78464DB0A7837F89
10 changed files with 445 additions and 106 deletions

View File

@ -74,6 +74,7 @@ use pocketmine\inventory\ShapelessRecipe;
use pocketmine\inventory\SimpleTransactionGroup; use pocketmine\inventory\SimpleTransactionGroup;
use pocketmine\inventory\StonecutterShapelessRecipe; use pocketmine\inventory\StonecutterShapelessRecipe;
use pocketmine\item\Item; use pocketmine\item\Item;
use pocketmine\level\ChunkLoader;
use pocketmine\level\format\FullChunk; use pocketmine\level\format\FullChunk;
use pocketmine\level\format\LevelProvider; use pocketmine\level\format\LevelProvider;
use pocketmine\level\Level; use pocketmine\level\Level;
@ -129,7 +130,7 @@ use pocketmine\utils\Utils;
/** /**
* Main class that handles networking, recovery, and packet sending to the server part * Main class that handles networking, recovery, and packet sending to the server part
*/ */
class Player extends Human implements CommandSender, InventoryHolder, IPlayer{ class Player extends Human implements CommandSender, InventoryHolder, ChunkLoader, IPlayer{
const SURVIVAL = 0; const SURVIVAL = 0;
const CREATIVE = 1; const CREATIVE = 1;
@ -199,6 +200,8 @@ class Player extends Human implements CommandSender, InventoryHolder, IPlayer{
protected $sleeping = null; protected $sleeping = null;
protected $clientID = null; protected $clientID = null;
private $loaderId = null;
protected $stepHeight = 0.6; protected $stepHeight = 0.6;
public $usedChunks = []; public $usedChunks = [];
@ -492,6 +495,7 @@ class Player extends Human implements CommandSender, InventoryHolder, IPlayer{
$this->ip = $ip; $this->ip = $ip;
$this->port = $port; $this->port = $port;
$this->clientID = $clientID; $this->clientID = $clientID;
$this->loaderId = Level::generateChunkLoaderId($this);
$this->chunksPerTick = (int) $this->server->getProperty("chunk-sending.per-tick", 4); $this->chunksPerTick = (int) $this->server->getProperty("chunk-sending.per-tick", 4);
$this->spawnThreshold = (int) $this->server->getProperty("chunk-sending.spawn-threshold", 56); $this->spawnThreshold = (int) $this->server->getProperty("chunk-sending.spawn-threshold", 56);
$this->spawnPosition = null; $this->spawnPosition = null;
@ -527,11 +531,7 @@ class Player extends Human implements CommandSender, InventoryHolder, IPlayer{
return false; return false;
} }
if(!isset($this->achievements[$achievementId]) or $this->achievements[$achievementId] == false){ return isset($this->achievements[$achievementId]) and $this->achievements[$achievementId] != false;
return false;
}
return true;
} }
/** /**
@ -596,10 +596,21 @@ class Player extends Human implements CommandSender, InventoryHolder, IPlayer{
return $this->sleeping !== null; return $this->sleeping !== null;
} }
public function unloadChunk($x, $z){ protected function switchLevel(Level $targetLevel){
$oldLevel = $this->level;
if(parent::switchLevel($targetLevel)){
foreach($this->usedChunks as $index => $d){
Level::getXZ($index, $X, $Z);
$this->unloadChunk($X, $Z, $oldLevel);
}
}
}
private function unloadChunk($x, $z, Level $level = null){
$level = $level === null ? $this->level : $level;
$index = Level::chunkHash($x, $z); $index = Level::chunkHash($x, $z);
if(isset($this->usedChunks[$index])){ if(isset($this->usedChunks[$index])){
foreach($this->level->getChunkEntities($x, $z) as $entity){ foreach($level->getChunkEntities($x, $z) as $entity){
if($entity !== $this){ if($entity !== $this){
$entity->despawnFrom($this); $entity->despawnFrom($this);
} }
@ -607,7 +618,7 @@ class Player extends Human implements CommandSender, InventoryHolder, IPlayer{
unset($this->usedChunks[$index]); unset($this->usedChunks[$index]);
} }
$this->level->freeChunk($x, $z, $this); $level->unregisterChunkLoader($this, $x, $z);
unset($this->loadQueue[$index]); unset($this->loadQueue[$index]);
} }
@ -672,11 +683,10 @@ class Player extends Human implements CommandSender, InventoryHolder, IPlayer{
} }
} }
unset($this->loadQueue[$index]); unset($this->loadQueue[$index]);
$this->usedChunks[$index] = false; $this->usedChunks[$index] = false;
$this->level->useChunk($X, $Z, $this); $this->level->registerChunkLoader($this, $X, $Z);
$this->level->requestChunk($X, $Z, $this, LevelProvider::ORDER_ZXY); $this->level->requestChunk($X, $Z, $this, LevelProvider::ORDER_ZXY);
} }
@ -2757,7 +2767,7 @@ class Player extends Human implements CommandSender, InventoryHolder, IPlayer{
foreach($this->usedChunks as $index => $d){ foreach($this->usedChunks as $index => $d){
Level::getXZ($index, $chunkX, $chunkZ); Level::getXZ($index, $chunkX, $chunkZ);
$this->level->freeChunk($chunkX, $chunkZ, $this); $this->level->unregisterChunkLoader($this, $chunkX, $chunkZ);
unset($this->usedChunks[$index]); unset($this->usedChunks[$index]);
} }
@ -3174,4 +3184,32 @@ class Player extends Human implements CommandSender, InventoryHolder, IPlayer{
} }
public function onChunkChanged(FullChunk $chunk){
$this->loadQueue[Level::chunkHash($chunk->getX(), $chunk->getZ())] = abs(($this->x >> 4) - $chunk->getX()) + abs(($this->z >> 4) - $chunk->getZ());
}
public function onChunkLoaded(FullChunk $chunk){
}
public function onChunkPopulated(FullChunk $chunk){
}
public function onChunkUnloaded(FullChunk $chunk){
}
public function onBlockChanged(Vector3 $block){
}
public function getLoaderId(){
return $this->loaderId;
}
public function isLoaderActive(){
return $this->isConnected();
}
} }

View File

@ -29,6 +29,7 @@ use pocketmine\command\defaults\DeopCommand;
use pocketmine\command\defaults\DifficultyCommand; use pocketmine\command\defaults\DifficultyCommand;
use pocketmine\command\defaults\EffectCommand; use pocketmine\command\defaults\EffectCommand;
use pocketmine\command\defaults\GamemodeCommand; use pocketmine\command\defaults\GamemodeCommand;
use pocketmine\command\defaults\GarbageCollectorCommand;
use pocketmine\command\defaults\GiveCommand; use pocketmine\command\defaults\GiveCommand;
use pocketmine\command\defaults\HelpCommand; use pocketmine\command\defaults\HelpCommand;
use pocketmine\command\defaults\KickCommand; use pocketmine\command\defaults\KickCommand;
@ -115,6 +116,7 @@ class SimpleCommandMap implements CommandMap{
if($this->server->getProperty("debug.commands", false) === true){ if($this->server->getProperty("debug.commands", false) === true){
$this->register("pocketmine", new StatusCommand("status")); $this->register("pocketmine", new StatusCommand("status"));
$this->register("pocketmine", new GarbageCollectorCommand("gc"));
} }
} }

View File

@ -0,0 +1,67 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
namespace pocketmine\command\defaults;
use pocketmine\command\CommandSender;
use pocketmine\utils\TextFormat;
use pocketmine\utils\Utils;
class GarbageCollectorCommand extends VanillaCommand{
public function __construct($name){
parent::__construct(
$name,
"%pocketmine.command.gc.description",
"%pocketmine.command.gc.usage"
);
$this->setPermission("pocketmine.command.gc");
}
public function execute(CommandSender $sender, $currentAlias, array $args){
if(!$this->testPermission($sender)){
return true;
}
$chunksCollected = 0;
$entitiesCollected = 0;
$tilesCollected = 0;
foreach($sender->getServer()->getLevels() as $level){
$diff = [count($level->getChunks()), count($level->getEntities()), count($level->getTiles())];
$level->doChunkGarbageCollection();
$level->unloadChunks();
$chunksCollected += $diff[0] - count($level->getChunks());
$entitiesCollected += $diff[1] - count($level->getEntities());
$tilesCollected += $diff[2] - count($level->getTiles());
}
$cyclesCollected = $sender->getServer()->getMemoryManager()->triggerGarbageCollector();
$sender->sendMessage(TextFormat::GREEN . "---- " . TextFormat::WHITE . "Garbage collection result" . TextFormat::GREEN . " ----");
$sender->sendMessage(TextFormat::GOLD . "Chunks: " . TextFormat::RED . number_format($chunksCollected));
$sender->sendMessage(TextFormat::GOLD . "Entities: " . TextFormat::RED . number_format($entitiesCollected));
$sender->sendMessage(TextFormat::GOLD . "Tiles: " . TextFormat::RED . number_format($tilesCollected));
$sender->sendMessage(TextFormat::GOLD . "Cycles: " . TextFormat::RED . number_format($cyclesCollected));
return true;
}
}

View File

@ -978,13 +978,8 @@ abstract class Entity extends Location implements Metadatable{
$this->chunk->removeEntity($this); $this->chunk->removeEntity($this);
} }
$this->despawnFromAll(); $this->despawnFromAll();
if($this instanceof Player){
foreach($this->usedChunks as $index => $d){
Level::getXZ($index, $X, $Z);
$this->unloadChunk($X, $Z);
}
}
} }
$this->setLevel($targetLevel); $this->setLevel($targetLevel);
$this->level->addEntity($this); $this->level->addEntity($this);
if($this instanceof Player){ if($this instanceof Player){

View File

@ -0,0 +1,112 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
namespace pocketmine\level;
use pocketmine\block\Block;
use pocketmine\level\format\FullChunk;
use pocketmine\math\Vector3;
/**
* If you want to keep chunks loaded and receive notifications on a specific area,
* extend this class and register it into Level. This will also tick chunks.
*
* Register Level->registerChunkLoader($this, $chunkX, $chunkZ)
* Unregister Level->unregisterChunkLoader($this, $chunkX, $chunkZ)
*
* WARNING: When moving this object around in the world or destroying it,
* be sure to free the existing references from Level, otherwise you'll leak memory.
*/
interface ChunkLoader{
/**
* Returns the ChunkLoader id.
* Call Level::generateChunkLoaderId($this) to generate and save it
*
* @return int
*/
public function getLoaderId();
/**
* Returns if the chunk loader is currently active
*
* @return bool
*/
public function isLoaderActive();
/**
* @return Position
*/
public function getPosition();
/**
* @return float
*/
public function getX();
/**
* @return float
*/
public function getZ();
/**
* @return Level
*/
public function getLevel();
/**
* This method will be called when a Chunk is replaced by a new one
*
* @param FullChunk $chunk
*/
public function onChunkChanged(FullChunk $chunk);
/**
* This method will be called when a registered chunk is loaded
*
* @param FullChunk $chunk
*/
public function onChunkLoaded(FullChunk $chunk);
/**
* This method will be called when a registered chunk is unloaded
*
* @param FullChunk $chunk
*/
public function onChunkUnloaded(FullChunk $chunk);
/**
* This method will be called when a registered chunk is populated
* Usually it'll be sent with another call to onChunkChanged()
*
* @param FullChunk $chunk
*/
public function onChunkPopulated(FullChunk $chunk);
/**
* This method will be called when a block changes in a registered chunk
*
* @param Block|Vector3 $block
*/
public function onBlockChanged(Vector3 $block);
}

View File

@ -109,6 +109,7 @@ use pocketmine\level\particle\DestroyBlockParticle;
class Level implements ChunkManager, Metadatable{ class Level implements ChunkManager, Metadatable{
private static $levelIdCounter = 1; private static $levelIdCounter = 1;
private static $chunkLoaderCounter = 1;
public static $COMPRESSION_LEVEL = 8; public static $COMPRESSION_LEVEL = 8;
@ -156,8 +157,12 @@ class Level implements ChunkManager, Metadatable{
/** @var LevelProvider */ /** @var LevelProvider */
private $provider; private $provider;
/** @var ChunkLoader[] */
private $loaders = [];
/** @var ChunkLoader[][] */
private $chunkLoaders = [];
/** @var Player[][] */ /** @var Player[][] */
private $usedChunks = []; private $playerLoaders = [];
/** @var FullChunk[]|Chunk[] */ /** @var FullChunk[]|Chunk[] */
private $unloadQueue; private $unloadQueue;
@ -285,6 +290,14 @@ class Level implements ChunkManager, Metadatable{
} }
} }
public static function generateChunkLoaderId(ChunkLoader $loader){
if($loader->getLoaderId() === 0 or $loader->getLoaderId() === null or $loader->getLoaderId() === null){
return self::$chunkLoaderCounter++;
}else{
throw new \InvalidStateException("ChunkLoader has a loader id already assigned: " . $loader->getLoaderId());
}
}
/** /**
* Init the default level data * Init the default level data
* *
@ -504,43 +517,76 @@ class Level implements ChunkManager, Metadatable{
} }
/** /**
* Gets the chunks being used by players * @deprecated Use Level->getChunkPlayers($chunkX, $chunkZ)
*/
public function getUsingChunk($chunkX, $chunkZ){
return $this->getChunkPlayers($chunkX, $chunkZ);
}
/**
* Gets the players being used in a specific chunk
* *
* @param int $X * @param int $chunkX
* @param int $Z * @param int $chunkZ
* *
* @return Player[] * @return Player[]
*/ */
public function getUsingChunk($X, $Z){ public function getChunkPlayers($chunkX, $chunkZ){
return isset($this->usedChunks[$index = Level::chunkHash($X, $Z)]) ? $this->usedChunks[$index] : []; return isset($this->playerLoaders[$index = Level::chunkHash($chunkX, $chunkZ)]) ? $this->playerLoaders[$index] : [];
} }
/** /**
* WARNING: Do not use this, it's only for internal use. * Gets the chunk loaders being used in a specific chunk
* Changes to this function won't be recorded on the version.
* *
* @param int $X * @param int $chunkX
* @param int $Z * @param int $chunkZ
* @param Player $player *
* @return ChunkLoader[]
*/ */
public function useChunk($X, $Z, Player $player){ public function getChunkLoaders($chunkX, $chunkZ){
$index = Level::chunkHash($X, $Z); return isset($this->chunkLoaders[$index = Level::chunkHash($chunkX, $chunkZ)]) ? $this->chunkLoaders[$index] : [];
$this->loadChunk($X, $Z);
$this->usedChunks[$index][$player->getId()] = $player;
} }
/** public function registerChunkLoader(ChunkLoader $loader, $chunkX, $chunkZ, $autoLoad = true){
* WARNING: Do not use this, it's only for internal use. $hash = spl_object_hash($loader);
* Changes to this function won't be recorded on the version.
*
* @param int $X
* @param int $Z
* @param Player $player
*/
public function freeChunk($X, $Z, Player $player){
unset($this->usedChunks[$index = Level::chunkHash($X, $Z)][$player->getId()]);
$this->unloadChunkRequest($X, $Z, true); if(!isset($this->chunkLoaders[$index = Level::chunkHash($chunkX, $chunkZ)])){
$this->chunkLoaders[$index] = [];
$this->playerLoaders[$index] = [];
}elseif(isset($this->chunkLoaders[$index][$hash])){
return;
}
$this->chunkLoaders[$index][$hash] = $loader;
if($loader instanceof Player){
$this->playerLoaders[$index][$hash] = $loader;
}
if(!isset($this->loaders[$hash])){
$this->loaders[$hash] = 1;
}else{
++$this->loaders[$hash];
}
if($autoLoad){
$this->loadChunk($chunkX, $chunkZ);
}
}
public function unregisterChunkLoader(ChunkLoader $loader, $chunkX, $chunkZ){
if(isset($this->chunkLoaders[$index = Level::chunkHash($chunkX, $chunkZ)][$hash = spl_object_hash($loader)])){
unset($this->chunkLoaders[$index][$hash = spl_object_hash($loader)]);
unset($this->playerLoaders[$index][$hash]);
if(count($this->chunkLoaders[$index]) === 0){
unset($this->chunkLoaders[$index]);
unset($this->playerLoaders[$index]);
$this->unloadChunkRequest($chunkX, $chunkZ, true);
}
if(--$this->loaders[$hash] === 0){
unset($this->loaders[$hash]);
}
}
} }
/** /**
@ -632,13 +678,14 @@ class Level implements ChunkManager, Metadatable{
if(count($this->players) > 0){ if(count($this->players) > 0){
foreach($this->changedBlocks as $index => $blocks){ foreach($this->changedBlocks as $index => $blocks){
unset($this->chunkCache[$index]); unset($this->chunkCache[$index]);
Level::getXZ($index, $X, $Z); Level::getXZ($index, $chunkX, $chunkZ);
if(count($blocks) > 512){ if(count($blocks) > 512){
foreach($this->getUsingChunk($X, $Z) as $p){ $chunk = $this->getChunk($chunkX, $chunkZ);
$p->unloadChunk($X, $Z); foreach($this->getChunkPlayers($chunkX, $chunkZ) as $p){
$p->onChunkChanged($chunk);
} }
}else{ }else{
$this->sendBlocks($this->getUsingChunk($X, $Z), $blocks, UpdateBlockPacket::FLAG_ALL); $this->sendBlocks($this->getChunkPlayers($chunkX, $chunkZ), $blocks, UpdateBlockPacket::FLAG_ALL);
} }
} }
}else{ }else{
@ -688,20 +735,46 @@ class Level implements ChunkManager, Metadatable{
* @param Player[] $target * @param Player[] $target
* @param Block[] $blocks * @param Block[] $blocks
* @param int $flags * @param int $flags
* @param bool $optimizeRebuilds
*/ */
public function sendBlocks(array $target, array $blocks, $flags = UpdateBlockPacket::FLAG_NONE){ public function sendBlocks(array $target, array $blocks, $flags = UpdateBlockPacket::FLAG_NONE, $optimizeRebuilds = false){
$pk = new UpdateBlockPacket(); $pk = new UpdateBlockPacket();
foreach($blocks as $b){
if($b === null){ if($optimizeRebuilds){
continue; $chunks = [];
foreach($blocks as $b){
if($b === null){
continue;
}
$first = false;
if(!isset($chunks[$index = Level::chunkHash($b->x >> 4, $b->z >> 4)])){
$chunks[$index] = true;
$first = true;
}
if($b instanceof Block){
$pk->records[] = [$b->x, $b->z, $b->y, $b->getId(), $b->getDamage(), $first ? $flags : UpdateBlockPacket::FLAG_NONE];
}else{
$fullBlock = $this->getFullBlock($b->x, $b->y, $b->z);
$pk->records[] = [$b->x, $b->z, $b->y, $fullBlock >> 4, $fullBlock & 0xf, $first ? $flags : UpdateBlockPacket::FLAG_NONE];
}
}
}else{
foreach($blocks as $b){
if($b === null){
continue;
}
if($b instanceof Block){
$pk->records[] = [$b->x, $b->z, $b->y, $b->getId(), $b->getDamage(), $flags];
}else{
$fullBlock = $this->getFullBlock($b->x, $b->y, $b->z);
$pk->records[] = [$b->x, $b->z, $b->y, $fullBlock >> 4, $fullBlock & 0xf, $flags];
}
} }
if($b instanceof Block){
$pk->records[] = [$b->x, $b->z, $b->y, $b->getId(), $b->getDamage(), $flags];
}else{
$fullBlock = $this->getFullBlock($b->x, $b->y, $b->z);
$pk->records[] = [$b->x, $b->z, $b->y, $fullBlock >> 4, $fullBlock & 0xf, $flags];
}
} }
Server::broadcastPacket($target, $pk->setChannel(Network::CHANNEL_BLOCKS)); Server::broadcastPacket($target, $pk->setChannel(Network::CHANNEL_BLOCKS));
} }
@ -719,18 +792,18 @@ class Level implements ChunkManager, Metadatable{
return; return;
} }
$chunksPerPlayer = min(200, max(1, (int) ((($this->chunksPerTick - count($this->players)) / count($this->players)) + 0.5))); $chunksPerLoader = min(200, max(1, (int) ((($this->chunksPerTick - count($this->loaders)) / count($this->loaders)) + 0.5)));
$randRange = 3 + $chunksPerPlayer / 30; $randRange = 3 + $chunksPerLoader / 30;
$randRange = $randRange > $this->chunkTickRadius ? $this->chunkTickRadius : $randRange; $randRange = $randRange > $this->chunkTickRadius ? $this->chunkTickRadius : $randRange;
foreach($this->players as $player){ foreach($this->loaders as $loader){
$x = $player->x >> 4; $chunkX = $loader->getX() >> 4;
$z = $player->z >> 4; $chunkZ = $loader->getZ() >> 4;
$index = Level::chunkHash($x, $z); $index = Level::chunkHash($x, $z);
$existingPlayers = max(0, isset($this->chunkTickList[$index]) ? $this->chunkTickList[$index] : 0); $existingLoaders = max(0, isset($this->chunkTickList[$index]) ? $this->chunkTickList[$index] : 0);
$this->chunkTickList[$index] = $existingPlayers + 1; $this->chunkTickList[$index] = $existingLoaders + 1;
for($chunk = 0; $chunk < $chunksPerPlayer; ++$chunk){ for($chunk = 0; $chunk < $chunksPerLoader; ++$chunk){
$dx = mt_rand(-$randRange, $randRange); $dx = mt_rand(-$randRange, $randRange);
$dz = mt_rand(-$randRange, $randRange); $dz = mt_rand(-$randRange, $randRange);
$hash = Level::chunkHash($dx + $x, $dz + $z); $hash = Level::chunkHash($dx + $x, $dz + $z);
@ -742,7 +815,7 @@ class Level implements ChunkManager, Metadatable{
$blockTest = 0; $blockTest = 0;
foreach($this->chunkTickList as $index => $players){ foreach($this->chunkTickList as $index => $loaders){
Level::getXZ($index, $chunkX, $chunkZ); Level::getXZ($index, $chunkX, $chunkZ);
@ -750,7 +823,7 @@ class Level implements ChunkManager, Metadatable{
if(!isset($this->chunks[$index]) or ($chunk = $this->getChunk($chunkX, $chunkZ, false)) === null){ if(!isset($this->chunks[$index]) or ($chunk = $this->getChunk($chunkX, $chunkZ, false)) === null){
unset($this->chunkTickList[$index]); unset($this->chunkTickList[$index]);
continue; continue;
}elseif($players <= 0){ }elseif($loaders <= 0){
unset($this->chunkTickList[$index]); unset($this->chunkTickList[$index]);
} }
@ -905,12 +978,12 @@ class Level implements ChunkManager, Metadatable{
* @return Block[] * @return Block[]
*/ */
public function getCollisionBlocks(AxisAlignedBB $bb){ public function getCollisionBlocks(AxisAlignedBB $bb){
$minX = Math::floorFloat($bb->minX); $minX = (int) $bb->minX;
$minY = Math::floorFloat($bb->minY); $minY = (int) $bb->minY;
$minZ = Math::floorFloat($bb->minZ); $minZ = (int) $bb->minZ;
$maxX = Math::ceilFloat($bb->maxX); $maxX = (int) ($bb->maxX + 1);
$maxY = Math::ceilFloat($bb->maxY); $maxY = (int) ($bb->maxY + 1);
$maxZ = Math::ceilFloat($bb->maxZ); $maxZ = (int) ($bb->maxZ + 1);
$collides = []; $collides = [];
@ -1238,7 +1311,7 @@ class Level implements ChunkManager, Metadatable{
$index = Level::chunkHash($pos->x >> 4, $pos->z >> 4); $index = Level::chunkHash($pos->x >> 4, $pos->z >> 4);
if($direct === true){ if($direct === true){
$this->sendBlocks($this->getUsingChunk($pos->x >> 4, $pos->z >> 4), [$block], UpdateBlockPacket::FLAG_ALL_PRIORITY); $this->sendBlocks($this->getChunkPlayers($pos->x >> 4, $pos->z >> 4), [$block], UpdateBlockPacket::FLAG_ALL_PRIORITY);
unset($this->chunkCache[$index]); unset($this->chunkCache[$index]);
}else{ }else{
if(!isset($this->changedBlocks[$index])){ if(!isset($this->changedBlocks[$index])){
@ -1248,6 +1321,10 @@ class Level implements ChunkManager, Metadatable{
$this->changedBlocks[$index][Level::blockHash($block->x, $block->y, $block->z)] = clone $block; $this->changedBlocks[$index][Level::blockHash($block->x, $block->y, $block->z)] = clone $block;
} }
foreach($this->getChunkLoaders($pos->x >> 4, $pos->z >> 4) as $loader){
$loader->onBlockChanged($block);
}
if($update === true){ if($update === true){
$this->updateAllLight($block); $this->updateAllLight($block);
@ -1604,10 +1681,10 @@ class Level implements ChunkManager, Metadatable{
public function getNearbyEntities(AxisAlignedBB $bb, Entity $entity = null){ public function getNearbyEntities(AxisAlignedBB $bb, Entity $entity = null){
$nearby = []; $nearby = [];
$minX = Math::floorFloat(($bb->minX - 2) / 16); $minX = (int) (($bb->minX - 2) / 16);
$maxX = Math::ceilFloat(($bb->maxX + 2) / 16); $maxX = (int) (($bb->maxX + 2) / 16 + 1);
$minZ = Math::floorFloat(($bb->minZ - 2) / 16); $minZ = (int) (($bb->minZ - 2) / 16);
$maxZ = Math::ceilFloat(($bb->maxZ + 2) / 16); $maxZ = (int) (($bb->maxZ + 2) / 16 + 1);
for($x = $minX; $x <= $maxX; ++$x){ for($x = $minX; $x <= $maxX; ++$x){
for($z = $minZ; $z <= $maxZ; ++$z){ for($z = $minZ; $z <= $maxZ; ++$z){
@ -1649,6 +1726,13 @@ class Level implements ChunkManager, Metadatable{
return $this->players; return $this->players;
} }
/**
* @return ChunkLoader[]
*/
public function getLoaders(){
return $this->loaders;
}
/** /**
* Returns the Tile in a position, or null if not found * Returns the Tile in a position, or null if not found
* *
@ -1718,7 +1802,10 @@ class Level implements ChunkManager, Metadatable{
if(!isset($this->changedBlocks[$index = Level::chunkHash($x >> 4, $z >> 4)])){ if(!isset($this->changedBlocks[$index = Level::chunkHash($x >> 4, $z >> 4)])){
$this->changedBlocks[$index] = []; $this->changedBlocks[$index] = [];
} }
$this->changedBlocks[$index][Level::blockHash($x, $y, $z)] = new Vector3($x, $y, $z); $this->changedBlocks[$index][Level::blockHash($x, $y, $z)] = $v = new Vector3($x, $y, $z);
foreach($this->getChunkLoaders($x >> 4, $z >> 4) as $loader){
$loader->onBlockChanged($v);
}
} }
/** /**
@ -1749,7 +1836,10 @@ class Level implements ChunkManager, Metadatable{
if(!isset($this->changedBlocks[$index = Level::chunkHash($x >> 4, $z >> 4)])){ if(!isset($this->changedBlocks[$index = Level::chunkHash($x >> 4, $z >> 4)])){
$this->changedBlocks[$index] = []; $this->changedBlocks[$index] = [];
} }
$this->changedBlocks[$index][Level::blockHash($x, $y, $z)] = new Vector3($x, $y, $z); $this->changedBlocks[$index][Level::blockHash($x, $y, $z)] = $v = new Vector3($x, $y, $z);
foreach($this->getChunkLoaders($x >> 4, $z >> 4) as $loader){
$loader->onBlockChanged($v);
}
} }
/** /**
@ -1915,6 +2005,10 @@ class Level implements ChunkManager, Metadatable{
$chunk = $this->getChunk($x, $z, false); $chunk = $this->getChunk($x, $z, false);
if($chunk !== null and ($oldChunk === null or $oldChunk->isPopulated() === false) and $chunk->isPopulated() and $chunk->getProvider() !== null){ if($chunk !== null and ($oldChunk === null or $oldChunk->isPopulated() === false) and $chunk->isPopulated() and $chunk->getProvider() !== null){
$this->server->getPluginManager()->callEvent(new ChunkPopulateEvent($chunk)); $this->server->getPluginManager()->callEvent(new ChunkPopulateEvent($chunk));
foreach($this->getChunkLoaders($x, $z) as $loader){
$loader->onChunkPopulated($chunk);
}
} }
}elseif(isset($this->chunkGenerationQueue[$index]) or isset($this->chunkPopulationLock[$index])){ }elseif(isset($this->chunkGenerationQueue[$index]) or isset($this->chunkPopulationLock[$index])){
unset($this->chunkGenerationQueue[$index]); unset($this->chunkGenerationQueue[$index]);
@ -1925,24 +2019,37 @@ class Level implements ChunkManager, Metadatable{
Timings::$generationCallbackTimer->stopTiming(); Timings::$generationCallbackTimer->stopTiming();
} }
public function setChunk($x, $z, FullChunk $chunk = null, $unload = true){ /**
* @param int $chunkX
* @param int $chunkZ
* @param FullChunk $chunk
* @param bool $unload
*/
public function setChunk($chunkX, $chunkZ, FullChunk $chunk = null, $unload = true){
if($chunk === null){ if($chunk === null){
return; return;
} }
$index = Level::chunkHash($x, $z); $index = Level::chunkHash($chunkX, $chunkZ);
if($unload){ if($unload){
foreach($this->getUsingChunk($x, $z) as $player){ if($this->isChunkLoaded($chunkX, $chunkZ) and ($oldChunk = $this->getChunk($chunkX, $chunkZ, false)) !== false){
$player->unloadChunk($x, $z); foreach($this->getChunkLoaders($chunkX, $chunkZ) as $loader){
$loader->onChunkUnloaded($oldChunk);
}
} }
$this->provider->setChunk($x, $z, $chunk);
$this->provider->setChunk($chunkX, $chunkZ, $chunk);
$this->chunks[$index] = $chunk; $this->chunks[$index] = $chunk;
}else{ }else{
$this->provider->setChunk($x, $z, $chunk); $this->provider->setChunk($chunkX, $chunkZ, $chunk);
$this->chunks[$index] = $chunk; $this->chunks[$index] = $chunk;
} }
unset($this->chunkCache[$index]); unset($this->chunkCache[$index]);
$chunk->setChanged(); $chunk->setChanged();
foreach($this->getChunkLoaders($chunkX, $chunkZ) as $loader){
$loader->onChunkChanged($chunk);
}
} }
/** /**
@ -2139,7 +2246,7 @@ class Level implements ChunkManager, Metadatable{
* @return bool * @return bool
*/ */
public function isChunkInUse($x, $z){ public function isChunkInUse($x, $z){
return isset($this->usedChunks[$index = Level::chunkHash($x, $z)]) and count($this->usedChunks[$index]) > 0; return isset($this->chunkLoaders[$index = Level::chunkHash($x, $z)]) and count($this->chunkLoaders[$index]) > 0;
} }
/** /**
@ -2179,6 +2286,10 @@ class Level implements ChunkManager, Metadatable{
$chunk->setChanged(false); $chunk->setChanged(false);
foreach($this->getChunkLoaders($x, $z) as $loader){
$loader->onChunkLoaded($chunk);
}
$this->timings->syncChunkLoadTimer->stopTiming(); $this->timings->syncChunkLoadTimer->stopTiming();
return true; return true;
@ -2227,18 +2338,24 @@ class Level implements ChunkManager, Metadatable{
} }
try{ try{
if($chunk !== null and $this->getAutoSave()){ if($chunk !== null){
$entities = 0; if($this->getAutoSave()){
foreach($chunk->getEntities() as $e){ $entities = 0;
if($e instanceof Player){ foreach($chunk->getEntities() as $e){
continue; if($e instanceof Player){
continue;
}
++$entities;
}
if($chunk->hasChanged() or count($chunk->getTiles()) > 0 or $entities > 0){
$this->provider->setChunk($x, $z, $chunk);
$this->provider->saveChunk($x, $z);
} }
++$entities;
} }
if($chunk->hasChanged() or count($chunk->getTiles()) > 0 or $entities > 0){ foreach($this->getChunkLoaders($x, $z) as $loader){
$this->provider->setChunk($x, $z, $chunk); $loader->onChunkUnloaded($chunk);
$this->provider->saveChunk($x, $z);
} }
} }
$this->provider->unloadChunk($x, $z, $safe); $this->provider->unloadChunk($x, $z, $safe);
@ -2495,7 +2612,7 @@ class Level implements ChunkManager, Metadatable{
$this->timings->doChunkGC->stopTiming(); $this->timings->doChunkGC->stopTiming();
} }
private function unloadChunks(){ public function unloadChunks(){
if(count($this->unloadQueue) > 0){ if(count($this->unloadQueue) > 0){
$X = null; $X = null;
$Z = null; $Z = null;

View File

@ -296,29 +296,30 @@ class Chunk extends BaseFullChunk{
foreach($this->getEntities() as $entity){ foreach($this->getEntities() as $entity){
if(!($entity instanceof Player) and !$entity->closed){ if(!($entity instanceof Player) and !$entity->closed){
$entity->saveNBT(); $entity->saveNBT();
$nbt->setData($entity->namedtag); $entities[] = $entity->namedtag;
$entities[] = $nbt->write();
} }
} }
if(count($entities) > 0){ if(count($entities) > 0){
$provider->getDatabase()->put($chunkIndex . LevelDB::ENTRY_ENTITIES, implode($entities)); $nbt->setData($entities);
$provider->getDatabase()->put($chunkIndex . LevelDB::ENTRY_ENTITIES, $nbt->write());
}else{ }else{
$provider->getDatabase()->delete($chunkIndex . LevelDB::ENTRY_ENTITIES); $provider->getDatabase()->delete($chunkIndex . LevelDB::ENTRY_ENTITIES);
} }
$tiles = []; $tiles = [];
foreach($this->getTiles() as $tile){ foreach($this->getTiles() as $tile){
if(!$tile->closed){ if(!$tile->closed){
$tile->saveNBT(); $tile->saveNBT();
$nbt->setData($tile->namedtag); $tiles[] = $tile->namedtag;
$tiles[] = $nbt->write();
} }
} }
if(count($tiles) > 0){ if(count($tiles) > 0){
$provider->getDatabase()->put($chunkIndex . LevelDB::ENTRY_TILES, implode($tiles)); $nbt->setData($tiles);
$provider->getDatabase()->put($chunkIndex . LevelDB::ENTRY_TILES, $nbt->write());
}else{ }else{
$provider->getDatabase()->delete($chunkIndex . LevelDB::ENTRY_TILES); $provider->getDatabase()->delete($chunkIndex . LevelDB::ENTRY_TILES);
} }

View File

@ -314,11 +314,17 @@ class NBT{
$this->fromArray($this->data, $data); $this->fromArray($this->data, $data);
} }
/**
* @return Compound|array
*/
public function getData(){ public function getData(){
return $this->data; return $this->data;
} }
public function setData(Compound $data){ /**
* @param Compound|array $data
*/
public function setData($data){
$this->data = $data; $this->data = $data;
} }

View File

@ -116,6 +116,7 @@ abstract class DefaultPermissions{
self::registerPermission(new Permission(self::ROOT . ".command.defaultgamemode", "Allows the user to change the default gamemode", Permission::DEFAULT_OP), $commands); self::registerPermission(new Permission(self::ROOT . ".command.defaultgamemode", "Allows the user to change the default gamemode", Permission::DEFAULT_OP), $commands);
self::registerPermission(new Permission(self::ROOT . ".command.seed", "Allows the user to view the seed of the world", Permission::DEFAULT_OP), $commands); self::registerPermission(new Permission(self::ROOT . ".command.seed", "Allows the user to view the seed of the world", Permission::DEFAULT_OP), $commands);
self::registerPermission(new Permission(self::ROOT . ".command.status", "Allows the user to view the server performance", Permission::DEFAULT_OP), $commands); self::registerPermission(new Permission(self::ROOT . ".command.status", "Allows the user to view the server performance", Permission::DEFAULT_OP), $commands);
self::registerPermission(new Permission(self::ROOT . ".command.gc", "Allows the user to fire garbage collection tasks", Permission::DEFAULT_OP), $commands);
self::registerPermission(new Permission(self::ROOT . ".command.timings", "Allows the user to records timings for all plugin events", Permission::DEFAULT_OP), $commands); self::registerPermission(new Permission(self::ROOT . ".command.timings", "Allows the user to records timings for all plugin events", Permission::DEFAULT_OP), $commands);
self::registerPermission(new Permission(self::ROOT . ".command.spawnpoint", "Allows the user to change player's spawnpoint", Permission::DEFAULT_OP), $commands); self::registerPermission(new Permission(self::ROOT . ".command.spawnpoint", "Allows the user to change player's spawnpoint", Permission::DEFAULT_OP), $commands);
self::registerPermission(new Permission(self::ROOT . ".command.setworldspawn", "Allows the user to change the world spawn", Permission::DEFAULT_OP), $commands); self::registerPermission(new Permission(self::ROOT . ".command.setworldspawn", "Allows the user to change the world spawn", Permission::DEFAULT_OP), $commands);

View File

@ -82,7 +82,7 @@ network:
debug: debug:
#If > 1, it will show debug messages in the console #If > 1, it will show debug messages in the console
level: 1 level: 1
#Enables /status #Enables /status, /gc
commands: false commands: false
level-settings: level-settings: