Added MemoryManager, new memory properties, improved performance, updated RakLib, fixed misc. bugs

This commit is contained in:
Shoghi Cervantes 2015-04-18 20:13:52 +02:00
parent ddc152ae0a
commit b2c25eaf36
No known key found for this signature in database
GPG Key ID: 78464DB0A7837F89
32 changed files with 652 additions and 164 deletions

View File

@ -0,0 +1,159 @@
<?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;
use pocketmine\event\server\LowMemoryEvent;
use pocketmine\event\Timings;
use pocketmine\scheduler\GarbageCollectionTask;
use pocketmine\utils\Utils;
class MemoryManager{
/** @var Server */
private $server;
private $memoryLimit;
private $globalMemoryLimit;
private $checkRate;
private $checkTicker = 0;
private $lowMemory = false;
private $continuousTrigger = true;
private $continuousTriggerRate;
private $continuousTriggerCount = 0;
private $continuousTriggerTicker = 0;
private $garbageCollectionPeriod;
private $garbageCollectionTicker = 0;
private $garbageCollectionTrigger;
private $garbageCollectionAsync;
private $chunkLimit;
private $chunkTrigger;
private $chunkCache;
private $cacheTrigger;
public function __construct(Server $server){
$this->server = $server;
$this->init();
}
private function init(){
$this->memoryLimit = ((int) $this->server->getProperty("memory.main-limit", 320)) * 1024 * 1024;
$this->globalMemoryLimit = ((int) $this->server->getProperty("memory.global-limit", 512)) * 1024 * 1024;
$this->checkRate = ((int) $this->server->getProperty("memory.check-rate", 20));
$this->continuousTrigger = (bool) $this->server->getProperty("memory.continuous-trigger", true);
$this->continuousTriggerRate = (int) $this->server->getProperty("memory.continuous-trigger-rate", 600);
$this->garbageCollectionPeriod = (int) $this->server->getProperty("memory.garbage-collection.period", 12000);
$this->garbageCollectionTrigger = (bool) $this->server->getProperty("memory.garbage-collection.low-memory-trigger", true);
$this->garbageCollectionAsync = (bool) $this->server->getProperty("memory.garbage-collection.collect-async-worker", true);
$this->chunkLimit = (int) $this->server->getProperty("memory.max-chunks.trigger-limit", 12000);
$this->chunkTrigger = (bool) $this->server->getProperty("memory.max-chunks.low-memory-trigger", true);
$this->chunkCache = (bool) $this->server->getProperty("memory.world-caches.disable-chunk-cache", true);
$this->cacheTrigger = (bool) $this->server->getProperty("memory.world-caches.low-memory-trigger", true);
gc_enable();
}
public function isLowMemory(){
return $this->lowMemory;
}
public function canUseChunkCache(){
return !($this->lowMemory and $this->chunkTrigger);
}
public function getViewDistance($distance){
return $this->lowMemory ? min($this->chunkLimit, $distance) : $distance;
}
public function trigger($memory, $limit, $global = false, $triggerCount = 0){
$this->server->getLogger()->debug("[Memory Manager] ".($global ? "Global " : "") ."Low memory triggered, limit ". round(($limit / 1024) / 1024, 2)."MB, using ". round(($memory / 1024) / 1024, 2)."MB");
if($this->cacheTrigger){
foreach($this->server->getLevels() as $level){
$level->clearCache(true);
}
}
$ev = new LowMemoryEvent($memory, $limit, $triggerCount);
$this->server->getPluginManager()->callEvent($ev);
if($this->garbageCollectionTrigger){
$this->triggerGarbageCollector();
}
$this->server->getLogger()->debug("[Memory Manager] Freed " . round(($ev->getMemoryFreed() / 1024) / 1024, 2)."MB");
}
public function check(){
if(($this->memoryLimit > 0 or $this->globalMemoryLimit > 0) and ++$this->checkTicker >= $this->checkRate){
$this->checkTicker = 0;
$memory = Utils::getMemoryUsage(true);
$trigger = false;
if($this->memoryLimit > 0 and $memory[0] > $this->memoryLimit){
$trigger = true;
}elseif($this->globalMemoryLimit > 0 and $memory[1] > $this->globalMemoryLimit){
$trigger = true;
}
if($trigger){
if($this->lowMemory and $this->continuousTrigger){
if(++$this->continuousTriggerTicker >= $this->continuousTriggerRate){
$this->continuousTriggerTicker = 0;
$this->trigger($memory, $this->memoryLimit, ++$this->continuousTriggerCount);
}
}else{
$this->lowMemory = true;
$this->continuousTriggerCount = 0;
$this->trigger($memory, $this->memoryLimit);
}
}else{
$this->lowMemory = false;
}
}
if($this->garbageCollectionPeriod > 0 and ++$this->garbageCollectionTicker >= $this->garbageCollectionPeriod){
$this->garbageCollectionTicker = 0;
$this->triggerGarbageCollector();
}
}
public function triggerGarbageCollector(){
Timings::$garbageCollectorTimer->startTiming();
gc_collect_cycles();
if($this->garbageCollectionAsync){
$size = $this->server->getScheduler()->getAsyncTaskPoolSize();
for($i = 0; $i < $size; ++$i){
$this->server->getScheduler()->scheduleAsyncTaskToWorker(new GarbageCollectionTask(), $i);
}
}
Timings::$garbageCollectorTimer->stopTiming();
}
}

View File

