* @version 0.0.0 * @copyright Copyright (c) 2007, 2008 Roland Haeder, 2009 - 2015 Core Developer Team * @license GNU GPL 3.0 or any newer version * @link http://www.shipsimu.org * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ class TaskHandler extends BaseHandler implements Registerable, HandleableTask { // Exception constants const EXCEPTION_TASK_IS_INVALID = 0xb00; /** * Protected constructor * * @return void */ protected function __construct () { // Call parent constructor parent::__construct(__CLASS__); // Set handler name $this->setHandlerName('task'); } /** * Creates an instance of this class * * @return $handlerInstance An instance of a HandleableTask class */ public static final function createTaskHandler () { // Get new instance $handlerInstance = new TaskHandler(); // Output debug message self::createDebugInstance(__CLASS__)->debugOutput('TASK-HANDLER[' . __METHOD__ . ':' . __LINE__ . ']: Initializing task handler.'); // Init the task list $handlerInstance->setListInstance(ObjectFactory::createObjectByConfiguredName('task_list_class')); // Get default instance $handlerInstance->setIteratorInstance($handlerInstance->getListInstance()->getIterator()); // Init visitor instance for faster loop $handlerInstance->setVisitorInstance(ObjectFactory::createObjectByConfiguredName('active_task_visitor_class')); // Register the first (and generic) idle-loop task $taskInstance = ObjectFactory::createObjectByConfiguredName('idle_task_class'); $handlerInstance->registerTask('idle_loop', $taskInstance); // Output debug message self::createDebugInstance(__CLASS__)->debugOutput('TASK-HANDLER[' . __METHOD__ . ':' . __LINE__ . ']: Task handler initialized.'); // Return the prepared instance return $handlerInstance; } /** * Tries to execute the given task. If as task should not be started (yet) * or the interval time (see task_interval_delay) is not yet reached the * task is quietly skipped. * * @return void * @throws InvalidTaskException If the current task is invalid */ private function executeCurrentTask () { // Update no task by default $updateTask = FALSE; // Is the current task valid? if (!$this->getListInstance()->getIterator()->valid()) { // Not valid! throw new InvalidTaskException($this, self::EXCEPTION_TASK_IS_INVALID); } // END - if // Get current task $currentTask = $this->getListInstance()->getIterator()->current(); // Is the task not yet started? if ($currentTask['task_started'] === FALSE) { // Determine difference between current time and registration $diff = ($this->getMilliTime() - $currentTask['task_registered']) * 1000; // Should we start now? if ($diff < $currentTask['task_startup_delay']) { // Skip this silently //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('TASK-HANDLER[' . __METHOD__ . ':' . __LINE__ . ']: Task ' . $currentTask['id'] . ' not started: diff=' . $diff . ',task_startup_delay=' . $currentTask['task_startup_delay']); return; } // END - if // Launch the task and mark it as updated $currentTask['task_started'] = TRUE; $updateTask = TRUE; // Debug message self::createDebugInstance(__CLASS__)->debugOutput('TASK-HANDLER[' . __METHOD__ . ':' . __LINE__ . ']: Task ' . $currentTask['id'] . ' started with startup_delay=' . $currentTask['task_startup_delay'] . 'ms'); } // END - if // Get time difference from interval delay $diff = ($this->getMilliTime() - $currentTask['task_last_activity']) * 1000; // Debug message //* 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']); // Is the interval delay reached? if ((($diff < $currentTask['task_interval_delay']) && ($currentTask['task_max_runs'] == 0)) || (($currentTask['task_max_runs'] > 0) && ($currentTask['task_total_runs'] == $currentTask['task_max_runs']))) { // Should we update the task from startup? if ($updateTask === TRUE) { // Update the task before leaving $this->updateTask($currentTask); } // END - if // Skip this silently return; } // END - if // Set last activity $currentTask['task_last_activity'] = $this->getMilliTime(); // Count this run $currentTask['task_total_runs']++; // Update the task $this->updateTask($currentTask); // And visit/run it // @TODO Messurement can be added around this call $currentTask['task_instance']->accept($this->getVisitorInstance()); } /** * Updates given task by updating the underlaying list * * @param $taskEntry An array with a task * @return void */ private function updateTask (array $taskEntry) { // Get the key from current iteration $key = $this->getListInstance()->getIterator()->key(); // Get the hash from key $hash = $this->getListInstance()->getHash($key); // Update the entry $this->getListInstance()->updateCurrentEntryByHash($hash, $taskEntry); } /** * Unregisters the given task * * @param $taskData Data of the task * @return void */ private function unregisterTask (array $taskData) { // Debug output //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('TASK-HANDLER[' . __METHOD__ . ':' . __LINE__ . ']: Removing task ' . $taskData['id'] . ' from queue - CALLED!'); // Remove the entry $this->getListInstance()->removeEntry('tasks', $taskData); // Debug output //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('TASK-HANDLER[' . __METHOD__ . ':' . __LINE__ . ']: Removing task ' . $taskData['id'] . ' from queue - EXIT!'); } /** * Searches a task by given instance * * @param $taskInstanc An instanceof a Taskable class * @return $taskName Name of the task as used while registration */ public function searchTask (Taskable $taskInstance) { // Default is an empty (not found) task name $taskName = ''; // Get whole list $taskList = $this->getListInstance()->getArrayFromList('tasks'); // Search all instances foreach ($taskList as $currentTask) { // Does it match given task instance? if ($currentTask['task_instance']->equals($taskInstance)) { // Found it $taskName = $currentTask['id']; // Abort here break; } // END - if } // END - foreach // Return found name return $taskName; } /** * Registers a task with a task handler. * * @param $taskName A task name to register the task on * @param $taskInstance The instance we should register as a task * @return void */ public function registerTask ($taskName, Visitable $taskInstance) { // Get interval delay $intervalDelay = $this->getConfigInstance()->getConfigEntry('task_' . $taskName . '_interval_delay'); $startupDelay = $this->getConfigInstance()->getConfigEntry('task_' . $taskName . '_startup_delay'); // If the task is 'idle_loop', a deplay of zero seconds is fine assert($intervalDelay >= 0); assert(($taskName != 'idle_loop') && ($intervalDelay > 0)); assert(($taskName != 'idle_loop') && ($startupDelay > 0)); // Create the entry $taskEntry = array( // Identifier for the generateHash() method 'id' => $taskName, // Whether the task is started 'task_started' => FALSE, // Whether the task is paused (not yet implemented) 'task_paused' => FALSE, // Whether the task can be paused (not yet implemented) 'task_pauseable' => TRUE, // Timestamp of registration 'task_registered' => $this->getMilliTime(), // Last activity timestamp 'task_last_activity' => 0, // Total runs of this task 'task_total_runs' => 0, // Task instance itself 'task_instance' => $taskInstance, // Startup delay in milliseconds 'task_startup_delay' => $startupDelay, // Interval time (delay) in milliseconds before this task is executed again 'task_interval_delay' => $intervalDelay, // How often should this task run? 'task_max_runs' => $this->getConfigInstance()->getConfigEntry('task_' . $taskName . '_max_runs'), ); // Add the entry $this->getListInstance()->addEntry('tasks', $taskEntry); // Debug message self::createDebugInstance(__CLASS__)->debugOutput('TASK-HANDLER[' . __METHOD__ . ':' . __LINE__ . ']: Task registered: taskName=' . $taskName . ' (taskInstance=' . $taskInstance->__toString() . ')' . ', startupDelay=' . $taskEntry['task_startup_delay'] . 'ms' . ', intervalDelay=' . $taskEntry['task_interval_delay'] . 'ms' . ', maxRuns=' . $taskEntry['task_max_runs'] . ' ...' ); } /** * Checks whether tasks are left including idle task * * @return $tasksLeft Whether there are tasks left to handle */ public function hasTasksLeft () { // Do we have tasks there? $tasksLeft = (($this->getListInstance() instanceof Listable) && ($this->getListInstance()->count() > 0)); // Return result return $tasksLeft; } /** * Handles all tasks by checking if they should startup or if it is their * turn to run. You should use this method in a while() loop in conjuntion * with hasTasksLeft() so you can e.g. shutdown by adding a ShutdownTask * which will attempt to remove all tasks from the task handler. * * @return void */ public function handleTasks () { // Should we rewind? if (!$this->getListInstance()->getIterator()->valid()) { // Rewind to the beginning for next loop $this->getListInstance()->getIterator()->rewind(); } // END - if // Try to execute the task $this->executeCurrentTask(); // Go to next entry $this->getListInstance()->getIterator()->next(); } /** * Shuts down all tasks and the task handler itself. This method should be * called from a corresponding filter class. * * @return void */ public function doShutdown () { // Always rewind to the beginning for next loop $this->getListInstance()->getIterator()->rewind(); // Debug message self::createDebugInstance(__CLASS__)->debugOutput('TASK-HANDLER[' . __METHOD__ . ':' . __LINE__ . ']: Shutting down all ' . $this->getListInstance()->count() . ' tasks...'); // Remember all tasks that has been shutdown for removal $tasks = array(); // Instance a visitor $this->setVisitorInstance(ObjectFactory::createObjectByConfiguredName('shutdown_task_visitor_class')); // Shutdown all tasks in once go while ($this->getListInstance()->getIterator()->valid()) { // Get current entry $currentTask = $this->getListInstance()->getIterator()->current(); // Output debug message self::createDebugInstance(__CLASS__)->debugOutput('TASK-HANDLER[' . __METHOD__ . ':' . __LINE__ . ']: Shutting down task ' . $currentTask['id'] . ' (taskInstance=' . $currentTask['task_instance']->__toString() . ') ...'); // Shutdown the task $currentTask['task_instance']->accept($this->getVisitorInstance()); // Remember this task array_push($tasks, $currentTask); // Advance to next one $this->getListInstance()->getIterator()->next(); } // END - while // Debug message self::createDebugInstance(__CLASS__)->debugOutput('TASK-HANDLER[' . __METHOD__ . ':' . __LINE__ . ']: Shutdown of all tasks completed.'); // Remove all tasks foreach ($tasks as $entry) { $this->unregisterTask($entry); } // END - foreach } } // [EOF] ?>