diff --git a/src/pocketmine/MemoryManager.php b/src/pocketmine/MemoryManager.php new file mode 100644 index 000000000..c60cbdfed --- /dev/null +++ b/src/pocketmine/MemoryManager.php @@ -0,0 +1,159 @@ +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(); + } +} \ No newline at end of file diff --git a/src/pocketmine/Player.php b/src/pocketmine/Player.php index 951ab3c64..1be321b96 100644 --- a/src/pocketmine/Player.php +++ b/src/pocketmine/Player.php @@ -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; diff --git a/src/pocketmine/PocketMine.php b/src/pocketmine/PocketMine.php index 14b3602ab..b6171a534 100644 --- a/src/pocketmine/PocketMine.php +++ b/src/pocketmine/PocketMine.php @@ -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")){ diff --git a/src/pocketmine/Server.php b/src/pocketmine/Server.php index b50425e85..83b6f3a5f 100644 --- a/src/pocketmine/Server.php +++ b/src/pocketmine/Server.php @@ -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() . @@ -2304,47 +2311,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 @@ -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(); diff --git a/src/pocketmine/block/Flowable.php b/src/pocketmine/block/Flowable.php index ccdb725e5..1072234a8 100644 --- a/src/pocketmine/block/Flowable.php +++ b/src/pocketmine/block/Flowable.php @@ -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; } diff --git a/src/pocketmine/command/CommandReader.php b/src/pocketmine/command/CommandReader.php index 9695d438c..a2783c15a 100644 --- a/src/pocketmine/command/CommandReader.php +++ b/src/pocketmine/command/CommandReader.php @@ -78,7 +78,7 @@ class CommandReader extends Thread{ if(($line = $this->readLine()) !== ""){ $this->buffer->synchronized(function (\Threaded $buffer, $line){ $buffer[] = preg_replace("#\\x1b\\x5b([^\\x1b]*\\x7e|[\\x40-\\x50])#", "", $line); - }, $this->buffer, $line ); + }, $this->buffer, $line); }elseif((microtime(true) - $lastLine) <= 0.1){ //Non blocking! Sleep to save CPU usleep(40000); } diff --git a/src/pocketmine/command/defaults/BanListCommand.php b/src/pocketmine/command/defaults/BanListCommand.php index 8d1d75340..251a096d3 100644 --- a/src/pocketmine/command/defaults/BanListCommand.php +++ b/src/pocketmine/command/defaults/BanListCommand.php @@ -52,6 +52,9 @@ class BanListCommand extends VanillaCommand{ return false; } + }else{ + $list = $sender->getServer()->getNameBans(); + $args[0] = "players"; } $message = ""; diff --git a/src/pocketmine/command/defaults/StatusCommand.php b/src/pocketmine/command/defaults/StatusCommand.php index 544eef036..4851f06f3 100644 --- a/src/pocketmine/command/defaults/StatusCommand.php +++ b/src/pocketmine/command/defaults/StatusCommand.php @@ -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; } diff --git a/src/pocketmine/entity/Squid.php b/src/pocketmine/entity/Squid.php index 31fbc48ec..653058426 100644 --- a/src/pocketmine/entity/Squid.php +++ b/src/pocketmine/entity/Squid.php @@ -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,8 +82,11 @@ class Squid extends WaterAnimal implements Ageable{ return false; } - if($currentTick % 20 === 0 and mt_rand(0, 100) < 5){ - $this->swimDirection = null; + if(++$this->switchDirectionTicker === 100){ + $this->switchDirectionTicker = 0; + if(mt_rand(0, 100) < 50){ + $this->swimDirection = null; + } } $this->lastUpdate = $currentTick; diff --git a/src/pocketmine/event/Timings.php b/src/pocketmine/event/Timings.php index 3c78e3dca..171f335ea 100644 --- a/src/pocketmine/event/Timings.php +++ b/src/pocketmine/event/Timings.php @@ -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"); diff --git a/src/pocketmine/event/server/LowMemoryEvent.php b/src/pocketmine/event/server/LowMemoryEvent.php new file mode 100644 index 000000000..41d0d499e --- /dev/null +++ b/src/pocketmine/event/server/LowMemoryEvent.php @@ -0,0 +1,79 @@ +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(); + } + +} \ No newline at end of file diff --git a/src/pocketmine/level/Level.php b/src/pocketmine/level/Level.php index 4410303fd..acab46959 100644 --- a/src/pocketmine/level/Level.php +++ b/src/pocketmine/level/Level.php @@ -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; diff --git a/src/pocketmine/level/format/FullChunk.php b/src/pocketmine/level/format/FullChunk.php index 548d8235e..38faba525 100644 --- a/src/pocketmine/level/format/FullChunk.php +++ b/src/pocketmine/level/format/FullChunk.php @@ -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); } \ No newline at end of file diff --git a/src/pocketmine/level/format/generic/BaseFullChunk.php b/src/pocketmine/level/format/generic/BaseFullChunk.php index 1a1ff95ee..6e46a83ce 100644 --- a/src/pocketmine/level/format/generic/BaseFullChunk.php +++ b/src/pocketmine/level/format/generic/BaseFullChunk.php @@ -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(); } diff --git a/src/pocketmine/level/format/leveldb/Chunk.php b/src/pocketmine/level/format/leveldb/Chunk.php index be853c73b..40d7fe93b 100644 --- a/src/pocketmine/level/format/leveldb/Chunk.php +++ b/src/pocketmine/level/format/leveldb/Chunk.php @@ -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)); diff --git a/src/pocketmine/level/format/mcregion/Chunk.php b/src/pocketmine/level/format/mcregion/Chunk.php index 0c9947140..2b83aee7c 100644 --- a/src/pocketmine/level/format/mcregion/Chunk.php +++ b/src/pocketmine/level/format/mcregion/Chunk.php @@ -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; } /** diff --git a/src/pocketmine/level/format/mcregion/RegionLoader.php b/src/pocketmine/level/format/mcregion/RegionLoader.php index 7d5955290..1bc30e918 100644 --- a/src/pocketmine/level/format/mcregion/RegionLoader.php +++ b/src/pocketmine/level/format/mcregion/RegionLoader.php @@ -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); } diff --git a/src/pocketmine/nbt/NBT.php b/src/pocketmine/nbt/NBT.php index c493e63c4..a287f36b4 100644 --- a/src/pocketmine/nbt/NBT.php +++ b/src/pocketmine/nbt/NBT.php @@ -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; } diff --git a/src/pocketmine/nbt/tag/ByteArray.php b/src/pocketmine/nbt/tag/ByteArray.php index 5f80e1452..f83f4b735 100644 --- a/src/pocketmine/nbt/tag/ByteArray.php +++ b/src/pocketmine/nbt/tag/ByteArray.php @@ -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; } diff --git a/src/pocketmine/nbt/tag/IntArray.php b/src/pocketmine/nbt/tag/IntArray.php index c1500fa23..3bcdabbe9 100644 --- a/src/pocketmine/nbt/tag/IntArray.php +++ b/src/pocketmine/nbt/tag/IntArray.php @@ -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); } } \ No newline at end of file diff --git a/src/pocketmine/nbt/tag/NamedTag.php b/src/pocketmine/nbt/tag/NamedTag.php index 70fca449e..6ab3d4b81 100644 --- a/src/pocketmine/nbt/tag/NamedTag.php +++ b/src/pocketmine/nbt/tag/NamedTag.php @@ -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){ diff --git a/src/pocketmine/nbt/tag/String.php b/src/pocketmine/nbt/tag/String.php index f615b1090..1f0bde0da 100644 --- a/src/pocketmine/nbt/tag/String.php +++ b/src/pocketmine/nbt/tag/String.php @@ -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; } diff --git a/src/pocketmine/network/CompressBatchedTask.php b/src/pocketmine/network/CompressBatchedTask.php index 565fcb9c9..0048dedbe 100644 --- a/src/pocketmine/network/CompressBatchedTask.php +++ b/src/pocketmine/network/CompressBatchedTask.php @@ -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){ } diff --git a/src/pocketmine/network/RakLibInterface.php b/src/pocketmine/network/RakLibInterface.php index 2e8b0d31f..f6e1d5677 100644 --- a/src/pocketmine/network/RakLibInterface.php +++ b/src/pocketmine/network/RakLibInterface.php @@ -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){ diff --git a/src/pocketmine/resources/pocketmine.yml b/src/pocketmine/resources/pocketmine.yml index 9b5e6b306..39fef54bd 100644 --- a/src/pocketmine/resources/pocketmine.yml +++ b/src/pocketmine/resources/pocketmine.yml @@ -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 diff --git a/src/pocketmine/scheduler/AsyncPool.php b/src/pocketmine/scheduler/AsyncPool.php index a58aac9e9..7be1af5d7 100644 --- a/src/pocketmine/scheduler/AsyncPool.php +++ b/src/pocketmine/scheduler/AsyncPool.php @@ -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(){ diff --git a/src/pocketmine/scheduler/AsyncTask.php b/src/pocketmine/scheduler/AsyncTask.php index e9ca81542..16594aeb5 100644 --- a/src/pocketmine/scheduler/AsyncTask.php +++ b/src/pocketmine/scheduler/AsyncTask.php @@ -131,4 +131,10 @@ abstract class AsyncTask extends \Collectable{ } + public function cleanObject(){ + foreach($this as $p => $v){ + $this->{$p} = null; + } + } + } diff --git a/src/pocketmine/scheduler/AsyncWorker.php b/src/pocketmine/scheduler/AsyncWorker.php index 76ce90c89..79ab59915 100644 --- a/src/pocketmine/scheduler/AsyncWorker.php +++ b/src/pocketmine/scheduler/AsyncWorker.php @@ -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){ diff --git a/src/pocketmine/scheduler/GarbageCollectionTask.php b/src/pocketmine/scheduler/GarbageCollectionTask.php new file mode 100644 index 000000000..e7b353d3d --- /dev/null +++ b/src/pocketmine/scheduler/GarbageCollectionTask.php @@ -0,0 +1,30 @@ +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 diff --git a/src/pocketmine/utils/Utils.php b/src/pocketmine/utils/Utils.php index fd0136a1e..6214542f3 100644 --- a/src/pocketmine/utils/Utils.php +++ b/src/pocketmine/utils/Utils.php @@ -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 * diff --git a/src/raklib b/src/raklib index a35e3d153..cba90210c 160000 --- a/src/raklib +++ b/src/raklib @@ -1 +1 @@ -Subproject commit a35e3d1535c327013de94e6d24078dc7573b57ec +Subproject commit cba90210cd8af5d924e29db6220927a0c896e12a