@ -581,7 +581,7 @@ class Player extends Human implements CommandSender, InventoryHolder, IPlayer{
}
}
public function sendChunk($x, $z, $payload){
public function sendChunk($x, $z, &$payload){
if($this->connected === false){
return;
}
@ -592,7 +592,7 @@ class Player extends Human implements CommandSender, InventoryHolder, IPlayer{
$pk = new FullChunkDataPacket();
$pk->chunkX = $x;
$pk->chunkZ = $z;
$pk->data = $payload;
$pk->data =& $payload;
$this->batchDataPacket($pk->setChannel(Network::CHANNEL_WORLD_CHUNKS));
if($this->spawned){
@ -692,8 +692,8 @@ class Player extends Human implements CommandSender, InventoryHolder, IPlayer{
$this->nextChunkOrderRun = 200;
$radiusSquared = $this->viewDistance;
$radius = ceil(sqrt($radiusSquared));
$viewDistance = $this->server->getMemoryManager()->getViewDistance($this->viewDistance);
$radius = ceil(sqrt($viewDistance));
$side = ceil($radius / 2);
$newOrder = [];
@ -716,7 +716,7 @@ class Player extends Human implements CommandSender, InventoryHolder, IPlayer{
asort($currentQueue);
$limit = $this->viewDistance;
$limit = $viewDistance;
foreach($currentQueue as $index => $distance){
if($limit-- <= 0){
break;
@ -733,11 +733,12 @@ class Player extends Human implements CommandSender, InventoryHolder, IPlayer{
$loadedChunks = count($this->usedChunks);
if((count($newOrder) + $loadedChunks) > $this->viewDistance){
if((count($newOrder) + $loadedChunks) > $viewDistance){
$count = $loadedChunks;
$this->loadQueue = [];
foreach($newOrder as $k => $distance){
if(++$count > $this->viewDistance){
if(++$count > $viewDistance){
break;
}
$this->loadQueue[$k] = $distance;
@ -1107,7 +1108,7 @@ class Player extends Human implements CommandSender, InventoryHolder, IPlayer{
$revert = false;
if($distanceSquared > 100){
if(($distanceSquared / $this->level->getTickRate()) > 100){
$revert = true;
}else{
if($this->chunk === null or !$this->chunk->isGenerated()){
@ -2863,7 +2864,7 @@ class Player extends Human implements CommandSender, InventoryHolder, IPlayer{
if($source->isCancelled()){
return;
}elseif($this->getLastDamageCause() === $source){
}elseif($this->getLastDamageCause() === $source and $this->spawned){
$pk = new EntityEventPacket();
$pk->eid = $this->getId();
$pk->event = 2;

View File

@ -314,6 +314,24 @@ namespace pocketmine {
}
}
/**
* @param object $value
* @param bool $includeCurrent
*
* @return int
*/
function getReferenceCount($value, $includeCurrent = true){
ob_start();
debug_zval_dump($value);
$ret = explode("\n", ob_get_contents());
ob_end_clean();
if(count($ret) >= 1 and preg_match('/^.* refcount\\(([0-9]+)\\)\\{$/', trim($ret[0]), $m) > 0){
return ((int) $m[1]) - ($includeCurrent ? 3 : 4); //$value + zval call + extra call
}
return -1;
}
function getTrace($start = 1, $trace = null){
if($trace === null){
if(function_exists("xdebug_get_function_stack")){

View File

@ -159,6 +159,9 @@ class Server{
/** @var \AttachableThreadedLogger */
private $logger;
/** @var MemoryManager */
private $memoryManager;
/** @var CommandReader */
private $console = null;
private $consoleThreaded;
@ -879,6 +882,13 @@ class Server{
* @param Player $player
*/
public function removePlayer(Player $player){
if(isset($this->identifiers[$hash = spl_object_hash($player)])){
$identifier = $this->identifiers[$hash];
unset($this->players[$identifier]);
unset($this->identifiers[$hash]);
return;
}
foreach($this->players as $identifier => $p){
if($player === $p){
unset($this->players[$identifier]);
@ -1521,9 +1531,20 @@ class Server{
$this->baseLang = new BaseLang($this->getProperty("settings.language", BaseLang::FALLBACK_LANGUAGE));
$this->logger->info($this->getLanguage()->translateString("language.selected", [$this->getLanguage()->getName(), $this->getLanguage()->getLang()]));
$this->memoryManager = new MemoryManager($this);
$this->logger->info($this->getLanguage()->translateString("pocketmine.server.start", [TextFormat::AQUA . $this->getVersion()]));
ServerScheduler::$WORKERS = $this->getProperty("settings.async-workers", ServerScheduler::$WORKERS);
if(($poolSize = $this->getProperty("settings.async-workers", "auto")) === "auto"){
$poolSize = ServerScheduler::$WORKERS;
$processors = Utils::getCoreCount();
if($processors > 0){
$poolSize = max(2, $processors);
}
}
ServerScheduler::$WORKERS = $poolSize;
if($this->getProperty("network.batch-threshold", 256) >= 0){
Network::$BATCH_THRESHOLD = (int) $this->getProperty("network.batch-threshold", 256);
@ -1560,15 +1581,10 @@ class Server{
$this->maxPlayers = $this->getConfigInt("max-players", 20);
$this->setAutoSave($this->getConfigBoolean("auto-save", true));
if(($memory = str_replace("B", "", strtoupper($this->getConfigString("memory-limit", -1)))) !== false and $memory > 1){
$value = ["M" => 1, "G" => 1024];
$real = ((int) substr($memory, 0, -1)) * $value[substr($memory, -1)];
if($real < 128){
$this->logger->warning($this->getName() . " may not work right with less than 128MB of memory");
}
@ini_set("memory_limit", $memory);
$this->logger->notice("The memory limit will only affect the main thread, and it's unreliable.");
$this->logger->notice("To control the memory usage, reduce the amount of threads and chunks loaded");
if($this->getConfigString("memory-limit", -1) !== false){
$this->logger->notice("The memory-limit setting has been deprecated.");
$this->logger->notice("There are new memory settings on pocketmine.yml to tune memory and events.");
$this->logger->notice("You can also reduce the amount of threads and chunks loaded control the memory usage.");
}
if($this->getConfigBoolean("hardcore", false) === true and $this->getDifficulty() < 3){
@ -1791,11 +1807,7 @@ class Server{
}
if(!$forceSync and $this->networkCompressionAsync){
$task = new CompressBatchedTask();
$task->targets = $targets;
$task->data = $str;
$task->channel = $channel;
$task->level = $this->networkCompressionLevel;
$task = new CompressBatchedTask($str, $targets, $this->networkCompressionLevel, $channel);
$this->getScheduler()->scheduleAsyncTask($task);
}else{
$this->broadcastPacketsCallback(zlib_encode($str, ZLIB_ENCODING_DEFLATE, $this->networkCompressionLevel), $targets, $channel);
@ -1904,17 +1916,6 @@ class Server{
$this->properties->reload();
$this->maxPlayers = $this->getConfigInt("max-players", 20);
if(($memory = str_replace("B", "", strtoupper($this->getConfigString("memory-limit", -1)))) !== false and $memory > 1){
$value = ["M" => 1, "G" => 1024];
$real = ((int) substr($memory, 0, -1)) * $value[substr($memory, -1)];
if($real < 256){
$this->logger->warning($this->getName() . " may not work right with less than 256MB of memory", true, true, 0);
}
@ini_set("memory_limit", $memory);
$this->logger->notice("The memory limit will only affect the main thread, and it's unreliable.");
$this->logger->notice("To control the memory usage, reduce the amount of threads and chunks loaded");
}
if($this->getConfigBoolean("hardcore", false) === true and $this->getDifficulty() < 3){
$this->setConfigInt("difficulty", 3);
}
@ -2211,8 +2212,7 @@ class Server{
if($player->isOnline()){
$player->save();
}elseif(!$player->isConnected()){
unset($this->players[$index]);
unset($this->identifiers[spl_object_hash($player)]);
$this->removePlayer($player);
}
}
@ -2248,8 +2248,8 @@ class Server{
"port" => $this->getPort(),
"os" => Utils::getOS(),
"name" => $this->getName(),
"memory_total" => $this->getConfigString("memory-limit"),
"memory_usage" => $this->getMemoryUsage(),
"memory_total" => $this->getConfigString("memory-limit"), //TODO
"memory_usage" => Utils::getMemoryUsage(),
"php_version" => PHP_VERSION,
"version" => $version->get(true),
"build" => $version->getBuild(),
@ -2285,13 +2285,20 @@ class Server{
return $this->network;
}
/**
* @return MemoryManager
*/
public function getMemoryManager(){
return $this->memoryManager;
}
private function titleTick(){
if(!Terminal::hasFormattingCodes()){
return;
}
$u = $this->getMemoryUsage(true);
$usage = round(($u[0] / 1024) / 1024, 2) . "/".round(($u[1] / 1024) / 1024, 2)." MB @ " . $this->getThreadCount() . " threads";
$u = Utils::getMemoryUsage(true);
$usage = round(($u[0] / 1024) / 1024, 2) . "/" . round(($u[1] / 1024) / 1024, 2) . "/".round(($u[2] / 1024) / 1024, 2)." MB @ " . Utils::getThreadCount() . " threads";
echo "\x1b]0;" . $this->getName() . " " .
$this->getPocketMineVersion() .
@ -2305,47 +2312,6 @@ class Server{
$this->network->resetStatistics();
}
public function getMemoryUsage($advanced = false){
$VmSize = null;
$VmRSS = null;
if(Utils::getOS() === "linux" or Utils::getOS() === "bsd"){
$status = file_get_contents("/proc/self/status");
if(preg_match("/VmRSS:[ \t]+([0-9]+) kB/", $status, $matches) > 0){
$VmRSS = $matches[1] * 1024;
}
if(preg_match("/VmSize:[ \t]+([0-9]+) kB/", $status, $matches) > 0){
$VmSize = $matches[1] * 1024;
}
}
if($VmRSS === null){
$VmRSS = memory_get_usage();
}
if(!$advanced){
return $VmRSS;
}
if($VmSize === null){
$VmSize = memory_get_usage(true);
}
return [$VmRSS, $VmSize];
}
public function getThreadCount(){
if(Utils::getOS() === "linux" or Utils::getOS() === "bsd"){
if(preg_match("/Threads:[ \t]+([0-9]+)/", file_get_contents("/proc/self/status"), $matches) > 0){
return (int) $matches[1];
}
}
return count(ThreadManager::getInstance()->getAll()) + 3; //RakLib + MainLogger + Main Thread
}
/**
* @param string $address
* @param int $port
@ -2365,7 +2331,7 @@ class Server{
}
}
$this->blockAddress($address, 600);
$this->getNetwork()->blockAddress($address, 600);
}
//TODO: add raw packet events
}
@ -2398,7 +2364,7 @@ class Server{
if(($this->tickCounter & 0b1111) === 0){
$this->titleTick();
if(isset($this->queryHandler) and ($this->tickCounter & 0b111111111) === 0){
if($this->queryHandler !== null and ($this->tickCounter & 0b111111111) === 0){
try{
$this->queryHandler->regenerateInfo();
}catch(\Exception $e){
@ -2415,6 +2381,7 @@ class Server{
}
}
$this->getMemoryManager()->check();
Timings::$serverTickTimer->stopTiming();

View File

@ -22,12 +22,18 @@
namespace pocketmine\block;
use pocketmine\item\Item;
abstract class Flowable extends Transparent{
public function canBeFlowedInto(){
return true;
}
public function getBreakTime(Item $item){
return 0;
}
public function isSolid(){
return false;
}

View File

@ -52,6 +52,9 @@ class BanListCommand extends VanillaCommand{
return false;
}
}else{
$list = $sender->getServer()->getNameBans();
$args[0] = "players";
}
$message = "";

View File

@ -23,6 +23,7 @@ namespace pocketmine\command\defaults;
use pocketmine\command\CommandSender;
use pocketmine\utils\TextFormat;
use pocketmine\utils\Utils;
class StatusCommand extends VanillaCommand{
@ -40,14 +41,16 @@ class StatusCommand extends VanillaCommand{
return true;
}
$mUsage = Utils::getMemoryUsage(true);
$server = $sender->getServer();
$sender->sendMessage(TextFormat::GREEN . "---- " . TextFormat::WHITE . "Server status" . TextFormat::GREEN . " ----");
$sender->sendMessage(TextFormat::GOLD . "TPS: " . TextFormat::WHITE . $server->getTicksPerSecond());
$sender->sendMessage(TextFormat::GOLD . "TPS Load: " . TextFormat::WHITE . $server->getTickUsage() . "%");
$sender->sendMessage(TextFormat::GOLD . "Upload: " . TextFormat::WHITE . round($server->getNetwork()->getUpload() / 1024, 2) . " kB/s");
$sender->sendMessage(TextFormat::GOLD . "Download: " . TextFormat::WHITE . round($server->getNetwork()->getDownload() / 1024, 2) . " kB/s");
$sender->sendMessage(TextFormat::GOLD . "Memory: " . TextFormat::WHITE . round(($server->getMemoryUsage() / 1024) / 1024, 2) . " MB");
$sender->sendMessage(TextFormat::GOLD . "Threads: " . TextFormat::WHITE . $server->getThreadCount());
$sender->sendMessage(TextFormat::GOLD . "Memory: " . TextFormat::WHITE . round(($mUsage[0] / 1024) / 1024, 2) . "/".round(($mUsage[1] / 1024) / 1024, 2) . "/".round(($mUsage[2] / 1024) / 1024, 2) . " MB");
$sender->sendMessage(TextFormat::GOLD . "Threads: " . TextFormat::WHITE . Utils::getThreadCount());
return true;
}

View File

@ -43,6 +43,8 @@ class Squid extends WaterAnimal implements Ageable{
public $swimDirection = null;
public $swimSpeed = 0.1;
private $switchDirectionTicker = 0;
public function initEntity(){
$this->setMaxHealth(5);
parent::initEntity();
@ -80,9 +82,12 @@ class Squid extends WaterAnimal implements Ageable{
return false;
}
if($currentTick % 20 === 0 and mt_rand(0, 100) < 5){
if(++$this->switchDirectionTicker === 100){
$this->switchDirectionTicker = 0;
if(mt_rand(0, 100) < 50){
$this->swimDirection = null;
}
}
$this->lastUpdate = $currentTick;

View File

@ -33,6 +33,8 @@ abstract class Timings{
/** @var TimingsHandler */
public static $serverTickTimer;
/** @var TimingsHandler */
public static $garbageCollectorTimer;
/** @var TimingsHandler */
public static $playerListTimer;
/** @var TimingsHandler */
public static $connectionTimer;
@ -96,6 +98,7 @@ abstract class Timings{
}
self::$serverTickTimer = new TimingsHandler("** Full Server Tick");
self::$garbageCollectorTimer = new TimingsHandler("Garbage Collector");
self::$playerListTimer = new TimingsHandler("Player List");
self::$connectionTimer = new TimingsHandler("Connection Handler");
self::$tickablesTimer = new TimingsHandler("Tickables");

View File

@ -0,0 +1,79 @@
<?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\event\server;
use pocketmine\utils\Utils;
/**
* Called when the server is in a low-memory state as defined by the properties
* Plugins should free caches or other non-essential data.
*/
class LowMemoryEvent extends ServerEvent{
public static $handlerList = null;
private $memory;
private $memoryLimit;
private $triggerCount;
public function __construct($memory, $memoryLimit, $triggerCount = 0){
$this->memory = $memory;
$this->memoryLimit = $memoryLimit;
$this->triggerCount = (int) $triggerCount;
}
/**
* Returns the memory usage at the time of the event call (in bytes)
*
* @return int
*/
public function getMemory(){
return $this->memory;
}
/**
* Returns the memory limit defined (in bytes)
*
* @return int
*/
public function getMemoryLimit(){
return $this->memory;
}
/**
* Returns the times this event has been called in the current low-memory state
*
* @return int
*/
public function getTriggerCount(){
return $this->triggerCount;
}
/**
* Amount of memory already freed
*
* @return int
*/
public function getMemoryFreed(){
return $this->getMemory() - Utils::getMemoryUsage();
}
}

View File

@ -93,12 +93,10 @@ use pocketmine\scheduler\AsyncTask;
use pocketmine\Server;
use pocketmine\tile\Chest;
use pocketmine\tile\Tile;
use pocketmine\utils\Cache;
use pocketmine\utils\LevelException;
use pocketmine\utils\MainLogger;
use pocketmine\utils\Random;
use pocketmine\utils\ReversePriorityQueue;
use pocketmine\utils\TextFormat;
use pocketmine\level\particle\Particle;
use pocketmine\level\sound\Sound;
use pocketmine\entity\Effect;
@ -145,6 +143,8 @@ class Level implements ChunkManager, Metadatable{
private $cacheChunks = false;
private $sendTimeTicker = 0;
/** @var Server */
private $server;
@ -512,6 +512,7 @@ class Level implements ChunkManager, Metadatable{
*/
public function freeChunk($X, $Z, Player $player){
unset($this->usedChunks[$index = Level::chunkHash($X, $Z)][$player->getId()]);
$this->unloadChunkRequest($X, $Z, true);
}
@ -553,8 +554,9 @@ class Level implements ChunkManager, Metadatable{
$this->checkTime();
if(($currentTick % 200) === 0){
if(++$this->sendTimeTicker === 200){
$this->sendTime();
$this->sendTimeTicker = 0;
}
$this->unloadChunks();
@ -646,7 +648,11 @@ class Level implements ChunkManager, Metadatable{
Server::broadcastPacket($target, $pk->setChannel(Network::CHANNEL_BLOCKS));
}
public function clearCache(){
public function clearCache($full = false){
if($full){
$this->chunkCache = [];
}
$this->blockCache = [];
}
@ -1987,11 +1993,11 @@ class Level implements ChunkManager, Metadatable{
}
}
public function chunkRequestCallback($x, $z, $payload){
public function chunkRequestCallback($x, $z, &$payload){
$index = Level::chunkHash($x, $z);
if(!isset($this->chunkCache[$index]) and $this->cacheChunks){
$this->chunkCache[$index] = $payload;
if(!isset($this->chunkCache[$index]) and $this->cacheChunks and $this->server->getMemoryManager()->canUseChunkCache()){
$this->chunkCache[$index] =& $payload;
}
if(isset($this->chunkSendTasks[$index])){
@ -2077,7 +2083,7 @@ class Level implements ChunkManager, Metadatable{
* @return bool
*/
public function isChunkInUse($x, $z){
return isset($this->usedChunks[Level::chunkHash($x, $z)]) and count($this->usedChunks[Level::chunkHash($x, $z)]) > 0;
return isset($this->usedChunks[$index = Level::chunkHash($x, $z)]) and count($this->usedChunks[$index]) > 0;
}
/**
@ -2118,6 +2124,8 @@ class Level implements ChunkManager, Metadatable{
return false;
}
$chunk->setChanged(false);
return true;
}
@ -2145,6 +2153,10 @@ class Level implements ChunkManager, Metadatable{
return false;
}
if(!$this->isChunkLoaded($x, $z)){
return true;
}
$this->timings->doChunkUnload->startTiming();
$index = Level::chunkHash($x, $z);
@ -2154,6 +2166,7 @@ class Level implements ChunkManager, Metadatable{
if($chunk !== null and $chunk->getProvider() !== null){
$this->server->getPluginManager()->callEvent($ev = new ChunkUnloadEvent($chunk));
if($ev->isCancelled()){
$this->timings->doChunkUnload->stopTiming();
return false;
}
}
@ -2187,6 +2200,10 @@ class Level implements ChunkManager, Metadatable{
unset($this->chunkTickList[$index]);
unset($this->chunkCache[$index]);
$refs = \pocketmine\getReferenceCount($chunk);
//$this->server->getLogger()->debug("Unloaded $x $z (".count($this->getChunks()).") [refs $refs]");
$this->timings->doChunkUnload->stopTiming();
return true;

View File

@ -288,25 +288,25 @@ interface FullChunk{
/**
* @return string[]
*/
public function getBiomeIdArray();
public function &getBiomeIdArray();
/**
* @return int[]
*/
public function getBiomeColorArray();
public function &getBiomeColorArray();
/**
* @return int[]
*/
public function getHeightMapArray();
public function &getHeightMapArray();
public function getBlockIdArray();
public function &getBlockIdArray();
public function getBlockDataArray();
public function &getBlockDataArray();
public function getBlockSkyLightArray();
public function &getBlockSkyLightArray();
public function getBlockLightArray();
public function &getBlockLightArray();
public function toBinary();
@ -328,7 +328,7 @@ interface FullChunk{
*
* @return FullChunk
*/
public static function fromBinary($data, LevelProvider $provider = null);
public static function fromBinary(&$data, LevelProvider $provider = null);
/**
* @param string $data
@ -336,6 +336,6 @@ interface FullChunk{
*
* @return FullChunk
*/
public static function fromFastBinary($data, LevelProvider $provider = null);
public static function fromFastBinary(&$data, LevelProvider $provider = null);
}

View File

@ -87,13 +87,13 @@ abstract class BaseFullChunk implements FullChunk{
$this->x = (int) $x;
$this->z = (int) $z;
$this->blocks = $blocks;
$this->data = $data;
$this->skyLight = $skyLight;
$this->blockLight = $blockLight;
$this->blocks =& $blocks;
$this->data =& $data;
$this->skyLight =& $skyLight;
$this->blockLight =& $blockLight;
if(strlen($biomeIds) === 256){
$this->biomeIds = $biomeIds;
$this->biomeIds =& $biomeIds;
}else{
$this->biomeIds = str_repeat("\x01", 256);
}
@ -321,31 +321,31 @@ abstract class BaseFullChunk implements FullChunk{
return true;
}
public function getBlockIdArray(){
public function &getBlockIdArray(){
return $this->blocks;
}
public function getBlockDataArray(){
public function &getBlockDataArray(){
return $this->data;
}
public function getBlockSkyLightArray(){
public function &getBlockSkyLightArray(){
return $this->skyLight;
}
public function getBlockLightArray(){
public function &getBlockLightArray(){
return $this->blockLight;
}
public function getBiomeIdArray(){
public function &getBiomeIdArray(){
return $this->biomeIds;
}
public function getBiomeColorArray(){
public function &getBiomeColorArray(){
return $this->biomeColors;
}
public function getHeightMapArray(){
public function &getHeightMapArray(){
return $this->heightMap;
}
@ -357,11 +357,11 @@ abstract class BaseFullChunk implements FullChunk{
$this->hasChanged = (bool) $changed;
}
public static function fromFastBinary($data, LevelProvider $provider = null){
public static function &fromFastBinary(&$data, LevelProvider $provider = null){
static::fromBinary($data, $provider);
}
public function toFastBinary(){
public function &toFastBinary(){
return $this->toBinary();
}

View File

@ -221,7 +221,7 @@ class Chunk extends BaseFullChunk{
$this->isGenerated = (bool) $value;
}
public static function fromFastBinary($data, LevelProvider $provider = null){
public static function fromFastBinary(&$data, LevelProvider $provider = null){
return self::fromBinary($data, $provider);
}
@ -231,7 +231,7 @@ class Chunk extends BaseFullChunk{
*
* @return Chunk
*/
public static function fromBinary($data, LevelProvider $provider = null){
public static function fromBinary(&$data, LevelProvider $provider = null){
try{
$chunkX = Binary::readLInt(substr($data, 0, 4));
$chunkZ = Binary::readLInt(substr($data, 4, 4));

View File

@ -276,7 +276,7 @@ class Chunk extends BaseFullChunk{
*
* @return Chunk
*/
public static function fromBinary($data, LevelProvider $provider = null){
public static function fromBinary(&$data, LevelProvider $provider = null){
$nbt = new NBT(NBT::BIG_ENDIAN);
try{
@ -293,7 +293,7 @@ class Chunk extends BaseFullChunk{
}
}
public static function fromFastBinary($data, LevelProvider $provider = null){
public static function fromFastBinary(&$data, LevelProvider $provider = null){
try{
$offset = 0;
@ -340,11 +340,11 @@ class Chunk extends BaseFullChunk{
}
}
public function toFastBinary(){
public function &toFastBinary(){
$biomeColors = pack("N*", ...$this->getBiomeColorArray());
$heightMap = pack("N*", ...$this->getHeightMapArray());
return
$data =
Binary::writeInt($this->x) .
Binary::writeInt($this->z) .
$this->getBlockIdArray() .
@ -355,9 +355,10 @@ class Chunk extends BaseFullChunk{
$biomeColors .
$heightMap .
chr(($this->isPopulated() ? 1 << 1 : 0) + ($this->isGenerated() ? 1 : 0));
return $data;
}
public function toBinary(){
public function &toBinary(){
$nbt = clone $this->getNBT();
$nbt->xPos = new Int("xPos", $this->x);
@ -400,7 +401,8 @@ class Chunk extends BaseFullChunk{
$nbt->setName("Level");
$writer->setData(new Compound("", ["Level" => $nbt]));
return $writer->writeCompressed(ZLIB_ENCODING_DEFLATE, RegionLoader::$COMPRESSION_LEVEL);
$data =& $writer->writeCompressed(ZLIB_ENCODING_DEFLATE, RegionLoader::$COMPRESSION_LEVEL);
return $data;
}
/**

View File

@ -130,7 +130,8 @@ class RegionLoader{
return null;
}
$chunk = Chunk::fromBinary(fread($this->filePointer, $length - 1), $this->levelProvider);
$data = fread($this->filePointer, $length - 1);
$chunk = Chunk::fromBinary($data, $this->levelProvider);
if($chunk instanceof Chunk){
return $chunk;
}elseif($forward === false){
@ -156,12 +157,15 @@ class RegionLoader{
$nbt->TerrainPopulated = new Byte("TerrainPopulated", 0);
$nbt->V = new Byte("V", self::VERSION);
$nbt->InhabitedTime = new Long("InhabitedTime", 0);
$nbt->Biomes = new ByteArray("Biomes", str_repeat(Binary::writeByte(-1), 256));
$biomes = str_repeat(Binary::writeByte(-1), 256);
$nbt->Biomes = new ByteArray("Biomes", $biomes);
$nbt->HeightMap = new IntArray("HeightMap", array_fill(0, 256, 127));
$nbt->BiomeColors = new IntArray("BiomeColors", array_fill(0, 256, Binary::readInt("\x00\x85\xb2\x4a")));
$nbt->Blocks = new ByteArray("Blocks", str_repeat("\x00", 32768));
$nbt->Data = new ByteArray("Data", $half = str_repeat("\x00", 16384));
$half = str_repeat("\x00", 16384);
$full = $half . $half;
$nbt->Blocks = new ByteArray("Blocks", $full);
$nbt->Data = new ByteArray("Data", $half);
$nbt->SkyLight = new ByteArray("SkyLight", $half);
$nbt->BlockLight = new ByteArray("BlockLight", $half);
@ -181,7 +185,7 @@ class RegionLoader{
}
}
protected function saveChunk($x, $z, $chunkData){
protected function saveChunk($x, $z, &$chunkData){
$length = strlen($chunkData) + 1;
if($length + 4 > self::MAX_SECTOR_LENGTH){
throw new ChunkException("Chunk is too big! ".($length + 4)." > ".self::MAX_SECTOR_LENGTH);
@ -208,7 +212,7 @@ class RegionLoader{
public function writeChunk(FullChunk $chunk){
$this->lastUsed = time();
$chunkData = $chunk->toBinary();
$chunkData = &$chunk->toBinary();
if($chunkData !== false){
$this->saveChunk($chunk->getX() - ($this->getX() * 32), $chunk->getZ() - ($this->getZ() * 32), $chunkData);
}

View File

@ -84,7 +84,7 @@ class NBT{
return $len === 1 ? $this->buffer{$this->offset++} : substr($this->buffer, ($this->offset += $len) - $len, $len);
}
public function put($v){
public function put(&$v){
$this->buffer .= $v;
}
@ -99,7 +99,7 @@ class NBT{
public function read($buffer, $doMultiple = false){
$this->offset = 0;
$this->buffer = $buffer;
$this->buffer =& $buffer;
$this->data = $this->readTag();
if($doMultiple and $this->offset < strlen($this->buffer)){
$this->data = [$this->data];
@ -117,28 +117,30 @@ class NBT{
/**
* @return string|bool
*/
public function write(){
public function &write(){
$this->offset = 0;
$data = false;
if($this->data instanceof Compound){
$this->writeTag($this->data);
return $this->buffer;
$data =& $this->buffer;
}elseif(is_array($this->data)){
foreach($this->data as $tag){
$this->writeTag($tag);
}
return $this->buffer;
$data =& $this->buffer;
}
return false;
return $data;
}
public function writeCompressed($compression = ZLIB_ENCODING_GZIP, $level = 7){
public function &writeCompressed($compression = ZLIB_ENCODING_GZIP, $level = 7){
$data = false;
if(($write = $this->write()) !== false){
return zlib_encode($write, $compression, $level);
$data = zlib_encode($write, $compression, $level);
}
return false;
return $data;
}
public function readTag(){
@ -256,7 +258,7 @@ class NBT{
return $this->get($this->getShort());
}
public function putString($v){
public function putString(&$v){
$this->putShort(strlen($v));
$this->buffer .= $v;
}

View File

@ -27,6 +27,13 @@ use pocketmine\nbt\NBT;
class ByteArray extends NamedTag{
public function __construct($name = "", &$value = null){
$this->name = $name;
if($value !== null){
$this->value =& $value;
}
}
public function getType(){
return NBT::TAG_ByteArray;
}

View File

@ -42,6 +42,7 @@ class IntArray extends NamedTag{
public function write(NBT $nbt){
$nbt->putInt(count($this->value));
$nbt->put(pack($nbt->endianness === NBT::LITTLE_ENDIAN ? "V*" : "N*", ...$this->value));
$data = pack($nbt->endianness === NBT::LITTLE_ENDIAN ? "V*" : "N*", ...$this->value);
$nbt->put($data);
}
}

View File

@ -30,15 +30,15 @@ abstract class NamedTag extends Tag{
* @param string $name
* @param bool|float|double|int|byte|short|array|Compound|Enum|string $value
*/
public function __construct($name = "", $value = false){
public function __construct($name = "", $value = null){
$this->name = $name;
if($value !== false){
if($value !== null){
$this->value = $value;
}
}
public function getName(){
return $this->name === false ? "" : $this->name;
public function &getName(){
return $this->name;
}
public function setName($name){

View File

@ -27,6 +27,13 @@ use pocketmine\nbt\NBT;
class String extends NamedTag{
public function __construct($name = "", $value = null){
$this->name = $name;
if($value !== null){
$this->value =& $value;
}
}
public function getType(){
return NBT::TAG_String;
}

View File

@ -32,9 +32,17 @@ class CompressBatchedTask extends AsyncTask{
public $channel = 0;
public $targets = [];
public function __construct(&$data, array $targets, $level = 7, $channel = 0){
$this->data = $data;
$this->targets = $targets;
$this->level = $level;
$this->channel = $channel;
}
public function onRun(){
try{
$this->final = zlib_encode($this->data, ZLIB_ENCODING_DEFLATE, $this->level);
$this->data = null;
}catch(\Exception $e){
}

View File

@ -28,7 +28,6 @@ use pocketmine\network\protocol\UnknownPacket;
use pocketmine\Player;
use pocketmine\Server;
use pocketmine\utils\MainLogger;
use pocketmine\utils\TextFormat;
use raklib\protocol\EncapsulatedPacket;
use raklib\RakLib;
use raklib\server\RakLibServer;
@ -37,9 +36,6 @@ use raklib\server\ServerInstance;
class RakLibInterface implements ServerInstance, AdvancedSourceInterface{
/** @var \SplFixedArray */
private $packetPool;
/** @var Server */
private $server;
@ -145,7 +141,7 @@ class RakLibInterface implements ServerInstance, AdvancedSourceInterface{
public function handleEncapsulated($identifier, EncapsulatedPacket $packet, $flags){
if(isset($this->players[$identifier])){
try{
$pk = $this->getPacket($packet->buffer);
$pk = &$this->getPacket($packet->buffer);
$pk->decode();
$this->players[$identifier]->handleDataPacket($pk);
}catch(\Exception $e){
@ -250,7 +246,7 @@ class RakLibInterface implements ServerInstance, AdvancedSourceInterface{
return null;
}
private function getPacket($buffer){
private function &getPacket(&$buffer){
$pid = ord($buffer{0});
if(($data = $this->network->getPacket($pid)) === null){

View File

@ -21,7 +21,48 @@ settings:
#Number of AsyncTask workers.
#Used for plugin asynchronous tasks, world generation, compression and web communication.
#Set this approximately to your number of cores.
async-workers: 2
#If set to auto, it'll try to detect the number of cores (or use 2)
async-workers: auto
memory:
#Global soft memory limit in megabytes. Set to 0 to disable
#This will trigger low-memory-triggers and fire an event to free memory when the usage goes over this
global-limit: 512
#Main thread soft memory limit in megabytes. Set to 0 to disable
#This will trigger low-memory-triggers and fire an event to free memory when the usage goes over this
main-limit: 320
#Period in ticks to check memory (default 1 second)
check-rate: 20
#Continue firing low-memory-triggers and event while on low memory
continuous-trigger: true
#Only if memory.continuous-trigger is enabled. Specifies the rate in memory.check-rate steps (default 30 seconds)
continuous-trigger-rate: 30
garbage-collection:
#Period in ticks to fire the garbage collector manually (default 10 minutes), set to 0 to disable
#This only affect the main thread. Other threads should fire their own collections
period: 12000
#Fire asynchronous tasks to collect garbage from workers
collect-async-worker: true
#Trigger on low memory
low-memory-trigger: true
max-chunks:
#Limit of chunks to load per player, overrides chunk-sending.max-chunks
trigger-limit: 96
#Trigger on low memory
low-memory-trigger: true
world-caches:
disable-chunk-cache: true
low-memory-trigger: true
network:
#Threshold for batching packets, in bytes. Only these packets will be compressed
@ -51,9 +92,9 @@ level-settings:
chunk-sending:
#Amount of chunks sent to players per tick
per-tick: 8
per-tick: 4
#Amount of chunks sent around each player
max-chunks: 256
max-chunks: 192
#Amount of chunks that need to be sent before spawning the player
spawn-threshold: 56
#Save a serialized copy of the chunk in memory for faster sending

View File

@ -47,7 +47,7 @@ class AsyncPool{
for($i = 0; $i < $this->size; ++$i){
$this->workerUsage[$i] = 0;
$this->workers[$i] = new AsyncWorker;
$this->workers[$i]->setClassLoader($server->getLoader());
$this->workers[$i]->setClassLoader($this->server->getLoader());
$this->workers[$i]->start();
}
}
@ -56,13 +56,41 @@ class AsyncPool{
return $this->size;
}
public function submitTask(AsyncTask $task){
public function increaseSize($newSize){
$newSize = (int) $newSize;
if($newSize > $this->size){
$this->size = $newSize;
for($i = $this->size; $i < $newSize; ++$i){
$this->workerUsage[$i] = 0;
$this->workers[$i] = new AsyncWorker;
$this->workers[$i]->setClassLoader($this->server->getLoader());
$this->workers[$i]->start();
}
}
}
public function submitTaskToWorker(AsyncTask $task, $worker){
if(isset($this->tasks[$task->getTaskId()]) or $task->isGarbage()){
return;
}
$worker = (int) $worker;
if($worker < 0 or $worker >= $this->size){
throw new \InvalidArgumentException("Invalid worker $worker");
}
$this->tasks[$task->getTaskId()] = $task;
$this->workers[$worker]->stack($task);
$this->workerUsage[$worker]++;
$this->taskWorkers[$task->getTaskId()] = $worker;
}
public function submitTask(AsyncTask $task){
if(isset($this->tasks[$task->getTaskId()]) or $task->isGarbage()){
return;
}
$selectedWorker = mt_rand(0, $this->size - 1);
$selectedTasks = $this->workerUsage[$selectedWorker];
for($i = 0; $i < $this->size; ++$i){
@ -72,9 +100,7 @@ class AsyncPool{
}
}
$this->workers[$selectedWorker]->stack($task);
$this->workerUsage[$selectedWorker]++;
$this->taskWorkers[$task->getTaskId()] = $selectedWorker;
$this->submitTaskToWorker($task, $selectedWorker);
}
private function removeTask(AsyncTask $task){
@ -85,6 +111,8 @@ class AsyncPool{
unset($this->tasks[$task->getTaskId()]);
unset($this->taskWorkers[$task->getTaskId()]);
$task->cleanObject();
}
public function removeTasks(){

View File

@ -131,4 +131,10 @@ abstract class AsyncTask extends \Collectable{
}
public function cleanObject(){
foreach($this as $p => $v){
$this->{$p} = null;
}
}
}

View File

@ -28,9 +28,11 @@ class AsyncWorker extends Worker{
public function run(){
$this->registerClassLoader();
gc_enable();
ini_set("memory_limit", -1);
global $store;
$store = [];
}
public function start($options = PTHREADS_INHERIT_NONE){

View File

@ -0,0 +1,30 @@
<?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
*
*
*/
namespace pocketmine\scheduler;
class GarbageCollectionTask extends AsyncTask{
public function onRun(){
gc_enable();
gc_collect_cycles();
}
}

View File

@ -78,6 +78,28 @@ class ServerScheduler{
$this->asyncPool->submitTask($task);
}
/**
* Submits an asynchronous task to a specific Worker in the Pool
*
* @param AsyncTask $task
* @param int $worker
*
* @return void
*/
public function scheduleAsyncTaskToWorker(AsyncTask $task, $worker){
$id = $this->nextId();
$task->setTaskId($id);
$this->asyncPool->submitTaskToWorker($task, $worker);
}
public function getAsyncTaskPoolSize(){
return $this->asyncPool->getSize();
}
public function increaseAsyncTaskPoolSize($newSize){
$this->asyncPool->increaseSize($newSize);
}
/**
* @param Task $task
* @param int $delay

View File

@ -23,6 +23,7 @@
* Various Utilities used around the code
*/
namespace pocketmine\utils;
use pocketmine\ThreadManager;
/**
* Big collection of functions
@ -177,6 +178,76 @@ class Utils{
return self::$os;
}
public static function getMemoryUsage($advanced = false){
$reserved = memory_get_usage();
$VmSize = null;
$VmRSS = null;
if(Utils::getOS() === "linux" or Utils::getOS() === "android"){
$status = file_get_contents("/proc/self/status");
if(preg_match("/VmRSS:[ \t]+([0-9]+) kB/", $status, $matches) > 0){
$VmRSS = $matches[1] * 1024;
}
if(preg_match("/VmSize:[ \t]+([0-9]+) kB/", $status, $matches) > 0){
$VmSize = $matches[1] * 1024;
}
}
//TODO: more OS
if($VmRSS === null){
$VmRSS = memory_get_usage();
}
if(!$advanced){
return $VmRSS;
}
if($VmSize === null){
$VmSize = memory_get_usage(true);
}
return [$reserved, $VmRSS, $VmSize];
}
public static function getThreadCount(){
if(Utils::getOS() === "linux" or Utils::getOS() === "android"){
if(preg_match("/Threads:[ \t]+([0-9]+)/", file_get_contents("/proc/self/status"), $matches) > 0){
return (int) $matches[1];
}
}
//TODO: more OS
return count(ThreadManager::getInstance()->getAll()) + 3; //RakLib + MainLogger + Main Thread
}
public static function getCoreCount(){
$processors = 0;
switch(Utils::getOS()){
case "linux":
case "android":
if(file_exists("/proc/cpuinfo")){
foreach(file("/proc/cpuinfo") as $l){
if(preg_match('/^processor[ \t]*:[ \t]*[0-9]+$/m', $l) > 0){
++$processors;
}
}
}
break;
case "bsd":
$processors = (int) `sysctl hw.ncpu | awk '{ print $2+1 }'`;
break;
case "mac":
$processors = (int) `sysctl hw.availcpu | awk '{ print $2+1 }'`;
break;
case "win":
$processors = (int) getenv("NUMBER_OF_PROCESSORS");
break;
}
return $processors;
}
/**
* Returns a prettified hexdump
*

@ -1 +1 @@
Subproject commit a35e3d1535c327013de94e6d24078dc7573b57ec
Subproject commit cba90210cd8af5d924e29db6220927a0c896e12a