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