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