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\StonecutterShapelessRecipe;
use pocketmine\item\Item;
use pocketmine\level\ChunkLoader;
use pocketmine\level\format\FullChunk;
use pocketmine\level\format\LevelProvider;
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
*/
class Player extends Human implements CommandSender, InventoryHolder, IPlayer{
class Player extends Human implements CommandSender, InventoryHolder, ChunkLoader, IPlayer{
const SURVIVAL = 0;
const CREATIVE = 1;
@ -199,6 +200,8 @@ class Player extends Human implements CommandSender, InventoryHolder, IPlayer{
protected $sleeping = null;
protected $clientID = null;
private $loaderId = null;
protected $stepHeight = 0.6;
public $usedChunks = [];
@ -492,6 +495,7 @@ class Player extends Human implements CommandSender, InventoryHolder, IPlayer{
$this->ip = $ip;
$this->port = $port;
$this->clientID = $clientID;
$this->loaderId = Level::generateChunkLoaderId($this);
$this->chunksPerTick = (int) $this->server->getProperty("chunk-sending.per-tick", 4);
$this->spawnThreshold = (int) $this->server->getProperty("chunk-sending.spawn-threshold", 56);
$this->spawnPosition = null;
@ -527,11 +531,7 @@ class Player extends Human implements CommandSender, InventoryHolder, IPlayer{
return false;
}
if(!isset($this->achievements[$achievementId]) or $this->achievements[$achievementId] == false){
return false;
}
return true;
return isset($this->achievements[$achievementId]) and $this->achievements[$achievementId] != false;
}
/**
@ -596,10 +596,21 @@ class Player extends Human implements CommandSender, InventoryHolder, IPlayer{
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);
if(isset($this->usedChunks[$index])){
foreach($this->level->getChunkEntities($x, $z) as $entity){
foreach($level->getChunkEntities($x, $z) as $entity){
if($entity !== $this){
$entity->despawnFrom($this);
}
@ -607,7 +618,7 @@ class Player extends Human implements CommandSender, InventoryHolder, IPlayer{
unset($this->usedChunks[$index]);
}
$this->level->freeChunk($x, $z, $this);
$level->unregisterChunkLoader($this, $x, $z);
unset($this->loadQueue[$index]);
}
@ -672,11 +683,10 @@ class Player extends Human implements CommandSender, InventoryHolder, IPlayer{
}
}
unset($this->loadQueue[$index]);
$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);
}
@ -2757,7 +2767,7 @@ class Player extends Human implements CommandSender, InventoryHolder, IPlayer{
foreach($this->usedChunks as $index => $d){
Level::getXZ($index, $chunkX, $chunkZ);
$this->level->freeChunk($chunkX, $chunkZ, $this);
$this->level->unregisterChunkLoader($this, $chunkX, $chunkZ);
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\EffectCommand;
use pocketmine\command\defaults\GamemodeCommand;
use pocketmine\command\defaults\GarbageCollectorCommand;
use pocketmine\command\defaults\GiveCommand;
use pocketmine\command\defaults\HelpCommand;
use pocketmine\command\defaults\KickCommand;
@ -115,6 +116,7 @@ class SimpleCommandMap implements CommandMap{
if($this->server->getProperty("debug.commands", false) === true){
$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->despawnFromAll();
if($this instanceof Player){
foreach($this->usedChunks as $index => $d){
Level::getXZ($index, $X, $Z);
$this->unloadChunk($X, $Z);
}
}
}
$this->setLevel($targetLevel);
$this->level->addEntity($this);
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{
private static $levelIdCounter = 1;
private static $chunkLoaderCounter = 1;
public static $COMPRESSION_LEVEL = 8;
@ -156,8 +157,12 @@ class Level implements ChunkManager, Metadatable{
/** @var LevelProvider */
private $provider;
/** @var ChunkLoader[] */
private $loaders = [];
/** @var ChunkLoader[][] */
private $chunkLoaders = [];
/** @var Player[][] */
private $usedChunks = [];
private $playerLoaders = [];
/** @var FullChunk[]|Chunk[] */
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
*
@ -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 $Z
* @param int $chunkX
* @param int $chunkZ
*
* @return Player[]
*/
public function getUsingChunk($X, $Z){
return isset($this->usedChunks[$index = Level::chunkHash($X, $Z)]) ? $this->usedChunks[$index] : [];
public function getChunkPlayers($chunkX, $chunkZ){
return isset($this->playerLoaders[$index = Level::chunkHash($chunkX, $chunkZ)]) ? $this->playerLoaders[$index] : [];
}
/**
* WARNING: Do not use this, it's only for internal use.
* Changes to this function won't be recorded on the version.
* Gets the chunk loaders being used in a specific chunk
*
* @param int $X
* @param int $Z
* @param Player $player
* @param int $chunkX
* @param int $chunkZ
*
* @return ChunkLoader[]
*/
public function useChunk($X, $Z, Player $player){
$index = Level::chunkHash($X, $Z);
$this->loadChunk($X, $Z);
$this->usedChunks[$index][$player->getId()] = $player;
public function getChunkLoaders($chunkX, $chunkZ){
return isset($this->chunkLoaders[$index = Level::chunkHash($chunkX, $chunkZ)]) ? $this->chunkLoaders[$index] : [];
}
/**
* WARNING: Do not use this, it's only for internal use.
* 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()]);
public function registerChunkLoader(ChunkLoader $loader, $chunkX, $chunkZ, $autoLoad = true){
$hash = spl_object_hash($loader);
$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){
foreach($this->changedBlocks as $index => $blocks){
unset($this->chunkCache[$index]);
Level::getXZ($index, $X, $Z);
Level::getXZ($index, $chunkX, $chunkZ);
if(count($blocks) > 512){
foreach($this->getUsingChunk($X, $Z) as $p){
$p->unloadChunk($X, $Z);
$chunk = $this->getChunk($chunkX, $chunkZ);
foreach($this->getChunkPlayers($chunkX, $chunkZ) as $p){
$p->onChunkChanged($chunk);
}
}else{
$this->sendBlocks($this->getUsingChunk($X, $Z), $blocks, UpdateBlockPacket::FLAG_ALL);
$this->sendBlocks($this->getChunkPlayers($chunkX, $chunkZ), $blocks, UpdateBlockPacket::FLAG_ALL);
}
}
}else{
@ -688,20 +735,46 @@ class Level implements ChunkManager, Metadatable{
* @param Player[] $target
* @param Block[] $blocks
* @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();
foreach($blocks as $b){
if($b === null){
continue;
if($optimizeRebuilds){
$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));
}
@ -719,18 +792,18 @@ class Level implements ChunkManager, Metadatable{
return;
}
$chunksPerPlayer = min(200, max(1, (int) ((($this->chunksPerTick - count($this->players)) / count($this->players)) + 0.5)));
$randRange = 3 + $chunksPerPlayer / 30;
$chunksPerLoader = min(200, max(1, (int) ((($this->chunksPerTick - count($this->loaders)) / count($this->loaders)) + 0.5)));
$randRange = 3 + $chunksPerLoader / 30;
$randRange = $randRange > $this->chunkTickRadius ? $this->chunkTickRadius : $randRange;
foreach($this->players as $player){
$x = $player->x >> 4;
$z = $player->z >> 4;
foreach($this->loaders as $loader){
$chunkX = $loader->getX() >> 4;
$chunkZ = $loader->getZ() >> 4;
$index = Level::chunkHash($x, $z);
$existingPlayers = max(0, isset($this->chunkTickList[$index]) ? $this->chunkTickList[$index] : 0);
$this->chunkTickList[$index] = $existingPlayers + 1;
for($chunk = 0; $chunk < $chunksPerPlayer; ++$chunk){
$existingLoaders = max(0, isset($this->chunkTickList[$index]) ? $this->chunkTickList[$index] : 0);
$this->chunkTickList[$index] = $existingLoaders + 1;
for($chunk = 0; $chunk < $chunksPerLoader; ++$chunk){
$dx = mt_rand(-$randRange, $randRange);
$dz = mt_rand(-$randRange, $randRange);
$hash = Level::chunkHash($dx + $x, $dz + $z);
@ -742,7 +815,7 @@ class Level implements ChunkManager, Metadatable{
$blockTest = 0;
foreach($this->chunkTickList as $index => $players){
foreach($this->chunkTickList as $index => $loaders){
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){
unset($this->chunkTickList[$index]);
continue;
}elseif($players <= 0){
}elseif($loaders <= 0){
unset($this->chunkTickList[$index]);
}
@ -905,12 +978,12 @@ class Level implements ChunkManager, Metadatable{
* @return Block[]
*/
public function getCollisionBlocks(AxisAlignedBB $bb){
$minX = Math::floorFloat($bb->minX);
$minY = Math::floorFloat($bb->minY);
$minZ = Math::floorFloat($bb->minZ);
$maxX = Math::ceilFloat($bb->maxX);
$maxY = Math::ceilFloat($bb->maxY);
$maxZ = Math::ceilFloat($bb->maxZ);
$minX = (int) $bb->minX;
$minY = (int) $bb->minY;
$minZ = (int) $bb->minZ;
$maxX = (int) ($bb->maxX + 1);
$maxY = (int) ($bb->maxY + 1);
$maxZ = (int) ($bb->maxZ + 1);
$collides = [];
@ -1238,7 +1311,7 @@ class Level implements ChunkManager, Metadatable{
$index = Level::chunkHash($pos->x >> 4, $pos->z >> 4);
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]);
}else{
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;
}
foreach($this->getChunkLoaders($pos->x >> 4, $pos->z >> 4) as $loader){
$loader->onBlockChanged($block);
}
if($update === true){
$this->updateAllLight($block);
@ -1604,10 +1681,10 @@ class Level implements ChunkManager, Metadatable{
public function getNearbyEntities(AxisAlignedBB $bb, Entity $entity = null){
$nearby = [];
$minX = Math::floorFloat(($bb->minX - 2) / 16);
$maxX = Math::ceilFloat(($bb->maxX + 2) / 16);
$minZ = Math::floorFloat(($bb->minZ - 2) / 16);
$maxZ = Math::ceilFloat(($bb->maxZ + 2) / 16);
$minX = (int) (($bb->minX - 2) / 16);
$maxX = (int) (($bb->maxX + 2) / 16 + 1);
$minZ = (int) (($bb->minZ - 2) / 16);
$maxZ = (int) (($bb->maxZ + 2) / 16 + 1);
for($x = $minX; $x <= $maxX; ++$x){
for($z = $minZ; $z <= $maxZ; ++$z){
@ -1649,6 +1726,13 @@ class Level implements ChunkManager, Metadatable{
return $this->players;
}
/**
* @return ChunkLoader[]
*/
public function getLoaders(){
return $this->loaders;
}
/**
* 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)])){
$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)])){
$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);
if($chunk !== null and ($oldChunk === null or $oldChunk->isPopulated() === false) and $chunk->isPopulated() and $chunk->getProvider() !== null){
$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])){
unset($this->chunkGenerationQueue[$index]);
@ -1925,24 +2019,37 @@ class Level implements ChunkManager, Metadatable{
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){
return;
}
$index = Level::chunkHash($x, $z);
$index = Level::chunkHash($chunkX, $chunkZ);
if($unload){
foreach($this->getUsingChunk($x, $z) as $player){
$player->unloadChunk($x, $z);
if($this->isChunkLoaded($chunkX, $chunkZ) and ($oldChunk = $this->getChunk($chunkX, $chunkZ, false)) !== false){
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;
}else{
$this->provider->setChunk($x, $z, $chunk);
$this->provider->setChunk($chunkX, $chunkZ, $chunk);
$this->chunks[$index] = $chunk;
}
unset($this->chunkCache[$index]);
$chunk->setChanged();
foreach($this->getChunkLoaders($chunkX, $chunkZ) as $loader){
$loader->onChunkChanged($chunk);
}
}
/**
@ -2139,7 +2246,7 @@ class Level implements ChunkManager, Metadatable{
* @return bool
*/
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);
foreach($this->getChunkLoaders($x, $z) as $loader){
$loader->onChunkLoaded($chunk);
}
$this->timings->syncChunkLoadTimer->stopTiming();
return true;
@ -2227,18 +2338,24 @@ class Level implements ChunkManager, Metadatable{
}
try{
if($chunk !== null and $this->getAutoSave()){
$entities = 0;
foreach($chunk->getEntities() as $e){
if($e instanceof Player){
continue;
if($chunk !== null){
if($this->getAutoSave()){
$entities = 0;
foreach($chunk->getEntities() as $e){
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){
$this->provider->setChunk($x, $z, $chunk);
$this->provider->saveChunk($x, $z);
foreach($this->getChunkLoaders($x, $z) as $loader){
$loader->onChunkUnloaded($chunk);
}
}
$this->provider->unloadChunk($x, $z, $safe);
@ -2495,7 +2612,7 @@ class Level implements ChunkManager, Metadatable{
$this->timings->doChunkGC->stopTiming();
}
private function unloadChunks(){
public function unloadChunks(){
if(count($this->unloadQueue) > 0){
$X = null;
$Z = null;

View File

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

View File

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

View File

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