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