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