From 67b0c4bc2ea27cb79ec146392132af2221a92103 Mon Sep 17 00:00:00 2001 From: Shoghi Cervantes Date: Sat, 12 Jul 2014 02:26:06 +0200 Subject: [PATCH 1/5] Added base timings --- src/pocketmine/Server.php | 8 +- src/pocketmine/command/Command.php | 7 +- src/pocketmine/command/SimpleCommandMap.php | 4 + .../command/defaults/TimingsCommand.php | 141 ++++++++++++++++ src/pocketmine/event/TimingsHandler.php | 151 ++++++++++++++++++ .../permission/DefaultPermissions.php | 1 + src/pocketmine/plugin/MethodEventExecutor.php | 4 + src/pocketmine/plugin/PluginManager.php | 27 +++- src/pocketmine/plugin/RegisteredListener.php | 26 ++- src/pocketmine/resources/pocketmine.yml | 1 + 10 files changed, 361 insertions(+), 9 deletions(-) create mode 100644 src/pocketmine/command/defaults/TimingsCommand.php create mode 100644 src/pocketmine/event/TimingsHandler.php diff --git a/src/pocketmine/Server.php b/src/pocketmine/Server.php index 6c045bde5..d3f207202 100644 --- a/src/pocketmine/Server.php +++ b/src/pocketmine/Server.php @@ -36,6 +36,7 @@ use pocketmine\event\HandlerList; use pocketmine\event\level\LevelInitEvent; use pocketmine\event\level\LevelLoadEvent; use pocketmine\event\server\ServerCommandEvent; +use pocketmine\event\TimingsHandler; use pocketmine\inventory\CraftingManager; use pocketmine\inventory\InventoryType; use pocketmine\inventory\Recipe; @@ -1354,7 +1355,7 @@ class Server{ $this->filePath = $filePath; $this->dataPath = $dataPath; $this->pluginPath = $pluginPath; - @mkdir($this->dataPath . "worlds/", 0777); + @mkdir($this->dataPath . "worlds/", 0777, true); @mkdir($this->dataPath . "players/", 0777); @mkdir($this->pluginPath, 0777); @@ -1474,8 +1475,10 @@ class Server{ Item::init(); $this->craftingManager = new CraftingManager(); + PluginManager::$pluginParentTimer = new TimingsHandler("** Plugins"); $this->pluginManager = new PluginManager($this, $this->commandMap); $this->pluginManager->subscribeToPermission(Server::BROADCAST_CHANNEL_ADMINISTRATIVE, $this->consoleSender); + $this->pluginManager->setUseTimings($this->getProperty("settings.enable-profiling", false)); $this->pluginManager->registerInterface("pocketmine\\plugin\\PharPluginLoader"); $this->pluginManager->loadPlugins($this->pluginPath); @@ -1664,6 +1667,7 @@ class Server{ $this->pluginManager->loadPlugins($this->pluginPath); $this->enablePlugins(PluginLoadOrder::STARTUP); $this->enablePlugins(PluginLoadOrder::POSTWORLD); + TimingsHandler::reload(); } /** @@ -2009,6 +2013,8 @@ class Server{ } } + TimingsHandler::tick(); + $this->tickMeasure = (($time = microtime(true)) - $this->tickTime); $this->tickTime = $time; $this->nextTick = 0.05 * (0.05 / max(0.05, $this->tickMeasure)) + $time; diff --git a/src/pocketmine/command/Command.php b/src/pocketmine/command/Command.php index d2a65b670..898820f85 100644 --- a/src/pocketmine/command/Command.php +++ b/src/pocketmine/command/Command.php @@ -24,6 +24,7 @@ */ namespace pocketmine\command; +use pocketmine\event\TimingsHandler; use pocketmine\Server; use pocketmine\utils\TextFormat; @@ -62,6 +63,9 @@ abstract class Command{ /** @var string */ private $permissionMessage = null; + /** @var TimingsHandler */ + public $timings; + /** * @param string $name * @param string $description @@ -76,6 +80,7 @@ abstract class Command{ $this->usageMessage = $usageMessage === null ? "/" . $name : $usageMessage; $this->aliases = $aliases; $this->activeAliases = (array) $aliases; + $this->timings = new TimingsHandler("** Command: ". $name); } /** @@ -156,8 +161,8 @@ abstract class Command{ public function setLabel($name){ $this->nextLabel = $name; if(!$this->isRegistered()){ + $this->timings = new TimingsHandler("** Command: ". $name); $this->label = $name; - return true; } diff --git a/src/pocketmine/command/SimpleCommandMap.php b/src/pocketmine/command/SimpleCommandMap.php index efb51a7f6..2847b52cb 100644 --- a/src/pocketmine/command/SimpleCommandMap.php +++ b/src/pocketmine/command/SimpleCommandMap.php @@ -51,6 +51,7 @@ use pocketmine\command\defaults\StopCommand; use pocketmine\command\defaults\TeleportCommand; use pocketmine\command\defaults\TellCommand; use pocketmine\command\defaults\TimeCommand; +use pocketmine\command\defaults\TimingsCommand; use pocketmine\command\defaults\VanillaCommand; use pocketmine\command\defaults\VersionCommand; use pocketmine\command\defaults\WhitelistCommand; @@ -102,6 +103,7 @@ class SimpleCommandMap implements CommandMap{ $this->register("pocketmine", new SetWorldSpawnCommand("setworldspawn")); $this->register("pocketmine", new TeleportCommand("tp")); $this->register("pocketmine", new TimeCommand("time")); + $this->register("pocketmine", new TimingsCommand("timings")); $this->register("pocketmine", new ReloadCommand("reload")); if($this->server->getProperty("debug.commands", false) === true){ @@ -175,7 +177,9 @@ class SimpleCommandMap implements CommandMap{ return false; } + $target->timings->startTiming(); $target->execute($sender, $sentCommandLabel, $args); + $target->timings->stopTiming(); return true; } diff --git a/src/pocketmine/command/defaults/TimingsCommand.php b/src/pocketmine/command/defaults/TimingsCommand.php new file mode 100644 index 000000000..59a21135a --- /dev/null +++ b/src/pocketmine/command/defaults/TimingsCommand.php @@ -0,0 +1,141 @@ +" + ); + $this->setPermission("pocketmine.command.timings"); + } + + public function execute(CommandSender $sender, $currentAlias, array $args){ + if(!$this->testPermission($sender)){ + return true; + } + + if(count($args) !== 1){ + $sender->sendMessage(TextFormat::RED . "Usage: " . $this->usageMessage); + return true; + } + + $mode = strtolower($args[0]); + + if($mode === "on"){ + $sender->getServer()->getPluginManager()->setUseTimings(true); + TimingsHandler::reload(); + $sender->sendMessage("Enabled Timings & Reset"); + return true; + }elseif($mode === "off"){ + $sender->getServer()->getPluginManager()->setUseTimings(false); + $sender->sendMessage("Disabled Timings"); + } + + if(!$sender->getServer()->getPluginManager()->useTimings()){ + $sender->sendMessage("Please enable timings by typing /timings on"); + return true; + } + + $paste = $mode === "paste"; + + if($mode === "reset"){ + TimingsHandler::reload(); + $sender->sendMessage("Timings reset"); + /*foreach(HandlerList::getHandlerLists() as $handlerList){ + foreach($handlerList->getRegisteredListeners() as $listener){ + if($listener instanceof TimedRegisteredListener){ + $listener->reset(); + } + } + }*/ + }elseif($mode === "merged" or $mode === "report" or $paste){ + + $sampleTime = microtime(true) - self::$timingStart; + $index = 0; + $timingFolder = $sender->getServer()->getDataPath() . "timings/"; + @mkdir($timingFolder, 0777); + $timings = $timingFolder . "timings.txt"; + while(file_exists($timings)){ + $timings = $timingFolder . "timings" . (++$index) . ".txt"; + } + + $fileTimings = $paste ? fopen("php://temp", "r+b") : fopen($timings, "a+b"); + + TimingsHandler::printTimings($fileTimings); + + fwrite($fileTimings, "Sample time ". round($sampleTime * 1000000000) ." (". $sampleTime ."s)" . PHP_EOL); + + if($paste){ + fseek($fileTimings, 0); + $data = [ + "public" => false, + "description" => "PocketMine-MP Timings", + "files" => [ + "timings.txt" => [ + "content" => stream_get_contents($fileTimings) + ] + ] + ]; + + $ch = curl_init("https://api.github.com/gists"); + curl_setopt($ch, CURLOPT_POST, 1); + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); + curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2); + curl_setopt($ch, CURLOPT_FORBID_REUSE, 1); + curl_setopt($ch, CURLOPT_FRESH_CONNECT, 1); + curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data, JSON_UNESCAPED_SLASHES)); + curl_setopt($ch, CURLOPT_AUTOREFERER, true); + curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); + curl_setopt($ch, CURLOPT_HTTPHEADER, ["Content-Type: application/json", "User-Agent: PocketMine-MP ".$sender->getServer()->getPocketMineVersion()]); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + $ret = curl_exec($ch); + $data = json_decode($ret); + curl_close($ch); + if($data === false or $data === null or !isset($data->html_url)){ + $sender->sendMessage("An error happened while pasting the report"); + return true; + } + $timings = $data->html_url; + } + + fclose($fileTimings); + $sender->sendMessage("Timings written to ". $timings); + $sender->sendMessage("Paste contents of file into form at http://aikar.co/timings.php to read results."); + } + + return true; + } +} \ No newline at end of file diff --git a/src/pocketmine/event/TimingsHandler.php b/src/pocketmine/event/TimingsHandler.php new file mode 100644 index 000000000..38a02e0e0 --- /dev/null +++ b/src/pocketmine/event/TimingsHandler.php @@ -0,0 +1,151 @@ +name = $name; + if($parent instanceof TimingsHandler){ + $this->parent = $parent; + } + + self::$HANDLERS[spl_object_hash($this)] = $this; + } + + public static function printTimings($fp){ + fwrite($fp, "Minecraft" . PHP_EOL); + + foreach(self::$HANDLERS as $timings){ + $time = $timings->totalTime; + $count = $timings->count; + if($count === 0){ + continue; + } + + $avg = $time / $count; + + fwrite($fp, " " . $timings->name ." Time: ". round($time * 1000000000) ." Count: ". $count ." Avg: ". round($avg * 1000000000) ." Violations: ". $timings->violations . PHP_EOL); + } + + fwrite($fp, "# Version " . Server::getInstance()->getVersion() . PHP_EOL); + fwrite($fp, "# PocketMine-MP " . Server::getInstance()->getPocketMineVersion() . PHP_EOL); + + $entities = 0; + $livingEntities = 0; + foreach(Server::getInstance()->getLevels() as $level){ + $entities += count($level->getEntities()); + foreach($level->getEntities() as $e){ + if($e instanceof Living){ + ++$livingEntities; + } + } + } + + fwrite($fp, "# Entities ". $entities . PHP_EOL); + fwrite($fp, "# LivingEntities ". $livingEntities . PHP_EOL); + } + + public static function reload(){ + if(Server::getInstance()->getPluginManager()->useTimings()){ + foreach(self::$HANDLERS as $timings){ + $timings->reset(); + } + TimingsCommand::$timingStart = microtime(true); + } + } + + public static function tick(){ + if(PluginManager::$useTimings){ + foreach(self::$HANDLERS as $timings){ + if($timings->curTickTotal > 0.05){ + $timings->violations += ceil($timings->curTickTotal / 0.05); + } + $timings->curTickTotal = 0; + $timings->timingDepth = 0; + } + } + } + + public function startTiming(){ + if(PluginManager::$useTimings and ++$this->timingDepth === 1){ + $this->start = microtime(true); + if($this->parent instanceof TimingsHandler and ++$this->parent->timingDepth === 1){ + $this->parent->start = $this->start; + } + } + } + + public function stopTiming(){ + if(PluginManager::$useTimings){ + if(--$this->timingDepth !== 0 or $this->start === 0){ + return; + } + + $diff = microtime(true) - $this->start; + $this->totalTime += $diff; + $this->curTickTotal += $diff; + $this->count++; + $this->start = 0; + if($this->parent instanceof TimingsHandler){ + $this->parent->stopTiming(); + } + } + } + + public function reset(){ + $this->count = 0; + $this->violations = 0; + $this->curTickTotal = 0; + $this->totalTime = 0; + $this->start = 0; + $this->timingDepth = 0; + } + + public function remove(){ + unset(self::$HANDLERS[spl_object_hash($this)]); + } + +} \ No newline at end of file diff --git a/src/pocketmine/permission/DefaultPermissions.php b/src/pocketmine/permission/DefaultPermissions.php index c9b7ad036..45fda6efe 100644 --- a/src/pocketmine/permission/DefaultPermissions.php +++ b/src/pocketmine/permission/DefaultPermissions.php @@ -107,6 +107,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.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); diff --git a/src/pocketmine/plugin/MethodEventExecutor.php b/src/pocketmine/plugin/MethodEventExecutor.php index a735f2954..43137db8e 100644 --- a/src/pocketmine/plugin/MethodEventExecutor.php +++ b/src/pocketmine/plugin/MethodEventExecutor.php @@ -35,4 +35,8 @@ class MethodEventExecutor implements EventExecutor{ public function execute(Listener $listener, Event $event){ call_user_func(array($listener, $this->method), $event); } + + public function getMethod(){ + return $this->method; + } } \ No newline at end of file diff --git a/src/pocketmine/plugin/PluginManager.php b/src/pocketmine/plugin/PluginManager.php index 3d55c638f..91f36c073 100644 --- a/src/pocketmine/plugin/PluginManager.php +++ b/src/pocketmine/plugin/PluginManager.php @@ -21,12 +21,14 @@ namespace pocketmine\plugin; +use pocketmine\command\defaults\TimingsCommand; use pocketmine\command\PluginCommand; use pocketmine\command\SimpleCommandMap; use pocketmine\event\Event; use pocketmine\event\EventPriority; use pocketmine\event\HandlerList; use pocketmine\event\Listener; +use pocketmine\event\TimingsHandler; use pocketmine\permission\Permissible; use pocketmine\permission\Permission; use pocketmine\Server; @@ -82,6 +84,11 @@ class PluginManager{ */ protected $fileAssociations = []; + /** @var TimingsHandler */ + public static $pluginParentTimer; + + public static $useTimings = false; + /** * @param Server $server * @param SimpleCommandMap $commandMap @@ -306,8 +313,10 @@ class PluginManager{ } } + TimingsCommand::$timingStart = microtime(true); return $loadedPlugins; }else{ + TimingsCommand::$timingStart = microtime(true); return []; } } @@ -699,7 +708,9 @@ class PluginManager{ throw new \Exception("Plugin attempted to register " . $event . " while not enabled"); } - $this->getEventListeners($event)->register(new RegisteredListener($listener, $executor, $priority, $plugin, $ignoreCancelled)); + $timings = new TimingsHandler("Plugin: ".$plugin->getDescription()->getName()." Event: ".get_class($listener)."::".($executor instanceof MethodEventExecutor ? $executor->getMethod() : "???")."(".(new \ReflectionClass($event))->getShortName().")", self::$pluginParentTimer); + + $this->getEventListeners($event)->register(new RegisteredListener($listener, $executor, $priority, $plugin, $ignoreCancelled, $timings)); } /** @@ -715,4 +726,18 @@ class PluginManager{ return $event::$handlerList; } + /** + * @return bool + */ + public function useTimings(){ + return self::$useTimings; + } + + /** + * @param bool $use + */ + public function setUseTimings($use){ + self::$useTimings = (bool) $use; + } + } diff --git a/src/pocketmine/plugin/RegisteredListener.php b/src/pocketmine/plugin/RegisteredListener.php index b24e01e5e..e613ee7c7 100644 --- a/src/pocketmine/plugin/RegisteredListener.php +++ b/src/pocketmine/plugin/RegisteredListener.php @@ -24,6 +24,7 @@ namespace pocketmine\plugin; use pocketmine\event\Cancellable; use pocketmine\event\Event; use pocketmine\event\Listener; +use pocketmine\event\TimingsHandler; class RegisteredListener{ @@ -42,19 +43,26 @@ class RegisteredListener{ /** @var bool */ private $ignoreCancelled; + /** @var TimingsHandler */ + private $timings; + + + /** - * @param Listener $listener - * @param EventExecutor $executor - * @param int $priority - * @param Plugin $plugin - * @param boolean $ignoreCancelled + * @param Listener $listener + * @param EventExecutor $executor + * @param int $priority + * @param Plugin $plugin + * @param boolean $ignoreCancelled + * @param TimingsHandler $timings */ - public function __construct(Listener $listener, EventExecutor $executor, $priority, Plugin $plugin, $ignoreCancelled){ + public function __construct(Listener $listener, EventExecutor $executor, $priority, Plugin $plugin, $ignoreCancelled, TimingsHandler $timings){ $this->listener = $listener; $this->priority = $priority; $this->plugin = $plugin; $this->executor = $executor; $this->ignoreCancelled = $ignoreCancelled; + $this->timings = $timings; } /** @@ -85,7 +93,13 @@ class RegisteredListener{ if($event instanceof Cancellable and $event->isCancelled() and $this->isIgnoringCancelled()){ return; } + $this->timings->startTiming(); $this->executor->execute($this->listener, $event); + $this->timings->stopTiming(); + } + + public function __destruct(){ + $this->timings->remove(); } /** diff --git a/src/pocketmine/resources/pocketmine.yml b/src/pocketmine/resources/pocketmine.yml index a932e6269..77957cea1 100644 --- a/src/pocketmine/resources/pocketmine.yml +++ b/src/pocketmine/resources/pocketmine.yml @@ -7,6 +7,7 @@ settings: plugin-profiling: false query-plugins: true deprecated-verbose: true + enable-profiling: false advanced-cache: false upnp-forwarding: false send-usage: true From 0bfa9506d1414a57a339838e185f96af534412a6 Mon Sep 17 00:00:00 2001 From: Shoghi Cervantes Date: Sat, 12 Jul 2014 02:26:41 +0200 Subject: [PATCH 2/5] Updated API version to 1.1.0 (backwards-compatible) --- src/pocketmine/PocketMine.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pocketmine/PocketMine.php b/src/pocketmine/PocketMine.php index b860b519e..6c6639ac3 100644 --- a/src/pocketmine/PocketMine.php +++ b/src/pocketmine/PocketMine.php @@ -74,7 +74,7 @@ namespace pocketmine { use raklib\RakLib; const VERSION = "Alpha_1.4dev"; - const API_VERSION = "1.0.0"; + const API_VERSION = "1.1.0"; const CODENAME = "絶好(Zekkou)ケーキ(Cake)"; const MINECRAFT_VERSION = "v0.9.0 alpha"; const PHP_VERSION = "5.5"; From 9bdd294a66c0c5d4166ca3363ad376cc1b5ef2d3 Mon Sep 17 00:00:00 2001 From: Shoghi Cervantes Date: Sat, 12 Jul 2014 14:09:29 +0200 Subject: [PATCH 3/5] Implemented global, tasks timings --- src/pocketmine/Player.php | 3 + src/pocketmine/Server.php | 22 ++- src/pocketmine/event/LevelTimings.php | 103 +++++++++++ src/pocketmine/event/Timings.php | 182 +++++++++++++++++++ src/pocketmine/level/Level.php | 15 +- src/pocketmine/resources/pocketmine.yml | 1 - src/pocketmine/scheduler/ServerScheduler.php | 5 +- src/pocketmine/scheduler/TaskHandler.php | 31 +++- 8 files changed, 348 insertions(+), 14 deletions(-) create mode 100644 src/pocketmine/event/LevelTimings.php create mode 100644 src/pocketmine/event/Timings.php diff --git a/src/pocketmine/Player.php b/src/pocketmine/Player.php index 3442075b4..6f2bcf0bf 100644 --- a/src/pocketmine/Player.php +++ b/src/pocketmine/Player.php @@ -47,6 +47,7 @@ use pocketmine\event\player\PlayerQuitEvent; use pocketmine\event\player\PlayerRespawnEvent; use pocketmine\event\server\DataPacketReceiveEvent; use pocketmine\event\server\DataPacketSendEvent; +use pocketmine\event\Timings; use pocketmine\inventory\BaseTransaction; use pocketmine\inventory\BigShapelessRecipe; use pocketmine\inventory\CraftingTransactionGroup; @@ -1845,7 +1846,9 @@ class Player extends Human implements CommandSender, InventoryHolder, IPlayer{ break; } if(substr($ev->getMessage(), 0, 1) === "/"){ //Command + Timings::$playerCommandTimer->startTiming(); $this->server->dispatchCommand($ev->getPlayer(), substr($ev->getMessage(), 1)); + Timings::$playerCommandTimer->stopTiming(); }else{ $this->server->getPluginManager()->callEvent($ev = new PlayerChatEvent($this, $ev->getMessage())); if(!$ev->isCancelled()){ diff --git a/src/pocketmine/Server.php b/src/pocketmine/Server.php index d3f207202..98059cff7 100644 --- a/src/pocketmine/Server.php +++ b/src/pocketmine/Server.php @@ -36,6 +36,7 @@ use pocketmine\event\HandlerList; use pocketmine\event\level\LevelInitEvent; use pocketmine\event\level\LevelLoadEvent; use pocketmine\event\server\ServerCommandEvent; +use pocketmine\event\Timings; use pocketmine\event\TimingsHandler; use pocketmine\inventory\CraftingManager; use pocketmine\inventory\InventoryType; @@ -1476,6 +1477,7 @@ class Server{ $this->craftingManager = new CraftingManager(); PluginManager::$pluginParentTimer = new TimingsHandler("** Plugins"); + Timings::init(); $this->pluginManager = new PluginManager($this, $this->commandMap); $this->pluginManager->subscribeToPermission(Server::BROADCAST_CHANNEL_ADMINISTRATIVE, $this->consoleSender); $this->pluginManager->setUseTimings($this->getProperty("settings.enable-profiling", false)); @@ -1599,10 +1601,15 @@ class Server{ } public function checkConsole(){ + Timings::$serverCommandTimer->startTiming(); if(($line = $this->console->getLine()) !== null){ $this->pluginManager->callEvent($ev = new ServerCommandEvent($this->consoleSender, $line)); + if($ev->isCancelled()){ + return; + } $this->dispatchCommand($this->consoleSender, $ev->getCommand()); } + Timings::$serverCommandTimer->stopTiming(); } /** @@ -1623,7 +1630,6 @@ class Server{ }else{ $sender->sendMessage("Unknown command. Type \"help\" for help."); } - return false; } @@ -1935,13 +1941,15 @@ class Server{ public function doAutoSave(){ - /*foreach($this->getOnlinePlayers() as $player){ + Timings::$worldSaveTimer->startTiming(); + foreach($this->getOnlinePlayers() as $player){ $player->save(); - }*/ + } foreach($this->getLevels() as $level){ - $level->save(); + $level->save(false); } + Timings::$worldSaveTimer->stopTiming(); } public function doLevelGC(){ @@ -1999,11 +2007,15 @@ class Server{ return false; } + Timings::$serverTickTimer->startTiming(); + $this->inTick = true; //Fix race conditions ++$this->tickCounter; $this->checkConsole(); + Timings::$schedulerTimer->startTiming(); $this->scheduler->mainThreadHeartbeat($this->tickCounter); + Timings::$schedulerTimer->stopTiming(); $this->checkTickUpdates($this->tickCounter); if(($this->tickCounter & 0b1111) === 0){ @@ -2020,6 +2032,8 @@ class Server{ $this->nextTick = 0.05 * (0.05 / max(0.05, $this->tickMeasure)) + $time; $this->inTick = false; + Timings::$serverTickTimer->stopTiming(); + return true; } diff --git a/src/pocketmine/event/LevelTimings.php b/src/pocketmine/event/LevelTimings.php new file mode 100644 index 000000000..e4787caf7 --- /dev/null +++ b/src/pocketmine/event/LevelTimings.php @@ -0,0 +1,103 @@ +getFolderName() . " - "; + + $this->mobSpawn = new TimingsHandler("** ". $name ."mobSpawn"); + $this->doChunkUnload = new TimingsHandler("** ". $name ."doChunkUnload"); + $this->doTickPending = new TimingsHandler("** ". $name ."doTickPending"); + $this->doTickTiles = new TimingsHandler("** ". $name ."doTickTiles"); + $this->doVillages = new TimingsHandler("** ". $name ."doVillages"); + $this->doChunkMap = new TimingsHandler("** ". $name ."doChunkMap"); + $this->doSounds = new TimingsHandler("** ". $name ."doSounds"); + $this->doChunkGC = new TimingsHandler("** ". $name ."doChunkGC"); + $this->doPortalForcer = new TimingsHandler("** ". $name ."doPortalForcer"); + $this->entityTick = new TimingsHandler("** ". $name ."entityTick"); + $this->tileEntityTick = new TimingsHandler("** ". $name ."tileEntityTick"); + $this->tileEntityPending = new TimingsHandler("** ". $name ."tileEntityPending"); + + $this->syncChunkLoadTimer = new TimingsHandler("** ". $name ."syncChunkLoad"); + $this->syncChunkLoadDataTimer = new TimingsHandler("** ". $name ."syncChunkLoad - Data"); + $this->syncChunkLoadStructuresTimer = new TimingsHandler("** ". $name ."syncChunkLoad - Structures"); + $this->syncChunkLoadEntitiesTimer = new TimingsHandler("** ". $name ."syncChunkLoad - Entities"); + $this->syncChunkLoadTileEntitiesTimer = new TimingsHandler("** ". $name ."syncChunkLoad - TileEntities"); + $this->syncChunkLoadTileTicksTimer = new TimingsHandler("** ". $name ."syncChunkLoad - TileTicks"); + $this->syncChunkLoadPostTimer = new TimingsHandler("** ". $name ."syncChunkLoad - Post"); + + $this->tracker = new TimingsHandler($name ."tracker"); + $this->doTick = new TimingsHandler($name ."doTick"); + $this->tickEntities = new TimingsHandler($name ."tickEntities"); + } + +} \ No newline at end of file diff --git a/src/pocketmine/event/Timings.php b/src/pocketmine/event/Timings.php new file mode 100644 index 000000000..51262411a --- /dev/null +++ b/src/pocketmine/event/Timings.php @@ -0,0 +1,182 @@ +getTask(); + if($ftask instanceof PluginTask and $ftask->getOwner() !== null){ + $plugin = $ftask->getOwner()->getDescription()->getName(); + }elseif($task->timingName !== null){ + $plugin = "Scheduler"; + }else{ + $plugin = "Unknown"; + } + + $taskname = $task->getTaskName(); + + $name = "Task: ". $plugin." Runnable: ". $taskname; + + if($period > 0){ + $name .= "(interval:".$period.")"; + }else{ + $name .= "(Single)"; + } + + if(!isset(self::$pluginTaskTimingMap[$name])){ + self::$pluginTaskTimingMap[$name] = new TimingsHandler($name, self::$schedulerSyncTimer); + } + + return self::$pluginTaskTimingMap[$name]; + } + + /** + * @param Entity $entity + * + * @return TimingsHandler + */ + public static function getEntityTimings(Entity $entity){ + $entityType = (new \ReflectionClass($entity))->getShortName(); + if(!isset(self::$entityTypeTimingMap[$entityType])){ + self::$entityTypeTimingMap[$entityType] = new TimingsHandler("** tickEntity - ". $entityType, self::$activatedEntityTimer); + } + + return self::$entityTypeTimingMap[$entityType]; + } + + /** + * @param Tile $tile + * + * @return TimingsHandler + */ + public static function getTileEntityTimings(Tile $tile){ + $tileType = (new \ReflectionClass($tile))->getShortName(); + if(!isset(self::$tileEntityTypeTimingMap[$tileType])){ + self::$tileEntityTypeTimingMap[$tileType] = new TimingsHandler("** tickTileEntity - ". $tileType, self::$tickTileEntityTimer); + } + + return self::$tileEntityTypeTimingMap[$tileType]; + } + +} \ No newline at end of file diff --git a/src/pocketmine/level/Level.php b/src/pocketmine/level/Level.php index 5253ecd95..6722b2ed3 100644 --- a/src/pocketmine/level/Level.php +++ b/src/pocketmine/level/Level.php @@ -33,6 +33,7 @@ use pocketmine\event\block\BlockPlaceEvent; use pocketmine\event\level\LevelSaveEvent; use pocketmine\event\level\LevelUnloadEvent; use pocketmine\event\level\SpawnChangeEvent; +use pocketmine\event\LevelTimings; use pocketmine\event\player\PlayerInteractEvent; use pocketmine\item\Item; use pocketmine\level\format\Chunk; @@ -157,6 +158,9 @@ class Level implements ChunkManager, Metadatable{ Block::BEETROOT_BLOCK, ]; + /** @var LevelTimings */ + public $timings; + /** * Returns the chunk unique hash/key * @@ -208,6 +212,7 @@ class Level implements ChunkManager, Metadatable{ $this->chunksPerTick = (int) $this->server->getProperty("chunk-ticking.per-tick", 128); $this->chunkTickList = []; $this->clearChunksOnTick = (bool) $this->server->getProperty("chunk-ticking.clear-tick-list", true); + $this->timings = new LevelTimings($this); } /** @@ -386,6 +391,8 @@ class Level implements ChunkManager, Metadatable{ */ public function doTick($currentTick){ + $this->timings->doTick->startTiming(); + if(($currentTick % 200) === 0){ $this->checkTime(); } @@ -450,9 +457,7 @@ class Level implements ChunkManager, Metadatable{ $this->processChunkRequest(); - if($this->nextSave < microtime(true)){ - $this->save(false); - } + $this->timings->doTick->stopTiming(); } private function tickChunks(){ @@ -1677,6 +1682,8 @@ class Level implements ChunkManager, Metadatable{ } public function doChunkGarbageCollection(){ + $this->timings->doChunkGC->startTiming(); + $X = null; $Z = null; @@ -1703,6 +1710,8 @@ class Level implements ChunkManager, Metadatable{ } } + + $this->timings->doChunkGC->stopTiming(); } diff --git a/src/pocketmine/resources/pocketmine.yml b/src/pocketmine/resources/pocketmine.yml index 77957cea1..94c07ee80 100644 --- a/src/pocketmine/resources/pocketmine.yml +++ b/src/pocketmine/resources/pocketmine.yml @@ -4,7 +4,6 @@ settings: shutdown-message: "Server closed" - plugin-profiling: false query-plugins: true deprecated-verbose: true enable-profiling: false diff --git a/src/pocketmine/scheduler/ServerScheduler.php b/src/pocketmine/scheduler/ServerScheduler.php index 8e77034a5..ccf33361f 100644 --- a/src/pocketmine/scheduler/ServerScheduler.php +++ b/src/pocketmine/scheduler/ServerScheduler.php @@ -178,7 +178,7 @@ class ServerScheduler{ $period = 1; } - return $this->handle(new TaskHandler($task, $this->nextId(), $delay, $period)); + return $this->handle(new TaskHandler(!($task instanceof PluginTask) ? "Scheduler" : null, $task, $this->nextId(), $delay, $period)); } private function handle(TaskHandler $handler){ @@ -201,12 +201,15 @@ class ServerScheduler{ public function mainThreadHeartbeat($currentTick){ $this->currentTick = $currentTick; while($this->isReady($this->currentTick)){ + /** @var TaskHandler $task */ $task = $this->queue->extract(); if($task->isCancelled()){ unset($this->tasks[$task->getTaskId()]); continue; }else{ + $task->timings->startTiming(); $task->run($this->currentTick); + $task->timings->stopTiming(); } if($task->isRepeating()){ $task->setNextRun($this->currentTick + $task->getPeriod()); diff --git a/src/pocketmine/scheduler/TaskHandler.php b/src/pocketmine/scheduler/TaskHandler.php index 4cd09563b..e34ca8bb8 100644 --- a/src/pocketmine/scheduler/TaskHandler.php +++ b/src/pocketmine/scheduler/TaskHandler.php @@ -21,6 +21,8 @@ namespace pocketmine\scheduler; +use pocketmine\event\Timings; + class TaskHandler{ /** @var Task */ @@ -41,17 +43,25 @@ class TaskHandler{ /** @var bool */ protected $cancelled = false; + /** @var \pocketmine\event\TimingsHandler */ + public $timings; + + public $timingName = null; + /** - * @param Task $task - * @param int $taskId - * @param int $delay - * @param int $period + * @param string $timingName + * @param Task $task + * @param int $taskId + * @param int $delay + * @param int $period */ - public function __construct(Task $task, $taskId, $delay = -1, $period = -1){ + public function __construct($timingName, Task $task, $taskId, $delay = -1, $period = -1){ $this->task = $task; $this->taskId = $taskId; $this->delay = $delay; $this->period = $period; + $this->timingName = $timingName === null ? "Unknown" : $timingName; + $this->timings = Timings::getPluginTaskTimings($this, $period); } /** @@ -127,6 +137,7 @@ class TaskHandler{ public function remove(){ $this->cancelled = true; $this->task->setHandler(null); + $this->timings->remove(); } /** @@ -135,4 +146,14 @@ class TaskHandler{ public function run($currentTick){ $this->task->onRun($currentTick); } + + /** + * @return string + */ + public function getTaskName(){ + if($this->timingName !== null){ + return $this->timingName; + } + return get_class($this->task); + } } \ No newline at end of file From 9a4ead54e3c5e857053df30158ef18b9415cc0f6 Mon Sep 17 00:00:00 2001 From: Shoghi Cervantes Date: Sat, 12 Jul 2014 15:23:06 +0200 Subject: [PATCH 4/5] Added more timings --- src/pocketmine/Server.php | 38 ++++++------------- src/pocketmine/entity/Entity.php | 4 ++ src/pocketmine/entity/Living.php | 7 ++++ src/pocketmine/level/Level.php | 11 +++++- src/pocketmine/level/format/anvil/Anvil.php | 3 ++ .../level/format/anvil/RegionLoader.php | 3 +- .../level/format/generic/BaseChunk.php | 5 ++- src/pocketmine/tile/Tile.php | 5 +++ src/raklib | 2 +- 9 files changed, 48 insertions(+), 30 deletions(-) diff --git a/src/pocketmine/Server.php b/src/pocketmine/Server.php index 98059cff7..158e5cc8f 100644 --- a/src/pocketmine/Server.php +++ b/src/pocketmine/Server.php @@ -1753,35 +1753,10 @@ class Server{ $this->logger->info("Done (" . round(microtime(true) - \pocketmine\START_TIME, 3) . 's)! For help, type "help" or "?"'); - //if(Utils::getOS() === "win"){ //Workaround less usleep() waste - // $this->tickProcessorWindows(); - //}else{ - $this->tickProcessor(); - //} + $this->tickProcessor(); $this->forceShutdown(); } - /*private function tickProcessorWindows(){ - $lastLoop = 0; - while($this->isRunning){ - foreach($this->interfaces as $interface){ - if($interface->process()){ - $lastLoop = 0; - } - } - $this->generationManager->handlePackets(); - - if(($ticks = $this->tick()) !== true){ - ++$lastLoop; - if($lastLoop > 8){ - usleep(1000); - } - }else{ - $lastLoop = 0; - } - } - }*/ - public function checkTicks(){ if($this->getTicksPerSecond() < 12){ $this->logger->warning("Can't keep up! Is the server overloaded?"); @@ -1885,13 +1860,17 @@ class Server{ private function tickProcessor(){ $lastLoop = 0; + $connectionTimer = Timings::$connectionTimer; while($this->isRunning){ + $connectionTimer->startTiming(); foreach($this->interfaces as $interface){ if($interface->process()){ $lastLoop = 0; } } + $connectionTimer->stopTiming(); + $this->generationManager->handlePackets(); ++$lastLoop; @@ -1913,22 +1892,29 @@ class Server{ } private function checkTickUpdates($currentTick){ + + //TODO: move this to each Level + //Update entities that need update if(count(Entity::$needUpdate) > 0){ + Timings::$tickEntityTimer->startTiming(); foreach(Entity::$needUpdate as $id => $entity){ if($entity->onUpdate() === false){ unset(Entity::$needUpdate[$id]); } } + Timings::$tickEntityTimer->stopTiming(); } //Update tiles that need update if(count(Tile::$needUpdate) > 0){ + Timings::$tickTileEntityTimer->startTiming(); foreach(Tile::$needUpdate as $id => $tile){ if($tile->onUpdate() === false){ unset(Tile::$needUpdate[$id]); } } + Timings::$tickTileEntityTimer->stopTiming(); } //TODO: Add level blocks diff --git a/src/pocketmine/entity/Entity.php b/src/pocketmine/entity/Entity.php index 0eef27859..e988cc426 100644 --- a/src/pocketmine/entity/Entity.php +++ b/src/pocketmine/entity/Entity.php @@ -33,6 +33,7 @@ use pocketmine\event\entity\EntityMotionEvent; use pocketmine\event\entity\EntityMoveEvent; use pocketmine\event\entity\EntitySpawnEvent; use pocketmine\event\entity\EntityTeleportEvent; +use pocketmine\event\Timings; use pocketmine\level\format\Chunk; use pocketmine\level\Level; use pocketmine\level\Position; @@ -669,6 +670,8 @@ abstract class Entity extends Position implements Metadatable{ return; } + Timings::$entityMoveTimer->startTiming(); + $ox = $this->x; $oy = $this->y; $oz = $this->z; @@ -832,6 +835,7 @@ abstract class Entity extends Position implements Metadatable{ //TODO: vehicle collision events (first we need to spawn them!) + Timings::$entityMoveTimer->stopTiming(); } diff --git a/src/pocketmine/entity/Living.php b/src/pocketmine/entity/Living.php index 27bba8bcc..e50680569 100644 --- a/src/pocketmine/entity/Living.php +++ b/src/pocketmine/entity/Living.php @@ -26,6 +26,7 @@ use pocketmine\event\entity\EntityDamageByEntityEvent; use pocketmine\event\entity\EntityDamageEvent; use pocketmine\event\entity\EntityDeathEvent; use pocketmine\event\entity\EntityRegainHealthEvent; +use pocketmine\event\Timings; use pocketmine\math\Vector3; use pocketmine\nbt\tag\Short; use pocketmine\network\protocol\EntityEventPacket; @@ -97,6 +98,12 @@ abstract class Living extends Entity implements Damageable{ } } + public function entityBaseTick(){ + Timings::$timerEntityBaseTick->startTiming(); + parent::entityBaseTick(); + Timings::$timerEntityBaseTick->stopTiming(); + } + /** * @return Item[] */ diff --git a/src/pocketmine/level/Level.php b/src/pocketmine/level/Level.php index 6722b2ed3..383aed897 100644 --- a/src/pocketmine/level/Level.php +++ b/src/pocketmine/level/Level.php @@ -447,13 +447,17 @@ class Level implements ChunkManager, Metadatable{ $X = null; $Z = null; - //Do chunk updates + //Do block updates + $this->timings->doTickPending->startTiming(); while($this->updateQueue->count() > 0 and $this->updateQueue->current()["priority"] <= $currentTick){ $block = $this->getBlock($this->updateQueue->extract()["data"]); $block->onUpdate(self::BLOCK_UPDATE_SCHEDULED); } + $this->timings->doTickPending->stopTiming(); + $this->timings->doTickTiles->startTiming(); $this->tickChunks(); + $this->timings->doTickTiles->stopTiming(); $this->processChunkRequest(); @@ -1486,7 +1490,9 @@ class Level implements ChunkManager, Metadatable{ if($chunk instanceof Chunk){ return true; }else{ + $this->timings->syncChunkLoadTimer->startTiming(); $this->provider->loadChunk($x, $z); + $this->timings->syncChunkLoadTimer->stopTiming(); return $this->provider->getChunk($x, $z) instanceof Chunk; } @@ -1514,10 +1520,13 @@ class Level implements ChunkManager, Metadatable{ if($safe === true and $this->isChunkInUse($x, $z)){ return false; } + $this->timings->doChunkUnload->startTiming(); $this->provider->unloadChunk($x, $z, $safe); Cache::remove("world:" . $this->getID() . ":$x:$z"); + $this->timings->doChunkUnload->stopTiming(); + return true; } diff --git a/src/pocketmine/level/format/anvil/Anvil.php b/src/pocketmine/level/format/anvil/Anvil.php index bdf9e0eda..4f0bbd131 100644 --- a/src/pocketmine/level/format/anvil/Anvil.php +++ b/src/pocketmine/level/format/anvil/Anvil.php @@ -117,7 +117,10 @@ class Anvil extends BaseLevelProvider{ $regionX = $regionZ = null; self::getRegionIndex($chunkX, $chunkZ, $regionX, $regionZ); $this->loadRegion($regionX, $regionZ); + + $this->level->timings->syncChunkLoadDataTimer->startTiming(); $chunk = $this->getRegion($regionX, $regionZ)->readChunk($chunkX - $regionX * 32, $chunkZ - $regionZ * 32, $create); //generate empty chunk if not loaded + $this->level->timings->syncChunkLoadDataTimer->stopTiming(); if($chunk instanceof Chunk){ $this->chunks[$index] = $chunk; diff --git a/src/pocketmine/level/format/anvil/RegionLoader.php b/src/pocketmine/level/format/anvil/RegionLoader.php index f9ddb3cc8..20063ee89 100644 --- a/src/pocketmine/level/format/anvil/RegionLoader.php +++ b/src/pocketmine/level/format/anvil/RegionLoader.php @@ -116,13 +116,14 @@ class RegionLoader{ $this->writeLocationIndex($index); }elseif($compression !== self::COMPRESSION_ZLIB and $compression !== self::COMPRESSION_GZIP){ trigger_error("Invalid compression type", E_USER_WARNING); - return false; } $nbt = new NBT(NBT::BIG_ENDIAN); $nbt->readCompressed(fread($this->filePointer, $length - 1), $compression); $chunk = $nbt->getData(); + + if(!isset($chunk->Level) or !($chunk->Level instanceof Compound)){ return false; } diff --git a/src/pocketmine/level/format/generic/BaseChunk.php b/src/pocketmine/level/format/generic/BaseChunk.php index 9e6d8109c..ef1394465 100644 --- a/src/pocketmine/level/format/generic/BaseChunk.php +++ b/src/pocketmine/level/format/generic/BaseChunk.php @@ -98,6 +98,7 @@ abstract class BaseChunk implements Chunk{ $this->biomeColors = array_fill(0, 256, Binary::readInt("\x00\x85\xb2\x4a")); } + $this->getLevel()->getLevel()->timings->syncChunkLoadEntitiesTimer->startTiming(); foreach($entities as $nbt){ if($nbt instanceof Compound){ if(!isset($nbt->id)){ @@ -115,8 +116,9 @@ abstract class BaseChunk implements Chunk{ } } } + $this->getLevel()->getLevel()->timings->syncChunkLoadEntitiesTimer->stopTiming(); - + $this->getLevel()->getLevel()->timings->syncChunkLoadTileEntitiesTimer->startTiming(); foreach($tiles as $nbt){ if($nbt instanceof Compound){ if(!isset($nbt->id)){ @@ -135,6 +137,7 @@ abstract class BaseChunk implements Chunk{ } } } + $this->getLevel()->getLevel()->timings->syncChunkLoadTileEntitiesTimer->stopTiming(); } public function getX(){ diff --git a/src/pocketmine/tile/Tile.php b/src/pocketmine/tile/Tile.php index a20fe7f21..593dce1a2 100644 --- a/src/pocketmine/tile/Tile.php +++ b/src/pocketmine/tile/Tile.php @@ -25,6 +25,7 @@ */ namespace pocketmine\tile; +use pocketmine\event\Timings; use pocketmine\level\format\Chunk; use pocketmine\level\Position; use pocketmine\nbt\tag\Compound; @@ -57,6 +58,9 @@ abstract class Tile extends Position{ protected $lastUpdate; protected $server; + /** @var \pocketmine\event\TimingsHandler */ + public $tickTimer; + public function __construct(Chunk $chunk, Compound $nbt){ $this->server = $chunk->getLevel()->getLevel()->getServer(); $this->chunk = $chunk; @@ -72,6 +76,7 @@ abstract class Tile extends Position{ $this->chunk->addTile($this); $this->getLevel()->addTile($this); + $this->tickTimer = Timings::getTileEntityTimings($this); } public function getID(){ diff --git a/src/raklib b/src/raklib index 577c371aa..df53c0844 160000 --- a/src/raklib +++ b/src/raklib @@ -1 +1 @@ -Subproject commit 577c371aa07bd2aa3cedc3b7d4bcdc1d54999c70 +Subproject commit df53c0844231a2ddc340dcd13a77944ca8822d27 From a7944502cdf7f9f1645e645741c3543168f40393 Mon Sep 17 00:00:00 2001 From: Shoghi Cervantes Date: Sat, 12 Jul 2014 19:27:51 +0200 Subject: [PATCH 5/5] Added small things --- src/pocketmine/PocketMine.php | 2 +- src/pocketmine/Server.php | 5 +++- src/pocketmine/level/Level.php | 11 +++++-- src/pocketmine/level/format/anvil/Anvil.php | 14 +++++++-- .../scheduler/PHPGarbageCollectionTask.php | 30 +++++++++++++++++++ src/pocketmine/scheduler/ServerScheduler.php | 2 +- 6 files changed, 56 insertions(+), 8 deletions(-) create mode 100644 src/pocketmine/scheduler/PHPGarbageCollectionTask.php diff --git a/src/pocketmine/PocketMine.php b/src/pocketmine/PocketMine.php index 6c6639ac3..7e44e9699 100644 --- a/src/pocketmine/PocketMine.php +++ b/src/pocketmine/PocketMine.php @@ -135,7 +135,7 @@ namespace pocketmine { } } - gc_enable(); + gc_disable(); error_reporting(E_ALL | E_STRICT); ini_set("allow_url_fopen", 1); ini_set("display_errors", 1); diff --git a/src/pocketmine/Server.php b/src/pocketmine/Server.php index 158e5cc8f..3857bb578 100644 --- a/src/pocketmine/Server.php +++ b/src/pocketmine/Server.php @@ -73,6 +73,7 @@ use pocketmine\plugin\Plugin; use pocketmine\plugin\PluginLoadOrder; use pocketmine\plugin\PluginManager; use pocketmine\scheduler\CallbackTask; +use pocketmine\scheduler\PHPGarbageCollectionTask; use pocketmine\scheduler\SendUsageTask; use pocketmine\scheduler\ServerScheduler; use pocketmine\tile\Tile; @@ -1521,9 +1522,11 @@ class Server{ } if($this->getProperty("chunk-gc.period-in-ticks", 600) > 0){ - $this->scheduler->scheduleDelayedRepeatingTask(new CallbackTask(array($this, "doLevelGC")), $this->getProperty("chunk-gc.period-in-ticks", 600), $this->getProperty("chunk-gc.period-in-ticks", 600)); + $this->scheduler->scheduleDelayedRepeatingTask(new CallbackTask([$this, "doLevelGC"]), $this->getProperty("chunk-gc.period-in-ticks", 600), $this->getProperty("chunk-gc.period-in-ticks", 600)); } + $this->scheduler->scheduleRepeatingTask(new PHPGarbageCollectionTask(), 100); + $this->enablePlugins(PluginLoadOrder::POSTWORLD); } diff --git a/src/pocketmine/level/Level.php b/src/pocketmine/level/Level.php index 383aed897..a2fa2d341 100644 --- a/src/pocketmine/level/Level.php +++ b/src/pocketmine/level/Level.php @@ -341,7 +341,7 @@ class Level implements ChunkManager, Metadatable{ */ public function freeAllChunks(Player $player){ foreach($this->usedChunks as $i => $c){ - unset($this->usedChunks[$i][spl_object_hash($player)]); + unset($this->usedChunks[$i][$player->getID()]); } } @@ -354,7 +354,7 @@ class Level implements ChunkManager, Metadatable{ * @param Player $player */ public function freeChunk($X, $Z, Player $player){ - unset($this->usedChunks[Level::chunkHash($X, $Z)][$player->getID()]); + unset($this->usedChunks[$index = Level::chunkHash($X, $Z)][$player->getID()]); $this->unloadChunkRequest($X, $Z, true); } @@ -1523,6 +1523,7 @@ class Level implements ChunkManager, Metadatable{ $this->timings->doChunkUnload->startTiming(); $this->provider->unloadChunk($x, $z, $safe); + unset($this->usedChunks[Level::chunkHash($x, $z)]); Cache::remove("world:" . $this->getID() . ":$x:$z"); $this->timings->doChunkUnload->stopTiming(); @@ -1682,7 +1683,7 @@ class Level implements ChunkManager, Metadatable{ } public function regenerateChunk($x, $z){ - $this->unloadChunk($x, $z); + $this->unloadChunk($x, $z, false); $this->cancelUnloadChunkRequest($x, $z); @@ -1714,6 +1715,10 @@ class Level implements ChunkManager, Metadatable{ if(count($c) === 0){ Level::getXZ($i, $X, $Z); if(!$this->isSpawnChunk($X, $Z)){ + if($this->getAutoSave()){ + $this->provider->saveChunk($X, $Z); + } + $this->unloadChunk($X, $Z, true); } } diff --git a/src/pocketmine/level/format/anvil/Anvil.php b/src/pocketmine/level/format/anvil/Anvil.php index 4f0bbd131..f866c6c96 100644 --- a/src/pocketmine/level/format/anvil/Anvil.php +++ b/src/pocketmine/level/format/anvil/Anvil.php @@ -130,8 +130,8 @@ class Anvil extends BaseLevelProvider{ } public function unloadChunk($x, $z, $safe = true){ + $chunk = $this->getChunk($x, $z, false); if($safe === true and $this->isChunkLoaded($x, $z)){ - $chunk = $this->getChunk($x, $z); foreach($chunk->getEntities() as $entity){ if($entity instanceof Player){ return false; @@ -139,7 +139,17 @@ class Anvil extends BaseLevelProvider{ } } - unset($this->chunks[Level::chunkHash($x, $z)]); + foreach($chunk->getEntities() as $entity){ + $entity->close(); + } + + foreach($chunk->getTiles() as $tile){ + $tile->close(); + } + + $this->chunks[$index = Level::chunkHash($x, $z)] = null; + + unset($this->chunks[$index]); return true; } diff --git a/src/pocketmine/scheduler/PHPGarbageCollectionTask.php b/src/pocketmine/scheduler/PHPGarbageCollectionTask.php new file mode 100644 index 000000000..3adfabffe --- /dev/null +++ b/src/pocketmine/scheduler/PHPGarbageCollectionTask.php @@ -0,0 +1,30 @@ +handle(new TaskHandler(!($task instanceof PluginTask) ? "Scheduler" : null, $task, $this->nextId(), $delay, $period)); + return $this->handle(new TaskHandler(get_class($task), $task, $this->nextId(), $delay, $period)); } private function handle(TaskHandler $handler){