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