Some updates:
[core.git] / framework / main / classes / handler / tasks / class_TaskHandler.php
1 <?php
2 // Own namespace
3 namespace Org\Mxchange\CoreFramework\Handler\Task;
4
5 // Import framework stuff
6 use Org\Mxchange\CoreFramework\Factory\ObjectFactory;
7 use Org\Mxchange\CoreFramework\Handler\BaseHandler;
8 use Org\Mxchange\CoreFramework\Lists\Listable;
9 use Org\Mxchange\CoreFramework\Registry\Registerable;
10 use Org\Mxchange\CoreFramework\Task\Taskable;
11 use Org\Mxchange\CoreFramework\Visitor\Visitable;
12
13 /**
14  * A Task handler
15  *
16  * @author              Roland Haeder <webmaster@shipsimu.org>
17  * @version             0.0.0
18 <<<<<<< HEAD:framework/main/classes/handler/tasks/class_TaskHandler.php
19  * @copyright   Copyright (c) 2007, 2008 Roland Haeder, 2009 - 2017 Core Developer Team
20 =======
21  * @copyright   Copyright (c) 2007, 2008 Roland Haeder, 2009 - 2016 Core Developer Team
22 >>>>>>> Some updates::inc/main/classes/handler/tasks/class_TaskHandler.php
23  * @license             GNU GPL 3.0 or any newer version
24  * @link                http://www.shipsimu.org
25  *
26  * This program is free software: you can redistribute it and/or modify
27  * it under the terms of the GNU General Public License as published by
28  * the Free Software Foundation, either version 3 of the License, or
29  * (at your option) any later version.
30  *
31  * This program is distributed in the hope that it will be useful,
32  * but WITHOUT ANY WARRANTY; without even the implied warranty of
33  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
34  * GNU General Public License for more details.
35  *
36  * You should have received a copy of the GNU General Public License
37  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
38  */
39 class TaskHandler extends BaseHandler implements Registerable, HandleableTask {
40         // Exception constants
41         const EXCEPTION_TASK_IS_INVALID = 0xb00;
42
43         /**
44          * Protected constructor
45          *
46          * @return      void
47          */
48         protected function __construct () {
49                 // Call parent constructor
50                 parent::__construct(__CLASS__);
51
52                 // Set handler name
53                 $this->setHandlerName('task');
54         }
55
56         /**
57          * Creates an instance of this class
58          *
59          * @return      $handlerInstance        An instance of a HandleableTask class
60          */
61         public static final function createTaskHandler () {
62                 // Get new instance
63                 $handlerInstance = new TaskHandler();
64
65                 // Output debug message
66                 self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('TASK-HANDLER[' . __METHOD__ . ':' . __LINE__ . ']: Initializing task handler.');
67
68                 // Init the task list
69                 $handlerInstance->setListInstance(ObjectFactory::createObjectByConfiguredName('task_list_class'));
70
71                 // Get default instance
72                 $handlerInstance->setIteratorInstance($handlerInstance->getListInstance()->getIterator());
73
74                 // Init visitor instance for faster loop
75                 $handlerInstance->setVisitorInstance(ObjectFactory::createObjectByConfiguredName('active_task_visitor_class'));
76
77                 // Register the first (and generic) idle-loop task
78                 $taskInstance = ObjectFactory::createObjectByConfiguredName('idle_task_class');
79                 $handlerInstance->registerTask('idle_loop', $taskInstance);
80
81                 // Output debug message
82                 self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('TASK-HANDLER[' . __METHOD__ . ':' . __LINE__ . ']: Task handler initialized.');
83
84                 // Return the prepared instance
85                 return $handlerInstance;
86         }
87
88         /**
89          * Tries to execute the given task. If as task should not be started (yet)
90          * or the interval time (see task_interval_delay) is not yet reached the
91          * task is quietly skipped.
92          *
93          * @return      void
94          * @throws      InvalidTaskException    If the current task is invalid
95          */
96         private function executeCurrentTask () {
97                 // Update no task by default
98                 $updateTask = false;
99
100                 // Is the current task valid?
101                 if (!$this->getListInstance()->getIterator()->valid()) {
102                         // Not valid!
103                         throw new InvalidTaskException($this, self::EXCEPTION_TASK_IS_INVALID);
104                 } // END - if
105
106                 // Get current task
107                 $currentTask = $this->getListInstance()->getIterator()->current();
108
109                 // Is the task not yet started?
110                 if ($currentTask['task_started'] === false) {
111                         // Determine difference between current time and registration
112                         $diff = ($this->getMilliTime() - $currentTask['task_registered']) * 1000;
113
114                         // Should we start now?
115                         if ($diff < $currentTask['task_startup_delay']) {
116                                 // Skip this silently
117                                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('TASK-HANDLER[' . __METHOD__ . ':' . __LINE__ . ']: Task ' . $currentTask['id'] . ' not started: diff=' . $diff . ',task_startup_delay=' . $currentTask['task_startup_delay']);
118                                 return;
119                         } // END - if
120
121                         // Launch the task and mark it as updated
122                         $currentTask['task_started'] = true;
123                         $updateTask = true;
124
125                         // Debug message
126                         self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('TASK-HANDLER[' . __METHOD__ . ':' . __LINE__ . ']: Task ' . $currentTask['id'] . ' started with startup_delay=' . $currentTask['task_startup_delay'] . 'ms');
127                 } // END - if
128
129                 // Get time difference from interval delay
130                 $diff = ($this->getMilliTime() - $currentTask['task_last_activity']) * 1000;
131
132                 // Debug message
133                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('TASK-HANDLER[' . __METHOD__ . ':' . __LINE__ . ']: Task ' . $currentTask['id'] . ' diff=' . $diff . ',task_interval_delay=' . $currentTask['task_interval_delay'] . ',task_max_runs=' . $currentTask['task_max_runs'] . ',task_total_runs=' . $currentTask['task_total_runs']);
134
135                 // Is the interval delay reached?
136                 if ((($diff < $currentTask['task_interval_delay']) && ($currentTask['task_max_runs'] == 0)) || (($currentTask['task_max_runs'] > 0) && ($currentTask['task_total_runs'] == $currentTask['task_max_runs']))) {
137                         // Should we update the task from startup?
138                         if ($updateTask === true) {
139                                 // Update the task before leaving
140                                 $this->updateTask($currentTask);
141                         } // END - if
142
143                         // Skip this silently
144                         return;
145                 } // END - if
146
147                 // Set last activity
148                 $currentTask['task_last_activity'] = $this->getMilliTime();
149
150                 // Count this run
151                 $currentTask['task_total_runs']++;
152
153                 // Update the task
154                 $this->updateTask($currentTask);
155
156                 // And visit/run it
157                 // @TODO Messurement can be added around this call
158                 $currentTask['task_instance']->accept($this->getVisitorInstance());
159         }
160
161         /**
162          * Updates given task by updating the underlaying list
163          *
164          * @param       $taskEntry      An array with a task
165          * @return      void
166          */
167         private function updateTask (array $taskEntry) {
168                 // Get the key from current iteration
169                 $key = $this->getListInstance()->getIterator()->key();
170
171                 // Get the hash from key
172                 $hash = $this->getListInstance()->getHash($key);
173
174                 // Update the entry
175                 $this->getListInstance()->updateCurrentEntryByHash($hash, $taskEntry);
176         }
177
178         /**
179          * Unregisters the given task
180          *
181          * @param       $taskData       Data of the task
182          * @return      void
183          */
184         private function unregisterTask (array $taskData) {
185                 // Debug output
186                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('TASK-HANDLER[' . __METHOD__ . ':' . __LINE__ . ']: Removing task ' . $taskData['id'] . ' from queue - CALLED!');
187
188                 // Remove the entry
189                 $this->getListInstance()->removeEntry('tasks', $taskData);
190
191                 // Debug output
192                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('TASK-HANDLER[' . __METHOD__ . ':' . __LINE__ . ']: Removing task ' . $taskData['id'] . ' from queue - EXIT!');
193          }
194
195         /**
196          * Searches a task by given instance
197          *
198          * @param       $taskInstanc    An instanceof a Taskable class
199          * @return      $taskName               Name of the task as used while registration
200          */
201         public function searchTask (Taskable $taskInstance) {
202                 // Default is an empty (not found) task name
203                 $taskName = '';
204
205                 // Get whole list
206                 $taskList = $this->getListInstance()->getArrayFromList('tasks');
207
208                 // Search all instances
209                 foreach ($taskList as $currentTask) {
210                         // Does it match given task instance?
211                         if ($currentTask['task_instance']->equals($taskInstance)) {
212                                 // Found it
213                                 $taskName = $currentTask['id'];
214
215                                 // Abort here
216                                 break;
217                         } // END - if
218                 } // END - foreach
219
220                 // Return found name
221                 return $taskName;
222         }
223
224         /**
225          * Registers a task with a task handler.
226          *
227          * @param       $taskName               A task name to register the task on
228          * @param       $taskInstance   The instance that should be registered as a task
229          * @return      void
230          */
231         public function registerTask ($taskName, Visitable $taskInstance) {
232                 // Get interval delay
233                 $intervalDelay = $this->getConfigInstance()->getConfigEntry('task_' . $taskName . '_interval_delay');
234                 $startupDelay  = $this->getConfigInstance()->getConfigEntry('task_' . $taskName . '_startup_delay');
235
236                 // If the task is 'idle_loop', a deplay of zero seconds is fine
237                 assert($intervalDelay >= 0);
238                 assert(($taskName === 'idle_loop') || (($taskName != 'idle_loop') && ($intervalDelay > 0)));
239                 assert(($taskName === 'idle_loop') || (($taskName != 'idle_loop') && ($startupDelay > 0)));
240
241                 // Create the entry
242                 $taskEntry = array(
243                         // Identifier for the generateHash() method
244                         'id'                  => $taskName,
245                         // Whether the task is started
246                         'task_started'        => false,
247                         // Whether the task is paused (not yet implemented)
248                         'task_paused'         => false,
249                         // Whether the task can be paused (not yet implemented)
250                         'task_pauseable'      => true,
251                         // Timestamp of registration
252                         'task_registered'     => $this->getMilliTime(),
253                         // Last activity timestamp
254                         'task_last_activity'  => 0,
255                         // Total runs of this task
256                         'task_total_runs'     => 0,
257                         // Task instance itself
258                         'task_instance'       => $taskInstance,
259                         // Startup delay in milliseconds
260                         'task_startup_delay'  => $startupDelay,
261                         // Interval time (delay) in milliseconds before this task is executed again
262                         'task_interval_delay' => $intervalDelay,
263                         // How often should this task run?
264                         'task_max_runs'       => $this->getConfigInstance()->getConfigEntry('task_' . $taskName . '_max_runs'),
265                 );
266
267                 // Add the entry
268                 $this->getListInstance()->addEntry('tasks', $taskEntry);
269
270                 // Debug message
271                 self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('TASK-HANDLER[' . __METHOD__ . ':' . __LINE__ . ']: Task registered: taskName=' . $taskName .
272                         ' (taskInstance=' . $taskInstance->__toString() . ')' .
273                         ', startupDelay=' . $taskEntry['task_startup_delay'] . 'ms' .
274                         ', intervalDelay=' . $taskEntry['task_interval_delay'] . 'ms' .
275                         ', maxRuns=' . $taskEntry['task_max_runs'] . ' ...'
276                 );
277         }
278
279         /**
280          * Checks whether tasks are left including idle task
281          *
282          * @return      $tasksLeft      Whether there are tasks left to handle
283          */
284         public function hasTasksLeft () {
285                 // Do we have tasks there?
286                 $tasksLeft = (($this->getListInstance() instanceof Listable) && ($this->getListInstance()->count() > 0));
287
288                 // Return result
289                 return $tasksLeft;
290         }
291
292         /**
293          * Handles all tasks by checking if they should startup or if it is their
294          * turn to run. You should use this method in a while() loop in conjuntion
295          * with hasTasksLeft() so you can e.g. shutdown by adding a ShutdownTask
296          * which will attempt to remove all tasks from the task handler.
297          *
298          * @return      void
299          */
300         public function handleTasks () {
301                 // Should we rewind?
302                 if (!$this->getListInstance()->getIterator()->valid()) {
303                         // Rewind to the beginning for next loop
304                         $this->getListInstance()->getIterator()->rewind();
305                 } // END - if
306
307                 // Try to execute the task
308                 $this->executeCurrentTask();
309
310                 // Go to next entry
311                 $this->getListInstance()->getIterator()->next();
312         }
313
314         /**
315          * Shuts down all tasks and the task handler itself. This method should be
316          * called from a corresponding filter class.
317          * 
318          * @return      void
319          */
320         public function doShutdown () {
321                 // Always rewind to the beginning for next loop
322                 $this->getListInstance()->getIterator()->rewind();
323
324                 // Debug message
325                 self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('TASK-HANDLER[' . __METHOD__ . ':' . __LINE__ . ']: Shutting down all ' . $this->getListInstance()->count() . ' tasks...');
326
327                 // Remember all tasks that has been shutdown for removal
328                 $tasks = array();
329
330                 // Instance a visitor
331                 $this->setVisitorInstance(ObjectFactory::createObjectByConfiguredName('shutdown_task_visitor_class'));
332
333                 // Shutdown all tasks in once go
334                 while ($this->getListInstance()->getIterator()->valid()) {
335                         // Get current entry
336                         $currentTask = $this->getListInstance()->getIterator()->current();
337
338                         // Output debug message
339                         self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('TASK-HANDLER[' . __METHOD__ . ':' . __LINE__ . ']: Shutting down task ' . $currentTask['id'] . ' (taskInstance=' . $currentTask['task_instance']->__toString() . ') ...');
340
341                         // Shutdown the task
342                         $currentTask['task_instance']->accept($this->getVisitorInstance());
343
344                         // Remember this task
345                         array_push($tasks, $currentTask);
346
347                         // Advance to next one
348                         $this->getListInstance()->getIterator()->next();
349                 } // END - while
350
351                 // Debug message
352                 self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('TASK-HANDLER[' . __METHOD__ . ':' . __LINE__ . ']: Shutdown of all tasks completed.');
353
354                 // Remove all tasks
355                 foreach ($tasks as $entry) {
356                         $this->unregisterTask($entry);
357                 } // END - foreach
358         }
359
360 }