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