From e6485c47341238a2121bfa0f3e70557f4e8fdb3d Mon Sep 17 00:00:00 2001 From: SOFe Date: Sat, 12 Nov 2016 17:31:59 +0800 Subject: [PATCH] Added AsyncTask progress update API --- src/pocketmine/scheduler/AsyncPool.php | 7 ++- src/pocketmine/scheduler/AsyncTask.php | 57 +++++++++++++++++++- src/pocketmine/scheduler/ServerScheduler.php | 36 ++++++++++--- 3 files changed, 91 insertions(+), 9 deletions(-) diff --git a/src/pocketmine/scheduler/AsyncPool.php b/src/pocketmine/scheduler/AsyncPool.php index 6c81d8c15..b8069501c 100644 --- a/src/pocketmine/scheduler/AsyncPool.php +++ b/src/pocketmine/scheduler/AsyncPool.php @@ -142,8 +142,13 @@ class AsyncPool{ Timings::$schedulerAsyncTimer->startTiming(); foreach($this->tasks as $task){ + if($task->progressUpdates !== null){ + if($task->progressUpdates->count() !== 0){ + $progress = $task->progressUpdates->shift(); + $task->onProgressUpdate($this->server, $progress); + } + } if($task->isGarbage() and !$task->isRunning() and !$task->isCrashed()){ - if(!$task->hasCancelledRun()){ $task->onCompletion($this->server); $this->server->getScheduler()->removeLocalComplex($task); diff --git a/src/pocketmine/scheduler/AsyncTask.php b/src/pocketmine/scheduler/AsyncTask.php index 52d1defc2..295b881b1 100644 --- a/src/pocketmine/scheduler/AsyncTask.php +++ b/src/pocketmine/scheduler/AsyncTask.php @@ -34,6 +34,9 @@ abstract class AsyncTask extends Collectable{ /** @var AsyncWorker $worker */ public $worker = null; + /** @var \Threaded */ + public $progressUpdates = null; + private $result = null; private $serialized = false; private $cancelRun = false; @@ -66,6 +69,7 @@ abstract class AsyncTask extends Collectable{ } public function run(){ + $this->progressUpdates = new \Threaded; // Do not move this to __construct for backwards compatibility. $this->result = null; if($this->cancelRun !== true){ @@ -169,7 +173,36 @@ abstract class AsyncTask extends Collectable{ } /** - * Call this method from {@link #onCompletion} to fetch the data stored in the constructor, if any. + * Call this method from {@link AsyncTask#onRun} (AsyncTask execution therad) to schedule a call to + * {@link AsyncTask#onProgressUpdate} from the main thread with the given progress parameter. + * + * @param \Threaded|mixed $progress A Threaded object, or a value that can be safely serialize()'ed. + */ + public function publishProgress($progress){ + $this->progressUpdates[] = $progress; + } + + /** + * Called from the main thread after {@link AsyncTask#publishProgress} is called. + * All {@link AsyncTask#publishProgress} calls should result in {@link AsyncTask#onProgressUpdate} calls before + * {@link AsyncTask#onCompletion} is called. + * + * @param Server $server + * @param \Threaded|mixed $progress The parameter passed to {@link AsyncTask#publishProgress}. If it is not a + * Threaded object, it would be serialize()'ed and later unserialize()'ed, as if it + * has been cloned. + */ + public function onProgressUpdate(Server $server, $progress){ + + } + + /** + * Call this method from {@link AsyncTask#onCompletion} to fetch the data stored in the constructor, if any, and + * clears it from the storage. + * + * Do not call this method from {@link AsyncTask#onProgressUpdate}, because this method deletes the data and cannot + * be used in the next {@link AsyncTask#onProgressUpdate} call or from {@link AsyncTask#onCompletion}. Use + * {@link AsyncTask#peekLocal} instead. * * @param Server $server default null * @@ -186,6 +219,28 @@ abstract class AsyncTask extends Collectable{ return $server->getScheduler()->fetchLocalComplex($this); } + /** + * Call this method from {@link AsyncTask#onProgressUpdate} to fetch the data stored in the constructor. + * + * Use {@link AsyncTask#peekLocal} instead from {@link AsyncTask#onCompletion}, because this method does not delete + * the data, and not clearing the data will result in a warning for memory leak after {@link AsyncTask#onCompletion} + * finished executing. + * + * @param Server|null $server default null + * + * @return mixed + * + * @throws \RuntimeException if no data were stored by this AsyncTask instance + */ + protected function peekLocal(Server $server = null){ + if($server === null){ + $server = Server::getInstance(); + assert($server !== null, "Call this method only from the main thread!"); + } + + return $server->getScheduler()->peekLocalComplex($this); + } + public function cleanObject(){ foreach($this as $p => $v){ if(!($v instanceof \Threaded)){ diff --git a/src/pocketmine/scheduler/ServerScheduler.php b/src/pocketmine/scheduler/ServerScheduler.php index 12beb4e51..b25453648 100644 --- a/src/pocketmine/scheduler/ServerScheduler.php +++ b/src/pocketmine/scheduler/ServerScheduler.php @@ -50,7 +50,7 @@ class ServerScheduler{ /** @var int */ protected $currentTick = 0; - /** @var \SplObjectStorage */ + /** @var \SplObjectStorage */ protected $objectStore; public function __construct(){ @@ -100,7 +100,7 @@ class ServerScheduler{ * * @internal Only call from AsyncTask.php * - * @param AsyncTask $for + * @param AsyncTask $for * @param object|array $cmplx * * @throws \RuntimeException if this method is called twice for the same instance of AsyncTask @@ -113,17 +113,39 @@ class ServerScheduler{ } /** - * Fetches data that must not be passed to other threads or be serialized, previously stored with {@link #storeLocalComplex} + * Fetches data that must not be passed to other threads or be serialized, previously stored with + * {@link ServerScheduler#storeLocalComplex}, without deletion of the data. * * @internal Only call from AsyncTask.php * * @param AsyncTask $for * - * @throws \RuntimException if no data associated with this AsyncTask can be found - */ + * @return object|array + * + * @throws \RuntimeException if no data associated with this AsyncTask can be found + */ + public function peekLocalComplex(AsyncTask $for){ + if(!isset($this->objectStore[$for])){ + throw new \RuntimeException("No local complex stored for this AsyncTask"); + } + return $this->objectStore[$for]; + } + + /** + * Fetches data that must not be passed to other threads or be serialized, previously stored with + * {@link ServerScheduler#storeLocalComplex}, and delete the data from the storage. + * + * @internal Only call from AsyncTask.php + * + * @param AsyncTask $for + * + * @return object|array + * + * @throws \RuntimeException if no data associated with this AsyncTask can be found + */ public function fetchLocalComplex(AsyncTask $for){ if(!isset($this->objectStore[$for])){ - throw new \RuntimeException("Attempt to fetch undefined complex"); + throw new \RuntimeException("No local complex stored for this AsyncTask"); } $cmplx = $this->objectStore[$for]; unset($this->objectStore[$for]); @@ -138,7 +160,7 @@ class ServerScheduler{ * @param AsyncTask $for * * @return bool returns false if any data are removed from this call, true otherwise - */ + */ public function removeLocalComplex(AsyncTask $for) : bool{ if(isset($this->objectStore[$for])){ Server::getInstance()->getLogger()->notice("AsyncTask " . get_class($for) . " stored local complex data but did not remove them after completion");